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