Feathercoin  0.5.0
P2P Digital Currency
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
transactiontablemodel.cpp
Go to the documentation of this file.
2 
3 #include "guiutil.h"
4 #include "transactionrecord.h"
5 #include "guiconstants.h"
6 #include "transactiondesc.h"
7 #include "walletmodel.h"
8 #include "optionsmodel.h"
9 #include "addresstablemodel.h"
10 #include "bitcoinunits.h"
11 
12 #include "wallet.h"
13 #include "ui_interface.h"
14 
15 #include <QList>
16 #include <QColor>
17 #include <QTimer>
18 #include <QIcon>
19 #include <QDateTime>
20 
21 // Amount column is right-aligned it contains numbers
22 static int column_alignments[] = {
23  Qt::AlignLeft|Qt::AlignVCenter,
24  Qt::AlignLeft|Qt::AlignVCenter,
25  Qt::AlignLeft|Qt::AlignVCenter,
26  Qt::AlignLeft|Qt::AlignVCenter,
27  Qt::AlignRight|Qt::AlignVCenter
28  };
29 
30 // Comparison operator for sort/binary search of model tx list
31 struct TxLessThan
32 {
33  bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
34  {
35  return a.hash < b.hash;
36  }
37  bool operator()(const TransactionRecord &a, const uint256 &b) const
38  {
39  return a.hash < b;
40  }
41  bool operator()(const uint256 &a, const TransactionRecord &b) const
42  {
43  return a < b.hash;
44  }
45 };
46 
47 // Private implementation
49 {
50 public:
52  wallet(wallet),
53  parent(parent)
54  {
55  }
58 
59  /* Local cache of wallet.
60  * As it is in the same order as the CWallet, by definition
61  * this is sorted by sha256.
62  */
63  QList<TransactionRecord> cachedWallet;
64 
65  /* Query entire wallet anew from core.
66  */
68  {
69  OutputDebugStringF("refreshWallet\n");
70  cachedWallet.clear();
71  {
72  LOCK(wallet->cs_wallet);
73  for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
74  {
76  cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
77  }
78  }
79  }
80 
81  /* Update our model of the wallet incrementally, to synchronize our model of the wallet
82  with that of the core.
83 
84  Call with transaction that was added, removed or changed.
85  */
86  void updateWallet(const uint256 &hash, int status)
87  {
88  OutputDebugStringF("updateWallet %s %i\n", hash.ToString().c_str(), status);
89  {
90  LOCK(wallet->cs_wallet);
91 
92  // Find transaction in wallet
93  std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
94  bool inWallet = mi != wallet->mapWallet.end();
95 
96  // Find bounds of this transaction in model
97  QList<TransactionRecord>::iterator lower = qLowerBound(
98  cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
99  QList<TransactionRecord>::iterator upper = qUpperBound(
100  cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
101  int lowerIndex = (lower - cachedWallet.begin());
102  int upperIndex = (upper - cachedWallet.begin());
103  bool inModel = (lower != upper);
104 
105  // Determine whether to show transaction or not
106  bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second));
107 
108  if(status == CT_UPDATED)
109  {
110  if(showTransaction && !inModel)
111  status = CT_NEW; /* Not in model, but want to show, treat as new */
112  if(!showTransaction && inModel)
113  status = CT_DELETED; /* In model, but want to hide, treat as deleted */
114  }
115 
116  OutputDebugStringF(" inWallet=%i inModel=%i Index=%i-%i showTransaction=%i derivedStatus=%i\n",
117  inWallet, inModel, lowerIndex, upperIndex, showTransaction, status);
118 
119  switch(status)
120  {
121  case CT_NEW:
122  if(inModel)
123  {
124  OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is already in model\n");
125  break;
126  }
127  if(!inWallet)
128  {
129  OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is not in wallet\n");
130  break;
131  }
132  if(showTransaction)
133  {
134  // Added -- insert at the right position
135  QList<TransactionRecord> toInsert =
136  TransactionRecord::decomposeTransaction(wallet, mi->second);
137  if(!toInsert.isEmpty()) /* only if something to insert */
138  {
139  parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
140  int insert_idx = lowerIndex;
141  foreach(const TransactionRecord &rec, toInsert)
142  {
143  cachedWallet.insert(insert_idx, rec);
144  insert_idx += 1;
145  }
146  parent->endInsertRows();
147  }
148  }
149  break;
150  case CT_DELETED:
151  if(!inModel)
152  {
153  OutputDebugStringF("Warning: updateWallet: Got CT_DELETED, but transaction is not in model\n");
154  break;
155  }
156  // Removed -- remove entire transaction from table
157  parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
158  cachedWallet.erase(lower, upper);
159  parent->endRemoveRows();
160  break;
161  case CT_UPDATED:
162  // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
163  // visible transactions.
164  break;
165  }
166  }
167  }
168 
169  int size()
170  {
171  return cachedWallet.size();
172  }
173 
175  {
176  if(idx >= 0 && idx < cachedWallet.size())
177  {
178  TransactionRecord *rec = &cachedWallet[idx];
179 
180  // If a status update is needed (blocks came in since last check),
181  // update the status of this transaction from the wallet. Otherwise,
182  // simply re-use the cached status.
183  if(rec->statusUpdateNeeded())
184  {
185  {
186  LOCK(wallet->cs_wallet);
187  std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
188 
189  if(mi != wallet->mapWallet.end())
190  {
191  rec->updateStatus(mi->second);
192  }
193  }
194  }
195  return rec;
196  }
197  else
198  {
199  return 0;
200  }
201  }
202 
204  {
205  {
206  LOCK(wallet->cs_wallet);
207  std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
208  if(mi != wallet->mapWallet.end())
209  {
210  return TransactionDesc::toHTML(wallet, mi->second);
211  }
212  }
213  return QString("");
214  }
215 
216 };
217 
219  QAbstractTableModel(parent),
220  wallet(wallet),
221  walletModel(parent),
222  priv(new TransactionTablePriv(wallet, this)),
223  cachedNumBlocks(0)
224 {
225  columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
226 
227  priv->refreshWallet();
228 
229  QTimer *timer = new QTimer(this);
230  connect(timer, SIGNAL(timeout()), this, SLOT(updateConfirmations()));
231  timer->start(MODEL_UPDATE_DELAY);
232 
233  connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
234 }
235 
237 {
238  delete priv;
239 }
240 
241 void TransactionTableModel::updateTransaction(const QString &hash, int status)
242 {
243  uint256 updated;
244  updated.SetHex(hash.toStdString());
245 
246  priv->updateWallet(updated, status);
247 }
248 
250 {
252  {
254  // Blocks came in since last poll.
255  // Invalidate status (number of confirmations) and (possibly) description
256  // for all rows. Qt is smart enough to only actually request the data for the
257  // visible rows.
258  emit dataChanged(index(0, Status), index(priv->size()-1, Status));
259  emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
260  }
261 }
262 
263 int TransactionTableModel::rowCount(const QModelIndex &parent) const
264 {
265  Q_UNUSED(parent);
266  return priv->size();
267 }
268 
269 int TransactionTableModel::columnCount(const QModelIndex &parent) const
270 {
271  Q_UNUSED(parent);
272  return columns.length();
273 }
274 
276 {
277  QString status;
278 
279  switch(wtx->status.status)
280  {
282  status = tr("Open for %n more block(s)","",wtx->status.open_for);
283  break;
285  status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
286  break;
288  status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
289  break;
291  status = tr("Unconfirmed (%1 of %2 confirmations)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
292  break;
294  status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
295  break;
296  }
298  {
299  switch(wtx->status.maturity)
300  {
302  status += "\n" + tr("Mined balance will be available when it matures in %n more block(s)", "", wtx->status.matures_in);
303  break;
305  break;
307  status += "\n" + tr("This block was not received by any other nodes and will probably not be accepted!");
308  break;
310  status += "\n" + tr("Generated but not accepted");
311  break;
312  }
313  }
314 
315  return status;
316 }
317 
319 {
320  if(wtx->time)
321  {
322  return GUIUtil::dateTimeStr(wtx->time);
323  }
324  else
325  {
326  return QString();
327  }
328 }
329 
330 /* Look up address in address book, if found return label (address)
331  otherwise just return (address)
332  */
333 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
334 {
335  QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
336  QString description;
337  if(!label.isEmpty())
338  {
339  description += label + QString(" ");
340  }
341  if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
342  {
343  description += QString("(") + QString::fromStdString(address) + QString(")");
344  }
345  return description;
346 }
347 
349 {
350  switch(wtx->type)
351  {
353  return tr("Received with");
355  return tr("Received from");
358  return tr("Sent to");
360  return tr("Payment to yourself");
362  return tr("Mined");
363  default:
364  return QString();
365  }
366 }
367 
369 {
370  switch(wtx->type)
371  {
373  return QIcon(":/icons/tx_mined");
376  return QIcon(":/icons/tx_input");
379  return QIcon(":/icons/tx_output");
380  default:
381  return QIcon(":/icons/tx_inout");
382  }
383  return QVariant();
384 }
385 
386 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
387 {
388  switch(wtx->type)
389  {
391  return QString::fromStdString(wtx->address);
395  return lookupAddress(wtx->address, tooltip);
397  return QString::fromStdString(wtx->address);
399  default:
400  return tr("(n/a)");
401  }
402 }
403 
405 {
406  // Show addresses without label in a less visible color
407  switch(wtx->type)
408  {
412  {
413  QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
414  if(label.isEmpty())
415  return COLOR_BAREADDRESS;
416  } break;
418  return COLOR_BAREADDRESS;
419  default:
420  break;
421  }
422  return QVariant();
423 }
424 
425 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
426 {
428  if(showUnconfirmed)
429  {
431  {
432  str = QString("[") + str + QString("]");
433  }
434  }
435  return QString(str);
436 }
437 
439 {
441  {
442  switch(wtx->status.maturity)
443  {
445  int total = wtx->status.depth + wtx->status.matures_in;
446  int part = (wtx->status.depth * 4 / total) + 1;
447  return QIcon(QString(":/icons/transaction_%1").arg(part));
448  }
450  return QIcon(":/icons/transaction_confirmed");
453  return QIcon(":/icons/transaction_0");
454  }
455  }
456  else
457  {
458  switch(wtx->status.status)
459  {
462  return QColor(64,64,255);
463  break;
465  return QColor(192,192,192);
467  switch(wtx->status.depth)
468  {
469  case 0: return QIcon(":/icons/transaction_0");
470  case 1: return QIcon(":/icons/transaction_1");
471  case 2: return QIcon(":/icons/transaction_2");
472  case 3: return QIcon(":/icons/transaction_3");
473  case 4: return QIcon(":/icons/transaction_4");
474  default: return QIcon(":/icons/transaction_5");
475  };
477  return QIcon(":/icons/transaction_confirmed");
478  }
479  }
480  return QColor(0,0,0);
481 }
482 
484 {
485  QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
488  {
489  tooltip += QString(" ") + formatTxToAddress(rec, true);
490  }
491  return tooltip;
492 }
493 
494 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
495 {
496  if(!index.isValid())
497  return QVariant();
498  TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
499 
500  switch(role)
501  {
502  case Qt::DecorationRole:
503  switch(index.column())
504  {
505  case Status:
506  return txStatusDecoration(rec);
507  case ToAddress:
508  return txAddressDecoration(rec);
509  }
510  break;
511  case Qt::DisplayRole:
512  switch(index.column())
513  {
514  case Date:
515  return formatTxDate(rec);
516  case Type:
517  return formatTxType(rec);
518  case ToAddress:
519  return formatTxToAddress(rec, false);
520  case Amount:
521  return formatTxAmount(rec);
522  }
523  break;
524  case Qt::EditRole:
525  // Edit role is used for sorting, so return the unformatted values
526  switch(index.column())
527  {
528  case Status:
529  return QString::fromStdString(rec->status.sortKey);
530  case Date:
531  return rec->time;
532  case Type:
533  return formatTxType(rec);
534  case ToAddress:
535  return formatTxToAddress(rec, true);
536  case Amount:
537  return rec->credit + rec->debit;
538  }
539  break;
540  case Qt::ToolTipRole:
541  return formatTooltip(rec);
542  case Qt::TextAlignmentRole:
543  return column_alignments[index.column()];
544  case Qt::ForegroundRole:
545  // Non-confirmed transactions are grey
546  if(!rec->status.confirmed)
547  {
548  return COLOR_UNCONFIRMED;
549  }
550  if(index.column() == Amount && (rec->credit+rec->debit) < 0)
551  {
552  return COLOR_NEGATIVE;
553  }
554  if(index.column() == ToAddress)
555  {
556  return addressColor(rec);
557  }
558  break;
559  case TypeRole:
560  return rec->type;
561  case DateRole:
562  return QDateTime::fromTime_t(static_cast<uint>(rec->time));
563  case LongDescriptionRole:
564  return priv->describe(rec);
565  case AddressRole:
566  return QString::fromStdString(rec->address);
567  case LabelRole:
568  return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
569  case AmountRole:
570  return rec->credit + rec->debit;
571  case TxIDRole:
572  return QString::fromStdString(rec->getTxID());
573  case ConfirmedRole:
574  // Return True if transaction counts for balance
575  return rec->status.confirmed && !(rec->type == TransactionRecord::Generated &&
577  case FormattedAmountRole:
578  return formatTxAmount(rec, false);
579  }
580  return QVariant();
581 }
582 
583 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
584 {
585  if(orientation == Qt::Horizontal)
586  {
587  if(role == Qt::DisplayRole)
588  {
589  return columns[section];
590  }
591  else if (role == Qt::TextAlignmentRole)
592  {
593  return column_alignments[section];
594  } else if (role == Qt::ToolTipRole)
595  {
596  switch(section)
597  {
598  case Status:
599  return tr("Transaction status. Hover over this field to show number of confirmations.");
600  case Date:
601  return tr("Date and time that the transaction was received.");
602  case Type:
603  return tr("Type of transaction.");
604  case ToAddress:
605  return tr("Destination address of transaction.");
606  case Amount:
607  return tr("Amount removed from or added to balance.");
608  }
609  }
610  }
611  return QVariant();
612 }
613 
614 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
615 {
616  Q_UNUSED(parent);
617  TransactionRecord *data = priv->index(row);
618  if(data)
619  {
620  return createIndex(row, column, priv->index(row));
621  }
622  else
623  {
624  return QModelIndex();
625  }
626 }
627 
629 {
630  // emit dataChanged to update Amount column with the current unit
631  emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));
632 }
QVariant addressColor(const TransactionRecord *wtx) const
int columnCount(const QModelIndex &parent) const
void SetHex(const char *psz)
Definition: uint256.h:306
QVariant data(const QModelIndex &index, int role) const
static QString toHTML(CWallet *wallet, CWalletTx &wtx)
QVariant txStatusDecoration(const TransactionRecord *wtx) const
TransactionTableModel(CWallet *wallet, WalletModel *parent=0)
bool operator()(const uint256 &a, const TransactionRecord &b) const
CCriticalSection cs_wallet
Definition: wallet.h:83
void updateWallet(const uint256 &hash, int status)
QString formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
std::string getTxID()
Return the unique identifier for this transaction (part)
QString dateTimeStr(const QDateTime &date)
Definition: guiutil.cpp:51
QVariant txAddressDecoration(const TransactionRecord *wtx) const
TransactionRecord * index(int idx)
QString lookupAddress(const std::string &address, bool tooltip) const
int total
Definition: db_bench.cc:272
void updateTransaction(const QString &hash, int status)
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
QString describe(TransactionRecord *rec)
QList< TransactionRecord > cachedWallet
UI model for a transaction.
TransactionStatus status
Status: can change with block chain update.
static QList< TransactionRecord > decomposeTransaction(const CWallet *wallet, const CWalletTx &wtx)
#define LOCK(cs)
Definition: sync.h:108
QString formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed=true) const
bool operator()(const TransactionRecord &a, const uint256 &b) const
void updateStatus(const CWalletTx &wtx)
Update status from core wallet tx.
Date and time this transaction was created.
static const int NumConfirmations
Number of confirmation needed for transaction.
int getDisplayUnit()
Definition: optionsmodel.h:50
UI model for the transaction table of a wallet.
#define COLOR_UNCONFIRMED
Definition: guiconstants.h:17
bool getDisplayAddresses()
Definition: optionsmodel.h:51
int OutputDebugStringF(const char *pszFormat,...)
Definition: util.cpp:237
QString formatTxType(const TransactionRecord *wtx) const
QString formatTooltip(const TransactionRecord *rec) const
Transaction will likely not mature because no nodes have confirmed.
int64 open_for
Timestamp if status==OpenUntilDate, otherwise number of additional blocks that need to be mined befor...
256-bit unsigned integer
Definition: uint256.h:537
bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
QString labelForAddress(const QString &address) const
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:36
std::string ToString() const
Definition: uint256.h:343
int rowCount(const QModelIndex &parent) const
A CWallet is an extension of a keystore, which also maintains a set of transactions and balances...
Definition: wallet.h:69
Label of address related to transaction.
bool statusUpdateNeeded()
Return whether a status update is needed.
std::map< uint256, CWalletTx > mapWallet
Definition: wallet.h:115
TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent)
int nBestHeight
Definition: main.cpp:41
Formatted amount, without brackets when unconfirmed.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const
#define COLOR_BAREADDRESS
Definition: guiconstants.h:21
uint32_t hash
Definition: cache.cc:34
static QString format(int unit, qint64 amount, bool plussign=false)
Format as string.
#define COLOR_NEGATIVE
Definition: guiconstants.h:19
QString formatTxDate(const TransactionRecord *wtx) const
void * arg
Definition: env_posix.cc:716
OptionsModel * getOptionsModel()