a84b45aee39ec5590ff7adc2e1fe5e2733e14688
[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         if(wtx->status.depth)
416         {
417             return QIcon(":/icons/transaction1");
418         }
419         else
420         {
421             return QIcon(":/icons/transaction0");
422         }
423     case TransactionStatus::HaveConfirmations:
424         return QIcon(":/icons/transaction2");
425     }
426     return QColor(0,0,0);
427 }
428
429 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
430 {
431     if(!index.isValid())
432         return QVariant();
433     TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
434
435     if(role == Qt::DecorationRole)
436     {
437         if(index.column() == Status)
438         {
439             return formatTxDecoration(rec);
440         }
441     }
442     else if(role == Qt::DisplayRole)
443     {
444         /* Delegate to specific column handlers */
445         switch(index.column())
446         {
447         //case Status:
448         //    return formatTxStatus(rec);
449         case Date:
450             return formatTxDate(rec);
451         case Description:
452             return formatTxDescription(rec);
453         case Debit:
454             return formatTxDebit(rec);
455         case Credit:
456             return formatTxCredit(rec);
457         }
458     }
459     else if(role == Qt::EditRole)
460     {
461         /* Edit role is used for sorting so return the real values */
462         switch(index.column())
463         {
464         case Status:
465             return QString::fromStdString(rec->status.sortKey);
466         case Date:
467             return rec->time;
468         case Description:
469             return formatTxDescription(rec);
470         case Debit:
471             return rec->debit;
472         case Credit:
473             return rec->credit;
474         }
475     }
476     else if (role == Qt::ToolTipRole)
477     {
478         if(index.column() == Status)
479         {
480             return formatTxStatus(rec);
481         }
482     }
483     else if (role == Qt::TextAlignmentRole)
484     {
485         return column_alignments[index.column()];
486     }
487     else if (role == Qt::ForegroundRole)
488     {
489         /* Non-confirmed transactions are grey */
490         if(rec->status.confirmed)
491         {
492             return QColor(0, 0, 0);
493         }
494         else
495         {
496             return QColor(128, 128, 128);
497         }
498     }
499     else if (role == TypeRole)
500     {
501         /* Role for filtering tabs by type */
502         switch(rec->type)
503         {
504         case TransactionRecord::RecvWithAddress:
505         case TransactionRecord::RecvFromIP:
506             return TransactionTableModel::Received;
507         case TransactionRecord::SendToAddress:
508         case TransactionRecord::SendToIP:
509         case TransactionRecord::SendToSelf:
510             return TransactionTableModel::Sent;
511         default:
512             return TransactionTableModel::Other;
513         }
514     }
515     else if (role == LongDescriptionRole)
516     {
517         return priv->describe(rec);
518     }
519     return QVariant();
520 }
521
522 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
523 {
524     if(orientation == Qt::Horizontal)
525     {
526         if(role == Qt::DisplayRole)
527         {
528             return columns[section];
529         }
530         else if (role == Qt::TextAlignmentRole)
531         {
532             return column_alignments[section];
533         } else if (role == Qt::ToolTipRole)
534         {
535             switch(section)
536             {
537             case Status:
538                 return tr("Transaction status. Hover over this field to show number of transactions.");
539             case Date:
540                 return tr("Date and time that the transaction was received.");
541             case Description:
542                 return tr("Short description of the transaction.");
543             case Debit:
544                 return tr("Amount removed from balance.");
545             case Credit:
546                 return tr("Amount added to balance.");
547             }
548         }
549     }
550     return QVariant();
551 }
552
553 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
554 {
555     return QAbstractTableModel::flags(index);
556 }
557
558 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
559 {
560     Q_UNUSED(parent);
561     TransactionRecord *data = priv->index(row);
562     if(data)
563     {
564         return createIndex(row, column, priv->index(row));
565     }
566     else
567     {
568         return QModelIndex();
569     }
570 }
571