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