Implement filter row instead of tabs, allows for more expressive filters

master
Wladimir J. van der Laan 2011-06-28 21:41:56 +02:00
parent 19a5975d5a
commit ceb6d4e11d
13 changed files with 444 additions and 110 deletions

View File

@ -72,7 +72,9 @@ HEADERS += src/qt/bitcoingui.h \
src/qt/transactiondescdialog.h \
src/qt/bitcoinamountfield.h \
src/wallet.h \
src/keystore.h
src/keystore.h \
src/qt/transactionfilterproxy.h \
src/qt/transactionview.h
SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \
src/qt/transactiontablemodel.cpp \
src/qt/addresstablemodel.cpp \
@ -105,7 +107,9 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \
src/qt/bitcoinstrings.cpp \
src/qt/bitcoinamountfield.cpp \
src/wallet.cpp \
src/keystore.cpp
src/keystore.cpp \
src/qt/transactionfilterproxy.cpp \
src/qt/transactionview.cpp
RESOURCES += \
src/qt/bitcoin.qrc

View File

@ -15,6 +15,7 @@
#include "optionsmodel.h"
#include "transactiondescdialog.h"
#include "addresstablemodel.h"
#include "transactionview.h"
#include "headers.h"
@ -29,12 +30,9 @@
#include <QToolBar>
#include <QStatusBar>
#include <QLabel>
#include <QTableView>
#include <QLineEdit>
#include <QPushButton>
#include <QHeaderView>
#include <QLocale>
#include <QSortFilterProxyModel>
#include <QClipboard>
#include <QMessageBox>
#include <QProgressBar>
@ -104,7 +102,9 @@ BitcoinGUI::BitcoinGUI(QWidget *parent):
vbox->addLayout(hbox_address);
vbox->addLayout(hbox_balance);
vbox->addWidget(createTabs());
transactionView = new TransactionView(this);
connect(transactionView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(transactionDetails(const QModelIndex&)));
vbox->addWidget(transactionView);
QWidget *centralwidget = new QWidget(this);
centralwidget->setLayout(vbox);
@ -198,7 +198,11 @@ void BitcoinGUI::setModel(ClientModel *model)
connect(model, SIGNAL(error(QString,QString)), this, SLOT(error(QString,QString)));
// Put transaction list in tabs
setTabsModel(model->getTransactionTableModel());
transactionView->setModel(model->getTransactionTableModel());
// Balloon popup for new transaction
connect(model->getTransactionTableModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
this, SLOT(incomingTransaction(const QModelIndex &, int, int)));
}
void BitcoinGUI::createTrayIcon()
@ -227,69 +231,6 @@ void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
}
}
QWidget *BitcoinGUI::createTabs()
{
QStringList tab_labels;
tab_labels << tr("All transactions")
<< tr("Sent/Received")
<< tr("Sent")
<< tr("Received");
QTabWidget *tabs = new QTabWidget(this);
for(int i = 0; i < tab_labels.size(); ++i)
{
QTableView *view = new QTableView(this);
tabs->addTab(view, tab_labels.at(i));
connect(view, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(transactionDetails(const QModelIndex&)));
transactionViews.append(view);
}
return tabs;
}
void BitcoinGUI::setTabsModel(QAbstractItemModel *transaction_model)
{
QStringList tab_filters;
tab_filters << "^."
<< "^["+TransactionTableModel::Sent+TransactionTableModel::Received+"]"
<< "^["+TransactionTableModel::Sent+"]"
<< "^["+TransactionTableModel::Received+"]";
for(int i = 0; i < transactionViews.size(); ++i)
{
QSortFilterProxyModel *proxy_model = new QSortFilterProxyModel(this);
proxy_model->setSourceModel(transaction_model);
proxy_model->setDynamicSortFilter(true);
proxy_model->setFilterRole(TransactionTableModel::TypeRole);
proxy_model->setFilterRegExp(QRegExp(tab_filters.at(i)));
proxy_model->setSortRole(Qt::EditRole);
QTableView *transaction_table = transactionViews.at(i);
transaction_table->setModel(proxy_model);
transaction_table->setAlternatingRowColors(true);
transaction_table->setSelectionBehavior(QAbstractItemView::SelectRows);
transaction_table->setSelectionMode(QAbstractItemView::ExtendedSelection);
transaction_table->setSortingEnabled(true);
transaction_table->sortByColumn(TransactionTableModel::Status, Qt::DescendingOrder);
transaction_table->verticalHeader()->hide();
transaction_table->horizontalHeader()->resizeSection(
TransactionTableModel::Status, 23);
transaction_table->horizontalHeader()->resizeSection(
TransactionTableModel::Date, 120);
transaction_table->horizontalHeader()->resizeSection(
TransactionTableModel::Type, 120);
transaction_table->horizontalHeader()->setResizeMode(
TransactionTableModel::ToAddress, QHeaderView::Stretch);
transaction_table->horizontalHeader()->resizeSection(
TransactionTableModel::Amount, 79);
}
connect(transaction_model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
this, SLOT(incomingTransaction(const QModelIndex &, int, int)));
}
void BitcoinGUI::sendcoinsClicked()
{
SendCoinsDialog dlg;

View File

@ -6,6 +6,7 @@
class TransactionTableModel;
class ClientModel;
class TransactionView;
QT_BEGIN_NAMESPACE
class QLabel;
@ -56,12 +57,11 @@ private:
QAction *openBitcoin;
QSystemTrayIcon *trayIcon;
QList<QTableView *> transactionViews;
TransactionView *transactionView;
void createActions();
QWidget *createTabs();
void createTrayIcon();
void setTabsModel(QAbstractItemModel *transaction_model);
public slots:
void setBalance(qint64 balance);

View File

@ -1,5 +1,6 @@
#include "guiutil.h"
#include "bitcoinaddressvalidator.h"
#include "util.h"
#include <QString>
#include <QDateTime>
@ -36,3 +37,12 @@ void GUIUtil::setupAmountWidget(QLineEdit *widget, QWidget *parent)
widget->setAlignment(Qt::AlignRight|Qt::AlignVCenter);
}
bool GUIUtil::parseMoney(const QString &amount, qint64 *val_out)
{
return ParseMoney(amount.toStdString(), *val_out);
}
QString GUIUtil::formatMoney(qint64 amount, bool plussign)
{
return QString::fromStdString(FormatMoney(amount, plussign));
}

