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