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