]> Nutra Git (v1) - gamesguru/feather.git/commitdiff
history: rework csv export
authortobtoht <tob@featherwallet.org>
Wed, 5 Mar 2025 12:21:57 +0000 (13:21 +0100)
committertobtoht <tob@featherwallet.org>
Wed, 5 Mar 2025 12:21:57 +0000 (13:21 +0100)
src/MainWindow.cpp
src/dialog/HistoryExportDialog.cpp [new file with mode: 0644]
src/dialog/HistoryExportDialog.h [new file with mode: 0644]
src/dialog/HistoryExportDialog.ui [new file with mode: 0644]
src/libwalletqt/TransactionHistory.cpp
src/libwalletqt/TransactionHistory.h

index aea3a43a52fe52cc391745d85ef20d184d1f0e7b..bac74d4680c651f6a371cfbc1a54cbf164c1e36e 100644 (file)
@@ -13,6 +13,7 @@
 #include "dialog/AddressCheckerIndexDialog.h"
 #include "dialog/BalanceDialog.h"
 #include "dialog/DebugInfoDialog.h"
+#include "dialog/HistoryExportDialog.h"
 #include "dialog/PasswordDialog.h"
 #include "dialog/TxBroadcastDialog.h"
 #include "dialog/TxConfAdvDialog.h"
