Revert "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     }
239 }
240
241 int MintingTableModel::rowCount(const QModelIndex &parent) const
242 {
243     Q_UNUSED(parent);
244     return priv->size();
245 }
246
247 int MintingTableModel::columnCount(const QModelIndex &parent) const
248 {
249     Q_UNUSED(parent);
250     return columns.length();
251 }
252
253 QVariant MintingTableModel::data(const QModelIndex &index, int role) const
254 {
255     if(!index.isValid())
256         return QVariant();
257     KernelRecord *rec = static_cast<KernelRecord*>(index.internalPointer());
258
259     switch(role)
260     {
261       case Qt::DisplayRole:
262         switch(index.column())
263         {
264         case Address:
265             return formatTxAddress(rec, false);
266         case TxHash:
267             return formatTxHash(rec);
268         case Age:
269             return formatTxAge(rec);
270         case Balance:
271             return formatTxBalance(rec);
272         case CoinDay:
273             return formatTxCoinDay(rec);
274         case MintProbability:
275             return formatDayToMint(rec);
276         }
277         break;
278       case Qt::TextAlignmentRole:
279         return column_alignments[index.column()];
280         break;
281       case Qt::ToolTipRole:
282         switch(index.column())
283         {
284         case MintProbability:
285             int interval = this->mintingInterval;
286             QString unit = tr("minutes");
287
288             int hours = interval / 60;
289             int days = hours  / 24;
290
291             if(hours > 1) {
292                 interval = hours;
293                 unit = tr("hours");
294             }
295             if(days > 1) {
296                 interval = days;
297                 unit = tr("days");
298             }
299
300             QString str = QString(tr("You have %1 chance to find a POS block if you mint %2 %3 at current difficulty."));
301             return str.arg(index.data().toString().toUtf8().constData()).arg(interval).arg(unit);
302         }
303         break;
304       case Qt::EditRole:
305         switch(index.column())
306         {
307         case Address:
308             return formatTxAddress(rec, false);
309         case TxHash:
310             return formatTxHash(rec);
311         case Age:
312             return rec->getAge();
313         case CoinDay:
314             return rec->coinAge;
315         case Balance:
316             return rec->nValue;
317         case MintProbability:
318             return getDayToMint(rec);
319         }
320         break;
321       case Qt::BackgroundColorRole:
322         int minAge = nStakeMinAge / 60 / 60 / 24;
323         int maxAge = nStakeMaxAge / 60 / 60 / 24;
324         if(rec->getAge() < minAge)
325         {
326             return COLOR_MINT_YOUNG;
327         }
328         else if (rec->getAge() >= minAge && rec->getAge() < (maxAge + minAge))
329         {
330             return COLOR_MINT_MATURE;
331         }
332         else
333         {
334             return COLOR_MINT_OLD;
335         }
336         break;
337
338     }
339     return QVariant();
340 }
341
342 void MintingTableModel::setMintingInterval(int interval)
343 {
344     mintingInterval = interval;
345 }
346
347 QString MintingTableModel::lookupAddress(const std::string &address, bool tooltip) const
348 {
349     QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
350     QString description;
351     if(!label.isEmpty())
352     {
353         description += label + QString(" ");
354     }
355     if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
356     {
357         description += QString("(") + QString::fromStdString(address) + QString(")");
358     }
359     return description;
360 }
361
362 double MintingTableModel::getDayToMint(KernelRecord *wtx) const
363 {
364     const CBlockIndex *p = GetLastBlockIndex(pindexBest, true);
365     double difficulty = GetDifficulty(p);
366
367     double prob = wtx->getProbToMintWithinNMinutes(difficulty, mintingInterval);
368     prob = prob * 100;
369     return prob;
370 }
371
372 QString MintingTableModel::formatDayToMint(KernelRecord *wtx) const
373 {
374     double prob = getDayToMint(wtx);
375     return QString::number(prob, 'f', 3) + "%";
376 }
377
378 QString MintingTableModel::formatTxAddress(const KernelRecord *wtx, bool tooltip) const
379 {
380     return QString::fromStdString(wtx->address);
381 }
382
383 QString MintingTableModel::formatTxHash(const KernelRecord *wtx) const
384 {
385     return QString::fromStdString(wtx->hash.ToString());
386 }
387
388 QString MintingTableModel::formatTxCoinDay(const KernelRecord *wtx) const
389 {
390     return QString::number(wtx->coinAge);
391 }
392
393 QString MintingTableModel::formatTxAge(const KernelRecord *wtx) const
394 {
395     int64 nAge = wtx->getAge();
396     return QString::number(nAge);
397 }
398
399 QString MintingTableModel::formatTxBalance(const KernelRecord *wtx) const
400 {
401     return BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->nValue);
402 }
403
404 QVariant MintingTableModel::headerData(int section, Qt::Orientation orientation, int role) const
405 {
406     if(orientation == Qt::Horizontal)
407     {
408         if(role == Qt::DisplayRole)
409         {
410             return columns[section];
411         }
412         else if (role == Qt::TextAlignmentRole)
413         {
414             return column_alignments[section];
415         } else if (role == Qt::ToolTipRole)
416         {
417             switch(section)
418             {
419             case Address:
420                 return tr("Destination address of the output.");
421             case TxHash:
422                 return tr("Original transaction id.");
423             case Age:
424                 return tr("Age of the transaction in days.");
425             case Balance:
426                 return tr("Balance of the output.");
427             case CoinDay:
428                 return tr("Coin age in the output.");
429             case MintProbability:
430                 return tr("Chance to mint a block within given time interval.");
431             }
432         }
433     }
434     return QVariant();
435 }
436
437 QModelIndex MintingTableModel::index(int row, int column, const QModelIndex &parent) const
438 {
439     Q_UNUSED(parent);
440     KernelRecord *data = priv->index(row);
441     if(data)
442     {
443         return createIndex(row, column, priv->index(row));
444     }
445     else
446     {
447         return QModelIndex();
448     }
449 }
450