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