You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

sendcoinsdialog.cpp 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. // Copyright (c) 2011-2014 The Bitcoin Core developers
  2. // Distributed under the MIT software license, see the accompanying
  3. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  4. #include "sendcoinsdialog.h"
  5. #include "ui_sendcoinsdialog.h"
  6. #include "addresstablemodel.h"
  7. #include "bitcoinunits.h"
  8. #include "clientmodel.h"
  9. #include "coincontroldialog.h"
  10. #include "guiutil.h"
  11. #include "optionsmodel.h"
  12. #include "scicon.h"
  13. #include "sendcoinsentry.h"
  14. #include "walletmodel.h"
  15. #include "base58.h"
  16. #include "coincontrol.h"
  17. #include "main.h"
  18. #include "ui_interface.h"
  19. #include "wallet/wallet.h"
  20. #include <QMessageBox>
  21. #include <QScrollBar>
  22. #include <QSettings>
  23. #include <QTextDocument>
  24. SendCoinsDialog::SendCoinsDialog(QWidget *parent) :
  25. QDialog(parent),
  26. ui(new Ui::SendCoinsDialog),
  27. clientModel(0),
  28. model(0),
  29. fNewRecipientAllowed(true),
  30. fFeeMinimized(true)
  31. {
  32. ui->setupUi(this);
  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. #else
  38. ui->addButton->setIcon(SingleColorIcon(":/icons/add"));
  39. ui->clearButton->setIcon(SingleColorIcon(":/icons/remove"));
  40. ui->sendButton->setIcon(SingleColorIcon(":/icons/send"));
  41. #endif
  42. GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
  43. addEntry();
  44. connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry()));
  45. connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
  46. // Coin Control
  47. connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked()));
  48. connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int)));
  49. connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &)));
  50. // Coin Control: clipboard actions
  51. QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
  52. QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
  53. QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
  54. QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
  55. QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
  56. QAction *clipboardPriorityAction = new QAction(tr("Copy priority"), this);
  57. QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
  58. QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
  59. connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity()));
  60. connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount()));
  61. connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee()));
  62. connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee()));
  63. connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes()));
  64. connect(clipboardPriorityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardPriority()));
  65. connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput()));
  66. connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange()));
  67. ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
  68. ui->labelCoinControlAmount->addAction(clipboardAmountAction);
  69. ui->labelCoinControlFee->addAction(clipboardFeeAction);
  70. ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
  71. ui->labelCoinControlBytes->addAction(clipboardBytesAction);
  72. ui->labelCoinControlPriority->addAction(clipboardPriorityAction);
  73. ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
  74. ui->labelCoinControlChange->addAction(clipboardChangeAction);
  75. // init transaction fee section
  76. QSettings settings;
  77. if (!settings.contains("fFeeSectionMinimized"))
  78. settings.setValue("fFeeSectionMinimized", true);
  79. if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
  80. settings.setValue("nFeeRadio", 1); // custom
  81. if (!settings.contains("nFeeRadio"))
  82. settings.setValue("nFeeRadio", 0); // recommended
  83. if (!settings.contains("nCustomFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
  84. settings.setValue("nCustomFeeRadio", 1); // total at least
  85. if (!settings.contains("nCustomFeeRadio"))
  86. settings.setValue("nCustomFeeRadio", 0); // per kilobyte
  87. if (!settings.contains("nSmartFeeSliderPosition"))
  88. settings.setValue("nSmartFeeSliderPosition", 0);
  89. if (!settings.contains("nTransactionFee"))
  90. settings.setValue("nTransactionFee", (qint64)DEFAULT_TRANSACTION_FEE);
  91. if (!settings.contains("fPayOnlyMinFee"))
  92. settings.setValue("fPayOnlyMinFee", false);
  93. if (!settings.contains("fSendFreeTransactions"))
  94. settings.setValue("fSendFreeTransactions", false);
  95. ui->groupFee->setId(ui->radioSmartFee, 0);
  96. ui->groupFee->setId(ui->radioCustomFee, 1);
  97. ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
  98. ui->groupCustomFee->setId(ui->radioCustomPerKilobyte, 0);
  99. ui->groupCustomFee->setId(ui->radioCustomAtLeast, 1);
  100. ui->groupCustomFee->button((int)std::max(0, std::min(1, settings.value("nCustomFeeRadio").toInt())))->setChecked(true);
  101. ui->sliderSmartFee->setValue(settings.value("nSmartFeeSliderPosition").toInt());
  102. ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
  103. ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool());
  104. ui->checkBoxFreeTx->setChecked(settings.value("fSendFreeTransactions").toBool());
  105. minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
  106. }
  107. void SendCoinsDialog::setClientModel(ClientModel *clientModel)
  108. {
  109. this->clientModel = clientModel;
  110. if (clientModel) {
  111. connect(clientModel, SIGNAL(numBlocksChanged(int,QDateTime)), this, SLOT(updateSmartFeeLabel()));
  112. }
  113. }
  114. void SendCoinsDialog::setModel(WalletModel *model)
  115. {
  116. this->model = model;
  117. if(model && model->getOptionsModel())
  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. entry->setModel(model);
  125. }
  126. }
  127. setBalance(model->getBalance(), model->getUnconfirmedBalance(), model->getImmatureBalance(),
  128. model->getWatchBalance(), model->getWatchUnconfirmedBalance(), model->getWatchImmatureBalance());
  129. connect(model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(setBalance(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)));
  130. connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
  131. updateDisplayUnit();
  132. // Coin Control
  133. connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels()));
  134. connect(model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool)));
  135. ui->frameCoinControl->setVisible(model->getOptionsModel()->getCoinControlFeatures());
  136. coinControlUpdateLabels();
  137. // fee section
  138. connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateSmartFeeLabel()));
  139. connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateGlobalFeeVariables()));
  140. connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(coinControlUpdateLabels()));
  141. connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls()));
  142. connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables()));
  143. connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
  144. connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables()));
  145. connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
  146. connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(updateGlobalFeeVariables()));
  147. connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels()));
  148. connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee()));
  149. connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls()));
  150. connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables()));
  151. connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
  152. connect(ui->checkBoxFreeTx, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables()));
  153. connect(ui->checkBoxFreeTx, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
  154. ui->customFee->setSingleStep(CWallet::minTxFee.GetFeePerK());
  155. updateFeeSectionControls();
  156. updateMinFeeLabel();
  157. updateSmartFeeLabel();
  158. updateGlobalFeeVariables();
  159. }
  160. }
  161. SendCoinsDialog::~SendCoinsDialog()
  162. {
  163. QSettings settings;
  164. settings.setValue("fFeeSectionMinimized", fFeeMinimized);
  165. settings.setValue("nFeeRadio", ui->groupFee->checkedId());
  166. settings.setValue("nCustomFeeRadio", ui->groupCustomFee->checkedId());
  167. settings.setValue("nSmartFeeSliderPosition", ui->sliderSmartFee->value());
  168. settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
  169. settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked());
  170. settings.setValue("fSendFreeTransactions", ui->checkBoxFreeTx->isChecked());
  171. delete ui;
  172. }
  173. void SendCoinsDialog::on_sendButton_clicked()
  174. {
  175. if(!model || !model->getOptionsModel())
  176. return;
  177. QList<SendCoinsRecipient> recipients;
  178. bool valid = true;
  179. for(int i = 0; i < ui->entries->count(); ++i)
  180. {
  181. SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
  182. if(entry)
  183. {
  184. if(entry->validate())
  185. {
  186. recipients.append(entry->getValue());
  187. }
  188. else
  189. {
  190. valid = false;
  191. }
  192. }
  193. }
  194. if(!valid || recipients.isEmpty())
  195. {
  196. return;
  197. }
  198. fNewRecipientAllowed = false;
  199. WalletModel::UnlockContext ctx(model->requestUnlock());
  200. if(!ctx.isValid())
  201. {
  202. // Unlock wallet was cancelled
  203. fNewRecipientAllowed = true;
  204. return;
  205. }
  206. // prepare transaction for getting txFee earlier
  207. WalletModelTransaction currentTransaction(recipients);
  208. WalletModel::SendCoinsReturn prepareStatus;
  209. if (model->getOptionsModel()->getCoinControlFeatures()) // coin control enabled
  210. prepareStatus = model->prepareTransaction(currentTransaction, CoinControlDialog::coinControl);
  211. else
  212. prepareStatus = model->prepareTransaction(currentTransaction);
  213. // process prepareStatus and on error generate message shown to user
  214. processSendCoinsReturn(prepareStatus,
  215. BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()));
  216. if(prepareStatus.status != WalletModel::OK) {
  217. fNewRecipientAllowed = true;
  218. return;
  219. }
  220. CAmount txFee = currentTransaction.getTransactionFee();
  221. // Format confirmation message
  222. QStringList formatted;
  223. foreach(const SendCoinsRecipient &rcp, currentTransaction.getRecipients())
  224. {
  225. // generate bold amount string
  226. QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
  227. amount.append("</b>");
  228. // generate monospace address string
  229. QString address = "<span style='font-family: monospace;'>" + rcp.address;
  230. address.append("</span>");
  231. QString recipientElement;
  232. if (!rcp.paymentRequest.IsInitialized()) // normal payment
  233. {
  234. if(rcp.label.length() > 0) // label with address
  235. {
  236. recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label));
  237. recipientElement.append(QString(" (%1)").arg(address));
  238. }
  239. else // just address
  240. {
  241. recipientElement = tr("%1 to %2").arg(amount, address);
  242. }
  243. }
  244. else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request
  245. {
  246. recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant));
  247. }
  248. else // unauthenticated payment request
  249. {
  250. recipientElement = tr("%1 to %2").arg(amount, address);
  251. }
  252. formatted.append(recipientElement);
  253. }
  254. QString questionString = tr("Are you sure you want to send?");
  255. questionString.append("<br /><br />%1");
  256. if(txFee > 0)
  257. {
  258. // append fee string if a fee is required
  259. questionString.append("<hr /><span style='color:#aa0000;'>");
  260. questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
  261. questionString.append("</span> ");
  262. questionString.append(tr("added as transaction fee"));
  263. // append transaction size
  264. questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB)");
  265. }
  266. // add total amount in all subdivision units
  267. questionString.append("<hr />");
  268. CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee;
  269. QStringList alternativeUnits;
  270. foreach(BitcoinUnits::Unit u, BitcoinUnits::availableUnits())
  271. {
  272. if(u != model->getOptionsModel()->getDisplayUnit())
  273. alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
  274. }
  275. questionString.append(tr("Total Amount %1 (= %2)")
  276. .arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount))
  277. .arg(alternativeUnits.join(" " + tr("or") + " ")));
  278. QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"),
  279. questionString.arg(formatted.join("<br />")),
  280. QMessageBox::Yes | QMessageBox::Cancel,
  281. QMessageBox::Cancel);
  282. if(retval != QMessageBox::Yes)
  283. {
  284. fNewRecipientAllowed = true;
  285. return;
  286. }
  287. // now send the prepared transaction
  288. WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction);
  289. // process sendStatus and on error generate message shown to user
  290. processSendCoinsReturn(sendStatus);
  291. if (sendStatus.status == WalletModel::OK)
  292. {
  293. accept();
  294. CoinControlDialog::coinControl->UnSelectAll();
  295. coinControlUpdateLabels();
  296. }
  297. fNewRecipientAllowed = true;
  298. }
  299. void SendCoinsDialog::clear()
  300. {
  301. // Remove entries until only one left
  302. while(ui->entries->count())
  303. {
  304. ui->entries->takeAt(0)->widget()->deleteLater();
  305. }
  306. addEntry();
  307. updateTabsAndLabels();
  308. }
  309. void SendCoinsDialog::reject()
  310. {
  311. clear();
  312. }
  313. void SendCoinsDialog::accept()
  314. {
  315. clear();
  316. }
  317. SendCoinsEntry *SendCoinsDialog::addEntry()
  318. {
  319. SendCoinsEntry *entry = new SendCoinsEntry(this);
  320. entry->setModel(model);
  321. ui->entries->addWidget(entry);
  322. connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
  323. connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
  324. connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
  325. updateTabsAndLabels();
  326. // Focus the field, so that entry can start immediately
  327. entry->clear();
  328. entry->setFocus();
  329. ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
  330. qApp->processEvents();
  331. QScrollBar* bar = ui->scrollArea->verticalScrollBar();
  332. if(bar)
  333. bar->setSliderPosition(bar->maximum());
  334. return entry;
  335. }
  336. void SendCoinsDialog::updateTabsAndLabels()
  337. {
  338. setupTabChain(0);
  339. coinControlUpdateLabels();
  340. }
  341. void SendCoinsDialog::removeEntry(SendCoinsEntry* entry)
  342. {
  343. entry->hide();
  344. // If the last entry is about to be removed add an empty one
  345. if (ui->entries->count() == 1)
  346. addEntry();
  347. entry->deleteLater();
  348. updateTabsAndLabels();
  349. }
  350. QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
  351. {
  352. for(int i = 0; i < ui->entries->count(); ++i)
  353. {
  354. SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
  355. if(entry)
  356. {
  357. prev = entry->setupTabChain(prev);
  358. }
  359. }
  360. QWidget::setTabOrder(prev, ui->sendButton);
  361. QWidget::setTabOrder(ui->sendButton, ui->clearButton);
  362. QWidget::setTabOrder(ui->clearButton, ui->addButton);
  363. return ui->addButton;
  364. }
  365. void SendCoinsDialog::setAddress(const QString &address)
  366. {
  367. SendCoinsEntry *entry = 0;
  368. // Replace the first entry if it is still unused
  369. if(ui->entries->count() == 1)
  370. {
  371. SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
  372. if(first->isClear())
  373. {
  374. entry = first;
  375. }
  376. }
  377. if(!entry)
  378. {
  379. entry = addEntry();
  380. }
  381. entry->setAddress(address);
  382. }
  383. void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
  384. {
  385. if(!fNewRecipientAllowed)
  386. return;
  387. SendCoinsEntry *entry = 0;
  388. // Replace the first entry if it is still unused
  389. if(ui->entries->count() == 1)
  390. {
  391. SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
  392. if(first->isClear())
  393. {
  394. entry = first;
  395. }
  396. }
  397. if(!entry)
  398. {
  399. entry = addEntry();
  400. }
  401. entry->setValue(rv);
  402. updateTabsAndLabels();
  403. }
  404. bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv)
  405. {
  406. // Just paste the entry, all pre-checks
  407. // are done in paymentserver.cpp.
  408. pasteEntry(rv);
  409. return true;
  410. }
  411. void SendCoinsDialog::setBalance(const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance,
  412. const CAmount& watchBalance, const CAmount& watchUnconfirmedBalance, const CAmount& watchImmatureBalance)
  413. {
  414. Q_UNUSED(unconfirmedBalance);
  415. Q_UNUSED(immatureBalance);
  416. Q_UNUSED(watchBalance);
  417. Q_UNUSED(watchUnconfirmedBalance);
  418. Q_UNUSED(watchImmatureBalance);
  419. if(model && model->getOptionsModel())
  420. {
  421. ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
  422. }
  423. }
  424. void SendCoinsDialog::updateDisplayUnit()
  425. {
  426. setBalance(model->getBalance(), 0, 0, 0, 0, 0);
  427. ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
  428. updateMinFeeLabel();
  429. updateSmartFeeLabel();
  430. }
  431. void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
  432. {
  433. QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
  434. // Default to a warning message, override if error message is needed
  435. msgParams.second = CClientUIInterface::MSG_WARNING;
  436. // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
  437. // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
  438. // all others are used only in WalletModel::prepareTransaction()
  439. switch(sendCoinsReturn.status)
  440. {
  441. case WalletModel::InvalidAddress:
  442. msgParams.first = tr("The recipient address is not valid. Please recheck.");
  443. break;
  444. case WalletModel::InvalidAmount:
  445. msgParams.first = tr("The amount to pay must be larger than 0.");
  446. break;
  447. case WalletModel::AmountExceedsBalance:
  448. msgParams.first = tr("The amount exceeds your balance.");
  449. break;
  450. case WalletModel::AmountWithFeeExceedsBalance:
  451. msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
  452. break;
  453. case WalletModel::DuplicateAddress:
  454. msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
  455. break;
  456. case WalletModel::TransactionCreationFailed:
  457. msgParams.first = tr("Transaction creation failed!");
  458. msgParams.second = CClientUIInterface::MSG_ERROR;
  459. break;
  460. case WalletModel::TransactionCommitFailed:
  461. 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.");
  462. msgParams.second = CClientUIInterface::MSG_ERROR;
  463. break;
  464. case WalletModel::AbsurdFee:
  465. msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), 10000000));
  466. break;
  467. case WalletModel::PaymentRequestExpired:
  468. msgParams.first = tr("Payment request expired.");
  469. msgParams.second = CClientUIInterface::MSG_ERROR;
  470. break;
  471. // included to prevent a compiler warning.
  472. case WalletModel::OK:
  473. default:
  474. return;
  475. }
  476. emit message(tr("Send Coins"), msgParams.first, msgParams.second);
  477. }
  478. void SendCoinsDialog::minimizeFeeSection(bool fMinimize)
  479. {
  480. ui->labelFeeMinimized->setVisible(fMinimize);
  481. ui->buttonChooseFee ->setVisible(fMinimize);
  482. ui->buttonMinimizeFee->setVisible(!fMinimize);
  483. ui->frameFeeSelection->setVisible(!fMinimize);
  484. ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
  485. fFeeMinimized = fMinimize;
  486. }
  487. void SendCoinsDialog::on_buttonChooseFee_clicked()
  488. {
  489. minimizeFeeSection(false);
  490. }
  491. void SendCoinsDialog::on_buttonMinimizeFee_clicked()
  492. {
  493. updateFeeMinimizedLabel();
  494. minimizeFeeSection(true);
  495. }
  496. void SendCoinsDialog::setMinimumFee()
  497. {
  498. ui->radioCustomPerKilobyte->setChecked(true);
  499. ui->customFee->setValue(CWallet::minTxFee.GetFeePerK());
  500. }
  501. void SendCoinsDialog::updateFeeSectionControls()
  502. {
  503. ui->sliderSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
  504. ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
  505. ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
  506. ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
  507. ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
  508. ui->labelSmartFeeNormal ->setEnabled(ui->radioSmartFee->isChecked());
  509. ui->labelSmartFeeFast ->setEnabled(ui->radioSmartFee->isChecked());
  510. ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked());
  511. ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
  512. ui->radioCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
  513. ui->radioCustomAtLeast ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
  514. ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
  515. }
  516. void SendCoinsDialog::updateGlobalFeeVariables()
  517. {
  518. if (ui->radioSmartFee->isChecked())
  519. {
  520. nTxConfirmTarget = (int)25 - (int)std::max(0, std::min(24, ui->sliderSmartFee->value()));
  521. payTxFee = CFeeRate(0);
  522. }
  523. else
  524. {
  525. nTxConfirmTarget = 25;
  526. payTxFee = CFeeRate(ui->customFee->value());
  527. fPayAtLeastCustomFee = ui->radioCustomAtLeast->isChecked();
  528. }
  529. fSendFreeTransactions = ui->checkBoxFreeTx->isChecked();
  530. }
  531. void SendCoinsDialog::updateFeeMinimizedLabel()
  532. {
  533. if(!model || !model->getOptionsModel())
  534. return;
  535. if (ui->radioSmartFee->isChecked())
  536. ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
  537. else {
  538. ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) +
  539. ((ui->radioCustomPerKilobyte->isChecked()) ? "/kB" : ""));
  540. }
  541. }
  542. void SendCoinsDialog::updateMinFeeLabel()
  543. {
  544. if (model && model->getOptionsModel())
  545. ui->checkBoxMinimumFee->setText(tr("Pay only the minimum fee of %1").arg(
  546. BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), CWallet::minTxFee.GetFeePerK()) + "/kB")
  547. );
  548. }
  549. void SendCoinsDialog::updateSmartFeeLabel()
  550. {
  551. if(!model || !model->getOptionsModel())
  552. return;
  553. int nBlocksToConfirm = (int)25 - (int)std::max(0, std::min(24, ui->sliderSmartFee->value()));
  554. CFeeRate feeRate = mempool.estimateFee(nBlocksToConfirm);
  555. if (feeRate <= CFeeRate(0)) // not enough data => minfee
  556. {
  557. ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), CWallet::minTxFee.GetFeePerK()) + "/kB");
  558. ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
  559. ui->labelFeeEstimation->setText("");
  560. }
  561. else
  562. {
  563. ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB");
  564. ui->labelSmartFee2->hide();
  565. ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", nBlocksToConfirm));
  566. }
  567. updateFeeMinimizedLabel();
  568. }
  569. // Coin Control: copy label "Quantity" to clipboard
  570. void SendCoinsDialog::coinControlClipboardQuantity()
  571. {
  572. GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
  573. }
  574. // Coin Control: copy label "Amount" to clipboard
  575. void SendCoinsDialog::coinControlClipboardAmount()
  576. {
  577. GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
  578. }
  579. // Coin Control: copy label "Fee" to clipboard
  580. void SendCoinsDialog::coinControlClipboardFee()
  581. {
  582. GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
  583. }
  584. // Coin Control: copy label "After fee" to clipboard
  585. void SendCoinsDialog::coinControlClipboardAfterFee()
  586. {
  587. GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
  588. }
  589. // Coin Control: copy label "Bytes" to clipboard
  590. void SendCoinsDialog::coinControlClipboardBytes()
  591. {
  592. GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
  593. }
  594. // Coin Control: copy label "Priority" to clipboard
  595. void SendCoinsDialog::coinControlClipboardPriority()
  596. {
  597. GUIUtil::setClipboard(ui->labelCoinControlPriority->text());
  598. }
  599. // Coin Control: copy label "Dust" to clipboard
  600. void SendCoinsDialog::coinControlClipboardLowOutput()
  601. {
  602. GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
  603. }
  604. // Coin Control: copy label "Change" to clipboard
  605. void SendCoinsDialog::coinControlClipboardChange()
  606. {
  607. GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
  608. }
  609. // Coin Control: settings menu - coin control enabled/disabled by user
  610. void SendCoinsDialog::coinControlFeatureChanged(bool checked)
  611. {
  612. ui->frameCoinControl->setVisible(checked);
  613. if (!checked && model) // coin control features disabled
  614. CoinControlDialog::coinControl->SetNull();
  615. if (checked)
  616. coinControlUpdateLabels();
  617. }
  618. // Coin Control: button inputs -> show actual coin control dialog
  619. void SendCoinsDialog::coinControlButtonClicked()
  620. {
  621. CoinControlDialog dlg;
  622. dlg.setModel(model);
  623. dlg.exec();
  624. coinControlUpdateLabels();
  625. }
  626. // Coin Control: checkbox custom change address
  627. void SendCoinsDialog::coinControlChangeChecked(int state)
  628. {
  629. if (state == Qt::Unchecked)
  630. {
  631. CoinControlDialog::coinControl->destChange = CNoDestination();
  632. ui->labelCoinControlChangeLabel->clear();
  633. }
  634. else
  635. // use this to re-validate an already entered address
  636. coinControlChangeEdited(ui->lineEditCoinControlChange->text());
  637. ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
  638. }
  639. // Coin Control: custom change address changed
  640. void SendCoinsDialog::coinControlChangeEdited(const QString& text)
  641. {
  642. if (model && model->getAddressTableModel())
  643. {
  644. // Default to no change address until verified
  645. CoinControlDialog::coinControl->destChange = CNoDestination();
  646. ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
  647. CBitcoinAddress addr = CBitcoinAddress(text.toStdString());
  648. if (text.isEmpty()) // Nothing entered
  649. {
  650. ui->labelCoinControlChangeLabel->setText("");
  651. }
  652. else if (!addr.IsValid()) // Invalid address
  653. {
  654. ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
  655. }
  656. else // Valid address
  657. {
  658. CPubKey pubkey;
  659. CKeyID keyid;
  660. addr.GetKeyID(keyid);
  661. if (!model->getPubKey(keyid, pubkey)) // Unknown change address
  662. {
  663. ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
  664. }
  665. else // Known change address
  666. {
  667. ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
  668. // Query label
  669. QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
  670. if (!associatedLabel.isEmpty())
  671. ui->labelCoinControlChangeLabel->setText(associatedLabel);
  672. else
  673. ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
  674. CoinControlDialog::coinControl->destChange = addr.Get();
  675. }
  676. }
  677. }
  678. }
  679. // Coin Control: update labels
  680. void SendCoinsDialog::coinControlUpdateLabels()
  681. {
  682. if (!model || !model->getOptionsModel() || !model->getOptionsModel()->getCoinControlFeatures())
  683. return;
  684. // set pay amounts
  685. CoinControlDialog::payAmounts.clear();
  686. CoinControlDialog::fSubtractFeeFromAmount = false;
  687. for(int i = 0; i < ui->entries->count(); ++i)
  688. {
  689. SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
  690. if(entry)
  691. {
  692. SendCoinsRecipient rcp = entry->getValue();
  693. CoinControlDialog::payAmounts.append(rcp.amount);
  694. if (rcp.fSubtractFeeFromAmount)
  695. CoinControlDialog::fSubtractFeeFromAmount = true;
  696. }
  697. }
  698. if (CoinControlDialog::coinControl->HasSelected())
  699. {
  700. // actual coin control calculation
  701. CoinControlDialog::updateLabels(model, this);
  702. // show coin control stats
  703. ui->labelCoinControlAutomaticallySelected->hide();
  704. ui->widgetCoinControl->show();
  705. }
  706. else
  707. {
  708. // hide coin control stats
  709. ui->labelCoinControlAutomaticallySelected->show();
  710. ui->widgetCoinControl->hide();
  711. ui->labelCoinControlInsuffFunds->hide();
  712. }
  713. }