Send: dialog redesign (automatically look up label for entered address)
[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)").arg(wtx->status.depth);
279         break;
280     case TransactionStatus::Unconfirmed:
281         status = tr("Unconfirmed (%1/%2)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
282         break;
283     case TransactionStatus::HaveConfirmations:
284         status = tr("Confirmed (%1)").arg(wtx->status.depth);
285         break;
286     }
287
288     return QVariant(status);
289 }
290
291 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
292 {
293     if(wtx->time)
294     {
295         return QVariant(GUIUtil::DateTimeStr(wtx->time));
296     }
297     else
298     {
299         return QVariant();
300     }
301 }
302
303 /* Look up address in address book, if found return
304      address[0:12]... (label)
305    otherwise just return address
306  */
307 QString TransactionTableModel::lookupAddress(const std::string &address) const
308 {
309     QString label = walletModel->labelForAddress(QString::fromStdString(address));
310     QString description;
311     if(label.isEmpty())
312     {
313         description = QString::fromStdString(address);
314     }
315     else
316     {
317         description = label + QString(" (") + QString::fromStdString(address.substr(0,12)) + QString("...)");
318     }
319     return description;
320 }
321
322 QVariant TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
323 {
324     QString description;
325
326     switch(wtx->type)
327     {
328     case TransactionRecord::RecvWithAddress:
329         description = tr("Received with");
330         break;
331     case TransactionRecord::RecvFromIP:
332         description = tr("Received from IP");
333         break;
334     case TransactionRecord::SendToAddress:
335         description = tr("Sent to");
336         break;
337     case TransactionRecord::SendToIP:
338         description = tr("Sent to IP");
339         break;
340     case TransactionRecord::SendToSelf:
341         description = tr("Payment to yourself");
342         break;
343     case TransactionRecord::Generated:
344         description = tr("Generated");
345         break;
346     }
347     return QVariant(description);
348 }
349
350 QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx) const
351 {
352     QString description;
353
354     switch(wtx->type)
355     {
356     case TransactionRecord::RecvWithAddress:
357         description = lookupAddress(wtx->address);
358         break;
359     case TransactionRecord::RecvFromIP:
360         description = QString::fromStdString(wtx->address);
361         break;
362     case TransactionRecord::SendToAddress:
363         description = lookupAddress(wtx->address);
364         break;
365     case TransactionRecord::SendToIP:
366         description = QString::fromStdString(wtx->address);
367         break;
368     case TransactionRecord::SendToSelf:
369         description = QString();
370         break;
371     case TransactionRecord::Generated:
372         switch(wtx->status.maturity)
373         {
374         case TransactionStatus::Immature:
375             description = tr("(matures in %n more blocks)", "",
376                            wtx->status.matures_in);
377             break;
378         case TransactionStatus::Mature:
379             description = QString();
380             break;
381         case TransactionStatus::MaturesWarning:
382             description = tr("(Warning: This block was not received by any other nodes and will probably not be accepted!)");
383             break;
384         case TransactionStatus::NotAccepted:
385             description = tr("(not accepted)");
386             break;
387         }
388         break;
389     }
390     return QVariant(description);
391 }
392
393 QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx) const
394 {
395     QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit));
396     if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
397     {
398         str = QString("[") + str + QString("]");
399     }
400     return QVariant(str);
401 }
402
403 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
404 {
405     switch(wtx->status.status)
406     {
407     case TransactionStatus::OpenUntilBlock:
408     case TransactionStatus::OpenUntilDate:
409         return QColor(64,64,255);
410         break;
411     case TransactionStatus::Offline:
412         return QColor(192,192,192);
413     case TransactionStatus::Unconfirmed:
414         switch(wtx->status.depth)
415         {
416         case 0: return QIcon(":/icons/transaction_0");
417         case 1: return QIcon(":/icons/transaction_1");
418         case 2: return QIcon(":/icons/transaction_2");
419         case 3: return QIcon(":/icons/transaction_3");
420         case 4: return QIcon(":/icons/transaction_4");
421         default: return QIcon(":/icons/transaction_5");
422         };
423     case TransactionStatus::HaveConfirmations:
424         return QIcon(":/icons/transaction_confirmed");
425     }
426     return QColor(0,0,0);
427 }
428
429 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
430 {
431     if(!index.isValid())
432         return QVariant();
433     TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
434
435     if(role == Qt::DecorationRole)
436     {
437         if(index.column() == Status)
438         {
439             return formatTxDecoration(rec);
440         }
441     }
442     else if(role == Qt::DisplayRole)
443     {
444         // Delegate to specific column handlers
445         switch(index.column())
446         {
447         case Date:
448             return formatTxDate(rec);
449         case Type:
450             return formatTxType(rec);
451         case ToAddress:
452             return formatTxToAddress(rec);
453         case Amount:
454             return formatTxAmount(rec);
455         }
456     }
457     else if(role == Qt::EditRole)
458     {
459         // Edit role is used for sorting so return the real values
460         switch(index.column())
461         {
462         case Status:
463             return QString::fromStdString(rec->status.sortKey);
464         case Date:
465             return rec->time;
466         case Type:
467             return formatTxType(rec);
468         case ToAddress:
469             return formatTxToAddress(rec);
470         case Amount:
471             return rec->credit + rec->debit;
472         }
473     }
474     else if (role == Qt::ToolTipRole)
475     {
476         if(index.column() == Status)
477         {
478             return formatTxStatus(rec);
479         }
480     }
481     else if (role == Qt::TextAlignmentRole)
482     {
483         return column_alignments[index.column()];
484     }
485     else if (role == Qt::ForegroundRole)
486     {
487         /* Non-confirmed transactions are grey */
488         if(!rec->status.confirmed)
489         {
490             return QColor(128, 128, 128);
491         }
492         if(index.column() == Amount && (rec->credit+rec->debit) < 0)
493         {
494             return QColor(255, 0, 0);
495         }
496     }
497     else if (role == TypeRole)
498     {
499         return rec->type;
500     }
501     else if (role == DateRole)
502     {
503         return QDateTime::fromTime_t(static_cast<uint>(rec->time));
504     }
505     else if (role == LongDescriptionRole)
506     {
507         return priv->describe(rec);
508     }
509     else if (role == AddressRole)
510     {
511         return QString::fromStdString(rec->address);
512     }
513     else if (role == LabelRole)
514     {
515         return walletModel->labelForAddress(QString::fromStdString(rec->address));
516     }
517     else if (role == AbsoluteAmountRole)
518     {
519         return llabs(rec->credit + rec->debit);
520     }
521     return QVariant();
522 }
523
524 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
525 {
526     if(orientation == Qt::Horizontal)
527     {
528         if(role == Qt::DisplayRole)
529         {
530             return columns[section];
531         }
532         else if (role == Qt::TextAlignmentRole)
533         {
534             return column_alignments[section];
535         } else if (role == Qt::ToolTipRole)
536         {
537             switch(section)
538             {
539             case Status:
540                 return tr("Transaction status. Hover over this field to show number of confirmations.");
541             case Date:
542                 return tr("Date and time that the transaction was received.");
543             case Type:
544                 return tr("Type of transaction.");
545             case ToAddress:
546                 return tr("Destination address of transaction.");
547             case Amount:
548                 return tr("Amount removed from or added to balance.");
549             }
550         }
551     }
552     return QVariant();
553 }
554
555 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
556 {
557     return QAbstractTableModel::flags(index);
558 }
559
560 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
561 {
562     Q_UNUSED(parent);
563     TransactionRecord *data = priv->index(row);
564     if(data)
565     {
566         return createIndex(row, column, priv->index(row));
567     }
568     else
569     {
570         return QModelIndex();
571     }
572 }
573