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