Browse Source

[Qt] Support for abandoned/abandoning transactions

0.13
Jonas Schnelli 9 years ago
parent
commit
8efed3bc93
No known key found for this signature in database
GPG Key ID: 29D4BCB6416F53EC
  1. 3
      src/Makefile.qt.include
  2. 1
      src/qt/bitcoin.qrc
  3. 2
      src/qt/guiconstants.h
  4. BIN
      src/qt/res/icons/transaction_abandoned.png
  5. 2
      src/qt/transactiondesc.cpp
  6. 2
      src/qt/transactionrecord.cpp
  7. 1
      src/qt/transactionrecord.h
  8. 10
      src/qt/transactiontablemodel.cpp
  9. 33
      src/qt/transactionview.cpp
  10. 2
      src/qt/transactionview.h
  11. 15
      src/qt/walletmodel.cpp
  12. 3
      src/qt/walletmodel.h

3
src/Makefile.qt.include

@ -268,7 +268,8 @@ RES_ICONS = \
qt/res/icons/tx_output.png \ qt/res/icons/tx_output.png \
qt/res/icons/tx_mined.png \ qt/res/icons/tx_mined.png \
qt/res/icons/warning.png \ qt/res/icons/warning.png \
qt/res/icons/verify.png qt/res/icons/verify.png \
qt/res/icons/transaction_abandoned.png
BITCOIN_QT_CPP = \ BITCOIN_QT_CPP = \
qt/bantablemodel.cpp \ qt/bantablemodel.cpp \

1
src/qt/bitcoin.qrc

@ -49,6 +49,7 @@
<file alias="fontbigger">res/icons/fontbigger.png</file> <file alias="fontbigger">res/icons/fontbigger.png</file>
<file alias="fontsmaller">res/icons/fontsmaller.png</file> <file alias="fontsmaller">res/icons/fontsmaller.png</file>
<file alias="prompticon">res/icons/chevron.png</file> <file alias="prompticon">res/icons/chevron.png</file>
<file alias="transaction_abandoned">res/icons/transaction_abandoned.png</file>
</qresource> </qresource>
<qresource prefix="/movies"> <qresource prefix="/movies">
<file alias="spinner-000">res/movies/spinner-000.png</file> <file alias="spinner-000">res/movies/spinner-000.png</file>

2
src/qt/guiconstants.h

@ -29,6 +29,8 @@ static const bool DEFAULT_SPLASHSCREEN = true;
#define COLOR_TX_STATUS_OPENUNTILDATE QColor(64, 64, 255) #define COLOR_TX_STATUS_OPENUNTILDATE QColor(64, 64, 255)
/* Transaction list -- TX status decoration - offline */ /* Transaction list -- TX status decoration - offline */
#define COLOR_TX_STATUS_OFFLINE QColor(192, 192, 192) #define COLOR_TX_STATUS_OFFLINE QColor(192, 192, 192)
/* Transaction list -- TX status decoration - danger, tx needs attention */
#define COLOR_TX_STATUS_DANGER QColor(200, 100, 100)
/* Transaction list -- TX status decoration - default color */ /* Transaction list -- TX status decoration - default color */
#define COLOR_BLACK QColor(0, 0, 0) #define COLOR_BLACK QColor(0, 0, 0)

BIN
src/qt/res/icons/transaction_abandoned.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

2
src/qt/transactiondesc.cpp

@ -39,7 +39,7 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx)
else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
return tr("%1/offline").arg(nDepth); return tr("%1/offline").arg(nDepth);
else if (nDepth == 0) else if (nDepth == 0)
return tr("0/unconfirmed, %1").arg((wtx.InMempool() ? tr("in memory pool") : tr("not in memory pool"))); return tr("0/unconfirmed, %1").arg((wtx.InMempool() ? tr("in memory pool") : tr("not in memory pool"))) + (wtx.isAbandoned() ? ", "+tr("abandoned") : "");
else if (nDepth < 6) else if (nDepth < 6)
return tr("%1/unconfirmed").arg(nDepth); return tr("%1/unconfirmed").arg(nDepth);
else else

2
src/qt/transactionrecord.cpp

@ -239,6 +239,8 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
else if (status.depth == 0) else if (status.depth == 0)
{ {
status.status = TransactionStatus::Unconfirmed; status.status = TransactionStatus::Unconfirmed;
if (wtx.isAbandoned())
status.status = TransactionStatus::Abandoned;
} }
else if (status.depth < RecommendedNumConfirmations) else if (status.depth < RecommendedNumConfirmations)
{ {

1
src/qt/transactionrecord.h

@ -33,6 +33,7 @@ public:
Unconfirmed, /**< Not yet mined into a block **/ Unconfirmed, /**< Not yet mined into a block **/
Confirming, /**< Confirmed, but waiting for the recommended number of confirmations **/ Confirming, /**< Confirmed, but waiting for the recommended number of confirmations **/
Conflicted, /**< Conflicts with other transaction or mempool **/ Conflicted, /**< Conflicts with other transaction or mempool **/
Abandoned, /**< Abandoned from the wallet **/
/// Generated (mined) transactions /// Generated (mined) transactions
Immature, /**< Mined but waiting for maturity */ Immature, /**< Mined but waiting for maturity */
MaturesWarning, /**< Transaction will likely not mature because no nodes have confirmed */ MaturesWarning, /**< Transaction will likely not mature because no nodes have confirmed */

10
src/qt/transactiontablemodel.cpp

@ -312,6 +312,9 @@ QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) cons
case TransactionStatus::Unconfirmed: case TransactionStatus::Unconfirmed:
status = tr("Unconfirmed"); status = tr("Unconfirmed");
break; break;
case TransactionStatus::Abandoned:
status = tr("Abandoned");
break;
case TransactionStatus::Confirming: case TransactionStatus::Confirming:
status = tr("Confirming (%1 of %2 recommended confirmations)").arg(wtx->status.depth).arg(TransactionRecord::RecommendedNumConfirmations); status = tr("Confirming (%1 of %2 recommended confirmations)").arg(wtx->status.depth).arg(TransactionRecord::RecommendedNumConfirmations);
break; break;
@ -468,6 +471,8 @@ QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx)
return COLOR_TX_STATUS_OFFLINE; return COLOR_TX_STATUS_OFFLINE;
case TransactionStatus::Unconfirmed: case TransactionStatus::Unconfirmed:
return QIcon(":/icons/transaction_0"); return QIcon(":/icons/transaction_0");
case TransactionStatus::Abandoned:
return QIcon(":/icons/transaction_abandoned");
case TransactionStatus::Confirming: case TransactionStatus::Confirming:
switch(wtx->status.depth) switch(wtx->status.depth)
{ {
@ -573,6 +578,11 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
case Qt::TextAlignmentRole: case Qt::TextAlignmentRole:
return column_alignments[index.column()]; return column_alignments[index.column()];
case Qt::ForegroundRole: case Qt::ForegroundRole:
// Use the "danger" color for abandoned transactions
if(rec->status.status == TransactionStatus::Abandoned)
{
return COLOR_TX_STATUS_DANGER;
}
// Non-confirmed (but not immature) as transactions are grey // Non-confirmed (but not immature) as transactions are grey
if(!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature) if(!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature)
{ {

33
src/qt/transactionview.cpp

@ -37,7 +37,7 @@
TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *parent) : TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *parent) :
QWidget(parent), model(0), transactionProxyModel(0), QWidget(parent), model(0), transactionProxyModel(0),
transactionView(0) transactionView(0), abandonAction(0)
{ {
// Build filter row // Build filter row
setContentsMargins(0,0,0,0); setContentsMargins(0,0,0,0);
@ -137,6 +137,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
transactionView = view; transactionView = view;
// Actions // Actions
abandonAction = new QAction(tr("Abandon transaction"), this);
QAction *copyAddressAction = new QAction(tr("Copy address"), this); QAction *copyAddressAction = new QAction(tr("Copy address"), this);
QAction *copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyLabelAction = new QAction(tr("Copy label"), this);
QAction *copyAmountAction = new QAction(tr("Copy amount"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
@ -153,8 +154,10 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
contextMenu->addAction(copyTxIDAction); contextMenu->addAction(copyTxIDAction);
contextMenu->addAction(copyTxHexAction); contextMenu->addAction(copyTxHexAction);
contextMenu->addAction(copyTxPlainText); contextMenu->addAction(copyTxPlainText);
contextMenu->addAction(editLabelAction);
contextMenu->addAction(showDetailsAction); contextMenu->addAction(showDetailsAction);
contextMenu->addSeparator();
contextMenu->addAction(abandonAction);
contextMenu->addAction(editLabelAction);
mapperThirdPartyTxUrls = new QSignalMapper(this); mapperThirdPartyTxUrls = new QSignalMapper(this);
@ -170,6 +173,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex))); connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex)));
connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint))); connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint)));
connect(abandonAction, SIGNAL(triggered()), this, SLOT(abandonTx()));
connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress()));
connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel()));
connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount()));
@ -360,12 +364,37 @@ void TransactionView::exportClicked()
void TransactionView::contextualMenu(const QPoint &point) void TransactionView::contextualMenu(const QPoint &point)
{ {
QModelIndex index = transactionView->indexAt(point); QModelIndex index = transactionView->indexAt(point);
QModelIndexList selection = transactionView->selectionModel()->selectedRows(0);
// check if transaction can be abandoned, disable context menu action in case it doesn't
uint256 hash;
hash.SetHex(selection.at(0).data(TransactionTableModel::TxHashRole).toString().toStdString());
abandonAction->setEnabled(model->transactionCanBeAbandoned(hash));
if(index.isValid()) if(index.isValid())
{ {
contextMenu->exec(QCursor::pos()); contextMenu->exec(QCursor::pos());
} }
} }
void TransactionView::abandonTx()
{
if(!transactionView || !transactionView->selectionModel())
return;
QModelIndexList selection = transactionView->selectionModel()->selectedRows(0);
// get the hash from the TxHashRole (QVariant / QString)
uint256 hash;
QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString();
hash.SetHex(hashQStr.toStdString());
// Abandon the wallet transaction over the walletModel
model->abandonTransaction(hash);
// Update the table
model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, false);
}
void TransactionView::copyAddress() void TransactionView::copyAddress()
{ {
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole); GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole);

