Export functionality for transaction list

This commit is contained in:
Wladimir J. van der Laan 2011-07-07 14:27:16 +02:00
parent d52a0f3bca
commit fbaee7a853
13 changed files with 230 additions and 13 deletions

View file

@ -77,7 +77,8 @@ HEADERS += src/qt/bitcoingui.h \
src/qt/transactionview.h \ src/qt/transactionview.h \
src/qt/walletmodel.h \ src/qt/walletmodel.h \
src/bitcoinrpc.h \ src/bitcoinrpc.h \
src/qt/overviewpage.h src/qt/overviewpage.h \
src/qt/csvmodelwriter.h
SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \
src/qt/transactiontablemodel.cpp \ src/qt/transactiontablemodel.cpp \
src/qt/addresstablemodel.cpp \ src/qt/addresstablemodel.cpp \
@ -114,7 +115,8 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \
src/qt/transactionview.cpp \ src/qt/transactionview.cpp \
src/qt/walletmodel.cpp \ src/qt/walletmodel.cpp \
src/bitcoinrpc.cpp \ src/bitcoinrpc.cpp \
src/qt/overviewpage.cpp src/qt/overviewpage.cpp \
src/qt/csvmodelwriter.cpp
RESOURCES += \ RESOURCES += \
src/qt/bitcoin.qrc src/qt/bitcoin.qrc

View file

@ -34,7 +34,8 @@ Designer: http://www.everaldo.com
Icon Pack: Crystal SVG Icon Pack: Crystal SVG
License: LGPL License: LGPL
Icon: src/qt/res/icons/receive.png, src/qt/res/icons/history.png Icon: src/qt/res/icons/receive.png, src/qt/res/icons/history.png,
src/qt/res/icons/export.png
Designer: Oxygen team Designer: Oxygen team
Icon Pack: Oxygen Icon Pack: Oxygen
License: Creative Common Attribution-ShareAlike 3.0 License or LGPL License: Creative Common Attribution-ShareAlike 3.0 License or LGPL

View file

@ -28,6 +28,7 @@
<file alias="editdelete">res/icons/editdelete.png</file> <file alias="editdelete">res/icons/editdelete.png</file>
<file alias="history">res/icons/history.png</file> <file alias="history">res/icons/history.png</file>
<file alias="overview">res/icons/overview.png</file> <file alias="overview">res/icons/overview.png</file>
<file alias="export">res/icons/export.png</file>
</qresource> </qresource>
<qresource prefix="/images"> <qresource prefix="/images">
<file alias="about">res/images/about.png</file> <file alias="about">res/images/about.png</file>

View file

