1 #include "transactiontablemodel.h"
3 #include "transactionrecord.h"
4 #include "guiconstants.h"
6 #include "transactiondesc.h"
13 #include <QtAlgorithms>
15 const QString TransactionTableModel::Sent = "s";
16 const QString TransactionTableModel::Received = "r";
17 const QString TransactionTableModel::Other = "o";
19 /* Comparison operator for sort/binary search of model tx list */
22 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
24 return a.hash < b.hash;
26 bool operator()(const TransactionRecord &a, const uint256 &b) const
30 bool operator()(const uint256 &a, const TransactionRecord &b) const
36 /* Private implementation */
37 struct TransactionTablePriv
39 TransactionTablePriv(TransactionTableModel *parent):
44 TransactionTableModel *parent;
46 /* Local cache of wallet.
47 * As it is in the same order as the CWallet, by definition
48 * this is sorted by sha256.
50 QList<TransactionRecord> cachedWallet;
54 qDebug() << "refreshWallet";
56 /* Query entire wallet from core.
59 CRITICAL_BLOCK(cs_mapWallet)
61 for(std::map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
63 cachedWallet.append(TransactionRecord::decomposeTransaction(it->second));
68 /* Update our model of the wallet incrementally.
69 Call with list of hashes of transactions that were added, removed or changed.
71 void updateWallet(const QList<uint256> &updated)
73 /* Walk through updated transactions, update model as needed.
75 qDebug() << "updateWallet";
77 /* Sort update list, and iterate through it in reverse, so that model updates
78 can be emitted from end to beginning (so that earlier updates will not influence
79 the indices of latter ones).
81 QList<uint256> updated_sorted = updated;
82 qSort(updated_sorted);
84 CRITICAL_BLOCK(cs_mapWallet)
86 for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
88 const uint256 &hash = updated_sorted.at(update_idx);
89 /* Find transaction in wallet */
90 std::map<uint256, CWalletTx>::iterator mi = mapWallet.find(hash);
91 bool inWallet = mi != mapWallet.end();
92 /* Find bounds of this transaction in model */
93 QList<TransactionRecord>::iterator lower = qLowerBound(
94 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
95 QList<TransactionRecord>::iterator upper = qUpperBound(
96 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
97 int lowerIndex = (lower - cachedWallet.begin());
98 int upperIndex = (upper - cachedWallet.begin());
100 bool inModel = false;
106 qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
107 << lowerIndex << "-" << upperIndex;
109 if(inWallet && !inModel)
112 QList<TransactionRecord> toInsert =
113 TransactionRecord::decomposeTransaction(mi->second);
114 if(!toInsert.isEmpty()) /* only if something to insert */
116 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
117 int insert_idx = lowerIndex;
118 foreach(const TransactionRecord &rec, toInsert)
120 cachedWallet.insert(insert_idx, rec);
123 parent->endInsertRows();
126 else if(!inWallet && inModel)
129 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
130 cachedWallet.erase(lower, upper);
131 parent->endRemoveRows();
133 else if(inWallet && inModel)
135 /* Updated -- nothing to do, status update will take care of this */
143 return cachedWallet.size();
146 TransactionRecord *index(int idx)
148 if(idx >= 0 && idx < cachedWallet.size())
150 TransactionRecord *rec = &cachedWallet[idx];
152 /* If a status update is needed (blocks came in since last check),
153 update the status of this transaction from the wallet. Otherwise,
154 simply re-use the cached status.
156 if(rec->statusUpdateNeeded())
158 CRITICAL_BLOCK(cs_mapWallet)
160 std::map<uint256, CWalletTx>::iterator mi = mapWallet.find(rec->hash);
162 if(mi != mapWallet.end())
164 rec->updateStatus(mi->second);
176 QString describe(TransactionRecord *rec)
178 CRITICAL_BLOCK(cs_mapWallet)
180 std::map<uint256, CWalletTx>::iterator mi = mapWallet.find(rec->hash);
181 if(mi != mapWallet.end())
183 return QString::fromStdString(TransactionDesc::toHTML(mi->second));
191 /* Credit and Debit columns are right-aligned as they contain numbers */
192 static int column_alignments[] = {
193 Qt::AlignLeft|Qt::AlignVCenter,
194 Qt::AlignLeft|Qt::AlignVCenter,
195 Qt::AlignLeft|Qt::AlignVCenter,
196 Qt::AlignRight|Qt::AlignVCenter,
197 Qt::AlignRight|Qt::AlignVCenter,
198 Qt::AlignLeft|Qt::AlignVCenter
201 TransactionTableModel::TransactionTableModel(QObject *parent):
202 QAbstractTableModel(parent),
203 priv(new TransactionTablePriv(this))
205 columns << tr("Status") << tr("Date") << tr("Description") << tr("Debit") << tr("Credit");
207 priv->refreshWallet();
209 QTimer *timer = new QTimer(this);
210 connect(timer, SIGNAL(timeout()), this, SLOT(update()));
211 timer->start(MODEL_UPDATE_DELAY);
214 TransactionTableModel::~TransactionTableModel()
219 void TransactionTableModel::update()
221 QList<uint256> updated;
223 /* Check if there are changes to wallet map */
224 TRY_CRITICAL_BLOCK(cs_mapWallet)
226 if(!vWalletUpdated.empty())
228 BOOST_FOREACH(uint256 hash, vWalletUpdated)
230 updated.append(hash);
232 vWalletUpdated.clear();
238 priv->updateWallet(updated);
240 /* Status (number of confirmations) and (possibly) description
241 columns changed for all rows.
243 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
244 emit dataChanged(index(0, Description), index(priv->size()-1, Description));
248 int TransactionTableModel::rowCount(const QModelIndex &parent) const
254 int TransactionTableModel::columnCount(const QModelIndex &parent) const
257 return columns.length();
260 QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
264 switch(wtx->status.status)
266 case TransactionStatus::OpenUntilBlock:
267 status = tr("Open for %n block(s)","",wtx->status.open_for);
269 case TransactionStatus::OpenUntilDate:
270 status = tr("Open until ") + GUIUtil::DateTimeStr(wtx->status.open_for);
272 case TransactionStatus::Offline:
273 status = tr("%1/offline").arg(wtx->status.depth);
275 case TransactionStatus::Unconfirmed:
276 status = tr("%1/unconfirmed").arg(wtx->status.depth);
278 case TransactionStatus::HaveConfirmations:
279 status = tr("%1 confirmations").arg(wtx->status.depth);
283 return QVariant(status);
286 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
290 return QVariant(GUIUtil::DateTimeStr(wtx->time));
298 /* Look up address in address book, if found return
299 address[0:12]... (label)
300 otherwise just return address
302 std::string lookupAddress(const std::string &address)
304 std::string description;
305 CRITICAL_BLOCK(cs_mapAddressBook)
307 std::map<std::string, std::string>::iterator mi = mapAddressBook.find(address);
308 if (mi != mapAddressBook.end() && !(*mi).second.empty())
310 std::string label = (*mi).second;
311 description += address.substr(0,12) + "... ";
312 description += "(" + label + ")";
316 description += address;
322 QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx) const
328 case TransactionRecord::RecvWithAddress:
329 description = tr("Received with: ") + QString::fromStdString(lookupAddress(wtx->address));
331 case TransactionRecord::RecvFromIP:
332 description = tr("Received from IP: ") + QString::fromStdString(wtx->address);
334 case TransactionRecord::SendToAddress:
335 description = tr("Sent to: ") + QString::fromStdString(lookupAddress(wtx->address));
337 case TransactionRecord::SendToIP:
338 description = tr("Sent to IP: ") + QString::fromStdString(wtx->address);
340 case TransactionRecord::SendToSelf:
341 description = tr("Payment to yourself");
343 case TransactionRecord::Generated:
344 switch(wtx->status.maturity)
346 case TransactionStatus::Immature:
347 description = tr("Generated (matures in %n more blocks)", "",
348 wtx->status.matures_in);
350 case TransactionStatus::Mature:
351 description = tr("Generated");
353 case TransactionStatus::MaturesWarning:
354 description = tr("Generated - Warning: This block was not received by any other nodes and will probably not be accepted!");
356 case TransactionStatus::NotAccepted:
357 description = tr("Generated (not accepted)");
362 return QVariant(description);
365 QVariant TransactionTableModel::formatTxDebit(const TransactionRecord *wtx) const
369 QString str = QString::fromStdString(FormatMoney(wtx->debit));
370 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
372 str = QString("[") + str + QString("]");
374 return QVariant(str);
382 QVariant TransactionTableModel::formatTxCredit(const TransactionRecord *wtx) const
386 QString str = QString::fromStdString(FormatMoney(wtx->credit));
387 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
389 str = QString("[") + str + QString("]");
391 return QVariant(str);
399 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
403 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
405 if(role == Qt::DisplayRole)
407 /* Delegate to specific column handlers */
408 switch(index.column())
411 return formatTxStatus(rec);
413 return formatTxDate(rec);
415 return formatTxDescription(rec);
417 return formatTxDebit(rec);
419 return formatTxCredit(rec);
422 else if(role == Qt::EditRole)
424 /* Edit role is used for sorting so return the real values */
425 switch(index.column())
428 return QString::fromStdString(rec->status.sortKey);
432 return formatTxDescription(rec);
439 else if (role == Qt::TextAlignmentRole)
441 return column_alignments[index.column()];
443 else if (role == Qt::ForegroundRole)
445 /* Non-confirmed transactions are grey */
446 if(rec->status.confirmed)
448 return QColor(0, 0, 0);
452 return QColor(128, 128, 128);
455 else if (role == TypeRole)
457 /* Role for filtering tabs by type */
460 case TransactionRecord::RecvWithAddress:
461 case TransactionRecord::RecvFromIP:
462 return TransactionTableModel::Received;
463 case TransactionRecord::SendToAddress:
464 case TransactionRecord::SendToIP:
465 case TransactionRecord::SendToSelf:
466 return TransactionTableModel::Sent;
468 return TransactionTableModel::Other;
471 else if (role == LongDescriptionRole)
473 return priv->describe(rec);
478 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
480 if(orientation == Qt::Horizontal)
482 if(role == Qt::DisplayRole)
484 return columns[section];
486 else if (role == Qt::TextAlignmentRole)
488 return column_alignments[section];
494 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
496 return QAbstractTableModel::flags(index);
499 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
502 TransactionRecord *data = priv->index(row);
505 return createIndex(row, column, priv->index(row));
509 return QModelIndex();