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