diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 882a061..324ea90 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -315,6 +315,7 @@ void MainWindow::initMenu() {
// [Wallet] -> [History]
connect(ui->actionExport_CSV, &QAction::triggered, this, &MainWindow::onExportHistoryCSV);
+ connect(ui->actionImportHistoryCSV, &QAction::triggered, this, &MainWindow::onImportHistoryDescriptionsCSV);
// [Wallet] -> [Contacts]
connect(ui->actionExportContactsCSV, &QAction::triggered, this, &MainWindow::onExportContactsCSV);
@@ -575,7 +576,7 @@ void MainWindow::onWalletOpened() {
m_wallet->history()->refresh();
});
// Vice versa
- connect(m_wallet->history(), &TransactionHistory::txNoteChanged, [this] {
+ connect(m_wallet->transactionHistoryModel(), &TransactionHistoryModel::transactionDescriptionChanged, [this] {
m_wallet->coins()->refresh();
});
@@ -1713,6 +1714,21 @@ void MainWindow::onExportHistoryCSV() {
Utils::showInfo(this, "CSV export", QString("Transaction history exported to %1").arg(fn));
}
+void MainWindow::onImportHistoryDescriptionsCSV() {
+ const QString fileName = QFileDialog::getOpenFileName(this, "Import CSV file", QDir::homePath(), "CSV Files (*.csv)");
+ if (fileName.isEmpty()) {
+ return;
+ }
+
+ QString error = m_wallet->history()->importLabelsFromCSV(fileName);
+ if (!error.isEmpty()) {
+ Utils::showError(this, "Unable to import transaction descriptions from CSV", error);
+ }
+ else {
+ Utils::showInfo(this, "Successfully imported transaction descriptions from CSV");
+ }
+}
+
void MainWindow::onExportContactsCSV() {
auto *model = m_wallet->addressBookModel();
if (model->rowCount() <= 0){
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 9e15b0c..84cbe41 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -113,6 +113,7 @@ private slots:
void menuToggleTabVisible(const QString &key);
void menuClearHistoryClicked();
void onExportHistoryCSV();
+ void onImportHistoryDescriptionsCSV();
void onExportContactsCSV();
void onCreateDesktopEntry();
void onShowDocumentation();
diff --git a/src/MainWindow.ui b/src/MainWindow.ui
index 7cde519..1133416 100644
--- a/src/MainWindow.ui
+++ b/src/MainWindow.ui
@@ -499,6 +499,7 @@
History
+
diff --git a/src/libwalletqt/Coins.cpp b/src/libwalletqt/Coins.cpp
index 7ab8a45..c5de0aa 100644
--- a/src/libwalletqt/Coins.cpp
+++ b/src/libwalletqt/Coins.cpp
@@ -34,6 +34,8 @@ CoinsInfo* Coins::coin(int index)
void Coins::refresh()
{
+ qDebug() << Q_FUNC_INFO;
+
emit refreshStarted();
boost::shared_lock transfers_lock(m_wallet2->m_transfers_mutex);
diff --git a/src/libwalletqt/TransactionHistory.cpp b/src/libwalletqt/TransactionHistory.cpp
index 064352d..7102f3e 100644
--- a/src/libwalletqt/TransactionHistory.cpp
+++ b/src/libwalletqt/TransactionHistory.cpp
@@ -64,6 +64,8 @@ TransactionRow* TransactionHistory::transaction(int index)
void TransactionHistory::refresh()
{
+ qDebug() << Q_FUNC_INFO;
+
QDateTime firstDateTime = QDate(2014, 4, 18).startOfDay();
QDateTime lastDateTime = QDateTime::currentDateTime().addDays(1); // tomorrow (guard against jitter and timezones)
@@ -281,12 +283,14 @@ void TransactionHistory::refresh()
void TransactionHistory::setTxNote(const QString &txid, const QString ¬e)
{
cryptonote::blobdata txid_data;
- if(!epee::string_tools::parse_hexstr_to_binbuff(txid.toStdString(), txid_data) || txid_data.size() != sizeof(crypto::hash))
+ if (!epee::string_tools::parse_hexstr_to_binbuff(txid.toStdString(), txid_data) || txid_data.size() != sizeof(crypto::hash)) {
+ qDebug() << Q_FUNC_INFO << "invalid txid";
return;
+ }
+
const crypto::hash htxid = *reinterpret_cast(txid_data.data());
m_wallet2->set_tx_note(htxid, note.toStdString());
- refresh();
emit txNoteChanged();
}
@@ -401,4 +405,102 @@ bool TransactionHistory::writeCSV(const QString &path) {
data = QString("blockHeight,timestamp,date,accountIndex,direction,balanceDelta,amount,fee,txid,description,paymentId,fiatAmount,fiatCurrency%1").arg(data);
return Utils::fileWrite(path, data);
-}
\ No newline at end of file
+}
+
+QStringList parseCSVLine(const QString &line) {
+ QStringList result;
+ QString currentField;
+ bool inQuotes = false;
+
+ for (int i = 0; i < line.length(); ++i) {
+ QChar currentChar = line[i];
+
+ if (currentChar == '"') {
+ if (inQuotes && i + 1 < line.length() && line[i + 1] == '"') {
+ currentField.append('"');
+ ++i;
+ } else {
+ inQuotes = !inQuotes;
+ }
+ } else if (currentChar == ',' && !inQuotes) {
+ result.append(currentField.trimmed());
+ currentField.clear();
+ } else {
+ currentField.append(currentChar);
+ }
+ }
+
+ result.append(currentField.trimmed());
+ return result;
+}
+
+QString TransactionHistory::importLabelsFromCSV(const QString &fileName) {
+ QFile file(fileName);
+
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ return QString("Could not open file: %1").arg(fileName);
+ }
+
+ QTextStream in(&file);
+
+ QList fields;
+ while (!in.atEnd()) {
+ QString line = in.readLine();
+ fields.append(parseCSVLine(line));
+ }
+
+ if (fields.empty()) {
+ return "CSV file appears to be empty";
+ }
+
+ qint64 txidField = -1;
+ qint64 descriptionField = -1;
+
+ QStringList header = fields[0];
+ for (int i = 0; i < header.length(); i++) {
+ if (header[i] == "txid") {
+ txidField = i;
+ continue;
+ }
+ if (header[i] == "description") {
+ descriptionField = i;
+ }
+ }
+
+ if (txidField < 0) {
+ return "'txid' field not found in CSV header";
+ }
+ if (descriptionField < 0) {
+ return "'description' field not found in CSV header";
+ }
+ qint64 maxIndex = std::max(txidField, descriptionField);
+
+ QList> descriptions;
+
+ for (int i = 1; i < fields.length(); i++) {
+ const auto& row = fields[i];
+ if (maxIndex >= row.length()) {
+ qDebug() << "Row with invalid length in CSV";
+ continue;
+ }
+
+ if (row[txidField].isEmpty()) {
+ continue;
+ }
+
+ if (row[descriptionField].isEmpty()) {
+ continue;
+ }
+
+ descriptions.push_back({row[txidField], row[descriptionField]});
+ }
+
+ for (const auto& description : descriptions) {
+ qDebug() << "Setting note for tx:" << description.first << "description:" << description.second;
+ this->setTxNote(description.first, description.second);
+ }
+
+ this->refresh();
+
+ return {};
+}
diff --git a/src/libwalletqt/TransactionHistory.h b/src/libwalletqt/TransactionHistory.h
index ce10b8e..7b0d5b4 100644
--- a/src/libwalletqt/TransactionHistory.h
+++ b/src/libwalletqt/TransactionHistory.h
@@ -34,7 +34,6 @@ public:
TransactionRow* transaction(int index);
void refresh();
void setTxNote(const QString &txid, const QString ¬e);
- bool writeCSV(const QString &path);
quint64 count() const;
QDateTime firstDateTime() const;
QDateTime lastDateTime() const;
@@ -42,6 +41,9 @@ public:
bool locked() const;
void clearRows();
+ bool writeCSV(const QString &path);
+ QString importLabelsFromCSV(const QString &fileName);
+
signals:
void refreshStarted() const;
void refreshFinished() const;
diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp
index 770718a..ce5f494 100644
--- a/src/libwalletqt/Wallet.cpp
+++ b/src/libwalletqt/Wallet.cpp
@@ -74,10 +74,6 @@ Wallet::Wallet(Monero::Wallet *wallet, QObject *parent)
this->updateBalance();
}
- connect(this->history(), &TransactionHistory::txNoteChanged, [this]{
- this->history()->refresh();
- });
-
connect(this, &Wallet::refreshed, this, &Wallet::onRefreshed);
connect(this, &Wallet::newBlock, this, &Wallet::onNewBlock);
connect(this, &Wallet::updated, this, &Wallet::onUpdated);
diff --git a/src/model/TransactionHistoryModel.cpp b/src/model/TransactionHistoryModel.cpp
index 932b43d..5e87563 100644
--- a/src/model/TransactionHistoryModel.cpp
+++ b/src/model/TransactionHistoryModel.cpp
@@ -236,6 +236,8 @@ bool TransactionHistoryModel::setData(const QModelIndex &index, const QVariant &
hash = tInfo.hash();
});
m_transactionHistory->setTxNote(hash, value.toString());
+ m_transactionHistory->refresh();
+ emit transactionDescriptionChanged();
break;
}
default:
diff --git a/src/model/TransactionHistoryModel.h b/src/model/TransactionHistoryModel.h
index d84a6d7..38f467d 100644
--- a/src/model/TransactionHistoryModel.h
+++ b/src/model/TransactionHistoryModel.h
@@ -45,6 +45,7 @@ public:
signals:
void transactionHistoryChanged();
+ void transactionDescriptionChanged();
private:
QVariant parseTransactionInfo(const TransactionRow &tInfo, int column, int role) const;