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