41c9db1123350c560220aa920f0afbcca16c2ea7
[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 class TransactionTablePriv
49 {
50 public:
51     TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
52             wallet(wallet),
53             parent(parent)
54     {
55     }
56     CWallet *wallet;
57     TransactionTableModel *parent;
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      */
67     void refreshWallet()
68     {
69 #ifdef WALLET_UPDATE_DEBUG
70         qDebug() << "refreshWallet";
71 #endif
72         cachedWallet.clear();
73         {
74             LOCK(wallet->cs_wallet);
75             for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
76             {
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 list of hashes of transactions that were added, removed or changed.
86      */
87     void updateWallet(const QList<uint256> &updated)
88     {
89         // Walk through updated transactions, update model as needed.
90 #ifdef WALLET_UPDATE_DEBUG
91         qDebug() << "updateWallet";
92 #endif
93         // Sort update list, and iterate through it in reverse, so that model updates
94         //  can be emitted from end to beginning (so that earlier updates will not influence
95         // the indices of latter ones).
96         QList<uint256> updated_sorted = updated;
97         qSort(updated_sorted);
98
99         {
100             LOCK(wallet->cs_wallet);
101             for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
102             {
103                 const uint256 &hash = updated_sorted.at(update_idx);
104                 // Find transaction in wallet
105                 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
106                 bool inWallet = mi != wallet->mapWallet.end();
107                 // Find bounds of this transaction in model
108                 QList<TransactionRecord>::iterator lower = qLowerBound(
109                     cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
110                 QList<TransactionRecord>::iterator upper = qUpperBound(
111                     cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
112                 int lowerIndex = (lower - cachedWallet.begin());
113                 int upperIndex = (upper - cachedWallet.begin());
114
115                 // Determine if transaction is in model already
116                 bool inModel = false;
117                 if(lower != upper)
118                 {
119                     inModel = true;
120                 }
121
122 #ifdef WALLET_UPDATE_DEBUG
123                 qDebug() << "  " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
124                         << lowerIndex << "-" << upperIndex;
125 #endif
126
127                 if(inWallet && !inModel)
128                 {
129                     // Added -- insert at the right position
130                     QList<TransactionRecord> toInsert =
131                             TransactionRecord::decomposeTransaction(wallet, mi->second);
132                     if(!toInsert.isEmpty()) /* only if something to insert */
133                     {
134                         parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
135                         int insert_idx = lowerIndex;
136                         foreach(const TransactionRecord &rec, toInsert)
137                         {
138                             cachedWallet.insert(insert_idx, rec);
139                             insert_idx += 1;
140                         }
141                         parent->endInsertRows();
142                     }
143                 }
144                 else if(!inWallet && inModel)
145                 {
146                     // Removed -- remove entire transaction from table
147                     parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
148                     cachedWallet.erase(lower, upper);
149                     parent->endRemoveRows();
150                 }
151                 else if(inWallet && inModel)
152                 {
153                     // Updated -- nothing to do, status update will take care of this
154                 }
155             }
156         }
157     }
158
159     int size()
160     {
161         return cachedWallet.size();
162     }
163
164     TransactionRecord *index(int idx)
165     {
166         if(idx >= 0 && idx < cachedWallet.size())
167         {
168             TransactionRecord *rec = &cachedWallet[idx];
169
170             // If a status update is needed (blocks came in since last check),
171             //  update the status of this transaction from the wallet. Otherwise,
172             // simply re-use the cached status.
173             if(rec->statusUpdateNeeded())
174             {
175                 {
176                     LOCK(wallet->cs_wallet);
177                     std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
178
179                     if(mi != wallet->mapWallet.end())
180                     {
181                         rec->updateStatus(mi->second);
182                     }
183                 }
184             }
185             return rec;
186         }
187         else
188         {
189             return 0;
190         }
191     }
192
193     QString describe(TransactionRecord *rec)
194     {
195         {
196             LOCK(wallet->cs_wallet);
197             std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
198             if(mi != wallet->mapWallet.end())
199             {
200                 return TransactionDesc::toHTML(wallet, mi->second);
201             }
202         }
203         return QString("");
204     }
205
206 };
207
208 TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *parent):
209         QAbstractTableModel(parent),
210         wallet(wallet),
211         walletModel(parent),
212         priv(new TransactionTablePriv(wallet, this))
213 {
214     columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
215
216     priv->refreshWallet();
217
218     QTimer *timer = new QTimer(this);
219     connect(timer, SIGNAL(timeout()), this, SLOT(update()));
220     timer->start(MODEL_UPDATE_DELAY);
221 }
222
223 TransactionTableModel::~TransactionTableModel()
224 {
225     delete priv;
226 }
227
228 void TransactionTableModel::update()
229 {
230     QList<uint256> updated;
231
232     // Check if there are changes to wallet map
233     {
234         TRY_LOCK(wallet->cs_wallet, lockWallet);
235         if (lockWallet && !wallet->vWalletUpdated.empty())
236         {
237             BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated)
238             {
239                 updated.append(hash);
240             }
241             wallet->vWalletUpdated.clear();
242         }
243     }
244
245     if(!updated.empty())
246     {
247         priv->updateWallet(updated);
248
249         // Status (number of confirmations) and (possibly) description
250         //  columns changed for all rows.
251         emit dataChanged(index(0, Status), index(priv->size()-1, Status));
252         emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
253     }
254 }
255
256 int TransactionTableModel::rowCount(const QModelIndex &parent) const
257 {
258     Q_UNUSED(parent);
259     return priv->size();
260 }
261
262 int TransactionTableModel::columnCount(const QModelIndex &parent) const
263 {
264     Q_UNUSED(parent);
265     return columns.length();
266 }
267
268 QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
269 {
270     QString status;
271
272     switch(wtx->status.status)
273     {
274     case TransactionStatus::OpenUntilBlock:
275         status = tr("Open for %n block(s)","",wtx->status.open_for);
276         break;
277     case TransactionStatus::OpenUntilDate:
278         status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
279         break;
280     case TransactionStatus::Offline:
281         status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
282         break;
283     case TransactionStatus::Unconfirmed:
284         status = tr("Unconfirmed (%1 of %2 confirmations)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
285         break;
286     case TransactionStatus::HaveConfirmations:
287         status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
288         break;
289     }
290     if(wtx->type == TransactionRecord::Generated)
291     {
292         switch(wtx->status.maturity)
293         {
294         case TransactionStatus::Immature:
295             status += "\n" + 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 += "\n" + tr("This block was not received by any other nodes and will probably not be accepted!");
302             break;
303         case TransactionStatus::NotAccepted:
304             status += "\n" + 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