fix bug "refresh minting tab"
[novacoin.git] / src / qt / mintingtablemodel.cpp
1 #include "mintingtablemodel.h"
2 #include "transactiontablemodel.h"
3 #include "guiutil.h"
4 #include "kernelrecord.h"
5 #include "guiconstants.h"
6 #include "transactiondesc.h"
7 #include "walletmodel.h"
8 #include "optionsmodel.h"
9 #include "addresstablemodel.h"
10 #include "bitcoinunits.h"
11 #include "util.h"
12 #include "kernel.h"
13
14 #include "wallet.h"
15
16 #include <QLocale>
17 #include <QList>
18 #include <QColor>
19 #include <QTimer>
20 #include <QIcon>
21 #include <QDateTime>
22 #include <QtAlgorithms>
23
24 extern double GetDifficulty(const CBlockIndex* blockindex);
25
26 static int column_alignments[] = {
27     Qt::AlignLeft|Qt::AlignVCenter,
28     Qt::AlignLeft|Qt::AlignVCenter,
29     Qt::AlignRight|Qt::AlignVCenter,
30     Qt::AlignRight|Qt::AlignVCenter,
31     Qt::AlignRight|Qt::AlignVCenter,
32     Qt::AlignRight|Qt::AlignVCenter
33 };
34
35 struct TxLessThan
36 {
37     bool operator()(const KernelRecord &a, const KernelRecord &b) const
38     {
39         return a.hash < b.hash;
40     }
41     bool operator()(const KernelRecord &a, const uint256 &b) const
42     {
43         return a.hash < b;
44     }
45     bool operator()(const uint256 &a, const KernelRecord &b) const
46     {
47         return a < b.hash;
48     }
49 };
50
51 class MintingTablePriv
52 {
53 public:
54     MintingTablePriv(CWallet *wallet, MintingTableModel *parent):
55         wallet(wallet),
56         parent(parent)
57     {
58     }
59     CWallet *wallet;
60     MintingTableModel *parent;
61
62     QList<KernelRecord> cachedWallet;
63
64     void refreshWallet()
65     {
66 #ifdef WALLET_UPDATE_DEBUG
67         qDebug() << "refreshWallet";
68 #endif
69         cachedWallet.clear();
70         {
71             LOCK(wallet->cs_wallet);
72             for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
73             {
74                 std::vector<KernelRecord> txList = KernelRecord::decomposeOutput(wallet, it->second);
75                 BOOST_FOREACH(KernelRecord& kr, txList) {
76                     if(!kr.spent) {
77                         cachedWallet.append(kr);
78                     }
79                 }
80
81             }
82         }
83     }
84
85     /* Update our model of the wallet incrementally, to synchronize our model of the wallet
86        with that of the core.
87
88        Call with list of hashes of transactions that were added, removed or changed.
89      */
90     void updateWallet(const QList<uint256> &updated)
91     {
92         // Walk through updated transactions, update model as needed.
93 #ifdef WALLET_UPDATE_DEBUG
94         qDebug() << "updateWallet";
95 #endif
96         // Sort update list, and iterate through it in reverse, so that model updates
97         //  can be emitted from end to beginning (so that earlier updates will not influence
98         // the indices of latter ones).
99         QList<uint256> updated_sorted = updated;
100         qSort(updated_sorted);
101
102         {
103             LOCK(wallet->cs_wallet);
104             for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
105             {
106                 const uint256 &hash = updated_sorted.at(update_idx);
107                 // Find transaction in wallet
108                 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
109                 bool inWallet = mi != wallet->mapWallet.end();
110                 // Find bounds of this transaction in model
111                 QList<KernelRecord>::iterator lower = qLowerBound(
112                     cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
113                 QList<KernelRecord>::iterator upper = qUpperBound(
114                     cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
115                 int lowerIndex = (lower - cachedWallet.begin());
116                 int upperIndex = (upper - cachedWallet.begin());
117
118                 // Determine if transaction is in model already
119                 bool inModel = false;
120                 if(lower != upper)
121                 {
122                     inModel = true;
123                 }
124
125 #ifdef WALLET_UPDATE_DEBUG
126                 qDebug() << "  " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
127                         << lowerIndex << "-" << upperIndex;
128 #endif
129
130                 if(inWallet && !inModel)
131                 {
132                     // Added -- insert at the right position
133                     std::vector<KernelRecord> toInsert =
134                             KernelRecord::decomposeOutput(wallet, mi->second);
135                     if(!toInsert.empty()) /* only if something to insert */
136                     {
137                         parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
138                         int insert_idx = lowerIndex;
139                         BOOST_FOREACH(const KernelRecord &rec, toInsert)
140                         {
141                             if(!rec.spent) {
142                                 cachedWallet.insert(insert_idx, rec);
143                                 insert_idx += 1;
144                             }
145                         }
146                         parent->endInsertRows();
147                     }
148                 }
149                 else if(!inWallet && inModel)
150                 {
151                     // Removed -- remove entire transaction from table
152                     parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
153                     cachedWallet.erase(lower, upper);
154                     parent->endRemoveRows();
155                 }
156                 else if(inWallet && inModel)
157                 {               
158                     // Updated -- nothing to do, status update will take care of this
159                 }
160             }
161         }
162     }
163
164     int size()
165     {
166         return cachedWallet.size();
167     }
168
169     KernelRecord *index(int idx)
170     {
171         if(idx >= 0 && idx < cachedWallet.size())
172         {
173             KernelRecord *rec = &cachedWallet[idx];
174             return rec;
175         }
176         else
177         {
178             return 0;
179         }
180     }
181
182     QString describe(KernelRecord *rec)
183     {
184         {
185             LOCK(wallet->cs_wallet);
186             std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
187             if(mi != wallet->mapWallet.end())
188             {
189                 return TransactionDesc::toHTML(wallet, mi->second);
190             }
191         }
192         return QString("");
193     }
194
195 };
196
197
198 MintingTableModel::MintingTableModel(CWallet *wallet, WalletModel *parent):
199         QAbstractTableModel(parent),
200         wallet(wallet),
201         walletModel(parent),
202         mintingInterval(10),
203         priv(new MintingTablePriv(wallet, this))
204 {
205     columns << tr("Transaction") <<  tr("Address") << tr("Age") << tr("Balance") << tr("CoinDay") << tr("MintProbability");
206     priv->refreshWallet();
207
208     QTimer *timer = new QTimer(this);
209     connect(timer, SIGNAL(timeout()), this, SLOT(update()));
210     timer->start(MODEL_UPDATE_DELAY);
211 }
212
213 MintingTableModel::~MintingTableModel()
214 {
215     delete priv;
216 }
217
218 void MintingTableModel::update()
219 {
220     QList<uint256> updated;
221
222     // Check if there are changes to wallet map
223     {
224         TRY_LOCK(wallet->cs_wallet, lockWallet);
225         if (lockWallet && !wallet->vMintingWalletUpdated.empty())
226         {
227             BOOST_FOREACH(uint256 hash, wallet->vMintingWalletUpdated)
228             {
229                 updated.append(hash);
230             }
231             wallet->vMintingWalletUpdated.clear();
232         }
233     }
234
235     if(!updated.empty())
236     {
237         priv->updateWallet(updated);
238         priv->refreshWallet();
239     }
240 }
241
242 int MintingTableModel::rowCount(const QModelIndex &parent) const
243 {
244     Q_UNUSED(parent);
245     return priv->size();
246 }
247
248 int MintingTableModel::columnCount(const QModelIndex &parent) const
249 {
250     Q_UNUSED(parent);
251     return columns.length();
252 }
253
254 QVariant MintingTableModel::data(const QModelIndex &index, int role) const
255 {
256     if(!index.isValid())
257         return QVariant();
258     KernelRecord *rec = static_cast<KernelRecord*>(index.internalPointer());
259
260     switch(role)
261     {
262       case Qt::DisplayRole:
263         switch(index.column())
264         {
265         case Address:
266             return formatTxAddress(rec, false);
267         case TxHash:
268             return formatTxHash(rec);
269         case Age:
270             return formatTxAge(rec);
271         case Balance:
272             return formatTxBalance(rec);
273         case CoinDay:
274             return formatTxCoinDay(rec);
275         case MintProbability:
276             return formatDayToMint(rec);
277         }
278         break;
279       case Qt::TextAlignmentRole:
280         return column_alignments[index.column()];
281         break;
282       case Qt::ToolTipRole:
283         switch(index.column())
284         {
285         case MintProbability:
286             int interval = this->mintingInterval;
287             QString unit = tr("minutes");
288
289             int hours = interval / 60;
290             int days = hours  / 24;
291
292             if(hours > 1) {
293                 interval = hours;
294                 unit = tr("hours");
295             }
296             if(days > 1) {
297                 interval = days;
298                 unit = tr("days");
299             }
300
301             QString str = QString(tr("You have %1 chance to find a POS block if you mint %2 %3 at current difficulty."));
302             return str.arg(index.data().toString().toUtf8().constData()).arg(interval).arg(unit);
303         }
304         break;
305       case Qt::EditRole:
306         switch(index.column())
307         {
308         case Address:
309             return formatTxAddress(rec, false);
310         case TxHash:
311             return formatTxHash(rec);
312         case Age:
313             return rec->getAge();
314         case CoinDay:
315             return rec->coinAge;
316         case Balance:
317             return rec->nValue;
318         case MintProbability:
319             return getDayToMint(rec);
320         }
321         break;
322       case Qt::BackgroundColorRole:
323         int minAge = nStakeMinAge / 60 / 60 / 24;
324         int maxAge = nStakeMaxAge / 60 / 60 / 24;
325         if(rec->getAge() < minAge)
326         {
327             return COLOR_MINT_YOUNG;
328         }
329         else if (rec->getAge() >= minAge && rec->getAge() < (maxAge + minAge))
330         {
331             return COLOR_MINT_MATURE;
332         }
333         else
334         {
335             return COLOR_MINT_OLD;
336         }
337         break;
338
339     }
340     return QVariant();
341 }
342
343 void MintingTableModel::setMintingInterval(int interval)
344 {
345     mintingInterval = interval;
346 }
347
348 QString MintingTableModel::lookupAddress(const std::string &address, bool tooltip) const
349 {
350     QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
351     QString description;
352     if(!label.isEmpty())
353     {
354         description += label + QString(" ");
355     }
356     if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
357     {
358         description += QString("(") + QString::fromStdString(address) + QString(")");
359     }
360     return description;
361 }
362
363 double MintingTableModel::getDayToMint(KernelRecord *wtx) const
364 {
365     const CBlockIndex *p = GetLastBlockIndex(pindexBest, true);
366     double difficulty = GetDifficulty(p);
367
368     double prob = wtx->getProbToMintWithinNMinutes(difficulty, mintingInterval);
369     prob = prob * 100;
370     return prob;
371 }
372
373 QString MintingTableModel::formatDayToMint(KernelRecord *wtx) const
374 {
375     double prob = getDayToMint(wtx);
376     return QString::number(prob, 'f', 3) + "%";
377 }
378
379 QString MintingTableModel::formatTxAddress(const KernelRecord *wtx, bool tooltip) const
380 {
381     return QString::fromStdString(wtx->address);
382 }
383
384 QString MintingTableModel::formatTxHash(const KernelRecord *wtx) const
385 {
386     return QString::fromStdString(wtx->hash.ToString());
387 }
388
389 QString MintingTableModel::formatTxCoinDay(const KernelRecord *wtx) const
390 {
391     return QString::number(wtx->coinAge);
392 }
393
394 QString MintingTableModel::formatTxAge(const KernelRecord *wtx) const
395 {
396     int64 nAge = wtx->getAge();
397     return QString::number(nAge);
398 }
399
400 QString MintingTableModel::formatTxBalance(const KernelRecord *wtx) const
401 {
402     return BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->nValue);
403 }
404
405 QVariant MintingTableModel::headerData(int section, Qt::Orientation orientation, int role) const
406 {
407     if(orientation == Qt::Horizontal)
408     {
409         if(role == Qt::DisplayRole)
410         {
411             return columns[section];
412         }
413         else if (role == Qt::TextAlignmentRole)
414         {
415             return column_alignments[section];
416         } else if (role == Qt::ToolTipRole)
417         {
418             switch(section)
419             {
420             case Address:
421                 return tr("Destination address of the output.");
422             case TxHash:
423                 return tr("Original transaction id.");
424             case Age:
425                 return tr("Age of the transaction in days.");
426             case Balance:
427                 return tr("Balance of the output.");
428             case CoinDay:
429                 return tr("Coin age in the output.");
430             case MintProbability:
431                 return tr("Chance to mint a block within given time interval.");
432             }
433         }
434     }
435     return QVariant();
436 }
437
438 QModelIndex MintingTableModel::index(int row, int column, const QModelIndex &parent) const
439 {
440     Q_UNUSED(parent);
441     KernelRecord *data = priv->index(row);
442     if(data)
443     {
444         return createIndex(row, column, priv->index(row));
445     }
446     else
447     {
448         return QModelIndex();
449     }
450 }
451