@ -75,8 +75,12 @@ BitcoinGUI::BitcoinGUI(QWidget *parent):
toolbar->addAction(receiveCoins); toolbar->addAction(receiveCoins);
toolbar->addAction(addressbook); toolbar->addAction(addressbook);
overviewPage = new OverviewPage(); QToolBar *toolbar2 = addToolBar("Transactions toolbar");
toolbar2->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toolbar2->addAction(exportAction);
// Overview page
overviewPage = new OverviewPage();
QVBoxLayout *vbox = new QVBoxLayout(); QVBoxLayout *vbox = new QVBoxLayout();
transactionView = new TransactionView(this); transactionView = new TransactionView(this);
@ -146,8 +150,10 @@ void BitcoinGUI::createActions()
receiveCoins->setToolTip(tr("Show the list of addresses for receiving payments")); receiveCoins->setToolTip(tr("Show the list of addresses for receiving payments"));
options = new QAction(QIcon(":/icons/options"), tr("&Options..."), this); options = new QAction(QIcon(":/icons/options"), tr("&Options..."), this);
options->setToolTip(tr("Modify configuration options for bitcoin")); options->setToolTip(tr("Modify configuration options for bitcoin"));
openBitcoin = new QAction(QIcon(":/icons/bitcoin"), "Open &Bitcoin", this); openBitcoin = new QAction(QIcon(":/icons/bitcoin"), tr("Open &Bitcoin"), this);
openBitcoin->setToolTip(tr("Show the Bitcoin window")); openBitcoin->setToolTip(tr("Show the Bitcoin window"));
exportAction = new QAction(QIcon(":/icons/export"), tr("&Export..."), this);
exportAction->setToolTip(tr("Export data in current view to a file"));
connect(quit, SIGNAL(triggered()), qApp, SLOT(quit())); connect(quit, SIGNAL(triggered()), qApp, SLOT(quit()));
connect(sendCoins, SIGNAL(triggered()), this, SLOT(sendCoinsClicked())); connect(sendCoins, SIGNAL(triggered()), this, SLOT(sendCoinsClicked()));
@ -156,6 +162,7 @@ void BitcoinGUI::createActions()
connect(options, SIGNAL(triggered()), this, SLOT(optionsClicked())); connect(options, SIGNAL(triggered()), this, SLOT(optionsClicked()));
connect(about, SIGNAL(triggered()), this, SLOT(aboutClicked())); connect(about, SIGNAL(triggered()), this, SLOT(aboutClicked()));
connect(openBitcoin, SIGNAL(triggered()), this, SLOT(show())); connect(openBitcoin, SIGNAL(triggered()), this, SLOT(show()));
connect(exportAction, SIGNAL(triggered()), this, SLOT(exportClicked()));
} }
void BitcoinGUI::setClientModel(ClientModel *clientModel) void BitcoinGUI::setClientModel(ClientModel *clientModel)
@ -410,10 +417,20 @@ void BitcoinGUI::gotoOverviewTab()
{ {
overviewAction->setChecked(true); overviewAction->setChecked(true);
centralWidget->setCurrentWidget(overviewPage); centralWidget->setCurrentWidget(overviewPage);
exportAction->setEnabled(false);
} }
void BitcoinGUI::gotoHistoryTab() void BitcoinGUI::gotoHistoryTab()
{ {
historyAction->setChecked(true); historyAction->setChecked(true);
centralWidget->setCurrentWidget(transactionsPage); centralWidget->setCurrentWidget(transactionsPage);
exportAction->setEnabled(true);
} }
void BitcoinGUI::exportClicked()
{
// Redirect to the right view, as soon as export for other views
// (such as address book) is implemented.
transactionView->exportClicked();
}

View file

@ -63,6 +63,7 @@ private:
QAction *receiveCoins; QAction *receiveCoins;
QAction *options; QAction *options;
QAction *openBitcoin; QAction *openBitcoin;
QAction *exportAction;
QSystemTrayIcon *trayIcon; QSystemTrayIcon *trayIcon;
TransactionView *transactionView; TransactionView *transactionView;
@ -92,6 +93,7 @@ private slots:
void trayIconActivated(QSystemTrayIcon::ActivationReason reason); void trayIconActivated(QSystemTrayIcon::ActivationReason reason);
void transactionDetails(const QModelIndex& idx); void transactionDetails(const QModelIndex& idx);
void incomingTransaction(const QModelIndex & parent, int start, int end); void incomingTransaction(const QModelIndex & parent, int start, int end);
void exportClicked();
void gotoOverviewTab(); void gotoOverviewTab();
void gotoHistoryTab(); void gotoHistoryTab();

83
src/qt/csvmodelwriter.cpp Normal file
View file

