e49629c9a305fe98f508916d3b822ebb2e1f068b
[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
7 #include "headers.h"
8
9 #include <QLocale>
10 #include <QDebug>
11 #include <QList>
12 #include <QColor>
13 #include <QTimer>
14 #include <QIcon>
15 #include <QtAlgorithms>
16
17 const QString TransactionTableModel::Sent = "s";
18 const QString TransactionTableModel::Received = "r";
19 const QString TransactionTableModel::Other = "o";
20
21 // Credit and Debit columns are right-aligned as they contain 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         CRITICAL_BLOCK(wallet->cs_mapWallet)
73         {
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         CRITICAL_BLOCK(wallet->cs_mapWallet)
99         {
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                 CRITICAL_BLOCK(wallet->cs_mapWallet)
175                 {
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         CRITICAL_BLOCK(wallet->cs_mapWallet)
195         {
196             std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
197             if(mi != wallet->mapWallet.end())
198             {
199                 return QString::fromStdString(TransactionDesc::toHTML(wallet, mi->second));
200             }
201         }
202         return QString("");
203     }
204
205 };
206
207 TransactionTableModel::TransactionTableModel(CWallet* wallet, QObject *parent):
208         QAbstractTableModel(parent),
209         wallet(wallet),
210         priv(new TransactionTablePriv(wallet, this))
211 {
212     columns << tr("Status") << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
213
214     priv->refreshWallet();
215
216     QTimer *timer = new QTimer(this);
217     connect(timer, SIGNAL(timeout()), this, SLOT(update()));
218     timer->start(MODEL_UPDATE_DELAY);
219 }
220
221 TransactionTableModel::~TransactionTableModel()
222 {
223     delete priv;
224 }
225
226 void TransactionTableModel::update()
227 {
228     QList<uint256> updated;
229
230     // Check if there are changes to wallet map
231     TRY_CRITICAL_BLOCK(wallet->cs_mapWallet)
232     {
233         if(!wallet->vWalletUpdated.empty())
234         {
235             BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated)
236             {
237                 updated.append(hash);
238             }
239             wallet->vWalletUpdated.clear();
240         }
241     }
242
243     if(!updated.empty())
244     {
245         priv->updateWallet(updated);
246
247         // Status (number of confirmations) and (possibly) description
248         //  columns changed for all rows.
249         emit dataChanged(index(0, Status), index(priv->size()-1, Status));
250         emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
251     }
252 }
253
254 int TransactionTableModel::rowCount(const QModelIndex &parent) const
255 {
256     Q_UNUSED(parent);
257     return priv->size();
258 }
259
260 int TransactionTableModel::columnCount(const QModelIndex &parent) const
261 {
262     Q_UNUSED(parent);
263     return columns.length();
264 }
265
266 QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
267 {
268     QString status;
269
270     switch(wtx->status.status)
271     {
272     case TransactionStatus::OpenUntilBlock:
273         status = tr("Open for %n block(s)","",wtx->status.open_for);
274         break;
275     case TransactionStatus::OpenUntilDate:
276         status = tr("Open until %1").arg(GUIUtil::DateTimeStr(wtx->status.open_for));
277         break;
278     case TransactionStatus::Offline:
279         status = tr("Offline (%1)").arg(wtx->status.depth);
280         break;
281     case TransactionStatus::Unconfirmed:
282         status = tr("Unconfirmed (%1/%2)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
283         break;
284     case TransactionStatus::HaveConfirmations:
285         status = tr("Confirmed (%1)").arg(wtx->status.depth);
286         break;
287     }
288
289     return QVariant(status);
290 }
291
292 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
293 {
294     if(wtx->time)
295     {
296         return QVariant(GUIUtil::DateTimeStr(wtx->time));
297     }
298     else
299     {
300         return QVariant();
301     }
302 }
303
304 /* Look up address in address book, if found return
305      address[0:12]... (label)
306    otherwise just return address
307  */
308 std::string TransactionTableModel::lookupAddress(const std::string &address) const
309 {
310     std::string description;
311     CRITICAL_BLOCK(wallet->cs_mapAddressBook)
312     {
313         std::map<std::string, std::string>::iterator mi = wallet->mapAddressBook.find(address);
314         if (mi != wallet->mapAddressBook.end() && !(*mi).second.empty())
315         {
316             std::string label = (*mi).second;
317             description += address.substr(0,12) + "... ";
318             description += "(" + label + ")";
319         }
320         else
321         {
322             description += address;
323         }
324     }
325     return description;
326 }
327
328 QVariant TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
329 {
330     QString description;
331
332     switch(wtx->type)
333     {
334     case TransactionRecord::RecvWithAddress:
335         description = tr("Received with");
336         break;
337     case TransactionRecord::RecvFromIP:
338         description = tr("Received from IP");
339         break;
340     case TransactionRecord::SendToAddress:
341         description = tr("Sent to");
342         break;
343     case TransactionRecord::SendToIP:
344         description = tr("Sent to IP");
345         break;
346     case TransactionRecord::SendToSelf:
347         description = tr("Payment to yourself");
348         break;
349     case TransactionRecord::Generated:
350         description = tr("Generated");
351         break;
352     }
353     return QVariant(description);
354 }
355
356 QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx) const
357 {
358     QString description;
359
360     switch(wtx->type)
361     {
362     case TransactionRecord::RecvWithAddress:
363         description = QString::fromStdString(lookupAddress(wtx->address));
364         break;
365     case TransactionRecord::RecvFromIP:
366         description = QString::fromStdString(wtx->address);
367         break;
368     case TransactionRecord::SendToAddress:
369         description = QString::fromStdString(lookupAddress(wtx->address));
370         break;
371     case TransactionRecord::SendToIP:
372         description = QString::fromStdString(wtx->address);
373         break;
374     case TransactionRecord::SendToSelf:
375         description = QString();
376         break;
377     case TransactionRecord::Generated:
378         switch(wtx->status.maturity)
379         {
380         case TransactionStatus::Immature:
381             description = tr("(matures in %n more blocks)", "",
382                            wtx->status.matures_in);
383             break;
384         case TransactionStatus::Mature:
385             description = QString();
386             break;
387         case TransactionStatus::MaturesWarning:
388             description = tr("(Warning: This block was not received by any other nodes and will probably not be accepted!)");
389             break;
390         case TransactionStatus::NotAccepted:
391             description = tr("(not accepted)");
392             break;
393         }
394         break;
395     }
396     return QVariant(description);
397 }
398
399 QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx) const
400 {
401     QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit));
402     if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
403     {
404         str = QString("[") + str + QString("]");
405     }
406     return QVariant(str);
407 }
408
409 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
410 {
411     switch(wtx->status.status)
412     {
413     case TransactionStatus::OpenUntilBlock:
414     case TransactionStatus::OpenUntilDate:
415         return QColor(64,64,255);
416         break;
417     case TransactionStatus::Offline:
418         return QColor(192,192,192);
419     case TransactionStatus::Unconfirmed:
420         switch(wtx->status.depth)
421         {
422         case 0: return QIcon(":/icons/transaction_0");
423         case 1: return QIcon(":/icons/transaction_1");
424         case 2: return QIcon(":/icons/transaction_2");
425         case 3: return QIcon(":/icons/transaction_3");
426         case 4: return QIcon(":/icons/transaction_4");
427         default: return QIcon(":/icons/transaction_5");
428         };
429     case TransactionStatus::HaveConfirmations:
430         return QIcon(":/icons/transaction_confirmed");
431     }
432     return QColor(0,0,0);
433 }
434
435 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
436 {
437     if(!index.isValid())
438         return QVariant();
439     TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
440
441     if(role == Qt::DecorationRole)
442     {
443         if(index.column() == Status)
444         {
445             return formatTxDecoration(rec);
446         }
447     }
448     else if(role == Qt::DisplayRole)
449     {
450         // Delegate to specific column handlers
451         switch(index.column())
452         {
453         case Date:
454             return formatTxDate(rec);
455         case Type:
456             return formatTxType(rec);
457         case ToAddress:
458             return formatTxToAddress(rec);
459         case Amount:
460             return formatTxAmount(rec);
461         }
462     }
463     else if(role == Qt::EditRole)
464     {
465         // Edit role is used for sorting so return the real values
466         switch(index.column())
467         {
468         case Status:
469             return QString::fromStdString(rec->status.sortKey);
470         case Date:
471             return rec->time;
472         case Type:
473             return formatTxType(rec);
474         case ToAddress:
475             return formatTxToAddress(rec);
476         case Amount:
477             return rec->credit + rec->debit;
478         }
479     }
480     else if (role == Qt::ToolTipRole)
481     {
482         if(index.column() == Status)
483         {
484             return formatTxStatus(rec);
485         }
486     }
487     else if (role == Qt::TextAlignmentRole)
488     {
489         return column_alignments[index.column()];
490     }
491     else if (role == Qt::ForegroundRole)
492     {
493         /* Non-confirmed transactions are grey */
494         if(!rec->status.confirmed)
495         {
496             return QColor(128, 128, 128);
497         }
498         if(index.column() == Amount && (rec->credit+rec->debit) < 0)
499         {
500             return QColor(255, 0, 0);
501         }
502     }
503     else if (role == TypeRole)
504     {
505         /* Role for filtering tabs by type */
506         switch(rec->type)
507         {
508         case TransactionRecord::RecvWithAddress:
509         case TransactionRecord::RecvFromIP:
510             return TransactionTableModel::Received;
511         case TransactionRecord::SendToAddress:
512         case TransactionRecord::SendToIP:
513         case TransactionRecord::SendToSelf:
514             return TransactionTableModel::Sent;
515         default:
516             return TransactionTableModel::Other;
517         }
518     }
519     else if (role == LongDescriptionRole)
520     {
521         return priv->describe(rec);
522     }
523     return QVariant();
524 }
525
526 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
527 {
528     if(orientation == Qt::Horizontal)
529     {
530         if(role == Qt::DisplayRole)
531         {
532             return columns[section];
533         }
534         else if (role == Qt::TextAlignmentRole)
535         {
536             return column_alignments[section];
537         } else if (role == Qt::ToolTipRole)
538         {
539             switch(section)
540             {
541             case Status:
542                 return tr("Transaction status. Hover over this field to show number of confirmations.");
543             case Date:
544                 return tr("Date and time that the transaction was received.");
545             case Type:
546                 return tr("Type of transaction.");
547             case ToAddress:
548                 return tr("Destination address of transaction.");
549             case Amount:
550                 return tr("Amount removed from or added to balance.");
551             }
552         }
553     }
554     return QVariant();
555 }
556
557 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
558 {
559     return QAbstractTableModel::flags(index);
560 }
561
562 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
563 {
564     Q_UNUSED(parent);
565     TransactionRecord *data = priv->index(row);
566     if(data)
567     {
568         return createIndex(row, column, priv->index(row));
569     }
570     else
571     {
572         return QModelIndex();
573     }
574 }
575