Export functionality for transaction list
This commit is contained in:
parent
d52a0f3bca
commit
fbaee7a853
13 changed files with 230 additions and 13 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
83
src/qt/csvmodelwriter.cpp
Normal 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
43
src/qt/csvmodelwriter.h
Normal 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
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -394,12 +394,15 @@ 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(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
|
if(showUnconfirmed)
|
||||||
{
|
{
|
||||||
str = QString("[") + str + QString("]");
|
if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
|
||||||
|
{
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue