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