48c03d3a64e6c61048614ee54120ad8389bc0832
[novacoin.git] / src / qt / transactiontablemodel.cpp
1 #include "transactiontablemodel.h"
2 #include "guiutil.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"
10
11 #include "wallet.h"
12 #include "ui_interface.h"
13
14 #include <QLocale>
15 #include <QList>
16 #include <QColor>
17 #include <QTimer>
18 #include <QIcon>
19 #include <QDateTime>
20 #include <QtAlgorithms>
21
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
29     };
30
31 // Comparison operator for sort/binary search of model tx list
32 struct TxLessThan
33 {
34     bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
35     {
36         return a.hash < b.hash;
37     }
38     bool operator()(const TransactionRecord &a, const uint256 &b) const
39     {
40         return a.hash < b;
41     }
42     bool operator()(const uint256 &a, const TransactionRecord &b) const
43     {
44         return a < b.hash;
45     }
46 };
47
48 // Private implementation
49 class TransactionTablePriv
50 {
51 public:
52     TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
53             wallet(wallet),
54             parent(parent)
55     {
56     }
57     CWallet *wallet;
58     TransactionTableModel *parent;
59
60     /* Local cache of wallet.
61      * As it is in the same order as the CWallet, by definition
62      * this is sorted by sha256.
63      */
64     QList<TransactionRecord> cachedWallet;
65
66     /* Query entire wallet anew from core.
67      */
68     void refreshWallet()
69     {
70         OutputDebugStringF("refreshWallet\n");
71         cachedWallet.clear();
72         {
73             LOCK(wallet->cs_wallet);
74             for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
75             {
76                 if(TransactionRecord::showTransaction(it->second))
77                     cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
78             }
79         }
80     }
81
82     /* Update our model of the wallet incrementally, to synchronize our model of the wallet
83        with that of the core.
84
85        Call with transaction that was added, removed or changed.
86      */
87     void updateWallet(const uint256 &hash, int status)
88     {
89         OutputDebugStringF("updateWallet %s %i\n", hash.ToString().c_str(), status);
90         {
91             LOCK(wallet->cs_wallet);
92
93             // Find transaction in wallet
94             std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
95             bool inWallet = mi != wallet->mapWallet.end();
96
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);
105
106             // Determine whether to show transaction or not
107             bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second));
108
109             if(status == CT_UPDATED)
110             {
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 */
115             }
116
117             OutputDebugStringF("   inWallet=%i inModel=%i Index=%i-%i showTransaction=%i derivedStatus=%i\n",
118                      inWallet, inModel, lowerIndex, upperIndex, showTransaction, status);
119
120             switch(status)
121             {
122             case CT_NEW:
123                 if(inModel)
124                 {
125                     OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is already in model\n");
126                     break;
127                 }
128                 if(!inWallet)
129                 {
130                     OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is not in wallet\n");
131                     break;
132                 }
133                 if(showTransaction)
134                 {
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 */
139                     {
140                         parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
141                         int insert_idx = lowerIndex;
142                         foreach(const TransactionRecord &rec, toInsert)
143                         {
144                             cachedWallet.insert(insert_idx, rec);
145                             insert_idx += 1;
146                         }
147                         parent->endInsertRows();
148                     }
149                 }
150                 break;
151             case CT_DELETED:
152                 if(!inModel)
153                 {
154                     OutputDebugStringF("Warning: updateWallet: Got CT_DELETED, but transaction is not in model\n");
155                     break;
156                 }
157                 // Removed -- remove entire transaction from table
158                 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
159                 cachedWallet.erase(lower, upper);
160                 parent->endRemoveRows();
161                 break;
162             case CT_UPDATED:
163                 // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
164                 // visible transactions.
165                 break;
166             }
167         }
168     }
169
170     int size()
171     {
172         return cachedWallet.size();
173     }
174
175     TransactionRecord *index(int idx)
176     {
177         if(idx >= 0 && idx < cachedWallet.size())
178         {
179             TransactionRecord *rec = &cachedWallet[idx];
180
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())
185             {
186                 {
187                     LOCK(wallet->cs_wallet);
188                     std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
189
190                     if(mi != wallet->mapWallet.end())
191                     {
192                         rec->updateStatus(mi->second);
193                     }
194                 }
195             }
196             return rec;
197         }
198         else
199         {
200             return 0;
201         }
202     }
203
204     QString describe(TransactionRecord *rec)
205     {
206         {
207             LOCK(wallet->cs_wallet);
208             std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
209             if(mi != wallet->mapWallet.end())
210             {
211                 return TransactionDesc::toHTML(wallet, mi->second);
212             }
213         }
214         return QString("");
215     }
216
217 };
218
219 TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *parent):
220         QAbstractTableModel(parent),
221         wallet(wallet),
222         walletModel(parent),
223         priv(new TransactionTablePriv(wallet, this)),
224         cachedNumBlocks(0)
225 {
226     columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
227
228     priv->refreshWallet();
229
230     QTimer *timer = new QTimer(this);
231     connect(timer, SIGNAL(timeout()), this, SLOT(updateConfirmations()));
232     timer->start(MODEL_UPDATE_DELAY);
233
234     connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
235 }
236
237 TransactionTableModel::~TransactionTableModel()
238 {
239     delete priv;
240 }
241
242 void TransactionTableModel::updateTransaction(const QString &hash, int status)
243 {
244     uint256 updated;
245     updated.SetHex(hash.toStdString());
246
247     priv->updateWallet(updated, status);
248 }
249
250 void TransactionTableModel::updateConfirmations()
251 {
252     if(nBestHeight != cachedNumBlocks)
253     {
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
258         //  visible rows.
259         emit dataChanged(index(0, Status), index(priv->size()-1, Status));
260         emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
261     }
262 }
263
264 int TransactionTableModel::rowCount(const QModelIndex &parent) const
265 {
266     Q_UNUSED(parent);
267     return priv->size();
268 }
269
270 int TransactionTableModel::columnCount(const QModelIndex &parent) const
271 {
272     Q_UNUSED(parent);
273     return columns.length();
274 }
275
276 QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
277 {
278     QString status;
279
280     switch(wtx->status.status)
281     {
282     case TransactionStatus::OpenUntilBlock:
283         status = tr("Open for %n block(s)","",wtx->status.open_for);
284         break;
285     case TransactionStatus::OpenUntilDate:
286         status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
287         break;
288     case TransactionStatus::Offline:
289         status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
290         break;
291     case TransactionStatus::Unconfirmed:
292         status = tr("Unconfirmed (%1 of %2 confirmations)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
293         break;
294     case TransactionStatus::HaveConfirmations:
295         status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
296         break;
297     }
298     if(wtx->type == TransactionRecord::Generated  || wtx->type == TransactionRecord::StakeMint)
299     {
300         switch(wtx->status.maturity)
301         {
302         case TransactionStatus::Immature:
303             status += "\n" + tr("Mined balance will be available when it matures in %n more block(s)", "", wtx->status.matures_in);
304             break;
305         case TransactionStatus::Mature:
306             break;
307         case TransactionStatus::MaturesWarning:
308             status += "\n" + tr("This block was not received by any other nodes and will probably not be accepted!");
309             break;
310         case TransactionStatus::NotAccepted:
311             status += "\n" + tr("Generated but not accepted");
312             break;
313         }
314     }
315
316     return status;
317 }
318
319 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
320 {
321     if(wtx->time)
322     {
323         return GUIUtil::dateTimeStr(wtx->time);
324     }
325     else
326     {
327         return QString();
328     }
329 }
330
331 /* Look up address in address book, if found return label (address)
332    otherwise just return (address)
333  */
334 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
335 {
336     QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
337     QString description;
338     if(!label.isEmpty())
339     {
340         description += label + QString(" ");
341     }
342     if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
343     {
344         description += QString("(") + QString::fromStdString(address) + QString(")");
345     }
346     return description;
347 }
348
349 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
350 {
351     switch(wtx->type)
352     {
353     case TransactionRecord::RecvWithAddress:
354         return tr("Received with");
355     case TransactionRecord::RecvFromOther:
356         return tr("Received from");
357     case TransactionRecord::SendToAddress:
358     case TransactionRecord::SendToOther:
359         return tr("Sent to");
360     case TransactionRecord::SendToSelf:
361         return tr("Payment to yourself");
362     case TransactionRecord::StakeMint:
363     case TransactionRecord::Generated:
364         return tr("Mined");
365     default:
366         return QString();
367     }
368 }
369
370 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
371 {
372     switch(wtx->type)
373     {
374     case TransactionRecord::Generated:
375     case TransactionRecord::StakeMint:
376         return QIcon(":/icons/tx_mined");
377     case TransactionRecord::RecvWithAddress:
378     case TransactionRecord::RecvFromOther:
379         return QIcon(":/icons/tx_input");
380     case TransactionRecord::SendToAddress:
381     case TransactionRecord::SendToOther:
382         return QIcon(":/icons/tx_output");
383     default:
384         return QIcon(":/icons/tx_inout");
385     }
386     return QVariant();
387 }
388
389 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
390 {
391     switch(wtx->type)
392     {
393     case TransactionRecord::RecvFromOther:
394         return QString::fromStdString(wtx->address);
395     case TransactionRecord::RecvWithAddress:
396     case TransactionRecord::SendToAddress:
397     case TransactionRecord::Generated:
398         return lookupAddress(wtx->address, tooltip);
399     case TransactionRecord::SendToOther:
400         return QString::fromStdString(wtx->address);
401     case TransactionRecord::SendToSelf:
402     default:
403         return tr("(n/a)");
404     }
405 }
406
407 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
408 {
409     // Show addresses without label in a less visible color
410     switch(wtx->type)
411     {
412     case TransactionRecord::RecvWithAddress:
413     case TransactionRecord::SendToAddress:
414     case TransactionRecord::Generated:
415         {
416         QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
417         if(label.isEmpty())
418             return COLOR_BAREADDRESS;
419         } break;
420     case TransactionRecord::SendToSelf:
421         return COLOR_BAREADDRESS;
422     default:
423         break;
424     }
425     return QVariant();
426 }
427
428 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
429 {
430     QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
431     if(showUnconfirmed)
432     {
433         if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
434         {
435             str = QString("[") + str + QString("]");
436         }
437     }
438     return QString(str);
439 }
440
441 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
442 {
443     if(wtx->type == TransactionRecord::Generated || wtx->type == TransactionRecord::StakeMint)
444     {
445         switch(wtx->status.maturity)
446         {
447         case TransactionStatus::Immature: {
448             int total = wtx->status.depth + wtx->status.matures_in;
449             int part = (wtx->status.depth * 4 / total) + 1;
450             return QIcon(QString(":/icons/transaction_%1").arg(part));
451             }
452         case TransactionStatus::Mature:
453             return QIcon(":/icons/transaction_confirmed");
454         case TransactionStatus::MaturesWarning:
455         case TransactionStatus::NotAccepted:
456             return QIcon(":/icons/transaction_0");
457         }
458     }
459     else
460     {
461         switch(wtx->status.status)
462         {
463         case TransactionStatus::OpenUntilBlock:
464         case TransactionStatus::OpenUntilDate:
465             return QColor(64,64,255);
466             break;
467         case TransactionStatus::Offline:
468             return QColor(192,192,192);
469         case TransactionStatus::Unconfirmed:
470             switch(wtx->status.depth)
471             {
472             case 0: return QIcon(":/icons/transaction_0");
473             case 1: return QIcon(":/icons/transaction_1");
474             case 2: return QIcon(":/icons/transaction_2");
475             case 3: return QIcon(":/icons/transaction_3");
476             case 4: return QIcon(":/icons/transaction_4");
477             default: return QIcon(":/icons/transaction_5");
478             };
479         case TransactionStatus::HaveConfirmations:
480             return QIcon(":/icons/transaction_confirmed");
481         }
482     }
483     return QColor(0,0,0);
484 }
485
486 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
487 {
488     QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
489     if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
490        rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
491     {
492         tooltip += QString(" ") + formatTxToAddress(rec, true);
493     }
494     return tooltip;
495 }
496
497 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
498 {
499     if(!index.isValid())
500         return QVariant();
501     TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
502
503     switch(role)
504     {
505     case Qt::DecorationRole:
506         switch(index.column())
507         {
508         case Status:
509             return txStatusDecoration(rec);
510         case ToAddress:
511             return txAddressDecoration(rec);
512         }
513         break;
514     case Qt::DisplayRole:
515         switch(index.column())
516         {
517         case Date:
518             return formatTxDate(rec);
519         case Type:
520             return formatTxType(rec);
521         case ToAddress:
522             return formatTxToAddress(rec, false);
523         case Amount:
524             return formatTxAmount(rec);
525         }
526         break;
527     case Qt::EditRole:
528         // Edit role is used for sorting, so return the unformatted values
529         switch(index.column())
530         {
531         case Status:
532             return QString::fromStdString(rec->status.sortKey);
533         case Date:
534             return rec->time;
535         case Type:
536             return formatTxType(rec);
537         case ToAddress:
538             return formatTxToAddress(rec, true);
539         case Amount:
540             return rec->credit + rec->debit;
541         }
542         break;
543     case Qt::ToolTipRole:
544         return formatTooltip(rec);
545     case Qt::TextAlignmentRole:
546         return column_alignments[index.column()];
547     case Qt::ForegroundRole:
548         // Non-confirmed transactions are grey
549         if(!rec->status.confirmed)
550         {
551             return COLOR_UNCONFIRMED;
552         }
553         if(index.column() == Amount && (rec->credit+rec->debit) < 0)
554         {
555             return COLOR_NEGATIVE;
556         }
557         if(index.column() == ToAddress)
558         {
559             return addressColor(rec);
560         }
561         break;
562     case TypeRole:
563         return rec->type;
564     case DateRole:
565         return QDateTime::fromTime_t(static_cast<uint>(rec->time));
566     case LongDescriptionRole:
567         return priv->describe(rec);
568     case AddressRole:
569         return QString::fromStdString(rec->address);
570     case LabelRole:
571         return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
572     case AmountRole:
573         return rec->credit + rec->debit;
574     case TxIDRole:
575         return QString::fromStdString(rec->getTxID());
576     case ConfirmedRole:
577         // Return True if transaction counts for balance
578         return rec->status.confirmed && !((rec->type == TransactionRecord::Generated || rec->type == TransactionRecord::StakeMint) &&
579                                           rec->status.maturity != TransactionStatus::Mature);
580     case FormattedAmountRole:
581         return formatTxAmount(rec, false);
582     }
583     return QVariant();
584 }
585
586 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
587 {
588     if(orientation == Qt::Horizontal)
589     {
590         if(role == Qt::DisplayRole)
591         {
592             return columns[section];
593         }
594         else if (role == Qt::TextAlignmentRole)
595         {
596             return column_alignments[section];
597         } else if (role == Qt::ToolTipRole)
598         {
599             switch(section)
600             {
601             case Status:
602                 return tr("Transaction status. Hover over this field to show number of confirmations.");
603             case Date:
604                 return tr("Date and time that the transaction was received.");
605             case Type:
606                 return tr("Type of transaction.");
607             case ToAddress:
608                 return tr("Destination address of transaction.");
609             case Amount:
610                 return tr("Amount removed from or added to balance.");
611             }
612         }
613     }
614     return QVariant();
615 }
616
617 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
618 {
619     Q_UNUSED(parent);
620     TransactionRecord *data = priv->index(row);
621     if(data)
622     {
623         return createIndex(row, column, priv->index(row));
624     }
625     else
626     {
627         return QModelIndex();
628     }
629 }
630
631 void TransactionTableModel::updateDisplayUnit()
632 {
633     // emit dataChanged to update Amount column with the current unit
634     emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));
635 }