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