CoinControl: add paste from address book and clipboard buttons for custom change...
[novacoin.git] / src / qt / sendcoinsdialog.cpp
1 #include "sendcoinsdialog.h"
2 #include "ui_sendcoinsdialog.h"
3
4 #include "init.h"
5 #include "walletmodel.h"
6 #include "addresstablemodel.h"
7 #include "addressbookpage.h"
8
9 #include "bitcoinunits.h"
10 #include "addressbookpage.h"
11 #include "optionsmodel.h"
12 #include "sendcoinsentry.h"
13 #include "guiutil.h"
14 #include "askpassphrasedialog.h"
15
16 #include "coincontrol.h"
17 #include "coincontroldialog.h"
18
19 #include <QMessageBox>
20 #include <QLocale>
21 #include <QTextDocument>
22 #include <QScrollBar>
23 #include <QClipboard>
24
25 SendCoinsDialog::SendCoinsDialog(QWidget *parent) :
26     QDialog(parent),
27     ui(new Ui::SendCoinsDialog),
28     model(0)
29 {
30     ui->setupUi(this);
31
32 #ifdef Q_OS_MAC // Icons on push buttons are very uncommon on Mac
33     ui->addButton->setIcon(QIcon());
34     ui->clearButton->setIcon(QIcon());
35     ui->sendButton->setIcon(QIcon());
36 #endif
37
38 #if QT_VERSION >= 0x040700
39     /* Do not move this to the XML file, Qt before 4.7 will choke on it */
40     ui->lineEditCoinControlChange->setPlaceholderText(tr("Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5)"));
41 #endif
42
43     addEntry();
44
45     connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry()));
46     connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
47
48     // Coin Control
49     ui->lineEditCoinControlChange->setFont(GUIUtil::bitcoinAddressFont());
50     connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked()));
51     connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int)));
52
53     // Coin Control: clipboard actions
54     QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
55     QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
56     QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
57     QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
58     QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
59     QAction *clipboardPriorityAction = new QAction(tr("Copy priority"), this);
60     QAction *clipboardLowOutputAction = new QAction(tr("Copy low output"), this);
61     QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
62     connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity()));
63     connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount()));
64     connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee()));
65     connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee()));
66     connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes()));
67     connect(clipboardPriorityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardPriority()));
68     connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput()));
69     connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange()));
70     ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
71     ui->labelCoinControlAmount->addAction(clipboardAmountAction);
72     ui->labelCoinControlFee->addAction(clipboardFeeAction);
73     ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
74     ui->labelCoinControlBytes->addAction(clipboardBytesAction);
75     ui->labelCoinControlPriority->addAction(clipboardPriorityAction);
76     ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
77     ui->labelCoinControlChange->addAction(clipboardChangeAction);
78
79     fNewRecipientAllowed = true;
80 }
81
82 void SendCoinsDialog::setModel(WalletModel *model)
83 {
84     this->model = model;
85
86     for(int i = 0; i < ui->entries->count(); ++i)
87     {
88         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
89         if(entry)
90         {
91             entry->setModel(model);
92         }
93     }
94     if(model && model->getOptionsModel())
95     {
96         setBalance(model->getBalance(), model->getStake(), model->getUnconfirmedBalance(), model->getImmatureBalance());
97         connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64, qint64)));
98         connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
99
100         // Coin Control
101         connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels()));
102         connect(model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool)));
103         connect(model->getOptionsModel(), SIGNAL(transactionFeeChanged(qint64)), this, SLOT(coinControlUpdateLabels()));
104         ui->frameCoinControl->setVisible(model->getOptionsModel()->getCoinControlFeatures());
105         coinControlUpdateLabels();
106     }
107 }
108
109 SendCoinsDialog::~SendCoinsDialog()
110 {
111     delete ui;
112 }
113
114 void SendCoinsDialog::on_sendButton_clicked()
115 {
116     QList<SendCoinsRecipient> recipients;
117     bool valid = true;
118
119     if(!model)
120         return;
121
122     if (ui->lineEditCoinControlChange->isEnabled())
123     {
124         if(!ui->lineEditCoinControlChange->hasAcceptableInput() ||
125            (model && !model->validateAddress(ui->lineEditCoinControlChange->text())))
126         {
127             ui->lineEditCoinControlChange->setValid(false);
128             valid = false;
129         }
130     }
131
132     for(int i = 0; i < ui->entries->count(); ++i)
133     {
134         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
135         if(entry)
136         {
137             if(entry->validate())
138             {
139                 recipients.append(entry->getValue());
140             }
141             else
142             {
143                 valid = false;
144             }
145         }
146     }
147
148     if(!valid || recipients.isEmpty())
149     {
150         return;
151     }
152
153     // Format confirmation message
154     QStringList formatted;
155     foreach(const SendCoinsRecipient &rcp, recipients)
156     {
157         formatted.append(tr("<b>%1</b> to %2 (%3)").arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount), Qt::escape(rcp.label), rcp.address));
158     }
159
160     fNewRecipientAllowed = false;
161
162     QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"),
163                           tr("Are you sure you want to send %1?").arg(formatted.join(tr(" and "))),
164           QMessageBox::Yes|QMessageBox::Cancel,
165           QMessageBox::Cancel);
166
167     if(retval != QMessageBox::Yes)
168     {
169         fNewRecipientAllowed = true;
170         return;
171     }
172
173     WalletModel::UnlockContext ctx(model->requestUnlock());
174     if(!ctx.isValid())
175     {
176         // Unlock wallet was cancelled
177         fNewRecipientAllowed = true;
178         return;
179     }
180
181     WalletModel::SendCoinsReturn sendstatus;
182
183     if (!model->getOptionsModel() || !model->getOptionsModel()->getCoinControlFeatures())
184         sendstatus = model->sendCoins(recipients);
185     else
186         sendstatus = model->sendCoins(recipients, CoinControlDialog::coinControl);
187
188     switch(sendstatus.status)
189     {
190     case WalletModel::InvalidAddress:
191         QMessageBox::warning(this, tr("Send Coins"),
192             tr("The recipient address is not valid, please recheck."),
193             QMessageBox::Ok, QMessageBox::Ok);
194         break;
195     case WalletModel::InvalidAmount:
196         QMessageBox::warning(this, tr("Send Coins"),
197             tr("The amount to pay must be larger than 0."),
198             QMessageBox::Ok, QMessageBox::Ok);
199         break;
200     case WalletModel::AmountExceedsBalance:
201         QMessageBox::warning(this, tr("Send Coins"),
202             tr("The amount exceeds your balance."),
203             QMessageBox::Ok, QMessageBox::Ok);
204         break;
205     case WalletModel::AmountWithFeeExceedsBalance:
206         QMessageBox::warning(this, tr("Send Coins"),
207             tr("The total exceeds your balance when the %1 transaction fee is included.").
208             arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, sendstatus.fee)),
209             QMessageBox::Ok, QMessageBox::Ok);
210         break;
211     case WalletModel::DuplicateAddress:
212         QMessageBox::warning(this, tr("Send Coins"),
213             tr("Duplicate address found, can only send to each address once per send operation."),
214             QMessageBox::Ok, QMessageBox::Ok);
215         break;
216     case WalletModel::TransactionCreationFailed:
217         QMessageBox::warning(this, tr("Send Coins"),
218             tr("Error: Transaction creation failed."),
219             QMessageBox::Ok, QMessageBox::Ok);
220         break;
221     case WalletModel::TransactionCommitFailed:
222         QMessageBox::warning(this, tr("Send Coins"),
223             tr("Error: 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."),
224             QMessageBox::Ok, QMessageBox::Ok);
225         break;
226     case WalletModel::Aborted: // User aborted, nothing to do
227         break;
228     case WalletModel::OK:
229         accept();
230         CoinControlDialog::coinControl->UnSelectAll();
231         coinControlUpdateLabels();
232         break;
233     }
234     fNewRecipientAllowed = true;
235 }
236
237 void SendCoinsDialog::clear()
238 {
239     // Remove entries until only one left
240     while(ui->entries->count())
241     {
242         delete ui->entries->takeAt(0)->widget();
243     }
244     addEntry();
245
246     updateRemoveEnabled();
247
248     ui->sendButton->setDefault(true);
249 }
250
251 void SendCoinsDialog::reject()
252 {
253     clear();
254 }
255
256 void SendCoinsDialog::accept()
257 {
258     clear();
259 }
260
261 SendCoinsEntry *SendCoinsDialog::addEntry()
262 {
263     SendCoinsEntry *entry = new SendCoinsEntry(this);
264     entry->setModel(model);
265     ui->entries->addWidget(entry);
266     connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
267     connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
268
269     updateRemoveEnabled();
270
271     // Focus the field, so that entry can start immediately
272     entry->clear();
273     entry->setFocus();
274     ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
275     QCoreApplication::instance()->processEvents();
276     QScrollBar* bar = ui->scrollArea->verticalScrollBar();
277     if(bar)
278         bar->setSliderPosition(bar->maximum());
279     return entry;
280 }
281
282 void SendCoinsDialog::updateRemoveEnabled()
283 {
284     // Remove buttons are enabled as soon as there is more than one send-entry
285     bool enabled = (ui->entries->count() > 1);
286     for(int i = 0; i < ui->entries->count(); ++i)
287     {
288         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
289         if(entry)
290         {
291             entry->setRemoveEnabled(enabled);
292         }
293     }
294     setupTabChain(0);
295     coinControlUpdateLabels();
296 }
297
298 void SendCoinsDialog::removeEntry(SendCoinsEntry* entry)
299 {
300     delete entry;
301     updateRemoveEnabled();
302 }
303
304 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
305 {
306     for(int i = 0; i < ui->entries->count(); ++i)
307     {
308         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
309         if(entry)
310         {
311             prev = entry->setupTabChain(prev);
312         }
313     }
314     QWidget::setTabOrder(prev, ui->addButton);
315     QWidget::setTabOrder(ui->addButton, ui->sendButton);
316     return ui->sendButton;
317 }
318
319 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
320 {
321     if(!fNewRecipientAllowed)
322         return;
323
324     SendCoinsEntry *entry = 0;
325     // Replace the first entry if it is still unused
326     if(ui->entries->count() == 1)
327     {
328         SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
329         if(first->isClear())
330         {
331             entry = first;
332         }
333     }
334     if(!entry)
335     {
336         entry = addEntry();
337     }
338
339     entry->setValue(rv);
340 }
341
342 bool SendCoinsDialog::handleURI(const QString &uri)
343 {
344     SendCoinsRecipient rv;
345     // URI has to be valid
346     if (GUIUtil::parseBitcoinURI(uri, &rv))
347     {
348         CBitcoinAddress address(rv.address.toStdString());
349         if (!address.IsValid())
350             return false;
351         pasteEntry(rv);
352         return true;
353     }
354
355     return false;
356 }
357
358 void SendCoinsDialog::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance)
359 {
360     Q_UNUSED(stake);
361     Q_UNUSED(unconfirmedBalance);
362     Q_UNUSED(immatureBalance);
363     if(!model || !model->getOptionsModel())
364         return;
365
366     int unit = model->getOptionsModel()->getDisplayUnit();
367     ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balance));
368 }
369
370 void SendCoinsDialog::updateDisplayUnit()
371 {
372     if(model && model->getOptionsModel())
373     {
374         // Update labelBalance with the current balance and the current unit
375         ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->getBalance()));
376     }
377 }
378
379 // Coin Control: copy label "Quantity" to clipboard
380 void SendCoinsDialog::coinControlClipboardQuantity()
381 {
382     QApplication::clipboard()->setText(ui->labelCoinControlQuantity->text());
383 }
384
385 // Coin Control: copy label "Amount" to clipboard
386 void SendCoinsDialog::coinControlClipboardAmount()
387 {
388     QApplication::clipboard()->setText(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
389 }
390
391 // Coin Control: copy label "Fee" to clipboard
392 void SendCoinsDialog::coinControlClipboardFee()
393 {
394     QApplication::clipboard()->setText(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")));
395 }
396
397 // Coin Control: copy label "After fee" to clipboard
398 void SendCoinsDialog::coinControlClipboardAfterFee()
399 {
400     QApplication::clipboard()->setText(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")));
401 }
402
403 // Coin Control: copy label "Bytes" to clipboard
404 void SendCoinsDialog::coinControlClipboardBytes()
405 {
406     QApplication::clipboard()->setText(ui->labelCoinControlBytes->text());
407 }
408
409 // Coin Control: copy label "Priority" to clipboard
410 void SendCoinsDialog::coinControlClipboardPriority()
411 {
412     QApplication::clipboard()->setText(ui->labelCoinControlPriority->text());
413 }
414
415 // Coin Control: copy label "Low output" to clipboard
416 void SendCoinsDialog::coinControlClipboardLowOutput()
417 {
418     QApplication::clipboard()->setText(ui->labelCoinControlLowOutput->text());
419 }
420
421 // Coin Control: copy label "Change" to clipboard
422 void SendCoinsDialog::coinControlClipboardChange()
423 {
424     QApplication::clipboard()->setText(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")));
425 }
426
427 // Coin Control: settings menu - coin control enabled/disabled by user
428 void SendCoinsDialog::coinControlFeatureChanged(bool checked)
429 {
430     ui->frameCoinControl->setVisible(checked);
431
432     if (!checked && model) // coin control features disabled
433         CoinControlDialog::coinControl->SetNull();
434 }
435
436 // Coin Control: button inputs -> show actual coin control dialog
437 void SendCoinsDialog::coinControlButtonClicked()
438 {
439     CoinControlDialog dlg;
440     dlg.setModel(model);
441     dlg.exec();
442     coinControlUpdateLabels();
443 }
444
445 // Coin Control: checkbox custom change address
446 void SendCoinsDialog::coinControlChangeChecked(int state)
447 {
448     if (model)
449     {
450         if (state == Qt::Checked)
451             CoinControlDialog::coinControl->destChange = CBitcoinAddress(ui->lineEditCoinControlChange->text().toStdString()).Get();
452         else
453             CoinControlDialog::coinControl->destChange = CNoDestination();
454     }
455
456     ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
457 //    ui->labelCoinControlChangeLabel->setEnabled((state == Qt::Checked));
458     ui->addressBookButton->setEnabled((state == Qt::Checked));
459     ui->pasteButton->setEnabled((state == Qt::Checked));
460 }
461
462 void SendCoinsDialog::on_pasteButton_clicked()
463 {
464     // Paste text from clipboard into recipient field
465     ui->lineEditCoinControlChange->setText(QApplication::clipboard()->text());
466 }
467
468 void SendCoinsDialog::on_addressBookButton_clicked()
469 {
470     if(!model)
471         return;
472     AddressBookPage dlg(AddressBookPage::ForSending, AddressBookPage::SendingTab, this);
473     dlg.setModel(model->getAddressTableModel());
474     if(dlg.exec())
475     {
476         ui->lineEditCoinControlChange->setText(dlg.getReturnValue());
477     }
478 }
479
480 // Coin Control: update labels
481 void SendCoinsDialog::coinControlUpdateLabels()
482 {
483     if (!model || !model->getOptionsModel() || !model->getOptionsModel()->getCoinControlFeatures())
484         return;
485
486     // set pay amounts
487     CoinControlDialog::payAmounts.clear();
488     for(int i = 0; i < ui->entries->count(); ++i)
489     {
490         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
491         if(entry)
492             CoinControlDialog::payAmounts.append(entry->getValue().amount);
493     }
494
495     if (CoinControlDialog::coinControl->HasSelected())
496     {
497         // actual coin control calculation
498         CoinControlDialog::updateLabels(model, this);
499
500         // show coin control stats
501         ui->labelCoinControlAutomaticallySelected->hide();
502         ui->widgetCoinControl->show();
503     }
504     else
505     {
506         // hide coin control stats
507         ui->labelCoinControlAutomaticallySelected->show();
508         ui->widgetCoinControl->hide();
509         ui->labelCoinControlInsuffFunds->hide();
510     }
511 }