Handle "conflicted" transactions properly
[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     case TransactionStatus::Conflicted:
298         status = tr("Conflicted");
299         break;
300     }
301
302     if(wtx->type == TransactionRecord::Generated)
303     {
304         switch(wtx->status.maturity)
305         {
306         case TransactionStatus::Immature:
307             status += "\n" + tr("Mined balance will be available when it matures in %n more block(s)", "", wtx->status.matures_in);
308             break;
309         case TransactionStatus::Mature:
310             break;
311         case TransactionStatus::MaturesWarning:
312             status += "\n" + tr("This block was not received by any other nodes and will probably not be accepted!");
313             break;
314         case TransactionStatus::NotAccepted:
315             status += "\n" + tr("Generated but not accepted");
316             break;
317         }
318     }
319
320     return status;
321 }
322
323 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
324 {
325     if(wtx->time)
326     {
327         return GUIUtil::dateTimeStr(wtx->time);
328     }
329     else
330     {
331         return QString();
332     }
333 }
334
335 /* Look up address in address book, if found return label (address)
336    otherwise just return (address)
337  */
338 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
339 {
340     QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
341     QString description;
342     if(!label.isEmpty())
343     {
344         description += label + QString(" ");
345     }
346     if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
347     {
348         description += QString("(") + QString::fromStdString(address) + QString(")");
349     }
350     return description;
351 }
352
353 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
354 {
355     switch(wtx->type)
356     {
357     case TransactionRecord::RecvWithAddress:
358         return tr("Received with");
359     case TransactionRecord::RecvFromOther:
360         return tr("Received from");
361     case TransactionRecord::SendToAddress:
362     case TransactionRecord::SendToOther:
363         return tr("Sent to");
364     case TransactionRecord::SendToSelf:
365         return tr("Payment to yourself");
366     case TransactionRecord::Generated:
367         return tr("Mined");
368     default:
369         return QString();
370     }
371 }
372
373 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
374 {
375     switch(wtx->type)
376     {
377     case TransactionRecord::Generated:
378         return QIcon(":/icons/tx_mined");
379     case TransactionRecord::RecvWithAddress:
380     case TransactionRecord::RecvFromOther:
381         return QIcon(":/icons/tx_input");
382     case TransactionRecord::SendToAddress:
383     case TransactionRecord::SendToOther:
384         return QIcon(":/icons/tx_output");
385     default:
386         return QIcon(":/icons/tx_inout");
387     }
388     return QVariant();
389 }
390
391 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
392 {
393     switch(wtx->type)
394     {
395     case TransactionRecord::RecvFromOther:
396         return QString::fromStdString(wtx->address);
397     case TransactionRecord::RecvWithAddress:
398     case TransactionRecord::SendToAddress:
399     case TransactionRecord::Generated:
400         return lookupAddress(wtx->address, tooltip);
401     case TransactionRecord::SendToOther:
402         return QString::fromStdString(wtx->address);
403     case TransactionRecord::SendToSelf:
404     default:
405         return tr("(n/a)");
406     }
407 }
408
409 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
410 {
411     // Show addresses without label in a less visible color
412     switch(wtx->type)
413     {
414     case TransactionRecord::RecvWithAddress:
415     case TransactionRecord::SendToAddress:
416     case TransactionRecord::Generated:
417         {
418         QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
419         if(label.isEmpty())
420             return COLOR_BAREADDRESS;
421         } break;
422     case TransactionRecord::SendToSelf:
423         return COLOR_BAREADDRESS;
424     default:
425         break;
426     }
427     return QVariant();
428 }
429
430 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
431 {
432     QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
433     if(showUnconfirmed)
434     {
435         if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
436         {
437             str = QString("[") + str + QString("]");
438         }
439     }
440     return QString(str);
441 }
442
443 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
444 {
445     if(wtx->type == TransactionRecord::Generated)
446     {
447         switch(wtx->status.maturity)
448         {
449         case TransactionStatus::Immature: {
450             int total = wtx->status.depth + wtx->status.matures_in;
451             int part = (wtx->status.depth * 4 / total) + 1;
452             return QIcon(QString(":/icons/transaction_%1").arg(part));
453             }
454         case TransactionStatus::Mature:
455             return QIcon(":/icons/transaction_confirmed");
456         case TransactionStatus::MaturesWarning:
457         case TransactionStatus::NotAccepted:
458             return QIcon(":/icons/transaction_0");
459         }
460     }
461     else
462     {
463         switch(wtx->status.status)
464         {
465         case TransactionStatus::OpenUntilBlock:
466         case TransactionStatus::OpenUntilDate:
467             return QColor(64,64,255);
468             break;
469         case TransactionStatus::Offline:
470             return QColor(192,192,192);
471         case TransactionStatus::Unconfirmed:
472             switch(wtx->status.depth)
473             {
474             case 0: return QIcon(":/icons/transaction_0");
475             case 1: return QIcon(":/icons/transaction_1");
476             case 2: return QIcon(":/icons/transaction_2");
477             case 3: return QIcon(":/icons/transaction_3");
478             case 4: return QIcon(":/icons/transaction_4");
479             default: return QIcon(":/icons/transaction_5");
480             };
481         case TransactionStatus::HaveConfirmations:
482             return QIcon(":/icons/transaction_confirmed");
483         case TransactionStatus::Conflicted:
484             return QIcon(":/icons/transaction_conflicted");
485         }
486     }
487     return QColor(0,0,0);
488 }
489
490 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
491 {
492     QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
493     if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
494        rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
495     {
496         tooltip += QString(" ") + formatTxToAddress(rec, true);
497     }
498     return tooltip;
499 }
500
501 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
502 {
503     if(!index.isValid())
504         return QVariant();
505     TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
506
507     switch(role)
508     {
509     case Qt::DecorationRole:
510         switch(index.column())
511         {
512         case Status:
513             return txStatusDecoration(rec);
514         case ToAddress:
515             return txAddressDecoration(rec);
516         }
517         break;
518     case Qt::DisplayRole:
519         switch(index.column())
520         {
521         case Date:
522             return formatTxDate(rec);
523         case Type:
524             return formatTxType(rec);
525         case ToAddress:
526             return formatTxToAddress(rec, false);
527         case Amount:
528             return formatTxAmount(rec);
529         }
530         break;
531     case Qt::EditRole:
532         // Edit role is used for sorting, so return the unformatted values
533         switch(index.column())
534         {
535         case Status:
536             return QString::fromStdString(rec->status.sortKey);
537         case Date:
538             return rec->time;
539         case Type:
540             return formatTxType(rec);
541         case ToAddress:
542             return formatTxToAddress(rec, true);
543         case Amount:
544             return rec->credit + rec->debit;
545         }
546         break;
547     case Qt::ToolTipRole:
548         return formatTooltip(rec);
549     case Qt::TextAlignmentRole:
550         return column_alignments[index.column()];
551     case Qt::ForegroundRole:
552         // Non-confirmed transactions are grey
553         if(!rec->status.confirmed)
554         {
555             return COLOR_UNCONFIRMED;
556         }
557         if(index.column() == Amount && (rec->credit+rec->debit) < 0)
558         {
559             return COLOR_NEGATIVE;
560         }
561         if(index.column() == ToAddress)
562         {
563             return addressColor(rec);
564         }
565         break;
566     case TypeRole:
567         return rec->type;
568     case DateRole:
569         return QDateTime::fromTime_t(static_cast<uint>(rec->time));
570     case LongDescriptionRole:
571         return priv->describe(rec);
572     case AddressRole:
573         return QString::fromStdString(rec->address);
574     case LabelRole:
575         return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
576     case AmountRole:
577         return rec->credit + rec->debit;
578     case TxIDRole:
579         return QString::fromStdString(rec->getTxID());
580     case ConfirmedRole:
581         // Return True if transaction counts for balance
582         return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && rec->status.maturity != TransactionStatus::Mature);
583     case FormattedAmountRole:
584         return formatTxAmount(rec, false);
585     case StatusRole:
586         return rec->status.status;
587     }
588     return QVariant();
589 }
590
591 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
592 {
593     if(orientation == Qt::Horizontal)
594     {
595         if(role == Qt::DisplayRole)
596         {
597             return columns[section];
598         }
599         else if (role == Qt::TextAlignmentRole)
600         {
601             return column_alignments[section];
602         } else if (role == Qt::ToolTipRole)
603         {
604             switch(section)
605             {
606             case Status:
607                 return tr("Transaction status. Hover over this field to show number of confirmations.");
608             case Date:
609                 return tr("Date and time that the transaction was received.");
610             case Type:
611                 return tr("Type of transaction.");
612             case ToAddress:
613                 return tr("Destination address of transaction.");
614             case Amount:
615                 return tr("Amount removed from or added to balance.");
616             }
617         }
618     }
619     return QVariant();
620 }
621
622 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
623 {
624     Q_UNUSED(parent);
625     TransactionRecord *data = priv->index(row);
626     if(data)
627     {
628         return createIndex(row, column, priv->index(row));
629     }
630     else
631     {
632         return QModelIndex();
633     }
634 }
635
636 void TransactionTableModel::updateDisplayUnit()
637 {
638     // emit dataChanged to update Amount column with the current unit
639     emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));
640 }