Anoncoin  0.9.4
P2P Digital Currency
transactiontablemodel.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2014 The Bitcoin developers
2 // Copyright (c) 2013-2014 The Anoncoin Core developers
3 // Distributed under the MIT/X11 software license, see the accompanying
4 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 
7 
8 #include "addresstablemodel.h"
9 #include "anoncoinunits.h"
10 #include "guiconstants.h"
11 #include "guiutil.h"
12 #include "optionsmodel.h"
13 #include "transactiondesc.h"
14 #include "transactionrecord.h"
15 #include "walletmodel.h"
16 
17 #include "main.h"
18 #include "sync.h"
19 #include "uint256.h"
20 #include "util.h"
21 #include "wallet.h"
22 
23 #include <QColor>
24 #include <QDateTime>
25 #include <QDebug>
26 #include <QIcon>
27 #include <QList>
28 
29 // Amount column is right-aligned it contains numbers
30 static int column_alignments[] = {
31  Qt::AlignLeft|Qt::AlignVCenter, /* status */
32  Qt::AlignLeft|Qt::AlignVCenter, /* watchonly */
33  Qt::AlignLeft|Qt::AlignVCenter, /* date */
34  Qt::AlignLeft|Qt::AlignVCenter, /* type */
35  Qt::AlignLeft|Qt::AlignVCenter, /* address */
36  Qt::AlignRight|Qt::AlignVCenter /* amount */
37  };
38 
39 // Comparison operator for sort/binary search of model tx list
40 struct TxLessThan
41 {
42  bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
43  {
44  return a.hash < b.hash;
45  }
46  bool operator()(const TransactionRecord &a, const uint256 &b) const
47  {
48  return a.hash < b;
49  }
50  bool operator()(const uint256 &a, const TransactionRecord &b) const
51  {
52  return a < b.hash;
53  }
54 };
55 
56 // Private implementation
58 {
59 public:
61  wallet(wallet),
62  parent(parent)
63  {
64  }
65 
68 
69  /* Local cache of wallet.
70  * As it is in the same order as the CWallet, by definition
71  * this is sorted by sha256.
72  */
73  QList<TransactionRecord> cachedWallet;
74 
75  /* Query entire wallet anew from core.
76  */
78  {
79  qDebug() << "TransactionTablePriv::refreshWallet";
80  cachedWallet.clear();
81  {
82  LOCK2(cs_main, wallet->cs_wallet);
83  for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
84  {
86  cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
87  }
88  }
89  }
90 
91  /* Update our model of the wallet incrementally, to synchronize our model of the wallet
92  with that of the core.
93 
94  Call with transaction that was added, removed or changed.
95  */
96  void updateWallet(const uint256 &hash, int status)
97  {
98  qDebug() << "TransactionTablePriv::updateWallet : " + QString::fromStdString(hash.ToString()) + " " + QString::number(status);
99  {
100  LOCK2(cs_main, wallet->cs_wallet);
101 
102  // Find transaction in wallet
103  std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
104  bool inWallet = mi != wallet->mapWallet.end();
105 
106  // Find bounds of this transaction in model
107  QList<TransactionRecord>::iterator lower = qLowerBound(
108  cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
109  QList<TransactionRecord>::iterator upper = qUpperBound(
110  cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
111  int lowerIndex = (lower - cachedWallet.begin());
112  int upperIndex = (upper - cachedWallet.begin());
113  bool inModel = (lower != upper);
114 
115  // Determine whether to show transaction or not
116  bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second));
117 
118  if(status == CT_UPDATED)
119  {
120  if(showTransaction && !inModel)
121  status = CT_NEW; /* Not in model, but want to show, treat as new */
122  if(!showTransaction && inModel)
123  status = CT_DELETED; /* In model, but want to hide, treat as deleted */
124  }
125 
126  qDebug() << " inWallet=" + QString::number(inWallet) + " inModel=" + QString::number(inModel) +
127  " Index=" + QString::number(lowerIndex) + "-" + QString::number(upperIndex) +
128  " showTransaction=" + QString::number(showTransaction) + " derivedStatus=" + QString::number(status);
129 
130  switch(status)
131  {
132  case CT_NEW:
133  if(inModel)
134  {
135  qDebug() << "TransactionTablePriv::updateWallet : Warning: Got CT_NEW, but transaction is already in model";
136  break;
137  }
138  if(!inWallet)
139  {
140  qDebug() << "TransactionTablePriv::updateWallet : Warning: Got CT_NEW, but transaction is not in wallet";
141  break;
142  }
143  if(showTransaction)
144  {
145  // Added -- insert at the right position
146  QList<TransactionRecord> toInsert =
147  TransactionRecord::decomposeTransaction(wallet, mi->second);
148  if(!toInsert.isEmpty()) /* only if something to insert */
149  {
150  parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
151  int insert_idx = lowerIndex;
152  foreach(const TransactionRecord &rec, toInsert)
153  {
154  cachedWallet.insert(insert_idx, rec);
155  insert_idx += 1;
156  }
157  parent->endInsertRows();
158  }
159  }
160  break;
161  case CT_DELETED:
162  if(!inModel)
163  {
164  qDebug() << "TransactionTablePriv::updateWallet : Warning: Got CT_DELETED, but transaction is not in model";
165  break;
166  }
167  // Removed -- remove entire transaction from table
168  parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
169  cachedWallet.erase(lower, upper);
170  parent->endRemoveRows();
171  break;
172  case CT_UPDATED:
173  // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
174  // visible transactions.
175  break;
176  }
177  }
178  }
179 
180  int size()
181  {
182  return cachedWallet.size();
183  }
184 
186  {
187  if(idx >= 0 && idx < cachedWallet.size())
188  {
189  TransactionRecord *rec = &cachedWallet[idx];
190 
191  // Get required locks upfront. This avoids the GUI from getting
192  // stuck if the core is holding the locks for a longer time - for
193  // example, during a wallet rescan.
194  //
195  // If a status update is needed (blocks came in since last check),
196  // update the status of this transaction from the wallet. Otherwise,
197  // simply re-use the cached status.
198  TRY_LOCK(cs_main, lockMain);
199  if(lockMain)
200  {
201  TRY_LOCK(wallet->cs_wallet, lockWallet);
202  if(lockWallet && rec->statusUpdateNeeded())
203  {
204  std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
205 
206  if(mi != wallet->mapWallet.end())
207  {
208  rec->updateStatus(mi->second);
209  }
210  }
211  }
212  return rec;
213  }
214  return 0;
215  }
216 
217  QString describe(TransactionRecord *rec, int unit)
218  {
219  {
220  LOCK2(cs_main, wallet->cs_wallet);
221  std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
222  if(mi != wallet->mapWallet.end())
223  {
224  return TransactionDesc::toHTML(wallet, mi->second, rec, unit);
225  }
226  }
227  return QString();
228  }
229 };
230 
232  QAbstractTableModel(parent),
233  wallet(wallet),
234  walletModel(parent),
235  priv(new TransactionTablePriv(wallet, this))
236 {
237  columns << QString() << QString() << tr("Date") << tr("Type") << tr("Address") << AnoncoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
238  priv->refreshWallet();
239 
240  connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
241 }
242 
244 {
245  delete priv;
246 }
247 
250 {
252  emit headerDataChanged(Qt::Horizontal,Amount,Amount);
253 }
254 
255 void TransactionTableModel::updateTransaction(const QString &hash, int status)
256 {
257  uint256 updated;
258  updated.SetHex(hash.toStdString());
259 
260  priv->updateWallet(updated, status);
261 }
262 
264 {
265  // Blocks came in since last poll.
266  // Invalidate status (number of confirmations) and (possibly) description
267  // for all rows. Qt is smart enough to only actually request the data for the
268  // visible rows.
269  emit dataChanged(index(0, Status), index(priv->size()-1, Status));
270  emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
271 }
272 
273 int TransactionTableModel::rowCount(const QModelIndex &parent) const
274 {
275  Q_UNUSED(parent);
276  return priv->size();
277 }
278 
279 int TransactionTableModel::columnCount(const QModelIndex &parent) const
280 {
281  Q_UNUSED(parent);
282  return columns.length();
283 }
284 
286 {
287  QString status;
288 
289  switch(wtx->status.status)
290  {
292  status = tr("Open for %n more block(s)","",wtx->status.open_for);
293  break;
295  status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
296  break;
298  status = tr("Offline");
299  break;
301  status = tr("Unconfirmed");
302  break;
304  status = tr("Confirming (%1 of %2 recommended confirmations)").arg(wtx->status.depth).arg(TransactionRecord::RecommendedNumConfirmations);
305  break;
307  status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
308  break;
310  status = tr("Conflicted");
311  break;
313  status = tr("Immature (%1 confirmations, will be available after %2)").arg(wtx->status.depth).arg(wtx->status.depth + wtx->status.matures_in);
314  break;
316  status = tr("This block was not received by any other nodes and will probably not be accepted!");
317  break;
319  status = tr("Generated but not accepted");
320  break;
321  }
322 
323  return status;
324 }
325 
327 {
328  if(wtx->time)
329  {
330  return GUIUtil::dateTimeStr(wtx->time);
331  }
332  return QString();
333 }
334 
335 /* Look up address in address book, if found return label (address)
336  otherwise just return (address)
337  */
338 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
339 {
340  QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
341  QString description;
342  if(!label.isEmpty())
343  {
344  description += label;
345  }
346  if(label.isEmpty() || tooltip)
347  {
348  description += QString(" (") + QString::fromStdString(address) + QString(")");
349  }
350  return description;
351 }
352 
354 {
355  switch(wtx->type)
356  {
358  return tr("Received with");
360  return tr("Received from");
363  return tr("Sent to");
365  return tr("Payment to yourself");
367  return tr("Mined");
368  default:
369  return QString();
370  }
371 }
372 
374 {
375  switch(wtx->type)
376  {
378  return QIcon(":/icons/tx_mined");
381  return QIcon(":/icons/tx_input");
384  return QIcon(":/icons/tx_output");
385  default:
386  return QIcon(":/icons/tx_inout");
387  }
388 }
389 
390 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
391 {
392  QString watchAddress;
393  if (tooltip) {
394  // Mark transactions involving watch-only addresses by adding " (watch-only)"
395  watchAddress = wtx->involvesWatchAddress ? QString(" (") + tr("watch-only") + QString(")") : "";
396  }
397 
398  switch(wtx->type)
399  {
401  return QString::fromStdString(wtx->address) + watchAddress;
405  return lookupAddress(wtx->address, tooltip) + watchAddress;
407  return QString::fromStdString(wtx->address) + watchAddress;
409  default:
410  return tr("(n/a)") + watchAddress;
411  }
412 }
413 
415 {
416  // Show addresses without label in a less visible color
417  switch(wtx->type)
418  {
422  {
423  QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
424  if(label.isEmpty())
425  return COLOR_BAREADDRESS;
426  } break;
428  return COLOR_BAREADDRESS;
429  default:
430  break;
431  }
432  return QVariant();
433 }
434 
435 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
436 {
438  if(showUnconfirmed)
439  {
440  if(!wtx->status.countsForBalance)
441  {
442  str = QString("[") + str + QString("]");
443  }
444  }
445  return QString(str);
446 }
447 
449 {
450  switch(wtx->status.status)
451  {
458  return QIcon(":/icons/transaction_0");
460  switch(wtx->status.depth)
461  {
462  case 1: return QIcon(":/icons/transaction_1");
463  case 2: return QIcon(":/icons/transaction_2");
464  case 3: return QIcon(":/icons/transaction_3");
465  case 4: return QIcon(":/icons/transaction_4");
466  default: return QIcon(":/icons/transaction_5");
467  };
469  return QIcon(":/icons/transaction_confirmed");
471  return QIcon(":/icons/transaction_conflicted");
473  int total = wtx->status.depth + wtx->status.matures_in;
474  int part = (wtx->status.depth * 4 / total) + 1;
475  return QIcon(QString(":/icons/transaction_%1").arg(part));
476  }
479  return QIcon(":/icons/transaction_0");
480  default:
481  return COLOR_BLACK;
482  }
483 }
484 
486 {
487  if (wtx->involvesWatchAddress)
488  return QIcon(":/icons/eye");
489  else
490  return QVariant();
491 }
492 
494 {
495  QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
498  {
499  tooltip += QString(" ") + formatTxToAddress(rec, true);
500  }
501  return tooltip;
502 }
503 
504 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
505 {
506  if(!index.isValid())
507  return QVariant();
508  TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
509 
510  switch(role)
511  {
512  case Qt::DecorationRole:
513  switch(index.column())
514  {
515  case Status:
516  return txStatusDecoration(rec);
517  case Watchonly:
518  return txWatchonlyDecoration(rec);
519  case ToAddress:
520  return txAddressDecoration(rec);
521  }
522  break;
523  case Qt::DisplayRole:
524  switch(index.column())
525  {
526  case Date:
527  return formatTxDate(rec);
528  case Type:
529  return formatTxType(rec);
530  case ToAddress:
531  return formatTxToAddress(rec, false);
532  case Amount:
533  return formatTxAmount(rec);
534  }
535  break;
536  case Qt::EditRole:
537  // Edit role is used for sorting, so return the unformatted values
538  switch(index.column())
539  {
540  case Status:
541  return QString::fromStdString(rec->status.sortKey);
542  case Date:
543  return rec->time;
544  case Type:
545  return formatTxType(rec);
546  case Watchonly:
547  return (rec->involvesWatchAddress ? 1 : 0);
548  case ToAddress:
549  return formatTxToAddress(rec, true);
550  case Amount:
551  return rec->credit + rec->debit;
552  }
553  break;
554  case Qt::ToolTipRole:
555  return formatTooltip(rec);
556  case Qt::TextAlignmentRole:
557  return column_alignments[index.column()];
558  case Qt::ForegroundRole:
559  // Non-confirmed (but not immature) as transactions are grey
561  {
562  return COLOR_UNCONFIRMED;
563  }
564  if(index.column() == Amount && (rec->credit+rec->debit) < 0)
565  {
566  return COLOR_NEGATIVE;
567  }
568  if(index.column() == ToAddress)
569  {
570  return addressColor(rec);
571  }
572  break;
573  case TypeRole:
574  return rec->type;
575  case DateRole:
576  return QDateTime::fromTime_t(static_cast<uint>(rec->time));
577  case WatchonlyRole:
578  return rec->involvesWatchAddress;
580  return txWatchonlyDecoration(rec);
581  case LongDescriptionRole:
583  case AddressRole:
584  return QString::fromStdString(rec->address);
585  case LabelRole:
586  return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
587  case AmountRole:
588  return rec->credit + rec->debit;
589  case TxIDRole:
590  return rec->getTxID();
591  case TxHashRole:
592  return QString::fromStdString(rec->hash.ToString());
593  case ConfirmedRole:
594  return rec->status.countsForBalance;
595  case FormattedAmountRole:
596  return formatTxAmount(rec, false);
597  case StatusRole:
598  return rec->status.status;
599  }
600  return QVariant();
601 }
602 
603 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
604 {
605  if(orientation == Qt::Horizontal)
606  {
607  if(role == Qt::DisplayRole)
608  {
609  return columns[section];
610  }
611  else if (role == Qt::TextAlignmentRole)
612  {
613  return column_alignments[section];
614  } else if (role == Qt::ToolTipRole)
615  {
616  switch(section)
617  {
618  case Status:
619  return tr("Transaction status. Hover over this field to show number of confirmations.");
620  case Date:
621  return tr("Date and time that the transaction was received.");
622  case Type:
623  return tr("Type of transaction.");
624  case Watchonly:
625  return tr("Whether or not a watch-only address is involved in this transaction.");
626  case ToAddress:
627  return tr("Destination address of transaction.");
628  case Amount:
629  return tr("Amount removed from or added to balance.");
630  }
631  }
632  }
633  return QVariant();
634 }
635 
636 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
637 {
638  Q_UNUSED(parent);
639  TransactionRecord *data = priv->index(row);
640  if(data)
641  {
642  return createIndex(row, column, priv->index(row));
643  }
644  return QModelIndex();
645 }
646 
648 {
649  // emit dataChanged to update Amount column with the current unit
651  emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));
652 }
QVariant addressColor(const TransactionRecord *wtx) const
void updateAmountColumnTitle()
Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table hea...
int columnCount(const QModelIndex &parent) const
void SetHex(const char *psz)
Definition: uint256.h:306
QVariant data(const QModelIndex &index, int role) const
QVariant txStatusDecoration(const TransactionRecord *wtx) const
QString describe(TransactionRecord *rec, int unit)
TransactionTableModel(CWallet *wallet, WalletModel *parent=0)
Confirmed, but waiting for the recommended number of confirmations.
bool operator()(const uint256 &a, const TransactionRecord &b) const
Transaction not yet final, waiting for block.
Transaction status (TransactionRecord::Status)
#define TRY_LOCK(cs, name)
Definition: sync.h:159
QString getTxID() const
Return the unique identifier for this transaction (part)
Not sent to any other nodes.
CCriticalSection cs_wallet
Main wallet lock.
Definition: wallet.h:133
Generated (mined) transactions.
void updateWallet(const uint256 &hash, int status)
QString formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
Have 6 or more confirmations (normal tx) or fully mature (mined tx)
std::string sortKey
Sorting key based on status.
QString dateTimeStr(const QDateTime &date)
Definition: guiutil.cpp:75
QVariant txAddressDecoration(const TransactionRecord *wtx) const
CCriticalSection cs_main
Definition: main.cpp:38
TransactionRecord * index(int idx)
Mined but not accepted.
Not yet mined into a block.
QString lookupAddress(const std::string &address, bool tooltip) const
void updateTransaction(const QString &hash, int status)
#define COLOR_TX_STATUS_OFFLINE
Definition: guiconstants.h:30
static bool showTransaction(const CWalletTx &wtx)
Decompose CWallet transaction to model transaction records.
AddressTableModel * getAddressTableModel()
TransactionTableModel * parent
QVariant headerData(int section, Qt::Orientation orientation, int role) const
TransactionTablePriv * priv
QString formatTxStatus(const TransactionRecord *wtx) const
QList< TransactionRecord > cachedWallet
#define LOCK2(cs1, cs2)
Definition: sync.h:158
static QString getAmountColumnTitle(int unit)
Gets title for amount column including current display unit if optionsModel reference available */...
UI model for a transaction.
TransactionStatus status
Status: can change with block chain update.
static QString format(int unit, qint64 amount, bool plussign=false)
Format as string.
static QList< TransactionRecord > decomposeTransaction(const CWallet *wallet, const CWalletTx &wtx)
QString formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed=true) const
bool operator()(const TransactionRecord &a, const uint256 &b) const
bool countsForBalance
Transaction counts towards available balance.
void updateStatus(const CWalletTx &wtx)
Update status from core wallet tx.
Date and time this transaction was created.
#define COLOR_BLACK
Definition: guiconstants.h:32
int getDisplayUnit()
Definition: optionsmodel.h:87
UI model for the transaction table of a wallet.
#define COLOR_UNCONFIRMED
Definition: guiconstants.h:22
Normal (sent/received) transactions.
QString formatTxType(const TransactionRecord *wtx) const
QString formatTooltip(const TransactionRecord *rec) const
QVariant txWatchonlyDecoration(const TransactionRecord *wtx) const
256-bit unsigned integer
Definition: uint256.h:532
bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
QString labelForAddress(const QString &address) const
Conflicts with other transaction or mempool.
Interface to Anoncoin wallet from Qt view code.
Definition: walletmodel.h:97
std::string ToString() const
Definition: uint256.h:341
int rowCount(const QModelIndex &parent) const
bool involvesWatchAddress
Whether the transaction was sent/received with a watch-only address.
A CWallet is an extension of a keystore, which also maintains a set of transactions and balances...
Definition: wallet.h:101
Label of address related to transaction.
bool statusUpdateNeeded()
Return whether a status update is needed.
std::map< uint256, CWalletTx > mapWallet
Definition: wallet.h:169
TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent)
#define COLOR_TX_STATUS_OPENUNTILDATE
Definition: guiconstants.h:28
qint64 open_for
Timestamp if status==OpenUntilDate, otherwise number of additional blocks that need to be mined befor...
Formatted amount, without brackets when unconfirmed.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const
static QString toHTML(CWallet *wallet, CWalletTx &wtx, TransactionRecord *rec, int unit)
#define COLOR_BAREADDRESS
Definition: guiconstants.h:26
#define COLOR_NEGATIVE
Definition: guiconstants.h:24
QString formatTxDate(const TransactionRecord *wtx) const
static const int RecommendedNumConfirmations
Number of confirmation recommended for accepting a transaction.
Transaction will likely not mature because no nodes have confirmed.
OptionsModel * getOptionsModel()