8c29273ff0
Re-submitting this pull request with a single commit. This patch introduces a GUIUtil class that is used when setting up the 2 tables we have so far on the Qt-GUI. In the past you could only resize the last column, which has BTC amounts from the right border of the column header, something that was rather unnatural. If a new table were ever to be added to the interface, fixing the last columns resizing behavior is rather simple. Just look at how we initialize here a TableViewLastColumnResizingFixer object when setting up the table header's behavior, and then how we override the resize event of the component (can be the table, or the dialog) and we invoke columnResizingFixer->stretchColumnWidth(columnIndex);
446 lines
15 KiB
C++
446 lines
15 KiB
C++
// Copyright (c) 2011-2013 The Bitcoin developers
|
|
// Distributed under the MIT/X11 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 "transactiondescdialog.h"
|
|
#include "transactionfilterproxy.h"
|
|
#include "transactionrecord.h"
|
|
#include "transactiontablemodel.h"
|
|
#include "walletmodel.h"
|
|
|
|
#include "ui_interface.h"
|
|
|
|
#include <QComboBox>
|
|
#include <QDateTimeEdit>
|
|
#include <QDoubleValidator>
|
|
#include <QHBoxLayout>
|
|
#include <QHeaderView>
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QMenu>
|
|
#include <QPoint>
|
|
#include <QScrollBar>
|
|
#include <QTableView>
|
|
#include <QVBoxLayout>
|
|
|
|
TransactionView::TransactionView(QWidget *parent) :
|
|
QWidget(parent), model(0), transactionProxyModel(0),
|
|
transactionView(0)
|
|
{
|
|
// Build filter row
|
|
setContentsMargins(0,0,0,0);
|
|
|
|
QHBoxLayout *hlayout = new QHBoxLayout();
|
|
hlayout->setContentsMargins(0,0,0,0);
|
|
#ifdef Q_OS_MAC
|
|
hlayout->setSpacing(5);
|
|
hlayout->addSpacing(26);
|
|
#else
|
|
hlayout->setSpacing(0);
|
|
hlayout->addSpacing(23);
|
|
#endif
|
|
|
|
dateWidget = new QComboBox(this);
|
|
#ifdef Q_OS_MAC
|
|
dateWidget->setFixedWidth(121);
|
|
#else
|
|
dateWidget->setFixedWidth(120);
|
|
#endif
|
|
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);
|
|
hlayout->addWidget(dateWidget);
|
|
|
|
typeWidget = new QComboBox(this);
|
|
#ifdef Q_OS_MAC
|
|
typeWidget->setFixedWidth(121);
|
|
#else
|
|
typeWidget->setFixedWidth(120);
|
|
#endif
|
|
|
|
typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_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("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf));
|
|
typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated));
|
|
typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other));
|
|
|
|
hlayout->addWidget(typeWidget);
|
|
|
|
addressWidget = new QLineEdit(this);
|
|
#if QT_VERSION >= 0x040700
|
|
addressWidget->setPlaceholderText(tr("Enter address or label to search"));
|
|
#endif
|
|
hlayout->addWidget(addressWidget);
|
|
|
|
amountWidget = new QLineEdit(this);
|
|
#if QT_VERSION >= 0x040700
|
|
amountWidget->setPlaceholderText(tr("Min amount"));
|
|
#endif
|
|
#ifdef Q_OS_MAC
|
|
amountWidget->setFixedWidth(97);
|
|
#else
|
|
amountWidget->setFixedWidth(100);
|
|
#endif
|
|
amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this));
|
|
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
|
|
#ifdef Q_OS_MAC
|
|
hlayout->addSpacing(width+2);
|
|
#else
|
|
hlayout->addSpacing(width);
|
|
#endif
|
|
// Always show scroll bar
|
|
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
view->setTabKeyNavigation(false);
|
|
view->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
transactionView = view;
|
|
|
|
// Actions
|
|
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 *editLabelAction = new QAction(tr("Edit label"), this);
|
|
QAction *showDetailsAction = new QAction(tr("Show transaction details"), this);
|
|
|
|
contextMenu = new QMenu();
|
|
contextMenu->addAction(copyAddressAction);
|
|
contextMenu->addAction(copyLabelAction);
|
|
contextMenu->addAction(copyAmountAction);
|
|
contextMenu->addAction(copyTxIDAction);
|
|
contextMenu->addAction(editLabelAction);
|
|
contextMenu->addAction(showDetailsAction);
|
|
|
|
// Connect actions
|
|
connect(dateWidget, SIGNAL(activated(int)), this, SLOT(chooseDate(int)));
|
|
connect(typeWidget, SIGNAL(activated(int)), this, SLOT(chooseType(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(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint)));
|
|
|
|
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(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel()));
|
|
connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails()));
|
|
}
|
|
|
|
void TransactionView::setModel(WalletModel *model)
|
|
{
|
|
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::Date, DATE_COLUMN_WIDTH);
|
|
transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH);
|
|
transactionView->setColumnWidth(TransactionTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH);
|
|
|
|
columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(transactionView, AMOUNT_MINIMUM_COLUMN_WIDTH, MINIMUM_COLUMN_WIDTH);
|
|
}
|
|
}
|
|
|
|
void TransactionView::chooseDate(int idx)
|
|
{
|
|
if(!transactionProxyModel)
|
|
return;
|
|
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, 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;
|
|
}
|
|
}
|
|
|
|
void TransactionView::chooseType(int idx)
|
|
{
|
|
if(!transactionProxyModel)
|
|
return;
|
|
transactionProxyModel->setTypeFilter(
|
|
typeWidget->itemData(idx).toInt());
|
|
}
|
|
|
|
void TransactionView::changedPrefix(const QString &prefix)
|
|
{
|
|
if(!transactionProxyModel)
|
|
return;
|
|
transactionProxyModel->setAddressPrefix(prefix);
|
|
}
|
|
|
|
void TransactionView::changedAmount(const QString &amount)
|
|
{
|
|
if(!transactionProxyModel)
|
|
return;
|
|
qint64 amount_parsed = 0;
|
|
if(BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amount, &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);
|
|
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(tr("Amount"), 0, TransactionTableModel::FormattedAmountRole);
|
|
writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole);
|
|
|
|
if(!writer.write()) {
|
|
emit message(tr("Exporting Failed"), tr("There was an error trying to save the transaction history to %1.").arg(filename),
|
|
CClientUIInterface::MSG_ERROR);
|
|
}
|
|
else {
|
|
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);
|
|
if(index.isValid())
|
|
{
|
|
contextMenu->exec(QCursor::pos());
|
|
}
|
|
}
|
|
|
|
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::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();
|
|
}
|
|
}
|
|
|
|
QWidget *TransactionView::createDateRangeWidget()
|
|
{
|
|
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->setDisplayFormat("dd/MM/yy");
|
|
dateFrom->setCalendarPopup(true);
|
|
dateFrom->setMinimumWidth(100);
|
|
dateFrom->setDate(QDate::currentDate().addDays(-7));
|
|
layout->addWidget(dateFrom);
|
|
layout->addWidget(new QLabel(tr("to")));
|
|
|
|
dateTo = new QDateTimeEdit(this);
|
|
dateTo->setDisplayFormat("dd/MM/yy");
|
|
dateTo->setCalendarPopup(true);
|
|
dateTo->setMinimumWidth(100);
|
|
dateTo->setDate(QDate::currentDate());
|
|
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;
|
|
transactionProxyModel->setDateRange(
|
|
QDateTime(dateFrom->date()),
|
|
QDateTime(dateTo->date()).addDays(1));
|
|
}
|
|
|
|
void TransactionView::focusTransaction(const QModelIndex &idx)
|
|
{
|
|
if(!transactionProxyModel)
|
|
return;
|
|
QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx);
|
|
transactionView->scrollTo(targetIdx);
|
|
transactionView->setCurrentIndex(targetIdx);
|
|
transactionView->setFocus();
|
|
}
|
|
|
|
//We override the virtual resizeEvent of the QWidget to adjust tablet's column sizes as the table's width is proportional to the dialog's.
|
|
void TransactionView::resizeEvent(QResizeEvent* event) {
|
|
QWidget::resizeEvent(event);
|
|
columnResizingFixer->stretchColumnWidth(TransactionTableModel::ToAddress);
|
|
}
|