From b60d65ee822c8cb463621c0a826cc19c91f3099e Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Tue, 6 Nov 2018 22:35:57 -0800 Subject: [PATCH] add address/label autocomplete --- src/addressbook.cpp | 124 ++++++++++++++++++++++++++------------------ src/addressbook.h | 13 +++-- src/mainwindow.cpp | 1 + src/mainwindow.h | 7 ++- src/precompiled.h | 1 + src/sendtab.cpp | 28 ++++++++++ src/utils.cpp | 57 ++++++++++---------- 7 files changed, 145 insertions(+), 86 deletions(-) diff --git a/src/addressbook.cpp b/src/addressbook.cpp index 02b0524..873eac2 100644 --- a/src/addressbook.cpp +++ b/src/addressbook.cpp @@ -10,83 +10,56 @@ AddressBookModel::AddressBookModel(QTableView *parent) headers << "Label" << "Address"; this->parent = parent; - loadDataFromStorage(); + loadData(); } AddressBookModel::~AddressBookModel() { - if (labels != nullptr) - saveDataToStorage(); - - delete labels; + saveData(); } -void AddressBookModel::saveDataToStorage() { - QFile file(writeableFile()); - file.open(QIODevice::ReadWrite | QIODevice::Truncate); - QDataStream out(&file); // we will serialize the data into the file - out << QString("v1") << *labels; - file.close(); +void AddressBookModel::saveData() { + AddressBook::writeToStorage(labels); // Save column positions QSettings().setValue("addresstablegeometry", parent->horizontalHeader()->saveState()); } -void AddressBookModel::loadDataFromStorage() { - QFile file(writeableFile()); - - delete labels; - labels = new QList>(); - - file.open(QIODevice::ReadOnly); - QDataStream in(&file); // read the data serialized from the file - QString version; - in >> version >> *labels; - - file.close(); +void AddressBookModel::loadData() { + labels = AddressBook::readFromStorage(); parent->horizontalHeader()->restoreState(QSettings().value("addresstablegeometry").toByteArray()); } void AddressBookModel::addNewLabel(QString label, QString addr) { - labels->push_back(QPair(label, addr)); + labels.push_back(QPair(label, addr)); + AddressBook::writeToStorage(labels); - dataChanged(index(0, 0), index(labels->size()-1, columnCount(index(0,0))-1)); + dataChanged(index(0, 0), index(labels.size()-1, columnCount(index(0,0))-1)); layoutChanged(); } void AddressBookModel::removeItemAt(int row) { - if (row >= labels->size()) + if (row >= labels.size()) return; - labels->removeAt(row); - dataChanged(index(0, 0), index(labels->size()-1, columnCount(index(0,0))-1)); + labels.removeAt(row); + AddressBook::writeToStorage(labels); + + + dataChanged(index(0, 0), index(labels.size()-1, columnCount(index(0,0))-1)); layoutChanged(); } QPair AddressBookModel::itemAt(int row) { - if (row >= labels->size()) return QPair(); + if (row >= labels.size()) return QPair(); - return labels->at(row); + return labels.at(row); } -QString AddressBookModel::writeableFile() { - auto filename = QStringLiteral("addresslabels.dat"); - - auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); - if (!dir.exists()) - QDir().mkpath(dir.absolutePath()); - - if (Settings::getInstance()->isTestnet()) { - return dir.filePath("testnet-" % filename); - } else { - return dir.filePath(filename); - } -} int AddressBookModel::rowCount(const QModelIndex&) const { - if (labels == nullptr) return 0; - return labels->size(); + return labels.size(); } int AddressBookModel::columnCount(const QModelIndex&) const { @@ -97,12 +70,12 @@ int AddressBookModel::columnCount(const QModelIndex&) const { QVariant AddressBookModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { switch(index.column()) { - case 0: return labels->at(index.row()).first; - case 1: return labels->at(index.row()).second; + case 0: return labels.at(index.row()).first; + case 1: return labels.at(index.row()).second; } } return QVariant(); -} +} QVariant AddressBookModel::headerData(int section, Qt::Orientation orientation, int role) const { @@ -127,6 +100,9 @@ void AddressBook::open(MainWindow* parent, QLineEdit* target) { ab.buttonBox->button(QDialogButtonBox::Ok)->setText("Pick"); } + // Connect the dialog's closing to updating the label address completor + QObject::connect(&d, &QDialog::finished, [=] (auto) { parent->updateLabelsAutoComplete(); }); + // If there is a target then make it the addr for the "Add to" button if (target != nullptr && Utils::isValidAddress(target->text())) { ab.addr->setText(target->text()); @@ -146,13 +122,18 @@ void AddressBook::open(MainWindow* parent, QLineEdit* target) { } }); + auto fnSetTargetLabelAddr = [=] (QLineEdit* target, QString label, QString addr) { + target->setText(label % "/" % addr); + }; + // Double-Click picks the item QObject::connect(ab.addresses, &QTableView::doubleClicked, [&] (auto index) { if (index.row() < 0) return; + QString lbl = model.itemAt(index.row()).first; QString addr = model.itemAt(index.row()).second; d.accept(); - target->setText(addr); + fnSetTargetLabelAddr(target, lbl, addr); }); // Right-Click @@ -162,13 +143,15 @@ void AddressBook::open(MainWindow* parent, QLineEdit* target) { if (index.row() < 0) return; + QString lbl = model.itemAt(index.row()).first; QString addr = model.itemAt(index.row()).second; QMenu menu(parent); if (target != nullptr) { menu.addAction("Pick", [&] () { - target->setText(addr); + d.accept(); + fnSetTargetLabelAddr(target, lbl, addr); }); } @@ -187,7 +170,46 @@ void AddressBook::open(MainWindow* parent, QLineEdit* target) { if (d.exec() == QDialog::Accepted && target != nullptr) { auto selection = ab.addresses->selectionModel(); if (selection->hasSelection()) { - target->setText(model.itemAt(selection->selectedRows().at(0).row()).second); + auto item = model.itemAt(selection->selectedRows().at(0).row()); + fnSetTargetLabelAddr(target, item.first, item.second); } }; +} + +QList> AddressBook::readFromStorage() { + QFile file(AddressBook::writeableFile()); + + QList> labels; + + file.open(QIODevice::ReadOnly); + QDataStream in(&file); // read the data serialized from the file + QString version; + in >> version >> labels; + + file.close(); + + return labels; +} + + +void AddressBook::writeToStorage(QList> labels) { + QFile file(AddressBook::writeableFile()); + file.open(QIODevice::ReadWrite | QIODevice::Truncate); + QDataStream out(&file); // we will serialize the data into the file + out << QString("v1") << labels; + file.close(); +} + +QString AddressBook::writeableFile() { + auto filename = QStringLiteral("addresslabels.dat"); + + auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); + if (!dir.exists()) + QDir().mkpath(dir.absolutePath()); + + if (Settings::getInstance()->isTestnet()) { + return dir.filePath("testnet-" % filename); + } else { + return dir.filePath(filename); + } } \ No newline at end of file diff --git a/src/addressbook.h b/src/addressbook.h index 5fe7666..9aa3f00 100644 --- a/src/addressbook.h +++ b/src/addressbook.h @@ -21,19 +21,22 @@ public: QVariant headerData(int section, Qt::Orientation orientation, int role) const; private: - void loadDataFromStorage(); - void saveDataToStorage(); - - QString writeableFile(); + void loadData(); + void saveData(); QTableView* parent; - QList>* labels = nullptr; + QList> labels; QStringList headers; }; class AddressBook { public: static void open(MainWindow* parent, QLineEdit* target = nullptr); + + static QList> readFromStorage(); + static void writeToStorage(QList> labels); + + static QString writeableFile(); }; #endif // ADDRESSBOOK_H \ No newline at end of file diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7e6273e..7ce0ae2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -910,6 +910,7 @@ MainWindow::~MainWindow() { delete ui; delete rpc; + delete labelCompleter; delete loadingMovie; } diff --git a/src/mainwindow.h b/src/mainwindow.h index 4f5005c..a8521ed 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -36,6 +36,8 @@ public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); + void updateLabelsAutoComplete(); + Ui::MainWindow* ui; QLabel* statusLabel; @@ -89,9 +91,10 @@ private: void restoreSavedStates(); - RPC* rpc = nullptr; + RPC* rpc = nullptr; + QCompleter* labelCompleter = nullptr; - QMovie* loadingMovie; + QMovie* loadingMovie; }; #endif // MAINWINDOW_H diff --git a/src/precompiled.h b/src/precompiled.h index 9210ca5..9c8f162 100644 --- a/src/precompiled.h +++ b/src/precompiled.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/src/sendtab.cpp b/src/sendtab.cpp index 146f556..ae4e89e 100644 --- a/src/sendtab.cpp +++ b/src/sendtab.cpp @@ -45,6 +45,11 @@ void MainWindow::setupSendTab() { QObject::connect(ui->Address1, &QLineEdit::textChanged, [=] (auto text) { this->addressChanged(1, text); }); + + // This is the damnest thing ever. If we do AddressBook::readFromStorage() directly, the whole file + // doesn't get read. It needs to run in a timer after everything has finished to be able to read + // the file properly. + QTimer::singleShot(100, [=]() { updateLabelsAutoComplete(); }); // The first address book button QObject::connect(ui->AddressBook1, &QPushButton::clicked, [=] () { @@ -87,6 +92,25 @@ void MainWindow::setupSendTab() { }); } +void MainWindow::updateLabelsAutoComplete() { + QList list; + auto labels = AddressBook::readFromStorage(); + + std::transform(labels.begin(), labels.end(), std::back_inserter(list), [=] (auto la) -> QString { + return la.first % "/" % la.second; + }); + + delete labelCompleter; + labelCompleter = new QCompleter(list, this); + labelCompleter->setCaseSensitivity(Qt::CaseInsensitive); + + // Then, find all the address fields and update the completer. + QRegExp re("Address[0-9]+", Qt::CaseInsensitive); + for (auto target: ui->sendToWidgets->findChildren(re)) { + target->setCompleter(labelCompleter); + } +} + void MainWindow::setDefaultPayFrom() { auto findMax = [=] (QString startsWith) { double max_amt = 0; @@ -148,6 +172,7 @@ void MainWindow::addAddressSection() { QObject::connect(Address1, &QLineEdit::textChanged, [=] (auto text) { this->addressChanged(itemNumber, text); }); + Address1->setCompleter(labelCompleter); horizontalLayout_12->addWidget(Address1); @@ -350,6 +375,9 @@ Tx MainWindow::createTxFromSendPage() { int totalItems = ui->sendToWidgets->children().size() - 2; // The last one is a spacer, so ignore that for (int i=0; i < totalItems; i++) { QString addr = ui->sendToWidgets->findChild(QString("Address") % QString::number(i+1))->text().trimmed(); + // Remove label if it exists + addr = addr.split("/").last(); + double amt = ui->sendToWidgets->findChild(QString("Amount") % QString::number(i+1))->text().trimmed().toDouble(); QString memo = ui->sendToWidgets->findChild(QString("MemoTxt") % QString::number(i+1))->text().trimmed(); diff --git a/src/utils.cpp b/src/utils.cpp index bffe3e9..2f66132 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -31,36 +31,37 @@ const QString Utils::getDevSproutAddr() { // Get the dev fee address based on the transaction const QString Utils::getDevAddr(Tx tx) { - auto testnetAddrLookup = [=] (const QString& addr) -> QString { - if (addr.startsWith("ztestsapling")) { - return "ztestsapling1kdp74adyfsmm9838jaupgfyx3npgw8ut63stjjx757pc248cuc0ymzphqeux60c64qe5qt68ygh"; - } else if (addr.startsWith("zt")) { - return getDevSproutAddr(); - } else { - return QString(); - } - }; + return QString(); + // auto testnetAddrLookup = [=] (const QString& addr) -> QString { + // if (addr.startsWith("ztestsapling")) { + // return "ztestsapling1kdp74adyfsmm9838jaupgfyx3npgw8ut63stjjx757pc248cuc0ymzphqeux60c64qe5qt68ygh"; + // } else if (addr.startsWith("zt")) { + // return getDevSproutAddr(); + // } else { + // return QString(); + // } + // }; - if (Settings::getInstance()->isTestnet()) { - auto devAddr = testnetAddrLookup(tx.fromAddr); - if (!devAddr.isEmpty()) { - return devAddr; - } + // if (Settings::getInstance()->isTestnet()) { + // auto devAddr = testnetAddrLookup(tx.fromAddr); + // if (!devAddr.isEmpty()) { + // return devAddr; + // } - // t-Addr, find if it is going to a Sprout or Sapling address - for (const ToFields& to : tx.toAddrs) { - devAddr = testnetAddrLookup(to.addr); - if (!devAddr.isEmpty()) { - return devAddr; - } - } + // // t-Addr, find if it is going to a Sprout or Sapling address + // for (const ToFields& to : tx.toAddrs) { + // devAddr = testnetAddrLookup(to.addr); + // if (!devAddr.isEmpty()) { + // return devAddr; + // } + // } - // If this is a t-Addr -> t-Addr transaction, use the Sapling address by default - return testnetAddrLookup("ztestsapling"); - } else { - // Mainnet doesn't have a fee yet! - return QString(); - } + // // If this is a t-Addr -> t-Addr transaction, use the Sapling address by default + // return testnetAddrLookup("ztestsapling"); + // } else { + // // Mainnet doesn't have a fee yet! + // return QString(); + // } } @@ -82,7 +83,7 @@ QString Utils::getZboardAddr() { } double Utils::getDevFee() { if (Settings::getInstance()->isTestnet()) { - return 0.0001; + return 0; } else { return 0; }