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