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