f418a2bce3ef1c06ccc6fa104eb5161053d53d43
[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 <QDebug>
15 #include <QList>
16 #include <QColor>
17 #include <QTimer>
18 #include <QIcon>
19 #include <QDateTime>
20 #include <QtAlgorithms>
21
22 // Credit and Debit columns are right-aligned as they contain 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 struct TransactionTablePriv
50 {
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         CRITICAL_BLOCK(wallet->cs_mapWallet)
74         {
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         CRITICAL_BLOCK(wallet->cs_mapWallet)
100         {
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                 CRITICAL_BLOCK(wallet->cs_mapWallet)
176                 {
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         CRITICAL_BLOCK(wallet->cs_mapWallet)
196         {
197             std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
198             if(mi != wallet->mapWallet.end())
199             {
200                 return QString::fromStdString(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     TRY_CRITICAL_BLOCK(wallet->cs_mapWallet)
234     {
235         if(!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 required)").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         status += "\n";
293         switch(wtx->status.maturity)
294         {
295         case TransactionStatus::Immature:
296             status += tr("Mined balance will be available in %n more blocks", "",
297                            wtx->status.matures_in);
298             break;
299         case TransactionStatus::Mature:
300             break;
301         case TransactionStatus::MaturesWarning:
302             status += tr("This block was not received by any other nodes and will probably not be accepted!");
303             break;
304         case TransactionStatus::NotAccepted:
305             status += tr("Generated but not accepted");
306             break;
307         }
308     }
309
310     return status;
311 }
312
313 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
314 {
315     if(wtx->time)
316     {
317         return GUIUtil::DateTimeStr(wtx->time);
318     }
319     else
320     {
321         return QString();
322     }
323 }
324
325 /* Look up address in address book, if found return label (address)
326    otherwise just return (address)
327  */
328 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
329 {
330     QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
331     QString description;
332     if(!label.isEmpty())
333     {
334         description += label + QString(" ");
335     }
336     if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
337     {
338         description += QString("(") + QString::fromStdString(address) + QString(")");
339     }
340     return description;
341 }
342
343 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
344 {
345     switch(wtx->type)
346     {
347     case TransactionRecord::RecvWithAddress:
348         return tr("Received with");
349     case TransactionRecord::RecvFromIP:
350         return tr("Received from IP");
351     case TransactionRecord::SendToAddress:
352         return tr("Sent to");
353     case TransactionRecord::SendToIP:
354         return tr("Sent to IP");
355     case TransactionRecord::SendToSelf:
356         return tr("Payment to yourself");
357     case TransactionRecord::Generated:
358         return tr("Mined");
359     default:
360         return QString();
361     }
362 }
363
364 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
365 {
366     switch(wtx->type)
367     {
368     case TransactionRecord::Generated:
369         return QIcon(":/icons/tx_mined");
370     case TransactionRecord::RecvWithAddress:
371     case TransactionRecord::RecvFromIP:
372         return QIcon(":/icons/tx_input");
373     case TransactionRecord::SendToAddress:
374     case TransactionRecord::SendToIP:
375         return QIcon(":/icons/tx_output");
376     default:
377         return QIcon(":/icons/tx_inout");
378     }
379     return QVariant();
380 }
381
382 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
383 {
384     switch(wtx->type)
385     {
386     case TransactionRecord::RecvFromIP:
387         return QString::fromStdString(wtx->address);
388     case TransactionRecord::RecvWithAddress:
389     case TransactionRecord::SendToAddress:
390         return lookupAddress(wtx->address, tooltip);
391     case TransactionRecord::SendToIP:
392         return QString::fromStdString(wtx->address);
393     case TransactionRecord::SendToSelf:
394     case TransactionRecord::Generated:
395     default:
396         return tr("(n/a)");
397     }
398 }
399
400 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
401 {
402     // Show addresses without label in a less visible color
403     switch(wtx->type)
404     {
405     case TransactionRecord::RecvWithAddress:
406     case TransactionRecord::SendToAddress:
407         {
408         QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
409         if(label.isEmpty())
410             return COLOR_BAREADDRESS;
411         } break;
412     case TransactionRecord::SendToSelf:
413     case TransactionRecord::Generated:
414         return COLOR_BAREADDRESS;
415     default:
416         break;
417     }
418     return QVariant();
419 }
420
421 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
422 {
423     QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
424     if(showUnconfirmed)
425     {
426         if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
427         {
428             str = QString("[") + str + QString("]");
429         }
430     }
431     return QString(str);
432 }
433
434 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
435 {
436     if(wtx->type == TransactionRecord::Generated)
437     {
438         switch(wtx->status.maturity)
439         {
440         case TransactionStatus::Immature: {
441             int total = wtx->status.depth + wtx->status.matures_in;
442             int part = (wtx->status.depth * 4 / total) + 1;
443             return QIcon(QString(":/icons/transaction_%1").arg(part));
444             }
445         case TransactionStatus::Mature:
446             return QIcon(":/icons/transaction_confirmed");
447         case TransactionStatus::MaturesWarning:
448         case TransactionStatus::NotAccepted:
449             return QIcon(":/icons/transaction_0");
450         }
451     }
452     else
453     {
454         switch(wtx->status.status)
455         {
456         case TransactionStatus::OpenUntilBlock:
457         case TransactionStatus::OpenUntilDate:
458             return QColor(64,64,255);
459             break;
460         case TransactionStatus::Offline:
461             return QColor(192,192,192);
462         case TransactionStatus::Unconfirmed:
463             switch(wtx->status.depth)
464             {
465             case 0: return QIcon(":/icons/transaction_0");
466             case 1: return QIcon(":/icons/transaction_1");
467             case 2: return QIcon(":/icons/transaction_2");
468             case 3: return QIcon(":/icons/transaction_3");
469             case 4: return QIcon(":/icons/transaction_4");
470             default: return QIcon(":/icons/transaction_5");
471             };
472         case TransactionStatus::HaveConfirmations:
473             return QIcon(":/icons/transaction_confirmed");
474         }
475     }
476     return QColor(0,0,0);
477 }
478
479 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
480 {
481     QString tooltip = formatTxType(rec);
482     if(rec->type==TransactionRecord::RecvFromIP || rec->type==TransactionRecord::SendToIP ||
483        rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
484     {
485         tooltip += QString(" ") + formatTxToAddress(rec, true);
486     }
487     tooltip += QString("\n") + formatTxStatus(rec);
488     return tooltip;
489 }
490
491 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
492 {
493     if(!index.isValid())
494         return QVariant();
495     TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
496
497     if(role == Qt::DecorationRole)
498     {
499         switch(index.column())
500         {
501         case Status:
502             return txStatusDecoration(rec);
503         case ToAddress:
504             return txAddressDecoration(rec);
505         }
506     }
507     else if(role == Qt::DisplayRole)
508     {
509         // Delegate to specific column handlers
510         switch(index.column())
511         {
512         case Date:
513             return formatTxDate(rec);
514         case Type:
515             return formatTxType(rec);
516         case ToAddress:
517             return formatTxToAddress(rec, false);
518         case Amount:
519             return formatTxAmount(rec);
520         }
521     }
522     else if(role == Qt::EditRole)
523     {
524         // Edit role is used for sorting so return the real values
525         switch(index.column())
526         {
527         case Status:
528             return QString::fromStdString(rec->status.sortKey);
529         case Date:
530             return rec->time;
531         case Type:
532             return formatTxType(rec);
533         case ToAddress:
534             return formatTxToAddress(rec, true);
535         case Amount:
536             return rec->credit + rec->debit;
537         }
538     }
539     else if (role == Qt::ToolTipRole)
540     {
541         switch(index.column())
542         {
543         case Status:
544             return formatTxStatus(rec);
545         default:
546             return formatTooltip(rec);
547         }
548     }
549     else if (role == Qt::TextAlignmentRole)
550     {
551         return column_alignments[index.column()];
552     }
553     else if (role == Qt::ForegroundRole)
554     {
555         /* Non-confirmed transactions are grey */
556         if(!rec->status.confirmed)
557         {
558             return COLOR_UNCONFIRMED;
559         }
560         if(index.column() == Amount && (rec->credit+rec->debit) < 0)
561         {
562             return COLOR_NEGATIVE;
563         }
564         if(index.column() == ToAddress)
565         {
566             return addressColor(rec);
567         }
568     }
569     else if (role == TypeRole)
570     {
571         return rec->type;
572     }
573     else if (role == DateRole)
574     {
575         return QDateTime::fromTime_t(static_cast<uint>(rec->time));
576     }
577     else if (role == LongDescriptionRole)
578     {
579         return priv->describe(rec);
580     }
581     else if (role == AddressRole)
582     {
583         return QString::fromStdString(rec->address);
584     }
585     else if (role == LabelRole)
586     {
587         return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
588     }
589     else if (role == AmountRole)
590     {
591         return rec->credit + rec->debit;
592     }
593     else if (role == TxIDRole)
594     {
595         return QString::fromStdString(rec->getTxID());
596     }
597     else if (role == ConfirmedRole)
598     {
599         // Return True if transaction counts for balance
600         return rec->status.confirmed && !(rec->type == TransactionRecord::Generated &&
601                                           rec->status.maturity != TransactionStatus::Mature);
602     }
603     else if (role == FormattedAmountRole)
604     {
605         return formatTxAmount(rec, false);
606     }
607     return QVariant();
608 }
609
610 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
611 {
612     if(orientation == Qt::Horizontal)
613     {
614         if(role == Qt::DisplayRole)
615         {
616             return columns[section];
617         }
618         else if (role == Qt::TextAlignmentRole)
619         {
620             return column_alignments[section];
621         } else if (role == Qt::ToolTipRole)
622         {
623             switch(section)
624             {
625             case Status:
626                 return tr("Transaction status. Hover over this field to show number of confirmations.");
627             case Date:
628                 return tr("Date and time that the transaction was received.");
629             case Type:
630                 return tr("Type of transaction.");
631             case ToAddress:
632                 return tr("Destination address of transaction.");
633             case Amount:
634                 return tr("Amount removed from or added to balance.");
635             }
636         }
637     }
638     return QVariant();
639 }
640
641 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
642 {
643     return QAbstractTableModel::flags(index);
644 }
645
646 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
647 {
648     Q_UNUSED(parent);
649     TransactionRecord *data = priv->index(row);
650     if(data)
651     {
652         return createIndex(row, column, priv->index(row));
653     }
654     else
655     {
656         return QModelIndex();
657     }
658 }
659