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 33KB

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