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