2
src/qt/transactionview.h

@ -75,6 +75,7 @@ private:
QFrame *dateRangeWidget; QFrame *dateRangeWidget;
QDateTimeEdit *dateFrom; QDateTimeEdit *dateFrom;
QDateTimeEdit *dateTo; QDateTimeEdit *dateTo;
QAction *abandonAction;
QWidget *createDateRangeWidget(); QWidget *createDateRangeWidget();
@ -97,6 +98,7 @@ private Q_SLOTS:
void copyTxPlainText(); void copyTxPlainText();
void openThirdPartyTxUrl(QString url); void openThirdPartyTxUrl(QString url);
void updateWatchOnlyColumn(bool fHaveWatchOnly); void updateWatchOnlyColumn(bool fHaveWatchOnly);
void abandonTx();
Q_SIGNALS: Q_SIGNALS:
void doubleClicked(const QModelIndex&); void doubleClicked(const QModelIndex&);

15
src/qt/walletmodel.cpp

@ -668,3 +668,18 @@ bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t
else else
return wallet->AddDestData(dest, key, sRequest); return wallet->AddDestData(dest, key, sRequest);
} }
bool WalletModel::transactionCanBeAbandoned(uint256 hash) const
{
LOCK2(cs_main, wallet->cs_wallet);
const CWalletTx *wtx = wallet->GetWalletTx(hash);
if (!wtx || wtx->isAbandoned() || wtx->GetDepthInMainChain() > 0 || wtx->InMempool())
return false;
return true;
}
bool WalletModel::abandonTransaction(uint256 hash) const
{
LOCK2(cs_main, wallet->cs_wallet);
return wallet->AbandonTransaction(hash);
}

3
src/qt/walletmodel.h

@ -200,6 +200,9 @@ public:
void loadReceiveRequests(std::vector<std::string>& vReceiveRequests); void loadReceiveRequests(std::vector<std::string>& vReceiveRequests);
bool saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest); bool saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest);
bool transactionCanBeAbandoned(uint256 hash) const;
bool abandonTransaction(uint256 hash) const;
private: private:
CWallet *wallet; CWallet *wallet;
bool fHaveWatchOnly; bool fHaveWatchOnly;

Loading…
Cancel
Save