View File

@ -14,12 +14,18 @@ class GUIUtil
public:
static QString DateTimeStr(qint64 nTime);
/* Render bitcoin addresses in monospace font */
// Render bitcoin addresses in monospace font
static QFont bitcoinAddressFont();
static void setupAddressWidget(QLineEdit *widget, QWidget *parent);
static void setupAmountWidget(QLineEdit *widget, QWidget *parent);
// Convenience wrapper around ParseMoney that takes QString
static bool parseMoney(const QString &amount, qint64 *val_out);
// Convenience wrapper around FormatMoney that returns QString
static QString formatMoney(qint64 amount, bool plussign=false);
};
#endif // GUIUTIL_H

View File

@ -12,9 +12,6 @@
#include <QLocale>
#include <QDebug>
#include "util.h"
#include "base58.h"
SendCoinsDialog::SendCoinsDialog(QWidget *parent, const QString &address) :
QDialog(parent),
ui(new Ui::SendCoinsDialog),
@ -49,7 +46,7 @@ void SendCoinsDialog::on_sendButton_clicked()
QString label;
qint64 payAmountParsed;
valid = ParseMoney(payAmount.toStdString(), payAmountParsed);
valid = GUIUtil::parseMoney(payAmount, &payAmountParsed);
if(!valid)
{
@ -88,7 +85,7 @@ void SendCoinsDialog::on_sendButton_clicked()
case ClientModel::AmountWithFeeExceedsBalance:
QMessageBox::warning(this, tr("Send Coins"),
tr("Total exceeds your balance when the %1 transaction fee is included").
arg(QString::fromStdString(FormatMoney(model->getOptionsModel()->getTransactionFee()))),
arg(GUIUtil::formatMoney(model->getOptionsModel()->getTransactionFee())),
QMessageBox::Ok, QMessageBox::Ok);
ui->payAmount->setFocus();
break;

View File

@ -0,0 +1,67 @@
#include "transactionfilterproxy.h"
#include "transactiontablemodel.h"
#include <QDateTime>
#include <QDebug>
// Earliest date that can be represented (far in the past)
const QDateTime TransactionFilterProxy::MIN_DATE = QDateTime::fromTime_t(0);
// Last date that can be represented (far in the future)
const QDateTime TransactionFilterProxy::MAX_DATE = QDateTime::fromTime_t(0xFFFFFFFF);
TransactionFilterProxy::TransactionFilterProxy(QObject *parent) :
QSortFilterProxyModel(parent),
dateFrom(MIN_DATE),
dateTo(MAX_DATE),
addrPrefix(),
typeFilter(ALL_TYPES),
minAmount(0)
{
}
bool TransactionFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
int type = index.data(TransactionTableModel::TypeRole).toInt();
QDateTime datetime = index.data(TransactionTableModel::DateRole).toDateTime();
QString address = index.data(TransactionTableModel::AddressRole).toString();
QString label = index.data(TransactionTableModel::LabelRole).toString();
qint64 amount = index.data(TransactionTableModel::AbsoluteAmountRole).toLongLong();
if(!(TYPE(type) & typeFilter))
return false;
if(datetime < dateFrom || datetime > dateTo)
return false;
if(!address.startsWith(addrPrefix) && !label.startsWith(addrPrefix))
return false;
if(amount < minAmount)
return false;
return true;
}
void TransactionFilterProxy::setDateRange(const QDateTime &from, const QDateTime &to)
{
this->dateFrom = from;
this->dateTo = to;
invalidateFilter();
}
void TransactionFilterProxy::setAddressPrefix(const QString &addrPrefix)
{
this->addrPrefix = addrPrefix;
invalidateFilter();
}
void TransactionFilterProxy::setTypeFilter(quint32 modes)
{
this->typeFilter = modes;
invalidateFilter();
}
void TransactionFilterProxy::setMinAmount(qint64 minimum)
{
this->minAmount = minimum;
invalidateFilter();
}

