diff --git a/bitcoin-qt.pro b/bitcoin-qt.pro
index a8705c36..77d70b7b 100644
--- a/bitcoin-qt.pro
+++ b/bitcoin-qt.pro
@@ -77,7 +77,8 @@ HEADERS += src/qt/bitcoingui.h \
src/qt/transactionview.h \
src/qt/walletmodel.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 \
src/qt/transactiontablemodel.cpp \
src/qt/addresstablemodel.cpp \
@@ -114,7 +115,8 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \
src/qt/transactionview.cpp \
src/qt/walletmodel.cpp \
src/bitcoinrpc.cpp \
- src/qt/overviewpage.cpp
+ src/qt/overviewpage.cpp \
+ src/qt/csvmodelwriter.cpp
RESOURCES += \
src/qt/bitcoin.qrc
diff --git a/doc/assets-attribution.txt b/doc/assets-attribution.txt
index d4eb8488..786427f2 100644
--- a/doc/assets-attribution.txt
+++ b/doc/assets-attribution.txt
@@ -34,7 +34,8 @@ Designer: http://www.everaldo.com
Icon Pack: Crystal SVG
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
Icon Pack: Oxygen
License: Creative Common Attribution-ShareAlike 3.0 License or LGPL
diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc
index 20ecd9f3..e64744cd 100644
--- a/src/qt/bitcoin.qrc
+++ b/src/qt/bitcoin.qrc
@@ -28,6 +28,7 @@
res/icons/editdelete.png
res/icons/history.png
res/icons/overview.png
+ res/icons/export.png
res/images/about.png
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index fbab51bd..34da1350 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -75,8 +75,12 @@ BitcoinGUI::BitcoinGUI(QWidget *parent):
toolbar->addAction(receiveCoins);
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();
transactionView = new TransactionView(this);
@@ -146,8 +150,10 @@ void BitcoinGUI::createActions()
receiveCoins->setToolTip(tr("Show the list of addresses for receiving payments"));
options = new QAction(QIcon(":/icons/options"), tr("&Options..."), this);
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"));
+ 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(sendCoins, SIGNAL(triggered()), this, SLOT(sendCoinsClicked()));
@@ -156,6 +162,7 @@ void BitcoinGUI::createActions()
connect(options, SIGNAL(triggered()), this, SLOT(optionsClicked()));
connect(about, SIGNAL(triggered()), this, SLOT(aboutClicked()));
connect(openBitcoin, SIGNAL(triggered()), this, SLOT(show()));
+ connect(exportAction, SIGNAL(triggered()), this, SLOT(exportClicked()));
}
void BitcoinGUI::setClientModel(ClientModel *clientModel)
@@ -410,10 +417,20 @@ void BitcoinGUI::gotoOverviewTab()
{
overviewAction->setChecked(true);
centralWidget->setCurrentWidget(overviewPage);
+ exportAction->setEnabled(false);
}
void BitcoinGUI::gotoHistoryTab()
{
historyAction->setChecked(true);
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();
}
+
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index e04bcf91..a5fcc8a8 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -63,6 +63,7 @@ private:
QAction *receiveCoins;
QAction *options;
QAction *openBitcoin;
+ QAction *exportAction;
QSystemTrayIcon *trayIcon;
TransactionView *transactionView;
@@ -92,6 +93,7 @@ private slots:
void trayIconActivated(QSystemTrayIcon::ActivationReason reason);
void transactionDetails(const QModelIndex& idx);
void incomingTransaction(const QModelIndex & parent, int start, int end);
+ void exportClicked();
void gotoOverviewTab();
void gotoHistoryTab();
diff --git a/src/qt/csvmodelwriter.cpp b/src/qt/csvmodelwriter.cpp
new file mode 100644
index 00000000..62c0b949
--- /dev/null
+++ b/src/qt/csvmodelwriter.cpp
@@ -0,0 +1,83 @@
+#include "csvmodelwriter.h"
+
+#include
+#include
+#include
+
+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; iindex(j, columns[i].column).data(columns[i].role);
+ writeValue(out, data.toString());
+ }
+ writeNewline(out);
+ }
+
+ file.close();
+
+ return file.error() == QFile::NoError;
+}
+
diff --git a/src/qt/csvmodelwriter.h b/src/qt/csvmodelwriter.h
new file mode 100644
index 00000000..7367f3a6
--- /dev/null
+++ b/src/qt/csvmodelwriter.h
@@ -0,0 +1,43 @@
+#ifndef CSVMODELWRITER_H
+#define CSVMODELWRITER_H
+
+#include
+#include
+
+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 columns;
+
+signals:
+
+public slots:
+
+};
+
+#endif // CSVMODELWRITER_H
diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp
index 864dffa9..1b527bc7 100644
--- a/src/qt/transactionrecord.cpp
+++ b/src/qt/transactionrecord.cpp
@@ -254,3 +254,9 @@ bool TransactionRecord::statusUpdateNeeded()
{
return status.cur_num_blocks != nBestHeight;
}
+
+std::string TransactionRecord::getTxID()
+{
+ return hash.ToString() + strprintf("-%03d", idx);
+}
+
diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h
index c196be2a..0050c878 100644
--- a/src/qt/transactionrecord.h
+++ b/src/qt/transactionrecord.h
@@ -103,6 +103,9 @@ public:
/* Status: can change with block chain update */
TransactionStatus status;
+ /* Return the unique identifier for this transaction (part) */
+ std::string getTxID();
+
/* Update status from wallet tx.
*/
void updateStatus(const CWalletTx &wtx);
diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp
index 70dbd0ff..52c9b0ce 100644
--- a/src/qt/transactiontablemodel.cpp
+++ b/src/qt/transactiontablemodel.cpp
@@ -394,12 +394,15 @@ QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx)
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));
- 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);
}
@@ -541,6 +544,18 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
{
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();
}
diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h
index f75f414d..85bfeebc 100644
--- a/src/qt/transactiontablemodel.h
+++ b/src/qt/transactiontablemodel.h
@@ -25,6 +25,7 @@ public:
} ColumnIndex;
// Roles to get specific information from a transaction row
+ // These are independent of column
enum {
// Type of transaction
TypeRole = Qt::UserRole,
@@ -36,8 +37,14 @@ public:
AddressRole,
// Label of address related to transaction
LabelRole,
- // Absolute net amount of transaction
- AbsoluteAmountRole
+ // Absolute net amount of transaction, for filtering
+ AbsoluteAmountRole,
+ // Unique identifier
+ TxIDRole,
+ // Is transaction confirmed?
+ ConfirmedRole,
+ // Formatted amount, without brackets when unconfirmed
+ FormattedAmountRole
} RoleIndex;
int rowCount(const QModelIndex &parent) const;
@@ -57,7 +64,7 @@ private:
QVariant formatTxDate(const TransactionRecord *wtx) const;
QVariant formatTxType(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;
private slots:
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index a3407e85..037dfbb3 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -1,11 +1,10 @@
#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 "csvmodelwriter.h"
#include
#include
@@ -15,6 +14,9 @@
#include
#include
#include
+#include
+#include
+#include
#include
@@ -76,6 +78,7 @@ TransactionView::TransactionView(QWidget *parent) :
QVBoxLayout *vlayout = new QVBoxLayout(this);
vlayout->setContentsMargins(0,0,0,0);
vlayout->setSpacing(0);
+ //vlayout->addLayout(hlayout2);
QTableView *view = new QTableView(this);
vlayout->addLayout(hlayout);
@@ -196,3 +199,36 @@ void TransactionView::changedAmount(const QString &amount)
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);
+ }
+}
+
diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h
index fe0f154b..25212c97 100644
--- a/src/qt/transactionview.h
+++ b/src/qt/transactionview.h
@@ -50,6 +50,7 @@ public slots:
void chooseType(int idx);
void changedPrefix(const QString &prefix);
void changedAmount(const QString &amount);
+ void exportClicked();
};