1 #include "transactiontablemodel.h"
3 #include "transactionrecord.h"
4 #include "guiconstants.h"
5 #include "transactiondesc.h"
6 #include "walletmodel.h"
7 #include "optionsmodel.h"
8 #include "addresstablemodel.h"
9 #include "bitcoinunits.h"
12 #include "ui_interface.h"
20 #include <QtAlgorithms>
22 // Amount column is right-aligned it contains numbers
23 static int column_alignments[] = {
24 Qt::AlignLeft|Qt::AlignVCenter,
25 Qt::AlignLeft|Qt::AlignVCenter,
26 Qt::AlignLeft|Qt::AlignVCenter,
27 Qt::AlignLeft|Qt::AlignVCenter,
28 Qt::AlignRight|Qt::AlignVCenter
31 // Comparison operator for sort/binary search of model tx list
34 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
36 return a.hash < b.hash;
38 bool operator()(const TransactionRecord &a, const uint256 &b) const
42 bool operator()(const uint256 &a, const TransactionRecord &b) const
48 // Private implementation
49 class TransactionTablePriv
52 TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
58 TransactionTableModel *parent;
60 /* Local cache of wallet.
61 * As it is in the same order as the CWallet, by definition
62 * this is sorted by sha256.
64 QList<TransactionRecord> cachedWallet;
66 /* Query entire wallet anew from core.
70 OutputDebugStringF("refreshWallet\n");
73 LOCK(wallet->cs_wallet);
74 for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
76 if(TransactionRecord::showTransaction(it->second))
77 cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
82 /* Update our model of the wallet incrementally, to synchronize our model of the wallet
83 with that of the core.
85 Call with transaction that was added, removed or changed.
87 void updateWallet(const uint256 &hash, int status)
89 OutputDebugStringF("updateWallet %s %i\n", hash.ToString().c_str(), status);
91 LOCK(wallet->cs_wallet);
93 // Find transaction in wallet
94 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
95 bool inWallet = mi != wallet->mapWallet.end();
97 // Find bounds of this transaction in model
98 QList<TransactionRecord>::iterator lower = qLowerBound(
99 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
100 QList<TransactionRecord>::iterator upper = qUpperBound(
101 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
102 int lowerIndex = (lower - cachedWallet.begin());
103 int upperIndex = (upper - cachedWallet.begin());
104 bool inModel = (lower != upper);
106 // Determine whether to show transaction or not
107 bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second));
109 if(status == CT_UPDATED)
111 if(showTransaction && !inModel)
112 status = CT_NEW; /* Not in model, but want to show, treat as new */
113 if(!showTransaction && inModel)
114 status = CT_DELETED; /* In model, but want to hide, treat as deleted */
117 OutputDebugStringF(" inWallet=%i inModel=%i Index=%i-%i showTransaction=%i derivedStatus=%i\n",
118 inWallet, inModel, lowerIndex, upperIndex, showTransaction, status);
125 OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is already in model\n");
130 OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is not in wallet\n");
135 // Added -- insert at the right position
136 QList<TransactionRecord> toInsert =
137 TransactionRecord::decomposeTransaction(wallet, mi->second);
138 if(!toInsert.isEmpty()) /* only if something to insert */
140 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
141 int insert_idx = lowerIndex;
142 foreach(const TransactionRecord &rec, toInsert)
144 cachedWallet.insert(insert_idx, rec);
147 parent->endInsertRows();
154 OutputDebugStringF("Warning: updateWallet: Got CT_DELETED, but transaction is not in model\n");
157 // Removed -- remove entire transaction from table
158 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
159 cachedWallet.erase(lower, upper);
160 parent->endRemoveRows();
163 // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
164 // visible transactions.
172 return cachedWallet.size();
175 TransactionRecord *index(int idx)
177 if(idx >= 0 && idx < cachedWallet.size())
179 TransactionRecord *rec = &cachedWallet[idx];
181 // If a status update is needed (blocks came in since last check),
182 // update the status of this transaction from the wallet. Otherwise,
183 // simply re-use the cached status.
184 if(rec->statusUpdateNeeded())
187 LOCK(wallet->cs_wallet);
188 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
190 if(mi != wallet->mapWallet.end())
192 rec->updateStatus(mi->second);
204 QString describe(TransactionRecord *rec)
207 LOCK(wallet->cs_wallet);
208 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
209 if(mi != wallet->mapWallet.end())
211 return TransactionDesc::toHTML(wallet, mi->second);
219 TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *parent):
220 QAbstractTableModel(parent),
223 priv(new TransactionTablePriv(wallet, this)),
226 columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
228 priv->refreshWallet();
230 QTimer *timer = new QTimer(this);
231 connect(timer, SIGNAL(timeout()), this, SLOT(updateConfirmations()));
232 timer->start(MODEL_UPDATE_DELAY);
234 connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
237 TransactionTableModel::~TransactionTableModel()
242 void TransactionTableModel::updateTransaction(const QString &hash, int status)
245 updated.SetHex(hash.toStdString());
247 priv->updateWallet(updated, status);
250 void TransactionTableModel::updateConfirmations()
252 if(nBestHeight != cachedNumBlocks)
254 cachedNumBlocks = nBestHeight;
255 // Blocks came in since last poll.
256 // Invalidate status (number of confirmations) and (possibly) description
257 // for all rows. Qt is smart enough to only actually request the data for the
259 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
260 emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
264 int TransactionTableModel::rowCount(const QModelIndex &parent) const
270 int TransactionTableModel::columnCount(const QModelIndex &parent) const
273 return columns.length();
276 QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
280 switch(wtx->status.status)
282 case TransactionStatus::OpenUntilBlock:
283 status = tr("Open for %n block(s)","",wtx->status.open_for);
285 case TransactionStatus::OpenUntilDate:
286 status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
288 case TransactionStatus::Offline:
289 status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
291 case TransactionStatus::Unconfirmed:
292 status = tr("Unconfirmed (%1 of %2 confirmations)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
294 case TransactionStatus::HaveConfirmations:
295 status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
299 if(wtx->type == TransactionRecord::Generated)
301 switch(wtx->status.maturity)
303 case TransactionStatus::Immature:
304 status += "\n" + tr("Mined balance will be available when it matures in %n more block(s)", "", wtx->status.matures_in);
306 case TransactionStatus::Mature:
308 case TransactionStatus::MaturesWarning:
309 status += "\n" + tr("This block was not received by any other nodes and will probably not be accepted!");
311 case TransactionStatus::NotAccepted:
312 status += "\n" + tr("Generated but not accepted");
320 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
324 return GUIUtil::dateTimeStr(wtx->time);
332 /* Look up address in address book, if found return label (address)
333 otherwise just return (address)
335 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
337 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
341 description += label + QString(" ");
343 if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
345 description += QString("(") + QString::fromStdString(address) + QString(")");
350 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
354 case TransactionRecord::RecvWithAddress:
355 return tr("Received with");
356 case TransactionRecord::RecvFromOther:
357 return tr("Received from");
358 case TransactionRecord::SendToAddress:
359 case TransactionRecord::SendToOther:
360 return tr("Sent to");
361 case TransactionRecord::SendToSelf:
362 return tr("Payment to yourself");
363 case TransactionRecord::Generated:
370 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
374 case TransactionRecord::Generated:
375 return QIcon(":/icons/tx_mined");
376 case TransactionRecord::RecvWithAddress:
377 case TransactionRecord::RecvFromOther:
378 return QIcon(":/icons/tx_input");
379 case TransactionRecord::SendToAddress:
380 case TransactionRecord::SendToOther:
381 return QIcon(":/icons/tx_output");
383 return QIcon(":/icons/tx_inout");
388 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
392 case TransactionRecord::RecvFromOther:
393 return QString::fromStdString(wtx->address);
394 case TransactionRecord::RecvWithAddress:
395 case TransactionRecord::SendToAddress:
396 case TransactionRecord::Generated:
397 return lookupAddress(wtx->address, tooltip);
398 case TransactionRecord::SendToOther:
399 return QString::fromStdString(wtx->address);
400 case TransactionRecord::SendToSelf:
406 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
408 // Show addresses without label in a less visible color
411 case TransactionRecord::RecvWithAddress:
412 case TransactionRecord::SendToAddress:
413 case TransactionRecord::Generated:
415 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
417 return COLOR_BAREADDRESS;
419 case TransactionRecord::SendToSelf:
420 return COLOR_BAREADDRESS;
427 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
429 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
432 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
434 str = QString("[") + str + QString("]");
440 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
442 if(wtx->type == TransactionRecord::Generated)
444 switch(wtx->status.maturity)
446 case TransactionStatus::Immature: {
447 int total = wtx->status.depth + wtx->status.matures_in;
448 int part = (wtx->status.depth * 4 / total) + 1;
449 return QIcon(QString(":/icons/transaction_%1").arg(part));
451 case TransactionStatus::Mature:
452 return QIcon(":/icons/transaction_confirmed");
453 case TransactionStatus::MaturesWarning:
454 case TransactionStatus::NotAccepted:
455 return QIcon(":/icons/transaction_0");
460 switch(wtx->status.status)
462 case TransactionStatus::OpenUntilBlock:
463 case TransactionStatus::OpenUntilDate:
464 return QColor(64,64,255);
466 case TransactionStatus::Offline:
467 return QColor(192,192,192);
468 case TransactionStatus::Unconfirmed:
469 switch(wtx->status.depth)
471 case 0: return QIcon(":/icons/transaction_0");
472 case 1: return QIcon(":/icons/transaction_1");
473 case 2: return QIcon(":/icons/transaction_2");
474 case 3: return QIcon(":/icons/transaction_3");
475 case 4: return QIcon(":/icons/transaction_4");
476 default: return QIcon(":/icons/transaction_5");
478 case TransactionStatus::HaveConfirmations:
479 return QIcon(":/icons/transaction_confirmed");
482 return QColor(0,0,0);
485 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
487 QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
488 if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
489 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
491 tooltip += QString(" ") + formatTxToAddress(rec, true);
496 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
500 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
504 case Qt::DecorationRole:
505 switch(index.column())
508 return txStatusDecoration(rec);
510 return txAddressDecoration(rec);
513 case Qt::DisplayRole:
514 switch(index.column())
517 return formatTxDate(rec);
519 return formatTxType(rec);
521 return formatTxToAddress(rec, false);
523 return formatTxAmount(rec);
527 // Edit role is used for sorting, so return the unformatted values
528 switch(index.column())
531 return QString::fromStdString(rec->status.sortKey);
535 return formatTxType(rec);
537 return formatTxToAddress(rec, true);
539 return rec->credit + rec->debit;
542 case Qt::ToolTipRole:
543 return formatTooltip(rec);
544 case Qt::TextAlignmentRole:
545 return column_alignments[index.column()];
546 case Qt::ForegroundRole:
547 // Non-confirmed transactions are grey
548 if(!rec->status.confirmed)
550 return COLOR_UNCONFIRMED;
552 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
554 return COLOR_NEGATIVE;
556 if(index.column() == ToAddress)
558 return addressColor(rec);
564 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
565 case LongDescriptionRole:
566 return priv->describe(rec);
568 return QString::fromStdString(rec->address);
570 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
572 return rec->credit + rec->debit;
574 return QString::fromStdString(rec->getTxID());
576 return QString::fromStdString(rec->hash.ToString());
578 // Return True if transaction counts for balance
579 return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && rec->status.maturity != TransactionStatus::Mature);
580 case FormattedAmountRole:
581 return formatTxAmount(rec, false);
586 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
588 if(orientation == Qt::Horizontal)
590 if(role == Qt::DisplayRole)
592 return columns[section];
594 else if (role == Qt::TextAlignmentRole)
596 return column_alignments[section];
597 } else if (role == Qt::ToolTipRole)
602 return tr("Transaction status. Hover over this field to show number of confirmations.");
604 return tr("Date and time that the transaction was received.");
606 return tr("Type of transaction.");
608 return tr("Destination address of transaction.");
610 return tr("Amount removed from or added to balance.");
617 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
620 TransactionRecord *data = priv->index(row);
623 return createIndex(row, column, priv->index(row));
627 return QModelIndex();
631 void TransactionTableModel::updateDisplayUnit()
633 // emit dataChanged to update Amount column with the current unit
634 emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));