Merge branch '0.5.x' into 0.6.0.x
[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_wallet)
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_wallet)
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_wallet)
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_wallet)
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_wallet)
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::RecvFromOther:
349         return tr("Received from");
350     case TransactionRecord::SendToAddress:
351     case TransactionRecord::SendToOther:
352         return tr("Sent to");
353     case TransactionRecord::SendToSelf:
354         return tr("Payment to yourself");
355     case TransactionRecord::Generated:
356         return tr("Mined");
357     default:
358         return QString();
359     }
360 }
361
362 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
363 {
364     switch(wtx->type)
365     {
366     case TransactionRecord::Generated:
367         return QIcon(":/icons/tx_mined");
368     case TransactionRecord::RecvWithAddress:
369     case TransactionRecord::RecvFromOther:
370         return QIcon(":/icons/tx_input");
371     case TransactionRecord::SendToAddress:
372     case TransactionRecord::SendToOther:
373         return QIcon(":/icons/tx_output");
374     default:
375         return QIcon(":/icons/tx_inout");
376     }
377     return QVariant();
378 }
379
380 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
381 {
382     switch(wtx->type)
383     {
384     case TransactionRecord::RecvFromOther:
385         return QString::fromStdString(wtx->address);
386     case TransactionRecord::RecvWithAddress:
387     case TransactionRecord::SendToAddress:
388         return lookupAddress(wtx->address, tooltip);
389     case TransactionRecord::SendToOther:
390         return QString::fromStdString(wtx->address);
391     case TransactionRecord::SendToSelf:
392     case TransactionRecord::Generated:
393     default:
394         return tr("(n/a)");
395     }
396 }
397
398 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
399 {
400     // Show addresses without label in a less visible color
401     switch(wtx->type)
402     {
403     case TransactionRecord::RecvWithAddress:
404     case TransactionRecord::SendToAddress:
405         {
406         QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
407         if(label.isEmpty())
408             return COLOR_BAREADDRESS;
409         } break;
410     case TransactionRecord::SendToSelf:
411     case TransactionRecord::Generated:
412         return COLOR_BAREADDRESS;
413     default:
414         break;
415     }
416     return QVariant();
417 }
418
419 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
420 {
421     QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
422     if(showUnconfirmed)
423     {
424         if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
425         {
426             str = QString("[") + str + QString("]");
427         }
428     }
429     return QString(str);
430 }
431
432 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
433 {
434     if(wtx->type == TransactionRecord::Generated)
435     {
436         switch(wtx->status.maturity)
437         {
438         case TransactionStatus::Immature: {
439             int total = wtx->status.depth + wtx->status.matures_in;
440             int part = (wtx->status.depth * 4 / total) + 1;
441             return QIcon(QString(":/icons/transaction_%1").arg(part));
442             }
443         case TransactionStatus::Mature:
444             return QIcon(":/icons/transaction_confirmed");
445         case TransactionStatus::MaturesWarning:
446         case TransactionStatus::NotAccepted:
447             return QIcon(":/icons/transaction_0");
448         }
449     }
450     else
451     {
452         switch(wtx->status.status)
453         {
454         case TransactionStatus::OpenUntilBlock:
455         case TransactionStatus::OpenUntilDate:
456             return QColor(64,64,255);
457             break;
458         case TransactionStatus::Offline:
459             return QColor(192,192,192);
460         case TransactionStatus::Unconfirmed:
461             switch(wtx->status.depth)
462             {
463             case 0: return QIcon(":/icons/transaction_0");
464             case 1: return QIcon(":/icons/transaction_1");
465             case 2: return QIcon(":/icons/transaction_2");
466             case 3: return QIcon(":/icons/transaction_3");
467             case 4: return QIcon(":/icons/transaction_4");
468             default: return QIcon(":/icons/transaction_5");
469             };
470         case TransactionStatus::HaveConfirmations:
471             return QIcon(":/icons/transaction_confirmed");
472         }
473     }
474     return QColor(0,0,0);
475 }
476
477 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
478 {
479     QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
480     if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
481        rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
482     {
483         tooltip += QString(" ") + formatTxToAddress(rec, true);
484     }
485     return tooltip;
486 }
487
488 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
489 {
490     if(!index.isValid())
491         return QVariant();
492     TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
493
494     switch(role)
495     {
496     case Qt::DecorationRole:
497         switch(index.column())
498         {
499         case Status:
500             return txStatusDecoration(rec);
501         case ToAddress:
502             return txAddressDecoration(rec);
503         }
504         break;
505     case Qt::DisplayRole:
506         switch(index.column())
507         {
508         case Date:
509             return formatTxDate(rec);
510         case Type:
511             return formatTxType(rec);
512         case ToAddress:
513             return formatTxToAddress(rec, false);
514         case Amount:
515             return formatTxAmount(rec);
516         }
517         break;
518     case Qt::EditRole:
519         // Edit role is used for sorting, so return the unformatted values
520         switch(index.column())
521         {
522         case Status:
523             return QString::fromStdString(rec->status.sortKey);
524         case Date:
525             return rec->time;
526         case Type:
527             return formatTxType(rec);
528         case ToAddress:
529             return formatTxToAddress(rec, true);
530         case Amount:
531             return rec->credit + rec->debit;
532         }
533         break;
534     case Qt::ToolTipRole:
535         return formatTooltip(rec);
536     case Qt::TextAlignmentRole:
537         return column_alignments[index.column()];
538     case Qt::ForegroundRole:
539         // Non-confirmed transactions are grey
540         if(!rec->status.confirmed)
541         {
542             return COLOR_UNCONFIRMED;
543         }
544         if(index.column() == Amount && (rec->credit+rec->debit) < 0)
545         {
546             return COLOR_NEGATIVE;
547         }
548         if(index.column() == ToAddress)
549         {
550             return addressColor(rec);
551         }
552         break;
553     case TypeRole:
554         return rec->type;
555     case DateRole:
556         return QDateTime::fromTime_t(static_cast<uint>(rec->time));
557     case LongDescriptionRole:
558         return priv->describe(rec);
559     case AddressRole:
560         return QString::fromStdString(rec->address);
561     case LabelRole:
562         return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
563     case AmountRole:
564         return rec->credit + rec->debit;
565     case TxIDRole:
566         return QString::fromStdString(rec->getTxID());
567     case ConfirmedRole:
568         // Return True if transaction counts for balance
569         return rec->status.confirmed && !(rec->type == TransactionRecord::Generated &&
570                                           rec->status.maturity != TransactionStatus::Mature);
571     case FormattedAmountRole:
572         return formatTxAmount(rec, false);
573     }
574     return QVariant();
575 }
576
577 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
578 {
579     if(orientation == Qt::Horizontal)
580     {
581         if(role == Qt::DisplayRole)
582         {
583             return columns[section];
584         }
585         else if (role == Qt::TextAlignmentRole)
586         {
587             return column_alignments[section];
588         } else if (role == Qt::ToolTipRole)
589         {
590             switch(section)
591             {
592             case Status:
593                 return tr("Transaction status. Hover over this field to show number of confirmations.");
594             case Date:
595                 return tr("Date and time that the transaction was received.");
596             case Type:
597                 return tr("Type of transaction.");
598             case ToAddress:
599                 return tr("Destination address of transaction.");
600             case Amount:
601                 return tr("Amount removed from or added to balance.");
602             }
603         }
604     }
605     return QVariant();
606 }
607
608 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
609 {
610     Q_UNUSED(parent);
611     TransactionRecord *data = priv->index(row);
612     if(data)
613     {
614         return createIndex(row, column, priv->index(row));
615     }
616     else
617     {
618         return QModelIndex();
619     }
620 }
621