View File

@ -0,0 +1,45 @@
#ifndef TRANSACTIONFILTERPROXY_H
#define TRANSACTIONFILTERPROXY_H
#include <QSortFilterProxyModel>
#include <QDateTime>
// Filter transaction list according to pre-specified rules
class TransactionFilterProxy : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit TransactionFilterProxy(QObject *parent = 0);
// Earliest date that can be represented (far in the past)
static const QDateTime MIN_DATE;
// Last date that can be represented (far in the future)
static const QDateTime MAX_DATE;
// Type filter bit field (all types)
static const quint32 ALL_TYPES = 0xFFFFFFFF;
static quint32 TYPE(int type) { return 1<<type; }
void setDateRange(const QDateTime &from, const QDateTime &to);
void setAddressPrefix(const QString &addrPrefix);
// Type filter takes a bitfield created with TYPE() or ALL_TYPES
void setTypeFilter(quint32 modes);
void setMinAmount(qint64 minimum);
protected:
bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const;
private:
QDateTime dateFrom;
QDateTime dateTo;
QString addrPrefix;
quint32 typeFilter;
qint64 minAmount;
signals:
public slots:
};
#endif // TRANSACTIONFILTERPROXY_H

View File

@ -1,11 +1,12 @@
#ifndef TRANSACTIONRECORD_H
#define TRANSACTIONRECORD_H
#include "main.h"
#include "uint256.h"
#include <QList>
class CWallet;
class CWalletTx;
class TransactionStatus
{

View File

@ -12,6 +12,7 @@
#include <QColor>
#include <QTimer>
#include <QIcon>
#include <QDateTime>
#include <QtAlgorithms>
const QString TransactionTableModel::Sent = "s";
@ -301,26 +302,37 @@ QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
}
}
/* Look up label for address in address book, if not found return empty string.
This should really move to the wallet class.
*/
QString TransactionTableModel::labelForAddress(const std::string &address) const
{
CRITICAL_BLOCK(wallet->cs_mapAddressBook)
{
std::map<std::string, std::string>::iterator mi = wallet->mapAddressBook.find(address);
if (mi != wallet->mapAddressBook.end())
{
return QString::fromStdString(mi->second);
}
}
return QString();
}
/* Look up address in address book, if found return
address[0:12]... (label)
otherwise just return address
*/
std::string TransactionTableModel::lookupAddress(const std::string &address) const
QString TransactionTableModel::lookupAddress(const std::string &address) const
{
std::string description;
CRITICAL_BLOCK(wallet->cs_mapAddressBook)
QString label = labelForAddress(address);
QString description;
if(label.isEmpty())
{
std::map<std::string, std::string>::iterator mi = wallet->mapAddressBook.find(address);
if (mi != wallet->mapAddressBook.end() && !(*mi).second.empty())
{
std::string label = (*mi).second;
description += address.substr(0,12) + "... ";
description += "(" + label + ")";
}
else
{
description += address;
}
description = QString::fromStdString(address);
}
else
{
description = QString::fromStdString(address.substr(0,12)) + QString("... (") + label + QString(")");
}
return description;
}
@ -360,13 +372,13 @@ QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx)
switch(wtx->type)
{
case TransactionRecord::RecvWithAddress:
description = QString::fromStdString(lookupAddress(wtx->address));
description = lookupAddress(wtx->address);
break;
case TransactionRecord::RecvFromIP:
description = QString::fromStdString(wtx->address);
break;
case TransactionRecord::SendToAddress:
description = QString::fromStdString(lookupAddress(wtx->address));
description = lookupAddress(wtx->address);
break;
case TransactionRecord::SendToIP:
description = QString::fromStdString(wtx->address);
@ -502,24 +514,28 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
}
else if (role == TypeRole)
{
/* Role for filtering tabs by type */
switch(rec->type)
{
case TransactionRecord::RecvWithAddress:
case TransactionRecord::RecvFromIP:
return TransactionTableModel::Received;
case TransactionRecord::SendToAddress:
case TransactionRecord::SendToIP:
case TransactionRecord::SendToSelf:
return TransactionTableModel::Sent;
default:
return TransactionTableModel::Other;
}
return rec->type;
}
else if (role == DateRole)
{
return QDateTime::fromTime_t(static_cast<uint>(rec->time));
}
else if (role == LongDescriptionRole)
{
return priv->describe(rec);
}
else if (role == AddressRole)
{
return QString::fromStdString(rec->address);
}
else if (role == LabelRole)
{
return labelForAddress(rec->address);
}
else if (role == AbsoluteAmountRole)
{
return llabs(rec->credit + rec->debit);
}
return QVariant();
}

