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