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