From cf0c3b8d82eb4774667248d1a4fac833f173675e Mon Sep 17 00:00:00 2001 From: tobtoht Date: Sun, 7 Jan 2024 20:19:38 +0100 Subject: [PATCH] Settings: allow configuring block explorer urls --- src/MainWindow.cpp | 6 +- src/SettingsDialog.cpp | 7 +- src/SettingsDialog.h | 1 - src/SettingsDialog.ui | 48 ++--------- src/dialog/MultiLineInputDialog.cpp | 39 +++++++++ src/dialog/MultiLineInputDialog.h | 30 +++++++ src/dialog/MultiLineInputDialog.ui | 81 ++++++++++++++++++ src/dialog/TxInfoDialog.cpp | 9 +- src/utils/Utils.cpp | 56 +++---------- src/utils/Utils.h | 2 +- src/utils/config.cpp | 8 +- src/utils/config.h | 1 + src/widgets/UrlListConfigureWidget.cpp | 112 +++++++++++++++++++++++++ src/widgets/UrlListConfigureWidget.h | 42 ++++++++++ src/widgets/UrlListConfigureWidget.ui | 50 +++++++++++ 15 files changed, 397 insertions(+), 95 deletions(-) create mode 100644 src/dialog/MultiLineInputDialog.cpp create mode 100644 src/dialog/MultiLineInputDialog.h create mode 100644 src/dialog/MultiLineInputDialog.ui create mode 100644 src/widgets/UrlListConfigureWidget.cpp create mode 100644 src/widgets/UrlListConfigureWidget.h create mode 100644 src/widgets/UrlListConfigureWidget.ui diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 5e06315..8f67db7 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1237,7 +1237,11 @@ void MainWindow::payToMany() { } void MainWindow::onViewOnBlockExplorer(const QString &txid) { - QString blockExplorerLink = Utils::blockExplorerLink(conf()->get(Config::blockExplorer).toString(), constants::networkType, txid); + QString blockExplorerLink = Utils::blockExplorerLink(txid); + if (blockExplorerLink.isEmpty()) { + Utils::showError(this, "Unable to open block explorer", "No block explorer configured", {"Go to Settings -> Misc -> Block explorer"}); + return; + } Utils::externalLinkWarning(this, blockExplorerLink); } diff --git a/src/SettingsDialog.cpp b/src/SettingsDialog.cpp index bd00a65..28cee3e 100644 --- a/src/SettingsDialog.cpp +++ b/src/SettingsDialog.cpp @@ -329,12 +329,7 @@ void Settings::setupPluginsTab() { void Settings::setupMiscTab() { // [Block explorer] - ui->comboBox_blockExplorer->setCurrentIndex(ui->comboBox_blockExplorer->findText(conf()->get(Config::blockExplorer).toString())); - connect(ui->comboBox_blockExplorer, QOverload::of(&QComboBox::currentIndexChanged), [this]{ - QString blockExplorer = ui->comboBox_blockExplorer->currentText(); - conf()->set(Config::blockExplorer, blockExplorer); - emit blockExplorerChanged(blockExplorer); - }); + ui->blockExplorerConfigureWidget->setup("Block explorers", Config::blockExplorers, Config::blockExplorer, {"%txid%"}); // [Reddit frontend] ui->comboBox_redditFrontend->setCurrentIndex(ui->comboBox_redditFrontend->findText(conf()->get(Config::redditFrontend).toString())); diff --git a/src/SettingsDialog.h b/src/SettingsDialog.h index 9f8a48e..10703e5 100644 --- a/src/SettingsDialog.h +++ b/src/SettingsDialog.h @@ -39,7 +39,6 @@ public: signals: void preferredFiatCurrencyChanged(QString currency); void skinChanged(QString skinName); - void blockExplorerChanged(QString blockExplorer); void hideUpdateNotifications(bool hidden); void websocketStatusChanged(bool enabled); void proxySettingsChanged(); diff --git a/src/SettingsDialog.ui b/src/SettingsDialog.ui index 882701c..897e7c6 100644 --- a/src/SettingsDialog.ui +++ b/src/SettingsDialog.ui @@ -6,7 +6,7 @@ 0 0 - 841 + 908 454 @@ -32,7 +32,7 @@ - 1 + 7 @@ -1051,43 +1051,7 @@ - - - - exploremonero.com - - - - - xmrchain.net - - - - - melo.tools - - - - - moneroblocks.info - - - - - blockchair.info - - - - - blkchairbknpn73cfjhevhla7rkp4ed5gg2knctvv7it4lioy22defid.onion - - - - - 127.0.0.1:31312 - - - + @@ -1201,6 +1165,12 @@
widgets/PluginWidget.h
1 + + UrlListConfigureWidget + QWidget +
widgets/UrlListConfigureWidget.h
+ 1 +
diff --git a/src/dialog/MultiLineInputDialog.cpp b/src/dialog/MultiLineInputDialog.cpp new file mode 100644 index 0000000..f3923f1 --- /dev/null +++ b/src/dialog/MultiLineInputDialog.cpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#include "MultiLineInputDialog.h" +#include "ui_MultiLineInputDialog.h" + +#include +#include + +#include "utils/Utils.h" + +MultiLineInputDialog::MultiLineInputDialog(QWidget *parent, const QString &title, const QString &label, const QStringList &defaultList) + : WindowModalDialog(parent) + , ui(new Ui::MultiLineInputDialog) +{ + ui->setupUi(this); + + this->setWindowTitle(title); + ui->label->setText(label); + + QFontMetrics metrics(ui->plainTextEdit->font()); + int maxWidth = 0; + for (const QString &line : defaultList) { + int width = metrics.boundingRect(line).width(); + maxWidth = qMax(maxWidth, width); + } + ui->plainTextEdit->setMinimumWidth(maxWidth + 10); + + ui->plainTextEdit->setWordWrapMode(QTextOption::NoWrap); + ui->plainTextEdit->setPlainText(defaultList.join("\n") + "\n"); + + this->adjustSize(); +} + +QStringList MultiLineInputDialog::getList() { + return ui->plainTextEdit->toPlainText().split("\n"); +} + +MultiLineInputDialog::~MultiLineInputDialog() = default; \ No newline at end of file diff --git a/src/dialog/MultiLineInputDialog.h b/src/dialog/MultiLineInputDialog.h new file mode 100644 index 0000000..a6a9c64 --- /dev/null +++ b/src/dialog/MultiLineInputDialog.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#ifndef FEATHER_MULTILINEINPUTDIALOG_H +#define FEATHER_MULTILINEINPUTDIALOG_H + +#include + +#include "components.h" + +namespace Ui { + class MultiLineInputDialog; +} + +class MultiLineInputDialog : public WindowModalDialog +{ +Q_OBJECT + +public: + explicit MultiLineInputDialog(QWidget *parent, const QString &title, const QString &label, const QStringList &defaultList); + ~MultiLineInputDialog() override; + + QStringList getList(); + +private: + QScopedPointer ui; +}; + + +#endif //FEATHER_MULTILINEINPUTDIALOG_H diff --git a/src/dialog/MultiLineInputDialog.ui b/src/dialog/MultiLineInputDialog.ui new file mode 100644 index 0000000..653bab1 --- /dev/null +++ b/src/dialog/MultiLineInputDialog.ui @@ -0,0 +1,81 @@ + + + MultiLineInputDialog + + + + 0 + 0 + 663 + 357 + + + + Dialog + + + + + + + + + + + + + + 500 + 0 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + MultiLineInputDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MultiLineInputDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/dialog/TxInfoDialog.cpp b/src/dialog/TxInfoDialog.cpp index b774af5..e014cb1 100644 --- a/src/dialog/TxInfoDialog.cpp +++ b/src/dialog/TxInfoDialog.cpp @@ -181,7 +181,14 @@ void TxInfoDialog::createTxProof() { } void TxInfoDialog::viewOnBlockExplorer() { - Utils::externalLinkWarning(this, Utils::blockExplorerLink(conf()->get(Config::blockExplorer).toString(), constants::networkType, m_txid)); + QString link = Utils::blockExplorerLink(m_txid); + + if (link.isEmpty()) { + Utils::showError(this, "Unable to open block explorer", "No block explorer configured", {"Go to Settings -> Misc -> Block explorer"}); + return; + } + + Utils::externalLinkWarning(this, link); } TxInfoDialog::~TxInfoDialog() = default; \ No newline at end of file diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp index 679a38e..e625aea 100644 --- a/src/utils/Utils.cpp +++ b/src/utils/Utils.cpp @@ -461,53 +461,19 @@ QStandardItem *qStandardItem(const QString& text, QFont &font) { return item; } -QString blockExplorerLink(const QString &blockExplorer, NetworkType::Type nettype, const QString &txid) { - if (blockExplorer == "exploremonero.com") { - if (nettype == NetworkType::MAINNET) { - return QString("https://exploremonero.com/transaction/%1").arg(txid); - } - } - else if (blockExplorer == "moneroblocks.info") { - if (nettype == NetworkType::MAINNET) { - return QString("https://moneroblocks.info/tx/%1").arg(txid); - } - } - else if (blockExplorer == "blockchair.com") { - if (nettype == NetworkType::MAINNET) { - return QString("https://blockchair.com/monero/transaction/%1").arg(txid); - } - } - else if (blockExplorer == "melo.tools") { - switch (nettype) { - case NetworkType::MAINNET: - return QString("https://melo.tools/explorer/mainnet/tx/%1").arg(txid); - case NetworkType::STAGENET: - return QString("https://melo.tools/explorer/stagenet/tx/%1").arg(txid); - case NetworkType::TESTNET: - return QString("https://melo.tools/explorer/testnet/tx/%1").arg(txid); - } - } - else if (blockExplorer == "blkchairbknpn73cfjhevhla7rkp4ed5gg2knctvv7it4lioy22defid.onion") { - if (nettype == NetworkType::MAINNET) { - return QString("http://blkchairbknpn73cfjhevhla7rkp4ed5gg2knctvv7it4lioy22defid.onion/monero/transaction/%1").arg(txid); - } - } - else if (blockExplorer == "127.0.0.1:31312") { - if (nettype == NetworkType::MAINNET) { - return QString("http://127.0.0.1:31312/tx?id=%1").arg(txid); - } - } - - switch (nettype) { - case NetworkType::MAINNET: - return QString("https://xmrchain.net/tx/%1").arg(txid); - case NetworkType::STAGENET: - return QString("https://stagenet.xmrchain.net/tx/%1").arg(txid); - case NetworkType::TESTNET: - return QString("https://testnet.xmrchain.net/tx/%1").arg(txid); +QString blockExplorerLink(const QString &txid) { + QString link = conf()->get(Config::blockExplorer).toString(); + + QUrl url(link); + if (url.scheme() != "http" && url.scheme() != "https") { + return {}; } - return {}; + if (!link.contains("%txid%")) { + return {}; + } + + return link.replace("%txid%", txid); } void externalLinkWarning(QWidget *parent, const QString &url){ diff --git a/src/utils/Utils.h b/src/utils/Utils.h index dc70b0e..e9a1fba 100644 --- a/src/utils/Utils.h +++ b/src/utils/Utils.h @@ -85,7 +85,7 @@ namespace Utils QStandardItem *qStandardItem(const QString &text); QStandardItem *qStandardItem(const QString &text, QFont &font); - QString blockExplorerLink(const QString &blockExplorer, NetworkType::Type nettype, const QString &txid); + QString blockExplorerLink(const QString &txid); void externalLinkWarning(QWidget *parent, const QString &url); QString displayAddress(const QString& address, int sections = 3, const QString & sep = " "); diff --git a/src/utils/config.cpp b/src/utils/config.cpp index 081f3fa..8617109 100644 --- a/src/utils/config.cpp +++ b/src/utils/config.cpp @@ -93,7 +93,13 @@ static const QHash configStrings = { {Config::writeStackTraceToDisk, {QS("writeStackTraceToDisk"), true}}, {Config::writeRecentlyOpenedWallets, {QS("writeRecentlyOpenedWallets"), true}}, - {Config::blockExplorer,{QS("blockExplorer"), "exploremonero.com"}}, + {Config::blockExplorers, {QS("blockExplorers"), QStringList{"https://xmrchain.net/tx/%txid%", + "https://melo.tools/explorer/mainnet/tx/%txid%", + "https://moneroblocks.info/tx/%txid%", + "https://blockchair.com/monero/transaction/%txid%", + "http://blkchairbknpn73cfjhevhla7rkp4ed5gg2knctvv7it4lioy22defid.onion/monero/transaction/%txid%", + "http://127.0.0.1:31312/tx?id=%txid%"}}}, + {Config::blockExplorer,{QS("blockExplorer"), "https://xmrchain.net/tx/%txid%"}}, {Config::redditFrontend, {QS("redditFrontend"), "old.reddit.com"}}, {Config::localMoneroFrontend, {QS("localMoneroFrontend"), "https://localmonero.co"}}, {Config::bountiesFrontend, {QS("bountiesFrontend"), "https://bounties.monero.social"}}, diff --git a/src/utils/config.h b/src/utils/config.h index ac94584..59c1810 100644 --- a/src/utils/config.h +++ b/src/utils/config.h @@ -123,6 +123,7 @@ public: offlineTxSigningForceKISync, // Misc + blockExplorers, blockExplorer, redditFrontend, localMoneroFrontend, diff --git a/src/widgets/UrlListConfigureWidget.cpp b/src/widgets/UrlListConfigureWidget.cpp new file mode 100644 index 0000000..4616e55 --- /dev/null +++ b/src/widgets/UrlListConfigureWidget.cpp @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#include "UrlListConfigureWidget.h" +#include "ui_UrlListConfigureWidget.h" + +#include +#include +#include + +#include "dialog/MultiLineInputDialog.h" +#include "utils/config.h" +#include "utils/Utils.h" + +UrlListConfigureWidget::UrlListConfigureWidget(QWidget *parent) + : QWidget(parent) + , ui(new Ui::UrlListConfigureWidget) +{ + ui->setupUi(this); +} + +void UrlListConfigureWidget::setup(const QString &what, Config::ConfigKey list, Config::ConfigKey preferred, const QStringList &keys) { + m_what = what; + m_listKey = list; + m_preferredKey = preferred; + m_keys = keys; + + this->setupComboBox(); + + connect(ui->configure, &QPushButton::clicked, this, &UrlListConfigureWidget::onConfigureClicked); + connect(ui->comboBox, &QComboBox::currentIndexChanged, this, &UrlListConfigureWidget::onUrlSelected); +} + +void UrlListConfigureWidget::onConfigureClicked() { + QStringList list = conf()->get(m_listKey).toStringList(); + QStringList newList; + + while (true) { + auto input = MultiLineInputDialog(this, m_what, QString("Set %1 (one per line):").arg(m_what.toLower()), list); + auto status = input.exec(); + + if (status == QDialog::Rejected) { + break; + } + + newList = input.getList(); + newList.removeAll(""); + newList.removeDuplicates(); + + bool error = false; + for (const auto& item : newList) { + auto url = QUrl::fromUserInput(item); + qDebug() << url.scheme(); + if (url.scheme() != "http" && url.scheme() != "https") { + Utils::showError(this, QString("Invalid %1 entered").arg(m_what.toLower()), QString("Invalid URL: %1").arg(item)); + error = true; + break; + } + + for (const auto& key : m_keys) { + if (!item.contains(key)) { + Utils::showError(this, QString("Invalid %1 entered").arg(m_what.toLower()), QString("Key %1 missing from URL: %2").arg(key, item)); + error = true; + break; + } + } + } + if (error) { + list = newList; + continue; + } + + conf()->set(m_listKey, newList); + this->setupComboBox(); + break; + } +} + +void UrlListConfigureWidget::setupComboBox() { + m_urls = conf()->get(m_listKey).toStringList(); + + QStringList cleanList; + for (const auto &item : m_urls) { + QUrl url(item); + cleanList << url.host(); + } + + ui->comboBox->clear(); + ui->comboBox->insertItems(0, cleanList); + + if (m_urls.empty()) { + return; + } + + QString preferred = conf()->get(m_preferredKey).toString(); + if (m_urls.contains(preferred)) { + ui->comboBox->setCurrentIndex(m_urls.indexOf(preferred)); + } + else { + conf()->set(m_preferredKey, m_urls.at(0)); + } +} + +void UrlListConfigureWidget::onUrlSelected(int index) { + if (index < 0 || index >= m_urls.length()) { + return; + } + + conf()->set(m_preferredKey, m_urls.at(index)); +} + +UrlListConfigureWidget::~UrlListConfigureWidget() = default; diff --git a/src/widgets/UrlListConfigureWidget.h b/src/widgets/UrlListConfigureWidget.h new file mode 100644 index 0000000..b84bd54 --- /dev/null +++ b/src/widgets/UrlListConfigureWidget.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#ifndef FEATHER_URLLISTCONFIGUREWIDGET_H +#define FEATHER_URLLISTCONFIGUREWIDGET_H + +#include +#include + +#include "utils/config.h" + +namespace Ui { + class UrlListConfigureWidget; +} + +class UrlListConfigureWidget : public QWidget +{ +Q_OBJECT + +public: + explicit UrlListConfigureWidget(QWidget *parent = nullptr); + ~UrlListConfigureWidget() override; + + void setup(const QString &what, Config::ConfigKey list, Config::ConfigKey preferred, const QStringList& keys); + +private slots: + void onConfigureClicked(); + void onUrlSelected(int index); + +private: + void setupComboBox(); + + QScopedPointer ui; + + QString m_what; + Config::ConfigKey m_listKey; + Config::ConfigKey m_preferredKey; + QStringList m_keys; + QStringList m_urls; +}; + +#endif //FEATHER_URLLISTCONFIGUREWIDGET_H diff --git a/src/widgets/UrlListConfigureWidget.ui b/src/widgets/UrlListConfigureWidget.ui new file mode 100644 index 0000000..2b2c46e --- /dev/null +++ b/src/widgets/UrlListConfigureWidget.ui @@ -0,0 +1,50 @@ + + + UrlListConfigureWidget + + + + 0 + 0 + 617 + 27 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Configure + + + + + + + +