@ -0,0 +1,83 @@
#include "csvmodelwriter.h"
#include <QAbstractItemModel>
#include <QFile>
#include <QTextStream>
CSVModelWriter::CSVModelWriter(const QString &filename, QObject *parent) :
QObject(parent),
filename(filename)
{
}
void CSVModelWriter::setModel(const QAbstractItemModel *model)
{
this->model = model;
}
void CSVModelWriter::addColumn(const QString &title, int column, int role)
{
Column col;
col.title = title;
col.column = column;
col.role = role;
columns.append(col);
}
static void writeValue(QTextStream &f, const QString &value)
{
// TODO: quoting if " or \n in string
f << "\"" << value << "\"";
}
static void writeSep(QTextStream &f)
{
f << ",";
}
static void writeNewline(QTextStream &f)
{
f << "\n";
}
bool CSVModelWriter::write()
{
QFile file(filename);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
return false;
QTextStream out(&file);
int numRows = model->rowCount();
// Header row
for(int i=0; i<columns.size(); ++i)
{
if(i!=0)
{
writeSep(out);
}
writeValue(out, columns[i].title);
}
writeNewline(out);
// Data rows
for(int j=0; j<numRows; ++j)
{
for(int i=0; i<columns.size(); ++i)
{
if(i!=0)
{
writeSep(out);
}
QVariant data = model->index(j, columns[i].column).data(columns[i].role);
writeValue(out, data.toString());
}
writeNewline(out);
}
file.close();
return file.error() == QFile::NoError;
}

43
src/qt/csvmodelwriter.h Normal file
View file

@ -0,0 +1,43 @@
#ifndef CSVMODELWRITER_H
#define CSVMODELWRITER_H
#include <QObject>
#include <QList>
QT_BEGIN_NAMESPACE
class QAbstractItemModel;
QT_END_NAMESPACE
// Export TableModel to CSV file
class CSVModelWriter : public QObject
{
Q_OBJECT
public:
explicit CSVModelWriter(const QString &filename, QObject *parent = 0);
void setModel(const QAbstractItemModel *model);
void addColumn(const QString &title, int column, int role=Qt::EditRole);
// Perform write operation
// Returns true on success, false otherwise
bool write();
private:
QString filename;
const QAbstractItemModel *model;
struct Column
{
QString title;
int column;
int role;
};
QList<Column> columns;
signals:
public slots:
};
#endif // CSVMODELWRITER_H

View file

@ -254,3 +254,9 @@ bool TransactionRecord::statusUpdateNeeded()
{ {
return status.cur_num_blocks != nBestHeight; return status.cur_num_blocks != nBestHeight;
} }
std::string TransactionRecord::getTxID()
{
return hash.ToString() + strprintf("-%03d", idx);
}

View file

@ -103,6 +103,9 @@ public:
/* Status: can change with block chain update */ /* Status: can change with block chain update */
TransactionStatus status; TransactionStatus status;
/* Return the unique identifier for this transaction (part) */
std::string getTxID();
/* Update status from wallet tx. /* Update status from wallet tx.
*/ */
void updateStatus(const CWalletTx &wtx); void updateStatus(const CWalletTx &wtx);

View file

@ -394,13 +394,16 @@ QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx)
return QVariant(description); return QVariant(description);
} }
QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx) const QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
{ {
QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit)); QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit));
if(showUnconfirmed)
{
if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature) if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
{ {
str = QString("[") + str + QString("]"); str = QString("[") + str + QString("]");
} }
}
return QVariant(str); return QVariant(str);
} }
@ -541,6 +544,18 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
{ {
return llabs(rec->credit + rec->debit); return llabs(rec->credit + rec->debit);
} }
else if (role == TxIDRole)
{
return QString::fromStdString(rec->getTxID());
}
else if (role == ConfirmedRole)
{
return rec->status.status == TransactionStatus::HaveConfirmations;
}
else if (role == FormattedAmountRole)
{
return formatTxAmount(rec, false);
}
return QVariant(); return QVariant();
} }

View file