@@ -1714,26 +1715,8 @@ void MainWindow::onTxPoolBacklog(const QVector<quint64> &backlog, quint64 origin
 }
 
 void MainWindow::onExportHistoryCSV() {
-    QString filePath = QFileDialog::getSaveFileName(this, "Save CSV file", QDir::homePath(), "CSV (*.csv)");
-    if (filePath.isEmpty())
-        return;
-    if (!filePath.endsWith(".csv"))
-        filePath += ".csv";
-    QFileInfo fileInfo(filePath);
-    QDir dir = fileInfo.absoluteDir();
-
-    if (!dir.exists()) {
-        Utils::showError(this, "Unable to export transaction history", QString("Path does not exist: %1").arg(dir.absolutePath()));
-        return;
-    }
-
-    bool success = m_wallet->history()->writeCSV(filePath);
-    if (!success) {
-        Utils::showError(this, "Unable to export transaction history", QString("No permission to write to: %1").arg(filePath));
-        return;
-    }
-
-    Utils::showInfo(this, "CSV export", QString("Transaction history exported to %1").arg(filePath));
+    HistoryExportDialog dialog{m_wallet, this};
+    dialog.exec();
 }
 
 void MainWindow::onImportHistoryDescriptionsCSV() {
diff --git a/src/dialog/HistoryExportDialog.cpp b/src/dialog/HistoryExportDialog.cpp
new file mode 100644 (file)
index 0000000..0849ced
--- /dev/null
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: The Monero Project
+
+#include "HistoryExportDialog.h"
+#include "ui_HistoryExportDialog.h"
+
+#include <QFileDialog>
+
+#include "Utils.h"
+#include "WalletManager.h"
+#include "libwalletqt/Wallet.h"
+#include "TransactionHistory.h"
+#include "utils/AppData.h"
+
+HistoryExportDialog::HistoryExportDialog(Wallet *wallet, QWidget *parent)
+        : WindowModalDialog(parent)
+        , ui(new Ui::HistoryExportDialog)
+        , m_wallet(wallet)
+{
+    ui->setupUi(this);
+
+    connect(ui->btn_export, &QPushButton::clicked, this, &HistoryExportDialog::exportHistory);
+
+    connect(ui->radio_everything, &QRadioButton::toggled, [this](bool toggled) {
+        if (!toggled) return;
+        this->setEverything();
+    });
+
+    connect(ui->radio_YTD, &QRadioButton::toggled, [this](bool toggled) {
+        if (!toggled) return;
+        ui->date_min->setDate(QDate(QDate::currentDate().year(), 1, 1));
+        ui->date_max->setDate(QDate::currentDate());
+    });
+
+    connect(ui->radio_lastDays, &QRadioButton::toggled, [this](bool toggled) {
+        ui->spin_days->setEnabled(toggled);
+        if (!toggled) return;
+        ui->date_min->setDate(QDate::currentDate().addDays(-ui->spin_days->value() + 1));
+        ui->date_max->setDate(QDate::currentDate());
+    });
+
+    connect(ui->spin_days, &QSpinBox::valueChanged, [this] {
+        ui->date_min->setDate(QDate::currentDate().addDays(-ui->spin_days->value() + 1));
+        ui->date_max->setDate(QDate::currentDate());
+    });
+
+    connect(ui->radio_lastMonth, &QRadioButton::toggled, [this](bool toggled) {
+        if (!toggled) return;
+        ui->date_min->setDate(QDate(QDate::currentDate().year(), QDate::currentDate().month(), 1).addMonths(-1));
+        ui->date_max->setDate(QDate(QDate::currentDate().year(), QDate::currentDate().month(), 1).addDays(-1));
+    });
+
+    connect(ui->radio_lastYear, &QRadioButton::toggled, [this](bool toggled) {
+        if (!toggled) return;
+        ui->date_min->setDate(QDate(QDate::currentDate().year() - 1, 1, 1));
+        ui->date_max->setDate(QDate(QDate::currentDate().year(), 1, 1).addDays(-1));
+    });
+
+    connect(ui->radio_customRange, &QRadioButton::toggled, [this](bool toggled) {
+        ui->date_min->setEnabled(toggled);
+        ui->date_max->setEnabled(toggled);
+    });
+
+    this->setEverything();
+
+    this->adjustSize();
+}
+
+void HistoryExportDialog::setEverything()
+{
+    ui->date_min->setDate(QDate(2014,4,18));
+    ui->date_max->setDate(QDate::currentDate());
+}
+
+void HistoryExportDialog::exportHistory()
+{
+    QString wallet_name = m_wallet->walletName();
+    QString csv_file_name = QString("/history_export_%1.csv").arg(wallet_name);
+
+    QString filePath = QFileDialog::getSaveFileName(this, "Save CSV file", QDir::homePath() + csv_file_name, "CSV (*.csv)");
+    if (filePath.isEmpty())
+        return;
+    if (!filePath.endsWith(".csv"))
+        filePath += ".csv";
+    QFileInfo fileInfo(filePath);
+    QDir dir = fileInfo.absoluteDir();
+
+    if (!dir.exists()) {
+        Utils::showError(this, "Unable to export transaction history", QString("Path does not exist: %1").arg(dir.absolutePath()));
+        return;
+    }
+
+    auto num_transactions = m_wallet->history()->count();
+
+    QList<QPair<uint64_t, QString>> csvData;
+
+    for (int i = 0; i < num_transactions; i++) {
+        TransactionRow* tx = m_wallet->history()->transaction(i);
+
+        QDate minimumDate = ui->date_min->date();
+        if (tx->timestamp().date() < minimumDate) {
+            continue;
+        }
+
+        QDate maximumDate = ui->date_max->date();
+        if (tx->timestamp().date() > maximumDate) {
+            continue;
+        }
+
+        if (ui->check_excludePending->isChecked() && tx->isPending()) {
+            continue;
+        }
+
+        if (ui->check_excludeFailed->isChecked() && tx->isFailed()) {
+            continue;
+        }
+
+        if (ui->radio_incomingTransactions->isChecked() && tx->direction() != TransactionRow::Direction_In) {
+            continue;
+        }
+
+        if (ui->radio_outgoingTransactions->isChecked() && tx->direction() != TransactionRow::Direction_Out) {
+            continue;
+        }
+
+        if (ui->radio_coinbaseTransactions->isChecked() && !tx->isCoinbase()) {
+            continue;
+        }
+
+        QString date = QString("%1T%2Z").arg(tx->date(), tx->time());
+
+        QString direction = QString("");
+        if (tx->direction() == TransactionRow::Direction_In)
+            direction = QString("in");
+        else if (tx->direction() == TransactionRow::Direction_Out)
+            direction = QString("out");
+        else
+            continue;  // skip TransactionInfo::Direction_Both
+
+        QString balanceDelta = WalletManager::displayAmount(abs(tx->balanceDelta()));
+        if (tx->direction() == TransactionRow::Direction_Out) {
+            balanceDelta = "-" + balanceDelta;
+        }
+
+        QString paymentId = tx->paymentId();
+        if (paymentId == "0000000000000000") {
+            paymentId = "";
+        }
+
+        const double usd_price = appData()->txFiatHistory->get(tx->timestamp().toString("yyyyMMdd"));
+        double fiat_price = usd_price * tx->amount();
+        QString fiatAmount = (usd_price > 0) ? QString::number(fiat_price, 'f', 2) : "?";
+
+        QString line = QString(R"(%1,%2,"%3",%4,"%5",%6,%7,%8,"%9","%10","%11","%12","%13")")
+        .arg(QString::number(tx->blockHeight()),
+             QString::number(tx->timestamp().toSecsSinceEpoch()),
+             date,
+             QString::number(tx->subaddrAccount()),
+             direction,
+             balanceDelta,
+             tx->displayAmount(),
+             tx->fee(),
+             tx->hash(),
+             tx->description(),
+             paymentId,
+             fiatAmount,
+             "USD");
+        csvData.append({tx->blockHeight(), line});
+    }
+
+    std::sort(csvData.begin(), csvData.end(), [](const QPair<uint64_t, QString> &tx1, const QPair<uint64_t, QString> &tx2){
+        return tx1.first < tx2.first;
+    });
+
+    QString csvString = "blockHeight,timestamp,date,accountIndex,direction,balanceDelta,amount,fee,txid,description,paymentId,fiatAmount,fiatCurrency";
+    for (const auto& data : csvData) {
+        csvString += "\n" + data.second;
+    }
+
+    bool success = Utils::fileWrite(filePath, csvString);
+
+    if (!success) {
+        Utils::showError(this, "Unable to export transaction history", QString("No permission to write to: %1").arg(filePath));
+        return;
+    }
+
+    Utils::showInfo(this, "CSV export", QString("Transaction history exported to:\n\n%1").arg(filePath));
+}
+
+HistoryExportDialog::~HistoryExportDialog() = default;
diff --git a/src/dialog/HistoryExportDialog.h b/src/dialog/HistoryExportDialog.h
new file mode 100644 (file)
index 0000000..8ec7bd6
--- /dev/null
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: The Monero Project
+
+#ifndef HISTORYEXPORTDIALOG_H
+#define HISTORYEXPORTDIALOG_H
+
+#include "components.h"
+
+namespace Ui {
+class HistoryExportDialog;
+}
+
+class Wallet;
+class HistoryExportDialog : public WindowModalDialog
+{
+    Q_OBJECT
+
+public:
+    explicit HistoryExportDialog(Wallet *wallet, QWidget *parent);
+    ~HistoryExportDialog() override;
+
+private:
+    void setEverything();
+    void exportHistory();
+
+    QScopedPointer<Ui::HistoryExportDialog> ui;
+    Wallet *m_wallet;
+};
+
+
+#endif //HISTORYEXPORTDIALOG_H
diff --git a/src/dialog/HistoryExportDialog.ui b/src/dialog/HistoryExportDialog.ui
new file mode 100644 (file)
index 0000000..b65737c
--- /dev/null
@@ -0,0 +1,302 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>HistoryExportDialog</class>
+ <widget class="QDialog" name="HistoryExportDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>351</width>
+    <height>539</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>History Export</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <item>
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>Export range</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <item>
+       <widget class="QRadioButton" name="radio_everything">
+        <property name="text">
+         <string>Everything</string>
+        </property>
+        <property name="checked">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QRadioButton" name="radio_YTD">
+        <property name="text">
+         <string>Year to Date</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout">
+        <item>
+         <widget class="QRadioButton" name="radio_lastDays">
+          <property name="text">
+           <string>Last</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QSpinBox" name="spin_days">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="minimum">
+           <number>1</number>
+          </property>
+          <property name="maximum">
+           <number>99999</number>
+          </property>
+          <property name="value">
+           <number>90</number>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLabel" name="label_2">
+          <property name="text">
+           <string>days</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <spacer name="horizontalSpacer_3">
+          <property name="orientation">
+           <enum>Qt::Orientation::Horizontal</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>0</width>
+            <height>20</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <widget class="QRadioButton" name="radio_lastMonth">
+        <property name="text">
+         <string>Last month</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QRadioButton" name="radio_lastYear">
+        <property name="text">
+         <string>Last year</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QRadioButton" name="radio_customRange">
+        <property name="text">
+         <string>Custom range</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout_4">
+        <item>
+         <spacer name="horizontalSpacer_2">
+          <property name="orientation">
+           <enum>Qt::Orientation::Horizontal</enum>
+          </property>
+          <property name="sizeType">
+           <enum>QSizePolicy::Policy::Fixed</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>30</width>
+            <height>0</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+        <item>
+         <widget class="QLabel" name="label">
+          <property name="text">
+           <string>From</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QDateEdit" name="date_min">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="displayFormat">
+           <string>yyyy-MM-dd</string>
+          </property>
+          <property name="calendarPopup">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLabel" name="label_3">
+          <property name="text">
+           <string>To</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QDateEdit" name="date_max">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="displayFormat">
+           <string>yyyy-MM-dd</string>
+          </property>
+          <property name="calendarPopup">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox_2">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="title">
+      <string>What to export</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_3">
+      <item>
+       <widget class="QRadioButton" name="radio_allTransactions">
+        <property name="text">
+         <string>All transactions</string>
+        </property>
+        <property name="checked">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QRadioButton" name="radio_incomingTransactions">
+        <property name="text">
+         <string>Incoming transactions</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QRadioButton" name="radio_outgoingTransactions">
+        <property name="text">
+         <string>Outgoing transactions</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QRadioButton" name="radio_coinbaseTransactions">
+        <property name="text">
+         <string>Coinbase (miner) transactions</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="Line" name="line">
+        <property name="orientation">
+         <enum>Qt::Orientation::Horizontal</enum>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QCheckBox" name="check_excludePending">
+        <property name="text">
+         <string>Exclude pending transactions</string>
+        </property>
+        <property name="checked">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QCheckBox" name="check_excludeFailed">
+        <property name="text">
+         <string>Exclude failed transactions</string>
+        </property>
+        <property name="checked">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_3">
+     <item>
+      <widget class="QPushButton" name="btn_export">
+       <property name="text">
+        <string>Export</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="orientation">
+        <enum>Qt::Orientation::Horizontal</enum>
+       </property>
+       <property name="standardButtons">
+        <set>QDialogButtonBox::StandardButton::Close</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>HistoryExportDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>HistoryExportDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
index cfc7fd21a087d7b2209455c061c6488951d24d30..14b0a468d0354a5817dd139f9ba9025b788eea95 100644 (file)
@@ -342,68 +342,6 @@ void TransactionHistory::clearRows() {
     m_rows.clear();
 }
 
-bool TransactionHistory::writeCSV(const QString &path) {
-    QString data;
-    QReadLocker locker(&m_lock);
-
-    auto transactions = m_rows;
-
-    std::sort(transactions.begin(), transactions.end(), [](const TransactionRow *info1, const TransactionRow *info2){
-        return info1->blockHeight() < info2->blockHeight();
-    });
-
-    for (const auto &info : transactions) {
-        // collect column data
-        QDateTime timeStamp = info->timestamp();
-        double amount = info->amount();
-
-        // calc historical fiat price
-        QString fiatAmount;
-        QString preferredFiatSymbol = conf()->get(Config::preferredFiatCurrency).toString();
-        const double usd_price = appData()->txFiatHistory->get(timeStamp.toString("yyyyMMdd"));
-        double fiat_price = usd_price * amount;
-
-        if (preferredFiatSymbol != "USD")
-            fiat_price = appData()->prices.convert("USD", preferredFiatSymbol, fiat_price);
-        double fiat_rounded = ceil(Utils::roundSignificant(fiat_price, 3) * 100.0) / 100.0;
-        if (usd_price != 0)
-            fiatAmount = QString::number(fiat_rounded);
-        else
-            fiatAmount = "\"?\"";
-
-        QString direction = QString("");
-        if (info->direction() == TransactionRow::Direction_In)
-            direction = QString("in");
-        else if (info->direction() == TransactionRow::Direction_Out)
-            direction = QString("out");
-        else
-            continue;  // skip TransactionInfo::Direction_Both
-
-        QString displayAmount = info->displayAmount();
-        QString paymentId = info->paymentId();
-        if (paymentId == "0000000000000000") {
-            paymentId = "";
-        }
-
-        QString date = QString("%1T%2Z").arg(info->date(), info->time()); // ISO 8601
-
-        QString balanceDelta = WalletManager::displayAmount(info->balanceDelta());
-        if (info->direction() == TransactionRow::Direction_Out) {
-            balanceDelta = "-" + balanceDelta;
-        }
-
-        // format and write
-        QString line = QString("\n%1,%2,\"%3\",%4,\"%5\",%6,%7,%8,\"%9\",\"%10\",\"%11\",%12,\"%13\"")
-                .arg(QString::number(info->blockHeight()), QString::number(timeStamp.toSecsSinceEpoch()), date,
-                     QString::number(info->subaddrAccount()), direction, balanceDelta, info->displayAmount(),
-                     info->fee(), info->hash(), info->description(), paymentId, fiatAmount, preferredFiatSymbol);
-        data += line;
-    }
-
-    data = QString("blockHeight,timestamp,date,accountIndex,direction,balanceDelta,amount,fee,txid,description,paymentId,fiatAmount,fiatCurrency%1").arg(data);
-    return Utils::fileWrite(path, data);
-}
-
 QStringList parseCSVLine(const QString &line) {
     QStringList result;
     QString currentField;
index ba8dc38a835cbe23caceeaaa215184a760ae2386..cd6653153b23ff2704854a596726b35238a069ec 100644 (file)
@@ -41,7 +41,6 @@ public:
     bool locked() const;
     void clearRows();
 
-    bool writeCSV(const QString &path);
     QString importLabelsFromCSV(const QString &fileName);
 
 signals: