// [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);
m_wallet->history()->refresh();
});
// Vice versa
- connect(m_wallet->history(), &TransactionHistory::txNoteChanged, [this] {
+ connect(m_wallet->transactionHistoryModel(), &TransactionHistoryModel::transactionDescriptionChanged, [this] {
m_wallet->coins()->refresh();
});
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){
void menuToggleTabVisible(const QString &key);
void menuClearHistoryClicked();
void onExportHistoryCSV();
+ void onImportHistoryDescriptionsCSV();
void onExportContactsCSV();
void onCreateDesktopEntry();
void onShowDocumentation();
<string>History</string>
</property>
<addaction name="actionExport_CSV"/>
+ <addaction name="actionImportHistoryCSV"/>
</widget>
<widget class="QMenu" name="menuContacts">
<property name="title">
<string>Tx pool viewer</string>
</property>
</action>
+ <action name="actionImportHistoryCSV">
+ <property name="text">
+ <string>Import descriptions from CSV</string>
+ </property>
+ </action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
void Coins::refresh()
{
+ qDebug() << Q_FUNC_INFO;
+
emit refreshStarted();
boost::shared_lock<boost::shared_mutex> transfers_lock(m_wallet2->m_transfers_mutex);
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)
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<const crypto::hash*>(txid_data.data());
m_wallet2->set_tx_note(htxid, note.toStdString());
- refresh();
emit txNoteChanged();
}
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<QStringList> 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<QPair<QString, QString>> 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 {};
+}
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;
bool locked() const;
void clearRows();
+ bool writeCSV(const QString &path);
+ QString importLabelsFromCSV(const QString &fileName);
+
signals:
void refreshStarted() const;
void refreshFinished() const;
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);
hash = tInfo.hash();
});
m_transactionHistory->setTxNote(hash, value.toString());
+ m_transactionHistory->refresh();
+ emit transactionDescriptionChanged();
break;
}
default:
signals:
void transactionHistoryChanged();
+ void transactionDescriptionChanged();
private:
QVariant parseTransactionInfo(const TransactionRow &tInfo, int column, int role) const;