Export functionality for transaction list
authorWladimir J. van der Laan <laanwj@gmail.com>
Thu, 7 Jul 2011 12:27:16 +0000 (14:27 +0200)
committerWladimir J. van der Laan <laanwj@gmail.com>
Thu, 7 Jul 2011 12:27:16 +0000 (14:27 +0200)
13 files changed:
bitcoin-qt.pro
doc/assets-attribution.txt
src/qt/bitcoin.qrc
src/qt/bitcoingui.cpp
src/qt/bitcoingui.h
src/qt/csvmodelwriter.cpp [new file with mode: 0644]
src/qt/csvmodelwriter.h [new file with mode: 0644]
src/qt/transactionrecord.cpp
src/qt/transactionrecord.h
src/qt/transactiontablemodel.cpp
src/qt/transactiontablemodel.h
src/qt/transactionview.cpp
src/qt/transactionview.h

index a8705c3..77d70b7 100644 (file)
@@ -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
index d4eb848..786427f 100644 (file)
@@ -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
index 20ecd9f..e64744c 100644 (file)
@@ -28,6 +28,7 @@
         <file alias="editdelete">res/icons/editdelete.png</file>
         <file alias="history">res/icons/history.png</file>
         <file alias="overview">res/icons/overview.png</file>
+        <file alias="export">res/icons/export.png</file>
     </qresource>
     <qresource prefix="/images">
         <file alias="about">res/images/about.png</file>
index fbab51b..34da135 100644 (file)
@@ -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();
 }
+
index e04bcf9..a5fcc8a 100644 (file)
@@ -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 (file)
index 0000000..62c0b94
--- /dev/null
@@ -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;
+}
+
diff --git a/src/qt/csvmodelwriter.h b/src/qt/csvmodelwriter.h
new file mode 100644 (file)
index 0000000..7367f3a
--- /dev/null
@@ -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
index 864dffa..1b527bc 100644 (file)
@@ -254,3 +254,9 @@ bool TransactionRecord::statusUpdateNeeded()
 {
     return status.cur_num_blocks != nBestHeight;
 }
+
+std::string TransactionRecord::getTxID()
+{
+    return hash.ToString() + strprintf("-%03d", idx);
+}
+
index c196be2..0050c87 100644 (file)
@@ -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);
index 70dbd0f..52c9b0c 100644 (file)
@@ -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();
 }
 
index f75f414..85bfeeb 100644 (file)
@@ -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:
index a3407e8..037dfbb 100644 (file)
@@ -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 <QScrollBar>
 #include <QComboBox>
@@ -15,6 +14,9 @@
 #include <QLineEdit>
 #include <QTableView>
 #include <QHeaderView>
+#include <QPushButton>
+#include <QFileDialog>
+#include <QMessageBox>
 
 #include <QDebug>
 
@@ -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);
+    }
+}
+
index fe0f154..25212c9 100644 (file)
@@ -50,6 +50,7 @@ public slots:
     void chooseType(int idx);
     void changedPrefix(const QString &prefix);
     void changedAmount(const QString &amount);
+    void exportClicked();
 
 };