]> Git Repo - VerusCoin.git/blob - src/qt/sendcoinsdialog.cpp
Copyright header updates s/2013/2014 on files whose last git commit was done in 2014.
[VerusCoin.git] / src / qt / sendcoinsdialog.cpp
1 // Copyright (c) 2011-2014 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
5 #include "sendcoinsdialog.h"
6 #include "ui_sendcoinsdialog.h"
7
8 #include "addresstablemodel.h"
9 #include "bitcoinunits.h"
10 #include "coincontroldialog.h"
11 #include "guiutil.h"
12 #include "optionsmodel.h"
13 #include "sendcoinsentry.h"
14 #include "walletmodel.h"
15
16 #include "base58.h"
17 #include "coincontrol.h"
18 #include "ui_interface.h"
19
20 #include <QMessageBox>
21 #include <QScrollBar>
22 #include <QTextDocument>
23
24 SendCoinsDialog::SendCoinsDialog(QWidget *parent) :
25     QDialog(parent),
26     ui(new Ui::SendCoinsDialog),
27     model(0)
28 {
29     ui->setupUi(this);
30
31 #ifdef Q_OS_MAC // Icons on push buttons are very uncommon on Mac
32     ui->addButton->setIcon(QIcon());
33     ui->clearButton->setIcon(QIcon());
34     ui->sendButton->setIcon(QIcon());
35 #endif
36
37     GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
38
39     addEntry();
40
41     connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry()));
42     connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
43
44     // Coin Control
45     connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked()));
46     connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int)));
47     connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &)));
48
49     // Coin Control: clipboard actions
50     QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
51     QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
52     QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
53     QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
54     QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
55     QAction *clipboardPriorityAction = new QAction(tr("Copy priority"), this);
56     QAction *clipboardLowOutputAction = new QAction(tr("Copy low output"), this);
57     QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
58     connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity()));
59     connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount()));
60     connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee()));
61     connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee()));
62     connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes()));
63     connect(clipboardPriorityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardPriority()));
64     connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput()));
65     connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange()));
66     ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
67     ui->labelCoinControlAmount->addAction(clipboardAmountAction);
68     ui->labelCoinControlFee->addAction(clipboardFeeAction);
69     ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
70     ui->labelCoinControlBytes->addAction(clipboardBytesAction);
71     ui->labelCoinControlPriority->addAction(clipboardPriorityAction);
72     ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
73     ui->labelCoinControlChange->addAction(clipboardChangeAction);
74
75     fNewRecipientAllowed = true;
76 }
77
78 void SendCoinsDialog::setModel(WalletModel *model)
79 {
80     this->model = model;
81
82     if(model && model->getOptionsModel())
83     {
84         for(int i = 0; i < ui->entries->count(); ++i)
85         {
86             SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
87             if(entry)
88             {
89                 entry->setModel(model);
90             }
91         }
92
93         setBalance(model->getBalance(), model->getUnconfirmedBalance(), model->getImmatureBalance());
94         connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64)));
95         connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
96
97         // Coin Control
98         connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels()));
99         connect(model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool)));
100         connect(model->getOptionsModel(), SIGNAL(transactionFeeChanged(qint64)), this, SLOT(coinControlUpdateLabels()));
101         ui->frameCoinControl->setVisible(model->getOptionsModel()->getCoinControlFeatures());
102         coinControlUpdateLabels();
103     }
104 }
105
106 SendCoinsDialog::~SendCoinsDialog()
107 {
108     delete ui;
109 }
110
111 void SendCoinsDialog::on_sendButton_clicked()
112 {
113     if(!model || !model->getOptionsModel())
114         return;
115
116     QList<SendCoinsRecipient> recipients;
117     bool valid = true;
118
119     for(int i = 0; i < ui->entries->count(); ++i)
120     {
121         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
122         if(entry)
123         {
124             if(entry->validate())
125             {
126                 recipients.append(entry->getValue());
127             }
128             else
129             {
130                 valid = false;
131             }
132         }
133     }
134
135     if(!valid || recipients.isEmpty())
136     {
137         return;
138     }
139
140     // Format confirmation message
141     QStringList formatted;
142     foreach(const SendCoinsRecipient &rcp, recipients)
143     {
144         // generate bold amount string
145         QString amount = "<b>" + BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
146         amount.append("</b>");
147         // generate monospace address string
148         QString address = "<span style='font-family: monospace;'>" + rcp.address;
149         address.append("</span>");
150
151         QString recipientElement;
152
153         if (!rcp.paymentRequest.IsInitialized()) // normal payment
154         {
155             if(rcp.label.length() > 0) // label with address
156             {
157                 recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label));
158                 recipientElement.append(QString(" (%1)").arg(address));
159             }
160             else // just address
161             {
162                 recipientElement = tr("%1 to %2").arg(amount, address);
163             }
164         }
165         else if(!rcp.authenticatedMerchant.isEmpty()) // secure payment request
166         {
167             recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant));
168         }
169         else // insecure payment request
170         {
171             recipientElement = tr("%1 to %2").arg(amount, address);
172         }
173
174         formatted.append(recipientElement);
175     }
176
177     fNewRecipientAllowed = false;
178
179
180     WalletModel::UnlockContext ctx(model->requestUnlock());
181     if(!ctx.isValid())
182     {
183         // Unlock wallet was cancelled
184         fNewRecipientAllowed = true;
185         return;
186     }
187
188     // prepare transaction for getting txFee earlier
189     WalletModelTransaction currentTransaction(recipients);
190     WalletModel::SendCoinsReturn prepareStatus;
191     if (model->getOptionsModel()->getCoinControlFeatures()) // coin control enabled
192         prepareStatus = model->prepareTransaction(currentTransaction, CoinControlDialog::coinControl);
193     else
194         prepareStatus = model->prepareTransaction(currentTransaction);
195
196     // process prepareStatus and on error generate message shown to user
197     processSendCoinsReturn(prepareStatus,
198         BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()));
199
200     if(prepareStatus.status != WalletModel::OK) {
201         fNewRecipientAllowed = true;
202         return;
203     }
204
205     qint64 txFee = currentTransaction.getTransactionFee();
206     QString questionString = tr("Are you sure you want to send?");
207     questionString.append("<br /><br />%1");
208
209     if(txFee > 0)
210     {
211         // append fee string if a fee is required
212         questionString.append("<hr /><span style='color:#aa0000;'>");
213         questionString.append(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
214         questionString.append("</span> ");
215         questionString.append(tr("added as transaction fee"));
216     }
217
218     // add total amount in all subdivision units
219     questionString.append("<hr />");
220     qint64 totalAmount = currentTransaction.getTotalTransactionAmount() + txFee;
221     QStringList alternativeUnits;
222     foreach(BitcoinUnits::Unit u, BitcoinUnits::availableUnits())
223     {
224         if(u != model->getOptionsModel()->getDisplayUnit())
225             alternativeUnits.append(BitcoinUnits::formatWithUnit(u, totalAmount));
226     }
227     questionString.append(tr("Total Amount %1 (= %2)")
228         .arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount))
229         .arg(alternativeUnits.join(" " + tr("or") + " ")));
230
231     QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"),
232         questionString.arg(formatted.join("<br />")),
233         QMessageBox::Yes | QMessageBox::Cancel,
234         QMessageBox::Cancel);
235
236     if(retval != QMessageBox::Yes)
237     {
238         fNewRecipientAllowed = true;
239         return;
240     }
241
242     // now send the prepared transaction
243     WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction);
244     // process sendStatus and on error generate message shown to user
245     processSendCoinsReturn(sendStatus);
246
247     if (sendStatus.status == WalletModel::OK)
248     {
249         accept();
250         CoinControlDialog::coinControl->UnSelectAll();
251         coinControlUpdateLabels();
252     }
253     fNewRecipientAllowed = true;
254 }
255
256 void SendCoinsDialog::clear()
257 {
258     // Remove entries until only one left
259     while(ui->entries->count())
260     {
261         ui->entries->takeAt(0)->widget()->deleteLater();
262     }
263     addEntry();
264
265     updateTabsAndLabels();
266 }
267
268 void SendCoinsDialog::reject()
269 {
270     clear();
271 }
272
273 void SendCoinsDialog::accept()
274 {
275     clear();
276 }
277
278 SendCoinsEntry *SendCoinsDialog::addEntry()
279 {
280     SendCoinsEntry *entry = new SendCoinsEntry(this);
281     entry->setModel(model);
282     ui->entries->addWidget(entry);
283     connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
284     connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
285
286     updateTabsAndLabels();
287
288     // Focus the field, so that entry can start immediately
289     entry->clear();
290     entry->setFocus();
291     ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
292     qApp->processEvents();
293     QScrollBar* bar = ui->scrollArea->verticalScrollBar();
294     if(bar)
295         bar->setSliderPosition(bar->maximum());
296     return entry;
297 }
298
299 void SendCoinsDialog::updateTabsAndLabels()
300 {
301     setupTabChain(0);
302     coinControlUpdateLabels();
303 }
304
305 void SendCoinsDialog::removeEntry(SendCoinsEntry* entry)
306 {
307     entry->hide();
308
309     // If the last entry is about to be removed add an empty one
310     if (ui->entries->count() == 1)
311         addEntry();
312
313     entry->deleteLater();
314
315     updateTabsAndLabels();
316 }
317
318 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
319 {
320     for(int i = 0; i < ui->entries->count(); ++i)
321     {
322         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
323         if(entry)
324         {
325             prev = entry->setupTabChain(prev);
326         }
327     }
328     QWidget::setTabOrder(prev, ui->sendButton);
329     QWidget::setTabOrder(ui->sendButton, ui->clearButton);
330     QWidget::setTabOrder(ui->clearButton, ui->addButton);
331     return ui->addButton;
332 }
333
334 void SendCoinsDialog::setAddress(const QString &address)
335 {
336     SendCoinsEntry *entry = 0;
337     // Replace the first entry if it is still unused
338     if(ui->entries->count() == 1)
339     {
340         SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
341         if(first->isClear())
342         {
343             entry = first;
344         }
345     }
346     if(!entry)
347     {
348         entry = addEntry();
349     }
350
351     entry->setAddress(address);
352 }
353
354 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
355 {
356     if(!fNewRecipientAllowed)
357         return;
358
359     SendCoinsEntry *entry = 0;
360     // Replace the first entry if it is still unused
361     if(ui->entries->count() == 1)
362     {
363         SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
364         if(first->isClear())
365         {
366             entry = first;
367         }
368     }
369     if(!entry)
370     {
371         entry = addEntry();
372     }
373
374     entry->setValue(rv);
375     updateTabsAndLabels();
376 }
377
378 bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv)
379 {
380     QString strSendCoins = tr("Send Coins");
381     if (rv.paymentRequest.IsInitialized()) {
382         // Expired payment request?
383         const payments::PaymentDetails& details = rv.paymentRequest.getDetails();
384         if (details.has_expires() && (int64_t)details.expires() < GetTime())
385         {
386             emit message(strSendCoins, tr("Payment request expired"),
387                 CClientUIInterface::MSG_WARNING);
388             return false;
389         }
390     }
391     else {
392         CBitcoinAddress address(rv.address.toStdString());
393         if (!address.IsValid()) {
394             emit message(strSendCoins, tr("Invalid payment address %1").arg(rv.address),
395                 CClientUIInterface::MSG_WARNING);
396             return false;
397         }
398     }
399
400     pasteEntry(rv);
401     return true;
402 }
403
404 void SendCoinsDialog::setBalance(qint64 balance, qint64 unconfirmedBalance, qint64 immatureBalance)
405 {
406     Q_UNUSED(unconfirmedBalance);
407     Q_UNUSED(immatureBalance);
408
409     if(model && model->getOptionsModel())
410     {
411         ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
412     }
413 }
414
415 void SendCoinsDialog::updateDisplayUnit()
416 {
417     setBalance(model->getBalance(), 0, 0);
418 }
419
420 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
421 {
422     QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
423     // Default to a warning message, override if error message is needed
424     msgParams.second = CClientUIInterface::MSG_WARNING;
425
426     // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
427     // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
428     // all others are used only in WalletModel::prepareTransaction()
429     switch(sendCoinsReturn.status)
430     {
431     case WalletModel::InvalidAddress:
432         msgParams.first = tr("The recipient address is not valid, please recheck.");
433         break;
434     case WalletModel::InvalidAmount:
435         msgParams.first = tr("The amount to pay must be larger than 0.");
436         break;
437     case WalletModel::AmountExceedsBalance:
438         msgParams.first = tr("The amount exceeds your balance.");
439         break;
440     case WalletModel::AmountWithFeeExceedsBalance:
441         msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
442         break;
443     case WalletModel::DuplicateAddress:
444         msgParams.first = tr("Duplicate address found, can only send to each address once per send operation.");
445         break;
446     case WalletModel::TransactionCreationFailed:
447         msgParams.first = tr("Transaction creation failed!");
448         msgParams.second = CClientUIInterface::MSG_ERROR;
449         break;
450     case WalletModel::TransactionCommitFailed:
451         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.");
452         msgParams.second = CClientUIInterface::MSG_ERROR;
453         break;
454     // included to prevent a compiler warning.
455     case WalletModel::OK:
456     default:
457         return;
458     }
459
460     emit message(tr("Send Coins"), msgParams.first, msgParams.second);
461 }
462
463 // Coin Control: copy label "Quantity" to clipboard
464 void SendCoinsDialog::coinControlClipboardQuantity()
465 {
466     GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
467 }
468
469 // Coin Control: copy label "Amount" to clipboard
470 void SendCoinsDialog::coinControlClipboardAmount()
471 {
472     GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
473 }
474
475 // Coin Control: copy label "Fee" to clipboard
476 void SendCoinsDialog::coinControlClipboardFee()
477 {
478     GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")));
479 }
480
481 // Coin Control: copy label "After fee" to clipboard
482 void SendCoinsDialog::coinControlClipboardAfterFee()
483 {
484     GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")));
485 }
486
487 // Coin Control: copy label "Bytes" to clipboard
488 void SendCoinsDialog::coinControlClipboardBytes()
489 {
490     GUIUtil::setClipboard(ui->labelCoinControlBytes->text());
491 }
492
493 // Coin Control: copy label "Priority" to clipboard
494 void SendCoinsDialog::coinControlClipboardPriority()
495 {
496     GUIUtil::setClipboard(ui->labelCoinControlPriority->text());
497 }
498
499 // Coin Control: copy label "Low output" to clipboard
500 void SendCoinsDialog::coinControlClipboardLowOutput()
501 {
502     GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
503 }
504
505 // Coin Control: copy label "Change" to clipboard
506 void SendCoinsDialog::coinControlClipboardChange()
507 {
508     GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")));
509 }
510
511 // Coin Control: settings menu - coin control enabled/disabled by user
512 void SendCoinsDialog::coinControlFeatureChanged(bool checked)
513 {
514     ui->frameCoinControl->setVisible(checked);
515
516     if (!checked && model) // coin control features disabled
517         CoinControlDialog::coinControl->SetNull();
518
519     if (checked)
520         coinControlUpdateLabels();
521 }
522
523 // Coin Control: button inputs -> show actual coin control dialog
524 void SendCoinsDialog::coinControlButtonClicked()
525 {
526     CoinControlDialog dlg;
527     dlg.setModel(model);
528     dlg.exec();
529     coinControlUpdateLabels();
530 }
531
532 // Coin Control: checkbox custom change address
533 void SendCoinsDialog::coinControlChangeChecked(int state)
534 {
535     if (state == Qt::Unchecked)
536     {
537         CoinControlDialog::coinControl->destChange = CNoDestination();
538         ui->labelCoinControlChangeLabel->clear();
539     }
540     else
541         // use this to re-validate an already entered address
542         coinControlChangeEdited(ui->lineEditCoinControlChange->text());
543
544     ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
545 }
546
547 // Coin Control: custom change address changed
548 void SendCoinsDialog::coinControlChangeEdited(const QString& text)
549 {
550     if (model && model->getAddressTableModel())
551     {
552         // Default to no change address until verified
553         CoinControlDialog::coinControl->destChange = CNoDestination();
554         ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
555
556         CBitcoinAddress addr = CBitcoinAddress(text.toStdString());
557
558         if (text.isEmpty()) // Nothing entered
559         {
560             ui->labelCoinControlChangeLabel->setText("");
561         }
562         else if (!addr.IsValid()) // Invalid address
563         {
564             ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
565         }
566         else // Valid address
567         {
568             CPubKey pubkey;
569             CKeyID keyid;
570             addr.GetKeyID(keyid);
571             if (!model->getPubKey(keyid, pubkey)) // Unknown change address
572             {
573                 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
574             }
575             else // Known change address
576             {
577                 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
578
579                 // Query label
580                 QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
581                 if (!associatedLabel.isEmpty())
582                     ui->labelCoinControlChangeLabel->setText(associatedLabel);
583                 else
584                     ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
585
586                 CoinControlDialog::coinControl->destChange = addr.Get();
587             }
588         }
589     }
590 }
591
592 // Coin Control: update labels
593 void SendCoinsDialog::coinControlUpdateLabels()
594 {
595     if (!model || !model->getOptionsModel() || !model->getOptionsModel()->getCoinControlFeatures())
596         return;
597
598     // set pay amounts
599     CoinControlDialog::payAmounts.clear();
600     for(int i = 0; i < ui->entries->count(); ++i)
601     {
602         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
603         if(entry)
604             CoinControlDialog::payAmounts.append(entry->getValue().amount);
605     }
606
607     if (CoinControlDialog::coinControl->HasSelected())
608     {
609         // actual coin control calculation
610         CoinControlDialog::updateLabels(model, this);
611
612         // show coin control stats
613         ui->labelCoinControlAutomaticallySelected->hide();
614         ui->widgetCoinControl->show();
615     }
616     else
617     {
618         // hide coin control stats
619         ui->labelCoinControlAutomaticallySelected->show();
620         ui->widgetCoinControl->hide();
621         ui->labelCoinControlInsuffFunds->hide();
622     }
623 }
This page took 0.057406 seconds and 4 git commands to generate.