1606df9f5aa9b06049f34a9df584788a4b030af4
[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 QVariant 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\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 QVariant(status);
311 }
312
313 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
314 {
315     if(wtx->time)
316     {
317         return QVariant(GUIUtil::DateTimeStr(wtx->time));
318     }
319     else
320     {
321         return QVariant();
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 QVariant 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 QVariant(str);
432 }
433
434 QVariant TransactionTableModel::formatTxDecoration(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 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
480 {
481     if(!index.isValid())
482         return QVariant();
483     TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
484
485     if(role == Qt::DecorationRole)
486     {
487         switch(index.column())
488         {
489         case Status:
490             return formatTxDecoration(rec);
491         case ToAddress:
492             return txAddressDecoration(rec);
493         }
494     }
495     else if(role == Qt::DisplayRole)
496     {
497         // Delegate to specific column handlers
498         switch(index.column())
499         {
500         case Date:
501             return formatTxDate(rec);
502         case Type:
503             return formatTxType(rec);
504         case ToAddress:
505             return formatTxToAddress(rec, false);
506         case Amount:
507             return formatTxAmount(rec);
508         }
509     }
510     else if(role == Qt::EditRole)
511     {
512         // Edit role is used for sorting so return the real values
513         switch(index.column())
514         {
515         case Status:
516             return QString::fromStdString(rec->status.sortKey);
517         case Date:
518             return rec->time;
519         case Type:
520             return formatTxType(rec);
521         case ToAddress:
522             return formatTxToAddress(rec, true);
523         case Amount:
524             return rec->credit + rec->debit;
525         }
526     }
527     else if (role == Qt::ToolTipRole)
528     {
529         switch(index.column())
530         {
531         case Status:
532             return formatTxStatus(rec);
533         case ToAddress:
534             return formatTxType(rec) + QString(" ") + formatTxToAddress(rec, true);
535         }
536     }
537     else if (role == Qt::TextAlignmentRole)
538     {
539         return column_alignments[index.column()];
540     }
541     else if (role == Qt::ForegroundRole)
542     {
543         /* Non-confirmed transactions are grey */
544         if(!rec->status.confirmed)
545         {
546             return COLOR_UNCONFIRMED;
547         }
548         if(index.column() == Amount && (rec->credit+rec->debit) < 0)
549         {
550             return COLOR_NEGATIVE;
551         }
552         if(index.column() == ToAddress)
553         {
554             return addressColor(rec);
555         }
556     }
557     else if (role == TypeRole)
558     {
559         return rec->type;
560     }
561     else if (role == DateRole)
562     {
563         return QDateTime::fromTime_t(static_cast<uint>(rec->time));
564     }
565     else if (role == LongDescriptionRole)
566     {
567         return priv->describe(rec);
568     }
569     else if (role == AddressRole)
570     {
571         return QString::fromStdString(rec->address);
572     }
573     else if (role == LabelRole)
574     {
575         return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
576     }
577     else if (role == AbsoluteAmountRole)
578     {
579         return llabs(rec->credit + rec->debit);
580     }
581     else if (role == TxIDRole)
582     {
583         return QString::fromStdString(rec->getTxID());
584     }
585     else if (role == ConfirmedRole)
586     {
587         return rec->status.status == TransactionStatus::HaveConfirmations;
588     }
589     else if (role == FormattedAmountRole)
590     {
591         return formatTxAmount(rec, false);
592     }
593     return QVariant();
594 }
595
596 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
597 {
598     if(orientation == Qt::Horizontal)
599     {
600         if(role == Qt::DisplayRole)
601         {
602             return columns[section];
603         }
604         else if (role == Qt::TextAlignmentRole)
605         {
606             return column_alignments[section];
607         } else if (role == Qt::ToolTipRole)
608         {
609             switch(section)
610             {
611             case Status:
612                 return tr("Transaction status. Hover over this field to show number of confirmations.");
613             case Date:
614                 return tr("Date and time that the transaction was received.");
615             case Type:
616                 return tr("Type of transaction.");
617             case ToAddress:
618                 return tr("Destination address of transaction.");
619             case Amount:
620                 return tr("Amount removed from or added to balance.");
621             }
622         }
623     }
624     return QVariant();
625 }
626
627 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
628 {
629     return QAbstractTableModel::flags(index);
630 }
631
632 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
633 {
634     Q_UNUSED(parent);
635     TransactionRecord *data = priv->index(row);
636     if(data)
637     {
638         return createIndex(row, column, priv->index(row));
639     }
640     else
641     {
642         return QModelIndex();
643     }
644 }
645