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