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