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