]> Nutra Git (v1) - gamesguru/feather.git/commitdiff
Settings: allow configuring block explorer urls
authortobtoht <tob@featherwallet.org>
Sun, 7 Jan 2024 19:19:38 +0000 (20:19 +0100)
committertobtoht <tob@featherwallet.org>
Sun, 7 Jan 2024 19:19:38 +0000 (20:19 +0100)
15 files changed:
src/MainWindow.cpp
src/SettingsDialog.cpp
src/SettingsDialog.h
src/SettingsDialog.ui
src/dialog/MultiLineInputDialog.cpp [new file with mode: 0644]
src/dialog/MultiLineInputDialog.h [new file with mode: 0644]
src/dialog/MultiLineInputDialog.ui [new file with mode: 0644]
src/dialog/TxInfoDialog.cpp
src/utils/Utils.cpp
src/utils/Utils.h
src/utils/config.cpp
src/utils/config.h
src/widgets/UrlListConfigureWidget.cpp [new file with mode: 0644]
src/widgets/UrlListConfigureWidget.h [new file with mode: 0644]
src/widgets/UrlListConfigureWidget.ui [new file with mode: 0644]

index 5e0631584ced071f7fb8bfeb6a7a7dd515a58727..8f67db7dfaf155c9c811eafb3bc3af490128fa06 100644 (file)
@@ -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);
 }
 
index bd00a65690fcfce7edaa814d0d084d55b046a17f..28cee3ed76213cf4eca7c654445fa2dd26c21235 100644 (file)
@@ -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<int>::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()));
index 9f8a48ebecc2e8d00d11dd3484d8e165a2389270..10703e59fd13871bb557823b0a2240b9a42070ba 100644 (file)
@@ -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();
index 882701cacab4b4f0948dc789947b1c1c62c8feea..897e7c6874d324280c46da3632c83102d0922318 100644 (file)
@@ -6,7 +6,7 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>841</width>
+    <width>908</width>
     <height>454</height>
    </rect>
   </property>
@@ -32,7 +32,7 @@
      <item>
       <widget class="QStackedWidget" name="stackedWidget">
        <property name="currentIndex">
-        <number>1</number>
+        <number>7</number>
        </property>
        <widget class="QWidget" name="page_appearance">
         <layout class="QVBoxLayout" name="verticalLayout_6">
               </widget>
              </item>
              <item>
-              <widget class="QComboBox" name="comboBox_blockExplorer">
-               <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>
+              <widget class="UrlListConfigureWidget" name="blockExplorerConfigureWidget" native="true"/>
              </item>
              <item>
               <widget class="QLabel" name="label_18">
    <header>widgets/PluginWidget.h</header>
    <container>1</container>
   </customwidget>
+  <customwidget>
+   <class>UrlListConfigureWidget</class>
+   <extends>QWidget</extends>
+   <header>widgets/UrlListConfigureWidget.h</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  <resources/>
  <connections>
diff --git a/src/dialog/MultiLineInputDialog.cpp b/src/dialog/MultiLineInputDialog.cpp
new file mode 100644 (file)
index 0000000..f3923f1
--- /dev/null
@@ -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;
\ No newline at end of file
diff --git a/src/dialog/MultiLineInputDialog.h b/src/dialog/MultiLineInputDialog.h
new file mode 100644 (file)
index 0000000..a6a9c64
--- /dev/null
@@ -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
diff --git a/src/dialog/MultiLineInputDialog.ui b/src/dialog/MultiLineInputDialog.ui
new file mode 100644 (file)
index 0000000..653bab1
--- /dev/null
@@ -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>
index b774af5a035257d6938c1e1616bf4c6cac5fa6de..e014cb101c48a03105e18c41c1dc51971ebab954 100644 (file)
@@ -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
index 679a38ea6637c11454758e05c9ee3e5fe68c31d0..e625aea0b8adf206bae6a3202a30e5491bb94344 100644 (file)
@@ -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);
-        }
+QString blockExplorerLink(const QString &txid) {
+    QString link = conf()->get(Config::blockExplorer).toString();
+
+    QUrl url(link);
+    if (url.scheme() != "http" && url.scheme() != "https") {
+        return {};
     }
-    
-    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);
+
+    if (!link.contains("%txid%")) {
+        return {};
     }
 
-    return {};
+    return link.replace("%txid%", txid);
 }
 
 void externalLinkWarning(QWidget *parent, const QString &url){
index dc70b0e43dbd60cb1649be1b4ab50faa2beba168..e9a1fba8a0d4a09589d640e3b9b7a0c1a2653e40 100644 (file)
@@ -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 = " ");
index 081f3fa6eeeebbf6fd27d340de2e51eae414a500..8617109e8b3f62d5b9f93e342c1a3af7fe34e983 100644 (file)
@@ -93,7 +93,13 @@ static const QHash<Config::ConfigKey, ConfigDirective> 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"}},
index ac9458412a645a7be4dcaa33ea36ea9c567868fe..59c18102936feb02f5eb6c25d3a3062673ab4d54 100644 (file)
@@ -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 (file)
index 0000000..4616e55
--- /dev/null
@@ -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;
diff --git a/src/widgets/UrlListConfigureWidget.h b/src/widgets/UrlListConfigureWidget.h
new file mode 100644 (file)
index 0000000..b84bd54
--- /dev/null
@@ -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
diff --git a/src/widgets/UrlListConfigureWidget.ui b/src/widgets/UrlListConfigureWidget.ui
new file mode 100644 (file)
index 0000000..2b2c46e
--- /dev/null
@@ -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>