Code cleanup
[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             // We need cast here to prevent ambigious conversion error
540             return static_cast<qlonglong>(rec->time);
541         case Type:
542             return formatTxType(rec);
543         case ToAddress:
544             return formatTxToAddress(rec, true);
545         case Amount:
546             // Same here
547             return static_cast<qlonglong>(rec->credit + rec->debit);
548         }
549         break;
550     case Qt::ToolTipRole:
551         return formatTooltip(rec);
552     case Qt::TextAlignmentRole:
553         return column_alignments[index.column()];
554     case Qt::ForegroundRole:
555         // Non-confirmed transactions are grey
556         if(!rec->status.confirmed)
557         {
558             return COLOR_UNCONFIRMED;
559         }
560         if(index.column() == Amount && (rec->credit+rec->debit) < 0)
561         {
562             return COLOR_NEGATIVE;
563         }
564         if(index.column() == ToAddress)
565         {
566             return addressColor(rec);
567         }
568         break;
569     case TypeRole:
570         return rec->type;
571     case DateRole:
572         return QDateTime::fromTime_t(static_cast<uint>(rec->time));
573     case LongDescriptionRole:
574         return priv->describe(rec);
575     case AddressRole:
576         return QString::fromStdString(rec->address);
577     case LabelRole:
578         return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
579     case AmountRole:
580         // And here
581         return static_cast<qlonglong>(rec->credit + rec->debit);
582     case TxIDRole:
583         return QString::fromStdString(rec->getTxID());
584     case TxHashRole:
585         return QString::fromStdString(rec->hash.ToString());
586     case ConfirmedRole:
587         // Return True if transaction counts for balance
588         return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && rec->status.maturity != TransactionStatus::Mature);
589     case FormattedAmountRole:
590         return formatTxAmount(rec, false);
591     }
592     return QVariant();
593 }
594
595 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
596 {
597     if(orientation == Qt::Horizontal)
598     {
599         if(role == Qt::DisplayRole)
600         {
601             return columns[section];
602         }
603         else if (role == Qt::TextAlignmentRole)
604         {
605             return column_alignments[section];
606         } else if (role == Qt::ToolTipRole)
607         {
608             switch(section)
609             {
610             case Status:
611                 return tr("Transaction status. Hover over this field to show number of confirmations.");
612             case Date:
613                 return tr("Date and time that the transaction was received.");
614             case Type:
615                 return tr("Type of transaction.");
616             case ToAddress:
617                 return tr("Destination address of transaction.");
618             case Amount:
619                 return tr("Amount removed from or added to balance.");
620             }
621         }
622     }
623     return QVariant();
624 }
625
626 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
627 {
628     Q_UNUSED(parent);
629     TransactionRecord *data = priv->index(row);
630     if(data)
631     {
632         return createIndex(row, column, priv->index(row));
633     }
634     else
635     {
636         return QModelIndex();
637     }
638 }
639
640 void TransactionTableModel::updateDisplayUnit()
641 {
642     // emit dataChanged to update Amount column with the current unit
643     emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));
644 }