View File

@ -23,9 +23,20 @@ public:
Amount = 4
} ColumnIndex;
// Roles to get specific information from a transaction row
enum {
// Type of transaction
TypeRole = Qt::UserRole,
LongDescriptionRole = Qt::UserRole+1
// Date and time this transaction was created
DateRole,
// Long description (HTML format)
LongDescriptionRole,
// Address of transaction
AddressRole,
// Label of address related to transaction
LabelRole,
// Absolute net amount of transaction
AbsoluteAmountRole
} RoleIndex;
/* TypeRole values */
@ -44,7 +55,8 @@ private:
QStringList columns;
TransactionTablePriv *priv;
std::string lookupAddress(const std::string &address) const;
QString labelForAddress(const std::string &address) const;
QString lookupAddress(const std::string &address) const;
QVariant formatTxStatus(const TransactionRecord *wtx) const;
QVariant formatTxDate(const TransactionRecord *wtx) const;
QVariant formatTxType(const TransactionRecord *wtx) const;

182
src/qt/transactionview.cpp Normal file
View File

@ -0,0 +1,182 @@
#include "transactionview.h"
// Temp includes for filtering prototype
// Move to TransactionFilterRow class
#include "transactionfilterproxy.h"
#include "transactionrecord.h"
#include "transactiontablemodel.h"
#include "guiutil.h"
#include <QScrollBar>
#include <QComboBox>
#include <QDoubleValidator>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLineEdit>
#include <QTableView>
#include <QHeaderView>
#include <QDebug>
TransactionView::TransactionView(QWidget *parent) :
QWidget(parent), model(0), transactionProxyModel(0),
transactionView(0)
{
// Build filter row
QHBoxLayout *hlayout = new QHBoxLayout();
hlayout->setContentsMargins(QMargins(0,0,0,0));
hlayout->setSpacing(0);
hlayout->addSpacing(23);
dateWidget = new QComboBox(this);
dateWidget->setMaximumWidth(120);
dateWidget->setMinimumWidth(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("This year"), ThisYear);
dateWidget->addItem(tr("Range..."), Range);
hlayout->addWidget(dateWidget);
typeWidget = new QComboBox(this);
typeWidget->setMaximumWidth(120);
typeWidget->setMinimumWidth(120);
typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES);
typeWidget->addItem(tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) |
TransactionFilterProxy::TYPE(TransactionRecord::RecvFromIP));
typeWidget->addItem(tr("Sent to"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) |
TransactionFilterProxy::TYPE(TransactionRecord::SendToIP));
typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf));
typeWidget->addItem(tr("Generated"), TransactionFilterProxy::TYPE(TransactionRecord::Generated));
typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other));
hlayout->addWidget(typeWidget);
addressWidget = new QLineEdit(this);
addressWidget->setPlaceholderText("Enter address or label to search");
hlayout->addWidget(addressWidget);
amountWidget = new QLineEdit(this);
amountWidget->setPlaceholderText("Min amount");
amountWidget->setMaximumWidth(79);
amountWidget->setMinimumWidth(79);
amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this));
hlayout->addWidget(amountWidget);
QVBoxLayout *vlayout = new QVBoxLayout(this);
QTableView *view = new QTableView(this);
vlayout->addLayout(hlayout);
vlayout->addWidget(view);
vlayout->setSpacing(0);
int width = view->verticalScrollBar()->sizeHint().width();
// Cover scroll bar width with spacing
hlayout->addSpacing(width);
// Always show scroll bar
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
transactionView = view;
connect(dateWidget, SIGNAL(activated(int)), this, SLOT(chooseDate(int)));
connect(typeWidget, SIGNAL(activated(int)), this, SLOT(chooseType(int)));
connect(addressWidget, SIGNAL(textChanged(const QString&)), this, SLOT(changedPrefix(const QString&)));
connect(amountWidget, SIGNAL(textChanged(const QString&)), this, SLOT(changedAmount(const QString&)));
}
void TransactionView::setModel(TransactionTableModel *model)
{
this->model = model;
transactionProxyModel = new TransactionFilterProxy(this);
transactionProxyModel->setSourceModel(model);
transactionProxyModel->setDynamicSortFilter(true);
transactionProxyModel->setSortRole(Qt::EditRole);
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->horizontalHeader()->resizeSection(
TransactionTableModel::Status, 23);
transactionView->horizontalHeader()->resizeSection(
TransactionTableModel::Date, 120);
transactionView->horizontalHeader()->resizeSection(
TransactionTableModel::Type, 120);
transactionView->horizontalHeader()->setResizeMode(
TransactionTableModel::ToAddress, QHeaderView::Stretch);
transactionView->horizontalHeader()->resizeSection(
TransactionTableModel::Amount, 79);
}
void TransactionView::chooseDate(int idx)
{
QDate current = QDate::currentDate();
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 ThisYear:
transactionProxyModel->setDateRange(
QDateTime(QDate(current.year(), 1, 1)),
TransactionFilterProxy::MAX_DATE);
break;
case Range:
// TODO ask specific range
break;
}
}
void TransactionView::chooseType(int idx)
{
transactionProxyModel->setTypeFilter(
typeWidget->itemData(idx).toInt());
}
void TransactionView::changedPrefix(const QString &prefix)
{
transactionProxyModel->setAddressPrefix(prefix);
}
void TransactionView::changedAmount(const QString &amount)
{
qint64 amount_parsed;
if(GUIUtil::parseMoney(amount, &amount_parsed))
{
transactionProxyModel->setMinAmount(amount_parsed);
}
else
{
transactionProxyModel->setMinAmount(0);
}
}

53
src/qt/transactionview.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef TRANSACTIONVIEW_H
#define TRANSACTIONVIEW_H
#include <QWidget>
class TransactionTableModel;
class TransactionFilterProxy;
QT_BEGIN_NAMESPACE
class QTableView;
class QComboBox;
class QLineEdit;
QT_END_NAMESPACE
class TransactionView : public QWidget
{
Q_OBJECT
public:
explicit TransactionView(QWidget *parent = 0);
void setModel(TransactionTableModel *model);
enum DateEnum
{
All,
Today,
ThisWeek,
ThisMonth,
ThisYear,
Range
};
private:
TransactionTableModel *model;
TransactionFilterProxy *transactionProxyModel;
QTableView *transactionView;
QComboBox *dateWidget;
QComboBox *typeWidget;
QLineEdit *addressWidget;
QLineEdit *amountWidget;
signals:
public slots:
void chooseDate(int idx);
void chooseType(int idx);
void changedPrefix(const QString &prefix);
void changedAmount(const QString &amount);
};
#endif // TRANSACTIONVIEW_H