diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 2c7aa67182..091f8ca1d4 100755 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -75,6 +75,10 @@ QT_FORMS_UI = \ qt/forms/aboutdialog.ui \ qt/forms/coincontroldialog.ui \ qt/forms/consolidateunspentdialog.ui \ + qt/forms/consolidateunspentwizard.ui \ + qt/forms/consolidateunspentwizardselectdestinationpage.ui \ + qt/forms/consolidateunspentwizardselectinputspage.ui \ + qt/forms/consolidateunspentwizardsendpage.ui \ qt/forms/diagnosticsdialog.ui \ qt/forms/optionsdialog.ui \ qt/forms/rpcconsole.ui \ @@ -113,6 +117,10 @@ QT_MOC_CPP = \ qt/moc_coincontroldialog.cpp \ qt/moc_coincontroltreewidget.cpp \ qt/moc_consolidateunspentdialog.cpp \ + qt/moc_consolidateunspentwizard.cpp \ + qt/moc_consolidateunspentwizardselectdestinationpage.cpp \ + qt/moc_consolidateunspentwizardselectinputspage.cpp \ + qt/moc_consolidateunspentwizardsendpage.cpp \ qt/moc_csvmodelwriter.cpp \ qt/moc_diagnosticsdialog.cpp \ qt/moc_editaddressdialog.cpp \ @@ -184,6 +192,10 @@ GRIDCOINRESEARCH_QT_H = \ qt/coincontroldialog.h \ qt/coincontroltreewidget.h \ qt/consolidateunspentdialog.h \ + qt/consolidateunspentwizard.h \ + qt/consolidateunspentwizardselectdestinationpage.h \ + qt/consolidateunspentwizardselectinputspage.h \ + qt/consolidateunspentwizardsendpage.h \ qt/csvmodelwriter.h \ qt/decoration.h \ qt/diagnosticsdialog.h \ @@ -247,6 +259,10 @@ GRIDCOINRESEARCH_QT_CPP = \ qt/coincontroldialog.cpp \ qt/coincontroltreewidget.cpp \ qt/consolidateunspentdialog.cpp \ + qt/consolidateunspentwizard.cpp \ + qt/consolidateunspentwizardselectdestinationpage.cpp \ + qt/consolidateunspentwizardselectinputspage.cpp \ + qt/consolidateunspentwizardsendpage.cpp \ qt/csvmodelwriter.cpp \ qt/decoration.cpp \ qt/diagnosticsdialog.cpp \ diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index dd605f2104..f92174bf6a 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -25,13 +25,13 @@ #include using namespace std; -QList CoinControlDialog::payAmounts; -CCoinControl* CoinControlDialog::coinControl = new CCoinControl(); -CoinControlDialog::CoinControlDialog(QWidget *parent) : +CoinControlDialog::CoinControlDialog(QWidget *parent, CCoinControl *coinControl, QList *payAmounts) : QDialog(parent), m_inputSelectionLimit(GetMaxInputsForConsolidationTxn()), ui(new Ui::CoinControlDialog), + coinControl(coinControl), + payAmounts(payAmounts), model(0) { ui->setupUi(this); @@ -158,7 +158,7 @@ void CoinControlDialog::setModel(WalletModel *model) { updateView(); //updateLabelLocked(); - CoinControlDialog::updateLabels(model, this); + CoinControlDialog::updateLabels(model, coinControl, payAmounts, this); } } @@ -226,7 +226,7 @@ void CoinControlDialog::buttonSelectAllClicked() ui->selectAllPushButton->setText("Select None"); } - CoinControlDialog::updateLabels(model, this); + CoinControlDialog::updateLabels(model, coinControl, payAmounts, this); showHideConsolidationReadyToSend(); } @@ -353,7 +353,7 @@ bool CoinControlDialog::filterInputsByValue(const bool& less, const CAmount& inp // Reenable update signals. ui->treeWidget->setEnabled(true); - CoinControlDialog::updateLabels(model, this); + CoinControlDialog::updateLabels(model, coinControl, payAmounts, this); // If the number of inputs selected was limited, then true is returned. return culled_inputs; @@ -596,7 +596,9 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) // selection changed -> update labels if (ui->treeWidget->isEnabled()) // do not update on every click for (un)select all - CoinControlDialog::updateLabels(model, this); + { + CoinControlDialog::updateLabels(model, coinControl, payAmounts, this); + } } showHideConsolidationReadyToSend(); @@ -633,7 +635,10 @@ QString CoinControlDialog::getPriorityLabel(double dPriority) else ui->labelLocked->setVisible(false); }*/ -void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) +void CoinControlDialog::updateLabels(WalletModel *model, + CCoinControl *coinControl, + QList* payAmounts, + QDialog* dialog) { if (!model) return; @@ -642,7 +647,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) bool fLowOutput = false; bool fDust = false; CTransaction txDummy; - foreach(const qint64 &amount, CoinControlDialog::payAmounts) + foreach(const qint64 &amount, *payAmounts) { nPayAmount += amount; @@ -704,7 +709,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) if (nQuantity > 0) { // Bytes - nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here + nBytes = nBytesInputs + ((payAmounts->size() > 0 ? payAmounts->size() + 1 : 2) * 34) + 10; // always assume +1 output for change here // Priority dPriority = dPriorityInputs / nBytes; diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h index b948181bef..1e511936d9 100644 --- a/src/qt/coincontroldialog.h +++ b/src/qt/coincontroldialog.h @@ -24,17 +24,19 @@ class CoinControlDialog : public QDialog Q_OBJECT public: - explicit CoinControlDialog(QWidget *parent = 0); + explicit CoinControlDialog(QWidget *parent = 0, + CCoinControl *coinControl = nullptr, + QList *payAmounts = nullptr); ~CoinControlDialog(); void setModel(WalletModel *model); // static because also called from sendcoinsdialog - static void updateLabels(WalletModel*, QDialog*); + static void updateLabels(WalletModel*, CCoinControl*, QList*, QDialog*); static QString getPriorityLabel(double); - static QList payAmounts; - static CCoinControl *coinControl; + //static QList payAmounts; + //static CCoinControl *coinControl; // This is based on what will guarantee a successful transaction. const size_t m_inputSelectionLimit; @@ -47,6 +49,8 @@ public slots: private: Ui::CoinControlDialog *ui; + CCoinControl *coinControl; + QList *payAmounts; WalletModel *model; int sortColumn; Qt::SortOrder sortOrder; diff --git a/src/qt/consolidateunspentdialog.h b/src/qt/consolidateunspentdialog.h index 440e4f160b..652777b165 100644 --- a/src/qt/consolidateunspentdialog.h +++ b/src/qt/consolidateunspentdialog.h @@ -3,7 +3,9 @@ #include #include +#include #include +#include namespace Ui { class ConsolidateUnspentDialog; @@ -14,7 +16,7 @@ class ConsolidateUnspentDialog : public QDialog Q_OBJECT public: - explicit ConsolidateUnspentDialog(QWidget *parent = 0, size_t inputSelectionLimit = 600); + explicit ConsolidateUnspentDialog(QWidget *parent = nullptr, size_t inputSelectionLimit = 600); ~ConsolidateUnspentDialog(); void SetAddressList(const std::map& addressList); diff --git a/src/qt/consolidateunspentwizard.cpp b/src/qt/consolidateunspentwizard.cpp new file mode 100644 index 0000000000..9afe177fea --- /dev/null +++ b/src/qt/consolidateunspentwizard.cpp @@ -0,0 +1,59 @@ +#include "coincontroldialog.h" +#include "consolidateunspentwizard.h" +#include "consolidateunspentdialog.h" +#include "ui_consolidateunspentwizard.h" + +#include "util.h" + + +ConsolidateUnspentWizard::ConsolidateUnspentWizard(QWidget *parent, + CCoinControl *coinControl, + QList *payAmounts, + size_t inputSelectionLimit) : + QWizard(parent), + ui(new Ui::ConsolidateUnspentWizard), + coinControl(coinControl), + payAmounts(payAmounts), + m_inputSelectionLimit(inputSelectionLimit) +{ + ui->setupUi(this); + this->setStartId(SelectInputsPage); + + ui->selectInputsPage->setCoinControl(coinControl); + ui->selectInputsPage->setPayAmounts(payAmounts); + + connect(this, SIGNAL(setModelSignal(WalletModel*)), ui->selectInputsPage, SLOT(setModel(WalletModel*))); + connect(this, SIGNAL(setModelSignal(WalletModel*)), ui->sendPage, SLOT(setModel(WalletModel*))); + + connect(ui->selectInputsPage, SIGNAL(setAddressListSignal(std::map)), + ui->selectDestinationPage, SLOT(SetAddressList(const std::map))); + + connect(ui->selectInputsPage, SIGNAL(setDefaultAddressSignal(QString)), + ui->selectDestinationPage, SLOT(setDefaultAddressSelection(QString))); + + connect(this->button(QWizard::FinishButton), SIGNAL(clicked()), ui->sendPage, SLOT(onFinishButtonClicked())); + connect(ui->sendPage, SIGNAL(selectedConsolidationRecipientSignal(SendCoinsRecipient)), + this, SIGNAL(selectedConsolidationRecipientSignal(SendCoinsRecipient))); +} + +ConsolidateUnspentWizard::~ConsolidateUnspentWizard() +{ + delete ui; +} + +void ConsolidateUnspentWizard::accept() +{ + QDialog::accept(); + //emit sendConsolidationTransactionSignal(); +} + +void ConsolidateUnspentWizard::setModel(WalletModel *model) +{ + this->model = model; + emit setModelSignal(model); +} + +WalletModel* ConsolidateUnspentWizard::getModel() +{ + return this->model; +} diff --git a/src/qt/consolidateunspentwizard.h b/src/qt/consolidateunspentwizard.h new file mode 100644 index 0000000000..a6142c7cda --- /dev/null +++ b/src/qt/consolidateunspentwizard.h @@ -0,0 +1,56 @@ +#ifndef CONSOLIDATEUNSPENTWIZARD_H +#define CONSOLIDATEUNSPENTWIZARD_H + +#include "walletmodel.h" + +#include +#include +#include +#include +#include + +namespace Ui { + class ConsolidateUnspentWizard; +} + +class CoinControlDialog; + +class ConsolidateUnspentWizard : public QWizard +{ + Q_OBJECT + +public: + enum Pages + { + SelectInputsPage, + SelectDestinationPage, + SendPage + }; + + explicit ConsolidateUnspentWizard(QWidget *parent = nullptr, + CCoinControl *coinControl = nullptr, + QList *payAmounts = nullptr, + size_t inputSelectionLimit = 600); + ~ConsolidateUnspentWizard(); + + void setModel(WalletModel *model); + WalletModel* getModel(); + + void accept() override; + +signals: + void setModelSignal(WalletModel*); + void passCoinControlSignal(CCoinControl*); + void selectedConsolidationRecipientSignal(SendCoinsRecipient); + void sendConsolidationTransactionSignal(); + +private: + Ui::ConsolidateUnspentWizard *ui; + CCoinControl *coinControl; + QList *payAmounts; + WalletModel *model; + + size_t m_inputSelectionLimit; +}; + +#endif // CONSOLIDATEUNSPENTWIZARD_H diff --git a/src/qt/consolidateunspentwizardselectdestinationpage.cpp b/src/qt/consolidateunspentwizardselectdestinationpage.cpp new file mode 100644 index 0000000000..c4a14c226e --- /dev/null +++ b/src/qt/consolidateunspentwizardselectdestinationpage.cpp @@ -0,0 +1,124 @@ +#include "consolidateunspentwizardselectdestinationpage.h" +#include "ui_consolidateunspentwizardselectdestinationpage.h" + +#include "util.h" + +ConsolidateUnspentWizardSelectDestinationPage::ConsolidateUnspentWizardSelectDestinationPage(QWidget *parent) : + QWizardPage(parent), + ui(new Ui::ConsolidateUnspentWizardSelectDestinationPage) +{ + ui->setupUi(this); + + QStringList headerLabels; + + // This should not be necessary because this is defined in the .ui file but for some reason only numbers + // are showing up without it. + headerLabels << tr("Label") << tr("Address"); + ui->addressTableWidget->setHorizontalHeaderLabels(headerLabels); + + ui->addressTableWidget->setSelectionMode(QAbstractItemView::SingleSelection); + + // destination address selection + connect(ui->addressTableWidget, SIGNAL(itemSelectionChanged()), this, SLOT(addressSelectionChanged())); + + ui->isCompleteCheckBox->hide(); + + // This is to provide a convenient way to populate the fields shown on the last page ("send" screen). + registerField("selectedAddressLabelField", ui->selectedAddressLabel, "text", "updateFieldsSignal()"); + registerField("selectedAddressField", ui->selectedAddress, "text", "updateFieldsSignal()"); + + //This is used to control the disable/enable of the next button on this page. + registerField("isCompleteSelectDestination*", ui->isCompleteCheckBox); +} + +ConsolidateUnspentWizardSelectDestinationPage::~ConsolidateUnspentWizardSelectDestinationPage() +{ + delete ui; +} + +// ----------------------------------------------------------------------------- address - label +void ConsolidateUnspentWizardSelectDestinationPage::SetAddressList(const std::map addressList) +{ + ui->addressTableWidget->setSortingEnabled(false); + + ui->addressTableWidget->clear(); + + int row = 0; + for (const auto& iter : addressList) + { + ui->addressTableWidget->insertRow(row); + + QTableWidgetItem* label = new QTableWidgetItem(iter.second); + QTableWidgetItem* address = new QTableWidgetItem(iter.first); + + if (label != nullptr) ui->addressTableWidget->setItem(row, 0, label); + if (address != nullptr) ui->addressTableWidget->setItem(row, 1, address); + + ++row; + } +} + +void ConsolidateUnspentWizardSelectDestinationPage::setDefaultAddressSelection(QString address) +{ + if (!address.size()) + { + ui->addressTableWidget->clearSelection(); + + LogPrint(BCLog::LogFlags::QT, "INFO: %s: Cleared (default) address selection", __func__); + + ui->isCompleteCheckBox->setChecked(false); + + return; + } + + QList defaultAddress = ui->addressTableWidget->findItems(address, Qt::MatchExactly); + + defaultAddress[0]->setSelected(true); + + LogPrint(BCLog::LogFlags::QT, "INFO: %s: Set default address to %s, QTableWidgetItem %s", + __func__, + address.toStdString(), + defaultAddress[0]->text().toStdString()); + + ui->addressTableWidget->setCurrentItem(defaultAddress[0]); + + LogPrintf("INFO: %s: currentRow = %i", __func__, ui->addressTableWidget->currentRow()); + + emit updateFieldsSignal(); +} + +void ConsolidateUnspentWizardSelectDestinationPage::addressSelectionChanged() +{ + + if (!ui->addressTableWidget->selectedItems().size()) + { + ui->selectedAddressLabel->setText(QString()); + ui->selectedAddress->setText(QString()); + + ui->isCompleteCheckBox->setChecked(false); + + return; + } + + ui->addressTableWidget->selectedItems()[0]->row(); + + int selectedRow = ui->addressTableWidget->selectedItems()[0]->row(); + + if (selectedRow < 0) return; + + QTableWidgetItem* selectedLabel = ui->addressTableWidget->item(selectedRow, 0); + QTableWidgetItem* selectedAddress = ui->addressTableWidget->item(selectedRow, 1); + + ui->selectedAddressLabel->setText(selectedLabel->text()); + ui->selectedAddress->setText(selectedAddress->text()); + + m_selectedDestinationAddress = std::make_pair(selectedLabel->text(), selectedAddress->text()); + + LogPrint(BCLog::LogFlags::QT, "INFO: %s: Label %, Address %s selected.", __func__, + m_selectedDestinationAddress.first.toStdString(), + m_selectedDestinationAddress.second.toStdString()); + + ui->isCompleteCheckBox->setChecked(true); + + emit updateFieldsSignal(); +} diff --git a/src/qt/consolidateunspentwizardselectdestinationpage.h b/src/qt/consolidateunspentwizardselectdestinationpage.h new file mode 100644 index 0000000000..62bf1e8dee --- /dev/null +++ b/src/qt/consolidateunspentwizardselectdestinationpage.h @@ -0,0 +1,34 @@ +#ifndef CONSOLIDATEUNSPENTWIZARDSELECTDESTINATIONPAGE_H +#define CONSOLIDATEUNSPENTWIZARDSELECTDESTINATIONPAGE_H + +#include + +namespace Ui { + class ConsolidateUnspentWizardSelectDestinationPage; +} + +class ConsolidateUnspentWizardSelectDestinationPage : public QWizardPage +{ + Q_OBJECT + +public: + explicit ConsolidateUnspentWizardSelectDestinationPage(QWidget *parent = nullptr); + ~ConsolidateUnspentWizardSelectDestinationPage(); + +signals: + void updateFieldsSignal(); + +public slots: + void SetAddressList(const std::map addressList); + void setDefaultAddressSelection(QString address); + +private: + Ui::ConsolidateUnspentWizardSelectDestinationPage *ui; + + std::pair m_selectedDestinationAddress; + +private slots: + void addressSelectionChanged(); +}; + +#endif // CONSOLIDATEUNSPENTWIZARDSELECTDESTINATIONPAGE_H diff --git a/src/qt/consolidateunspentwizardselectinputspage.cpp b/src/qt/consolidateunspentwizardselectinputspage.cpp new file mode 100644 index 0000000000..0f61295a38 --- /dev/null +++ b/src/qt/consolidateunspentwizardselectinputspage.cpp @@ -0,0 +1,719 @@ +#include "coincontroldialog.h" +#include "consolidateunspentwizardselectinputspage.h" +#include "ui_consolidateunspentwizardselectinputspage.h" + +#include "init.h" +#include "bitcoinunits.h" +#include "addresstablemodel.h" +#include "optionsmodel.h" +#include "policy/policy.h" +#include "policy/fees.h" +#include "validation.h" +#include "wallet/coincontrol.h" +#include "consolidateunspentdialog.h" + +using namespace std; + +ConsolidateUnspentWizardSelectInputsPage::ConsolidateUnspentWizardSelectInputsPage(QWidget *parent) : + QWizardPage(parent), + ui(new Ui::ConsolidateUnspentWizardSelectInputsPage) +{ + m_InputSelectionLimit = GetMaxInputsForConsolidationTxn(); + + ui->setupUi(this); + + // toggle tree/list mode + connect(ui->treeModeRadioButton, SIGNAL(toggled(bool)), this, SLOT(treeModeRadioButton(bool))); + connect(ui->listModeRadioButton, SIGNAL(toggled(bool)), this, SLOT(listModeRadioButton(bool))); + + // click on checkbox + connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(viewItemChanged(QTreeWidgetItem*, int))); + + // click on header + ui->treeWidget->header()->setSectionsClickable(true); + connect(ui->treeWidget->header(), SIGNAL(sectionClicked(int)), this, SLOT(headerSectionClicked(int))); + + // (un)select all + connect(ui->selectAllPushButton, SIGNAL(clicked()), this, SLOT(buttonSelectAllClicked())); + + // filter/consolidate button interaction + connect(ui->maxMinOutputValue, SIGNAL(textChanged()), this, SLOT(maxMinOutputValueChanged())); + + // filter mode + connect(ui->filterModePushButton, SIGNAL(clicked()), this, SLOT(buttonFilterModeClicked())); + + // filter + connect(ui->filterPushButton, SIGNAL(clicked()), this, SLOT(buttonFilterClicked())); + + ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 150); + ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 170); + ui->treeWidget->setColumnWidth(COLUMN_LABEL, 200); + ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 290); + ui->treeWidget->setColumnWidth(COLUMN_DATE, 110); + ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 100); + ui->treeWidget->setColumnWidth(COLUMN_PRIORITY, 100); + ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store transacton hash in this column, but don't show it + ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // store vout index in this column, but don't show it + ui->treeWidget->setColumnHidden(COLUMN_AMOUNT_INT64, true); // store amount int64_t in this column, but don't show it + ui->treeWidget->setColumnHidden(COLUMN_PRIORITY_INT64, true); // store priority int64_t in this column, but don't show it + ui->treeWidget->setColumnHidden(COLUMN_CHANGE_BOOL, true); // store change flag but don't show it + + // This is to provide a convenient way to populate the fields shown on the last page ("send" screen). + registerField("quantityField", ui->quantityLabel, "text", "updateFieldsSignal()"); + registerField("feeField", ui->feeLabel, "text", "updateFieldsSignal()"); + registerField("afterFeeAmountField", ui->afterFeeLabel, "text", "updateFieldsSignal()"); + + //This is used to control the disable/enable of the next button on this page. + registerField("isCompleteSelectInputs*", ui->isCompleteCheckBox); + + // default view is sorted by amount desc + sortView(COLUMN_AMOUNT_INT64, Qt::DescendingOrder); + + ui->outputLimitWarningIconLabel->setToolTip(tr("Note: The number of inputs selected for consolidation has been " + "limited to %1 to prevent a transaction failure due to too many " + "inputs.").arg(m_InputSelectionLimit)); + ui->outputLimitStopIconLabel->setToolTip(tr("Note: The number of inputs selected for consolidation is currently more " + "than the limit of %1. Please use the filter or manual selection to reduce " + "the number of inputs to %1 or less to prevent a transaction failure due to " + "too many inputs.").arg(m_InputSelectionLimit)); + + ui->outputLimitWarningIconLabel->setVisible(false); + ui->outputLimitStopIconLabel->setVisible(false); + + ui->isCompleteCheckBox->hide(); +} + +ConsolidateUnspentWizardSelectInputsPage::~ConsolidateUnspentWizardSelectInputsPage() +{ + delete ui; +} + +void ConsolidateUnspentWizardSelectInputsPage::setModel(WalletModel *model) +{ + this->model = model; + + if (model && model->getOptionsModel() && model->getAddressTableModel() && coinControl != nullptr) + { + updateView(); + updateLabels(); + } +} + +void ConsolidateUnspentWizardSelectInputsPage::setCoinControl(CCoinControl *coinControl) +{ + this->coinControl = coinControl; +} + +void ConsolidateUnspentWizardSelectInputsPage::setPayAmounts(QList *payAmounts) +{ + this->payAmounts = payAmounts; +} + +// helper function str_pad +QString ConsolidateUnspentWizardSelectInputsPage::strPad(QString s, int nPadLength, QString sPadding) +{ + while (s.length() < nPadLength) + s = sPadding + s; + + return s; +} + +// (un)select all +void ConsolidateUnspentWizardSelectInputsPage::buttonSelectAllClicked() +{ + m_InputSelectionLimitedByFilter = false; + + ui->treeWidget->setEnabled(false); + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != m_ToState) + ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, m_ToState); + ui->treeWidget->setEnabled(true); + + if (m_ToState == Qt::Checked) + { + m_ToState = Qt::Unchecked; + } + else + { + m_ToState = Qt::Checked; + } + + if (m_ToState == Qt::Checked) + { + ui->selectAllPushButton->setText("Select All"); + } + else + { + ui->selectAllPushButton->setText("Select None"); + } + + updateLabels(); +} + +void ConsolidateUnspentWizardSelectInputsPage::maxMinOutputValueChanged() +{ + ui->maxMinOutputValue->value(&m_FilterValueValid); +} + +void ConsolidateUnspentWizardSelectInputsPage::buttonFilterModeClicked() +{ + if (m_FilterMode) + { + m_FilterMode = false; + ui->filterModePushButton->setText(">="); + } + else + { + m_FilterMode = true; + ui->filterModePushButton->setText("<="); + } +} + +void ConsolidateUnspentWizardSelectInputsPage::buttonFilterClicked() +{ + m_ViewItemsChangedViaFilter = true; + + m_InputSelectionLimitedByFilter = filterInputsByValue(m_FilterMode, ui->maxMinOutputValue->value(), m_InputSelectionLimit); + + updateLabels(); + + m_ViewItemsChangedViaFilter = false; +} + +bool ConsolidateUnspentWizardSelectInputsPage::filterInputsByValue(const bool& less, const CAmount& inputFilterValue, + const unsigned int& inputSelectionLimit) +{ + + // Disable generating update signals unnecessarily during this filter operation. + ui->treeWidget->setEnabled(false); + + QTreeWidgetItemIterator iter(ui->treeWidget); + + // If less is true, then we are choosing the smallest inputs upward, and so the map comparator needs to be "less than". + // If less is false, then we are choosing the largest inputs downward, and so the map comparator needs to be "greater + // than". + auto comp = [less](CAmount a, CAmount b) + { + if (less) + { + return (a < b); + } + else + { + return (a > b); + } + }; + + std::multimap, decltype(comp)> input_map(comp); + + bool culled_inputs = false; + + while (*iter) + { + CAmount input_value = (*iter)->text(COLUMN_AMOUNT_INT64).toLongLong(); + COutPoint outpoint(uint256S((*iter)->text(COLUMN_TXHASH).toStdString()), (*iter)->text(COLUMN_VOUT_INDEX).toUInt()); + + if ((*iter)->checkState(COLUMN_CHECKBOX) == Qt::Checked) + { + if ((*iter)->text(COLUMN_TXHASH).length() == 64) + { + if ((less && input_value <= inputFilterValue) || (!less && input_value >= inputFilterValue)) + { + input_map.insert(std::make_pair(input_value, std::make_pair(*iter, outpoint))); + } + else + { + (*iter)->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + coinControl->UnSelect(outpoint); + } + } + } + + ++iter; + } + + // The second loop is to limit the number of selected outputs to the inputCountLimit. + unsigned int input_count = 0; + + for (auto& input : input_map) + { + if (input_count >= inputSelectionLimit) + { + LogPrint(BCLog::LogFlags::QT, "INFO: %s: Culled input %u with value %f.", + __func__, input_count, (double) input.first / COIN); + + if (coinControl->IsSelected(input.second.second.hash, input.second.second.n)) + { + input.second.first->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + + culled_inputs = true; + coinControl->UnSelect(input.second.second); + } + } + + ++input_count; + } + + // Reenable update signals. + ui->treeWidget->setEnabled(true); + + // If the number of inputs selected was limited, then true is returned. + return culled_inputs; +} + +// treeview: sort +void ConsolidateUnspentWizardSelectInputsPage::sortView(int column, Qt::SortOrder order) +{ + sortColumn = column; + sortOrder = order; + ui->treeWidget->sortItems(column, order); + ui->treeWidget->header()->setSortIndicator((sortColumn == COLUMN_AMOUNT_INT64 ? + COLUMN_AMOUNT : (sortColumn == COLUMN_PRIORITY_INT64 ? + COLUMN_PRIORITY : sortColumn)), + sortOrder); +} + +// treeview: clicked on header +void ConsolidateUnspentWizardSelectInputsPage::headerSectionClicked(int logicalIndex) +{ + if (logicalIndex == COLUMN_CHECKBOX) // click on most left column -> do nothing + { + ui->treeWidget->header()->setSortIndicator((sortColumn == COLUMN_AMOUNT_INT64 ? + COLUMN_AMOUNT : (sortColumn == COLUMN_PRIORITY_INT64 ? + COLUMN_PRIORITY : sortColumn)), + sortOrder); + } + else + { + if (logicalIndex == COLUMN_AMOUNT) // sort by amount + logicalIndex = COLUMN_AMOUNT_INT64; + + if (logicalIndex == COLUMN_PRIORITY) // sort by priority + logicalIndex = COLUMN_PRIORITY_INT64; + + if (sortColumn == logicalIndex) + sortOrder = ((sortOrder == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder); + else + { + sortColumn = logicalIndex; + + // if amount,date,conf,priority then default => desc, else default => asc + sortOrder = ((sortColumn == COLUMN_AMOUNT_INT64 || sortColumn == COLUMN_PRIORITY_INT64 + || sortColumn == COLUMN_DATE || sortColumn == COLUMN_CONFIRMATIONS) ? + Qt::DescendingOrder : Qt::AscendingOrder); + } + + sortView(sortColumn, sortOrder); + } +} + + +// toggle tree mode +void ConsolidateUnspentWizardSelectInputsPage::treeModeRadioButton(bool checked) +{ + if (checked && model) + updateView(); +} + +// toggle list mode +void ConsolidateUnspentWizardSelectInputsPage::listModeRadioButton(bool checked) +{ + if (checked && model) + updateView(); +} + +// checkbox clicked by user +void ConsolidateUnspentWizardSelectInputsPage::viewItemChanged(QTreeWidgetItem* item, int column) +{ + if (!m_ViewItemsChangedViaFilter) m_InputSelectionLimitedByFilter = false; + + if (column == COLUMN_CHECKBOX) + { + // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) + if (item->text(COLUMN_TXHASH).length() == 64) + { + COutPoint outpt(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()); + + if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) + { + coinControl->UnSelect(outpt); + } + else if (item->isDisabled()) // locked (this happens if "check all" through parent node) + { + item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + } + else + { + coinControl->Select(outpt); + } + } + + // selection changed -> update labels + if (ui->treeWidget->isEnabled()) + { + // do not update on every click for (un)select all + updateLabels(); + } + } +} + +void ConsolidateUnspentWizardSelectInputsPage::updateLabels() +{ + if (!model) return; + + // nPayAmount + qint64 nPayAmount = 0; + CTransaction txDummy; + for (const auto& amount: *payAmounts) + { + nPayAmount += amount; + + if (amount > 0) + { + CTxOut txout(amount, (CScript)vector(24, 0)); + txDummy.vout.push_back(txout); + } + } + + QString sPriorityLabel = QString(); + int64_t nAmount = 0; + int64_t nPayFee = 0; + int64_t nAfterFee = 0; + int64_t nChange = 0; + unsigned int nBytes = 0; + unsigned int nBytesInputs = 0; + unsigned int nQuantity = 0; + + vector vCoinControl; + vector vOutputs; + coinControl->ListSelected(vCoinControl); + model->getOutputs(vCoinControl, vOutputs); + + for (const auto& out : vOutputs) + { + // Quantity + nQuantity++; + + // Amount + nAmount += out.tx->vout[out.i].nValue; + + // Bytes + CTxDestination address; + if (ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) + { + CPubKey pubkey; + try { + if (model->getPubKey(std::get(address), pubkey)) + nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); + else + nBytesInputs += 148; // in all error cases, simply assume 148 here + } catch (const std::bad_variant_access&) { + nBytesInputs += 148; + } + } + else nBytesInputs += 148; + } + + // calculation + if (nQuantity > 0) + { + // Bytes - always assume +1 output for change here + nBytes = nBytesInputs + ((payAmounts->size() > 0 ? payAmounts->size() + 1 : 2) * 34) + 10; + + // Fee + int64_t nFee = nTransactionFee * (1 + (int64_t)nBytes / 1000); + + // Min Fee + int64_t nMinFee = GetMinFee(txDummy, 1000, GMF_SEND, nBytes); + + nPayFee = max(nFee, nMinFee); + + if (nPayAmount > 0) + { + nChange = nAmount - nPayFee - nPayAmount; + + // if sub-cent change is required, the fee must be raised to at least CTransaction::nMinTxFee + if (nPayFee < CENT && nChange > 0 && nChange < CENT) + { + if (nChange < CENT) // change < 0.01 => simply move all change to fees + { + nPayFee = nChange; + nChange = 0; + } + else + { + nChange = nChange + nPayFee - CENT; + nPayFee = CENT; + } + } + + if (nChange == 0) nBytes -= 34; + } + + // after fee + nAfterFee = nAmount - nPayFee; + if (nAfterFee < 0) nAfterFee = 0; + } + + // actually update labels + int nDisplayUnit = BitcoinUnits::BTC; + if (model && model->getOptionsModel()) nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + + // stats + ui->quantityLabel->setText(QString::number(nQuantity)); // Quantity + ui->feeLabel->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nPayFee)); // Fee + ui->afterFeeLabel->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAfterFee)); // After Fee + + std::map addressList; + QString defaultAddress; + unsigned int numberAddressesWhereOutputsChecked = 0; + + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) + { + QString label = ui->treeWidget->topLevelItem(i)->text(COLUMN_LABEL); + QString address = ui->treeWidget->topLevelItem(i)->text(COLUMN_ADDRESS); + QString change = ui->treeWidget-> topLevelItem(i)->text(COLUMN_CHANGE_BOOL); + + Qt::CheckState state = ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX); + + // If a not unchecked top level item is not a change address and it results in an insert into the m_AddressList + if (!change.toInt() && addressList.insert(std::make_pair(address, label)).second) + { + if (state == Qt::Checked || state == Qt::PartiallyChecked) + { + defaultAddress = label; + + ++numberAddressesWhereOutputsChecked; + } + + if (!addressList.empty()) emit setAddressListSignal(addressList); + } + } + + // This covers the 0 case too, where the default address will be an empty QString. + if (numberAddressesWhereOutputsChecked < 2) + { + // This will be an empty QString if the numberAddressesWhereOutputsChecked equals 0. It will be + // the above defaultAddress if numberAddressesWhereOutputsChecked equals 1. + emit setDefaultAddressSignal(defaultAddress); + } + else + { + // If numberAddressesWhereOutputsChecked is 2 or greater, then clear the default address (i.e. set to + // empty QString. + emit setDefaultAddressSignal(QString()); + } + + // This provids the trigger to update the fields from the labels, since they are QLabels and don't have appropriate + // internal signals. + emit updateFieldsSignal(); + + if (nQuantity < 2) + { + SetOutputWarningStop(InputStatus::INSUFFICIENT_OUTPUTS); + } + else if (nQuantity < m_InputSelectionLimit + || (nQuantity == m_InputSelectionLimit && !m_InputSelectionLimitedByFilter)) + { + SetOutputWarningStop(InputStatus::NORMAL); + } + else if (nQuantity == m_InputSelectionLimit && m_InputSelectionLimitedByFilter) + { + SetOutputWarningStop(InputStatus::WARNING); + } + else if (nQuantity > m_InputSelectionLimit) + { + SetOutputWarningStop(InputStatus::STOP); + } +} + +void ConsolidateUnspentWizardSelectInputsPage::updateView() +{ + bool treeMode = ui->treeModeRadioButton->isChecked(); + + ui->treeWidget->clear(); + ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox + ui->treeWidget->setAlternatingRowColors(!treeMode); + QFlags flgCheckbox=Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + QFlags flgTristate=Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; + + int nDisplayUnit = BitcoinUnits::BTC; + + if (model && model->getOptionsModel()) + { + nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + } + + map> mapCoins; + model->listCoins(mapCoins); + + for (auto const& coins : mapCoins) + { + QTreeWidgetItem *itemWalletAddress = new QTreeWidgetItem(); + QString sWalletAddress = coins.first; + QString sWalletLabel = ""; + if (model->getAddressTableModel()) + sWalletLabel = model->getAddressTableModel()->labelForAddress(sWalletAddress); + if (sWalletLabel.length() == 0) + sWalletLabel = tr("(no label)"); + + if (treeMode) + { + // wallet address + ui->treeWidget->addTopLevelItem(itemWalletAddress); + + itemWalletAddress->setFlags(flgTristate); + itemWalletAddress->setCheckState(COLUMN_CHECKBOX,Qt::Unchecked); + + // label + itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel); + + // address + itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress); + } + + int64_t nSum = 0; + double dPrioritySum = 0; + int nChildren = 0; + int nInputSum = 0; + + for (auto const& out : coins.second) + { + int nInputSize = 148; // 180 if uncompressed public key + nSum += out.tx->vout[out.i].nValue; + nChildren++; + + QTreeWidgetItem *itemOutput; + if (treeMode) itemOutput = new QTreeWidgetItem(itemWalletAddress); + else itemOutput = new QTreeWidgetItem(ui->treeWidget); + itemOutput->setFlags(flgCheckbox); + itemOutput->setCheckState(COLUMN_CHECKBOX,Qt::Unchecked); + + // address + CTxDestination outputAddress; + QString sAddress = ""; + if (ExtractDestination(out.tx->vout[out.i].scriptPubKey, outputAddress)) + { + sAddress = CBitcoinAddress(outputAddress).ToString().c_str(); + + // if listMode or change => show bitcoin address. In tree mode, address is not shown again for direct wallet address outputs + if (!treeMode || (!(sAddress == sWalletAddress))) + itemOutput->setText(COLUMN_ADDRESS, sAddress); + + CPubKey pubkey; + try { + if (model->getPubKey(std::get(outputAddress), pubkey) && !pubkey.IsCompressed()) + nInputSize = 180; + } catch (const std::bad_variant_access&) {} + } + + // label + if (!(sAddress == sWalletAddress)) // change + { + // tooltip from where the change comes from + itemOutput->setToolTip(COLUMN_LABEL, tr("change from %1 (%2)").arg(sWalletLabel).arg(sWalletAddress)); + itemOutput->setText(COLUMN_LABEL, tr("(change)")); + itemOutput->setText(COLUMN_CHANGE_BOOL, QString::number(1)); + } + else if (!treeMode) + { + QString sLabel = ""; + if (model->getAddressTableModel()) + sLabel = model->getAddressTableModel()->labelForAddress(sAddress); + if (sLabel.length() == 0) + sLabel = tr("(no label)"); + itemOutput->setText(COLUMN_LABEL, sLabel); + } + + // amount + itemOutput->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.tx->vout[out.i].nValue)); + itemOutput->setText(COLUMN_AMOUNT_INT64, strPad(QString::number(out.tx->vout[out.i].nValue), 15, " ")); // padding so that sorting works correctly + + // date + itemOutput->setText(COLUMN_DATE, QDateTime::fromTime_t(out.tx->GetTxTime()).toUTC().toString("yy-MM-dd hh:mm")); + + // immature PoS reward + { + // LOCK on cs_main must be taken for depth and maturity. + LOCK(cs_main); + + if (out.tx->IsCoinStake() && out.tx->GetBlocksToMaturity() > 0 && out.tx->GetDepthInMainChain() > 0) { + itemOutput->setBackground(COLUMN_CONFIRMATIONS, Qt::red); + itemOutput->setDisabled(true); + } + } + + // confirmations + itemOutput->setText(COLUMN_CONFIRMATIONS, strPad(QString::number(out.nDepth), 8, " ")); + + // priority + double dPriority = ((double)out.tx->vout[out.i].nValue / (nInputSize + 78)) * (out.nDepth+1); // 78 = 2 * 34 + 10 + itemOutput->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPriority)); + itemOutput->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPriority), 20, " ")); + dPrioritySum += (double)out.tx->vout[out.i].nValue * (out.nDepth+1); + nInputSum += nInputSize; + + // transaction hash + uint256 txhash = out.tx->GetHash(); + itemOutput->setText(COLUMN_TXHASH, txhash.GetHex().c_str()); + + // vout index + itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(out.i)); + + // set checkbox + if (coinControl->IsSelected(txhash, out.i)) + { + itemOutput->setCheckState(COLUMN_CHECKBOX,Qt::Checked); + } + } + + // amount + if (treeMode) + { + dPrioritySum = dPrioritySum / (nInputSum + 78); + itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); + itemWalletAddress->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum)); + itemWalletAddress->setText(COLUMN_AMOUNT_INT64, strPad(QString::number(nSum), 15, " ")); + itemWalletAddress->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPrioritySum)); + itemWalletAddress->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPrioritySum), 20, " ")); + } + } + + // expand all partially selected + if (treeMode) + { + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) + ui->treeWidget->topLevelItem(i)->setExpanded(true); + } + + // sort view + sortView(sortColumn, sortOrder); + ui->treeWidget->setEnabled(true); +} + +void ConsolidateUnspentWizardSelectInputsPage::SetOutputWarningStop(InputStatus input_status) +{ + switch (input_status) + { + case InputStatus::INSUFFICIENT_OUTPUTS: + ui->outputLimitWarningIconLabel->setVisible(false); + ui->outputLimitStopIconLabel->setVisible(false); + ui->isCompleteCheckBox->setChecked(false); + break; + case InputStatus::NORMAL: + ui->outputLimitWarningIconLabel->setVisible(false); + ui->outputLimitStopIconLabel->setVisible(false); + ui->isCompleteCheckBox->setChecked(true); + break; + case InputStatus::WARNING: + ui->outputLimitWarningIconLabel->setVisible(true); + ui->outputLimitStopIconLabel->setVisible(false); + ui->isCompleteCheckBox->setChecked(true); + break; + case InputStatus::STOP: + ui->outputLimitWarningIconLabel->setVisible(false); + ui->outputLimitStopIconLabel->setVisible(true); + ui->isCompleteCheckBox->setChecked(false); + } +} diff --git a/src/qt/consolidateunspentwizardselectinputspage.h b/src/qt/consolidateunspentwizardselectinputspage.h new file mode 100644 index 0000000000..5db61f2f5e --- /dev/null +++ b/src/qt/consolidateunspentwizardselectinputspage.h @@ -0,0 +1,91 @@ +#ifndef CONSOLIDATEUNSPENTWIZARDSELECTINPUTS_H +#define CONSOLIDATEUNSPENTWIZARDSELECTINPUTS_H + +#include "walletmodel.h" +#include "amount.h" + +#include +#include + +namespace Ui { + class ConsolidateUnspentWizardSelectInputsPage; +} + +class CoinControlDialog; + +class ConsolidateUnspentWizardSelectInputsPage : public QWizardPage +{ + Q_OBJECT + +public: + explicit ConsolidateUnspentWizardSelectInputsPage(QWidget *parent = nullptr); + ~ConsolidateUnspentWizardSelectInputsPage(); + + void setCoinControl(CCoinControl* coinControl); + void setPayAmounts(QList *payAmounts); + +signals: + void setAddressListSignal(std::map); + void setDefaultAddressSignal(QString); + void updateFieldsSignal(); + +public slots: + void setModel(WalletModel*); + void updateLabels(); + +private: + Ui::ConsolidateUnspentWizardSelectInputsPage *ui; + CCoinControl *coinControl; + QList *payAmounts; + WalletModel *model; + int sortColumn; + Qt::SortOrder sortOrder; + size_t m_InputSelectionLimit; + Qt::CheckState m_ToState = Qt::Checked; + bool m_FilterMode = true; + bool m_FilterValueValid = false; + bool m_InputSelectionLimitedByFilter = false; + bool m_ViewItemsChangedViaFilter = false; + + QString strPad(QString, int, QString); + void sortView(int, Qt::SortOrder); + void updateView(); + bool filterInputsByValue(const bool& less, const CAmount& inputFilterValue, const unsigned int& inputSelectionLimit); + + enum + { + COLUMN_CHECKBOX, + COLUMN_AMOUNT, + COLUMN_LABEL, + COLUMN_ADDRESS, + COLUMN_DATE, + COLUMN_CONFIRMATIONS, + COLUMN_PRIORITY, + COLUMN_TXHASH, + COLUMN_VOUT_INDEX, + COLUMN_AMOUNT_INT64, + COLUMN_PRIORITY_INT64, + COLUMN_CHANGE_BOOL + }; + + enum InputStatus + { + INSUFFICIENT_OUTPUTS, + NORMAL, + WARNING, + STOP + }; + +private slots: + void treeModeRadioButton(bool); + void listModeRadioButton(bool); + void viewItemChanged(QTreeWidgetItem*, int); + void headerSectionClicked(int); + void buttonSelectAllClicked(); + void maxMinOutputValueChanged(); + void buttonFilterModeClicked(); + void buttonFilterClicked(); + void SetOutputWarningStop(InputStatus input_status); +}; + +#endif // CONSOLIDATEUNSPENTWIZARDSELECTINPUTS_H diff --git a/src/qt/consolidateunspentwizardsendpage.cpp b/src/qt/consolidateunspentwizardsendpage.cpp new file mode 100644 index 0000000000..2a50e3e080 --- /dev/null +++ b/src/qt/consolidateunspentwizardsendpage.cpp @@ -0,0 +1,53 @@ +#include "consolidateunspentwizardsendpage.h" +#include "ui_consolidateunspentwizardsendpage.h" + +#include "util.h" +#include "bitcoinunits.h" +#include "optionsmodel.h" + +ConsolidateUnspentWizardSendPage::ConsolidateUnspentWizardSendPage(QWidget *parent) : + QWizardPage(parent), + ui(new Ui::ConsolidateUnspentWizardSendPage) +{ + ui->setupUi(this); +} + +ConsolidateUnspentWizardSendPage::~ConsolidateUnspentWizardSendPage() +{ + delete ui; +} + +void ConsolidateUnspentWizardSendPage::initializePage() +{ + ui->InputQuantityLabel->setText(field("quantityField").toString()); + ui->feeLabel->setText(field("feeField").toString()); + ui->afterFeeAmountLabel->setText(field("afterFeeAmountField").toString()); + ui->destinationAddressLabelLabel->setText(field("selectedAddressLabelField").toString()); + ui->destinationAddressLabel->setText(field("selectedAddressField").toString()); + + LogPrint(BCLog::LogFlags::QT, "INFO: %s: destinationAddress = %s", + __func__, field("selectedAddressField").toString().toStdString()); + + qint64 amount = 0; + bool parse_status = false; + + m_recipient.label = ui->destinationAddressLabelLabel->text(); + m_recipient.address = ui->destinationAddressLabel->text(); + + parse_status = BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), + ui->afterFeeAmountLabel->text() + .left(ui->afterFeeAmountLabel->text().indexOf(" ")), + &amount); + + if (parse_status) m_recipient.amount = amount; +} + +void ConsolidateUnspentWizardSendPage::setModel(WalletModel *model) +{ + this->model = model; +} + +void ConsolidateUnspentWizardSendPage::onFinishButtonClicked() +{ + emit selectedConsolidationRecipientSignal(m_recipient); +} diff --git a/src/qt/consolidateunspentwizardsendpage.h b/src/qt/consolidateunspentwizardsendpage.h new file mode 100644 index 0000000000..974a1425ef --- /dev/null +++ b/src/qt/consolidateunspentwizardsendpage.h @@ -0,0 +1,38 @@ +#ifndef CONSOLIDATEUNSPENTWIZARDSENDPAGE_H +#define CONSOLIDATEUNSPENTWIZARDSENDPAGE_H + +#include "walletmodel.h" +#include "amount.h" + +#include + +namespace Ui { + class ConsolidateUnspentWizardSendPage; +} + +class ConsolidateUnspentWizardSendPage : public QWizardPage +{ + Q_OBJECT + +public: + explicit ConsolidateUnspentWizardSendPage(QWidget *parent = nullptr); + ~ConsolidateUnspentWizardSendPage(); + + void initializePage(); + +public slots: + void setModel(WalletModel*); + void onFinishButtonClicked(); + +signals: + void selectedConsolidationRecipientSignal(SendCoinsRecipient consolidationRecipient); + +private: + Ui::ConsolidateUnspentWizardSendPage *ui; + WalletModel *model; + SendCoinsRecipient m_recipient; + + size_t m_inputSelectionLimit; +}; + +#endif // CONSOLIDATEUNSPENTWIZARDSENDPAGE_H diff --git a/src/qt/forms/consolidateunspentwizard.ui b/src/qt/forms/consolidateunspentwizard.ui new file mode 100644 index 0000000000..ddde9fb2d2 --- /dev/null +++ b/src/qt/forms/consolidateunspentwizard.ui @@ -0,0 +1,69 @@ + + + ConsolidateUnspentWizard + + + + 0 + 0 + 933 + 700 + + + + + 0 + 0 + + + + Conolidate Unspent Transaction Outputs (UTXOs) + + + true + + + true + + + QWizard::ClassicStyle + + + + 0 + + + + + 1 + + + + + 2 + + + + + + ConsolidateUnspentWizardSelectInputsPage + QWizardPage +
consolidateunspentwizardselectinputspage.h
+ 1 +
+ + ConsolidateUnspentWizardSelectDestinationPage + QWizardPage +
consolidateunspentwizardselectdestinationpage.h
+ 1 +
+ + ConsolidateUnspentWizardSendPage + QWizardPage +
consolidateunspentwizardsendpage.h
+ 1 +
+
+ + +
diff --git a/src/qt/forms/consolidateunspentwizardselectdestinationpage.ui b/src/qt/forms/consolidateunspentwizardselectdestinationpage.ui new file mode 100644 index 0000000000..c7486abf21 --- /dev/null +++ b/src/qt/forms/consolidateunspentwizardselectdestinationpage.ui @@ -0,0 +1,132 @@ + + + ConsolidateUnspentWizardSelectDestinationPage + + + + 0 + 0 + 900 + 700 + + + + WizardPage + + + + + 19 + 19 + 851 + 581 + + + + + + + Step 2: Select the destination address for the consolidation transaction. Note that all of the selected inputs will be consolidated to an output on this address. If there is a very small amount of change (due to uncertainty in the fee calculation), it will also be sent to this address. If you selected inputs only from a particular address on the previous page, then that address will already be selected by default. + + + true + + + + + + + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ScrollPerPixel + + + 90 + + + true + + + false + + + + Label + + + + + Address + + + + + + + + + + + + Currently selected: + + + + + + + + + + + Label + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Address + + + + + + + + + isComplete + + + + + + + + + diff --git a/src/qt/forms/consolidateunspentwizardselectinputspage.ui b/src/qt/forms/consolidateunspentwizardselectinputspage.ui new file mode 100644 index 0000000000..bd719917df --- /dev/null +++ b/src/qt/forms/consolidateunspentwizardselectinputspage.ui @@ -0,0 +1,371 @@ + + + ConsolidateUnspentWizardSelectInputsPage + + + + 0 + 0 + 900 + 700 + + + + WizardPage + + + + + 20 + 20 + 851 + 581 + + + + + + + + + + 0 + 0 + + + + Step 1: Select the inputs to be consolidated. Remember that the inputs to the consolidation are your unspent outputs (UTXOs) in your wallet. + + + true + + + + + + + + + + + Select All + + + + + + + Tree Mode + + + true + + + + + + + List Mode + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Select inputs + + + + + + + + + + <= + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + Filters the already selected inputs. + + + Filter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::CustomContextMenu + + + false + + + 11 + + + true + + + false + + + + + + + + + Amount + + + + + Label + + + + + Address + + + + + Date + + + + + Confirmations + + + Confirmed + + + + + Priority + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + :/icons/warning + + + true + + + + + + + + 64 + 64 + + + + + + + :/icons/white_and_red_x + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Quantity + + + + + + + 99999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Fee + + + + + + + 99.9999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + After Fee Amount + + + + + + + 999999999.9999 + + + + + + + isComplete + + + + + + + + + + + CoinControlTreeWidget + QTreeWidget +
coincontroltreewidget.h
+
+ + BitcoinAmountField + QSpinBox +
bitcoinamountfield.h
+ 1 +
+
+ + + + +
diff --git a/src/qt/forms/consolidateunspentwizardsendpage.ui b/src/qt/forms/consolidateunspentwizardsendpage.ui new file mode 100644 index 0000000000..c0ce6e79b1 --- /dev/null +++ b/src/qt/forms/consolidateunspentwizardsendpage.ui @@ -0,0 +1,119 @@ + + + ConsolidateUnspentWizardSendPage + + + + 0 + 0 + 900 + 700 + + + + WizardPage + + + + + 19 + 19 + 851 + 581 + + + + + + + + + Step 3: Confirm Consolidation Transaction Details. Transaction will be ready to send when Finish is pressed. + + + true + + + + + + + + + + + Number of Inputs + + + + + + + 999999 + + + + + + + Transaction Fee + + + + + + + 99.9999 + + + + + + + Amount + + + + + + + 999999999.9999 + + + + + + + Destination Address + + + + + + + address + + + + + + + Destination Address Label + + + + + + + label + + + + + + + + + + + diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index 3478b8fb4f..17004d7df1 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -85,7 +85,7 @@ - + 8 @@ -129,6 +129,13 @@ + + + + Consolidate Wizard + + + diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 09c742b2b0..c8cbf75729 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -12,7 +12,10 @@ #include "askpassphrasedialog.h" #include "wallet/coincontrol.h" +#include "policy/policy.h" #include "coincontroldialog.h" +#include "consolidateunspentdialog.h" +#include "consolidateunspentwizard.h" #include #include @@ -23,6 +26,8 @@ SendCoinsDialog::SendCoinsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::SendCoinsDialog), + coinControl(new CCoinControl), + payAmounts(new QList), model(0) { ui->setupUi(this); @@ -34,6 +39,7 @@ SendCoinsDialog::SendCoinsDialog(QWidget *parent) : // Coin Control ui->coinControlChangeEdit->setFont(GUIUtil::bitcoinAddressFont()); connect(ui->coinControlPushButton, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked())); + connect(ui->coinControlConsolidateWizardPushButton, SIGNAL(clicked()), this, SLOT(coinControlConsolidateWizardButtonClicked())); connect(ui->coinControlResetPushButton, SIGNAL(clicked()), this, SLOT(coinControlResetButtonClicked())); connect(ui->coinControlChangeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int))); connect(ui->coinControlChangeEdit, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &))); @@ -167,7 +173,7 @@ void SendCoinsDialog::on_sendButton_clicked() if (!model->getOptionsModel() || !model->getOptionsModel()->getCoinControlFeatures()) sendstatus = model->sendCoins(recipients); else - sendstatus = model->sendCoins(recipients, CoinControlDialog::coinControl); + sendstatus = model->sendCoins(recipients, coinControl); switch(sendstatus.status) { @@ -211,7 +217,7 @@ void SendCoinsDialog::on_sendButton_clicked() break; case WalletModel::OK: accept(); - CoinControlDialog::coinControl->UnSelectAll(); + coinControl->UnSelectAll(); coinControlUpdateLabels(); break; } @@ -423,13 +429,13 @@ void SendCoinsDialog::coinControlFeatureChanged(bool checked) ui->frameCoinControl->setVisible(checked); if (!checked && model) // coin control features disabled - CoinControlDialog::coinControl->SetNull(); + coinControl->SetNull(); } // Coin Control: button inputs -> show actual coin control dialog void SendCoinsDialog::coinControlButtonClicked() { - CoinControlDialog dlg; + CoinControlDialog dlg(this, coinControl, payAmounts); dlg.setModel(model); connect(&dlg, SIGNAL(selectedConsolidationRecipientSignal(SendCoinsRecipient)), @@ -441,12 +447,31 @@ void SendCoinsDialog::coinControlButtonClicked() void SendCoinsDialog::coinControlResetButtonClicked() { - CoinControlDialog::coinControl->SetNull(); + coinControl->SetNull(); coinControlUpdateLabels(); } +void SendCoinsDialog::coinControlConsolidateWizardButtonClicked() +{ + CoinControlDialog dlg(this, coinControl, payAmounts); + dlg.setModel(model); + + connect(&dlg, SIGNAL(selectedConsolidationRecipientSignal(SendCoinsRecipient)), + this, SLOT(selectedConsolidationRecipient(SendCoinsRecipient))); + + ConsolidateUnspentWizard wizard(this, coinControl, payAmounts, GetMaxInputsForConsolidationTxn()); + wizard.setModel(model); + + connect(&wizard, SIGNAL(selectedConsolidationRecipientSignal(SendCoinsRecipient)), + this, SLOT(selectedConsolidationRecipient(SendCoinsRecipient))); + + wizard.exec(); +} + void SendCoinsDialog::selectedConsolidationRecipient(SendCoinsRecipient consolidationRecipient) { + LogPrintf("INFO: %s: SLOT called.", __func__); + ui->coinControlChangeCheckBox->setChecked(true); ui->coinControlChangeEdit->setText(consolidationRecipient.address); @@ -469,9 +494,9 @@ void SendCoinsDialog::coinControlChangeChecked(int state) if (model) { if (state == Qt::Checked) - CoinControlDialog::coinControl->destChange = CBitcoinAddress(ui->coinControlChangeEdit->text().toStdString()).Get(); + coinControl->destChange = CBitcoinAddress(ui->coinControlChangeEdit->text().toStdString()).Get(); else - CoinControlDialog::coinControl->destChange = CNoDestination(); + coinControl->destChange = CNoDestination(); } ui->coinControlChangeEdit->setEnabled((state == Qt::Checked)); @@ -483,7 +508,7 @@ void SendCoinsDialog::coinControlChangeEdited(const QString & text) { if (model) { - CoinControlDialog::coinControl->destChange = CBitcoinAddress(text.toStdString()).Get(); + coinControl->destChange = CBitcoinAddress(text.toStdString()).Get(); // label for the change address ui->coinControlChangeLabel->setStyleSheet("QLabel{color:black;}"); @@ -523,18 +548,17 @@ void SendCoinsDialog::coinControlUpdateLabels() return; // set pay amounts - CoinControlDialog::payAmounts.clear(); - for(int i = 0; i < ui->entries->count(); ++i) + payAmounts->clear(); + for (int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); - if(entry) - CoinControlDialog::payAmounts.append(entry->getValue().amount); + if (entry) payAmounts->append(entry->getValue().amount); } - if (CoinControlDialog::coinControl->HasSelected()) + if (coinControl->HasSelected()) { // actual coin control calculation - CoinControlDialog::updateLabels(model, this); + CoinControlDialog::updateLabels(model, coinControl, payAmounts, this); // show coin control stats ui->coinControlAutomaticallySelectedLabel->hide(); diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 81878ca005..b0479bd9ce 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -43,6 +43,8 @@ public slots: private: Ui::SendCoinsDialog *ui; + CCoinControl *coinControl; + QList *payAmounts; WalletModel *model; bool fNewRecipientAllowed; @@ -53,6 +55,7 @@ private slots: void coinControlFeatureChanged(bool); void coinControlButtonClicked(); void coinControlResetButtonClicked(); + void coinControlConsolidateWizardButtonClicked(); void coinControlChangeChecked(int); void coinControlChangeEdited(const QString &); void coinControlUpdateLabels();