From b2f758242dc8ee4f3900e7d5b44ab027e2126ff2 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Mon, 5 Nov 2018 12:09:06 -0800 Subject: [PATCH] #23 Basic address book support for sending addresses zboard small fixes --- src/addressbook.cpp | 192 ++++++++++++++++++++++++++++++++++++++++++++ src/addressbook.h | 39 +++++++++ src/addressbook.ui | 133 ++++++++++++++++++++++++++++++ src/mainwindow.cpp | 24 +++++- src/mainwindow.h | 1 + src/mainwindow.ui | 26 +++++- src/precompiled.h | 3 + src/sendtab.cpp | 19 ++++- src/utils.cpp | 11 +++ src/utils.h | 2 + src/zboard.ui | 2 +- zec-qt-wallet.pro | 9 ++- 12 files changed, 451 insertions(+), 10 deletions(-) create mode 100644 src/addressbook.cpp create mode 100644 src/addressbook.h create mode 100644 src/addressbook.ui diff --git a/src/addressbook.cpp b/src/addressbook.cpp new file mode 100644 index 0000000..27f98dc --- /dev/null +++ b/src/addressbook.cpp @@ -0,0 +1,192 @@ +#include "addressbook.h" +#include "ui_addressbook.h" +#include "ui_mainwindow.h" +#include "settings.h" +#include "mainwindow.h" +#include "utils.h" + +AddressBookModel::AddressBookModel(QTableView *parent) + : QAbstractTableModel(parent) { + headers << "Label" << "Address"; + + this->parent = parent; + loadDataFromStorage(); +} + +AddressBookModel::~AddressBookModel() { + if (labels != nullptr) + saveDataToStorage(); + + delete labels; +} + +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(); + + // 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(); + + parent->horizontalHeader()->restoreState(QSettings().value("addresstablegeometry").toByteArray()); +} + +void AddressBookModel::addNewLabel(QString label, QString addr) { + labels->push_back(QPair(label, addr)); + + dataChanged(index(0, 0), index(labels->size()-1, columnCount(index(0,0))-1)); + layoutChanged(); +} + +void AddressBookModel::removeItemAt(int row) { + if (row >= labels->size()) + return; + labels->removeAt(row); + + 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(); + + 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(); +} + +int AddressBookModel::columnCount(const QModelIndex&) const { + return headers.size(); +} + + +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; + } + } + return QVariant(); +} + + +QVariant AddressBookModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { + return headers.at(section); + } + + return QVariant(); +} + +void AddressBook::open(MainWindow* parent, QLineEdit* target) { + QDialog d(parent); + Ui_addressBook ab; + ab.setupUi(&d); + + AddressBookModel model(ab.addresses); + ab.addresses->setModel(&model); + + // If there is no target, the we'll call the button "Ok", else "Pick" + if (target != nullptr) { + ab.buttonBox->button(QDialogButtonBox::Ok)->setText("Pick"); + } + + // 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()); + ab.label->setFocus(); + } + + // Add new address button + QObject::connect(ab.addNew, &QPushButton::clicked, [&] () { + auto addr = ab.addr->text().trimmed(); + if (!addr.isEmpty() && !ab.label->text().isEmpty()) { + // Test if address is valid. + if (!Utils::isValidAddress(addr)) { + QMessageBox::critical(parent, "Address Format Error", addr + " doesn't seem to be a valid Zcash address.", QMessageBox::Ok); + } else { + model.addNewLabel(ab.label->text(), ab.addr->text()); + } + } + }); + + // Double-Click picks the item + QObject::connect(ab.addresses, &QTableView::doubleClicked, [&] (auto index) { + if (index.row() < 0) return; + + QString addr = model.itemAt(index.row()).second; + d.accept(); + target->setText(addr); + }); + + // Right-Click + ab.addresses->setContextMenuPolicy(Qt::CustomContextMenu); + QObject::connect(ab.addresses, &QTableView::customContextMenuRequested, [&] (QPoint pos) { + QModelIndex index = ab.addresses->indexAt(pos); + + if (index.row() < 0) return; + + QString addr = model.itemAt(index.row()).second; + + QMenu menu(parent); + + if (target != nullptr) { + menu.addAction("Pick", [&] () { + target->setText(addr); + }); + } + + menu.addAction("Copy Address", [&] () { + QGuiApplication::clipboard()->setText(addr); + parent->ui->statusBar->showMessage("Copied to clipboard", 3 * 1000); + }); + + menu.addAction("Delete Label", [&] () { + model.removeItemAt(index.row()); + }); + + menu.exec(ab.addresses->viewport()->mapToGlobal(pos)); + }); + + 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); + } + }; +} \ No newline at end of file diff --git a/src/addressbook.h b/src/addressbook.h new file mode 100644 index 0000000..5fe7666 --- /dev/null +++ b/src/addressbook.h @@ -0,0 +1,39 @@ +#ifndef ADDRESSBOOK_H +#define ADDRESSBOOK_H + +#include "precompiled.h" + +class MainWindow; + +class AddressBookModel : public QAbstractTableModel { + +public: + AddressBookModel(QTableView* parent); + ~AddressBookModel(); + + void addNewLabel(QString label, QString addr); + void removeItemAt(int row); + QPair itemAt(int row); + + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + +private: + void loadDataFromStorage(); + void saveDataToStorage(); + + QString writeableFile(); + + QTableView* parent; + QList>* labels = nullptr; + QStringList headers; +}; + +class AddressBook { +public: + static void open(MainWindow* parent, QLineEdit* target = nullptr); +}; + +#endif // ADDRESSBOOK_H \ No newline at end of file diff --git a/src/addressbook.ui b/src/addressbook.ui new file mode 100644 index 0000000..974cd79 --- /dev/null +++ b/src/addressbook.ui @@ -0,0 +1,133 @@ + + + addressBook + + + + 0 + 0 + 690 + 562 + + + + Address Book + + + + + + Add New Address + + + + + + Address (z-Addr or t-Addr) + + + + + + + + + + Label + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Add to Address Book + + + + + + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + addressBook + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + addressBook + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1205f25..8646895 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,5 +1,7 @@ #include "mainwindow.h" +#include "addressbook.h" #include "ui_mainwindow.h" +#include "ui_addressbook.h" #include "ui_zboard.h" #include "ui_privkey.h" #include "ui_about.h" @@ -50,6 +52,9 @@ MainWindow::MainWindow(QWidget *parent) : // z-Board.net QObject::connect(ui->actionz_board_net, &QAction::triggered, this, &MainWindow::postToZBoard); + // Address Book + QObject::connect(ui->action_Address_Book, &QAction::triggered, this, &MainWindow::addressBook); + // Set up about action QObject::connect(ui->actionAbout, &QAction::triggered, [=] () { QDialog aboutDialog(this); @@ -404,9 +409,23 @@ void MainWindow::setupSettingsModal() { } }; }); - } +void MainWindow::addressBook() { + // Check to see if there is a target. + QRegExp re("Address[0-9]+", Qt::CaseInsensitive); + for (auto target: ui->sendToWidgets->findChildren(re)) { + if (target->hasFocus()) { + AddressBook::open(this, target); + return; + } + }; + + // If there was no target, then just run with no target. + AddressBook::open(this); +} + + void MainWindow::donate() { // Set up a donation to me :) ui->Address1->setText(Utils::getDonationAddr( @@ -452,11 +471,12 @@ void MainWindow::postToZBoard() { tx.fromAddr = zb.fromAddr->currentText(); if (tx.fromAddr.isEmpty()) { QMessageBox::critical(this, "Error Posting Message", "You need a sapling address with available balance to post", QMessageBox::Ok); + return; } auto memo = zb.memoTxt->toPlainText().trimmed(); if (!zb.postAs->text().trimmed().isEmpty()) - memo = zb.postAs->text().trimmed() + "::" + memo; + memo = zb.postAs->text().trimmed() + ":: " + memo; tx.toAddrs.push_back(ToFields{ Utils::getZboardAddr(), Utils::getZboardAmount(), memo, memo.toUtf8().toHex() }); tx.fee = Utils::getMinerFee(); diff --git a/src/mainwindow.h b/src/mainwindow.h index d1abc5d..4f5005c 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -81,6 +81,7 @@ private: QString doSendTxValidations(Tx tx); void donate(); + void addressBook(); void postToZBoard(); void importPrivKey(); void exportAllKeys(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 6534465..838cd52 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -22,7 +22,7 @@ - 2 + 1 @@ -343,6 +343,13 @@ + + + + Address Book + + + @@ -727,7 +734,6 @@ - @@ -746,7 +752,15 @@ + + + &Edit + + + + + @@ -805,6 +819,14 @@ Ctrl+A, Ctrl+Z + + + Address &Book + + + Ctrl+B + + diff --git a/src/precompiled.h b/src/precompiled.h index 34e5b02..9210ca5 100644 --- a/src/precompiled.h +++ b/src/precompiled.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -19,6 +21,7 @@ #include #include #include +#include #include #include #include diff --git a/src/sendtab.cpp b/src/sendtab.cpp index 61ceabd..146f556 100644 --- a/src/sendtab.cpp +++ b/src/sendtab.cpp @@ -1,5 +1,6 @@ #include "mainwindow.h" #include "ui_mainwindow.h" +#include "addressbook.h" #include "ui_confirm.h" #include "ui_memodialog.h" #include "settings.h" @@ -45,6 +46,12 @@ void MainWindow::setupSendTab() { this->addressChanged(1, text); }); + // The first address book button + QObject::connect(ui->AddressBook1, &QPushButton::clicked, [=] () { + AddressBook::open(this, ui->Address1); + }); + + // The first Amount button QObject::connect(ui->Amount1, &QLineEdit::textChanged, [=] (auto text) { this->amountChanged(1, text); @@ -143,6 +150,16 @@ void MainWindow::addAddressSection() { }); horizontalLayout_12->addWidget(Address1); + + auto addressBook1 = new QPushButton(verticalGroupBox); + addressBook1->setObjectName(QStringLiteral("AddressBook") % QString::number(itemNumber)); + addressBook1->setText("Address Book"); + QObject::connect(addressBook1, &QPushButton::clicked, [=] () { + AddressBook::open(this, Address1); + }); + + horizontalLayout_12->addWidget(addressBook1); + sendAddressLayout->addLayout(horizontalLayout_12); auto horizontalLayout_13 = new QHBoxLayout(); @@ -546,7 +563,5 @@ QString MainWindow::doSendTxValidations(Tx tx) { void MainWindow::cancelButton() { removeExtraAddresses(); - // Back to the balances tab - ui->tabWidget->setCurrentIndex(0); } diff --git a/src/utils.cpp b/src/utils.cpp index 55380af..bffe3e9 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -87,4 +87,15 @@ double Utils::getDevFee() { return 0; } } + double Utils::getTotalFee() { return getMinerFee() + getDevFee(); } + +bool Utils::isValidAddress(QString addr) { + QRegExp zcexp("^z[a-z0-9]{94}$", Qt::CaseInsensitive); + QRegExp zsexp("^z[a-z0-9]{77}$", Qt::CaseInsensitive); + QRegExp ztsexp("^ztestsapling[a-z0-9]{76}", Qt::CaseInsensitive); + QRegExp texp("^t[a-z0-9]{34}$", Qt::CaseInsensitive); + + return zcexp.exactMatch(addr) || texp.exactMatch(addr) || + ztsexp.exactMatch(addr) || zsexp.exactMatch(addr); +} diff --git a/src/utils.h b/src/utils.h index 19d1ed5..d7f9796 100644 --- a/src/utils.h +++ b/src/utils.h @@ -22,6 +22,8 @@ public: static double getDevFee(); static double getTotalFee(); + static bool isValidAddress(QString addr); + static const int updateSpeed = 20 * 1000; // 20 sec static const int quickUpdateSpeed = 5 * 1000; // 5 sec static const int priceRefreshSpeed = 60 * 60 * 1000; // 1 hr diff --git a/src/zboard.ui b/src/zboard.ui index a37e8f7..504d251 100644 --- a/src/zboard.ui +++ b/src/zboard.ui @@ -95,7 +95,7 @@ - <html><head/><body><p>ZBoard is Fully anonymous and untraceable chat messages based on the ZCash blockchain. <a href="http://www.z-board.net/"><span style=" text-decoration: underline; color:#0000ff;">http://www.z-board.net/</span></a></p><p>Posting to ZBoard: #Main_Area</p></body></html> + <html><head/><body><p>ZBoard is a fully anonymous and untraceable chat messages based on the ZCash blockchain. <a href="http://www.z-board.net/"><span style=" text-decoration: underline; color:#0000ff;">http://www.z-board.net/</span></a></p><p>Posting to ZBoard: #Main_Area</p></body></html> true diff --git a/zec-qt-wallet.pro b/zec-qt-wallet.pro index 3f7bcc5..c4088dc 100644 --- a/zec-qt-wallet.pro +++ b/zec-qt-wallet.pro @@ -53,7 +53,8 @@ SOURCES += \ src/turnstile.cpp \ src/utils.cpp \ src/qrcodelabel.cpp \ - src/connection.cpp + src/connection.cpp \ + src/addressbook.cpp HEADERS += \ src/mainwindow.h \ @@ -71,7 +72,8 @@ HEADERS += \ src/turnstile.h \ src/utils.h \ src/qrcodelabel.h \ - src/connection.h + src/connection.h \ + src/addressbook.h FORMS += \ src/mainwindow.ui \ @@ -83,7 +85,8 @@ FORMS += \ src/privkey.ui \ src/memodialog.ui \ src/connection.ui \ - src/zboard.ui + src/zboard.ui \ + src/addressbook.ui win32: RC_ICONS = res/icon.ico