2477a1e8c9ef10d7f4ac5e51dd8aa37d3353e4d0
[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
8 #include "headers.h"
9
10 #include <QLocale>
11 #include <QDebug>
12 #include <QList>
13 #include <QColor>
14 #include <QTimer>
15 #include <QIcon>
16 #include <QDateTime>
17 #include <QtAlgorithms>
18
19 // Credit and Debit columns are right-aligned as they contain numbers
20 static int column_alignments[] = {
21         Qt::AlignLeft|Qt::AlignVCenter,
22         Qt::AlignLeft|Qt::AlignVCenter,
23         Qt::AlignLeft|Qt::AlignVCenter,
24         Qt::AlignLeft|Qt::AlignVCenter,
25         Qt::AlignRight|Qt::AlignVCenter
26     };
27
28 // Comparison operator for sort/binary search of model tx list
29 struct TxLessThan
30 {
31     bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
32     {
33         return a.hash < b.hash;
34     }
35     bool operator()(const TransactionRecord &a, const uint256 &b) const
36     {
37         return a.hash < b;
38     }
39     bool operator()(const uint256 &a, const TransactionRecord &b) const
40     {
41         return a < b.hash;
42     }
43 };
44
45 // Private implementation
46 struct TransactionTablePriv
47 {
48     TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
49             wallet(wallet),
50             parent(parent)
51     {
52     }
53     CWallet *wallet;
54     TransactionTableModel *parent;
55
56     /* Local cache of wallet.
57      * As it is in the same order as the CWallet, by definition
58      * this is sorted by sha256.
59      */
60     QList<TransactionRecord> cachedWallet;
61
62     /* Query entire wallet anew from core.
63      */
64     void refreshWallet()
65     {
66 #ifdef WALLET_UPDATE_DEBUG
67         qDebug() << "refreshWallet";
68 #endif
69         cachedWallet.clear();
70         CRITICAL_BLOCK(wallet->cs_mapWallet)
71         {
72             for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
73             {
74                 cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
75             }
76         }
77     }
78
79     /* Update our model of the wallet incrementally, to synchronize our model of the wallet
80        with that of the core.
81
82        Call with list of hashes of transactions that were added, removed or changed.
83      */
84     void updateWallet(const QList<uint256> &updated)
85     {
86         // Walk through updated transactions, update model as needed.
87 #ifdef WALLET_UPDATE_DEBUG
88         qDebug() << "updateWallet";
89 #endif
90         // Sort update list, and iterate through it in reverse, so that model updates
91         //  can be emitted from end to beginning (so that earlier updates will not influence
92         // the indices of latter ones).
93         QList<uint256> updated_sorted = updated;
94         qSort(updated_sorted);
95
96         CRITICAL_BLOCK(wallet->cs_mapWallet)
97         {
98             for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
99             {
100                 const uint256 &hash = updated_sorted.at(update_idx);
101                 /* Find transaction in wallet */
102                 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
103                 bool inWallet = mi != wallet->mapWallet.end();
104                 /* Find bounds of this transaction in model */
105                 QList<TransactionRecord>::iterator lower = qLowerBound(
106                     cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
107                 QList<TransactionRecord>::iterator upper = qUpperBound(
108                     cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
109                 int lowerIndex = (lower - cachedWallet.begin());
110                 int upperIndex = (upper - cachedWallet.begin());
111
112                 // Determine if transaction is in model already
113                 bool inModel = false;
114                 if(lower != upper)
115                 {
116                     inModel = true;
117                 }
118
119 #ifdef WALLET_UPDATE_DEBUG
120                 qDebug() << "  " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
121                         << lowerIndex << "-" << upperIndex;
122 #endif
123
124                 if(inWallet && !inModel)
125                 {
126                     // Added -- insert at the right position
127                     QList<TransactionRecord> toInsert =
128                             TransactionRecord::decomposeTransaction(wallet, mi->second);
129                     if(!toInsert.isEmpty()) /* only if something to insert */
130                     {
131                         parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
132                         int insert_idx = lowerIndex;
133                         foreach(const TransactionRecord &rec, toInsert)
134                         {
135                             cachedWallet.insert(insert_idx, rec);
136                             insert_idx += 1;
137                         }
138                         parent->endInsertRows();
139                     }
140                 }
141                 else if(!inWallet && inModel)
142                 {
143                     // Removed -- remove entire transaction from table
144                     parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
145                     cachedWallet.erase(lower, upper);
146                     parent->endRemoveRows();
147                 }
148                 else if(inWallet && inModel)
149                 {
150                     // Updated -- nothing to do, status update will take care of this
151                 }
152             }
153         }
154     }
155
156     int size()
157     {
158         return cachedWallet.size();
159     }
160
161     TransactionRecord *index(int idx)
162     {
163         if(idx >= 0 && idx < cachedWallet.size())
164         {
165             TransactionRecord *rec = &cachedWallet[idx];
166
167             // If a status update is needed (blocks came in since last check),
168             //  update the status of this transaction from the wallet. Otherwise,
169             // simply re-use the cached status.
170             if(rec->statusUpdateNeeded())
171             {
172                 CRITICAL_BLOCK(wallet->cs_mapWallet)
173                 {
174                     std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
175
176                     if(mi != wallet->mapWallet.end())
177                     {
178                         rec->updateStatus(mi->second);
179                     }
180                 }
181             }
182             return rec;
183         }
184         else
185         {
186             return 0;
187         }
188     }
189
190     QString describe(TransactionRecord *rec)
191     {
192         CRITICAL_BLOCK(wallet->cs_mapWallet)
193         {
194             std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
195             if(mi != wallet->mapWallet.end())
196             {
197                 return QString::fromStdString(TransactionDesc::toHTML(wallet, mi->second));
198             }
199         }
200         return QString("");
201     }
202
203 };
204
205 TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *parent):
206         QAbstractTableModel(parent),
207         wallet(wallet),
208         walletModel(parent),
209         priv(new TransactionTablePriv(wallet, this))
210 {
211     columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
212
213     priv->refreshWallet();
214
215     QTimer *timer = new QTimer(this);
216     connect(timer, SIGNAL(timeout()), this, SLOT(update()));
217     timer->start(MODEL_UPDATE_DELAY);
218 }
219
220 TransactionTableModel::~TransactionTableModel()
221 {
222     delete priv;
223 }
224
225 void TransactionTableModel::update()
226 {
227     QList<uint256> updated;
228
229     // Check if there are changes to wallet map
230     TRY_CRITICAL_BLOCK(wallet->cs_mapWallet)
231     {
232         if(!wallet->vWalletUpdated.empty())
233         {
234             BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated)
235             {
236                 updated.append(hash);
237             }
238             wallet->vWalletUpdated.clear();
239         }
240     }
241
242     if(!updated.empty())
243     {
244         priv->updateWallet(updated);
245
246         // Status (number of confirmations) and (possibly) description
247         //  columns changed for all rows.
248         emit dataChanged(index(0, Status), index(priv->size()-1, Status));
249         emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
250     }
251 }
252
253 int TransactionTableModel::rowCount(const QModelIndex &parent) const
254 {
255     Q_UNUSED(parent);
256     return priv->size();
257 }
258
259 int TransactionTableModel::columnCount(const QModelIndex &parent) const
260 {
261     Q_UNUSED(parent);
262     return columns.length();
263 }
264
265 QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
266 {
267     QString status;
268
269     switch(wtx->status.status)
270     {
271     case TransactionStatus::OpenUntilBlock:
272         status = tr("Open for %n block(s)","",wtx->status.open_for);
273         break;
274     case TransactionStatus::OpenUntilDate:
275         status = tr("Open until %1").arg(GUIUtil::DateTimeStr(wtx->status.open_for));
276         break;
277     case TransactionStatus::Offline:
278         status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
279         break;
280     case TransactionStatus::Unconfirmed:
281         status = tr("Unconfirmed (%1 of %2 confirmations required)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
282         break;
283     case TransactionStatus::HaveConfirmations:
284         status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
285         break;
286     }
287     if(wtx->type == TransactionRecord::Generated)
288     {
289         status += "\n\n";
290         switch(wtx->status.maturity)
291         {
292         case TransactionStatus::Immature:
293             status += tr("Mined balance will be available in %n more blocks", "",
294                            wtx->status.matures_in);
295             break;
296         case TransactionStatus::Mature:
297             break;
298         case TransactionStatus::MaturesWarning:
299             status += tr("This block was not received by any other nodes and will probably not be accepted!");
300             break;
301         case TransactionStatus::NotAccepted:
302             status += tr("Generated but not accepted");
303             break;
304         }
305     }
306
307     return QVariant(status);
308 }
309
310 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
311 {
312     if(wtx->time)
313     {
314         return QVariant(GUIUtil::DateTimeStr(wtx->time));
315     }
316     else
317     {
318         return QVariant();
319     }
320 }
321
322 /* Look up address in address book, if found return
323      address (label)
324    otherwise just return address
325  */
326 QString TransactionTableModel::lookupAddress(const std::string &address) const
327 {
328     QString label = walletModel->labelForAddress(QString::fromStdString(address));
329     QString description;
330     if(label.isEmpty())
331     {
332         description = QString::fromStdString(address);
333     }
334     else
335     {
336         description = label + QString(" (") + QString::fromStdString(address) + QString(")");
337     }
338     return description;
339 }
340
341 QVariant TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
342 {
343     QString description;
344
345     switch(wtx->type)
346     {
347     case TransactionRecord::RecvWithAddress:
348         description = tr("Received with");
349         break;
350     case TransactionRecord::RecvFromIP:
351         description = tr("Received from IP");
352         break;
353     case TransactionRecord::SendToAddress:
354         description = tr("Sent to");
355         break;
356     case TransactionRecord::SendToIP:
357         description = tr("Sent to IP");
358         break;
359     case TransactionRecord::SendToSelf:
360         description = tr("Payment to yourself");
361         break;
362     case TransactionRecord::Generated:
363         description = tr("Mined");
364         break;
365     }
366     return QVariant(description);
367 }
368
369 QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx) const
370 {
371     QString description;
372
373     switch(wtx->type)
374     {
375     case TransactionRecord::RecvWithAddress:
376         description = lookupAddress(wtx->address);
377         break;
378     case TransactionRecord::RecvFromIP:
379         description = QString::fromStdString(wtx->address);
380         break;
381     case TransactionRecord::SendToAddress:
382         description = lookupAddress(wtx->address);
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::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
398 {
399     QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit));
400     if(showUnconfirmed)
401     {
402         if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
403         {
404             str = QString("[") + str + QString("]");
405         }
406     }
407     return QVariant(str);
408 }
409
410 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
411 {
412     if(wtx->type == TransactionRecord::Generated)
413     {
414         switch(wtx->status.maturity)
415         {
416         case TransactionStatus::Immature: {
417             int total = wtx->status.depth + wtx->status.matures_in;
418             int part = (wtx->status.depth * 4 / total) + 1;
419             return QIcon(QString(":/icons/transaction_%1").arg(part));
420             }
421         case TransactionStatus::Mature:
422             return QIcon(":/icons/transaction_confirmed");
423         case TransactionStatus::MaturesWarning:
424         case TransactionStatus::NotAccepted:
425             return QIcon(":/icons/transaction_0");
426         }
427     }
428     else
429     {
430         switch(wtx->status.status)
431         {
432         case TransactionStatus::OpenUntilBlock:
433         case TransactionStatus::OpenUntilDate:
434             return QColor(64,64,255);
435             break;
436         case TransactionStatus::Offline:
437             return QColor(192,192,192);
438         case TransactionStatus::Unconfirmed:
439             switch(wtx->status.depth)
440             {
441             case 0: return QIcon(":/icons/transaction_0");
442             case 1: return QIcon(":/icons/transaction_1");
443             case 2: return QIcon(":/icons/transaction_2");
444             case 3: return QIcon(":/icons/transaction_3");
445             case 4: return QIcon(":/icons/transaction_4");
446             default: return QIcon(":/icons/transaction_5");
447             };
448         case TransactionStatus::HaveConfirmations:
449             return QIcon(":/icons/transaction_confirmed");
450         }
451     }
452     return QColor(0,0,0);
453 }
454
455 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
456 {
457     if(!index.isValid())
458         return QVariant();
459     TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
460
461     if(role == Qt::DecorationRole)
462     {
463         if(index.column() == Status)
464         {
465             return formatTxDecoration(rec);
466         }
467     }
468     else if(role == Qt::DisplayRole)
469     {
470         // Delegate to specific column handlers
471         switch(index.column())
472         {
473         case Date:
474             return formatTxDate(rec);
475         case Type:
476             return formatTxType(rec);
477         case ToAddress:
478             return formatTxToAddress(rec);
479         case Amount:
480             return formatTxAmount(rec);
481         }
482     }
483     else if(role == Qt::EditRole)
484     {
485         // Edit role is used for sorting so return the real values
486         switch(index.column())
487         {
488         case Status:
489             return QString::fromStdString(rec->status.sortKey);
490         case Date:
491             return rec->time;
492         case Type:
493             return formatTxType(rec);
494         case ToAddress:
495             return formatTxToAddress(rec);
496         case Amount:
497             return rec->credit + rec->debit;
498         }
499     }
500     else if (role == Qt::ToolTipRole)
501     {
502         if(index.column() == Status)
503         {
504             return formatTxStatus(rec);
505         }
506     }
507     else if (role == Qt::TextAlignmentRole)
508     {
509         return column_alignments[index.column()];
510     }
511     else if (role == Qt::ForegroundRole)
512     {
513         /* Non-confirmed transactions are grey */
514         if(!rec->status.confirmed)
515         {
516             return QColor(128, 128, 128);
517         }
518         if(index.column() == Amount && (rec->credit+rec->debit) < 0)
519         {
520             return QColor(255, 0, 0);
521         }
522     }
523     else if (role == TypeRole)
524     {
525         return rec->type;
526     }
527     else if (role == DateRole)
528     {
529         return QDateTime::fromTime_t(static_cast<uint>(rec->time));
530     }
531     else if (role == LongDescriptionRole)
532     {
533         return priv->describe(rec);
534     }
535     else if (role == AddressRole)
536     {
537         return QString::fromStdString(rec->address);
538     }
539     else if (role == LabelRole)
540     {
541         return walletModel->labelForAddress(QString::fromStdString(rec->address));
542     }
543     else if (role == AbsoluteAmountRole)
544     {
545         return llabs(rec->credit + rec->debit);
546     }
547     else if (role == TxIDRole)
548     {
549         return QString::fromStdString(rec->getTxID());
550     }
551     else if (role == ConfirmedRole)
552     {
553         return rec->status.status == TransactionStatus::HaveConfirmations;
554     }
555     else if (role == FormattedAmountRole)
556     {
557         return formatTxAmount(rec, false);
558     }
559     return QVariant();
560 }
561
562 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
563 {
564     if(orientation == Qt::Horizontal)
565     {
566         if(role == Qt::DisplayRole)
567         {
568             return columns[section];
569         }
570         else if (role == Qt::TextAlignmentRole)
571         {
572             return column_alignments[section];
573         } else if (role == Qt::ToolTipRole)
574         {
575             switch(section)
576             {
577             case Status:
578                 return tr("Transaction status. Hover over this field to show number of confirmations.");
579             case Date:
580                 return tr("Date and time that the transaction was received.");
581             case Type:
582                 return tr("Type of transaction.");
583             case ToAddress:
584                 return tr("Destination address of transaction.");
585             case Amount:
586                 return tr("Amount removed from or added to balance.");
587             }
588         }
589     }
590     return QVariant();
591 }
592
593 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
594 {
595     return QAbstractTableModel::flags(index);
596 }
597
598 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
599 {
600     Q_UNUSED(parent);
601     TransactionRecord *data = priv->index(row);
602     if(data)
603     {
604         return createIndex(row, column, priv->index(row));
605     }
606     else
607     {
608         return QModelIndex();
609     }
610 }
611