remove wallet updating debug output
[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 <QtAlgorithms>
14
15 const QString TransactionTableModel::Sent = "s";
16 const QString TransactionTableModel::Received = "r";
17 const QString TransactionTableModel::Other = "o";
18
19 /* Comparison operator for sort/binary search of model tx list */
20 struct TxLessThan
21 {
22     bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
23     {
24         return a.hash < b.hash;
25     }
26     bool operator()(const TransactionRecord &a, const uint256 &b) const
27     {
28         return a.hash < b;
29     }
30     bool operator()(const uint256 &a, const TransactionRecord &b) const
31     {
32         return a < b.hash;
33     }
34 };
35
36 /* Private implementation */
37 struct TransactionTablePriv
38 {
39     TransactionTablePriv(TransactionTableModel *parent):
40             parent(parent)
41     {
42     }
43
44     TransactionTableModel *parent;
45
46     /* Local cache of wallet.
47      * As it is in the same order as the CWallet, by definition
48      * this is sorted by sha256.
49      */
50     QList<TransactionRecord> cachedWallet;
51
52     void refreshWallet()
53     {
54 #ifdef WALLET_UPDATE_DEBUG
55         qDebug() << "refreshWallet";
56 #endif
57         /* Query entire wallet from core.
58          */
59         cachedWallet.clear();
60         CRITICAL_BLOCK(cs_mapWallet)
61         {
62             for(std::map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
63             {
64                 cachedWallet.append(TransactionRecord::decomposeTransaction(it->second));
65             }
66         }
67     }
68
69     /* Update our model of the wallet incrementally.
70        Call with list of hashes of transactions that were added, removed or changed.
71      */
72     void updateWallet(const QList<uint256> &updated)
73     {
74         /* Walk through updated transactions, update model as needed.
75          */
76 #ifdef WALLET_UPDATE_DEBUG
77         qDebug() << "updateWallet";
78 #endif
79         /* Sort update list, and iterate through it in reverse, so that model updates
80            can be emitted from end to beginning (so that earlier updates will not influence
81            the indices of latter ones).
82          */
83         QList<uint256> updated_sorted = updated;
84         qSort(updated_sorted);
85
86         CRITICAL_BLOCK(cs_mapWallet)
87         {
88             for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
89             {
90                 const uint256 &hash = updated_sorted.at(update_idx);
91                 /* Find transaction in wallet */
92                 std::map<uint256, CWalletTx>::iterator mi = mapWallet.find(hash);
93                 bool inWallet = mi != mapWallet.end();
94                 /* Find bounds of this transaction in model */
95                 QList<TransactionRecord>::iterator lower = qLowerBound(
96                     cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
97                 QList<TransactionRecord>::iterator upper = qUpperBound(
98                     cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
99                 int lowerIndex = (lower - cachedWallet.begin());
100                 int upperIndex = (upper - cachedWallet.begin());
101
102                 bool inModel = false;
103                 if(lower != upper)
104                 {
105                     inModel = true;
106                 }
107
108 #ifdef WALLET_UPDATE_DEBUG
109                 qDebug() << "  " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
110                         << lowerIndex << "-" << upperIndex;
111 #endif
112
113                 if(inWallet && !inModel)
114                 {
115                     /* Added */
116                     QList<TransactionRecord> toInsert =
117                             TransactionRecord::decomposeTransaction(mi->second);
118                     if(!toInsert.isEmpty()) /* only if something to insert */
119                     {
120                         parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
121                         int insert_idx = lowerIndex;
122                         foreach(const TransactionRecord &rec, toInsert)
123                         {
124                             cachedWallet.insert(insert_idx, rec);
125                             insert_idx += 1;
126                         }
127                         parent->endInsertRows();
128                     }
129                 }
130                 else if(!inWallet && inModel)
131                 {
132                     /* Removed */
133                     parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
134                     cachedWallet.erase(lower, upper);
135                     parent->endRemoveRows();
136                 }
137                 else if(inWallet && inModel)
138                 {
139                     /* Updated -- nothing to do, status update will take care of this */
140                 }
141             }
142         }
143     }
144
145     int size()
146     {
147         return cachedWallet.size();
148     }
149
150     TransactionRecord *index(int idx)
151     {
152         if(idx >= 0 && idx < cachedWallet.size())
153         {
154             TransactionRecord *rec = &cachedWallet[idx];
155
156             /* If a status update is needed (blocks came in since last check),
157                update the status of this transaction from the wallet. Otherwise,
158                simply re-use the cached status.
159              */
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          */
247         emit dataChanged(index(0, Status), index(priv->size()-1, Status));
248         emit dataChanged(index(0, Description), index(priv->size()-1, Description));
249     }
250 }
251
252 int TransactionTableModel::rowCount(const QModelIndex &parent) const
253 {
254     Q_UNUSED(parent);
255     return priv->size();
256 }
257
258 int TransactionTableModel::columnCount(const QModelIndex &parent) const
259 {
260     Q_UNUSED(parent);
261     return columns.length();
262 }
263
264 QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
265 {
266     QString status;
267
268     switch(wtx->status.status)
269     {
270     case TransactionStatus::OpenUntilBlock:
271         status = tr("Open for %n block(s)","",wtx->status.open_for);
272         break;
273     case TransactionStatus::OpenUntilDate:
274         status = tr("Open until ") + GUIUtil::DateTimeStr(wtx->status.open_for);
275         break;
276     case TransactionStatus::Offline:
277         status = tr("%1/offline").arg(wtx->status.depth);
278         break;
279     case TransactionStatus::Unconfirmed:
280         status = tr("%1/unconfirmed").arg(wtx->status.depth);
281         break;
282     case TransactionStatus::HaveConfirmations:
283         status = tr("%1 confirmations").arg(wtx->status.depth);
284         break;
285     }
286
287     return QVariant(status);
288 }
289
290 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
291 {
292     if(wtx->time)
293     {
294         return QVariant(GUIUtil::DateTimeStr(wtx->time));
295     }
296     else
297     {
298         return QVariant();
299     }
300 }
301
302 /* Look up address in address book, if found return
303      address[0:12]... (label)
304    otherwise just return address
305  */
306 std::string lookupAddress(const std::string &address)
307 {
308     std::string description;
309     CRITICAL_BLOCK(cs_mapAddressBook)
310     {
311         std::map<std::string, std::string>::iterator mi = mapAddressBook.find(address);
312         if (mi != mapAddressBook.end() && !(*mi).second.empty())
313         {
314             std::string label = (*mi).second;
315             description += address.substr(0,12) + "... ";
316             description += "(" + label + ")";
317         }
318         else
319         {
320             description += address;
321         }
322     }
323     return description;
324 }
325
326 QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx) const
327 {
328     QString description;
329
330     switch(wtx->type)
331     {
332     case TransactionRecord::RecvWithAddress:
333         description = tr("Received with: ") + QString::fromStdString(lookupAddress(wtx->address));
334         break;
335     case TransactionRecord::RecvFromIP:
336         description = tr("Received from IP: ") + QString::fromStdString(wtx->address);
337         break;
338     case TransactionRecord::SendToAddress:
339         description = tr("Sent to: ") + QString::fromStdString(lookupAddress(wtx->address));
340         break;
341     case TransactionRecord::SendToIP:
342         description = tr("Sent to IP: ") + QString::fromStdString(wtx->address);
343         break;
344     case TransactionRecord::SendToSelf:
345         description = tr("Payment to yourself");
346         break;
347     case TransactionRecord::Generated:
348         switch(wtx->status.maturity)
349         {
350         case TransactionStatus::Immature:
351             description = tr("Generated (matures in %n more blocks)", "",
352                            wtx->status.matures_in);
353             break;
354         case TransactionStatus::Mature:
355             description = tr("Generated");
356             break;
357         case TransactionStatus::MaturesWarning:
358             description = tr("Generated - Warning: This block was not received by any other nodes and will probably not be accepted!");
359             break;
360         case TransactionStatus::NotAccepted:
361             description = tr("Generated (not accepted)");
362             break;
363         }
364         break;
365     }
366     return QVariant(description);
367 }
368
369 QVariant TransactionTableModel::formatTxDebit(const TransactionRecord *wtx) const
370 {
371     if(wtx->debit)
372     {
373         QString str = QString::fromStdString(FormatMoney(wtx->debit));
374         if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
375         {
376             str = QString("[") + str + QString("]");
377         }
378         return QVariant(str);
379     }
380     else
381     {
382         return QVariant();
383     }
384 }
385
386 QVariant TransactionTableModel::formatTxCredit(const TransactionRecord *wtx) const
387 {
388     if(wtx->credit)
389     {
390         QString str = QString::fromStdString(FormatMoney(wtx->credit));
391         if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
392         {
393             str = QString("[") + str + QString("]");
394         }
395         return QVariant(str);
396     }
397     else
398     {
399         return QVariant();
400     }
401 }
402
403 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
404 {
405     if(!index.isValid())
406         return QVariant();
407     TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
408
409     if(role == Qt::DisplayRole)
410     {
411         /* Delegate to specific column handlers */
412         switch(index.column())
413         {
414         case Status:
415             return formatTxStatus(rec);
416         case Date:
417             return formatTxDate(rec);
418         case Description:
419             return formatTxDescription(rec);
420         case Debit:
421             return formatTxDebit(rec);
422         case Credit:
423             return formatTxCredit(rec);
424         }
425     }
426     else if(role == Qt::EditRole)
427     {
428         /* Edit role is used for sorting so return the real values */
429         switch(index.column())
430         {
431         case Status:
432             return QString::fromStdString(rec->status.sortKey);
433         case Date:
434             return rec->time;
435         case Description:
436             return formatTxDescription(rec);
437         case Debit:
438             return rec->debit;
439         case Credit:
440             return rec->credit;
441         }
442     }
443     else if (role == Qt::TextAlignmentRole)
444     {
445         return column_alignments[index.column()];
446     }
447     else if (role == Qt::ForegroundRole)
448     {
449         /* Non-confirmed transactions are grey */
450         if(rec->status.confirmed)
451         {
452             return QColor(0, 0, 0);
453         }
454         else
455         {
456             return QColor(128, 128, 128);
457         }
458     }
459     else if (role == TypeRole)
460     {
461         /* Role for filtering tabs by type */
462         switch(rec->type)
463         {
464         case TransactionRecord::RecvWithAddress:
465         case TransactionRecord::RecvFromIP:
466             return TransactionTableModel::Received;
467         case TransactionRecord::SendToAddress:
468         case TransactionRecord::SendToIP:
469         case TransactionRecord::SendToSelf:
470             return TransactionTableModel::Sent;
471         default:
472             return TransactionTableModel::Other;
473         }
474     }
475     else if (role == LongDescriptionRole)
476     {
477         return priv->describe(rec);
478     }
479     return QVariant();
480 }
481
482 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
483 {
484     if(orientation == Qt::Horizontal)
485     {
486         if(role == Qt::DisplayRole)
487         {
488             return columns[section];
489         }
490         else if (role == Qt::TextAlignmentRole)
491         {
492             return column_alignments[section];
493         }
494     }
495     return QVariant();
496 }
497
498 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
499 {
500     return QAbstractTableModel::flags(index);
501 }
502
503 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
504 {
505     Q_UNUSED(parent);
506     TransactionRecord *data = priv->index(row);
507     if(data)
508     {
509         return createIndex(row, column, priv->index(row));
510     }
511     else
512     {
513         return QModelIndex();
514     }
515 }
516