sendcoinsdialog.cpp 18 KB

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