]>
Commit | Line | Data |
---|---|---|
f914f1a7 | 1 | // Copyright (c) 2011-2014 The Bitcoin Core developers |
78253fcb | 2 | // Distributed under the MIT software license, see the accompanying |
e592d43f WL |
3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | ||
0fd01780 | 5 | #include "sendcoinsdialog.h" |
df577886 | 6 | #include "ui_sendcoinsdialog.h" |
32af5266 | 7 | |
0689f46c | 8 | #include "addresstablemodel.h" |
e285ffcd | 9 | #include "bitcoinunits.h" |
c1c9d5b4 | 10 | #include "clientmodel.h" |
0689f46c | 11 | #include "coincontroldialog.h" |
51ed9ec9 | 12 | #include "guiutil.h" |
968d55aa | 13 | #include "optionsmodel.h" |
9b7d3fb1 | 14 | #include "scicon.h" |
a5e6d723 | 15 | #include "sendcoinsentry.h" |
51ed9ec9 BD |
16 | #include "walletmodel.h" |
17 | ||
2bc15836 | 18 | #include "base58.h" |
6a86c24d | 19 | #include "coincontrol.h" |
0689f46c | 20 | #include "ui_interface.h" |
a328dd60 | 21 | #include "wallet.h" |
3a7abc2c | 22 | |
992ff49b | 23 | #include <QMessageBox> |
9a93c4c0 | 24 | #include <QScrollBar> |
c1c9d5b4 | 25 | #include <QSettings> |
51ed9ec9 | 26 | #include <QTextDocument> |
3f323a61 | 27 | |
a5e6d723 | 28 | SendCoinsDialog::SendCoinsDialog(QWidget *parent) : |
df577886 | 29 | QDialog(parent), |
6630c1cb | 30 | ui(new Ui::SendCoinsDialog), |
a328dd60 PK |
31 | clientModel(0), |
32 | model(0), | |
33 | fNewRecipientAllowed(true), | |
34 | fFeeMinimized(true) | |
df577886 WL |
35 | { |
36 | ui->setupUi(this); | |
83b82370 | 37 | |
81605d90 | 38 | #ifdef Q_OS_MAC // Icons on push buttons are very uncommon on Mac |
527137e3 | 39 | ui->addButton->setIcon(QIcon()); |
40 | ui->clearButton->setIcon(QIcon()); | |
41 | ui->sendButton->setIcon(QIcon()); | |
9b7d3fb1 LD |
42 | #else |
43 | ui->addButton->setIcon(SingleColorIcon(":/icons/add")); | |
44 | ui->clearButton->setIcon(SingleColorIcon(":/icons/remove")); | |
45 | ui->sendButton->setIcon(SingleColorIcon(":/icons/send")); | |
527137e3 | 46 | #endif |
c78bd937 PK |
47 | |
48 | GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this); | |
527137e3 | 49 | |
a5e6d723 | 50 | addEntry(); |
992ff49b | 51 | |
a5e6d723 | 52 | connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry())); |
609acbf4 | 53 | connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); |
7d145a0f | 54 | |
6a86c24d | 55 | // Coin Control |
6a86c24d CL |
56 | connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked())); |
57 | connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int))); | |
58 | connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &))); | |
59 | ||
60 | // Coin Control: clipboard actions | |
61 | QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); | |
62 | QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); | |
63 | QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); | |
64 | QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); | |
65 | QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); | |
66 | QAction *clipboardPriorityAction = new QAction(tr("Copy priority"), this); | |
95a93836 | 67 | QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); |
6a86c24d CL |
68 | QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); |
69 | connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity())); | |
70 | connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount())); | |
71 | connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee())); | |
72 | connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee())); | |
73 | connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes())); | |
74 | connect(clipboardPriorityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardPriority())); | |
75 | connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput())); | |
76 | connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange())); | |
77 | ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); | |
78 | ui->labelCoinControlAmount->addAction(clipboardAmountAction); | |
79 | ui->labelCoinControlFee->addAction(clipboardFeeAction); | |
80 | ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); | |
81 | ui->labelCoinControlBytes->addAction(clipboardBytesAction); | |
82 | ui->labelCoinControlPriority->addAction(clipboardPriorityAction); | |
83 | ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); | |
84 | ui->labelCoinControlChange->addAction(clipboardChangeAction); | |
85 | ||
c1c9d5b4 CL |
86 | // init transaction fee section |
87 | QSettings settings; | |
88 | if (!settings.contains("fFeeSectionMinimized")) | |
89 | settings.setValue("fFeeSectionMinimized", true); | |
90 | if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility | |
91 | settings.setValue("nFeeRadio", 1); // custom | |
92 | if (!settings.contains("nFeeRadio")) | |
93 | settings.setValue("nFeeRadio", 0); // recommended | |
94 | if (!settings.contains("nCustomFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility | |
95 | settings.setValue("nCustomFeeRadio", 1); // total at least | |
96 | if (!settings.contains("nCustomFeeRadio")) | |
97 | settings.setValue("nCustomFeeRadio", 0); // per kilobyte | |
98 | if (!settings.contains("nSmartFeeSliderPosition")) | |
99 | settings.setValue("nSmartFeeSliderPosition", 0); | |
100 | if (!settings.contains("nTransactionFee")) | |
101 | settings.setValue("nTransactionFee", (qint64)DEFAULT_TRANSACTION_FEE); | |
102 | if (!settings.contains("fPayOnlyMinFee")) | |
103 | settings.setValue("fPayOnlyMinFee", false); | |
104 | if (!settings.contains("fSendFreeTransactions")) | |
105 | settings.setValue("fSendFreeTransactions", false); | |
106 | ui->groupFee->setId(ui->radioSmartFee, 0); | |
107 | ui->groupFee->setId(ui->radioCustomFee, 1); | |
108 | ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true); | |
109 | ui->groupCustomFee->setId(ui->radioCustomPerKilobyte, 0); | |
110 | ui->groupCustomFee->setId(ui->radioCustomAtLeast, 1); | |
111 | ui->groupCustomFee->button((int)std::max(0, std::min(1, settings.value("nCustomFeeRadio").toInt())))->setChecked(true); | |
112 | ui->sliderSmartFee->setValue(settings.value("nSmartFeeSliderPosition").toInt()); | |
113 | ui->customFee->setValue(settings.value("nTransactionFee").toLongLong()); | |
114 | ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool()); | |
115 | ui->checkBoxFreeTx->setChecked(settings.value("fSendFreeTransactions").toBool()); | |
116 | minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool()); | |
df577886 WL |
117 | } |
118 | ||
c1c9d5b4 CL |
119 | void SendCoinsDialog::setClientModel(ClientModel *clientModel) |
120 | { | |
121 | this->clientModel = clientModel; | |
a328dd60 PK |
122 | |
123 | if (clientModel) { | |
8517e970 | 124 | connect(clientModel, SIGNAL(numBlocksChanged(int,QDateTime)), this, SLOT(updateSmartFeeLabel())); |
a328dd60 | 125 | } |
c1c9d5b4 CL |
126 | } |
127 | ||
ef079e18 | 128 | void SendCoinsDialog::setModel(WalletModel *model) |
6630c1cb WL |
129 | { |
130 | this->model = model; | |
a5e6d723 | 131 | |
6728e007 | 132 | if(model && model->getOptionsModel()) |
a5e6d723 | 133 | { |
6728e007 | 134 | for(int i = 0; i < ui->entries->count(); ++i) |
a5e6d723 | 135 | { |
6728e007 PK |
136 | SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); |
137 | if(entry) | |
138 | { | |
139 | entry->setModel(model); | |
140 | } | |
a5e6d723 | 141 | } |
6728e007 | 142 | |
ffd40da3 J |
143 | setBalance(model->getBalance(), model->getUnconfirmedBalance(), model->getImmatureBalance(), |
144 | model->getWatchBalance(), model->getWatchUnconfirmedBalance(), model->getWatchImmatureBalance()); | |
c122f552 | 145 | connect(model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(setBalance(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount))); |
e0873daf | 146 | connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); |
c1c9d5b4 | 147 | updateDisplayUnit(); |
6a86c24d CL |
148 | |
149 | // Coin Control | |
150 | connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels())); | |
151 | connect(model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool))); | |
6a86c24d CL |
152 | ui->frameCoinControl->setVisible(model->getOptionsModel()->getCoinControlFeatures()); |
153 | coinControlUpdateLabels(); | |
c1c9d5b4 CL |
154 | |
155 | // fee section | |
c1c9d5b4 CL |
156 | connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateSmartFeeLabel())); |
157 | connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateGlobalFeeVariables())); | |
158 | connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(coinControlUpdateLabels())); | |
159 | connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls())); | |
160 | connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables())); | |
161 | connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels())); | |
162 | connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables())); | |
163 | connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels())); | |
164 | connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(updateGlobalFeeVariables())); | |
165 | connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels())); | |
166 | connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee())); | |
167 | connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls())); | |
168 | connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables())); | |
169 | connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); | |
170 | connect(ui->checkBoxFreeTx, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables())); | |
171 | connect(ui->checkBoxFreeTx, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); | |
172 | ui->customFee->setSingleStep(CWallet::minTxFee.GetFeePerK()); | |
173 | updateFeeSectionControls(); | |
174 | updateMinFeeLabel(); | |
175 | updateSmartFeeLabel(); | |
176 | updateGlobalFeeVariables(); | |
dead0ff8 | 177 | } |
6630c1cb WL |
178 | } |
179 | ||
df577886 WL |
180 | SendCoinsDialog::~SendCoinsDialog() |
181 | { | |
c1c9d5b4 CL |
182 | QSettings settings; |
183 | settings.setValue("fFeeSectionMinimized", fFeeMinimized); | |
184 | settings.setValue("nFeeRadio", ui->groupFee->checkedId()); | |
185 | settings.setValue("nCustomFeeRadio", ui->groupCustomFee->checkedId()); | |
186 | settings.setValue("nSmartFeeSliderPosition", ui->sliderSmartFee->value()); | |
187 | settings.setValue("nTransactionFee", (qint64)ui->customFee->value()); | |
188 | settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked()); | |
189 | settings.setValue("fSendFreeTransactions", ui->checkBoxFreeTx->isChecked()); | |
190 | ||
df577886 WL |
191 | delete ui; |
192 | } | |
3a7abc2c WL |
193 | |
194 | void SendCoinsDialog::on_sendButton_clicked() | |
195 | { | |
bdd0c59a PK |
196 | if(!model || !model->getOptionsModel()) |
197 | return; | |
198 | ||
a5e6d723 WL |
199 | QList<SendCoinsRecipient> recipients; |
200 | bool valid = true; | |
dead0ff8 | 201 | |
a5e6d723 WL |
202 | for(int i = 0; i < ui->entries->count(); ++i) |
203 | { | |
204 | SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); | |
205 | if(entry) | |
206 | { | |
207 | if(entry->validate()) | |
208 | { | |
209 | recipients.append(entry->getValue()); | |
210 | } | |
211 | else | |
212 | { | |
213 | valid = false; | |
214 | } | |
215 | } | |
216 | } | |
6630c1cb | 217 | |
a5e6d723 | 218 | if(!valid || recipients.isEmpty()) |
2097c09a | 219 | { |
992ff49b | 220 | return; |
2097c09a | 221 | } |
0eba0044 | 222 | |
90a43c1e | 223 | fNewRecipientAllowed = false; |
90a43c1e CL |
224 | WalletModel::UnlockContext ctx(model->requestUnlock()); |
225 | if(!ctx.isValid()) | |
226 | { | |
227 | // Unlock wallet was cancelled | |
228 | fNewRecipientAllowed = true; | |
229 | return; | |
230 | } | |
231 | ||
232 | // prepare transaction for getting txFee earlier | |
233 | WalletModelTransaction currentTransaction(recipients); | |
234 | WalletModel::SendCoinsReturn prepareStatus; | |
235 | if (model->getOptionsModel()->getCoinControlFeatures()) // coin control enabled | |
236 | prepareStatus = model->prepareTransaction(currentTransaction, CoinControlDialog::coinControl); | |
237 | else | |
238 | prepareStatus = model->prepareTransaction(currentTransaction); | |
239 | ||
240 | // process prepareStatus and on error generate message shown to user | |
241 | processSendCoinsReturn(prepareStatus, | |
242 | BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee())); | |
243 | ||
244 | if(prepareStatus.status != WalletModel::OK) { | |
245 | fNewRecipientAllowed = true; | |
246 | return; | |
247 | } | |
248 | ||
249 | CAmount txFee = currentTransaction.getTransactionFee(); | |
250 | ||
a5e6d723 WL |
251 | // Format confirmation message |
252 | QStringList formatted; | |
292623ad | 253 | foreach(const SendCoinsRecipient &rcp, currentTransaction.getRecipients()) |
a5e6d723 | 254 | { |
23b48d13 | 255 | // generate bold amount string |
70074029 | 256 | QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); |
23b48d13 PK |
257 | amount.append("</b>"); |
258 | // generate monospace address string | |
259 | QString address = "<span style='font-family: monospace;'>" + rcp.address; | |
260 | address.append("</span>"); | |
261 | ||
262 | QString recipientElement; | |
263 | ||
c6c97e0f | 264 | if (!rcp.paymentRequest.IsInitialized()) // normal payment |
a41d5fe0 | 265 | { |
23b48d13 | 266 | if(rcp.label.length() > 0) // label with address |
9e8904f6 | 267 | { |
23b48d13 PK |
268 | recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label)); |
269 | recipientElement.append(QString(" (%1)").arg(address)); | |
9e8904f6 | 270 | } |
23b48d13 | 271 | else // just address |
9e8904f6 | 272 | { |
23b48d13 | 273 | recipientElement = tr("%1 to %2").arg(amount, address); |
9e8904f6 | 274 | } |
a41d5fe0 | 275 | } |
c6c97e0f | 276 | else if(!rcp.authenticatedMerchant.isEmpty()) // secure payment request |
a41d5fe0 | 277 | { |
23b48d13 | 278 | recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant)); |
a41d5fe0 | 279 | } |
c6c97e0f PK |
280 | else // insecure payment request |
281 | { | |
282 | recipientElement = tr("%1 to %2").arg(amount, address); | |
283 | } | |
23b48d13 PK |
284 | |
285 | formatted.append(recipientElement); | |
a5e6d723 | 286 | } |
38deedc1 | 287 | |
9e8904f6 JS |
288 | QString questionString = tr("Are you sure you want to send?"); |
289 | questionString.append("<br /><br />%1"); | |
290 | ||
291 | if(txFee > 0) | |
292 | { | |
293 | // append fee string if a fee is required | |
294 | questionString.append("<hr /><span style='color:#aa0000;'>"); | |
70074029 | 295 | questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee)); |
9e8904f6 JS |
296 | questionString.append("</span> "); |
297 | questionString.append(tr("added as transaction fee")); | |
c1c9d5b4 CL |
298 | |
299 | // append transaction size | |
300 | questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB)"); | |
9e8904f6 | 301 | } |
fb0507fe WL |
302 | |
303 | // add total amount in all subdivision units | |
304 | questionString.append("<hr />"); | |
a372168e | 305 | CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee; |
fb0507fe WL |
306 | QStringList alternativeUnits; |
307 | foreach(BitcoinUnits::Unit u, BitcoinUnits::availableUnits()) | |
9e8904f6 | 308 | { |
fb0507fe | 309 | if(u != model->getOptionsModel()->getDisplayUnit()) |
70074029 | 310 | alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount)); |
9e8904f6 | 311 | } |
fb0507fe | 312 | questionString.append(tr("Total Amount %1 (= %2)") |
70074029 | 313 | .arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount)) |
84b695cc | 314 | .arg(alternativeUnits.join(" " + tr("or") + " "))); |
9e8904f6 JS |
315 | |
316 | QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"), | |
317 | questionString.arg(formatted.join("<br />")), | |
23b48d13 | 318 | QMessageBox::Yes | QMessageBox::Cancel, |
9e8904f6 JS |
319 | QMessageBox::Cancel); |
320 | ||
321 | if(retval != QMessageBox::Yes) | |
322 | { | |
323 | fNewRecipientAllowed = true; | |
324 | return; | |
325 | } | |
326 | ||
327 | // now send the prepared transaction | |
71ba4670 PK |
328 | WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction); |
329 | // process sendStatus and on error generate message shown to user | |
330 | processSendCoinsReturn(sendStatus); | |
331 | ||
332 | if (sendStatus.status == WalletModel::OK) | |
9e8904f6 | 333 | { |
92f20d53 | 334 | accept(); |
6a86c24d CL |
335 | CoinControlDialog::coinControl->UnSelectAll(); |
336 | coinControlUpdateLabels(); | |
2097c09a | 337 | } |
7d145a0f | 338 | fNewRecipientAllowed = true; |
3a7abc2c WL |
339 | } |
340 | ||
a5e6d723 | 341 | void SendCoinsDialog::clear() |
3a7abc2c | 342 | { |
a5e6d723 | 343 | // Remove entries until only one left |
db7f0234 | 344 | while(ui->entries->count()) |
a5e6d723 | 345 | { |
6c98cca9 | 346 | ui->entries->takeAt(0)->widget()->deleteLater(); |
a5e6d723 | 347 | } |
db7f0234 | 348 | addEntry(); |
4d1bb15e | 349 | |
84b695cc | 350 | updateTabsAndLabels(); |
3479849d WL |
351 | } |
352 | ||
353 | void SendCoinsDialog::reject() | |
354 | { | |
355 | clear(); | |
356 | } | |
357 | ||
358 | void SendCoinsDialog::accept() | |
359 | { | |
360 | clear(); | |
361 | } | |
a5e6d723 | 362 | |
db7f0234 | 363 | SendCoinsEntry *SendCoinsDialog::addEntry() |
a5e6d723 WL |
364 | { |
365 | SendCoinsEntry *entry = new SendCoinsEntry(this); | |
366 | entry->setModel(model); | |
367 | ui->entries->addWidget(entry); | |
368 | connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*))); | |
6a86c24d | 369 | connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels())); |
292623ad | 370 | connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels())); |
a5e6d723 | 371 | |
84b695cc | 372 | updateTabsAndLabels(); |
a5e6d723 WL |
373 | |
374 | // Focus the field, so that entry can start immediately | |
375 | entry->clear(); | |
9a93c4c0 MC |
376 | entry->setFocus(); |
377 | ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint()); | |
1ce04488 | 378 | qApp->processEvents(); |
9a93c4c0 | 379 | QScrollBar* bar = ui->scrollArea->verticalScrollBar(); |
e0873daf | 380 | if(bar) |
9a93c4c0 | 381 | bar->setSliderPosition(bar->maximum()); |
db7f0234 | 382 | return entry; |
a5e6d723 WL |
383 | } |
384 | ||
84b695cc | 385 | void SendCoinsDialog::updateTabsAndLabels() |
a5e6d723 | 386 | { |
a5e6d723 | 387 | setupTabChain(0); |
6a86c24d | 388 | coinControlUpdateLabels(); |
a5e6d723 WL |
389 | } |
390 | ||
391 | void SendCoinsDialog::removeEntry(SendCoinsEntry* entry) | |
392 | { | |
24646ee7 | 393 | entry->hide(); |
84b695cc | 394 | |
24646ee7 PK |
395 | // If the last entry is about to be removed add an empty one |
396 | if (ui->entries->count() == 1) | |
84b695cc PK |
397 | addEntry(); |
398 | ||
24646ee7 PK |
399 | entry->deleteLater(); |
400 | ||
84b695cc | 401 | updateTabsAndLabels(); |
a5e6d723 WL |
402 | } |
403 | ||
404 | QWidget *SendCoinsDialog::setupTabChain(QWidget *prev) | |
405 | { | |
406 | for(int i = 0; i < ui->entries->count(); ++i) | |
407 | { | |
408 | SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); | |
409 | if(entry) | |
410 | { | |
411 | prev = entry->setupTabChain(prev); | |
412 | } | |
413 | } | |
69d03bc6 WL |
414 | QWidget::setTabOrder(prev, ui->sendButton); |
415 | QWidget::setTabOrder(ui->sendButton, ui->clearButton); | |
416 | QWidget::setTabOrder(ui->clearButton, ui->addButton); | |
417 | return ui->addButton; | |
a5e6d723 | 418 | } |
db7f0234 | 419 | |
311993ab PK |
420 | void SendCoinsDialog::setAddress(const QString &address) |
421 | { | |
422 | SendCoinsEntry *entry = 0; | |
423 | // Replace the first entry if it is still unused | |
424 | if(ui->entries->count() == 1) | |
425 | { | |
426 | SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget()); | |
427 | if(first->isClear()) | |
428 | { | |
429 | entry = first; | |
430 | } | |
431 | } | |
432 | if(!entry) | |
433 | { | |
434 | entry = addEntry(); | |
435 | } | |
436 | ||
437 | entry->setAddress(address); | |
438 | } | |
439 | ||
db7f0234 WL |
440 | void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) |
441 | { | |
e0873daf | 442 | if(!fNewRecipientAllowed) |
7d145a0f MC |
443 | return; |
444 | ||
db7f0234 WL |
445 | SendCoinsEntry *entry = 0; |
446 | // Replace the first entry if it is still unused | |
447 | if(ui->entries->count() == 1) | |
448 | { | |
449 | SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget()); | |
450 | if(first->isClear()) | |
451 | { | |
452 | entry = first; | |
453 | } | |
454 | } | |
455 | if(!entry) | |
456 | { | |
457 | entry = addEntry(); | |
458 | } | |
459 | ||
460 | entry->setValue(rv); | |
84b695cc | 461 | updateTabsAndLabels(); |
db7f0234 WL |
462 | } |
463 | ||
a41d5fe0 | 464 | bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv) |
db7f0234 | 465 | { |
bdc83e8f PK |
466 | // Just paste the entry, all pre-checks |
467 | // are done in paymentserver.cpp. | |
a41d5fe0 GA |
468 | pasteEntry(rv); |
469 | return true; | |
db7f0234 | 470 | } |
b8afa21f | 471 | |
a328dd60 | 472 | void SendCoinsDialog::setBalance(const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance, |
a372168e | 473 | const CAmount& watchBalance, const CAmount& watchUnconfirmedBalance, const CAmount& watchImmatureBalance) |
b8afa21f WL |
474 | { |
475 | Q_UNUSED(unconfirmedBalance); | |
8fdb7e10 | 476 | Q_UNUSED(immatureBalance); |
ffd40da3 J |
477 | Q_UNUSED(watchBalance); |
478 | Q_UNUSED(watchUnconfirmedBalance); | |
479 | Q_UNUSED(watchImmatureBalance); | |
dead0ff8 | 480 | |
bdd0c59a PK |
481 | if(model && model->getOptionsModel()) |
482 | { | |
483 | ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance)); | |
484 | } | |
b8afa21f | 485 | } |
e0873daf PK |
486 | |
487 | void SendCoinsDialog::updateDisplayUnit() | |
488 | { | |
ffd40da3 | 489 | setBalance(model->getBalance(), 0, 0, 0, 0, 0); |
c1c9d5b4 CL |
490 | ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); |
491 | updateMinFeeLabel(); | |
492 | updateSmartFeeLabel(); | |
e0873daf | 493 | } |
71ba4670 PK |
494 | |
495 | void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg) | |
496 | { | |
497 | QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams; | |
498 | // Default to a warning message, override if error message is needed | |
499 | msgParams.second = CClientUIInterface::MSG_WARNING; | |
500 | ||
501 | // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn. | |
502 | // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins() | |
503 | // all others are used only in WalletModel::prepareTransaction() | |
504 | switch(sendCoinsReturn.status) | |
505 | { | |
506 | case WalletModel::InvalidAddress: | |
507 | msgParams.first = tr("The recipient address is not valid, please recheck."); | |
508 | break; | |
509 | case WalletModel::InvalidAmount: | |
510 | msgParams.first = tr("The amount to pay must be larger than 0."); | |
511 | break; | |
512 | case WalletModel::AmountExceedsBalance: | |
513 | msgParams.first = tr("The amount exceeds your balance."); | |
514 | break; | |
515 | case WalletModel::AmountWithFeeExceedsBalance: | |
516 | msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg); | |
517 | break; | |
518 | case WalletModel::DuplicateAddress: | |
519 | msgParams.first = tr("Duplicate address found, can only send to each address once per send operation."); | |
520 | break; | |
521 | case WalletModel::TransactionCreationFailed: | |
522 | msgParams.first = tr("Transaction creation failed!"); | |
523 | msgParams.second = CClientUIInterface::MSG_ERROR; | |
524 | break; | |
525 | case WalletModel::TransactionCommitFailed: | |
526 | msgParams.first = tr("The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); | |
527 | msgParams.second = CClientUIInterface::MSG_ERROR; | |
528 | break; | |
1371e6f5 DH |
529 | case WalletModel::AbsurdFee: |
530 | msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), 10000000)); | |
c1c9d5b4 | 531 | break; |
6715efb9 PK |
532 | case WalletModel::PaymentRequestExpired: |
533 | msgParams.first = tr("Payment request expired!"); | |
534 | msgParams.second = CClientUIInterface::MSG_ERROR; | |
535 | break; | |
4a61c394 | 536 | // included to prevent a compiler warning. |
71ba4670 | 537 | case WalletModel::OK: |
71ba4670 PK |
538 | default: |
539 | return; | |
540 | } | |
541 | ||
542 | emit message(tr("Send Coins"), msgParams.first, msgParams.second); | |
543 | } | |
6a86c24d | 544 | |
c1c9d5b4 CL |
545 | void SendCoinsDialog::minimizeFeeSection(bool fMinimize) |
546 | { | |
547 | ui->labelFeeMinimized->setVisible(fMinimize); | |
548 | ui->buttonChooseFee ->setVisible(fMinimize); | |
549 | ui->buttonMinimizeFee->setVisible(!fMinimize); | |
550 | ui->frameFeeSelection->setVisible(!fMinimize); | |
551 | ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0); | |
552 | fFeeMinimized = fMinimize; | |
553 | } | |
554 | ||
555 | void SendCoinsDialog::on_buttonChooseFee_clicked() | |
556 | { | |
557 | minimizeFeeSection(false); | |
558 | } | |
559 | ||
560 | void SendCoinsDialog::on_buttonMinimizeFee_clicked() | |
561 | { | |
562 | updateFeeMinimizedLabel(); | |
563 | minimizeFeeSection(true); | |
564 | } | |
565 | ||
566 | void SendCoinsDialog::setMinimumFee() | |
567 | { | |
568 | ui->radioCustomPerKilobyte->setChecked(true); | |
569 | ui->customFee->setValue(CWallet::minTxFee.GetFeePerK()); | |
570 | } | |
571 | ||
572 | void SendCoinsDialog::updateFeeSectionControls() | |
573 | { | |
574 | ui->sliderSmartFee ->setEnabled(ui->radioSmartFee->isChecked()); | |
575 | ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked()); | |
576 | ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked()); | |
577 | ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked()); | |
578 | ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked()); | |
579 | ui->labelSmartFeeNormal ->setEnabled(ui->radioSmartFee->isChecked()); | |
580 | ui->labelSmartFeeFast ->setEnabled(ui->radioSmartFee->isChecked()); | |
581 | ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked()); | |
582 | ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked()); | |
583 | ui->radioCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); | |
584 | ui->radioCustomAtLeast ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); | |
585 | ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); | |
586 | } | |
587 | ||
588 | void SendCoinsDialog::updateGlobalFeeVariables() | |
589 | { | |
590 | if (ui->radioSmartFee->isChecked()) | |
591 | { | |
592 | nTxConfirmTarget = (int)25 - (int)std::max(0, std::min(24, ui->sliderSmartFee->value())); | |
593 | payTxFee = CFeeRate(0); | |
594 | } | |
595 | else | |
596 | { | |
597 | nTxConfirmTarget = 25; | |
598 | payTxFee = CFeeRate(ui->customFee->value()); | |
599 | fPayAtLeastCustomFee = ui->radioCustomAtLeast->isChecked(); | |
600 | } | |
601 | ||
602 | fSendFreeTransactions = ui->checkBoxFreeTx->isChecked(); | |
603 | } | |
604 | ||
605 | void SendCoinsDialog::updateFeeMinimizedLabel() | |
606 | { | |
607 | if(!model || !model->getOptionsModel()) | |
608 | return; | |
609 | ||
610 | if (ui->radioSmartFee->isChecked()) | |
611 | ui->labelFeeMinimized->setText(ui->labelSmartFee->text()); | |
612 | else { | |
613 | ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) + | |
614 | ((ui->radioCustomPerKilobyte->isChecked()) ? "/kB" : "")); | |
615 | } | |
616 | } | |
617 | ||
618 | void SendCoinsDialog::updateMinFeeLabel() | |
619 | { | |
620 | if (model && model->getOptionsModel()) | |
621 | ui->checkBoxMinimumFee->setText(tr("Pay only the minimum fee of %1").arg( | |
622 | BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), CWallet::minTxFee.GetFeePerK()) + "/kB") | |
623 | ); | |
624 | } | |
625 | ||
626 | void SendCoinsDialog::updateSmartFeeLabel() | |
627 | { | |
628 | if(!model || !model->getOptionsModel()) | |
629 | return; | |
630 | ||
631 | int nBlocksToConfirm = (int)25 - (int)std::max(0, std::min(24, ui->sliderSmartFee->value())); | |
632 | CFeeRate feeRate = mempool.estimateFee(nBlocksToConfirm); | |
633 | if (feeRate <= CFeeRate(0)) // not enough data => minfee | |
634 | { | |
635 | ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), CWallet::minTxFee.GetFeePerK()) + "/kB"); | |
636 | ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...) | |
637 | ui->labelFeeEstimation->setText(""); | |
638 | } | |
639 | else | |
640 | { | |
641 | ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB"); | |
642 | ui->labelSmartFee2->hide(); | |
2747f7cf | 643 | ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", nBlocksToConfirm)); |
c1c9d5b4 CL |
644 | } |
645 | ||
646 | updateFeeMinimizedLabel(); | |
647 | } | |
648 | ||
6a86c24d CL |
649 | // Coin Control: copy label "Quantity" to clipboard |
650 | void SendCoinsDialog::coinControlClipboardQuantity() | |
651 | { | |
652 | GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); | |
653 | } | |
654 | ||
655 | // Coin Control: copy label "Amount" to clipboard | |
656 | void SendCoinsDialog::coinControlClipboardAmount() | |
657 | { | |
658 | GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" "))); | |
659 | } | |
660 | ||
661 | // Coin Control: copy label "Fee" to clipboard | |
662 | void SendCoinsDialog::coinControlClipboardFee() | |
663 | { | |
1d84aead | 664 | GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); |
6a86c24d CL |
665 | } |
666 | ||
667 | // Coin Control: copy label "After fee" to clipboard | |
668 | void SendCoinsDialog::coinControlClipboardAfterFee() | |
669 | { | |
1d84aead | 670 | GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); |
6a86c24d CL |
671 | } |
672 | ||
673 | // Coin Control: copy label "Bytes" to clipboard | |
674 | void SendCoinsDialog::coinControlClipboardBytes() | |
675 | { | |
1d84aead | 676 | GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, "")); |
6a86c24d CL |
677 | } |
678 | ||
679 | // Coin Control: copy label "Priority" to clipboard | |
680 | void SendCoinsDialog::coinControlClipboardPriority() | |
681 | { | |
682 | GUIUtil::setClipboard(ui->labelCoinControlPriority->text()); | |
683 | } | |
684 | ||
95a93836 | 685 | // Coin Control: copy label "Dust" to clipboard |
6a86c24d CL |
686 | void SendCoinsDialog::coinControlClipboardLowOutput() |
687 | { | |
688 | GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); | |
689 | } | |
690 | ||
691 | // Coin Control: copy label "Change" to clipboard | |
692 | void SendCoinsDialog::coinControlClipboardChange() | |
693 | { | |
1d84aead | 694 | GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, "")); |
6a86c24d CL |
695 | } |
696 | ||
697 | // Coin Control: settings menu - coin control enabled/disabled by user | |
698 | void SendCoinsDialog::coinControlFeatureChanged(bool checked) | |
699 | { | |
700 | ui->frameCoinControl->setVisible(checked); | |
701 | ||
702 | if (!checked && model) // coin control features disabled | |
703 | CoinControlDialog::coinControl->SetNull(); | |
edd735da CL |
704 | |
705 | if (checked) | |
706 | coinControlUpdateLabels(); | |
6a86c24d CL |
707 | } |
708 | ||
709 | // Coin Control: button inputs -> show actual coin control dialog | |
710 | void SendCoinsDialog::coinControlButtonClicked() | |
711 | { | |
712 | CoinControlDialog dlg; | |
713 | dlg.setModel(model); | |
714 | dlg.exec(); | |
715 | coinControlUpdateLabels(); | |
716 | } | |
717 | ||
718 | // Coin Control: checkbox custom change address | |
719 | void SendCoinsDialog::coinControlChangeChecked(int state) | |
720 | { | |
834e14e5 | 721 | if (state == Qt::Unchecked) |
6a86c24d | 722 | { |
834e14e5 | 723 | CoinControlDialog::coinControl->destChange = CNoDestination(); |
834e14e5 | 724 | ui->labelCoinControlChangeLabel->clear(); |
6a86c24d | 725 | } |
834e14e5 PK |
726 | else |
727 | // use this to re-validate an already entered address | |
728 | coinControlChangeEdited(ui->lineEditCoinControlChange->text()); | |
6a86c24d CL |
729 | |
730 | ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked)); | |
6a86c24d CL |
731 | } |
732 | ||
733 | // Coin Control: custom change address changed | |
24646ee7 | 734 | void SendCoinsDialog::coinControlChangeEdited(const QString& text) |
6a86c24d | 735 | { |
3380713a | 736 | if (model && model->getAddressTableModel()) |
6a86c24d | 737 | { |
3380713a PK |
738 | // Default to no change address until verified |
739 | CoinControlDialog::coinControl->destChange = CNoDestination(); | |
740 | ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}"); | |
741 | ||
742 | CBitcoinAddress addr = CBitcoinAddress(text.toStdString()); | |
6a86c24d | 743 | |
3380713a PK |
744 | if (text.isEmpty()) // Nothing entered |
745 | { | |
6a86c24d | 746 | ui->labelCoinControlChangeLabel->setText(""); |
3380713a PK |
747 | } |
748 | else if (!addr.IsValid()) // Invalid address | |
6a86c24d | 749 | { |
6a86c24d CL |
750 | ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address")); |
751 | } | |
3380713a | 752 | else // Valid address |
6a86c24d | 753 | { |
3380713a PK |
754 | CPubKey pubkey; |
755 | CKeyID keyid; | |
756 | addr.GetKeyID(keyid); | |
757 | if (!model->getPubKey(keyid, pubkey)) // Unknown change address | |
6a86c24d | 758 | { |
3380713a PK |
759 | ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address")); |
760 | } | |
761 | else // Known change address | |
762 | { | |
763 | ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}"); | |
764 | ||
765 | // Query label | |
766 | QString associatedLabel = model->getAddressTableModel()->labelForAddress(text); | |
767 | if (!associatedLabel.isEmpty()) | |
768 | ui->labelCoinControlChangeLabel->setText(associatedLabel); | |
6a86c24d | 769 | else |
3380713a PK |
770 | ui->labelCoinControlChangeLabel->setText(tr("(no label)")); |
771 | ||
772 | CoinControlDialog::coinControl->destChange = addr.Get(); | |
6a86c24d CL |
773 | } |
774 | } | |
775 | } | |
776 | } | |
777 | ||
778 | // Coin Control: update labels | |
779 | void SendCoinsDialog::coinControlUpdateLabels() | |
780 | { | |
781 | if (!model || !model->getOptionsModel() || !model->getOptionsModel()->getCoinControlFeatures()) | |
782 | return; | |
783 | ||
784 | // set pay amounts | |
785 | CoinControlDialog::payAmounts.clear(); | |
292623ad | 786 | CoinControlDialog::fSubtractFeeFromAmount = false; |
6a86c24d CL |
787 | for(int i = 0; i < ui->entries->count(); ++i) |
788 | { | |
789 | SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); | |
790 | if(entry) | |
292623ad CL |
791 | { |
792 | SendCoinsRecipient rcp = entry->getValue(); | |
793 | CoinControlDialog::payAmounts.append(rcp.amount); | |
794 | if (rcp.fSubtractFeeFromAmount) | |
795 | CoinControlDialog::fSubtractFeeFromAmount = true; | |
796 | } | |
6a86c24d CL |
797 | } |
798 | ||
799 | if (CoinControlDialog::coinControl->HasSelected()) | |
800 | { | |
801 | // actual coin control calculation | |
802 | CoinControlDialog::updateLabels(model, this); | |
803 | ||
804 | // show coin control stats | |
805 | ui->labelCoinControlAutomaticallySelected->hide(); | |
806 | ui->widgetCoinControl->show(); | |
807 | } | |
808 | else | |
809 | { | |
810 | // hide coin control stats | |
811 | ui->labelCoinControlAutomaticallySelected->show(); | |
812 | ui->widgetCoinControl->hide(); | |
813 | ui->labelCoinControlInsuffFunds->hide(); | |
814 | } | |
815 | } |