update to 0.4 preview
[novacoin.git] / src / qt / sendcoinsdialog.cpp
1 #include "sendcoinsdialog.h"
2 #include "ui_sendcoinsdialog.h"
3 #include "walletmodel.h"
4 #include "bitcoinunits.h"
5 #include "addressbookpage.h"
6 #include "optionsmodel.h"
7 #include "sendcoinsentry.h"
8 #include "guiutil.h"
9 #include "askpassphrasedialog.h"
10 #include "base58.h"
11
12 #include <QMessageBox>
13 #include <QLocale>
14 #include <QTextDocument>
15 #include <QScrollBar>
16
17 SendCoinsDialog::SendCoinsDialog(QWidget *parent) :
18     QDialog(parent),
19     ui(new Ui::SendCoinsDialog),
20     model(0)
21 {
22     ui->setupUi(this);
23
24 #ifdef Q_OS_MAC // Icons on push buttons are very uncommon on Mac
25     ui->addButton->setIcon(QIcon());
26     ui->clearButton->setIcon(QIcon());
27     ui->sendButton->setIcon(QIcon());
28 #endif
29
30     addEntry();
31
32     connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry()));
33     connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
34
35     fNewRecipientAllowed = true;
36 }
37
38 void SendCoinsDialog::setModel(WalletModel *model)
39 {
40     this->model = model;
41
42     for(int i = 0; i < ui->entries->count(); ++i)
43     {
44         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
45         if(entry)
46         {
47             entry->setModel(model);
48         }
49     }
50     if(model && model->getOptionsModel())
51     {
52         setBalance(model->getBalance(), model->getStake(), model->getUnconfirmedBalance(), model->getImmatureBalance());
53         connect(model, SIGNAL(balanceChanged(qint64, qint64, quint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64, qint64)));
54         connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
55     }
56 }
57
58 SendCoinsDialog::~SendCoinsDialog()
59 {
60     delete ui;
61 }
62
63 void SendCoinsDialog::on_sendButton_clicked()
64 {
65     QList<SendCoinsRecipient> recipients;
66     bool valid = true;
67
68     if(!model)
69         return;
70
71     for(int i = 0; i < ui->entries->count(); ++i)
72     {
73         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
74         if(entry)
75         {
76             if(entry->validate())
77             {
78                 recipients.append(entry->getValue());
79             }
80             else
81             {
82                 valid = false;
83             }
84         }
85     }
86
87     if(!valid || recipients.isEmpty())
88     {
89         return;
90     }
91
92     // Format confirmation message
93     QStringList formatted;
94     foreach(const SendCoinsRecipient &rcp, recipients)
95     {
96         formatted.append(tr("<b>%1</b> to %2 (%3)").arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount), Qt::escape(rcp.label), rcp.address));
97     }
98
99     fNewRecipientAllowed = false;
100
101     QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"),
102                           tr("Are you sure you want to send %1?").arg(formatted.join(tr(" and "))),
103           QMessageBox::Yes|QMessageBox::Cancel,
104           QMessageBox::Cancel);
105
106     if(retval != QMessageBox::Yes)
107     {
108         fNewRecipientAllowed = true;
109         return;
110     }
111
112     WalletModel::UnlockContext ctx(model->requestUnlock());
113     if(!ctx.isValid())
114     {
115         // Unlock wallet was cancelled
116         fNewRecipientAllowed = true;
117         return;
118     }
119
120     WalletModel::SendCoinsReturn sendstatus = model->sendCoins(recipients);
121     switch(sendstatus.status)
122     {
123     case WalletModel::InvalidAddress:
124         QMessageBox::warning(this, tr("Send Coins"),
125             tr("The recipient address is not valid, please recheck."),
126             QMessageBox::Ok, QMessageBox::Ok);
127         break;
128     case WalletModel::InvalidAmount:
129         QMessageBox::warning(this, tr("Send Coins"),
130             tr("The amount to pay must be larger than 0."),
131             QMessageBox::Ok, QMessageBox::Ok);
132         break;
133     case WalletModel::AmountExceedsBalance:
134         QMessageBox::warning(this, tr("Send Coins"),
135             tr("The amount exceeds your balance."),
136             QMessageBox::Ok, QMessageBox::Ok);
137         break;
138     case WalletModel::AmountWithFeeExceedsBalance:
139         QMessageBox::warning(this, tr("Send Coins"),
140             tr("The total exceeds your balance when the %1 transaction fee is included.").
141             arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, sendstatus.fee)),
142             QMessageBox::Ok, QMessageBox::Ok);
143         break;
144     case WalletModel::DuplicateAddress:
145         QMessageBox::warning(this, tr("Send Coins"),
146             tr("Duplicate address found, can only send to each address once per send operation."),
147             QMessageBox::Ok, QMessageBox::Ok);
148         break;
149     case WalletModel::TransactionCreationFailed:
150         QMessageBox::warning(this, tr("Send Coins"),
151             tr("Error: Transaction creation failed."),
152             QMessageBox::Ok, QMessageBox::Ok);
153         break;
154     case WalletModel::TransactionCommitFailed:
155         QMessageBox::warning(this, tr("Send Coins"),
156             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."),
157             QMessageBox::Ok, QMessageBox::Ok);
158         break;
159     case WalletModel::Aborted: // User aborted, nothing to do
160         break;
161     case WalletModel::OK:
162         accept();
163         break;
164     }
165     fNewRecipientAllowed = true;
166 }
167
168 void SendCoinsDialog::clear()
169 {
170     // Remove entries until only one left
171     while(ui->entries->count())
172     {
173         delete ui->entries->takeAt(0)->widget();
174     }
175     addEntry();
176
177     updateRemoveEnabled();
178
179     ui->sendButton->setDefault(true);
180 }
181
182 void SendCoinsDialog::reject()
183 {
184     clear();
185 }
186
187 void SendCoinsDialog::accept()
188 {
189     clear();
190 }
191
192 SendCoinsEntry *SendCoinsDialog::addEntry()
193 {
194     SendCoinsEntry *entry = new SendCoinsEntry(this);
195     entry->setModel(model);
196     ui->entries->addWidget(entry);
197     connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
198
199     updateRemoveEnabled();
200
201     // Focus the field, so that entry can start immediately
202     entry->clear();
203     entry->setFocus();
204     ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
205     QCoreApplication::instance()->processEvents();
206     QScrollBar* bar = ui->scrollArea->verticalScrollBar();
207     if(bar)
208         bar->setSliderPosition(bar->maximum());
209     return entry;
210 }
211
212 void SendCoinsDialog::updateRemoveEnabled()
213 {
214     // Remove buttons are enabled as soon as there is more than one send-entry
215     bool enabled = (ui->entries->count() > 1);
216     for(int i = 0; i < ui->entries->count(); ++i)
217     {
218         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
219         if(entry)
220         {
221             entry->setRemoveEnabled(enabled);
222         }
223     }
224     setupTabChain(0);
225 }
226
227 void SendCoinsDialog::removeEntry(SendCoinsEntry* entry)
228 {
229     delete entry;
230     updateRemoveEnabled();
231 }
232
233 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
234 {
235     for(int i = 0; i < ui->entries->count(); ++i)
236     {
237         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
238         if(entry)
239         {
240             prev = entry->setupTabChain(prev);
241         }
242     }
243     QWidget::setTabOrder(prev, ui->addButton);
244     QWidget::setTabOrder(ui->addButton, ui->sendButton);
245     return ui->sendButton;
246 }
247
248 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
249 {
250     if(!fNewRecipientAllowed)
251         return;
252
253     SendCoinsEntry *entry = 0;
254     // Replace the first entry if it is still unused
255     if(ui->entries->count() == 1)
256     {
257         SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
258         if(first->isClear())
259         {
260             entry = first;
261         }
262     }
263     if(!entry)
264     {
265         entry = addEntry();
266     }
267
268     entry->setValue(rv);
269 }
270
271 bool SendCoinsDialog::handleURI(const QString &uri)
272 {
273     SendCoinsRecipient rv;
274     // URI has to be valid
275     if (GUIUtil::parseBitcoinURI(uri, &rv))
276     {
277         CBitcoinAddress address(rv.address.toStdString());
278         if (!address.IsValid())
279             return false;
280         pasteEntry(rv);
281         return true;
282     }
283
284     return false;
285 }
286
287 void SendCoinsDialog::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance)
288 {
289     Q_UNUSED(stake);
290     Q_UNUSED(unconfirmedBalance);
291     Q_UNUSED(immatureBalance);
292     if(!model || !model->getOptionsModel())
293         return;
294
295     int unit = model->getOptionsModel()->getDisplayUnit();
296     ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balance));
297 }
298
299 void SendCoinsDialog::updateDisplayUnit()
300 {
301     if(model && model->getOptionsModel())
302     {
303         // Update labelBalance with the current balance and the current unit
304         ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->getBalance()));
305     }
306 }