Settings: allow configuring block explorer urls

This commit is contained in:
tobtoht 2024-01-07 20:19:38 +01:00
parent a5ff6a3b67
commit cf0c3b8d82
No known key found for this signature in database
GPG key ID: E45B10DD027D2472
15 changed files with 397 additions and 95 deletions

View file

@ -1237,7 +1237,11 @@ void MainWindow::payToMany() {
} }
void MainWindow::onViewOnBlockExplorer(const QString &txid) { 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); Utils::externalLinkWarning(this, blockExplorerLink);
} }

View file

@ -329,12 +329,7 @@ void Settings::setupPluginsTab() {
void Settings::setupMiscTab() { void Settings::setupMiscTab() {
// [Block explorer] // [Block explorer]
ui->comboBox_blockExplorer->setCurrentIndex(ui->comboBox_blockExplorer->findText(conf()->get(Config::blockExplorer).toString())); ui->blockExplorerConfigureWidget->setup("Block explorers", Config::blockExplorers, Config::blockExplorer, {"%txid%"});
connect(ui->comboBox_blockExplorer, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{
QString blockExplorer = ui->comboBox_blockExplorer->currentText();
conf()->set(Config::blockExplorer, blockExplorer);
emit blockExplorerChanged(blockExplorer);
});
// [Reddit frontend] // [Reddit frontend]
ui->comboBox_redditFrontend->setCurrentIndex(ui->comboBox_redditFrontend->findText(conf()->get(Config::redditFrontend).toString())); ui->comboBox_redditFrontend->setCurrentIndex(ui->comboBox_redditFrontend->findText(conf()->get(Config::redditFrontend).toString()));

View file

@ -39,7 +39,6 @@ public:
signals: signals:
void preferredFiatCurrencyChanged(QString currency); void preferredFiatCurrencyChanged(QString currency);
void skinChanged(QString skinName); void skinChanged(QString skinName);
void blockExplorerChanged(QString blockExplorer);
void hideUpdateNotifications(bool hidden); void hideUpdateNotifications(bool hidden);
void websocketStatusChanged(bool enabled); void websocketStatusChanged(bool enabled);
void proxySettingsChanged(); void proxySettingsChanged();

View file

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>841</width> <width>908</width>
<height>454</height> <height>454</height>
</rect> </rect>
</property> </property>
@ -32,7 +32,7 @@
<item> <item>
<widget class="QStackedWidget" name="stackedWidget"> <widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>1</number> <number>7</number>
</property> </property>
<widget class="QWidget" name="page_appearance"> <widget class="QWidget" name="page_appearance">
<layout class="QVBoxLayout" name="verticalLayout_6"> <layout class="QVBoxLayout" name="verticalLayout_6">
@ -1051,43 +1051,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QComboBox" name="comboBox_blockExplorer"> <widget class="UrlListConfigureWidget" name="blockExplorerConfigureWidget" native="true"/>
<item>
<property name="text">
<string>exploremonero.com</string>
</property>
</item>
<item>
<property name="text">
<string>xmrchain.net</string>
</property>
</item>
<item>
<property name="text">
<string>melo.tools</string>
</property>
</item>
<item>
<property name="text">
<string>moneroblocks.info</string>
</property>
</item>
<item>
<property name="text">
<string>blockchair.info</string>
</property>
</item>
<item>
<property name="text">
<string>blkchairbknpn73cfjhevhla7rkp4ed5gg2knctvv7it4lioy22defid.onion</string>
</property>
</item>
<item>
<property name="text">
<string>127.0.0.1:31312</string>
</property>
</item>
</widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="label_18"> <widget class="QLabel" name="label_18">
@ -1201,6 +1165,12 @@
<header>widgets/PluginWidget.h</header> <header>widgets/PluginWidget.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>UrlListConfigureWidget</class>
<extends>QWidget</extends>
<header>widgets/UrlListConfigureWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections> <connections>

View file

@ -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 <QDialog>
#include <QFontMetrics>
#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;

View file

@ -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 <QDialog>
#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::MultiLineInputDialog> ui;
};
#endif //FEATHER_MULTILINEINPUTDIALOG_H

View file

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MultiLineInputDialog</class>
<widget class="QDialog" name="MultiLineInputDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>663</width>
<height>357</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="plainTextEdit">
<property name="minimumSize">
<size>
<width>500</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>MultiLineInputDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>MultiLineInputDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -181,7 +181,14 @@ void TxInfoDialog::createTxProof() {
} }
void TxInfoDialog::viewOnBlockExplorer() { 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; TxInfoDialog::~TxInfoDialog() = default;

View file

@ -461,53 +461,19 @@ QStandardItem *qStandardItem(const QString& text, QFont &font) {
return item; return item;
} }
QString blockExplorerLink(const QString &blockExplorer, NetworkType::Type nettype, const QString &txid) { QString blockExplorerLink(const QString &txid) {
if (blockExplorer == "exploremonero.com") { QString link = conf()->get(Config::blockExplorer).toString();
if (nettype == NetworkType::MAINNET) {
return QString("https://exploremonero.com/transaction/%1").arg(txid); QUrl url(link);
} if (url.scheme() != "http" && url.scheme() != "https") {
} return {};
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) { if (!link.contains("%txid%")) {
case NetworkType::MAINNET: return {};
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);
} }
return {}; return link.replace("%txid%", txid);
} }
void externalLinkWarning(QWidget *parent, const QString &url){ void externalLinkWarning(QWidget *parent, const QString &url){

View file

@ -85,7 +85,7 @@ namespace Utils
QStandardItem *qStandardItem(const QString &text); QStandardItem *qStandardItem(const QString &text);
QStandardItem *qStandardItem(const QString &text, QFont &font); 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); void externalLinkWarning(QWidget *parent, const QString &url);
QString displayAddress(const QString& address, int sections = 3, const QString & sep = " "); QString displayAddress(const QString& address, int sections = 3, const QString & sep = " ");

View file

@ -93,7 +93,13 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::writeStackTraceToDisk, {QS("writeStackTraceToDisk"), true}}, {Config::writeStackTraceToDisk, {QS("writeStackTraceToDisk"), true}},
{Config::writeRecentlyOpenedWallets, {QS("writeRecentlyOpenedWallets"), 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::redditFrontend, {QS("redditFrontend"), "old.reddit.com"}},
{Config::localMoneroFrontend, {QS("localMoneroFrontend"), "https://localmonero.co"}}, {Config::localMoneroFrontend, {QS("localMoneroFrontend"), "https://localmonero.co"}},
{Config::bountiesFrontend, {QS("bountiesFrontend"), "https://bounties.monero.social"}}, {Config::bountiesFrontend, {QS("bountiesFrontend"), "https://bounties.monero.social"}},

View file

@ -123,6 +123,7 @@ public:
offlineTxSigningForceKISync, offlineTxSigningForceKISync,
// Misc // Misc
blockExplorers,
blockExplorer, blockExplorer,
redditFrontend, redditFrontend,
localMoneroFrontend, localMoneroFrontend,

View file

@ -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 <QComboBox>
#include <QWidget>
#include <QInputDialog>
#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;

View file

@ -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 <QWidget>
#include <QTextEdit>
#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::UrlListConfigureWidget> ui;
QString m_what;
Config::ConfigKey m_listKey;
Config::ConfigKey m_preferredKey;
QStringList m_keys;
QStringList m_urls;
};
#endif //FEATHER_URLLISTCONFIGUREWIDGET_H

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UrlListConfigureWidget</class>
<widget class="QWidget" name="UrlListConfigureWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>617</width>
<height>27</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="comboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="configure">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>