]>
Commit | Line | Data |
---|---|---|
e592d43f WL |
1 | // Copyright (c) 2011-2013 The Bitcoin developers |
2 | // Distributed under the MIT/X11 software license, see the accompanying | |
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 | |
e285ffcd | 8 | #include "bitcoinunits.h" |
968d55aa | 9 | #include "optionsmodel.h" |
a5e6d723 | 10 | #include "sendcoinsentry.h" |
db7f0234 | 11 | #include "guiutil.h" |
b7bcaf94 | 12 | #include "askpassphrasedialog.h" |
2bc15836 | 13 | #include "base58.h" |
71ba4670 | 14 | #include "ui_interface.h" |
3a7abc2c | 15 | |
992ff49b | 16 | #include <QMessageBox> |
dedf83a1 | 17 | #include <QTextDocument> |
9a93c4c0 | 18 | #include <QScrollBar> |
3f323a61 | 19 | |
a5e6d723 | 20 | SendCoinsDialog::SendCoinsDialog(QWidget *parent) : |
df577886 | 21 | QDialog(parent), |
6630c1cb WL |
22 | ui(new Ui::SendCoinsDialog), |
23 | model(0) | |
df577886 WL |
24 | { |
25 | ui->setupUi(this); | |
83b82370 | 26 | |
81605d90 | 27 | #ifdef Q_OS_MAC // Icons on push buttons are very uncommon on Mac |
527137e3 | 28 | ui->addButton->setIcon(QIcon()); |
29 | ui->clearButton->setIcon(QIcon()); | |
30 | ui->sendButton->setIcon(QIcon()); | |
31 | #endif | |
32 | ||
a5e6d723 | 33 | addEntry(); |
992ff49b | 34 | |
a5e6d723 | 35 | connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry())); |
609acbf4 | 36 | connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); |
7d145a0f MC |
37 | |
38 | fNewRecipientAllowed = true; | |
df577886 WL |
39 | } |
40 | ||
ef079e18 | 41 | void SendCoinsDialog::setModel(WalletModel *model) |
6630c1cb WL |
42 | { |
43 | this->model = model; | |
a5e6d723 | 44 | |
6728e007 | 45 | if(model && model->getOptionsModel()) |
a5e6d723 | 46 | { |
6728e007 | 47 | for(int i = 0; i < ui->entries->count(); ++i) |
a5e6d723 | 48 | { |
6728e007 PK |
49 | SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); |
50 | if(entry) | |
51 | { | |
52 | entry->setModel(model); | |
53 | } | |
a5e6d723 | 54 | } |
6728e007 | 55 | |
8fdb7e10 | 56 | setBalance(model->getBalance(), model->getUnconfirmedBalance(), model->getImmatureBalance()); |
57 | connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64))); | |
e0873daf | 58 | connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); |
dead0ff8 | 59 | } |
6630c1cb WL |
60 | } |
61 | ||
df577886 WL |
62 | SendCoinsDialog::~SendCoinsDialog() |
63 | { | |
64 | delete ui; | |
65 | } | |
3a7abc2c WL |
66 | |
67 | void SendCoinsDialog::on_sendButton_clicked() | |
68 | { | |
bdd0c59a PK |
69 | if(!model || !model->getOptionsModel()) |
70 | return; | |
71 | ||
a5e6d723 WL |
72 | QList<SendCoinsRecipient> recipients; |
73 | bool valid = true; | |
dead0ff8 | 74 | |
a5e6d723 WL |
75 | for(int i = 0; i < ui->entries->count(); ++i) |
76 | { | |
77 | SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); | |
78 | if(entry) | |
79 | { | |
80 | if(entry->validate()) | |
81 | { | |
82 | recipients.append(entry->getValue()); | |
83 | } | |
84 | else | |
85 | { | |
86 | valid = false; | |
87 | } | |
88 | } | |
89 | } | |
6630c1cb | 90 | |
a5e6d723 | 91 | if(!valid || recipients.isEmpty()) |
2097c09a | 92 | { |
992ff49b | 93 | return; |
2097c09a | 94 | } |
0eba0044 | 95 | |
a5e6d723 WL |
96 | // Format confirmation message |
97 | QStringList formatted; | |
98 | foreach(const SendCoinsRecipient &rcp, recipients) | |
99 | { | |
23b48d13 PK |
100 | // generate bold amount string |
101 | QString amount = "<b>" + BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); | |
102 | amount.append("</b>"); | |
103 | // generate monospace address string | |
104 | QString address = "<span style='font-family: monospace;'>" + rcp.address; | |
105 | address.append("</span>"); | |
106 | ||
107 | QString recipientElement; | |
108 | ||
c6c97e0f | 109 | if (!rcp.paymentRequest.IsInitialized()) // normal payment |
a41d5fe0 | 110 | { |
23b48d13 | 111 | if(rcp.label.length() > 0) // label with address |
9e8904f6 | 112 | { |
23b48d13 PK |
113 | recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label)); |
114 | recipientElement.append(QString(" (%1)").arg(address)); | |
9e8904f6 | 115 | } |
23b48d13 | 116 | else // just address |
9e8904f6 | 117 | { |
23b48d13 | 118 | recipientElement = tr("%1 to %2").arg(amount, address); |
9e8904f6 | 119 | } |
a41d5fe0 | 120 | } |
c6c97e0f | 121 | else if(!rcp.authenticatedMerchant.isEmpty()) // secure payment request |
a41d5fe0 | 122 | { |
23b48d13 | 123 | recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant)); |
a41d5fe0 | 124 | } |
c6c97e0f PK |
125 | else // insecure payment request |
126 | { | |
127 | recipientElement = tr("%1 to %2").arg(amount, address); | |
128 | } | |
23b48d13 PK |
129 | |
130 | formatted.append(recipientElement); | |
a5e6d723 | 131 | } |
38deedc1 | 132 | |
7d145a0f MC |
133 | fNewRecipientAllowed = false; |
134 | ||
3479849d | 135 | |
b7bcaf94 WL |
136 | WalletModel::UnlockContext ctx(model->requestUnlock()); |
137 | if(!ctx.isValid()) | |
138 | { | |
139 | // Unlock wallet was cancelled | |
7d145a0f | 140 | fNewRecipientAllowed = true; |
b7bcaf94 WL |
141 | return; |
142 | } | |
143 | ||
9e8904f6 JS |
144 | // prepare transaction for getting txFee earlier |
145 | WalletModelTransaction currentTransaction(recipients); | |
146 | WalletModel::SendCoinsReturn prepareStatus = model->prepareTransaction(currentTransaction); | |
71ba4670 PK |
147 | // process prepareStatus and on error generate message shown to user |
148 | processSendCoinsReturn(prepareStatus, | |
149 | BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee())); | |
9e8904f6 JS |
150 | |
151 | if(prepareStatus.status != WalletModel::OK) { | |
152 | fNewRecipientAllowed = true; | |
153 | return; | |
154 | } | |
155 | ||
156 | qint64 txFee = currentTransaction.getTransactionFee(); | |
157 | QString questionString = tr("Are you sure you want to send?"); | |
158 | questionString.append("<br /><br />%1"); | |
159 | ||
160 | if(txFee > 0) | |
161 | { | |
162 | // append fee string if a fee is required | |
163 | questionString.append("<hr /><span style='color:#aa0000;'>"); | |
164 | questionString.append(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee)); | |
165 | questionString.append("</span> "); | |
166 | questionString.append(tr("added as transaction fee")); | |
167 | } | |
168 | if(txFee > 0 || recipients.count() > 1) | |
169 | { | |
170 | // add total amount string if there are more then one recipients or a fee is required | |
171 | questionString.append("<hr />"); | |
172 | questionString.append(tr("Total Amount %1").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTotalTransactionAmount()+txFee))); | |
173 | } | |
174 | ||
175 | QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"), | |
176 | questionString.arg(formatted.join("<br />")), | |
23b48d13 | 177 | QMessageBox::Yes | QMessageBox::Cancel, |
9e8904f6 JS |
178 | QMessageBox::Cancel); |
179 | ||
180 | if(retval != QMessageBox::Yes) | |
181 | { | |
182 | fNewRecipientAllowed = true; | |
183 | return; | |
184 | } | |
185 | ||
186 | // now send the prepared transaction | |
71ba4670 PK |
187 | WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction); |
188 | // process sendStatus and on error generate message shown to user | |
189 | processSendCoinsReturn(sendStatus); | |
190 | ||
191 | if (sendStatus.status == WalletModel::OK) | |
9e8904f6 | 192 | { |
92f20d53 | 193 | accept(); |
2097c09a | 194 | } |
7d145a0f | 195 | fNewRecipientAllowed = true; |
3a7abc2c WL |
196 | } |
197 | ||
a5e6d723 | 198 | void SendCoinsDialog::clear() |
3a7abc2c | 199 | { |
a5e6d723 | 200 | // Remove entries until only one left |
db7f0234 | 201 | while(ui->entries->count()) |
a5e6d723 WL |
202 | { |
203 | delete ui->entries->takeAt(0)->widget(); | |
204 | } | |
db7f0234 | 205 | addEntry(); |
4d1bb15e | 206 | |
a5e6d723 | 207 | updateRemoveEnabled(); |
3479849d | 208 | |
3ddf10e5 | 209 | ui->sendButton->setDefault(true); |
3479849d WL |
210 | } |
211 | ||
212 | void SendCoinsDialog::reject() | |
213 | { | |
214 | clear(); | |
215 | } | |
216 | ||
217 | void SendCoinsDialog::accept() | |
218 | { | |
219 | clear(); | |
220 | } | |
a5e6d723 | 221 | |
db7f0234 | 222 | SendCoinsEntry *SendCoinsDialog::addEntry() |
a5e6d723 WL |
223 | { |
224 | SendCoinsEntry *entry = new SendCoinsEntry(this); | |
225 | entry->setModel(model); | |
226 | ui->entries->addWidget(entry); | |
227 | connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*))); | |
228 | ||
229 | updateRemoveEnabled(); | |
230 | ||
231 | // Focus the field, so that entry can start immediately | |
232 | entry->clear(); | |
9a93c4c0 MC |
233 | entry->setFocus(); |
234 | ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint()); | |
1ce04488 | 235 | qApp->processEvents(); |
9a93c4c0 | 236 | QScrollBar* bar = ui->scrollArea->verticalScrollBar(); |
e0873daf | 237 | if(bar) |
9a93c4c0 | 238 | bar->setSliderPosition(bar->maximum()); |
db7f0234 | 239 | return entry; |
a5e6d723 WL |
240 | } |
241 | ||
242 | void SendCoinsDialog::updateRemoveEnabled() | |
243 | { | |
244 | // Remove buttons are enabled as soon as there is more than one send-entry | |
245 | bool enabled = (ui->entries->count() > 1); | |
246 | for(int i = 0; i < ui->entries->count(); ++i) | |
247 | { | |
248 | SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); | |
249 | if(entry) | |
250 | { | |
251 | entry->setRemoveEnabled(enabled); | |
252 | } | |
253 | } | |
254 | setupTabChain(0); | |
255 | } | |
256 | ||
257 | void SendCoinsDialog::removeEntry(SendCoinsEntry* entry) | |
258 | { | |
259 | delete entry; | |
260 | updateRemoveEnabled(); | |
261 | } | |
262 | ||
263 | QWidget *SendCoinsDialog::setupTabChain(QWidget *prev) | |
264 | { | |
265 | for(int i = 0; i < ui->entries->count(); ++i) | |
266 | { | |
267 | SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); | |
268 | if(entry) | |
269 | { | |
270 | prev = entry->setupTabChain(prev); | |
271 | } | |
272 | } | |
273 | QWidget::setTabOrder(prev, ui->addButton); | |
274 | QWidget::setTabOrder(ui->addButton, ui->sendButton); | |
275 | return ui->sendButton; | |
276 | } | |
db7f0234 | 277 | |
311993ab PK |
278 | void SendCoinsDialog::setAddress(const QString &address) |
279 | { | |
280 | SendCoinsEntry *entry = 0; | |
281 | // Replace the first entry if it is still unused | |
282 | if(ui->entries->count() == 1) | |
283 | { | |
284 | SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget()); | |
285 | if(first->isClear()) | |
286 | { | |
287 | entry = first; | |
288 | } | |
289 | } | |
290 | if(!entry) | |
291 | { | |
292 | entry = addEntry(); | |
293 | } | |
294 | ||
295 | entry->setAddress(address); | |
296 | } | |
297 | ||
db7f0234 WL |
298 | void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) |
299 | { | |
e0873daf | 300 | if(!fNewRecipientAllowed) |
7d145a0f MC |
301 | return; |
302 | ||
db7f0234 WL |
303 | SendCoinsEntry *entry = 0; |
304 | // Replace the first entry if it is still unused | |
305 | if(ui->entries->count() == 1) | |
306 | { | |
307 | SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget()); | |
308 | if(first->isClear()) | |
309 | { | |
310 | entry = first; | |
311 | } | |
312 | } | |
313 | if(!entry) | |
314 | { | |
315 | entry = addEntry(); | |
316 | } | |
317 | ||
318 | entry->setValue(rv); | |
319 | } | |
320 | ||
a41d5fe0 | 321 | bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv) |
db7f0234 | 322 | { |
23b48d13 | 323 | QString strSendCoins = tr("Send Coins"); |
c6c97e0f | 324 | if (rv.paymentRequest.IsInitialized()) { |
a41d5fe0 GA |
325 | // Expired payment request? |
326 | const payments::PaymentDetails& details = rv.paymentRequest.getDetails(); | |
327 | if (details.has_expires() && (int64)details.expires() < GetTime()) | |
328 | { | |
71ba4670 PK |
329 | emit message(strSendCoins, tr("Payment request expired"), |
330 | CClientUIInterface::MSG_WARNING); | |
a41d5fe0 GA |
331 | return false; |
332 | } | |
333 | } | |
334 | else { | |
2bc15836 | 335 | CBitcoinAddress address(rv.address.toStdString()); |
a41d5fe0 | 336 | if (!address.IsValid()) { |
71ba4670 PK |
337 | emit message(strSendCoins, tr("Invalid payment address %1").arg(rv.address), |
338 | CClientUIInterface::MSG_WARNING); | |
2bc15836 | 339 | return false; |
a41d5fe0 | 340 | } |
db7f0234 | 341 | } |
93b7af30 | 342 | |
a41d5fe0 GA |
343 | pasteEntry(rv); |
344 | return true; | |
db7f0234 | 345 | } |
b8afa21f | 346 | |
8fdb7e10 | 347 | void SendCoinsDialog::setBalance(qint64 balance, qint64 unconfirmedBalance, qint64 immatureBalance) |
b8afa21f WL |
348 | { |
349 | Q_UNUSED(unconfirmedBalance); | |
8fdb7e10 | 350 | Q_UNUSED(immatureBalance); |
dead0ff8 | 351 | |
bdd0c59a PK |
352 | if(model && model->getOptionsModel()) |
353 | { | |
354 | ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance)); | |
355 | } | |
b8afa21f | 356 | } |
e0873daf PK |
357 | |
358 | void SendCoinsDialog::updateDisplayUnit() | |
359 | { | |
bdd0c59a | 360 | setBalance(model->getBalance(), 0, 0); |
e0873daf | 361 | } |
71ba4670 PK |
362 | |
363 | void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg) | |
364 | { | |
365 | QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams; | |
366 | // Default to a warning message, override if error message is needed | |
367 | msgParams.second = CClientUIInterface::MSG_WARNING; | |
368 | ||
369 | // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn. | |
370 | // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins() | |
371 | // all others are used only in WalletModel::prepareTransaction() | |
372 | switch(sendCoinsReturn.status) | |
373 | { | |
374 | case WalletModel::InvalidAddress: | |
375 | msgParams.first = tr("The recipient address is not valid, please recheck."); | |
376 | break; | |
377 | case WalletModel::InvalidAmount: | |
378 | msgParams.first = tr("The amount to pay must be larger than 0."); | |
379 | break; | |
380 | case WalletModel::AmountExceedsBalance: | |
381 | msgParams.first = tr("The amount exceeds your balance."); | |
382 | break; | |
383 | case WalletModel::AmountWithFeeExceedsBalance: | |
384 | msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg); | |
385 | break; | |
386 | case WalletModel::DuplicateAddress: | |
387 | msgParams.first = tr("Duplicate address found, can only send to each address once per send operation."); | |
388 | break; | |
389 | case WalletModel::TransactionCreationFailed: | |
390 | msgParams.first = tr("Transaction creation failed!"); | |
391 | msgParams.second = CClientUIInterface::MSG_ERROR; | |
392 | break; | |
393 | case WalletModel::TransactionCommitFailed: | |
394 | 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."); | |
395 | msgParams.second = CClientUIInterface::MSG_ERROR; | |
396 | break; | |
397 | // OK and Aborted are included to prevent a compiler warning. | |
398 | case WalletModel::OK: | |
399 | case WalletModel::Aborted: | |
400 | default: | |
401 | return; | |
402 | } | |
403 | ||
404 | emit message(tr("Send Coins"), msgParams.first, msgParams.second); | |
405 | } |