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