Line data Source code
1 : // Copyright (c) 2011-2014 The Bitcoin 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 "transactiontablemodel.h"
6 :
7 : #include "addresstablemodel.h"
8 : #include "guiconstants.h"
9 : #include "guiutil.h"
10 : #include "optionsmodel.h"
11 : #include "platformstyle.h"
12 : #include "transactiondesc.h"
13 : #include "transactionrecord.h"
14 : #include "walletmodel.h"
15 :
16 : #include "main.h"
17 : #include "sync.h"
18 : #include "uint256.h"
19 : #include "util.h"
20 : #include "wallet/wallet.h"
21 :
22 : #include <QColor>
23 : #include <QDateTime>
24 : #include <QDebug>
25 : #include <QIcon>
26 : #include <QList>
27 :
28 : #include <boost/foreach.hpp>
29 :
30 : // Amount column is right-aligned it contains numbers
31 : static int column_alignments[] = {
32 0 : Qt::AlignLeft|Qt::AlignVCenter, /* status */
33 0 : Qt::AlignLeft|Qt::AlignVCenter, /* watchonly */
34 0 : Qt::AlignLeft|Qt::AlignVCenter, /* date */
35 0 : Qt::AlignLeft|Qt::AlignVCenter, /* type */
36 0 : Qt::AlignLeft|Qt::AlignVCenter, /* address */
37 0 : Qt::AlignRight|Qt::AlignVCenter /* amount */
38 0 : };
39 :
40 : // Comparison operator for sort/binary search of model tx list
41 : struct TxLessThan
42 : {
43 : bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
44 : {
45 : return a.hash < b.hash;
46 : }
47 0 : bool operator()(const TransactionRecord &a, const uint256 &b) const
48 : {
49 0 : return a.hash < b;
50 : }
51 0 : bool operator()(const uint256 &a, const TransactionRecord &b) const
52 : {
53 0 : return a < b.hash;
54 : }
55 : };
56 :
57 : // Private implementation
58 0 : class TransactionTablePriv
59 : {
60 : public:
61 0 : TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent) :
62 : wallet(wallet),
63 0 : parent(parent)
64 : {
65 0 : }
66 :
67 : CWallet *wallet;
68 : TransactionTableModel *parent;
69 :
70 : /* Local cache of wallet.
71 : * As it is in the same order as the CWallet, by definition
72 : * this is sorted by sha256.
73 : */
74 : QList<TransactionRecord> cachedWallet;
75 :
76 : /* Query entire wallet anew from core.
77 : */
78 0 : void refreshWallet()
79 : {
80 0 : qDebug() << "TransactionTablePriv::refreshWallet";
81 0 : cachedWallet.clear();
82 : {
83 0 : LOCK2(cs_main, wallet->cs_wallet);
84 0 : for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
85 : {
86 0 : if(TransactionRecord::showTransaction(it->second))
87 0 : cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
88 : }
89 : }
90 0 : }
91 :
92 : /* Update our model of the wallet incrementally, to synchronize our model of the wallet
93 : with that of the core.
94 :
95 : Call with transaction that was added, removed or changed.
96 : */
97 0 : void updateWallet(const uint256 &hash, int status, bool showTransaction)
98 : {
99 0 : qDebug() << "TransactionTablePriv::updateWallet: " + QString::fromStdString(hash.ToString()) + " " + QString::number(status);
100 :
101 : // Find bounds of this transaction in model
102 : QList<TransactionRecord>::iterator lower = qLowerBound(
103 0 : cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
104 : QList<TransactionRecord>::iterator upper = qUpperBound(
105 0 : cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
106 0 : int lowerIndex = (lower - cachedWallet.begin());
107 0 : int upperIndex = (upper - cachedWallet.begin());
108 0 : bool inModel = (lower != upper);
109 :
110 0 : if(status == CT_UPDATED)
111 : {
112 0 : if(showTransaction && !inModel)
113 0 : status = CT_NEW; /* Not in model, but want to show, treat as new */
114 0 : if(!showTransaction && inModel)
115 0 : status = CT_DELETED; /* In model, but want to hide, treat as deleted */
116 : }
117 :
118 0 : qDebug() << " inModel=" + QString::number(inModel) +
119 0 : " Index=" + QString::number(lowerIndex) + "-" + QString::number(upperIndex) +
120 0 : " showTransaction=" + QString::number(showTransaction) + " derivedStatus=" + QString::number(status);
121 :
122 0 : switch(status)
123 : {
124 : case CT_NEW:
125 0 : if(inModel)
126 : {
127 0 : qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_NEW, but transaction is already in model";
128 0 : break;
129 : }
130 0 : if(showTransaction)
131 : {
132 0 : LOCK2(cs_main, wallet->cs_wallet);
133 : // Find transaction in wallet
134 0 : std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
135 0 : if(mi == wallet->mapWallet.end())
136 : {
137 0 : qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_NEW, but transaction is not in wallet";
138 : break;
139 : }
140 : // Added -- insert at the right position
141 : QList<TransactionRecord> toInsert =
142 0 : TransactionRecord::decomposeTransaction(wallet, mi->second);
143 0 : if(!toInsert.isEmpty()) /* only if something to insert */
144 : {
145 0 : parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
146 0 : int insert_idx = lowerIndex;
147 0 : Q_FOREACH(const TransactionRecord &rec, toInsert)
148 : {
149 0 : cachedWallet.insert(insert_idx, rec);
150 0 : insert_idx += 1;
151 : }
152 0 : parent->endInsertRows();
153 : }
154 : }
155 : break;
156 : case CT_DELETED:
157 0 : if(!inModel)
158 : {
159 0 : qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_DELETED, but transaction is not in model";
160 0 : break;
161 : }
162 : // Removed -- remove entire transaction from table
163 0 : parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
164 0 : cachedWallet.erase(lower, upper);
165 0 : parent->endRemoveRows();
166 : break;
167 : case CT_UPDATED:
168 : // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
169 : // visible transactions.
170 : break;
171 : }
172 0 : }
173 :
174 : int size()
175 : {
176 0 : return cachedWallet.size();
177 : }
178 :
179 0 : TransactionRecord *index(int idx)
180 : {
181 0 : if(idx >= 0 && idx < cachedWallet.size())
182 : {
183 0 : TransactionRecord *rec = &cachedWallet[idx];
184 :
185 : // Get required locks upfront. This avoids the GUI from getting
186 : // stuck if the core is holding the locks for a longer time - for
187 : // example, during a wallet rescan.
188 : //
189 : // If a status update is needed (blocks came in since last check),
190 : // update the status of this transaction from the wallet. Otherwise,
191 : // simply re-use the cached status.
192 0 : TRY_LOCK(cs_main, lockMain);
193 0 : if(lockMain)
194 : {
195 0 : TRY_LOCK(wallet->cs_wallet, lockWallet);
196 0 : if(lockWallet && rec->statusUpdateNeeded())
197 : {
198 0 : std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
199 :
200 0 : if(mi != wallet->mapWallet.end())
201 : {
202 0 : rec->updateStatus(mi->second);
203 : }
204 : }
205 : }
206 0 : return rec;
207 : }
208 : return 0;
209 : }
210 :
211 0 : QString describe(TransactionRecord *rec, int unit)
212 : {
213 : {
214 0 : LOCK2(cs_main, wallet->cs_wallet);
215 0 : std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
216 0 : if(mi != wallet->mapWallet.end())
217 : {
218 0 : return TransactionDesc::toHTML(wallet, mi->second, rec, unit);
219 : }
220 : }
221 : return QString();
222 : }
223 : };
224 :
225 0 : TransactionTableModel::TransactionTableModel(const PlatformStyle *platformStyle, CWallet* wallet, WalletModel *parent):
226 : QAbstractTableModel(parent),
227 : wallet(wallet),
228 : walletModel(parent),
229 : priv(new TransactionTablePriv(wallet, this)),
230 : fProcessingQueuedTransactions(false),
231 0 : platformStyle(platformStyle)
232 : {
233 0 : columns << QString() << QString() << tr("Date") << tr("Type") << tr("Label") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
234 0 : priv->refreshWallet();
235 :
236 0 : connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
237 :
238 0 : subscribeToCoreSignals();
239 0 : }
240 :
241 0 : TransactionTableModel::~TransactionTableModel()
242 : {
243 0 : unsubscribeFromCoreSignals();
244 0 : delete priv;
245 0 : }
246 :
247 : /** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */
248 0 : void TransactionTableModel::updateAmountColumnTitle()
249 : {
250 0 : columns[Amount] = BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
251 0 : Q_EMIT headerDataChanged(Qt::Horizontal,Amount,Amount);
252 0 : }
253 :
254 0 : void TransactionTableModel::updateTransaction(const QString &hash, int status, bool showTransaction)
255 : {
256 : uint256 updated;
257 0 : updated.SetHex(hash.toStdString());
258 :
259 0 : priv->updateWallet(updated, status, showTransaction);
260 0 : }
261 :
262 0 : void TransactionTableModel::updateConfirmations()
263 : {
264 : // Blocks came in since last poll.
265 : // Invalidate status (number of confirmations) and (possibly) description
266 : // for all rows. Qt is smart enough to only actually request the data for the
267 : // visible rows.
268 0 : Q_EMIT dataChanged(index(0, Status), index(priv->size()-1, Status));
269 0 : Q_EMIT dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
270 0 : }
271 :
272 0 : int TransactionTableModel::rowCount(const QModelIndex &parent) const
273 : {
274 : Q_UNUSED(parent);
275 0 : return priv->size();
276 : }
277 :
278 0 : int TransactionTableModel::columnCount(const QModelIndex &parent) const
279 : {
280 : Q_UNUSED(parent);
281 0 : return columns.length();
282 : }
283 :
284 0 : QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
285 : {
286 : QString status;
287 :
288 0 : switch(wtx->status.status)
289 : {
290 : case TransactionStatus::OpenUntilBlock:
291 0 : status = tr("Open for %n more block(s)","",wtx->status.open_for);
292 0 : break;
293 : case TransactionStatus::OpenUntilDate:
294 0 : status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
295 0 : break;
296 : case TransactionStatus::Offline:
297 0 : status = tr("Offline");
298 0 : break;
299 : case TransactionStatus::Unconfirmed:
300 0 : status = tr("Unconfirmed");
301 0 : break;
302 : case TransactionStatus::Confirming:
303 0 : status = tr("Confirming (%1 of %2 recommended confirmations)").arg(wtx->status.depth).arg(TransactionRecord::RecommendedNumConfirmations);
304 0 : break;
305 : case TransactionStatus::Confirmed:
306 0 : status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
307 0 : break;
308 : case TransactionStatus::Conflicted:
309 0 : status = tr("Conflicted");
310 0 : break;
311 : case TransactionStatus::Immature:
312 0 : status = tr("Immature (%1 confirmations, will be available after %2)").arg(wtx->status.depth).arg(wtx->status.depth + wtx->status.matures_in);
313 0 : break;
314 : case TransactionStatus::MaturesWarning:
315 0 : status = tr("This block was not received by any other nodes and will probably not be accepted!");
316 0 : break;
317 : case TransactionStatus::NotAccepted:
318 0 : status = tr("Generated but not accepted");
319 0 : break;
320 : }
321 :
322 0 : return status;
323 : }
324 :
325 0 : QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
326 : {
327 0 : if(wtx->time)
328 : {
329 0 : return GUIUtil::dateTimeStr(wtx->time);
330 : }
331 : return QString();
332 : }
333 :
334 : /* Look up address in address book, if found return label (address)
335 : otherwise just return (address)
336 : */
337 0 : QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
338 : {
339 0 : QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
340 : QString description;
341 0 : if(!label.isEmpty())
342 : {
343 : description += label;
344 : }
345 0 : if(label.isEmpty() || tooltip)
346 : {
347 0 : description += QString(" (") + QString::fromStdString(address) + QString(")");
348 : }
349 0 : return description;
350 : }
351 :
352 0 : QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
353 : {
354 0 : switch(wtx->type)
355 : {
356 : case TransactionRecord::RecvWithAddress:
357 : return tr("Received with");
358 : case TransactionRecord::RecvFromOther:
359 : return tr("Received from");
360 : case TransactionRecord::SendToAddress:
361 : case TransactionRecord::SendToOther:
362 : return tr("Sent to");
363 : case TransactionRecord::SendToSelf:
364 : return tr("Payment to yourself");
365 : case TransactionRecord::Generated:
366 : return tr("Mined");
367 : default:
368 : return QString();
369 : }
370 : }
371 :
372 0 : QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
373 : {
374 0 : switch(wtx->type)
375 : {
376 : case TransactionRecord::Generated:
377 0 : return QIcon(":/icons/tx_mined");
378 : case TransactionRecord::RecvWithAddress:
379 : case TransactionRecord::RecvFromOther:
380 0 : return QIcon(":/icons/tx_input");
381 : case TransactionRecord::SendToAddress:
382 : case TransactionRecord::SendToOther:
383 0 : return QIcon(":/icons/tx_output");
384 : default:
385 0 : return QIcon(":/icons/tx_inout");
386 : }
387 : }
388 :
389 0 : QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
390 : {
391 : QString watchAddress;
392 0 : if (tooltip) {
393 : // Mark transactions involving watch-only addresses by adding " (watch-only)"
394 0 : watchAddress = wtx->involvesWatchAddress ? QString(" (") + tr("watch-only") + QString(")") : "";
395 : }
396 :
397 0 : switch(wtx->type)
398 : {
399 : case TransactionRecord::RecvFromOther:
400 0 : return QString::fromStdString(wtx->address) + watchAddress;
401 : case TransactionRecord::RecvWithAddress:
402 : case TransactionRecord::SendToAddress:
403 : case TransactionRecord::Generated:
404 0 : return lookupAddress(wtx->address, tooltip) + watchAddress;
405 : case TransactionRecord::SendToOther:
406 0 : return QString::fromStdString(wtx->address) + watchAddress;
407 : case TransactionRecord::SendToSelf:
408 : default:
409 0 : return tr("(n/a)") + watchAddress;
410 0 : }
411 : }
412 :
413 0 : QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
414 : {
415 : // Show addresses without label in a less visible color
416 0 : switch(wtx->type)
417 : {
418 : case TransactionRecord::RecvWithAddress:
419 : case TransactionRecord::SendToAddress:
420 : case TransactionRecord::Generated:
421 : {
422 0 : QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
423 0 : if(label.isEmpty())
424 0 : return COLOR_BAREADDRESS;
425 0 : } break;
426 : case TransactionRecord::SendToSelf:
427 0 : return COLOR_BAREADDRESS;
428 : default:
429 : break;
430 : }
431 : return QVariant();
432 : }
433 :
434 0 : QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed, BitcoinUnits::SeparatorStyle separators) const
435 : {
436 0 : QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit, false, separators);
437 0 : if(showUnconfirmed)
438 : {
439 0 : if(!wtx->status.countsForBalance)
440 : {
441 0 : str = QString("[") + str + QString("]");
442 : }
443 : }
444 0 : return QString(str);
445 : }
446 :
447 0 : QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
448 : {
449 0 : switch(wtx->status.status)
450 : {
451 : case TransactionStatus::OpenUntilBlock:
452 : case TransactionStatus::OpenUntilDate:
453 0 : return COLOR_TX_STATUS_OPENUNTILDATE;
454 : case TransactionStatus::Offline:
455 0 : return COLOR_TX_STATUS_OFFLINE;
456 : case TransactionStatus::Unconfirmed:
457 0 : return QIcon(":/icons/transaction_0");
458 : case TransactionStatus::Confirming:
459 0 : switch(wtx->status.depth)
460 : {
461 0 : case 1: return QIcon(":/icons/transaction_1");
462 0 : case 2: return QIcon(":/icons/transaction_2");
463 0 : case 3: return QIcon(":/icons/transaction_3");
464 0 : case 4: return QIcon(":/icons/transaction_4");
465 0 : default: return QIcon(":/icons/transaction_5");
466 : };
467 : case TransactionStatus::Confirmed:
468 0 : return QIcon(":/icons/transaction_confirmed");
469 : case TransactionStatus::Conflicted:
470 0 : return QIcon(":/icons/transaction_conflicted");
471 : case TransactionStatus::Immature: {
472 0 : int total = wtx->status.depth + wtx->status.matures_in;
473 0 : int part = (wtx->status.depth * 4 / total) + 1;
474 0 : return QIcon(QString(":/icons/transaction_%1").arg(part));
475 : }
476 : case TransactionStatus::MaturesWarning:
477 : case TransactionStatus::NotAccepted:
478 0 : return QIcon(":/icons/transaction_0");
479 : default:
480 0 : return COLOR_BLACK;
481 : }
482 : }
483 :
484 0 : QVariant TransactionTableModel::txWatchonlyDecoration(const TransactionRecord *wtx) const
485 : {
486 0 : if (wtx->involvesWatchAddress)
487 0 : return QIcon(":/icons/eye");
488 : else
489 : return QVariant();
490 : }
491 :
492 0 : QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
493 : {
494 0 : QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
495 0 : if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
496 0 : rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
497 : {
498 0 : tooltip += QString(" ") + formatTxToAddress(rec, true);
499 : }
500 0 : return tooltip;
501 : }
502 :
503 0 : QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
504 : {
505 0 : if(!index.isValid())
506 : return QVariant();
507 0 : TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
508 :
509 0 : switch(role)
510 : {
511 : case RawDecorationRole:
512 0 : switch(index.column())
513 : {
514 : case Status:
515 0 : return txStatusDecoration(rec);
516 : case Watchonly:
517 0 : return txWatchonlyDecoration(rec);
518 : case ToAddress:
519 0 : return txAddressDecoration(rec);
520 : }
521 : break;
522 : case Qt::DecorationRole:
523 : {
524 0 : QIcon icon = qvariant_cast<QIcon>(index.data(RawDecorationRole));
525 0 : return platformStyle->TextColorIcon(icon);
526 : }
527 : case Qt::DisplayRole:
528 0 : switch(index.column())
529 : {
530 : case Date:
531 0 : return formatTxDate(rec);
532 : case Type:
533 0 : return formatTxType(rec);
534 : case ToAddress:
535 0 : return formatTxToAddress(rec, false);
536 : case Amount:
537 0 : return formatTxAmount(rec, true, BitcoinUnits::separatorAlways);
538 : }
539 : break;
540 : case Qt::EditRole:
541 : // Edit role is used for sorting, so return the unformatted values
542 0 : switch(index.column())
543 : {
544 : case Status:
545 0 : return QString::fromStdString(rec->status.sortKey);
546 : case Date:
547 0 : return rec->time;
548 : case Type:
549 0 : return formatTxType(rec);
550 : case Watchonly:
551 0 : return (rec->involvesWatchAddress ? 1 : 0);
552 : case ToAddress:
553 0 : return formatTxToAddress(rec, true);
554 : case Amount:
555 0 : return qint64(rec->credit + rec->debit);
556 : }
557 : break;
558 : case Qt::ToolTipRole:
559 0 : return formatTooltip(rec);
560 : case Qt::TextAlignmentRole:
561 0 : return column_alignments[index.column()];
562 : case Qt::ForegroundRole:
563 : // Non-confirmed (but not immature) as transactions are grey
564 0 : if(!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature)
565 : {
566 0 : return COLOR_UNCONFIRMED;
567 : }
568 0 : if(index.column() == Amount && (rec->credit+rec->debit) < 0)
569 : {
570 0 : return COLOR_NEGATIVE;
571 : }
572 0 : if(index.column() == ToAddress)
573 : {
574 0 : return addressColor(rec);
575 : }
576 : break;
577 : case TypeRole:
578 0 : return rec->type;
579 : case DateRole:
580 0 : return QDateTime::fromTime_t(static_cast<uint>(rec->time));
581 : case WatchonlyRole:
582 0 : return rec->involvesWatchAddress;
583 : case WatchonlyDecorationRole:
584 0 : return txWatchonlyDecoration(rec);
585 : case LongDescriptionRole:
586 0 : return priv->describe(rec, walletModel->getOptionsModel()->getDisplayUnit());
587 : case AddressRole:
588 0 : return QString::fromStdString(rec->address);
589 : case LabelRole:
590 0 : return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
591 : case AmountRole:
592 0 : return qint64(rec->credit + rec->debit);
593 : case TxIDRole:
594 0 : return rec->getTxID();
595 : case TxHashRole:
596 0 : return QString::fromStdString(rec->hash.ToString());
597 : case ConfirmedRole:
598 0 : return rec->status.countsForBalance;
599 : case FormattedAmountRole:
600 : // Used for copy/export, so don't include separators
601 0 : return formatTxAmount(rec, false, BitcoinUnits::separatorNever);
602 : case StatusRole:
603 0 : return rec->status.status;
604 : }
605 : return QVariant();
606 : }
607 :
608 0 : QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
609 : {
610 0 : if(orientation == Qt::Horizontal)
611 : {
612 0 : if(role == Qt::DisplayRole)
613 : {
614 0 : return columns[section];
615 : }
616 0 : else if (role == Qt::TextAlignmentRole)
617 : {
618 0 : return column_alignments[section];
619 0 : } else if (role == Qt::ToolTipRole)
620 : {
621 0 : switch(section)
622 : {
623 : case Status:
624 0 : return tr("Transaction status. Hover over this field to show number of confirmations.");
625 : case Date:
626 0 : return tr("Date and time that the transaction was received.");
627 : case Type:
628 0 : return tr("Type of transaction.");
629 : case Watchonly:
630 0 : return tr("Whether or not a watch-only address is involved in this transaction.");
631 : case ToAddress:
632 0 : return tr("User-defined intent/purpose of the transaction.");
633 : case Amount:
634 0 : return tr("Amount removed from or added to balance.");
635 : }
636 : }
637 : }
638 : return QVariant();
639 : }
640 :
641 0 : QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
642 : {
643 : Q_UNUSED(parent);
644 0 : TransactionRecord *data = priv->index(row);
645 0 : if(data)
646 : {
647 0 : return createIndex(row, column, priv->index(row));
648 : }
649 : return QModelIndex();
650 : }
651 :
652 0 : void TransactionTableModel::updateDisplayUnit()
653 : {
654 : // emit dataChanged to update Amount column with the current unit
655 0 : updateAmountColumnTitle();
656 0 : Q_EMIT dataChanged(index(0, Amount), index(priv->size()-1, Amount));
657 0 : }
658 :
659 : // queue notifications to show a non freezing progress dialog e.g. for rescan
660 : struct TransactionNotification
661 : {
662 : public:
663 : TransactionNotification() {}
664 0 : TransactionNotification(uint256 hash, ChangeType status, bool showTransaction):
665 0 : hash(hash), status(status), showTransaction(showTransaction) {}
666 :
667 0 : void invoke(QObject *ttm)
668 : {
669 0 : QString strHash = QString::fromStdString(hash.GetHex());
670 0 : qDebug() << "NotifyTransactionChanged: " + strHash + " status= " + QString::number(status);
671 : QMetaObject::invokeMethod(ttm, "updateTransaction", Qt::QueuedConnection,
672 : Q_ARG(QString, strHash),
673 : Q_ARG(int, status),
674 0 : Q_ARG(bool, showTransaction));
675 0 : }
676 : private:
677 : uint256 hash;
678 : ChangeType status;
679 : bool showTransaction;
680 : };
681 :
682 : static bool fQueueNotifications = false;
683 0 : static std::vector< TransactionNotification > vQueueNotifications;
684 :
685 0 : static void NotifyTransactionChanged(TransactionTableModel *ttm, CWallet *wallet, const uint256 &hash, ChangeType status)
686 : {
687 : // Find transaction in wallet
688 0 : std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
689 : // Determine whether to show transaction or not (determine this here so that no relocking is needed in GUI thread)
690 0 : bool inWallet = mi != wallet->mapWallet.end();
691 0 : bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second));
692 :
693 0 : TransactionNotification notification(hash, status, showTransaction);
694 :
695 0 : if (fQueueNotifications)
696 : {
697 0 : vQueueNotifications.push_back(notification);
698 0 : return;
699 : }
700 0 : notification.invoke(ttm);
701 : }
702 :
703 0 : static void ShowProgress(TransactionTableModel *ttm, const std::string &title, int nProgress)
704 : {
705 0 : if (nProgress == 0)
706 0 : fQueueNotifications = true;
707 :
708 0 : if (nProgress == 100)
709 : {
710 0 : fQueueNotifications = false;
711 0 : if (vQueueNotifications.size() > 10) // prevent balloon spam, show maximum 10 balloons
712 0 : QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true));
713 0 : for (unsigned int i = 0; i < vQueueNotifications.size(); ++i)
714 : {
715 0 : if (vQueueNotifications.size() - i <= 10)
716 0 : QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false));
717 :
718 0 : vQueueNotifications[i].invoke(ttm);
719 : }
720 : std::vector<TransactionNotification >().swap(vQueueNotifications); // clear
721 : }
722 0 : }
723 :
724 0 : void TransactionTableModel::subscribeToCoreSignals()
725 : {
726 : // Connect signals to wallet
727 0 : wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
728 0 : wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2));
729 0 : }
730 :
731 0 : void TransactionTableModel::unsubscribeFromCoreSignals()
732 : {
733 : // Disconnect signals from wallet
734 0 : wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
735 0 : wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2));
736 0 : }
|