PIVX Core  5.6.99
P2P Digital Currency
sendconfirmdialog.cpp
Go to the documentation of this file.
1 // Copyright (c) 2019-2021 The PIVX 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 
5 #include "sendconfirmdialog.h"
6 #include "ui_sendconfirmdialog.h"
7 
8 #include "bitcoinunits.h"
9 #include "guiutil.h"
10 #include "qtutils.h"
11 #include "transactionrecord.h"
12 #include "transactiontablemodel.h"
13 #include "wallet/wallet.h"
14 #include "walletmodel.h"
15 
16 #include <QDateTime>
17 #include <QList>
18 
19 TxDetailDialog::TxDetailDialog(QWidget *parent, bool _isConfirmDialog, const QString& warningStr) :
20  FocusedDialog(parent),
21  ui(new Ui::TxDetailDialog),
22  isConfirmDialog(_isConfirmDialog)
23 {
24  ui->setupUi(this);
25 
26  this->setStyleSheet(parent->styleSheet());
27 
28  // Container
29  setCssProperty(ui->frame, "container-dialog");
30  setCssProperty(ui->labelTitle, "text-title-dialog");
31 
32  // Labels
33  setCssProperty(ui->labelWarning, "text-title2-dialog");
34  setCssProperty({ui->labelAmount, ui->labelSend, ui->labelInputs, ui->labelFee, ui->labelChange, ui->labelId, ui->labelSize, ui->labelStatus, ui->labelConfirmations, ui->labelDate, ui->labelMemo}, "text-subtitle");
35  setCssProperty({ui->labelDividerID, ui->labelDividerOutputs, ui->labelDividerPrevtx, ui->labelDividerFeeSize, ui->labelDividerChange, ui->labelDividerConfs, ui->labelDividerMemo}, "container-divider");
36  setCssProperty({ui->textAmount, ui->textSendLabel, ui->textInputs, ui->textFee, ui->textChange, ui->textId, ui->textSize, ui->textStatus, ui->textConfirmations, ui->textDate, ui->textMemo} , "text-body3-dialog");
37 
38  setCssProperty({ui->pushCopy, ui->pushCopyMemo}, "ic-copy-big");
39  setCssProperty({ui->pushInputs, ui->pushOutputs}, "ic-arrow-down");
40  setCssProperty(ui->btnEsc, "ic-close");
41 
42  ui->labelWarning->setVisible(false);
43  ui->gridInputs->setVisible(false);
44  ui->outputsScrollArea->setVisible(false);
45 
46  // hide change address for now
47  ui->contentChangeAddress->setVisible(false);
48  ui->labelDividerChange->setVisible(false);
49 
50  // Memo text
51  ui->textMemo->setProperty("cssClass","edit-primary-no-border");
52 
53  setCssProperty({ui->labelOutputIndex, ui->textSend, ui->labelTitlePrevTx}, "text-body2-dialog");
54 
55  if (isConfirmDialog) {
56  ui->labelTitle->setText(tr("Confirm Your Transaction"));
57  setCssProperty(ui->btnCancel, "btn-dialog-cancel");
58  ui->btnSave->setText(tr("SEND"));
59  setCssBtnPrimary(ui->btnSave);
60  if (!warningStr.isEmpty()) {
61  ui->labelWarning->setVisible(true);
62  ui->labelWarning->setText(warningStr);
63  } else {
64  ui->labelWarning->setVisible(false);
65  }
66 
67  // hide id / confirmations / date / status / size
68  ui->contentID->setVisible(false);
69  ui->labelDividerID->setVisible(false);
70  ui->gridConfDateStatus->setVisible(false);
71  ui->labelDividerConfs->setVisible(false);
72  ui->contentSize->setVisible(false);
73 
74  connect(ui->btnCancel, &QPushButton::clicked, this, &TxDetailDialog::close);
75  connect(ui->btnSave, &QPushButton::clicked, [this](){accept();});
76  } else {
77  ui->labelTitle->setText(tr("Transaction Details"));
78  ui->containerButtons->setVisible(false);
79  }
80 
81  connect(ui->btnEsc, &QPushButton::clicked, this, &TxDetailDialog::close);
82  connect(ui->pushInputs, &QPushButton::clicked, this, &TxDetailDialog::onInputsClicked);
83  connect(ui->pushOutputs, &QPushButton::clicked, this, &TxDetailDialog::onOutputsClicked);
84 }
85 
87 {
88  if (tx->sapData && tx->sapData->vShieldedSpend.empty()) {
89  ui->labelTitlePrevTx->setText(tr("Previous Transaction"));
90  ui->labelOutputIndex->setText(tr("Output Index"));
91  } else {
92  ui->labelTitlePrevTx->setText(tr("Note From Address"));
93  ui->labelOutputIndex->setText(tr("Index"));
94  }
95 }
96 
97 void TxDetailDialog::setData(WalletModel *_model, const QModelIndex &index)
98 {
99  this->model = _model;
100  TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
101  QDateTime date = index.data(TransactionTableModel::DateRole).toDateTime();
102  QString address = index.data(Qt::DisplayRole).toString();
103  qint64 amount = index.data(TransactionTableModel::AmountRole).toLongLong();
104  QString amountText = BitcoinUnits::formatWithUnit(nDisplayUnit, amount, true, BitcoinUnits::separatorAlways);
105  ui->textAmount->setText(amountText);
106 
107  const CWalletTx* _tx = model->getTx(rec->hash);
108  if (_tx) {
109  this->txHash = rec->hash;
110  QString hash = QString::fromStdString(_tx->GetHash().GetHex());
111  ui->textId->setText(hash.left(20) + "..." + hash.right(20));
112  ui->textId->setTextInteractionFlags(Qt::TextSelectableByMouse);
113  // future: subdivide shielded and transparent by type and
114  // do not show send xxx recipients for txes with a single output + change (show the address directly).
115  if (_tx->tx->vout.size() == 1 || (_tx->tx->sapData && _tx->tx->sapData->vShieldedOutput.size() == 1)) {
116  ui->textSendLabel->setText((address.size() < 40) ? address : address.left(20) + "..." + address.right(20));
117  } else {
118  ui->textSendLabel->setText(QString::number(_tx->tx->vout.size() +
119  (_tx->tx->sapData ? _tx->tx->sapData->vShieldedOutput.size() : 0)) + " recipients");
120  }
121  ui->textSend->setVisible(false);
122  isShieldedToShieldedRecv = rec->type == TransactionRecord::Type::RecvWithShieldedAddress;
123 
124  // Do not show inputs button if there is no data to show
125  QString shieldedInputsExtraMsg = "";
127  ui->pushInputs->setVisible(false);
128  shieldedInputsExtraMsg = " shielded";
129  }
130 
131  setInputsType(_tx->tx);
132  int inputsSize = (_tx->tx->sapData && !_tx->tx->sapData->vShieldedSpend.empty()) ? _tx->tx->sapData->vShieldedSpend.size() : _tx->tx->vin.size();
133  ui->textInputs->setText(QString::number(inputsSize) + shieldedInputsExtraMsg);
134  ui->textConfirmations->setText(QString::number(rec->status.depth));
135  ui->textDate->setText(GUIUtil::dateTimeStrWithSeconds(date));
136  ui->textStatus->setText(QString::fromStdString(rec->statusToString()));
137  ui->textSize->setText(QString::number(rec->size) + " bytes");
138 
139  // If there is a memo in this record
140  if (rec->memo) {
141  ui->textMemo->insertPlainText(QString::fromStdString(*rec->memo));
142  ui->contentMemo->setVisible(true);
143  ui->labelDividerMemo->setVisible(true);
144  ui->textMemo->adjustSize();
145  ui->textMemo->moveCursor(QTextCursor::Start);
146  ui->textMemo->ensureCursorVisible();
147  } else {
148  ui->contentMemo->setVisible(false);
149  ui->labelDividerMemo->setVisible(false);
150  adjustSize();
151  }
152 
153  connect(ui->pushCopy, &QPushButton::clicked, [this](){
154  GUIUtil::setClipboard(QString::fromStdString(this->txHash.GetHex()));
155  if (!snackBar) snackBar = new SnackBar(nullptr, this);
156  snackBar->setText(tr("ID copied"));
157  snackBar->resize(this->width(), snackBar->height());
158  openDialog(snackBar, this);
159  });
160 
161  connect(ui->pushCopyMemo, &QPushButton::clicked, [this, rec](){
162  GUIUtil::setClipboard(QString::fromStdString(*rec->memo));
163  if (!snackBar) snackBar = new SnackBar(nullptr, this);
164  snackBar->setText(tr("Memo copied"));
165  snackBar->resize(this->width(), snackBar->height());
166  openDialog(snackBar, this);
167  });
168  }
169 
170 }
171 
172 QString formatAdressToShow(const QString& address)
173 {
174  QString addressToShow;
175  if (address.size() > 60) {
176  addressToShow = address.left(57) + "\n" + address.mid(57);
177  } else {
178  addressToShow = address;
179  }
180  return addressToShow;
181 }
182 
184 {
185  this->model = _model;
186  this->tx = _tx;
187  CAmount txFee = tx->getTransactionFee();
188 
189  // inputs label
190  CTransactionRef walletTx = tx->getTransaction();
191  setInputsType(walletTx);
192 
193  CAmount totalAmount = tx->getTotalTransactionAmount();
194  if (tx->subtractFeeFromRecipents() == 0) totalAmount += txFee;
195 
196  ui->textAmount->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, totalAmount, false, BitcoinUnits::separatorAlways) + " (Fee included)");
197 
198  const QList<SendCoinsRecipient>& recipients = tx->getRecipients();
199  int nRecipients = recipients.size();
200  if (nRecipients == 1) {
201  const SendCoinsRecipient& recipient = recipients.at(0);
202  if (recipient.isP2CS) {
203  ui->labelSend->setText(tr("Delegating to"));
204  }
205  if (recipient.isShieldedAddr) {
206  ui->labelSend->setText(tr("Shielding to"));
207  }
208  if (recipient.label.isEmpty()) { // If there is no label, then do not show the blank space.
209  ui->textSend->setVisible(false);
210  ui->textSendLabel->setText(formatAdressToShow(recipient.address));
211  } else {
212  ui->textSend->setText(formatAdressToShow(recipient.address));
213  ui->textSendLabel->setText(recipient.label);
214  }
215  ui->pushOutputs->setVisible(false);
216 
217  // If there is a single output, then show the memo.
218  if (!recipient.message.isEmpty()) {
219  ui->textMemo->insertPlainText(recipient.message);
220  ui->contentMemo->setVisible(true);
221  ui->labelDividerMemo->setVisible(true);
222  ui->textMemo->moveCursor(QTextCursor::Start);
223  ui->textMemo->ensureCursorVisible();
224  } else {
225  ui->contentMemo->setVisible(false);
226  ui->labelDividerMemo->setVisible(false);
227  }
228  } else {
229  ui->textSendLabel->setText(QString::number(nRecipients) + " recipients");
230  ui->textSend->setVisible(false);
231  ui->contentMemo->setVisible(false);
232  ui->labelDividerMemo->setVisible(false);
233  }
234 
235  int inputsSize = (walletTx->sapData && !walletTx->sapData->vShieldedSpend.empty()) ? walletTx->sapData->vShieldedSpend.size() : walletTx->vin.size();
236  ui->textInputs->setText(QString::number(inputsSize));
238 }
239 
241 {
242  if (isConfirmDialog) {
243  this->confirm = true;
244  this->sendStatus = model->sendCoins(*this->tx);
245  }
246  QDialog::accept();
247 }
248 
249 void loadInputs(const QString& leftLabel, const QString& rightLabel, QGridLayout *gridLayoutInput, int pos)
250 {
251  QLabel *label_txid = new QLabel(leftLabel);
252  QLabel *label_txidn = new QLabel(rightLabel);
253  label_txidn->setAlignment(Qt::AlignCenter | Qt::AlignRight);
254  setCssProperty({label_txid, label_txidn}, "text-body2-dialog");
255 
256  gridLayoutInput->addWidget(label_txid, pos, 0);
257  gridLayoutInput->addWidget(label_txidn, pos, 1);
258 }
259 
261 {
262  if (ui->gridInputs->isVisible()) {
263  ui->gridInputs->setVisible(false);
264  } else {
265  bool showGrid = !isShieldedToShieldedRecv;
266  if (!inputsLoaded) {
267  inputsLoaded = true;
268  if (showGrid) {
269  CTransactionRef walletTx = (this->tx) ? this->tx->getTransaction() : model->getTx(this->txHash)->tx;
270  if (walletTx) {
271  if (walletTx->sapData && walletTx->sapData->vShieldedSpend.empty()) {
272  // transparent inputs
273  ui->gridInputs->setMinimumHeight(50 + (50 * walletTx->vin.size()));
274  int i = 1;
275  for (const CTxIn& in : walletTx->vin) {
276  QString hash = QString::fromStdString(in.prevout.hash.GetHex());
277  loadInputs(hash.left(18) + "..." + hash.right(18),
278  QString::number(in.prevout.n),
279  ui->gridLayoutInput, i);
280  i++;
281  }
282  } else {
283  ui->gridInputs->setMinimumHeight(50 + (50 * walletTx->sapData->vShieldedSpend.size()));
284  bool fInfoAvailable = false;
285  for (int i = 0; i < (int) walletTx->sapData->vShieldedSpend.size(); ++i) {
286  Optional<QString> opAddr = model->getShieldedAddressFromSpendDesc(walletTx->GetHash(), i);
287  if (opAddr) {
288  QString addr = *opAddr;
289  loadInputs(addr.left(18) + "..." + addr.right(18),
290  QString::number(i),
291  ui->gridLayoutInput, i + 1);
292  fInfoAvailable = true;
293  }
294  }
295 
296  if (!fInfoAvailable) {
297  // note: the spends are not from the wallet, let's not show anything here
298  showGrid = false;
299  }
300 
301  }
302  }
303  }
304  }
305  ui->gridInputs->setVisible(showGrid);
306  }
307 }
308 
309 void appendOutput(QGridLayout* layoutGrid, int gridPosition, QString labelRes, CAmount nValue, int nDisplayUnit)
310 {
311  QLabel *label_address = new QLabel(labelRes);
312  QLabel *label_value = new QLabel(BitcoinUnits::formatWithUnit(nDisplayUnit, nValue, false, BitcoinUnits::separatorAlways));
313  label_value->setAlignment(Qt::AlignCenter | Qt::AlignRight);
314  setCssProperty({label_address, label_value}, "text-body2-dialog");
315  layoutGrid->addWidget(label_address, gridPosition, 0);
316  layoutGrid->addWidget(label_value, gridPosition, 0);
317 }
318 
320 {
321  if (ui->outputsScrollArea->isVisible()) {
322  ui->outputsScrollArea->setVisible(false);
323  } else {
324  ui->outputsScrollArea->setVisible(true);
325  if (!outputsLoaded) {
326  outputsLoaded = true;
327  QGridLayout* layoutGrid = new QGridLayout(this);
328  layoutGrid->setContentsMargins(0,0,12,0);
329  ui->container_outputs_base->setLayout(layoutGrid);
330 
331  // If the there is a model tx, then this is a confirmation dialog
332  if (tx) {
333  const QList<SendCoinsRecipient>& recipients = tx->getRecipients();
334  unsigned int sffa = tx->subtractFeeFromRecipents();
335  CAmount rcp_fee = (sffa > 0) ? (tx->getTransactionFee() / sffa) : 0;
336  for (int i = 0; i < recipients.size(); ++i) {
337  const auto& recipient = recipients[i];
338  CAmount rcp_amt = recipient.amount - (recipient.fSubtractFee ? rcp_fee : 0);
339  int charsSize = recipient.isShieldedAddr ? 18 : 16;
340  QString labelRes = recipient.address.left(charsSize) + "..." + recipient.address.right(charsSize);
341  appendOutput(layoutGrid, i, labelRes, rcp_amt, nDisplayUnit);
342  }
343  } else {
344  // Tx detail dialog
345  const CWalletTx* walletTx = model->getTx(this->txHash);
346  if (!walletTx) return;
347 
348  // transparent recipients
349  int i = 0;
350  for (const CTxOut& out : walletTx->tx->vout) {
351  QString labelRes;
352  CTxDestination dest;
353  bool isCsAddress = out.scriptPubKey.IsPayToColdStaking();
354  bool isExchange = out.scriptPubKey.IsPayToExchangeAddress();
355  if (ExtractDestination(out.scriptPubKey, dest, isCsAddress)) {
356  std::string address = EncodeDestination(dest, isCsAddress, isExchange);
357  labelRes = QString::fromStdString(address);
358  labelRes = labelRes.left(16) + "..." + labelRes.right(16);
359  } else {
360  labelRes = tr("Unknown");
361  }
362  appendOutput(layoutGrid, i, labelRes, out.nValue, nDisplayUnit);
363  i++;
364  }
365 
366  // shielded recipients
367  if (walletTx->tx->sapData) {
368  for (int j = 0; j < (int) walletTx->tx->sapData->vShieldedOutput.size(); ++j) {
369  const SaplingOutPoint op(walletTx->GetHash(), j);
370  // TODO: This only works for txs that are stored, not for when this is a confirmation dialog..
371  if (walletTx->mapSaplingNoteData.find(op) == walletTx->mapSaplingNoteData.end()) {
372  continue;
373  }
374  // Obtain the noteData to get the cached amount value
375  SaplingNoteData noteData = walletTx->mapSaplingNoteData.at(op);
376  const QString& addrStr = model->getSaplingAddressString(walletTx, op);
377  appendOutput(layoutGrid, i, addrStr, *noteData.amount, nDisplayUnit);
378 
379  i++;
380  }
381  }
382 
383  }
384  }
385  }
386 }
387 
389 {
390  if (snackBar && snackBar->isVisible()) snackBar->hide();
391  QDialog::reject();
392 }
393 
395 {
396  if (snackBar) delete snackBar;
397  delete ui;
398 }
int64_t CAmount
Amount in PIV (Can be negative)
Definition: amount.h:13
uint256 hash
Definition: transaction.h:35
uint32_t n
Definition: transaction.h:36
static QString formatWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=separatorStandard)
Format as string (with unit)
bool IsPayToColdStaking() const
Definition: script.cpp:233
bool IsPayToExchangeAddress() const
Definition: script.cpp:254
An input of a transaction.
Definition: transaction.h:94
COutPoint prevout
Definition: transaction.h:96
An output of a transaction.
Definition: transaction.h:137
CScript scriptPubKey
Definition: transaction.h:140
CAmount nValue
Definition: transaction.h:139
A transaction with a bunch of additional info that only the owner cares about.
Definition: wallet.h:325
CTransactionRef tx
Definition: wallet.h:364
mapSaplingNoteData_t mapSaplingNoteData
Definition: wallet.h:336
const uint256 & GetHash() const
Definition: wallet.h:561
Optional< CAmount > amount
Cached note amount.
An outpoint - a combination of a transaction hash and an index n into its sapling output description ...
Definition: transaction.h:82
UI model for a transaction.
std::string statusToString()
Return transaction status.
TransactionStatus status
Status: can change with block chain update.
Optional< std::string > memo
@ DateRole
Date and time this transaction was created.
@ AmountRole
Net amount of transaction.
void reject() override
SnackBar * snackBar
void accept() override
WalletModel::SendCoinsReturn sendStatus
WalletModel * model
Ui::TxDetailDialog * ui
void setInputsType(CTransactionRef _tx)
TxDetailDialog(QWidget *parent=nullptr, bool isConfirmDialog=true, const QString &warningStr=QString())
void setData(WalletModel *model, WalletModelTransaction *tx)
WalletModelTransaction * tx
Interface to PIVX wallet from Qt view code.
Definition: walletmodel.h:109
QString getSaplingAddressString(const CWalletTx *wtx, const SaplingOutPoint &op) const
SendCoinsReturn sendCoins(WalletModelTransaction &transaction)
Optional< QString > getShieldedAddressFromSpendDesc(const uint256 &txHash, int index)
const CWalletTx * getTx(uint256 id)
Data model for a walletmodel transaction.
unsigned int subtractFeeFromRecipents() const
QList< SendCoinsRecipient > getRecipients()
CTransactionRef & getTransaction()
std::string GetHex() const
Definition: uint256.cpp:21
QString dateTimeStrWithSeconds(const QDateTime &date)
Definition: guiutil.cpp:74
std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType)
boost::optional< T > Optional
Substitute for C++17 std::optional.
Definition: optional.h:12
void setCssProperty(std::initializer_list< QWidget * > args, const QString &value)
Definition: qtutils.cpp:334
void setCssBtnPrimary(QPushButton *btn, bool forceUpdate)
Definition: qtutils.cpp:302
void appendOutput(QGridLayout *layoutGrid, int gridPosition, QString labelRes, CAmount nValue, int nDisplayUnit)
QString formatAdressToShow(const QString &address)
void loadInputs(const QString &leftLabel, const QString &rightLabel, QGridLayout *gridLayoutInput, int pos)
bool ExtractDestination(const CScript &scriptPubKey, CTxDestination &addressRet, bool fColdStake)
Parse a standard scriptPubKey for the destination address.
Definition: standard.cpp:162
boost::variant< CNoDestination, CKeyID, CScriptID, CExchangeKeyID > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:72
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:456