PosTab
[novacoin.git] / src / qt / mintingtablemodel.cpp
diff --git a/src/qt/mintingtablemodel.cpp b/src/qt/mintingtablemodel.cpp
new file mode 100644 (file)
index 0000000..4973480
--- /dev/null
@@ -0,0 +1,450 @@
+#include "mintingtablemodel.h"
+#include "transactiontablemodel.h"
+#include "guiutil.h"
+#include "kernelrecord.h"
+#include "guiconstants.h"
+#include "transactiondesc.h"
+#include "walletmodel.h"
+#include "optionsmodel.h"
+#include "addresstablemodel.h"
+#include "bitcoinunits.h"
+#include "util.h"
+#include "kernel.h"
+
+#include "wallet.h"
+
+#include <QLocale>
+#include <QList>
+#include <QColor>
+#include <QTimer>
+#include <QIcon>
+#include <QDateTime>
+#include <QtAlgorithms>
+
+extern double GetDifficulty(const CBlockIndex* blockindex);
+
+static int column_alignments[] = {
+    Qt::AlignLeft|Qt::AlignVCenter,
+    Qt::AlignLeft|Qt::AlignVCenter,
+    Qt::AlignRight|Qt::AlignVCenter,
+    Qt::AlignRight|Qt::AlignVCenter,
+    Qt::AlignRight|Qt::AlignVCenter,
+    Qt::AlignRight|Qt::AlignVCenter
+};
+
+struct TxLessThan
+{
+    bool operator()(const KernelRecord &a, const KernelRecord &b) const
+    {
+        return a.hash < b.hash;
+    }
+    bool operator()(const KernelRecord &a, const uint256 &b) const
+    {
+        return a.hash < b;
+    }
+    bool operator()(const uint256 &a, const KernelRecord &b) const
+    {
+        return a < b.hash;
+    }
+};
+
+class MintingTablePriv
+{
+public:
+    MintingTablePriv(CWallet *wallet, MintingTableModel *parent):
+        wallet(wallet),
+        parent(parent)
+    {
+    }
+    CWallet *wallet;
+    MintingTableModel *parent;
+
+    QList<KernelRecord> cachedWallet;
+
+    void refreshWallet()
+    {
+#ifdef WALLET_UPDATE_DEBUG
+        qDebug() << "refreshWallet";
+#endif
+        cachedWallet.clear();
+        {
+            LOCK(wallet->cs_wallet);
+            for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
+            {
+                std::vector<KernelRecord> txList = KernelRecord::decomposeOutput(wallet, it->second);
+                BOOST_FOREACH(KernelRecord& kr, txList) {
+                    if(!kr.spent) {
+                        cachedWallet.append(kr);
+                    }
+                }
+
+            }
+        }
+    }
+
+    /* Update our model of the wallet incrementally, to synchronize our model of the wallet
+       with that of the core.
+
+       Call with list of hashes of transactions that were added, removed or changed.
+     */
+    void updateWallet(const QList<uint256> &updated)
+    {
+        // Walk through updated transactions, update model as needed.
+#ifdef WALLET_UPDATE_DEBUG
+        qDebug() << "updateWallet";
+#endif
+        // Sort update list, and iterate through it in reverse, so that model updates
+        //  can be emitted from end to beginning (so that earlier updates will not influence
+        // the indices of latter ones).
+        QList<uint256> updated_sorted = updated;
+        qSort(updated_sorted);
+
+        {
+            LOCK(wallet->cs_wallet);
+            for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
+            {
+                const uint256 &hash = updated_sorted.at(update_idx);
+                // Find transaction in wallet
+                std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
+                bool inWallet = mi != wallet->mapWallet.end();
+                // Find bounds of this transaction in model
+                QList<KernelRecord>::iterator lower = qLowerBound(
+                    cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
+                QList<KernelRecord>::iterator upper = qUpperBound(
+                    cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
+                int lowerIndex = (lower - cachedWallet.begin());
+                int upperIndex = (upper - cachedWallet.begin());
+
+                // Determine if transaction is in model already
+                bool inModel = false;
+                if(lower != upper)
+                {
+                    inModel = true;
+                }
+
+#ifdef WALLET_UPDATE_DEBUG
+                qDebug() << "  " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
+                        << lowerIndex << "-" << upperIndex;
+#endif
+
+                if(inWallet && !inModel)
+                {
+                    // Added -- insert at the right position
+                    std::vector<KernelRecord> toInsert =
+                            KernelRecord::decomposeOutput(wallet, mi->second);
+                    if(!toInsert.empty()) /* only if something to insert */
+                    {
+                        parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
+                        int insert_idx = lowerIndex;
+                        BOOST_FOREACH(const KernelRecord &rec, toInsert)
+                        {
+                            if(!rec.spent) {
+                                cachedWallet.insert(insert_idx, rec);
+                                insert_idx += 1;
+                            }
+                        }
+                        parent->endInsertRows();
+                    }
+                }
+                else if(!inWallet && inModel)
+                {
+                    // Removed -- remove entire transaction from table
+                    parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
+                    cachedWallet.erase(lower, upper);
+                    parent->endRemoveRows();
+                }
+                else if(inWallet && inModel)
+                {               
+                    // Updated -- nothing to do, status update will take care of this
+                }
+            }
+        }
+    }
+
+    int size()
+    {
+        return cachedWallet.size();
+    }
+
+    KernelRecord *index(int idx)
+    {
+        if(idx >= 0 && idx < cachedWallet.size())
+        {
+            KernelRecord *rec = &cachedWallet[idx];
+            return rec;
+        }
+        else
+        {
+            return 0;
+        }
+    }
+
+    QString describe(KernelRecord *rec)
+    {
+        {
+            LOCK(wallet->cs_wallet);
+            std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
+            if(mi != wallet->mapWallet.end())
+            {
+                return TransactionDesc::toHTML(wallet, mi->second);
+            }
+        }
+        return QString("");
+    }
+
+};
+
+
+MintingTableModel::MintingTableModel(CWallet *wallet, WalletModel *parent):
+        QAbstractTableModel(parent),
+        wallet(wallet),
+        walletModel(parent),
+        mintingInterval(10),
+        priv(new MintingTablePriv(wallet, this))
+{
+    columns << tr("Transaction") <<  tr("Address") << tr("Age") << tr("Balance") << tr("CoinDay") << tr("MintProbability");
+    priv->refreshWallet();
+
+    QTimer *timer = new QTimer(this);
+    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
+    timer->start(MODEL_UPDATE_DELAY);
+}
+
+MintingTableModel::~MintingTableModel()
+{
+    delete priv;
+}
+
+void MintingTableModel::update()
+{
+    QList<uint256> updated;
+
+    // Check if there are changes to wallet map
+    {
+        TRY_LOCK(wallet->cs_wallet, lockWallet);
+        if (lockWallet && !wallet->vMintingWalletUpdated.empty())
+        {
+            BOOST_FOREACH(uint256 hash, wallet->vMintingWalletUpdated)
+            {
+                updated.append(hash);
+            }
+            wallet->vMintingWalletUpdated.clear();
+        }
+    }
+
+    if(!updated.empty())
+    {
+        priv->updateWallet(updated);
+    }
+}
+
+int MintingTableModel::rowCount(const QModelIndex &parent) const
+{
+    Q_UNUSED(parent);
+    return priv->size();
+}
+
+int MintingTableModel::columnCount(const QModelIndex &parent) const
+{
+    Q_UNUSED(parent);
+    return columns.length();
+}
+
+QVariant MintingTableModel::data(const QModelIndex &index, int role) const
+{
+    if(!index.isValid())
+        return QVariant();
+    KernelRecord *rec = static_cast<KernelRecord*>(index.internalPointer());
+
+    switch(role)
+    {
+      case Qt::DisplayRole:
+        switch(index.column())
+        {
+        case Address:
+            return formatTxAddress(rec, false);
+        case TxHash:
+            return formatTxHash(rec);
+        case Age:
+            return formatTxAge(rec);
+        case Balance:
+            return formatTxBalance(rec);
+        case CoinDay:
+            return formatTxCoinDay(rec);
+        case MintProbability:
+            return formatDayToMint(rec);
+        }
+        break;
+      case Qt::TextAlignmentRole:
+        return column_alignments[index.column()];
+        break;
+      case Qt::ToolTipRole:
+        switch(index.column())
+        {
+        case MintProbability:
+            int interval = this->mintingInterval;
+            QString unit = tr("minutes");
+
+            int hours = interval / 60;
+            int days = hours  / 24;
+
+            if(hours > 1) {
+                interval = hours;
+                unit = tr("hours");
+            }
+            if(days > 1) {
+                interval = days;
+                unit = tr("days");
+            }
+
+            QString str = QString(tr("You have %1 chance to find a POS block if you mint %2 %3 at current difficulty."));
+            return str.arg(index.data().toString().toUtf8().constData()).arg(interval).arg(unit);
+        }
+        break;
+      case Qt::EditRole:
+        switch(index.column())
+        {
+        case Address:
+            return formatTxAddress(rec, false);
+        case TxHash:
+            return formatTxHash(rec);
+        case Age:
+            return rec->getAge();
+        case CoinDay:
+            return rec->coinAge;
+        case Balance:
+            return rec->nValue;
+        case MintProbability:
+            return getDayToMint(rec);
+        }
+        break;
+      case Qt::BackgroundColorRole:
+        int minAge = nStakeMinAge / 60 / 60 / 24;
+        int maxAge = nStakeMaxAge / 60 / 60 / 24;
+        if(rec->getAge() < minAge)
+        {
+            return COLOR_MINT_YOUNG;
+        }
+        else if (rec->getAge() >= minAge && rec->getAge() < (maxAge + minAge))
+        {
+            return COLOR_MINT_MATURE;
+        }
+        else
+        {
+            return COLOR_MINT_OLD;
+        }
+        break;
+
+    }
+    return QVariant();
+}
+
+void MintingTableModel::setMintingInterval(int interval)
+{
+    mintingInterval = interval;
+}
+
+QString MintingTableModel::lookupAddress(const std::string &address, bool tooltip) const
+{
+    QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
+    QString description;
+    if(!label.isEmpty())
+    {
+        description += label + QString(" ");
+    }
+    if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
+    {
+        description += QString("(") + QString::fromStdString(address) + QString(")");
+    }
+    return description;
+}
+
+double MintingTableModel::getDayToMint(KernelRecord *wtx) const
+{
+    const CBlockIndex *p = GetLastBlockIndex(pindexBest, true);
+    double difficulty = GetDifficulty(p);
+
+    double prob = wtx->getProbToMintWithinNMinutes(difficulty, mintingInterval);
+    prob = prob * 100;
+    return prob;
+}
+
+QString MintingTableModel::formatDayToMint(KernelRecord *wtx) const
+{
+    double prob = getDayToMint(wtx);
+    return QString::number(prob, 'f', 3) + "%";
+}
+
+QString MintingTableModel::formatTxAddress(const KernelRecord *wtx, bool tooltip) const
+{
+    return QString::fromStdString(wtx->address);
+}
+
+QString MintingTableModel::formatTxHash(const KernelRecord *wtx) const
+{
+    return QString::fromStdString(wtx->hash.ToString());
+}
+
+QString MintingTableModel::formatTxCoinDay(const KernelRecord *wtx) const
+{
+    return QString::number(wtx->coinAge);
+}
+
+QString MintingTableModel::formatTxAge(const KernelRecord *wtx) const
+{
+    int64 nAge = wtx->getAge();
+    return QString::number(nAge);
+}
+
+QString MintingTableModel::formatTxBalance(const KernelRecord *wtx) const
+{
+    return BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->nValue);
+}
+
+QVariant MintingTableModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    if(orientation == Qt::Horizontal)
+    {
+        if(role == Qt::DisplayRole)
+        {
+            return columns[section];
+        }
+        else if (role == Qt::TextAlignmentRole)
+        {
+            return column_alignments[section];
+        } else if (role == Qt::ToolTipRole)
+        {
+            switch(section)
+            {
+            case Address:
+                return tr("Destination address of the output.");
+            case TxHash:
+                return tr("Original transaction id.");
+            case Age:
+                return tr("Age of the transaction in days.");
+            case Balance:
+                return tr("Balance of the output.");
+            case CoinDay:
+                return tr("Coin age in the output.");
+            case MintProbability:
+                return tr("Chance to mint a block within given time interval.");
+            }
+        }
+    }
+    return QVariant();
+}
+
+QModelIndex MintingTableModel::index(int row, int column, const QModelIndex &parent) const
+{
+    Q_UNUSED(parent);
+    KernelRecord *data = priv->index(row);
+    if(data)
+    {
+        return createIndex(row, column, priv->index(row));
+    }
+    else
+    {
+        return QModelIndex();
+    }
+}
+