@ -25,6 +25,7 @@ public:
} ColumnIndex; } ColumnIndex;
// Roles to get specific information from a transaction row // Roles to get specific information from a transaction row
// These are independent of column
enum { enum {
// Type of transaction // Type of transaction
TypeRole = Qt::UserRole, TypeRole = Qt::UserRole,
@ -36,8 +37,14 @@ public:
AddressRole, AddressRole,
// Label of address related to transaction // Label of address related to transaction
LabelRole, LabelRole,
// Absolute net amount of transaction // Absolute net amount of transaction, for filtering
AbsoluteAmountRole AbsoluteAmountRole,
// Unique identifier
TxIDRole,
// Is transaction confirmed?
ConfirmedRole,
// Formatted amount, without brackets when unconfirmed
FormattedAmountRole
} RoleIndex; } RoleIndex;
int rowCount(const QModelIndex &parent) const; int rowCount(const QModelIndex &parent) const;
@ -57,7 +64,7 @@ private:
QVariant formatTxDate(const TransactionRecord *wtx) const; QVariant formatTxDate(const TransactionRecord *wtx) const;
QVariant formatTxType(const TransactionRecord *wtx) const; QVariant formatTxType(const TransactionRecord *wtx) const;
QVariant formatTxToAddress(const TransactionRecord *wtx) const; QVariant formatTxToAddress(const TransactionRecord *wtx) const;
QVariant formatTxAmount(const TransactionRecord *wtx) const; QVariant formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed=true) const;
QVariant formatTxDecoration(const TransactionRecord *wtx) const; QVariant formatTxDecoration(const TransactionRecord *wtx) const;
private slots: private slots:

View file

@ -1,11 +1,10 @@
#include "transactionview.h" #include "transactionview.h"
// Temp includes for filtering prototype
// Move to TransactionFilterRow class
#include "transactionfilterproxy.h" #include "transactionfilterproxy.h"
#include "transactionrecord.h" #include "transactionrecord.h"
#include "transactiontablemodel.h" #include "transactiontablemodel.h"
#include "guiutil.h" #include "guiutil.h"
#include "csvmodelwriter.h"
#include <QScrollBar> #include <QScrollBar>
#include <QComboBox> #include <QComboBox>
@ -15,6 +14,9 @@
#include <QLineEdit> #include <QLineEdit>
#include <QTableView> #include <QTableView>
#include <QHeaderView> #include <QHeaderView>
#include <QPushButton>
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug> #include <QDebug>
@ -76,6 +78,7 @@ TransactionView::TransactionView(QWidget *parent) :
QVBoxLayout *vlayout = new QVBoxLayout(this); QVBoxLayout *vlayout = new QVBoxLayout(this);
vlayout->setContentsMargins(0,0,0,0); vlayout->setContentsMargins(0,0,0,0);
vlayout->setSpacing(0); vlayout->setSpacing(0);
//vlayout->addLayout(hlayout2);
QTableView *view = new QTableView(this); QTableView *view = new QTableView(this);
vlayout->addLayout(hlayout); vlayout->addLayout(hlayout);
@ -196,3 +199,36 @@ void TransactionView::changedAmount(const QString &amount)
transactionProxyModel->setMinAmount(0); transactionProxyModel->setMinAmount(0);
} }
} }
void TransactionView::exportClicked()
{
// CSV is currently the only supported format
QString filename = QFileDialog::getSaveFileName(
this,
tr("Export Transaction Data"),
QDir::currentPath(),
tr("Comma separated file (*.csv)"));
if(!filename.endsWith(".csv"))
{
filename += ".csv";
}
CSVModelWriter writer(filename);
// name, column, role
writer.setModel(transactionProxyModel);
writer.addColumn("Confirmed", 0, TransactionTableModel::ConfirmedRole);
writer.addColumn("Date", 0, TransactionTableModel::DateRole);
writer.addColumn("Type", TransactionTableModel::Type, Qt::EditRole);
writer.addColumn("Label", 0, TransactionTableModel::LabelRole);
writer.addColumn("Address", 0, TransactionTableModel::AddressRole);
writer.addColumn("Amount", 0, TransactionTableModel::FormattedAmountRole);
writer.addColumn("ID", 0, TransactionTableModel::TxIDRole);
if(!writer.write())
{
QMessageBox::critical(this, tr("Error exporting"), tr("Could not write to file %1.").arg(filename),
QMessageBox::Abort, QMessageBox::Abort);
}
}

View file

@ -50,6 +50,7 @@ public slots:
void chooseType(int idx); void chooseType(int idx);
void changedPrefix(const QString &prefix); void changedPrefix(const QString &prefix);
void changedAmount(const QString &amount); void changedAmount(const QString &amount);
void exportClicked();
}; };