diff --git a/src/confirm.ui b/src/confirm.ui index 5473136..ea6cce7 100644 --- a/src/confirm.ui +++ b/src/confirm.ui @@ -59,6 +59,13 @@ + + + + TextLabel + + + diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1594b1e..fdd0346 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -29,18 +29,23 @@ MainWindow::MainWindow(QWidget *parent) : ui->statusBar->addPermanentWidget(loadingLabel); loadingLabel->setVisible(false); + // Custom status bar menu ui->statusBar->setContextMenuPolicy(Qt::CustomContextMenu); QObject::connect(ui->statusBar, &QStatusBar::customContextMenuRequested, [=](QPoint pos) { auto msg = ui->statusBar->currentMessage(); - if (!msg.isEmpty() && msg.startsWith(Utils::txidStatusMessage)) { - QMenu menu(this); + QMenu menu(this); + + if (!msg.isEmpty() && msg.startsWith(Utils::txidStatusMessage)) { menu.addAction("Copy txid", [=]() { QGuiApplication::clipboard()->setText(msg.split(":")[1].trimmed()); }); - QPoint gpos(mapToGlobal(pos).x(), mapToGlobal(pos).y() + this->height() - ui->statusBar->height()); - menu.exec(gpos); } - + + menu.addAction("Refresh", [=]() { + rpc->refresh(); + }); + QPoint gpos(mapToGlobal(pos).x(), mapToGlobal(pos).y() + this->height() - ui->statusBar->height()); + menu.exec(gpos); }); statusLabel = new QLabel(); diff --git a/src/mainwindow.h b/src/mainwindow.h index c64460f..4f2466a 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -7,8 +7,16 @@ class RPC; class Settings; +// Struct used to hold destination info when sending a Tx. +struct ToFields { + QString addr; + double amount; + QString txtMemo; + QString encodedMemo; +}; + namespace Ui { -class MainWindow; + class MainWindow; } class MainWindow : public QMainWindow @@ -40,7 +48,9 @@ private: void addAddressSection(); void maxAmountChecked(int checked); - QString doSendTxValidations(QString fromAddr, QList> toAddrs); + void memoButtonClicked(int number); + + QString doSendTxValidations(QString fromAddr, QList toAddrs); void donate(); void importPrivKeys(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 8c79e83..4b8a4c2 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -216,7 +216,7 @@ - Address Balance: + Address Balance @@ -259,7 +259,22 @@ Send To + + false + + + 0 + + + 0 + + + 0 + + + 0 + @@ -273,8 +288,8 @@ 0 0 - 823 - 226 + 841 + 288 @@ -327,7 +342,7 @@ - Maximum Available + Max Available @@ -344,8 +359,33 @@ + + + + true + + + + + + Memo + + + + + + + + 10 + + + + + + + @@ -413,47 +453,44 @@ - - - Fees - - - - - - - - Fee - - - - - - - - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - + + + + + Fee + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + diff --git a/src/rpc.cpp b/src/rpc.cpp index 51c600c..f6c88e9 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -30,7 +30,7 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) { QObject::connect(timer, &QTimer::timeout, [=]() { refresh(); }); - timer->start(10 * 1000); + timer->start(Utils::updateSpeed); // Set up the timer to watch for tx status txTimer = new QTimer(main); @@ -38,7 +38,7 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) { refreshTxStatus(); }); // Start at every 10s. When an operation is pending, this will change to every second - txTimer->start(10 * 1000); + txTimer->start(Utils::updateSpeed); } RPC::~RPC() { @@ -457,7 +457,7 @@ void RPC::refreshTxStatus(const QString& newOpid) { main->loadingLabel->setVisible(false); watchingOps.remove(id); - txTimer->start(10 * 1000); + txTimer->start(Utils::updateSpeed); // Refresh balances to show unconfirmed balances refresh(); diff --git a/src/rpc.h b/src/rpc.h index f4cf2a0..7f505a6 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -32,6 +32,7 @@ public: void newZaddr (const std::function& cb); void newTaddr (const std::function& cb); + private: void doRPC (const json& payload, const std::function& cb); void doSendRPC (const json& payload, const std::function& cb); diff --git a/src/sendtab.cpp b/src/sendtab.cpp index 4bfa568..1dbab2d 100644 --- a/src/sendtab.cpp +++ b/src/sendtab.cpp @@ -33,11 +33,19 @@ void MainWindow::setupSendTab() { // Max available Checkbox QObject::connect(ui->Max1, &QCheckBox::stateChanged, this, &MainWindow::maxAmountChecked); + // The first Memo button + QObject::connect(ui->MemoBtn1, &QPushButton::clicked, [=] () { + memoButtonClicked(1); + }); + // Set up focus enter to set fees QObject::connect(ui->tabWidget, &QTabWidget::currentChanged, [=] (int pos) { if (pos == 1) { // Set the fees ui->sendTxFees->setText("0.0001 " + Utils::getTokenName()); + + // Set focus to the first address box + ui->Address1->setFocus(); } }); @@ -117,30 +125,78 @@ void MainWindow::addAddressSection() { // Create the validator for send to/amount fields auto amtValidator = new QDoubleValidator(0, 21000000, 8, Amount1); Amount1->setValidator(amtValidator); - horizontalLayout_13->addWidget(Amount1); - auto horizontalSpacer_4 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + auto horizontalSpacer_4 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_13->addItem(horizontalSpacer_4); + + + auto MemoBtn1 = new QPushButton(verticalGroupBox); + MemoBtn1->setObjectName(QString("MemoBtn") % QString::number(itemNumber)); + MemoBtn1->setText("Memo"); + // Connect Memo Clicked button + QObject::connect(MemoBtn1, &QPushButton::clicked, [=] () { + memoButtonClicked(itemNumber); + }); + horizontalLayout_13->addWidget(MemoBtn1); + sendAddressLayout->addLayout(horizontalLayout_13); + auto MemoTxt1 = new QLabel(verticalGroupBox); + MemoTxt1->setObjectName(QString("MemoTxt") % QString::number(itemNumber)); + QFont font1; + font1.setPointSize(10); + MemoTxt1->setFont(font1); + sendAddressLayout->addWidget(MemoTxt1); + ui->sendToLayout->insertWidget(itemNumber-1, verticalGroupBox); + // Set focus into the address + Address1->setFocus(); + // Delay the call to scroll to allow the scroll window to adjust QTimer::singleShot(10, [=] () {ui->sendToScrollArea->ensureWidgetVisible(ui->addAddressButton);}); } +void MainWindow::memoButtonClicked(int number) { + // Memos can only be used with zAddrs. So check that first + auto addr = ui->sendToWidgets->findChild(QString("Address") + QString::number(number)); + if (!addr->text().trimmed().startsWith("z")) { + QMessageBox msg(QMessageBox::Critical, "Memos can only be used with z Addresses", + "The Memo field can only be used with a z Address.\n" + addr->text() + "\ndoesn't look like a z Address", + QMessageBox::Ok, this); + + msg.exec(); + return; + } + + auto memoTxt = ui->sendToWidgets->findChild(QString("MemoTxt") + QString::number(number)); + // Get the current memo if it exists + QString currentMemo = memoTxt->text(); + + // Ref to see if the button was clicked + bool ok; + QString newMemo = QInputDialog::getText(this, "Memo", + "Please type a memo to include with the amount. The memo will be visible to the recepient", + QLineEdit::Normal, currentMemo, &ok); + if (ok) { + memoTxt->setText(newMemo); + } +} + void MainWindow::removeExtraAddresses() { // The last one is a spacer, so ignore that int totalItems = ui->sendToWidgets->children().size() - 2; - // Clear the first field + // Clear the first recepient fields auto addr = ui->sendToWidgets->findChild(QString("Address1")); addr->clear(); auto amt = ui->sendToWidgets->findChild(QString("Amount1")); amt->clear(); auto max = ui->sendToWidgets->findChild(QString("Max1")); max->setChecked(false); + auto memo = ui->sendToWidgets->findChild(QString("MemoTxt1")); + memo->clear(); // Start the deletion after the first item, since we want to keep 1 send field there all there for (int i=1; i < totalItems; i++) { @@ -179,6 +235,7 @@ void MainWindow::maxAmountChecked(int checked) { } } + void MainWindow::sendButton() { auto fnSplitAddressForWrap = [=] (const QString& a) -> QString { if (!a.startsWith("z")) return a; @@ -191,13 +248,16 @@ void MainWindow::sendButton() { // Gather the from / to addresses QString fromAddr = ui->inputsCombo->currentText().split("(")[0].trimmed(); - QList> toAddrs; + + QList toAddrs; // For each addr/amt in the sendTo tab int totalItems = ui->sendToWidgets->children().size() - 2; // The last one is a spacer, so ignore that for (int i=0; i < totalItems; i++) { - auto addr = ui->sendToWidgets->findChild(QString("Address") % QString::number(i+1))->text().trimmed(); - auto amt = ui->sendToWidgets->findChild(QString("Amount") % QString::number(i+1))->text().trimmed().toDouble(); - toAddrs.push_back(QPair(addr, amt)); + QString addr = ui->sendToWidgets->findChild(QString("Address") % QString::number(i+1))->text().trimmed(); + 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(); + + toAddrs.push_back( ToFields{addr, amt, memo, memo.toUtf8().toHex()} ); } QString error = doSendTxValidations(fromAddr, toAddrs); @@ -226,23 +286,27 @@ void MainWindow::sendButton() { // Remove all existing address/amt qlabels int totalConfirmAddrItems = confirm.sendToAddrs->children().size(); - for (int i = 0; i < totalConfirmAddrItems; i++) { - auto addr = confirm.sendToAddrs->findChild(QString("Addr") % QString::number(i+1)); - auto amt = confirm.sendToAddrs->findChild(QString("Amt") % QString::number(i+1)); + for (int i = 0; i < totalConfirmAddrItems / 3; i++) { + auto addr = confirm.sendToAddrs->findChild(QString("Addr") % QString::number(i+1)); + auto amt = confirm.sendToAddrs->findChild(QString("Amt") % QString::number(i+1)); + auto memo = confirm.sendToAddrs->findChild(QString("Memo") % QString::number(i+1)); + delete memo; delete addr; delete amt; } - // For each addr/amt - //std::for_each(toAddr.begin(), toAddr.end(), [&] (auto toAddr) { + // For each addr/amt/memo, construct the JSON and also build the confirm dialog box for (int i=0; i < toAddrs.size(); i++) { auto toAddr = toAddrs[i]; // Construct the JSON params json rec = json::object(); - rec["address"] = toAddr.first.toStdString(); - rec["amount"] = toAddr.second; + rec["address"] = toAddr.addr.toStdString(); + rec["amount"] = toAddr.amount; + if (toAddr.addr.startsWith("z")) + rec["memo"] = toAddr.encodedMemo.toStdString(); + allRecepients.push_back(rec); // Add new Address widgets instead of the same one. @@ -250,14 +314,25 @@ void MainWindow::sendButton() { auto Addr = new QLabel(confirm.sendToAddrs); Addr->setObjectName(QString("Addr") % QString::number(i + 1)); Addr->setWordWrap(true); - Addr->setText(fnSplitAddressForWrap(toAddr.first)); - confirm.gridLayout->addWidget(Addr, i, 0, 1, 1); + Addr->setText(fnSplitAddressForWrap(toAddr.addr)); + confirm.gridLayout->addWidget(Addr, i*2, 0, 1, 1); auto Amt = new QLabel(confirm.sendToAddrs); Amt->setObjectName(QString("Amt") % QString::number(i + 1)); - Amt->setText(QString::number(toAddr.second, 'g', 8) % " " % Utils::getTokenName()); + Amt->setText(QString::number(toAddr.amount, 'g', 8) % " " % Utils::getTokenName()); Amt->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter); - confirm.gridLayout->addWidget(Amt, i, 1, 1, 1); + confirm.gridLayout->addWidget(Amt, i*2, 1, 1, 1); + + if (toAddr.addr.startsWith("z")) { + auto Memo = new QLabel(confirm.sendToAddrs); + Memo->setObjectName(QStringLiteral("Memo") % QString::number(i + 1)); + Memo->setText(toAddr.txtMemo); + QFont font1; + font1.setPointSize(10); + Memo->setFont(font1); + + confirm.gridLayout->addWidget(Memo, (i*2)+1, 0, 1, 2); + } } } @@ -270,7 +345,7 @@ void MainWindow::sendButton() { confirm.sendFrom->setText(fnSplitAddressForWrap(fromAddr)); // Fees in the confirm dialog - confirm.feesLabel->setText("Fees: 0.0001 " % Utils::getTokenName()); + confirm.feesLabel->setText("Fee 0.0001 " % Utils::getTokenName()); // Show the dialog and submit it if the user confirms if (d.exec() == QDialog::Accepted) { @@ -287,7 +362,7 @@ void MainWindow::sendButton() { } } -QString MainWindow::doSendTxValidations(QString fromAddr, QList> toAddrs) { +QString MainWindow::doSendTxValidations(QString fromAddr, QList toAddrs) { // 1. Addresses are valid format. QRegExp zcexp("^z[a-z0-9]{94}$", Qt::CaseInsensitive); QRegExp zsexp("^z[a-z0-9]{77}$", Qt::CaseInsensitive); @@ -304,8 +379,8 @@ QString MainWindow::doSendTxValidations(QString fromAddr, QListfirst)) - return QString("To Address ") % toAddr->first % " is Invalid"; + if (!matchesAnyAddr(toAddr->addr)) + return QString("Recipient Address ") % toAddr->addr % " is Invalid"; }; return QString(); diff --git a/src/ui_confirm.h b/src/ui_confirm.h index ff1fb97..8c4ff29 100644 --- a/src/ui_confirm.h +++ b/src/ui_confirm.h @@ -33,6 +33,7 @@ public: QGridLayout *gridLayout; QLabel *Addr1; QLabel *Amt1; + QLabel *Memo1; QSpacerItem *verticalSpacer; QFrame *line; QLabel *feesLabel; @@ -74,6 +75,11 @@ public: gridLayout->addWidget(Amt1, 0, 1, 1, 1); + Memo1 = new QLabel(sendToAddrs); + Memo1->setObjectName(QStringLiteral("Memo1")); + + gridLayout->addWidget(Memo1, 1, 0, 1, 2); + verticalLayout->addWidget(sendToAddrs); @@ -116,6 +122,7 @@ public: sendToAddrs->setTitle(QApplication::translate("confirm", "To", nullptr)); Addr1->setText(QApplication::translate("confirm", "TextLabel", nullptr)); Amt1->setText(QApplication::translate("confirm", "TextLabel", nullptr)); + Memo1->setText(QApplication::translate("confirm", "TextLabel", nullptr)); feesLabel->setText(QString()); } // retranslateUi diff --git a/src/ui_mainwindow.h b/src/ui_mainwindow.h index 2070bea..e83719c 100644 --- a/src/ui_mainwindow.h +++ b/src/ui_mainwindow.h @@ -96,13 +96,13 @@ public: QLineEdit *Amount1; QCheckBox *Max1; QSpacerItem *horizontalSpacer_4; + QPushButton *MemoBtn1; + QLabel *MemoTxt1; QHBoxLayout *horizontalLayout_7; QSpacerItem *horizontalSpacer_2; QPushButton *addAddressButton; QSpacerItem *horizontalSpacer_3; QSpacerItem *verticalSpacer_2; - QGroupBox *groupBox_7; - QVBoxLayout *verticalLayout_10; QHBoxLayout *horizontalLayout_14; QLabel *label_7; QLineEdit *sendTxFees; @@ -342,17 +342,19 @@ public: groupBox_3 = new QGroupBox(tab_2); groupBox_3->setObjectName(QStringLiteral("groupBox_3")); + groupBox_3->setFlat(false); verticalLayout_3 = new QVBoxLayout(groupBox_3); verticalLayout_3->setSpacing(6); verticalLayout_3->setContentsMargins(11, 11, 11, 11); verticalLayout_3->setObjectName(QStringLiteral("verticalLayout_3")); + verticalLayout_3->setContentsMargins(0, 0, 0, 0); sendToScrollArea = new QScrollArea(groupBox_3); sendToScrollArea->setObjectName(QStringLiteral("sendToScrollArea")); sendToScrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); sendToScrollArea->setWidgetResizable(true); sendToWidgets = new QWidget(); sendToWidgets->setObjectName(QStringLiteral("sendToWidgets")); - sendToWidgets->setGeometry(QRect(0, 0, 823, 226)); + sendToWidgets->setGeometry(QRect(0, 0, 841, 288)); sendToLayout = new QVBoxLayout(sendToWidgets); sendToLayout->setSpacing(6); sendToLayout->setContentsMargins(11, 11, 11, 11); @@ -402,9 +404,23 @@ public: horizontalLayout_13->addItem(horizontalSpacer_4); + MemoBtn1 = new QPushButton(verticalGroupBox); + MemoBtn1->setObjectName(QStringLiteral("MemoBtn1")); + MemoBtn1->setEnabled(true); + + horizontalLayout_13->addWidget(MemoBtn1); + sendAddressLayout->addLayout(horizontalLayout_13); + MemoTxt1 = new QLabel(verticalGroupBox); + MemoTxt1->setObjectName(QStringLiteral("MemoTxt1")); + QFont font1; + font1.setPointSize(10); + MemoTxt1->setFont(font1); + + sendAddressLayout->addWidget(MemoTxt1); + sendToLayout->addWidget(verticalGroupBox); @@ -439,22 +455,18 @@ public: verticalLayout_4->addWidget(groupBox_3); - groupBox_7 = new QGroupBox(tab_2); - groupBox_7->setObjectName(QStringLiteral("groupBox_7")); - verticalLayout_10 = new QVBoxLayout(groupBox_7); - verticalLayout_10->setSpacing(6); - verticalLayout_10->setContentsMargins(11, 11, 11, 11); - verticalLayout_10->setObjectName(QStringLiteral("verticalLayout_10")); horizontalLayout_14 = new QHBoxLayout(); horizontalLayout_14->setSpacing(6); horizontalLayout_14->setObjectName(QStringLiteral("horizontalLayout_14")); - label_7 = new QLabel(groupBox_7); + label_7 = new QLabel(tab_2); label_7->setObjectName(QStringLiteral("label_7")); horizontalLayout_14->addWidget(label_7); - sendTxFees = new QLineEdit(groupBox_7); + sendTxFees = new QLineEdit(tab_2); sendTxFees->setObjectName(QStringLiteral("sendTxFees")); + sizePolicy.setHeightForWidth(sendTxFees->sizePolicy().hasHeightForWidth()); + sendTxFees->setSizePolicy(sizePolicy); sendTxFees->setReadOnly(true); horizontalLayout_14->addWidget(sendTxFees); @@ -464,10 +476,7 @@ public: horizontalLayout_14->addItem(horizontalSpacer_5); - verticalLayout_10->addLayout(horizontalLayout_14); - - - verticalLayout_4->addWidget(groupBox_7); + verticalLayout_4->addLayout(horizontalLayout_14); horizontalLayout_6 = new QHBoxLayout(); horizontalLayout_6->setSpacing(6); @@ -673,16 +682,20 @@ public: groupBox_2->setTitle(QApplication::translate("MainWindow", "Address Balances", nullptr)); tabWidget->setTabText(tabWidget->indexOf(tab), QApplication::translate("MainWindow", "Balance", nullptr)); groupBox_4->setTitle(QApplication::translate("MainWindow", "Pay From", nullptr)); - label_5->setText(QApplication::translate("MainWindow", "Address Balance:", nullptr)); + label_5->setText(QApplication::translate("MainWindow", "Address Balance", nullptr)); groupBox_3->setTitle(QApplication::translate("MainWindow", "Send To", nullptr)); verticalGroupBox->setTitle(QApplication::translate("MainWindow", "Recipient", nullptr)); label_4->setText(QApplication::translate("MainWindow", "Address", nullptr)); Address1->setPlaceholderText(QApplication::translate("MainWindow", "Address", nullptr)); label_6->setText(QApplication::translate("MainWindow", "Amount", nullptr)); Amount1->setPlaceholderText(QApplication::translate("MainWindow", "Amount", nullptr)); - Max1->setText(QApplication::translate("MainWindow", "Maximum Available", nullptr)); + Max1->setText(QApplication::translate("MainWindow", "Max Available", nullptr)); +#ifndef QT_NO_TOOLTIP + MemoBtn1->setToolTip(QString()); +#endif // QT_NO_TOOLTIP + MemoBtn1->setText(QApplication::translate("MainWindow", "Memo", nullptr)); + MemoTxt1->setText(QString()); addAddressButton->setText(QApplication::translate("MainWindow", "Add Address", nullptr)); - groupBox_7->setTitle(QApplication::translate("MainWindow", "Fees", nullptr)); label_7->setText(QApplication::translate("MainWindow", "Fee", nullptr)); sendTxFees->setText(QString()); sendTransactionButton->setText(QApplication::translate("MainWindow", "Send", nullptr)); diff --git a/src/utils.h b/src/utils.h index db0b139..5408ae1 100644 --- a/src/utils.h +++ b/src/utils.h @@ -9,6 +9,8 @@ public: static const QString txidStatusMessage; static const QString getTokenName(); + + static const int updateSpeed = 20 * 1000; // 20 sec private: Utils() = delete; };