1adc81bb7ff19e436bf8da7ca25465b271175791
[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") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
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 /** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */
243 void TransactionTableModel::updateAmountColumnTitle()
244 {
245     columns[Amount] = BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
246     emit headerDataChanged(Qt::Horizontal,Amount,Amount);
247 }
248
249 void TransactionTableModel::updateTransaction(const QString &hash, int status)
250 {
251     uint256 updated;
252     updated.SetHex(hash.toStdString());
253
254     priv->updateWallet(updated, status);
255 }
256
257 void TransactionTableModel::updateConfirmations()
258 {
259     if(nBestHeight != cachedNumBlocks)
260     {
261         cachedNumBlocks = nBestHeight;
262         // Blocks came in since last poll.
263         // Invalidate status (number of confirmations) and (possibly) description
264         //  for all rows. Qt is smart enough to only actually request the data for the
265         //  visible rows.
266         emit dataChanged(index(0, Status), index(priv->size()-1, Status));
267         emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
268     }
269 }
270
271 void TransactionTableModel::refresh()
272 {
273     priv->refreshWallet();
274     emit dataChanged(index(0, 0), index(priv->size() - 1, Amount));
275 }
276
277 int TransactionTableModel::rowCount(const QModelIndex &parent) const
278 {
279     Q_UNUSED(parent);
280     return priv->size();
281 }
282
283 int TransactionTableModel::columnCount(const QModelIndex &parent) const
284 {
285     Q_UNUSED(parent);
286     return columns.length();
287 }
288
289 QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
290 {
291     QString status;
292     int nNumConf = TransactionRecord::NumConfirmations;
293
294     if (wtx->type == TransactionRecord::Generated)
295     {
296         nNumConf = nCoinbaseMaturity + 20;
297     }
298
299     switch(wtx->status.status)
300     {
301     case TransactionStatus::OpenUntilBlock:
302         status = tr("Open for %n block(s)","",wtx->status.open_for);
303         break;
304     case TransactionStatus::OpenUntilDate:
305         status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
306         break;
307     case TransactionStatus::Offline:
308         status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
309         break;
310     case TransactionStatus::Unconfirmed:
311         status = tr("Unconfirmed (%1 of %2 confirmations)").arg(wtx->status.depth).arg(nNumConf);
312         break;
313     case TransactionStatus::HaveConfirmations:
314         status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
315         break;
316     }
317
318     if(wtx->type == TransactionRecord::Generated)
319     {
320         switch(wtx->status.maturity)
321         {
322         case TransactionStatus::Immature:
323             status += "\n" + tr("Mined balance will be available when it matures in %n more block(s)", "", wtx->status.matures_in);
324             break;
325         case TransactionStatus::Mature:
326             break;
327         case TransactionStatus::MaturesWarning:
328             status += "\n" + tr("This block was not received by any other nodes and will probably not be accepted!");
329             break;
330         case TransactionStatus::NotAccepted:
331             status += "\n" + tr("Generated but not accepted");
332             break;
333         }
334     }
335
336     return status;
337 }
338
339 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
340 {
341     if(wtx->time)
342     {
343         return GUIUtil::dateTimeStr(wtx->time);
344     }
345     else
346     {
347         return QString();
348     }
349 }
350
351 /* Look up address in address book, if found return label (address)
352    otherwise just return (address)
353  */
354 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
355 {
356     QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
357     QString description;
358     if(!label.isEmpty())
359     {
360         description += label + QString(" ");
361     }
362     if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
363     {
364         description += QString("(") + QString::fromStdString(address) + QString(")");
365     }
366     return description;
367 }
368
369 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
370 {
371     switch(wtx->type)
372     {
373     case TransactionRecord::RecvWithAddress:
374         return tr("Received with");
375     case TransactionRecord::RecvFromOther:
376         return tr("Received from");
377     case TransactionRecord::SendToAddress:
378     case TransactionRecord::SendToOther:
379         return tr("Sent to");
380     case TransactionRecord::SendToSelf:
381         return tr("Payment to yourself");
382     case TransactionRecord::Generated:
383         return tr("Mined");
384     default:
385         return QString();
386     }
387 }
388
389 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
390 {
391     switch(wtx->type)
392     {
393     case TransactionRecord::Generated:
394         return QIcon(":/icons/tx_mined");
395     case TransactionRecord::RecvWithAddress:
396     case TransactionRecord::RecvFromOther:
397         return QIcon(":/icons/tx_input");
398     case TransactionRecord::SendToAddress:
399     case TransactionRecord::SendToOther:
400         return QIcon(":/icons/tx_output");
401     default:
402         return QIcon(":/icons/tx_inout");
403     }
404     return QVariant();
405 }
406
407 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
408 {
409     switch(wtx->type)
410     {
411     case TransactionRecord::RecvFromOther:
412         return QString::fromStdString(wtx->address);
413     case TransactionRecord::RecvWithAddress:
414     case TransactionRecord::SendToAddress:
415     case TransactionRecord::Generated:
416         return lookupAddress(wtx->address, tooltip);
417     case TransactionRecord::SendToOther:
418         return QString::fromStdString(wtx->address);
419     case TransactionRecord::SendToSelf:
420     default:
421         return tr("(n/a)");
422     }
423 }
424
425 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
426 {
427     // Show addresses without label in a less visible color
428     switch(wtx->type)
429     {
430     case TransactionRecord::RecvWithAddress:
431     case TransactionRecord::SendToAddress:
432     case TransactionRecord::Generated:
433         {
434         QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
435         if(label.isEmpty())
436             return COLOR_BAREADDRESS;
437         } break;
438     case TransactionRecord::SendToSelf:
439         return COLOR_BAREADDRESS;
440     default:
441         break;
442     }
443     return QVariant();
444 }
445
446 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
447 {
448     QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
449     if(showUnconfirmed)
450     {
451         if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
452         {
453             str = QString("[") + str + QString("]");
454         }
455     }
456     return QString(str);
457 }
458
459 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
460 {
461     if(wtx->type == TransactionRecord::Generated)
462     {
463         switch(wtx->status.maturity)
464         {
465         case TransactionStatus::Immature: {
466             int total = wtx->status.depth + wtx->status.matures_in;
467             int part = (wtx->status.depth * 4 / total) + 1;
468             return QIcon(QString(":/icons/transaction_%1").arg(part));
469             }
470         case TransactionStatus::Mature:
471             return QIcon(":/icons/transaction_confirmed");
472         case TransactionStatus::MaturesWarning:
473         case TransactionStatus::NotAccepted:
474             return QIcon(":/icons/transaction_0");
475         }
476     }
477     else
478     {
479         switch(wtx->status.status)
480         {
481         case TransactionStatus::OpenUntilBlock:
482         case TransactionStatus::OpenUntilDate:
483             return QColor(64,64,255);
484             break;
485         case TransactionStatus::Offline:
486             return QColor(192,192,192);
487         case TransactionStatus::Unconfirmed:
488             switch(wtx->status.depth)
489             {
490             case 0: return QIcon(":/icons/transaction_0");
491             case 1: return QIcon(":/icons/transaction_1");
492             case 2: return QIcon(":/icons/transaction_2");
493             case 3: return QIcon(":/icons/transaction_3");
494             case 4: return QIcon(":/icons/transaction_4");
495             default: return QIcon(":/icons/transaction_5");
496             };
497         case TransactionStatus::HaveConfirmations:
498             return QIcon(":/icons/transaction_confirmed");
499         }
500     }
501     return QColor(0,0,0);
502 }
503
504 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
505 {
506     QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
507     if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
508        rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
509     {
510         tooltip += QString(" ") + formatTxToAddress(rec, true);
511     }
512     return tooltip;
513 }
514
515 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
516 {
517     if(!index.isValid())
518         return QVariant();
519     TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
520
521     switch(role)
522     {
523     case Qt::DecorationRole:
524         switch(index.column())
525         {
526         case Status:
527             return txStatusDecoration(rec);
528         case ToAddress:
529             return txAddressDecoration(rec);
530         }
531         break;
532     case Qt::DisplayRole:
533         switch(index.column())
534         {
535         case Date:
536             return formatTxDate(rec);
537         case Type:
538             return formatTxType(rec);
539         case ToAddress:
540             return formatTxToAddress(rec, false);
541         case Amount:
542             return formatTxAmount(rec);
543         }
544         break;
545     case Qt::EditRole:
546         // Edit role is used for sorting, so return the unformatted values
547         switch(index.column())
548         {
549         case Status:
550             return QString::fromStdString(rec->status.sortKey);
551         case Date:
552             // We need cast here to prevent ambigious conversion error
553             return static_cast<qlonglong>(rec->time);
554         case Type:
555             return formatTxType(rec);
556         case ToAddress:
557             return formatTxToAddress(rec, true);
558         case Amount:
559             // Same here
560             return static_cast<qlonglong>(rec->credit + rec->debit);
561         }
562         break;
563     case Qt::ToolTipRole:
564         return formatTooltip(rec);
565     case Qt::TextAlignmentRole:
566         return column_alignments[index.column()];
567     case Qt::ForegroundRole:
568         // Non-confirmed transactions are grey
569         if(!rec->status.confirmed)
570         {
571             return COLOR_UNCONFIRMED;
572         }
573         if(index.column() == Amount && (rec->credit+rec->debit) < 0)
574         {
575             return COLOR_NEGATIVE;
576         }
577         if(index.column() == ToAddress)
578         {
579             return addressColor(rec);
580         }
581         break;
582     case TypeRole:
583         return rec->type;
584     case DateRole:
585         return QDateTime::fromTime_t(static_cast<uint>(rec->time));
586     case LongDescriptionRole:
587         return priv->describe(rec);
588     case AddressRole:
589         return QString::fromStdString(rec->address);
590     case LabelRole:
591         return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
592     case AmountRole:
593         // And here
594         return static_cast<qlonglong>(rec->credit + rec->debit);
595     case TxIDRole:
596         return QString::fromStdString(rec->getTxID());
597     case TxHashRole:
598         return QString::fromStdString(rec->hash.ToString());
599     case ConfirmedRole:
600         // Return True if transaction counts for balance
601         return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && rec->status.maturity != TransactionStatus::Mature);
602     case FormattedAmountRole:
603         return formatTxAmount(rec, false);
604     }
605     return QVariant();
606 }
607
608 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
609 {
610     if(orientation == Qt::Horizontal)
611     {
612         if(role == Qt::DisplayRole)
613         {
614             return columns[section];
615         }
616         else if (role == Qt::TextAlignmentRole)
617         {
618             return column_alignments[section];
619         } else if (role == Qt::ToolTipRole)
620         {
621             switch(section)
622             {
623             case Status:
624                 return tr("Transaction status. Hover over this field to show number of confirmations.");
625             case Date:
626                 return tr("Date and time that the transaction was received.");
627             case Type:
628                 return tr("Type of transaction.");
629             case ToAddress:
630                 return tr("Destination address of transaction.");
631             case Amount:
632                 return tr("Amount removed from or added to balance.");
633             }
634         }
635     }
636     return QVariant();
637 }
638
639 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
640 {
641     Q_UNUSED(parent);
642     TransactionRecord *data = priv->index(row);
643     if(data)
644     {
645         return createIndex(row, column, priv->index(row));
646     }
647     else
648     {
649         return QModelIndex();
650     }
651 }
652
653 void TransactionTableModel::updateDisplayUnit()
654 {
655     updateAmountColumnTitle();
656     // emit dataChanged to update Amount column with the current unit
657     emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));
658 }