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