dash/src/qt/transactionview.cpp
Alexander Block 91d99fcd3f Backport Bitcoin Qt/Gui changes up to 0.14.x part 3 (#1617)
* Merge #8996: Network activity toggle

19f46f1 Qt: New network_disabled icon (Luke Dashjr)
54cf997 RPC/Net: Use boolean consistently for networkactive, and remove from getinfo (Luke Dashjr)
b2b33d9 Overhaul network activity toggle (Jonas Schnelli)
32efa79 Qt: Add GUI feedback and control of network activity state. (Jon Lund Steffensen)
e38993b RPC: Add "togglenetwork" method to toggle network activity temporarily (Jon Lund Steffensen)
7c9a98a Allow network activity to be temporarily suspended. (Jon Lund Steffensen)

* Revert on-click behavior of network status icon to showing peers list

Stay with the way Dash handled clicking on the status icon

* Add theme support for network disabled icon

* Merge #8874: Multiple Selection for peer and ban tables

1077577 Fix auto-deselection of peers (Andrew Chow)
addfdeb Multiple Selection for peer and ban tables (Andrew Chow)

* Merge #9190: qt: Plug many memory leaks

ed998ea qt: Avoid OpenSSL certstore-related memory leak (Wladimir J. van der Laan)
5204598 qt: Avoid shutdownwindow-related memory leak (Wladimir J. van der Laan)
e4f126a qt: Avoid splash-screen related memory leak (Wladimir J. van der Laan)
693384e qt: Prevent thread/memory leak on exiting RPCConsole (Wladimir J. van der Laan)
47db075 qt: Plug many memory leaks (Wladimir J. van der Laan)

* Merge #9218: qt: Show progress overlay when clicking spinner icon

042f9fa qt: Show progress overlay when clicking spinner icon (Wladimir J. van der Laan)
827d9a3 qt: Replace NetworkToggleStatusBarControl with generic ClickableLabel (Wladimir J. van der Laan)

* Merge #9266: Bugfix: Qt/RPCConsole: Put column enum in the right places

df17fe0 Bugfix: Qt/RPCConsole: Put column enum in the right places (Luke Dashjr)

* Merge #9255: qt: layoutAboutToChange signal is called layoutAboutToBeChanged

f36349e qt: Remove on_toggleNetworkActiveButton_clicked from RPCConsole (Wladimir J. van der Laan)
297cc20 qt: layoutAboutToChange signal is called layoutAboutToBeChanged (Wladimir J. van der Laan)

* Use UniValue until bitcoin PR #8788 is backported

Network active toggle was already based on
"[RPC] Give RPC commands more information about the RPC request"
We need to use the old UniValue style until that one is backported

* Merge #8906: [qt] sync-overlay: Don't show progress twice

fafeec3 [qt] sync-overlay: Don't show progress twice (MarcoFalke)

* Merge #8985: Use pindexBestHeader instead of setBlockIndexCandidates for NotifyHeaderTip()

3154d6e [Qt] use NotifyHeaderTip's height and date for the progress update (Jonas Schnelli)
0a261b6 Use pindexBestHeader instead of setBlockIndexCandidates for NotifyHeaderTip() (Jonas Schnelli)

* Merge #9280: [Qt] Show ModalOverlay by pressing the progress bar, allow hiding

89a3723 [Qt] Show ModalOverlay by pressing the progress bar, disabled show() in sync mode (Jonas Schnelli)

* Merge #9461: [Qt] Improve progress display during headers-sync and peer-finding

40ec7c7 [Qt] Improve progress display during headers-sync and peer-finding (Jonas Schnelli)

* Merge #9588: qt: Use nPowTargetSpacing constant

fa4d478 qt: Use nPowTargetSpacing constant (MarcoFalke)

* Hide modal overlay forever when syncing has catched up

Don't allow to open it again by clicking on the progress bar and spinner
icon. Currently the overlay does not show meaningful information about
masternode sync and it gives the impression of being stuck after the block
chain sync is done.

* Don't include chainparams.h in sendcoinsdialog.cpp

This was just a remainder of a backported PR which meant to change some
calculation in this file which does not apply to Dash.

* Also check for fNetworkActive in ConnectNode

* Merge #9528: [qt] Rename formateNiceTimeOffset(qint64) to formatNiceTimeOffset(qint64)

988d300 [qt] Rename formateNiceTimeOffset(qint64) to formatNiceTimeOffset(qint64) (practicalswift)

* Merge #11237: qt: Fixing division by zero in time remaining

c8d38abd6 Refactor tipUpdate as per style guide (MeshCollider)
3b69a08c5 Fix division by zero in time remaining (MeshCollider)

Pull request description:

  Fixes https://github.com/bitcoin/bitcoin/issues/10291, https://github.com/bitcoin/bitcoin/issues/11265

  progressDelta may be 0 (or even negative according to 11265), this checks for that and prints unknown if it is, because we cannot calculate an estimate for the time remaining (would be infinite or negative).

Tree-SHA512: bc5708e5ed6e4670d008219558c5fbb25709bd99a32c98ec39bb74f94a0b7fa058f3d03389ccdd39e6723e6b5b48e34b13ceee7c051c2db631e51d8ec3e1d68c
2017-09-11 16:38:14 +03:00

650 lines
25 KiB
C++

// Copyright (c) 2011-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "transactionview.h"
#include "addresstablemodel.h"
#include "bitcoinunits.h"
#include "csvmodelwriter.h"
#include "editaddressdialog.h"
#include "guiutil.h"
#include "optionsmodel.h"
#include "platformstyle.h"
#include "transactiondescdialog.h"
#include "transactionfilterproxy.h"
#include "transactionrecord.h"
#include "transactiontablemodel.h"
#include "walletmodel.h"
#include "ui_interface.h"
#include <QComboBox>
#include <QDateTimeEdit>
#include <QDesktopServices>
#include <QDoubleValidator>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QPoint>
#include <QScrollBar>
#include <QSettings>
#include <QSignalMapper>
#include <QTableView>
#include <QUrl>
#include <QVBoxLayout>
/** Date format for persistence */
static const char* PERSISTENCE_DATE_FORMAT = "yyyy-MM-dd";
TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *parent) :
QWidget(parent), model(0), transactionProxyModel(0),
transactionView(0), abandonAction(0), columnResizingFixer(0)
{
QSettings settings;
// Build filter row
setContentsMargins(0,0,0,0);
QHBoxLayout *hlayout = new QHBoxLayout();
hlayout->setContentsMargins(0,0,0,0);
if (platformStyle->getUseExtraSpacing()) {
hlayout->setSpacing(0);
hlayout->addSpacing(6);
} else {
hlayout->setSpacing(1);
hlayout->addSpacing(5);
}
QString theme = GUIUtil::getThemeName();
watchOnlyWidget = new QComboBox(this);
watchOnlyWidget->setFixedWidth(24);
watchOnlyWidget->addItem("", TransactionFilterProxy::WatchOnlyFilter_All);
watchOnlyWidget->addItem(QIcon(":/icons/" + theme + "/eye_plus"), "", TransactionFilterProxy::WatchOnlyFilter_Yes);
watchOnlyWidget->addItem(QIcon(":/icons/" + theme + "/eye_minus"), "", TransactionFilterProxy::WatchOnlyFilter_No);
hlayout->addWidget(watchOnlyWidget);
dateWidget = new QComboBox(this);
if (platformStyle->getUseExtraSpacing()) {
dateWidget->setFixedWidth(120);
} else {
dateWidget->setFixedWidth(120);
}
dateWidget->addItem(tr("All"), All);
dateWidget->addItem(tr("Today"), Today);
dateWidget->addItem(tr("This week"), ThisWeek);
dateWidget->addItem(tr("This month"), ThisMonth);
dateWidget->addItem(tr("Last month"), LastMonth);
dateWidget->addItem(tr("This year"), ThisYear);
dateWidget->addItem(tr("Range..."), Range);
dateWidget->setCurrentIndex(settings.value("transactionDate").toInt());
hlayout->addWidget(dateWidget);
typeWidget = new QComboBox(this);
if (platformStyle->getUseExtraSpacing()) {
typeWidget->setFixedWidth(TYPE_COLUMN_WIDTH);
} else {
typeWidget->setFixedWidth(TYPE_COLUMN_WIDTH-1);
}
typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES);
typeWidget->addItem(tr("Most Common"), TransactionFilterProxy::COMMON_TYPES);
typeWidget->addItem(tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) |
TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther));
typeWidget->addItem(tr("Sent to"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) |
TransactionFilterProxy::TYPE(TransactionRecord::SendToOther));
typeWidget->addItem(tr("PrivateSend"), TransactionFilterProxy::TYPE(TransactionRecord::PrivateSend));
typeWidget->addItem(tr("PrivateSend Make Collateral Inputs"), TransactionFilterProxy::TYPE(TransactionRecord::PrivateSendMakeCollaterals));
typeWidget->addItem(tr("PrivateSend Create Denominations"), TransactionFilterProxy::TYPE(TransactionRecord::PrivateSendCreateDenominations));
typeWidget->addItem(tr("PrivateSend Denominate"), TransactionFilterProxy::TYPE(TransactionRecord::PrivateSendDenominate));
typeWidget->addItem(tr("PrivateSend Collateral Payment"), TransactionFilterProxy::TYPE(TransactionRecord::PrivateSendCollateralPayment));
typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf));
typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated));
typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other));
typeWidget->setCurrentIndex(settings.value("transactionType").toInt());
hlayout->addWidget(typeWidget);
addressWidget = new QLineEdit(this);
#if QT_VERSION >= 0x040700
addressWidget->setPlaceholderText(tr("Enter address or label to search"));
#endif
addressWidget->setObjectName("addressWidget");
hlayout->addWidget(addressWidget);
amountWidget = new QLineEdit(this);
#if QT_VERSION >= 0x040700
amountWidget->setPlaceholderText(tr("Min amount"));
#endif
if (platformStyle->getUseExtraSpacing()) {
amountWidget->setFixedWidth(118);
} else {
amountWidget->setFixedWidth(125);
}
amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this));
amountWidget->setObjectName("amountWidget");
hlayout->addWidget(amountWidget);
QVBoxLayout *vlayout = new QVBoxLayout(this);
vlayout->setContentsMargins(0,0,0,0);
vlayout->setSpacing(0);
QTableView *view = new QTableView(this);
vlayout->addLayout(hlayout);
vlayout->addWidget(createDateRangeWidget());
vlayout->addWidget(view);
vlayout->setSpacing(0);
int width = view->verticalScrollBar()->sizeHint().width();
// Cover scroll bar width with spacing
if (platformStyle->getUseExtraSpacing()) {
hlayout->addSpacing(width+2);
} else {
hlayout->addSpacing(width);
}
// Always show scroll bar
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
view->setTabKeyNavigation(false);
view->setContextMenuPolicy(Qt::CustomContextMenu);
view->installEventFilter(this);
transactionView = view;
// Actions
abandonAction = new QAction(tr("Abandon transaction"), this);
QAction *copyAddressAction = new QAction(tr("Copy address"), this);
QAction *copyLabelAction = new QAction(tr("Copy label"), this);
QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this);
QAction *copyTxHexAction = new QAction(tr("Copy raw transaction"), this);
QAction *copyTxPlainText = new QAction(tr("Copy full transaction details"), this);
QAction *editLabelAction = new QAction(tr("Edit label"), this);
QAction *showDetailsAction = new QAction(tr("Show transaction details"), this);
contextMenu = new QMenu(this);
contextMenu->addAction(copyAddressAction);
contextMenu->addAction(copyLabelAction);
contextMenu->addAction(copyAmountAction);
contextMenu->addAction(copyTxIDAction);
contextMenu->addAction(copyTxHexAction);
contextMenu->addAction(copyTxPlainText);
contextMenu->addAction(showDetailsAction);
contextMenu->addSeparator();
contextMenu->addAction(abandonAction);
contextMenu->addAction(editLabelAction);
mapperThirdPartyTxUrls = new QSignalMapper(this);
// Connect actions
connect(mapperThirdPartyTxUrls, SIGNAL(mapped(QString)), this, SLOT(openThirdPartyTxUrl(QString)));
connect(dateWidget, SIGNAL(activated(int)), this, SLOT(chooseDate(int)));
connect(typeWidget, SIGNAL(activated(int)), this, SLOT(chooseType(int)));
connect(watchOnlyWidget, SIGNAL(activated(int)), this, SLOT(chooseWatchonly(int)));
connect(addressWidget, SIGNAL(textChanged(QString)), this, SLOT(changedPrefix(QString)));
connect(amountWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAmount(QString)));
connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex)));
connect(view, SIGNAL(clicked(QModelIndex)), this, SLOT(computeSum()));
connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint)));
connect(abandonAction, SIGNAL(triggered()), this, SLOT(abandonTx()));
connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress()));
connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel()));
connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount()));
connect(copyTxIDAction, SIGNAL(triggered()), this, SLOT(copyTxID()));
connect(copyTxHexAction, SIGNAL(triggered()), this, SLOT(copyTxHex()));
connect(copyTxPlainText, SIGNAL(triggered()), this, SLOT(copyTxPlainText()));
connect(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel()));
connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails()));
}
void TransactionView::setModel(WalletModel *model)
{
QSettings settings;
this->model = model;
if(model)
{
transactionProxyModel = new TransactionFilterProxy(this);
transactionProxyModel->setSourceModel(model->getTransactionTableModel());
transactionProxyModel->setDynamicSortFilter(true);
transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
transactionProxyModel->setSortRole(Qt::EditRole);
transactionView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
transactionView->setModel(transactionProxyModel);
transactionView->setAlternatingRowColors(true);
transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
transactionView->setSortingEnabled(true);
transactionView->sortByColumn(TransactionTableModel::Status, Qt::DescendingOrder);
transactionView->verticalHeader()->hide();
transactionView->setColumnWidth(TransactionTableModel::Status, STATUS_COLUMN_WIDTH);
transactionView->setColumnWidth(TransactionTableModel::Watchonly, WATCHONLY_COLUMN_WIDTH);
transactionView->setColumnWidth(TransactionTableModel::Date, DATE_COLUMN_WIDTH);
transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH);
transactionView->setColumnWidth(TransactionTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH);
// Note: it's a good idea to connect this signal AFTER the model is set
connect(transactionView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(computeSum()));
columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(transactionView, AMOUNT_MINIMUM_COLUMN_WIDTH, MINIMUM_COLUMN_WIDTH, this);
if (model->getOptionsModel())
{
// Add third party transaction URLs to context menu
QStringList listUrls = model->getOptionsModel()->getThirdPartyTxUrls().split("|", QString::SkipEmptyParts);
for (int i = 0; i < listUrls.size(); ++i)
{
QString host = QUrl(listUrls[i].trimmed(), QUrl::StrictMode).host();
if (!host.isEmpty())
{
QAction *thirdPartyTxUrlAction = new QAction(host, this); // use host as menu item label
if (i == 0)
contextMenu->addSeparator();
contextMenu->addAction(thirdPartyTxUrlAction);
connect(thirdPartyTxUrlAction, SIGNAL(triggered()), mapperThirdPartyTxUrls, SLOT(map()));
mapperThirdPartyTxUrls->setMapping(thirdPartyTxUrlAction, listUrls[i].trimmed());
}
}
}
// show/hide column Watch-only
updateWatchOnlyColumn(model->haveWatchOnly());
// Watch-only signal
connect(model, SIGNAL(notifyWatchonlyChanged(bool)), this, SLOT(updateWatchOnlyColumn(bool)));
// Update transaction list with persisted settings
chooseType(settings.value("transactionType").toInt());
chooseDate(settings.value("transactionDate").toInt());
}
}
void TransactionView::chooseDate(int idx)
{
if(!transactionProxyModel)
return;
QSettings settings;
QDate current = QDate::currentDate();
dateRangeWidget->setVisible(false);
switch(dateWidget->itemData(idx).toInt())
{
case All:
transactionProxyModel->setDateRange(
TransactionFilterProxy::MIN_DATE,
TransactionFilterProxy::MAX_DATE);
break;
case Today:
transactionProxyModel->setDateRange(
QDateTime(current),
TransactionFilterProxy::MAX_DATE);
break;
case ThisWeek: {
// Find last Monday
QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1));
transactionProxyModel->setDateRange(
QDateTime(startOfWeek),
TransactionFilterProxy::MAX_DATE);
} break;
case ThisMonth:
transactionProxyModel->setDateRange(
QDateTime(QDate(current.year(), current.month(), 1)),
TransactionFilterProxy::MAX_DATE);
break;
case LastMonth:
transactionProxyModel->setDateRange(
QDateTime(QDate(current.year(), current.month(), 1).addMonths(-1)),
QDateTime(QDate(current.year(), current.month(), 1)));
break;
case ThisYear:
transactionProxyModel->setDateRange(
QDateTime(QDate(current.year(), 1, 1)),
TransactionFilterProxy::MAX_DATE);
break;
case Range:
dateRangeWidget->setVisible(true);
dateRangeChanged();
break;
}
// Persist new date settings
settings.setValue("transactionDate", idx);
if (dateWidget->itemData(idx).toInt() == Range){
settings.setValue("transactionDateFrom", dateFrom->date().toString(PERSISTENCE_DATE_FORMAT));
settings.setValue("transactionDateTo", dateTo->date().toString(PERSISTENCE_DATE_FORMAT));
}
}
void TransactionView::chooseType(int idx)
{
if(!transactionProxyModel)
return;
transactionProxyModel->setTypeFilter(
typeWidget->itemData(idx).toInt());
// Persist settings
QSettings settings;
settings.setValue("transactionType", idx);
}
void TransactionView::chooseWatchonly(int idx)
{
if(!transactionProxyModel)
return;
transactionProxyModel->setWatchOnlyFilter(
(TransactionFilterProxy::WatchOnlyFilter)watchOnlyWidget->itemData(idx).toInt());
}
void TransactionView::changedPrefix(const QString &prefix)
{
if(!transactionProxyModel)
return;
transactionProxyModel->setAddressPrefix(prefix);
}
void TransactionView::changedAmount(const QString &amount)
{
if(!transactionProxyModel)
return;
CAmount amount_parsed = 0;
// Replace "," by "." so BitcoinUnits::parse will not fail for users entering "," as decimal separator
QString newAmount = amount;
newAmount.replace(QString(","), QString("."));
if(BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), newAmount, &amount_parsed))
{
transactionProxyModel->setMinAmount(amount_parsed);
}
else
{
transactionProxyModel->setMinAmount(0);
}
}
void TransactionView::exportClicked()
{
// CSV is currently the only supported format
QString filename = GUIUtil::getSaveFileName(this,
tr("Export Transaction History"), QString(),
tr("Comma separated file (*.csv)"), NULL);
if (filename.isNull())
return;
CSVModelWriter writer(filename);
// name, column, role
writer.setModel(transactionProxyModel);
writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole);
if (model && model->haveWatchOnly())
writer.addColumn(tr("Watch-only"), TransactionTableModel::Watchonly);
writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole);
writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole);
writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole);
writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole);
writer.addColumn(BitcoinUnits::getAmountColumnTitle(model->getOptionsModel()->getDisplayUnit()), 0, TransactionTableModel::FormattedAmountRole);
writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole);
if(!writer.write()) {
Q_EMIT message(tr("Exporting Failed"), tr("There was an error trying to save the transaction history to %1.").arg(filename),
CClientUIInterface::MSG_ERROR);
}
else {
Q_EMIT message(tr("Exporting Successful"), tr("The transaction history was successfully saved to %1.").arg(filename),
CClientUIInterface::MSG_INFORMATION);
}
}
void TransactionView::contextualMenu(const QPoint &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())
{
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()
{
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole);
}
void TransactionView::copyLabel()
{
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::LabelRole);
}
void TransactionView::copyAmount()
{
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole);
}
void TransactionView::copyTxID()
{
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxIDRole);
}
void TransactionView::copyTxHex()
{
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxHexRole);
}
void TransactionView::copyTxPlainText()
{
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole);
}
void TransactionView::editLabel()
{
if(!transactionView->selectionModel() ||!model)
return;
QModelIndexList selection = transactionView->selectionModel()->selectedRows();
if(!selection.isEmpty())
{
AddressTableModel *addressBook = model->getAddressTableModel();
if(!addressBook)
return;
QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString();
if(address.isEmpty())
{
// If this transaction has no associated address, exit
return;
}
// Is address in address book? Address book can miss address when a transaction is
// sent from outside the UI.
int idx = addressBook->lookupAddress(address);
if(idx != -1)
{
// Edit sending / receiving address
QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex());
// Determine type of address, launch appropriate editor dialog type
QString type = modelIdx.data(AddressTableModel::TypeRole).toString();
EditAddressDialog dlg(
type == AddressTableModel::Receive
? EditAddressDialog::EditReceivingAddress
: EditAddressDialog::EditSendingAddress, this);
dlg.setModel(addressBook);
dlg.loadRow(idx);
dlg.exec();
}
else
{
// Add sending address
EditAddressDialog dlg(EditAddressDialog::NewSendingAddress,
this);
dlg.setModel(addressBook);
dlg.setAddress(address);
dlg.exec();
}
}
}
void TransactionView::showDetails()
{
if(!transactionView->selectionModel())
return;
QModelIndexList selection = transactionView->selectionModel()->selectedRows();
if(!selection.isEmpty())
{
TransactionDescDialog dlg(selection.at(0));
dlg.exec();
}
}
/** Compute sum of all selected transactions */
void TransactionView::computeSum()
{
qint64 amount = 0;
int nDisplayUnit = model->getOptionsModel()->getDisplayUnit();
if(!transactionView->selectionModel())
return;
QModelIndexList selection = transactionView->selectionModel()->selectedRows();
Q_FOREACH (QModelIndex index, selection){
amount += index.data(TransactionTableModel::AmountRole).toLongLong();
}
QString strAmount(BitcoinUnits::formatWithUnit(nDisplayUnit, amount, true, BitcoinUnits::separatorAlways));
if (amount < 0) strAmount = "<span style='color:red;'>" + strAmount + "</span>";
Q_EMIT trxAmount(strAmount);
}
void TransactionView::openThirdPartyTxUrl(QString url)
{
if(!transactionView || !transactionView->selectionModel())
return;
QModelIndexList selection = transactionView->selectionModel()->selectedRows(0);
if(!selection.isEmpty())
QDesktopServices::openUrl(QUrl::fromUserInput(url.replace("%s", selection.at(0).data(TransactionTableModel::TxHashRole).toString())));
}
QWidget *TransactionView::createDateRangeWidget()
{
// Create default dates in case nothing is persisted
QString defaultDateFrom = QDate::currentDate().toString(PERSISTENCE_DATE_FORMAT);
QString defaultDateTo = QDate::currentDate().addDays(1).toString(PERSISTENCE_DATE_FORMAT);
QSettings settings;
dateRangeWidget = new QFrame();
dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised);
dateRangeWidget->setContentsMargins(1,1,1,1);
QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget);
layout->setContentsMargins(0,0,0,0);
layout->addSpacing(23);
layout->addWidget(new QLabel(tr("Range:")));
dateFrom = new QDateTimeEdit(this);
dateFrom->setCalendarPopup(true);
dateFrom->setMinimumWidth(100);
// Load persisted FROM date
dateFrom->setDate(QDate::fromString(settings.value("transactionDateFrom", defaultDateFrom).toString(), PERSISTENCE_DATE_FORMAT));
layout->addWidget(dateFrom);
layout->addWidget(new QLabel(tr("to")));
dateTo = new QDateTimeEdit(this);
dateTo->setCalendarPopup(true);
dateTo->setMinimumWidth(100);
// Load persisted TO date
dateTo->setDate(QDate::fromString(settings.value("transactionDateTo", defaultDateTo).toString(), PERSISTENCE_DATE_FORMAT));
layout->addWidget(dateTo);
layout->addStretch();
// Hide by default
dateRangeWidget->setVisible(false);
// Notify on change
connect(dateFrom, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged()));
connect(dateTo, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged()));
return dateRangeWidget;
}
void TransactionView::dateRangeChanged()
{
if(!transactionProxyModel)
return;
// Persist new date range
QSettings settings;
settings.setValue("transactionDateFrom", dateFrom->date().toString(PERSISTENCE_DATE_FORMAT));
settings.setValue("transactionDateTo", dateTo->date().toString(PERSISTENCE_DATE_FORMAT));
transactionProxyModel->setDateRange(
QDateTime(dateFrom->date()),
QDateTime(dateTo->date()));
}
void TransactionView::focusTransaction(const QModelIndex &idx)
{
if(!transactionProxyModel)
return;
QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx);
transactionView->selectRow(targetIdx.row());
computeSum();
transactionView->scrollTo(targetIdx);
transactionView->setCurrentIndex(targetIdx);
transactionView->setFocus();
}
// We override the virtual resizeEvent of the QWidget to adjust tables column
// sizes as the tables width is proportional to the dialogs width.
void TransactionView::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
columnResizingFixer->stretchColumnWidth(TransactionTableModel::ToAddress);
}
// Need to override default Ctrl+C action for amount as default behaviour is just to copy DisplayRole text
bool TransactionView::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_C && ke->modifiers().testFlag(Qt::ControlModifier))
{
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole);
return true;
}
}
return QWidget::eventFilter(obj, event);
}
// show/hide column Watch-only
void TransactionView::updateWatchOnlyColumn(bool fHaveWatchOnly)
{
watchOnlyWidget->setVisible(true);
transactionView->setColumnHidden(TransactionTableModel::Watchonly, !fHaveWatchOnly);
}