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(); };