]> Nutra Git (v1) - gamesguru/feather.git/commitdiff
Plugins
authortobtoht <tob@featherwallet.org>
Thu, 30 Nov 2023 14:01:39 +0000 (15:01 +0100)
committertobtoht <tob@featherwallet.org>
Mon, 4 Dec 2023 19:47:19 +0000 (20:47 +0100)
96 files changed:
CMakeLists.txt
HACKING.md
src/CMakeLists.txt
src/MainWindow.cpp
src/MainWindow.h
src/MainWindow.ui
src/SettingsDialog.cpp
src/SettingsDialog.h
src/SettingsDialog.ui
src/WindowManager.cpp
src/WindowManager.h
src/assets.qrc
src/assets/images/connect_white.svg [new file with mode: 0644]
src/main.cpp
src/plugins/Plugin.h [new file with mode: 0644]
src/plugins/PluginRegistry.h [new file with mode: 0644]
src/plugins/bounties/BountiesModel.cpp
src/plugins/bounties/BountiesPlugin.cpp [new file with mode: 0644]
src/plugins/bounties/BountiesPlugin.h [new file with mode: 0644]
src/plugins/bounties/BountiesProxyModel.cpp [new file with mode: 0644]
src/plugins/bounties/BountiesProxyModel.h [new file with mode: 0644]
src/plugins/bounties/BountiesWidget.cpp
src/plugins/bounties/BountiesWidget.h
src/plugins/bounties/BountiesWidget.ui
src/plugins/calc/CalcConfigDialog.cpp [moved from src/dialog/CalcConfigDialog.cpp with 99% similarity]
src/plugins/calc/CalcConfigDialog.h [moved from src/dialog/CalcConfigDialog.h with 100% similarity]
src/plugins/calc/CalcConfigDialog.ui [moved from src/dialog/CalcConfigDialog.ui with 100% similarity]
src/plugins/calc/CalcPlugin.cpp [new file with mode: 0644]
src/plugins/calc/CalcPlugin.h [new file with mode: 0644]
src/plugins/calc/CalcWidget.cpp [moved from src/CalcWidget.cpp with 99% similarity]
src/plugins/calc/CalcWidget.h [moved from src/CalcWidget.h with 100% similarity]
src/plugins/calc/CalcWidget.ui [moved from src/CalcWidget.ui with 93% similarity]
src/plugins/calc/CalcWindow.cpp [moved from src/CalcWindow.cpp with 100% similarity]
src/plugins/calc/CalcWindow.h [moved from src/CalcWindow.h with 100% similarity]
src/plugins/calc/CalcWindow.ui [moved from src/CalcWindow.ui with 96% similarity]
src/plugins/ccs/CCSWidget.cpp [deleted file]
src/plugins/crowdfunding/CCSEntry.h [moved from src/plugins/ccs/CCSEntry.h with 100% similarity]
src/plugins/crowdfunding/CCSModel.cpp [moved from src/plugins/ccs/CCSModel.cpp with 98% similarity]
src/plugins/crowdfunding/CCSModel.h [moved from src/plugins/ccs/CCSModel.h with 100% similarity]
src/plugins/crowdfunding/CCSProgressDelegate.cpp [moved from src/plugins/ccs/CCSProgressDelegate.cpp with 100% similarity]
src/plugins/crowdfunding/CCSProgressDelegate.h [moved from src/plugins/ccs/CCSProgressDelegate.h with 100% similarity]
src/plugins/crowdfunding/CCSWidget.cpp [new file with mode: 0644]
src/plugins/crowdfunding/CCSWidget.h [moved from src/plugins/ccs/CCSWidget.h with 90% similarity]
src/plugins/crowdfunding/CCSWidget.ui [moved from src/plugins/ccs/CCSWidget.ui with 100% similarity]
src/plugins/crowdfunding/CrowdfundingPlugin.cpp [new file with mode: 0644]
src/plugins/crowdfunding/CrowdfundingPlugin.h [new file with mode: 0644]
src/plugins/exchange/ExchangePlugin.cpp [new file with mode: 0644]
src/plugins/exchange/ExchangePlugin.h [new file with mode: 0644]
src/plugins/exchange/ExchangeWidget.cpp [new file with mode: 0644]
src/plugins/exchange/ExchangeWidget.h [new file with mode: 0644]
src/plugins/exchange/ExchangeWidget.ui [copied from src/plugins/bounties/BountiesWidget.ui with 68% similarity]
src/plugins/home/HomePlugin.cpp [new file with mode: 0644]
src/plugins/home/HomePlugin.h [new file with mode: 0644]
src/plugins/home/HomeWidget.cpp [new file with mode: 0644]
src/plugins/home/HomeWidget.h [new file with mode: 0644]
src/plugins/home/HomeWidget.ui [copied from src/plugins/bounties/BountiesWidget.ui with 51% similarity]
src/plugins/localmonero/LocalMoneroPlugin.cpp [new file with mode: 0644]
src/plugins/localmonero/LocalMoneroPlugin.h [new file with mode: 0644]
src/plugins/reddit/RedditModel.cpp
src/plugins/reddit/RedditPlugin.cpp [new file with mode: 0644]
src/plugins/reddit/RedditPlugin.h [new file with mode: 0644]
src/plugins/reddit/RedditProxyModel.cpp [new file with mode: 0644]
src/plugins/reddit/RedditProxyModel.h [new file with mode: 0644]
src/plugins/reddit/RedditWidget.cpp
src/plugins/reddit/RedditWidget.h
src/plugins/revuo/RevuoPlugin.cpp [new file with mode: 0644]
src/plugins/revuo/RevuoPlugin.h [new file with mode: 0644]
src/plugins/revuo/RevuoWidget.cpp
src/plugins/tickers/TickersConfigAddDialog.cpp [new file with mode: 0644]
src/plugins/tickers/TickersConfigAddDialog.h [new file with mode: 0644]
src/plugins/tickers/TickersConfigAddDialog.ui [new file with mode: 0644]
src/plugins/tickers/TickersConfigDialog.cpp [new file with mode: 0644]
src/plugins/tickers/TickersConfigDialog.h [new file with mode: 0644]
src/plugins/tickers/TickersConfigDialog.ui [new file with mode: 0644]
src/plugins/tickers/TickersPlugin.cpp [new file with mode: 0644]
src/plugins/tickers/TickersPlugin.h [new file with mode: 0644]
src/plugins/tickers/TickersWidget.cpp [new file with mode: 0644]
src/plugins/tickers/TickersWidget.h [new file with mode: 0644]
src/plugins/tickers/TickersWidget.ui [new file with mode: 0644]
src/plugins/xmrig/XMRigPlugin.cpp [new file with mode: 0644]
src/plugins/xmrig/XMRigPlugin.h [new file with mode: 0644]
src/plugins/xmrig/XMRigWidget.cpp
src/utils/Utils.cpp
src/utils/Utils.h
src/utils/WebsocketNotifier.cpp
src/utils/WebsocketNotifier.h
src/utils/config.cpp
src/utils/config.h
src/widgets/PluginWidget.cpp [new file with mode: 0644]
src/widgets/PluginWidget.h [new file with mode: 0644]
src/widgets/PluginWidget.ui [new file with mode: 0644]
src/wizard/PagePlugins.cpp [new file with mode: 0644]
src/wizard/PagePlugins.h [new file with mode: 0644]
src/wizard/PagePlugins.ui [new file with mode: 0644]
src/wizard/WalletWizard.cpp
src/wizard/WalletWizard.h

index 02b2c89b0cd8b9546ae9ff6826363e5084cbad3d..596ce24fbe941e3477bc92490b5e62129c2f5d16 100644 (file)
@@ -18,8 +18,6 @@ set(COPYRIGHT_HOLDERS "The Monero Project")
 # Configurable options
 option(STATIC "Link libraries statically, requires static Qt" OFF)
 option(SELF_CONTAINED "Disable when building Feather for packages" OFF)
-option(LOCALMONERO "Include LocalMonero module" ON)
-option(XMRIG "Include XMRig module" ON)
 option(TOR_DIR "Directory containing Tor binaries to embed inside Feather" OFF)
 option(CHECK_UPDATES "Enable checking for application updates" OFF)
 option(PLATFORM_INSTALLER "Built-in updater fetches installer (windows-only)" OFF)
@@ -28,6 +26,18 @@ option(DONATE_BEG "Prompt donation window every once in a while" OFF)
 option(WITH_SCANNER "Enable webcam QR scanner" ON)
 option(STACK_TRACE "Dump stack trace on crash (Linux only)" OFF)
 
+# Plugins
+option(WITH_PLUGIN_HOME "Include Home tab plugin" ON)
+option(WITH_PLUGIN_TICKERS "Include Tickers Home plugin" ON)
+option(WITH_PLUGIN_CROWDFUNDING "Include Crowdfunding Home plugin" ON)
+option(WITH_PLUGIN_BOUNTIES "Include Bounties Home plugin" ON)
+option(WITH_PLUGIN_REDDIT "Include Reddit Home plugin" ON)
+option(WITH_PLUGIN_REVUO "Include Revuo Home plugin" ON)
+option(WITH_PLUGIN_CALC "Include Calc tab plugin" ON)
+option(WITH_PLUGIN_EXCHANGE "Include Exchange tab plugin" ON)
+option(WITH_PLUGIN_LOCALMONERO "Include LocalMonero plugin" ON)
+option(WITH_PLUGIN_XMRIG "Include XMRig plugin" ON)
+
 list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_SOURCE_DIR}/cmake")
 include(CheckCCompilerFlag)
 include(CheckCXXCompilerFlag)
index 6e8b51b13fbb7eb340f253f0a451d48e89b81b1f..79bd9abfffe90003a136826f6fbc214b420dcc51 100644 (file)
@@ -123,10 +123,9 @@ On platforms without `execinfo.h` use `cmake -DSTACK_TRACE:BOOL=OFF ..` instead
 
 There are some CMake options that you may pass to control how Feather is built:
 
-- `-DLOCALMONERO=OFF` - disable LocalMonero feature
-- `-DXMRIG=OFF` - disable XMRig feature
 - `-DCHECK_UPDATES=ON` - enable checking for updates, only for standalone binaries
 - `-DDONATE_BEG=OFF` - disable the dreaded donate requests
 - `-DUSE_DEVICE_TREZOR=OFF` - disable Trezor hardware wallet support
 - `-DWITH_SCANNER=ON` - enable the webcam QR code scanner
 - `-DTOR_DIR=/path/to/tor/` - embed a Tor binary in Feather, argument should be a directory containing the binary
+- `-DWITH_PLUGIN_<NAME>=OFF` - disable a plugin
index a3b7c7715e97a1a04c98ef74ccd67b207d39c899..ca1646022bde96962a27776bc6c3ed7e28e5d832 100644 (file)
@@ -69,10 +69,34 @@ file(GLOB SOURCE_FILES
         "monero_seed/*.cpp"
         "monero_seed/*.c"
         "monero_seed/*.hpp"
-        "plugins/*/*.cpp"
-        "plugins/*/*.h"
+        "plugins/*.cpp"
+        "plugins/*.h"
         )
 
+get_cmake_property(_vars VARIABLES)
+set(PLUGIN_PREFIX "WITH_PLUGIN_")
+
+foreach (_var ${_vars})
+    string(REGEX MATCH "^${PLUGIN_PREFIX}" _isPlugin ${_var})
+
+    if (NOT _var)
+        continue()
+    endif()
+
+    if(_isPlugin)
+        string(REPLACE "${PLUGIN_PREFIX}" "" _suffix ${_var})
+        string(TOLOWER "${_suffix}" _plugin)
+        message(STATUS "Adding plugin: ${_plugin}")
+        file (GLOB PLUGIN_FILES
+                "plugins/${_plugin}/*.cpp"
+                "plugins/${_plugin}/*.h"
+        )
+        list (APPEND SOURCE_FILES
+                ${PLUGIN_FILES}
+        )
+    endif()
+endforeach()
+
 if (CHECK_UPDATES)
     file(GLOB UPDATER_FILES
             "utils/updater/*.h"
@@ -177,18 +201,10 @@ if (CHECK_UPDATES)
     target_compile_definitions(feather PRIVATE CHECK_UPDATES=1)
 endif()
 
-if(LOCALMONERO)
-    target_compile_definitions(feather PRIVATE HAS_LOCALMONERO=1)
-endif()
-
 if(TOR_DIR)
     target_compile_definitions(feather PRIVATE HAS_TOR_BIN=1)
 endif()
 
-if(XMRIG)
-    target_compile_definitions(feather PRIVATE HAS_XMRIG=1)
-endif()
-
 if(WITH_SCANNER)
     target_compile_definitions(feather PRIVATE WITH_SCANNER=1)
 endif()
index ef2ce74437f730cac5bc518ad04437d0ff3b9f71..252b639090ab1a815e376c4c30847f34c30c4a32 100644 (file)
@@ -14,7 +14,6 @@
 #include "dialog/BalanceDialog.h"
 #include "dialog/DebugInfoDialog.h"
 #include "dialog/PasswordDialog.h"
-#include "dialog/TorInfoDialog.h"
 #include "dialog/TxBroadcastDialog.h"
 #include "dialog/TxConfAdvDialog.h"
 #include "dialog/TxConfDialog.h"
@@ -26,6 +25,7 @@
 #include "libwalletqt/AddressBook.h"
 #include "libwalletqt/rows/CoinsInfo.h"
 #include "libwalletqt/Transfer.h"
+#include "plugins/PluginRegistry.h"
 #include "utils/AppData.h"
 #include "utils/AsyncTask.h"
 #include "utils/ColorScheme.h"
@@ -44,7 +44,6 @@
 #ifdef CHECK_UPDATES
 #include "utils/updater/UpdateDialog.h"
 #endif
-//#include "misc_log_ex.h"
 
 MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *parent)
     : QMainWindow(parent)
@@ -56,12 +55,9 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
 {
     ui->setupUi(this);
 
-//    MCWARNING("feather", "Platform tag: " << this->getPlatformTag().toStdString());
-
     // Ensure the destructor is called after closeEvent()
     setAttribute(Qt::WA_DeleteOnClose);
 
-    m_windowCalc = new CalcWindow(this);
     m_splashDialog = new SplashDialog(this);
     m_accountSwitcherDialog = new AccountSwitcherDialog(m_wallet, this);
 
@@ -72,25 +68,21 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
     this->restoreGeo();
 
     this->initStatusBar();
+    this->initPlugins();
     this->initWidgets();
     this->initMenu();
-    this->initHome();
     this->initOffline();
     this->initWalletContext();
+    emit uiSetup();
 
     this->onOfflineMode(conf()->get(Config::offlineMode).toBool());
+    conf()->set(Config::restartRequired, false);
     
     // Websocket notifier
-    connect(websocketNotifier(), &WebsocketNotifier::CCSReceived, ui->ccsWidget->model(), &CCSModel::updateEntries);
-    connect(websocketNotifier(), &WebsocketNotifier::BountyReceived, ui->bountiesWidget->model(), &BountiesModel::updateBounties);
-    connect(websocketNotifier(), &WebsocketNotifier::RedditReceived, ui->redditWidget->model(), &RedditModel::updatePosts);
-    connect(websocketNotifier(), &WebsocketNotifier::RevuoReceived, ui->revuoWidget, &RevuoWidget::updateItems);
 #ifdef CHECK_UPDATES
     connect(websocketNotifier(), &WebsocketNotifier::UpdatesReceived, m_updater.data(), &Updater::wsUpdatesReceived);
 #endif
-#ifdef HAS_XMRIG
-    connect(websocketNotifier(), &WebsocketNotifier::XMRigDownloadsReceived, m_xmrig, &XMRigWidget::onDownloads);
-#endif
+
     websocketNotifier()->emitCache(); // Get cached data
 
     connect(m_windowManager, &WindowManager::websocketStatusChanged, this, &MainWindow::onWebsocketStatusChanged);
@@ -194,10 +186,33 @@ void MainWindow::initStatusBar() {
     m_statusBtnHwDevice->hide();
 }
 
-void MainWindow::initWidgets() {
-    int homeWidget = conf()->get(Config::homeWidget).toInt();
-    ui->tabHomeWidget->setCurrentIndex(TabsHome(homeWidget));
+void MainWindow::initPlugins() {
+    const QStringList enabledPlugins = conf()->get(Config::enabledPlugins).toStringList();
+
+    for (const auto& plugin_creator : PluginRegistry::getPluginCreators()) {
+        Plugin* plugin = plugin_creator();
+
+        if (!PluginRegistry::getInstance().isPluginEnabled(plugin->id())) {
+            continue;
+        }
+
+        qDebug() << "Initializing plugin: " << plugin->id();
+        plugin->initialize(m_wallet, this);
+        connect(plugin, &Plugin::setStatusText, this, &MainWindow::setStatusText);
+        connect(plugin, &Plugin::fillSendTab, this, &MainWindow::fillSendTab);
+        connect(this, &MainWindow::updateIcons, plugin, &Plugin::skinChanged);
+        connect(this, &MainWindow::aboutToQuit, plugin, &Plugin::aboutToQuit);
+        connect(this, &MainWindow::uiSetup, plugin, &Plugin::uiSetup);
 
+        m_plugins.append(plugin);
+    }
+
+    std::sort(m_plugins.begin(), m_plugins.end(), [](Plugin *a, Plugin *b) {
+        return a->idx() < b->idx();
+    });
+}
+
+void MainWindow::initWidgets() {
     // [History]
     m_historyWidget = new HistoryWidget(m_wallet, this);
     ui->historyWidgetLayout->addWidget(m_historyWidget);
@@ -216,7 +231,7 @@ void MainWindow::initWidgets() {
     ui->receiveWidgetLayout->addWidget(m_receiveWidget);
     connect(m_receiveWidget, &ReceiveWidget::showTransactions, [this](const QString &text) {
         m_historyWidget->setSearchText(text);
-        ui->tabWidget->setCurrentIndex(Tabs::HISTORY);
+        ui->tabWidget->setCurrentIndex(this->findTab("History"));
     });
     connect(m_contactsWidget, &ContactsWidget::fillAddress, m_sendWidget, &SendWidget::fillAddress);
 
@@ -224,26 +239,24 @@ void MainWindow::initWidgets() {
     m_coinsWidget = new CoinsWidget(m_wallet, this);
     ui->coinsWidgetLayout->addWidget(m_coinsWidget);
 
-#ifdef HAS_LOCALMONERO
-    m_localMoneroWidget = new LocalMoneroWidget(this, m_wallet);
-    ui->localMoneroLayout->addWidget(m_localMoneroWidget);
-#else
-    ui->tabWidgetExchanges->setTabVisible(0, false);
-#endif
-
-#ifdef HAS_XMRIG
-    m_xmrig = new XMRigWidget(m_wallet, this);
-    ui->xmrRigLayout->addWidget(m_xmrig);
+    // [Plugins..]
+    for (auto* plugin : m_plugins) {
+        if (!plugin->hasParent()) {
+            qDebug() << "Adding tab: " << plugin->displayName();
 
-    connect(m_xmrig, &XMRigWidget::miningStarted, [this]{ this->updateTitle(); });
-    connect(m_xmrig, &XMRigWidget::miningEnded, [this]{ this->updateTitle(); });
-#else
-    ui->tabWidget->setTabVisible(Tabs::XMRIG, false);
-#endif
+            if (plugin->insertFirst()) {
+                ui->tabWidget->insertTab(0, plugin->tab(), icons()->icon(plugin->icon()), plugin->displayName());
+            } else {
+                ui->tabWidget->addTab(plugin->tab(), icons()->icon(plugin->icon()), plugin->displayName());
+            }
 
-#if defined(Q_OS_MACOS)
-    ui->line->hide();
-#endif
+            for (auto* child : m_plugins) {
+                if (child->hasParent() && child->parent() == plugin->id()) {
+                    plugin->addSubPlugin(child);
+                }
+            }
+        }
+    }
 
     ui->frame_coinControl->setVisible(false);
     connect(ui->btn_resetCoinControl, &QPushButton::clicked, [this]{
@@ -257,6 +270,7 @@ void MainWindow::initWidgets() {
     connect(m_walletUnlockWidget, &WalletUnlockWidget::closeWallet, this, &MainWindow::close);
     connect(m_walletUnlockWidget, &WalletUnlockWidget::unlockWallet, this, &MainWindow::unlockWallet);
 
+    ui->tabWidget->setCurrentIndex(0);
     ui->stackedWidget->setCurrentIndex(0);
 }
 
@@ -301,43 +315,31 @@ void MainWindow::initMenu() {
     connect(ui->actionShow_Searchbar, &QAction::toggled, this, &MainWindow::toggleSearchbar);
     ui->actionShow_Searchbar->setChecked(conf()->get(Config::showSearchbar).toBool());
 
-    // Show/Hide Home
-    connect(ui->actionShow_Home, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
-    m_tabShowHideMapper["Home"] = new ToggleTab(ui->tabHome, "Home", "Home", ui->actionShow_Home, Config::showTabHome);
-    m_tabShowHideSignalMapper->setMapping(ui->actionShow_Home, "Home");
-
     // Show/Hide Coins
     connect(ui->actionShow_Coins, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
-    m_tabShowHideMapper["Coins"] = new ToggleTab(ui->tabCoins, "Coins", "Coins", ui->actionShow_Coins, Config::showTabCoins);
+    m_tabShowHideMapper["Coins"] = new ToggleTab(ui->tabCoins, "Coins", "Coins", ui->actionShow_Coins);
     m_tabShowHideSignalMapper->setMapping(ui->actionShow_Coins, "Coins");
 
-    // Show/Hide Calc
-    connect(ui->actionShow_calc, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
-    m_tabShowHideMapper["Calc"] = new ToggleTab(ui->tabCalc, "Calc", "Calc", ui->actionShow_calc, Config::showTabCalc);
-    m_tabShowHideSignalMapper->setMapping(ui->actionShow_calc, "Calc");
-
-    // Show/Hide Exchange
-#if defined(HAS_LOCALMONERO)
-    connect(ui->actionShow_Exchange, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
-    m_tabShowHideMapper["Exchange"] = new ToggleTab(ui->tabExchange, "Exchange", "Exchange", ui->actionShow_Exchange, Config::showTabExchange);
-    m_tabShowHideSignalMapper->setMapping(ui->actionShow_Exchange, "Exchange");
-#else
-    ui->actionShow_Exchange->setVisible(false);
-    ui->tabWidget->setTabVisible(Tabs::EXCHANGES, false);
-#endif
+    // Show/Hide Plugins..
+    for (const auto &plugin : m_plugins) {
+        if (plugin->parent() != "") {
+            continue;
+        }
 
-    // Show/Hide Mining
-#if defined(HAS_XMRIG)
-    connect(ui->actionShow_XMRig, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
-    m_tabShowHideMapper["Mining"] = new ToggleTab(ui->tabXmrRig, "Mining", "Mining", ui->actionShow_XMRig, Config::showTabXMRig);
-    m_tabShowHideSignalMapper->setMapping(ui->actionShow_XMRig, "Mining");
-#else
-    ui->actionShow_XMRig->setVisible(false);
-#endif
+        auto* pluginAction = new QAction(QString("Show %1").arg(plugin->displayName()), this);
+        ui->menuView->insertAction(plugin->insertFirst() ? ui->actionPlaceholderBegin : ui->actionPlaceholderEnd, pluginAction);
+        connect(pluginAction, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
+        m_tabShowHideMapper[plugin->displayName()] = new ToggleTab(plugin->tab(), plugin->displayName(), plugin->displayName(), pluginAction);
+        m_tabShowHideSignalMapper->setMapping(pluginAction, plugin->displayName());
+    }
+    ui->actionPlaceholderBegin->setVisible(false);
+    ui->actionPlaceholderEnd->setVisible(false);
 
+    QStringList enabledTabs = conf()->get(Config::enabledTabs).toStringList();
     for (const auto &key: m_tabShowHideMapper.keys()) {
         const auto toggleTab = m_tabShowHideMapper.value(key);
-        const bool show = conf()->get(toggleTab->configKey).toBool();
+        bool show = enabledTabs.contains(key);
+
         toggleTab->menuAction->setText((show ? QString("Hide ") : QString("Show ")) + toggleTab->name);
         ui->tabWidget->setTabVisible(ui->tabWidget->indexOf(toggleTab->tab), show);
     }
@@ -353,7 +355,6 @@ void MainWindow::initMenu() {
     connect(ui->actionTransmitOverUR,              &QAction::triggered, this, &MainWindow::showURDialog);
     connect(ui->actionPay_to_many,                 &QAction::triggered, this, &MainWindow::payToMany);
     connect(ui->actionAddress_checker,             &QAction::triggered, this, &MainWindow::showAddressChecker);
-    connect(ui->actionCalculator,                  &QAction::triggered, this, &MainWindow::showCalcWindow);
     connect(ui->actionCreateDesktopEntry,          &QAction::triggered, this, &MainWindow::onCreateDesktopEntry);
 
     if (m_wallet->viewOnly()) {
@@ -398,27 +399,6 @@ void MainWindow::initMenu() {
     ui->actionDocumentation->setShortcut(QKeySequence("F1"));
 }
 
-void MainWindow::initHome() {
-    // Ticker widgets
-    m_tickerWidgets.append(new PriceTickerWidget(this, m_wallet, "XMR"));
-    m_tickerWidgets.append(new PriceTickerWidget(this, m_wallet, "BTC"));
-    m_tickerWidgets.append(new RatioTickerWidget(this, m_wallet, "XMR", "BTC"));
-    for (const auto &widget : m_tickerWidgets) {
-        ui->tickerLayout->addWidget(widget);
-    }
-
-    m_balanceTickerWidget = new BalanceTickerWidget(this, m_wallet, false);
-    ui->fiatTickerLayout->addWidget(m_balanceTickerWidget);
-
-    connect(ui->ccsWidget, &CCSWidget::selected, this, &MainWindow::showSendScreen);
-    connect(ui->bountiesWidget, &BountiesWidget::donate, this, &MainWindow::fillSendTab);
-    connect(ui->redditWidget, &RedditWidget::setStatusText, this, &MainWindow::setStatusText);
-    connect(ui->revuoWidget, &RevuoWidget::donate, [this](const QString &address, const QString &description){
-        m_sendWidget->fill(address, description);
-        ui->tabWidget->setCurrentIndex(Tabs::SEND);
-    });
-}
-
 void MainWindow::initOffline() {
     // TODO: check if we have any cameras available
 
@@ -502,9 +482,18 @@ void MainWindow::initWalletContext() {
 
 void MainWindow::menuToggleTabVisible(const QString &key){
     const auto toggleTab = m_tabShowHideMapper[key];
-    bool show = conf()->get(toggleTab->configKey).toBool();
+
+    QStringList enabledTabs = conf()->get(Config::enabledTabs).toStringList();
+    bool show = enabledTabs.contains(key);
     show = !show;
-    conf()->set(toggleTab->configKey, show);
+
+    if (show) {
+        enabledTabs.append(key);
+    } else {
+        enabledTabs.removeAll(key);
+    }
+
+    conf()->set(Config::enabledTabs, enabledTabs);
     ui->tabWidget->setTabVisible(ui->tabWidget->indexOf(toggleTab->tab), show);
     toggleTab->menuAction->setText((show ? QString("Hide ") : QString("Show ")) + toggleTab->name);
 }
@@ -599,7 +588,6 @@ void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) {
 
     m_statusLabelBalance->setToolTip("Click for details");
     m_statusLabelBalance->setText(balance_str);
-    m_balanceTickerWidget->setHidden(hide);
 }
 
 void MainWindow::setStatusText(const QString &text, bool override, int timeout) {
@@ -631,19 +619,22 @@ void MainWindow::tryStoreWallet() {
 
 void MainWindow::onWebsocketStatusChanged(bool enabled) {
     ui->actionShow_Home->setVisible(enabled);
-    ui->actionShow_calc->setVisible(enabled);
-    ui->actionShow_Exchange->setVisible(enabled);
 
-    ui->tabWidget->setTabVisible(Tabs::HOME, enabled && conf()->get(Config::showTabHome).toBool());
-    ui->tabWidget->setTabVisible(Tabs::CALC, enabled && conf()->get(Config::showTabCalc).toBool());
-    ui->tabWidget->setTabVisible(Tabs::EXCHANGES, enabled && conf()->get(Config::showTabExchange).toBool());
+    QStringList enabledTabs = conf()->get(Config::enabledTabs).toStringList();
+
+    for (const auto &plugin : m_plugins) {
+        if (plugin->hasParent()) {
+            continue;
+        }
+
+        if (plugin->requiresWebsocket()) {
+            // TODO: unload plugins
+            ui->tabWidget->setTabVisible(this->findTab(plugin->displayName()), enabled && enabledTabs.contains(plugin->displayName()));
+        }
+    }
 
     m_historyWidget->setWebsocketEnabled(enabled);
     m_sendWidget->setWebsocketEnabled(enabled);
-
-#ifdef HAS_XMRIG
-    m_xmrig->setDownloadsTabEnabled(enabled);
-#endif
 }
 
 void MainWindow::onProxySettingsChanged() {
@@ -1129,11 +1120,7 @@ void MainWindow::skinChanged(const QString &skinName) {
 
 void MainWindow::updateWidgetIcons() {
     m_sendWidget->skinChanged();
-#ifdef HAS_LOCALMONERO
-    m_localMoneroWidget->skinChanged();
-#endif
-    ui->conversionWidget->skinChanged();
-    ui->revuoWidget->skinChanged();
+    emit updateIcons();
 
     m_statusBtnHwDevice->setIcon(this->hardwareDevicePairedIcon());
 }
@@ -1162,7 +1149,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
     if (!this->cleanedUp) {
         this->cleanedUp = true;
 
-        conf()->set(Config::homeWidget, ui->tabHomeWidget->currentIndex());
+        emit aboutToQuit();
 
         m_historyWidget->resetModel();
 
@@ -1193,25 +1180,21 @@ void MainWindow::changeEvent(QEvent* event)
 
 void MainWindow::donateButtonClicked() {
     m_sendWidget->fill(constants::donationAddress, constants::donationDescription);
-    ui->tabWidget->setCurrentIndex(Tabs::SEND);
+    ui->tabWidget->setCurrentIndex(this->findTab("Send"));
 }
 
 void MainWindow::showHistoryTab() {
     this->raise();
-    ui->tabWidget->setCurrentIndex(Tabs::HISTORY);
+    ui->tabWidget->setCurrentIndex(this->findTab("History"));
 }
 
 void MainWindow::fillSendTab(const QString &address, const QString &description) {
     m_sendWidget->fill(address, description);
-    ui->tabWidget->setCurrentIndex(Tabs::SEND);
-}
-
-void MainWindow::showCalcWindow() {
-    m_windowCalc->show();
+    ui->tabWidget->setCurrentIndex(this->findTab("Send"));
 }
 
 void MainWindow::payToMany() {
-    ui->tabWidget->setCurrentIndex(Tabs::SEND);
+    ui->tabWidget->setCurrentIndex(this->findTab("Send"));
     m_sendWidget->payToMany();
     Utils::showInfo(this, "Pay to many", "Enter a list of outputs in the 'Pay to' field.\n"
                                          "One output per line.\n"
@@ -1219,11 +1202,6 @@ void MainWindow::payToMany() {
                                          "A maximum of 16 addresses may be specified.");
 }
 
-void MainWindow::showSendScreen(const CCSEntry &entry) { // TODO: rename this function
-    m_sendWidget->fill(entry.address, QString("Donation to %1: %2").arg(entry.organizer, entry.title));
-    ui->tabWidget->setCurrentIndex(Tabs::SEND);
-}
-
 void MainWindow::onViewOnBlockExplorer(const QString &txid) {
     QString blockExplorerLink = Utils::blockExplorerLink(conf()->get(Config::blockExplorer).toString(), constants::networkType, txid);
     Utils::externalLinkWarning(this, blockExplorerLink);
@@ -1502,10 +1480,6 @@ void MainWindow::bringToFront() {
 }
 
 void MainWindow::onPreferredFiatCurrencyChanged() {
-    for (const auto &widget : m_tickerWidgets) {
-        widget->updateDisplay();
-    }
-    m_balanceTickerWidget->updateDisplay();
     m_sendWidget->onPreferredFiatCurrencyChanged();
 }
 
@@ -1651,12 +1625,9 @@ QString MainWindow::getHardwareDevice() {
 void MainWindow::updateTitle() {
     QString title = QString("%1 (#%2)").arg(this->walletName(), QString::number(m_wallet->currentSubaddressAccount()));
 
-    if (m_wallet->viewOnly())
+    if (m_wallet->viewOnly()) {
         title += " [view-only]";
-#ifdef HAS_XMRIG
-    if (m_xmrig->isMining())
-        title += " [mining]";
-#endif
+    }
 
     title += " - Feather";
 
@@ -1832,16 +1803,25 @@ void MainWindow::toggleSearchbar(bool visible) {
     m_coinsWidget->setSearchbarVisible(visible);
 
     int currentTab = ui->tabWidget->currentIndex();
-    if (currentTab == Tabs::HISTORY)
+    if (currentTab == this->findTab("History"))
         m_historyWidget->focusSearchbar();
-    else if (currentTab == Tabs::SEND)
+    else if (currentTab == this->findTab("Send"))
         m_contactsWidget->focusSearchbar();
-    else if (currentTab == Tabs::RECEIVE)
+    else if (currentTab == this->findTab("Receive"))
         m_receiveWidget->focusSearchbar();
-    else if (currentTab == Tabs::COINS)
+    else if (currentTab == this->findTab("Coins"))
         m_coinsWidget->focusSearchbar();
 }
 
+int MainWindow::findTab(const QString &title) {
+    for (int i = 0; i < ui->tabWidget->count(); i++) {
+        if (ui->tabWidget->tabText(i) == title) {
+            return i;
+        }
+    }
+    return -1;
+}
+
 MainWindow::~MainWindow() {
     qDebug() << "~MainWindow";
 }
\ No newline at end of file
index b7ba34a0a752e68bac74431a489926c95d9c9dbc..926645555dabc3b480497388cf7ca8886c84411e 100644 (file)
@@ -6,10 +6,8 @@
 
 #include <QMainWindow>
 #include <QSystemTrayIcon>
-#include <QMenu>
 
 #include "components.h"
-#include "CalcWindow.h"
 #include "SettingsDialog.h"
 
 #include "dialog/AboutDialog.h"
@@ -31,8 +29,6 @@
 #include "utils/config.h"
 #include "utils/daemonrpc.h"
 #include "utils/EventFilter.h"
-#include "plugins/ccs/CCSWidget.h"
-#include "plugins/reddit/RedditWidget.h"
 #include "widgets/TickerWidget.h"
 #include "widgets/WalletUnlockWidget.h"
 #include "wizard/WalletWizard.h"
 #include "CoinsWidget.h"
 
 #include "WindowManager.h"
+#include "plugins/Plugin.h"
 
 #ifdef CHECK_UPDATES
 #include "utils/updater/Updater.h"
 #endif
 
-#ifdef HAS_LOCALMONERO
-#include "plugins/localmonero/LocalMoneroWidget.h"
-#endif
-
-#ifdef HAS_XMRIG
-#include "plugins/xmrig/XMRigWidget.h"
-#endif
-
 namespace Ui {
     class MainWindow;
 }
 
 struct ToggleTab {
-    ToggleTab(QWidget *tab, QString name, QString description, QAction *menuAction, Config::ConfigKey configKey) :
-            tab(tab), key(std::move(name)), name(std::move(description)), menuAction(menuAction), configKey(configKey){}
+    ToggleTab(QWidget *tab, QString name, QString description, QAction *menuAction) :
+            tab(tab), key(std::move(name)), name(std::move(description)), menuAction(menuAction) {}
     QWidget *tab;
     QString key;
     QString name;
     QAction *menuAction;
-    Config::ConfigKey configKey;
 };
 
 class WindowManager;
@@ -84,24 +72,6 @@ public:
     QString walletCachePath();
     QString walletKeysPath();
 
-    enum Tabs {
-        HOME = 0,
-        HISTORY,
-        SEND,
-        RECEIVE,
-        COINS,
-        CALC,
-        EXCHANGES,
-        XMRIG
-    };
-
-    enum TabsHome {
-        CCS = 0,
-        BOUNTIES,
-        REDDIT,
-        REVUO
-    };
-
     enum Stack {
         WALLET = 0,
         LOCKED,
@@ -116,7 +86,10 @@ public slots:
     void onHideUpdateNotifications(bool hidden);
 
 signals:
+    void updateIcons();
     void closed();
+    void uiSetup();
+    void aboutToQuit();
 
 protected:
     void changeEvent(QEvent* event) override;
@@ -173,10 +146,8 @@ private slots:
     void showURDialog();
     
     void donateButtonClicked();
-    void showCalcWindow();
     void payToMany();
     void showHistoryTab();
-    void showSendScreen(const CCSEntry &entry);
     void skinChanged(const QString &skinName);
     void onBlockchainSync(int height, int target);
     void onRefreshSync(int height, int target);
@@ -201,9 +172,9 @@ private:
     friend WindowManager;
 
     void initStatusBar();
+    void initPlugins();
     void initWidgets();
     void initMenu();
-    void initHome();
     void initOffline();
     void initWalletContext();
 
@@ -231,6 +202,7 @@ private:
     void lockWallet();
     void unlockWallet(const QString &password);
     void closeQDialogChildren(QObject *object);
+    int findTab(const QString &title);
 
     QIcon hardwareDevicePairedIcon();
     QIcon hardwareDeviceUnpairedIcon();
@@ -241,25 +213,15 @@ private:
     Nodes *m_nodes;
     DaemonRpc *m_rpc;
 
-    CalcWindow *m_windowCalc = nullptr;
     SplashDialog *m_splashDialog = nullptr;
     AccountSwitcherDialog *m_accountSwitcherDialog = nullptr;
 
     WalletUnlockWidget *m_walletUnlockWidget = nullptr;
-#ifdef HAS_XMRIG
-    XMRigWidget *m_xmrig = nullptr;
-#endif
     ContactsWidget *m_contactsWidget = nullptr;
     HistoryWidget *m_historyWidget = nullptr;
     SendWidget *m_sendWidget = nullptr;
     ReceiveWidget *m_receiveWidget = nullptr;
     CoinsWidget *m_coinsWidget = nullptr;
-#ifdef HAS_LOCALMONERO
-    LocalMoneroWidget *m_localMoneroWidget = nullptr;
-#endif
-
-    QList<TickerWidgetBase*> m_tickerWidgets;
-    BalanceTickerWidget *m_balanceTickerWidget;
 
     QPointer<QAction> m_clearRecentlyOpenAction;
 
@@ -282,6 +244,8 @@ private:
     QTimer m_updateBytes;
     QTimer m_checkUserActivity;
 
+    QList<Plugin*> m_plugins;
+
     QString m_statusText;
     int m_statusDots;
     bool m_constructingTransaction = false;
index c1f355c89fe898641e1b0848040531b75a8ee7cb..477e9f2b79e452e4bb2fe4bb47557fe219393492 100644 (file)
@@ -40,7 +40,7 @@
     <item>
      <widget class="QStackedWidget" name="stackedWidget">
       <property name="currentIndex">
-       <number>2</number>
+       <number>0</number>
       </property>
       <widget class="QWidget" name="page_wallet">
        <layout class="QVBoxLayout" name="verticalLayout_11">
             <height>16</height>
            </size>
           </property>
-          <widget class="QWidget" name="tabHome">
-           <attribute name="icon">
-            <iconset resource="assets.qrc">
-             <normaloff>:/assets/images/tab_home.png</normaloff>:/assets/images/tab_home.png</iconset>
-           </attribute>
-           <attribute name="title">
-            <string>Home</string>
-           </attribute>
-           <layout class="QVBoxLayout" name="verticalLayout_3">
-            <property name="bottomMargin">
-             <number>5</number>
-            </property>
-            <item>
-             <layout class="QHBoxLayout" name="horizontalLayout">
-              <item>
-               <layout class="QHBoxLayout" name="tickerLayout"/>
-              </item>
-              <item>
-               <spacer name="horizontalSpacer">
-                <property name="orientation">
-                 <enum>Qt::Horizontal</enum>
-                </property>
-                <property name="sizeHint" stdset="0">
-                 <size>
-                  <width>40</width>
-                  <height>20</height>
-                 </size>
-                </property>
-               </spacer>
-              </item>
-              <item>
-               <layout class="QHBoxLayout" name="fiatTickerLayout"/>
-              </item>
-             </layout>
-            </item>
-            <item>
-             <widget class="Line" name="line">
-              <property name="orientation">
-               <enum>Qt::Horizontal</enum>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QTabWidget" name="tabHomeWidget">
-              <property name="currentIndex">
-               <number>0</number>
-              </property>
-              <property name="documentMode">
-               <bool>true</bool>
-              </property>
-              <widget class="QWidget" name="tab">
-               <attribute name="title">
-                <string>Crowdfunding</string>
-               </attribute>
-               <layout class="QVBoxLayout" name="verticalLayout_6">
-                <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="CCSWidget" name="ccsWidget" native="true"/>
-                </item>
-               </layout>
-              </widget>
-              <widget class="QWidget" name="tab_4">
-               <attribute name="title">
-                <string>Bounties</string>
-               </attribute>
-               <layout class="QVBoxLayout" name="verticalLayout_10">
-                <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="BountiesWidget" name="bountiesWidget" native="true"/>
-                </item>
-               </layout>
-              </widget>
-              <widget class="QWidget" name="tab_2">
-               <attribute name="title">
-                <string>/r/Monero</string>
-               </attribute>
-               <layout class="QVBoxLayout" name="verticalLayout_7">
-                <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="RedditWidget" name="redditWidget" native="true"/>
-                </item>
-               </layout>
-              </widget>
-              <widget class="QWidget" name="tab_3">
-               <attribute name="title">
-                <string>Revuo</string>
-               </attribute>
-               <layout class="QVBoxLayout" name="verticalLayout_9">
-                <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="RevuoWidget" name="revuoWidget" native="true"/>
-                </item>
-               </layout>
-              </widget>
-             </widget>
-            </item>
-           </layout>
-          </widget>
           <widget class="QWidget" name="tabHistory">
            <attribute name="icon">
             <iconset resource="assets.qrc">
             </item>
            </layout>
           </widget>
-          <widget class="QWidget" name="tabCalc">
-           <attribute name="icon">
-            <iconset resource="assets.qrc">
-             <normaloff>:/assets/images/gnome-calc.png</normaloff>:/assets/images/gnome-calc.png</iconset>
-           </attribute>
-           <attribute name="title">
-            <string>Calc</string>
-           </attribute>
-           <layout class="QVBoxLayout" name="verticalLayout_5">
-            <item>
-             <layout class="QHBoxLayout" name="horizontalLayout_2">
-              <item>
-               <widget class="CalcWidget" name="conversionWidget" native="true"/>
-              </item>
-             </layout>
-            </item>
-            <item>
-             <spacer name="verticalSpacer">
-              <property name="orientation">
-               <enum>Qt::Vertical</enum>
-              </property>
-              <property name="sizeHint" stdset="0">
-               <size>
-                <width>20</width>
-                <height>40</height>
-               </size>
-              </property>
-             </spacer>
-            </item>
-           </layout>
-          </widget>
-          <widget class="QWidget" name="tabExchange">
-           <attribute name="icon">
-            <iconset resource="assets.qrc">
-             <normaloff>:/assets/images/update.png</normaloff>:/assets/images/update.png</iconset>
-           </attribute>
-           <attribute name="title">
-            <string>Exchange</string>
-           </attribute>
-           <layout class="QVBoxLayout" name="verticalLayout_8">
-            <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="QTabWidget" name="tabWidgetExchanges">
-              <property name="currentIndex">
-               <number>0</number>
-              </property>
-              <widget class="QWidget" name="tabLocalMonero">
-               <attribute name="icon">
-                <iconset resource="assets.qrc">
-                 <normaloff>:/assets/images/localMonero_logo.png</normaloff>:/assets/images/localMonero_logo.png</iconset>
-               </attribute>
-               <attribute name="title">
-                <string>LocalMonero</string>
-               </attribute>
-               <layout class="QVBoxLayout" name="verticalLayout_4">
-                <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>
-                 <layout class="QVBoxLayout" name="localMoneroLayout"/>
-                </item>
-               </layout>
-              </widget>
-             </widget>
-            </item>
-           </layout>
-          </widget>
-          <widget class="QWidget" name="tabXmrRig">
-           <attribute name="icon">
-            <iconset resource="assets.qrc">
-             <normaloff>:/assets/images/mining.png</normaloff>:/assets/images/mining.png</iconset>
-           </attribute>
-           <attribute name="title">
-            <string>Mining</string>
-           </attribute>
-           <layout class="QGridLayout" name="gridLayout_2">
-            <item row="0" column="0">
-             <layout class="QGridLayout" name="xmrRigLayout"/>
-            </item>
-           </layout>
-          </widget>
          </widget>
         </item>
         <item>
     <addaction name="separator"/>
     <addaction name="actionPay_to_many"/>
     <addaction name="actionAddress_checker"/>
-    <addaction name="actionCalculator"/>
     <addaction name="actionCreateDesktopEntry"/>
    </widget>
    <widget class="QMenu" name="menuHelp">
     <property name="title">
      <string>View</string>
     </property>
-    <addaction name="actionShow_Home"/>
+    <addaction name="actionPlaceholderBegin"/>
     <addaction name="actionShow_Coins"/>
-    <addaction name="actionShow_calc"/>
-    <addaction name="actionShow_Exchange"/>
-    <addaction name="actionShow_XMRig"/>
+    <addaction name="actionPlaceholderEnd"/>
     <addaction name="separator"/>
     <addaction name="actionShow_Searchbar"/>
    </widget>
     <string>Transmit over UR</string>
    </property>
   </action>
+  <action name="actionPlaceholderEnd">
+   <property name="text">
+    <string>Placeholder</string>
+   </property>
+  </action>
+  <action name="actionPlaceholderBegin">
+   <property name="text">
+    <string>PlaceholderBegin</string>
+   </property>
+  </action>
  </widget>
  <layoutdefault spacing="6" margin="11"/>
  <customwidgets>
-  <customwidget>
-   <class>CalcWidget</class>
-   <extends>QWidget</extends>
-   <header>CalcWidget.h</header>
-   <container>1</container>
-  </customwidget>
-  <customwidget>
-   <class>CCSWidget</class>
-   <extends>QWidget</extends>
-   <header>plugins/ccs/CCSWidget.h</header>
-   <container>1</container>
-  </customwidget>
-  <customwidget>
-   <class>RedditWidget</class>
-   <extends>QWidget</extends>
-   <header>plugins/reddit/RedditWidget.h</header>
-   <container>1</container>
-  </customwidget>
-  <customwidget>
-   <class>RevuoWidget</class>
-   <extends>QWidget</extends>
-   <header>plugins/revuo/RevuoWidget.h</header>
-   <container>1</container>
-  </customwidget>
-  <customwidget>
-   <class>BountiesWidget</class>
-   <extends>QWidget</extends>
-   <header>plugins/bounties/BountiesWidget.h</header>
-   <container>1</container>
-  </customwidget>
   <customwidget>
    <class>ClickableLabel</class>
    <extends>QLabel</extends>
index 22a1e5b24795b64fce45bfc9952df3a7d38d495d..5153291c8858e508799e50b0c79646c4127aac0d 100644 (file)
@@ -15,6 +15,8 @@
 #include "utils/WebsocketNotifier.h"
 #include "widgets/NetworkProxyWidget.h"
 #include "WindowManager.h"
+#include "plugins/PluginRegistry.h"
+#include "utils/ColorScheme.h"
 
 Settings::Settings(Nodes *nodes, QWidget *parent)
         : QDialog(parent)
@@ -29,6 +31,7 @@ Settings::Settings(Nodes *nodes, QWidget *parent)
     this->setupDisplayTab();
     this->setupMemoryTab();
     this->setupTransactionsTab();
+    this->setupPluginsTab();
     this->setupMiscTab();
 
     connect(ui->selector, &QListWidget::currentItemChanged, [this](QListWidgetItem *current, QListWidgetItem *previous){
@@ -37,7 +40,6 @@ Settings::Settings(Nodes *nodes, QWidget *parent)
 
     ui->selector->setSelectionMode(QAbstractItemView::SingleSelection);
     ui->selector->setSelectionBehavior(QAbstractItemView::SelectRows);
-//    ui->selector->setCurrentRow(conf()->get(Config::lastSettingsPage).toInt());
 
     new QListWidgetItem(icons()->icon("interface_32px.png"), "Appearance", ui->selector, Pages::APPEARANCE);
     new QListWidgetItem(icons()->icon("nw_32px.png"), "Network", ui->selector, Pages::NETWORK);
@@ -45,6 +47,8 @@ Settings::Settings(Nodes *nodes, QWidget *parent)
     new QListWidgetItem(icons()->icon("vrdp_32px.png"), "Display", ui->selector, Pages::DISPLAY);
 //  new QListWidgetItem(icons()->icon("chipset_32px.png"), "Memory", ui->selector, Pages::MEMORY);
     new QListWidgetItem(icons()->icon("file_manager_32px.png"), "Transactions", ui->selector, Pages::TRANSACTIONS);
+    QString connectIcon = ColorScheme::darkScheme ? "connect_white.svg" : "connect.svg";;
+    new QListWidgetItem(icons()->icon(connectIcon), "Plugins", ui->selector, Pages::PLUGINS);
     new QListWidgetItem(icons()->icon("settings_disabled_32px.png"), "Misc", ui->selector, Pages::MISC);
 
     ui->selector->setFixedWidth(ui->selector->sizeHintForColumn(0) + ui->selector->frameWidth() + 5);
@@ -317,6 +321,12 @@ void Settings::setupTransactionsTab() {
     ui->checkBox_requirePasswordToSpend->hide();
 }
 
+void Settings::setupPluginsTab() {
+    connect(ui->pluginWidget, &PluginWidget::pluginConfigured, [this](const QString &id) {
+       emit pluginConfigured(id);
+    });
+}
+
 void Settings::setupMiscTab() {
     // [Block explorer]
     ui->comboBox_blockExplorer->setCurrentIndex(ui->comboBox_blockExplorer->findText(conf()->get(Config::blockExplorer).toString()));
index 77809fb1115aade97ebcdc833f2e33510998b25a..73c089bdf7fadd28e680afd1dd3bf675af1f9752 100644 (file)
@@ -32,6 +32,7 @@ public:
         DISPLAY,
         MEMORY,
         TRANSACTIONS,
+        PLUGINS,
         MISC
     };
 
@@ -44,6 +45,7 @@ signals:
     void proxySettingsChanged();
     void updateBalance();
     void offlineMode(bool offline);
+    void pluginConfigured(const QString &id);
 
 public slots:
 //    void checkboxExternalLinkWarn();
@@ -59,6 +61,7 @@ private:
     void setupDisplayTab();
     void setupMemoryTab();
     void setupTransactionsTab();
+    void setupPluginsTab();
     void setupMiscTab();
 
     void setupThemeComboBox();
index a62598082f993d3390895774d40230fba79deb92..882701cacab4b4f0948dc789947b1c1c62c8feea 100644 (file)
          </item>
         </layout>
        </widget>
+       <widget class="QWidget" name="page_plugins">
+        <layout class="QVBoxLayout" name="verticalLayout_21">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="label_20">
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;Plugins&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="PluginWidget" name="pluginWidget" native="true">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
        <widget class="QWidget" name="page_misc">
         <layout class="QVBoxLayout" name="verticalLayout_18">
          <property name="leftMargin">
    <header>widgets/NetworkProxyWidget.h</header>
    <container>1</container>
   </customwidget>
+  <customwidget>
+   <class>PluginWidget</class>
+   <extends>QWidget</extends>
+   <header>widgets/PluginWidget.h</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  <resources/>
  <connections>
index 06a5c5e00b373b93e3a4c6d2a9721219a4f3c3ce..a68b880cc42bfe48dbb7920800772d1dc884c0eb 100644 (file)
@@ -161,6 +161,7 @@ void WindowManager::showSettings(Nodes *nodes, QWidget *parent, bool showProxyTa
         for (const auto &window : m_windows) {
             window->onPreferredFiatCurrencyChanged();
         }
+        emit preferredFiatCurrencyChanged();
     });
     connect(&settings, &Settings::skinChanged, this, &WindowManager::onChangeTheme);
     connect(&settings, &Settings::updateBalance, this, &WindowManager::updateBalance);
@@ -172,6 +173,9 @@ void WindowManager::showSettings(Nodes *nodes, QWidget *parent, bool showProxyTa
             window->onHideUpdateNotifications(hidden);
         }
     });
+    connect(&settings, &Settings::pluginConfigured, [this](const QString &id) {
+        emit pluginConfigured(id);
+    });
 
     if (showProxyTab) {
         settings.showNetworkProxyTab();
index 1ac21273b6967dca014d595753d6f50b42045896..300863624763d69fc213359883fcef1669994d23 100644 (file)
@@ -47,7 +47,9 @@ signals:
     void proxySettingsChanged();
     void websocketStatusChanged(bool enabled);
     void updateBalance();
+    void preferredFiatCurrencyChanged();
     void offlineMode(bool offline);
+    void pluginConfigured(const QString &id);
 
 public slots:
     void onProxySettingsChanged();
index f6a64daa4391bf6ee0423b30fafcfeb734f77cd8..e84bf88fc348513782b9d925c584e7620d28b576 100644 (file)
@@ -28,6 +28,7 @@
     <file>assets/images/coldcard_unpaired.png</file>
     <file>assets/images/confirmed.svg</file>
     <file>assets/images/connect.svg</file>
+    <file>assets/images/connect_white.svg</file>
     <file>assets/images/copy.png</file>
     <file>assets/images/edit.png</file>
     <file>assets/images/external-link.svg</file>
diff --git a/src/assets/images/connect_white.svg b/src/assets/images/connect_white.svg
new file mode 100644 (file)
index 0000000..c36baef
--- /dev/null
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="iso-8859-1"?>\r
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->\r
+<svg fill="#FFFFFF" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"\r
+        viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">\r
+<g>\r
+       <g>\r
+               <path d="M297.448,279.808c-5.533-5.532-14.505-5.532-20.038,0.001l-31.873,31.873l-45.219-45.219l31.873-31.874\r
+                       c5.534-5.534,5.534-14.506,0-20.039c-5.533-5.534-14.506-5.534-20.039,0l-31.873,31.874l-25.485-25.485\r
+                       c-5.533-5.534-14.506-5.534-20.039,0l-56.36,56.36c-39.275,39.274-41.73,101.64-7.364,143.801\r
+                       c-0.46,0.358-0.909,0.738-1.332,1.161l-65.548,65.55c-5.534,5.533-5.534,14.506,0,20.039c2.767,2.767,6.393,4.15,10.019,4.15\r
+                       c3.626,0,7.253-1.384,10.019-4.15l65.549-65.549c0.423-0.423,0.803-0.872,1.161-1.332c19.675,16.037,43.75,24.055,67.825,24.055\r
+                       c27.515,0,55.029-10.473,75.976-31.42l56.36-56.36c5.534-5.533,5.534-14.506,0-20.039l-25.485-25.485l31.873-31.873\r
+                       C302.982,294.314,302.982,285.341,297.448,279.808z M214.661,413.565c-30.845,30.843-81.029,30.843-111.874,0l-4.352-4.352\r
+                       c-30.844-30.844-30.844-81.03,0-111.874l46.34-46.34l116.227,116.226L214.661,413.565z"/>\r
+       </g>\r
+</g>\r
+<g>\r
+       <g>\r
+               <path d="M507.849,24.19c5.534-5.533,5.534-14.505,0-20.039c-5.532-5.534-14.505-5.534-20.039,0l-65.549,65.548\r
+                       c-0.423,0.422-0.801,0.87-1.159,1.33c-19.112-15.613-42.816-24.104-67.827-24.104c-28.7,0-55.682,11.177-75.976,31.471\r
+                       l-56.36,56.36c-5.534,5.534-5.534,14.505,0,20.039L357.206,291.06c2.657,2.658,6.261,4.15,10.019,4.15\r
+                       c3.758,0,7.363-1.493,10.019-4.15l56.36-56.36c20.294-20.294,31.47-47.276,31.47-75.975c0-25.011-8.49-48.715-24.104-67.827\r
+                       c0.459-0.358,0.907-0.737,1.33-1.159L507.849,24.19z M413.565,214.662l-46.34,46.341L250.998,144.775l46.34-46.341\r
+                       c14.942-14.941,34.807-23.17,55.937-23.17c21.131,0,40.996,8.229,55.937,23.17l4.352,4.352\r
+                       c14.941,14.941,23.17,34.807,23.17,55.937C436.735,179.855,428.506,199.72,413.565,214.662z"/>\r
+       </g>\r
+</g>\r
+<g>\r
+       <g>\r
+               <path d="M374.062,196.728l-58.79-58.791c-5.533-5.534-14.506-5.534-20.039,0c-5.534,5.534-5.534,14.505,0,20.039l58.791,58.791\r
+                       c2.767,2.767,6.393,4.15,10.019,4.15c3.626,0,7.253-1.383,10.019-4.15C379.596,211.233,379.596,202.262,374.062,196.728z"/>\r
+       </g>\r
+</g>\r
+<g>\r
+       <g>\r
+               <path d="M218.149,352.641l-58.791-58.791c-5.533-5.533-14.506-5.533-20.039,0c-5.533,5.534-5.533,14.506,0.001,20.039\r
+                       l58.791,58.791c2.767,2.767,6.393,4.15,10.019,4.15c3.626,0,7.253-1.384,10.019-4.15\r
+                       C223.683,367.146,223.683,358.173,218.149,352.641z"/>\r
+       </g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+</svg>\r
index da90b57a6b0892804f427418d8c43605ac107258..7cc85f97dd1102fb0a07b3e69927897b24d52245 100644 (file)
@@ -193,6 +193,8 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
     if (parser.isSet("use-local-tor"))
         conf()->set(Config::useLocalTor, true);
 
+    conf()->set(Config::restartRequired, false);
+
     parser.process(app); // Parse again for --help and --version
 
     if (!quiet) {
diff --git a/src/plugins/Plugin.h b/src/plugins/Plugin.h
new file mode 100644 (file)
index 0000000..90b43cd
--- /dev/null
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef PLUGIN_H
+#define PLUGIN_H
+
+#include <QString>
+#include <QWidget>
+#include <QTabWidget>
+
+#include "libwalletqt/Wallet.h"
+
+class Plugin : public QObject {
+Q_OBJECT
+
+public:
+    enum PluginType {
+        TAB = 0,
+        WIDGET
+    };
+
+    virtual QString id() = 0;
+
+    // Used for sorting
+    virtual int idx() const = 0;
+
+    // id of parent plugin, plugin is only loaded if parent is available
+    virtual QString parent() = 0;
+    virtual QString displayName() = 0;
+    virtual QString description() = 0;
+    virtual QString icon() = 0;
+
+    // register expected websocket data
+    virtual QStringList socketData() = 0;
+    virtual PluginType type() = 0;
+    virtual QWidget* tab() = 0;
+
+    virtual bool configurable() {return false;}
+    virtual QDialog* configDialog(QWidget *parent) {return nullptr;}
+
+    // the plugin is automatically enabled if it has any enabled children
+    virtual bool implicitEnable() {return false;}
+    virtual bool requiresWebsocket() {return true;}
+
+    // insert tab to the left of standard tabs
+    virtual bool insertFirst() {return false;}
+    virtual void addSubPlugin(Plugin* plugin) {}
+
+    virtual void initialize(Wallet *wallet, QObject *parent) = 0;
+
+    bool hasParent() {return !parent().isEmpty();}
+
+signals:
+    void setStatusText(const QString &text, bool override, int timeout);
+    void fillSendTab(const QString &address, const QString &description);
+
+public slots:
+    virtual void skinChanged() {}
+    virtual void uiSetup() {}
+    virtual void aboutToQuit() {}
+
+protected:
+    Wallet* m_wallet = nullptr;
+};
+
+#endif //PLUGIN_H
diff --git a/src/plugins/PluginRegistry.h b/src/plugins/PluginRegistry.h
new file mode 100644 (file)
index 0000000..e505c7a
--- /dev/null
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef PLUGINREGISTRY_H
+#define PLUGINREGISTRY_H
+
+#include "Plugin.h"
+#include "utils/config.h"
+
+class PluginRegistry {
+public:
+    static void registerPlugin(Plugin* plugin) {
+        getInstance().plugins.append(plugin);
+        getInstance().pluginMap[plugin->id()] = plugin;
+
+        std::sort(getInstance().plugins.begin(), getInstance().plugins.end(), [](Plugin *a, Plugin *b) {
+            return a->idx() < b->idx();
+        });
+    }
+
+    void registerPluginCreator(const std::function<Plugin*()>& creator) {
+        plugin_creators.append(creator);
+    }
+
+    static const QList<Plugin*>& getPlugins() {
+        return getInstance().plugins;
+    }
+
+    static Plugin* getPlugin(const QString& id) {
+        return getInstance().pluginMap.value(id, nullptr);
+    }
+
+    static const QList<std::function<Plugin*()>>& getPluginCreators() {
+        return getInstance().plugin_creators;
+    }
+
+    bool isPluginEnabled(const QString &id) {
+        if (!pluginMap.contains(id)) {
+            return false;
+        }
+
+        Plugin* plugin = pluginMap[id];
+
+        // Don't load plugins that require the websocket connection if it is disabled
+        bool websocketDisabled = conf()->get(Config::disableWebsocket).toBool();
+        if (websocketDisabled && plugin->requiresWebsocket()) {
+            return false;
+        }
+
+        QStringList enabledPlugins = conf()->get(Config::enabledPlugins).toStringList();
+        if (enabledPlugins.contains(id) && !plugin->implicitEnable()) {
+            return true;
+        }
+
+        bool enabled = false;
+        if (plugin->implicitEnable()) {
+            for (const auto& child : plugins) {
+                if (child->parent() == plugin->id() && enabledPlugins.contains(child->id())) {
+                    enabled = true;
+                }
+            }
+        }
+
+        return enabled;
+    }
+
+private:
+    QMap<QString, Plugin*> pluginMap;
+    QList<Plugin*> plugins;
+    QList<std::function<Plugin*()>> plugin_creators;
+
+public:
+    static PluginRegistry& getInstance() {
+        static PluginRegistry instance;
+        return instance;
+    }
+};
+
+#endif //PLUGINREGISTRY_H
index 7d084a6ad046ee7211682121fd6f9e3939a769d6..12b8b1dc0b5c52f9add0c04fa62a2758f4c26f74 100644 (file)
@@ -50,15 +50,23 @@ QVariant BountiesModel::data(const QModelIndex &index, int role) const
 
     QSharedPointer<BountyEntry> post = m_bounties.at(index.row());
 
-    if(role == Qt::DisplayRole) {
+    if(role == Qt::DisplayRole || role == Qt::UserRole) {
         switch(index.column()) {
-            case Votes:
+            case Votes: {
+                if (role == Qt::UserRole) {
+                    return post->votes;
+                }
                 return QString::number(post->votes);
+            }
             case Title:
                 return post->title;
             case Status:
                 return post->status;
             case Bounty: {
+                if (role == Qt::UserRole) {
+                    return post->bountyAmount;
+                }
+
                 if (post->bountyAmount > 0) {
                     return QString("%1 XMR").arg(QString::number(post->bountyAmount, 'f', 5));
                 }
@@ -90,7 +98,7 @@ QVariant BountiesModel::headerData(int section, Qt::Orientation orientation, int
     {
         switch(section) {
             case Votes:
-                return QString(" ðŸ¡… ");
+                return QString("Score ");
             case Title:
                 return QString("Title");
             case Status:
diff --git a/src/plugins/bounties/BountiesPlugin.cpp b/src/plugins/bounties/BountiesPlugin.cpp
new file mode 100644 (file)
index 0000000..9362cd7
--- /dev/null
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "BountiesPlugin.h"
+
+#include "plugins/PluginRegistry.h"
+#include "BountiesWidget.h"
+
+BountiesPlugin::BountiesPlugin()
+{
+}
+
+void BountiesPlugin::initialize(Wallet *wallet, QObject *parent) {
+    this->setParent(parent);
+    m_tab = new BountiesWidget(nullptr);
+    connect(m_tab, &BountiesWidget::donate, this, &Plugin::fillSendTab);
+}
+
+QString BountiesPlugin::id() {
+    return "bounties";
+}
+
+int BountiesPlugin::idx() const {
+    return 20;
+}
+
+QString BountiesPlugin::parent() {
+    return "home";
+}
+
+QString BountiesPlugin::displayName() {
+    return "Bounties";
+}
+
+QString BountiesPlugin::description() {
+    return "";
+}
+
+QString BountiesPlugin::icon() {
+    return {};
+}
+
+QStringList BountiesPlugin::socketData() {
+    return {"bounties"};
+}
+
+Plugin::PluginType BountiesPlugin::type() {
+    return Plugin::PluginType::TAB;
+}
+
+QWidget* BountiesPlugin::tab() {
+    return m_tab;
+}
+
+const bool BountiesPlugin::registered = [] {
+    PluginRegistry::registerPlugin(BountiesPlugin::create());
+    PluginRegistry::getInstance().registerPluginCreator(&BountiesPlugin::create);
+    return true;
+}();
diff --git a/src/plugins/bounties/BountiesPlugin.h b/src/plugins/bounties/BountiesPlugin.h
new file mode 100644 (file)
index 0000000..39fee4f
--- /dev/null
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef BOUNTIESPLUGIN_H
+#define BOUNTIESPLUGIN_H
+
+#include "plugins/Plugin.h"
+#include "BountiesWidget.h"
+
+class BountiesPlugin : public Plugin {
+    Q_OBJECT
+
+public:
+    explicit BountiesPlugin();
+
+    QString id() override;
+    int idx() const override;
+    QString parent() override;
+    QString displayName() override;
+    QString description() override;
+    QString icon() override;
+    QStringList socketData() override;
+    PluginType type() override;
+    QWidget* tab() override;
+
+    void initialize(Wallet *wallet, QObject *parent) override;
+
+    static BountiesPlugin* create() { return new BountiesPlugin(); }
+
+private:
+    BountiesWidget* m_tab = nullptr;
+    static const bool registered;
+};
+
+
+#endif //BOUNTIESPLUGIN_H
diff --git a/src/plugins/bounties/BountiesProxyModel.cpp b/src/plugins/bounties/BountiesProxyModel.cpp
new file mode 100644 (file)
index 0000000..513ba6b
--- /dev/null
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "BountiesProxyModel.h"
+
+BountiesProxyModel::BountiesProxyModel(QObject *parent)
+        : QSortFilterProxyModel(parent)
+{
+    setSortRole(Qt::UserRole);
+}
diff --git a/src/plugins/bounties/BountiesProxyModel.h b/src/plugins/bounties/BountiesProxyModel.h
new file mode 100644 (file)
index 0000000..b3a2e15
--- /dev/null
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef BOUNTIESPROXYMODEL_H
+#define BOUNTIESPROXYMODEL_H
+
+#include <QSortFilterProxyModel>
+
+class BountiesProxyModel : public QSortFilterProxyModel
+{
+    Q_OBJECT
+
+public:
+    explicit BountiesProxyModel(QObject* parent = nullptr);
+};
+
+#endif //BOUNTIESPROXYMODEL_H
index 1ae6e5139f5a1186af8e4c9183837d3fb6abefd3..c0218fbf589a6170364becb86a3a9d663be50230 100644 (file)
@@ -4,13 +4,12 @@
 #include "BountiesWidget.h"
 #include "ui_BountiesWidget.h"
 
-#include <QDesktopServices>
-#include <QStandardItemModel>
 #include <QTableWidget>
 
 #include "BountiesModel.h"
 #include "utils/Utils.h"
 #include "utils/config.h"
+#include "utils/WebsocketNotifier.h"
 
 BountiesWidget::BountiesWidget(QWidget *parent)
         : QWidget(parent)
@@ -19,7 +18,13 @@ BountiesWidget::BountiesWidget(QWidget *parent)
         , m_contextMenu(new QMenu(this))
 {
     ui->setupUi(this);
-    ui->tableView->setModel(m_model);
+
+    m_proxyModel = new BountiesProxyModel(this);
+    m_proxyModel->setSourceModel(m_model);
+
+    ui->tableView->setModel(m_proxyModel);
+    ui->tableView->setSortingEnabled(true);
+    ui->tableView->sortByColumn(3, Qt::DescendingOrder);
     this->setupTable();
 
     m_contextMenu->addAction("View Bounty", this, &BountiesWidget::linkClicked);
@@ -28,15 +33,32 @@ BountiesWidget::BountiesWidget(QWidget *parent)
 
     connect(ui->tableView, &QTableView::doubleClicked, this, &BountiesWidget::linkClicked);
 
-    ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
-}
+    connect(websocketNotifier(), &WebsocketNotifier::dataReceived, this, [this](const QString &type, const QJsonValue &json) {
+        if (type == "bounties") {
+            QJsonArray bounties_data = json.toArray();
+            QList<QSharedPointer<BountyEntry>> l;
+
+            for (const auto& entry : bounties_data) {
+                QJsonObject obj = entry.toObject();
+                auto bounty = new BountyEntry(obj.value("votes").toInt(),
+                                              obj.value("title").toString(),
+                                              obj.value("amount").toDouble(),
+                                              obj.value("link").toString(),
+                                              obj.value("address").toString(),
+                                              obj.value("status").toString());
+                QSharedPointer<BountyEntry> b = QSharedPointer<BountyEntry>(bounty);
+                l.append(b);
+            }
+
+            m_model->updateBounties(l);
+        }
+    });
 
-BountiesModel * BountiesWidget::model() {
-    return m_model;
+    ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
 }
 
 void BountiesWidget::linkClicked() {
-    QModelIndex index = ui->tableView->currentIndex();
+    QModelIndex index = m_proxyModel->mapToSource(ui->tableView->currentIndex());
     auto post = m_model->post(index.row());
 
     if (post)
@@ -44,7 +66,7 @@ void BountiesWidget::linkClicked() {
 }
 
 void BountiesWidget::donateClicked() {
-    QModelIndex index = ui->tableView->currentIndex();
+    QModelIndex index = m_proxyModel->mapToSource(ui->tableView->currentIndex());
     auto bounty = m_model->post(index.row());
 
     if (bounty) {
index 02e51a4f6fd7804a3751a0f53e54716d362280b8..db526fa8654070254c7469a756dadfe131f19f38 100644 (file)
@@ -9,6 +9,7 @@
 #include <QWidget>
 
 #include "BountiesModel.h"
+#include "BountiesProxyModel.h"
 
 namespace Ui {
     class BountiesWidget;
@@ -21,7 +22,6 @@ class BountiesWidget : public QWidget
 public:
     explicit BountiesWidget(QWidget *parent = nullptr);
     ~BountiesWidget() override;
-    BountiesModel* model();
 
 public slots:
     void linkClicked();
@@ -38,6 +38,7 @@ private:
 
     QScopedPointer<Ui::BountiesWidget> ui;
     BountiesModel *m_model;
+    BountiesProxyModel *m_proxyModel;
     QMenu *m_contextMenu;
 };
 
index d4e985afb798784f2ee0a540f045a594a5ff3c26..c57cafd8f5e5c7620990c12ccd3dff066bd481c9 100644 (file)
      <property name="contextMenuPolicy">
       <enum>Qt::CustomContextMenu</enum>
      </property>
+     <property name="selectionMode">
+      <enum>QAbstractItemView::SingleSelection</enum>
+     </property>
+     <property name="sortingEnabled">
+      <bool>false</bool>
+     </property>
     </widget>
    </item>
   </layout>
similarity index 99%
rename from src/dialog/CalcConfigDialog.cpp
rename to src/plugins/calc/CalcConfigDialog.cpp
index e592bba6d8751f0eb90d19a99ed241d5060946ef..c85a240276435a678f6d7f9cd3a162e9b7f7a5c1 100644 (file)
@@ -4,7 +4,7 @@
 #include "CalcConfigDialog.h"
 #include "ui_CalcConfigDialog.h"
 
-#include "AppData.h"
+#include "utils/AppData.h"
 #include "utils/config.h"
 
 CalcConfigDialog::CalcConfigDialog(QWidget *parent)
diff --git a/src/plugins/calc/CalcPlugin.cpp b/src/plugins/calc/CalcPlugin.cpp
new file mode 100644 (file)
index 0000000..092b11e
--- /dev/null
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "CalcPlugin.h"
+#include "CalcConfigDialog.h"
+
+#include "plugins/PluginRegistry.h"
+
+CalcPlugin::CalcPlugin()
+{
+}
+
+void CalcPlugin::initialize(Wallet *wallet, QObject *parent) {
+    this->setParent(parent);
+    m_tab = new CalcWidget(nullptr);
+}
+
+QString CalcPlugin::id() {
+    return "calc";
+}
+
+int CalcPlugin::idx() const {
+    return 60;
+}
+
+QString CalcPlugin::parent() {
+    return {};
+}
+
+QString CalcPlugin::displayName() {
+    return "Calc";
+}
+
+QString CalcPlugin::description() {
+    return {};
+}
+
+QString CalcPlugin::icon() {
+    return "gnome-calc.png";
+}
+
+QStringList CalcPlugin::socketData() {
+    return {};
+}
+
+Plugin::PluginType CalcPlugin::type() {
+    return Plugin::PluginType::TAB;
+}
+
+QWidget* CalcPlugin::tab() {
+    return m_tab;
+}
+
+bool CalcPlugin::configurable() {
+    return true;
+}
+
+QDialog* CalcPlugin::configDialog(QWidget *parent) {
+    return new CalcConfigDialog{parent};
+}
+
+void CalcPlugin::skinChanged() {
+    m_tab->skinChanged();
+}
+
+const bool CalcPlugin::registered = [] {
+    PluginRegistry::registerPlugin(CalcPlugin::create());
+    PluginRegistry::getInstance().registerPluginCreator(&CalcPlugin::create);
+    return true;
+}();
diff --git a/src/plugins/calc/CalcPlugin.h b/src/plugins/calc/CalcPlugin.h
new file mode 100644 (file)
index 0000000..5b051ad
--- /dev/null
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef CALCPLUGIN_H
+#define CALCPLUGIN_H
+
+#include "plugins/Plugin.h"
+#include "CalcWidget.h"
+
+class CalcPlugin : public Plugin {
+    Q_OBJECT
+
+public:
+    explicit CalcPlugin();
+
+    QString id() override;
+    int idx() const override;
+    QString parent() override;
+    QString displayName() override;
+    QString description() override;
+    QString icon() override;
+    QStringList socketData() override;
+    PluginType type() override;
+    QWidget* tab() override;
+    bool configurable() override;
+    QDialog* configDialog(QWidget *parent) override;
+
+    void initialize(Wallet *wallet, QObject *parent) override;
+
+    static CalcPlugin* create() { return new CalcPlugin(); }
+
+public slots:
+    void skinChanged() override;
+
+private:
+    CalcWidget* m_tab = nullptr;
+    static const bool registered;
+};
+
+
+#endif //CALCPLUGIN_H
similarity index 99%
rename from src/CalcWidget.cpp
rename to src/plugins/calc/CalcWidget.cpp
index 65e35eb08faee9ea838563bfa835ac0ecb07a0e2..9ccc1e028108e61acc5d73f8341129cca7dd6a2d 100644 (file)
@@ -6,7 +6,7 @@
 
 #include <QList>
 
-#include "dialog/CalcConfigDialog.h"
+#include "CalcConfigDialog.h"
 #include "utils/AppData.h"
 #include "utils/ColorScheme.h"
 #include "utils/config.h"
similarity index 93%
rename from src/CalcWidget.ui
rename to src/plugins/calc/CalcWidget.ui
index ea3c22ca8451cd5b729066cd191e9124c57f86e3..d2e87b38f97fdc40cf04ee17e52f6156fe5c3ac2 100644 (file)
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>800</width>
-    <height>242</height>
+    <height>366</height>
    </rect>
   </property>
   <property name="windowTitle">
      </item>
     </layout>
    </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>0</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
   </layout>
  </widget>
  <customwidgets>
similarity index 96%
rename from src/CalcWindow.ui
rename to src/plugins/calc/CalcWindow.ui
index 2447bbabb68a0ee65a2229d6973f8a3636e8344a..79bed2d22187c81bd4dd8b554f20245687f1553a 100644 (file)
@@ -37,7 +37,7 @@
   <customwidget>
    <class>CalcWidget</class>
    <extends>QWidget</extends>
-   <header>CalcWidget.h</header>
+   <header>plugins/calc/CalcWidget.h</header>
    <container>1</container>
   </customwidget>
  </customwidgets>
diff --git a/src/plugins/ccs/CCSWidget.cpp b/src/plugins/ccs/CCSWidget.cpp
deleted file mode 100644 (file)
index d2ace60..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-// SPDX-License-Identifier: BSD-3-Clause
-// SPDX-FileCopyrightText: 2020-2023 The Monero Project
-
-#include "CCSWidget.h"
-#include "ui_CCSWidget.h"
-
-#include <QDesktopServices>
-#include <QStandardItemModel>
-#include <QTableWidget>
-
-#include "CCSProgressDelegate.h"
-#include "utils/Utils.h"
-
-CCSWidget::CCSWidget(QWidget *parent)
-        : QWidget(parent)
-        , ui(new Ui::CSSWidget)
-        , m_model(new CCSModel(this))
-        , m_contextMenu(new QMenu(this))
-{
-    ui->setupUi(this);
-    ui->treeView->setModel(m_model);
-
-    m_contextMenu->addAction("View proposal", this, &CCSWidget::linkClicked);
-    m_contextMenu->addAction("Donate", this, &CCSWidget::donateClicked);
-
-    connect(ui->treeView, &QHeaderView::customContextMenuRequested, this, &CCSWidget::showContextMenu);
-    connect(ui->treeView, &QTreeView::doubleClicked, this, &CCSWidget::linkClicked);
-
-    ui->treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
-    ui->treeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
-    ui->treeView->header()->setSectionResizeMode(CCSModel::Title, QHeaderView::Stretch);
-}
-
-CCSModel* CCSWidget::model() {
-    return m_model;
-}
-
-void CCSWidget::linkClicked() {
-    QModelIndex index = ui->treeView->currentIndex();
-    auto entry = m_model->entry(index.row());
-
-    if (entry) {
-        Utils::externalLinkWarning(this, entry->url);
-    }
-}
-
-void CCSWidget::donateClicked() {
-    QModelIndex index = ui->treeView->currentIndex();
-    auto entry = m_model->entry(index.row());
-
-    if (entry)
-        emit selected(*entry);
-}
-
-void CCSWidget::showContextMenu(const QPoint &pos) {
-    QModelIndex index = ui->treeView->indexAt(pos);
-        if (!index.isValid()) {
-        return;
-    }
-
-    m_contextMenu->exec(ui->treeView->viewport()->mapToGlobal(pos));
-}
-
-CCSWidget::~CCSWidget() = default;
\ No newline at end of file
similarity index 98%
rename from src/plugins/ccs/CCSModel.cpp
rename to src/plugins/crowdfunding/CCSModel.cpp
index a8e9d1ca74223245f97434370dd1bdaf22081419..101ef7cb66cf4e7068410d7cadc6e73509f49ab5 100644 (file)
@@ -78,7 +78,7 @@ QVariant CCSModel::headerData(int section, Qt::Orientation orientation, int role
             case Title:
                 return QString("Proposal");
             case Organizer:
-                return QString("Organizer");
+                return QString("Organizer ");
             case Author:
                 return QString("Author");
             case Progress:
diff --git a/src/plugins/crowdfunding/CCSWidget.cpp b/src/plugins/crowdfunding/CCSWidget.cpp
new file mode 100644 (file)
index 0000000..f1fbe5d
--- /dev/null
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "CCSWidget.h"
+#include "ui_CCSWidget.h"
+
+#include <QTableWidget>
+
+#include "CCSProgressDelegate.h"
+#include "utils/Utils.h"
+#include "utils/WebsocketNotifier.h"
+
+CCSWidget::CCSWidget(QWidget *parent)
+        : QWidget(parent)
+        , ui(new Ui::CSSWidget)
+        , m_model(new CCSModel(this))
+        , m_contextMenu(new QMenu(this))
+{
+    ui->setupUi(this);
+    ui->treeView->setModel(m_model);
+
+    m_contextMenu->addAction("View proposal", this, &CCSWidget::linkClicked);
+    m_contextMenu->addAction("Donate", this, &CCSWidget::donateClicked);
+
+    connect(ui->treeView, &QHeaderView::customContextMenuRequested, this, &CCSWidget::showContextMenu);
+    connect(ui->treeView, &QTreeView::doubleClicked, this, &CCSWidget::linkClicked);
+
+    ui->treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
+    ui->treeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+    ui->treeView->header()->setSectionResizeMode(CCSModel::Title, QHeaderView::Stretch);
+
+    connect(websocketNotifier(), &WebsocketNotifier::dataReceived, this, [this](const QString& type, const QJsonValue& json) {
+        if (type == "ccs") {
+            QJsonArray ccs_data = json.toArray();
+            QList<QSharedPointer<CCSEntry>> l;
+
+            for (const auto& entry: ccs_data) {
+                auto obj = entry.toObject();
+                auto c = QSharedPointer<CCSEntry>(new CCSEntry());
+
+                if (obj.value("state").toString() != "FUNDING-REQUIRED")
+                    continue;
+
+                c->state = obj.value("state").toString();
+                c->address = obj.value("address").toString();
+                c->author = obj.value("author").toString();
+                c->date = obj.value("date").toString();
+                c->title = obj.value("title").toString();
+                c->target_amount = obj.value("target_amount").toDouble();
+                c->raised_amount = obj.value("raised_amount").toDouble();
+                c->percentage_funded = obj.value("percentage_funded").toDouble();
+                c->contributions = obj.value("contributions").toInt();
+                c->organizer = obj.value("organizer").toString();
+                c->currency = obj.value("currency").toString();
+
+                QString urlpath = obj.value("urlpath").toString();
+                if (c->organizer == "CCS") {
+                    c->url = QString("https://ccs.getmonero.org/%1").arg(urlpath);
+                }
+                else if (c->organizer == "MAGIC") {
+                    c->url = QString("https://monerofund.org/%1").arg(urlpath);
+                }
+                else {
+                    continue;
+                }
+
+                l.append(c);
+            }
+
+            m_model->updateEntries(l);
+        }
+    });
+}
+
+void CCSWidget::linkClicked() {
+    QModelIndex index = ui->treeView->currentIndex();
+    auto entry = m_model->entry(index.row());
+
+    if (entry) {
+        Utils::externalLinkWarning(this, entry->url);
+    }
+}
+
+void CCSWidget::donateClicked() {
+    QModelIndex index = ui->treeView->currentIndex();
+    auto entry = m_model->entry(index.row());
+
+    if (entry) {
+        emit fillSendTab(entry->address, QString("Donation to %1: %2").arg(entry->organizer, entry->title));
+    }
+}
+
+void CCSWidget::showContextMenu(const QPoint &pos) {
+    QModelIndex index = ui->treeView->indexAt(pos);
+        if (!index.isValid()) {
+        return;
+    }
+
+    m_contextMenu->exec(ui->treeView->viewport()->mapToGlobal(pos));
+}
+
+CCSWidget::~CCSWidget() = default;
\ No newline at end of file
similarity index 90%
rename from src/plugins/ccs/CCSWidget.h
rename to src/plugins/crowdfunding/CCSWidget.h
index cd344cfe0f2b5c531dba2646af52c1430b0f7852..d5ac1d73aa81f2923932e08970134b539aea438e 100644 (file)
@@ -24,10 +24,9 @@ Q_OBJECT
 public:
     explicit CCSWidget(QWidget *parent = nullptr);
     ~CCSWidget();
-    CCSModel *model();
 
 signals:
-    void selected(CCSEntry entry);
+    void fillSendTab(const QString &address, const QString &description);
 
 public slots:
     void donateClicked();
diff --git a/src/plugins/crowdfunding/CrowdfundingPlugin.cpp b/src/plugins/crowdfunding/CrowdfundingPlugin.cpp
new file mode 100644 (file)
index 0000000..834d432
--- /dev/null
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "CrowdfundingPlugin.h"
+
+#include "plugins/PluginRegistry.h"
+#include "CCSWidget.h"
+
+CrowdfundingPlugin::CrowdfundingPlugin()
+{
+}
+
+void CrowdfundingPlugin::initialize(Wallet *wallet, QObject *parent) {
+    this->setParent(parent);
+    m_tab = new CCSWidget(nullptr);
+    connect(m_tab, &CCSWidget::fillSendTab, this, &Plugin::fillSendTab);
+}
+
+QString CrowdfundingPlugin::id() {
+    return "crowdfunding";
+}
+
+int CrowdfundingPlugin::idx() const {
+    return 10;
+}
+
+QString CrowdfundingPlugin::parent() {
+    return "home";
+}
+
+QString CrowdfundingPlugin::displayName() {
+    return "Crowdfunding";
+}
+
+QString CrowdfundingPlugin::description() {
+    return {};
+}
+
+QString CrowdfundingPlugin::icon() {
+    return {};
+}
+
+QStringList CrowdfundingPlugin::socketData() {
+    return {"ccs"};
+}
+
+Plugin::PluginType CrowdfundingPlugin::type() {
+    return Plugin::PluginType::TAB;
+}
+
+QWidget* CrowdfundingPlugin::tab() {
+    return m_tab;
+}
+
+const bool CrowdfundingPlugin::registered = [] {
+    PluginRegistry::registerPlugin(CrowdfundingPlugin::create());
+    PluginRegistry::getInstance().registerPluginCreator(&CrowdfundingPlugin::create);
+    return true;
+}();
diff --git a/src/plugins/crowdfunding/CrowdfundingPlugin.h b/src/plugins/crowdfunding/CrowdfundingPlugin.h
new file mode 100644 (file)
index 0000000..90c8a3f
--- /dev/null
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef CROWDFUNDINGPLUGIN_H
+#define CROWDFUNDINGPLUGIN_H
+
+#include "plugins/Plugin.h"
+#include "CCSWidget.h"
+
+class CrowdfundingPlugin : public Plugin {
+    Q_OBJECT
+
+public:
+    explicit CrowdfundingPlugin();
+
+    QString id() override;
+    int idx() const override;
+    QString parent() override;
+    QString displayName() override;
+    QString description() override;
+    QString icon() override;
+    QStringList socketData() override;
+    PluginType type() override;
+    QWidget* tab() override;
+
+    void initialize(Wallet *wallet, QObject *parent) override;
+
+    static CrowdfundingPlugin* create() { return new CrowdfundingPlugin(); }
+
+private:
+    CCSWidget* m_tab = nullptr;
+    static const bool registered;
+};
+
+#endif //CROWDFUNDINGPLUGIN_H
diff --git a/src/plugins/exchange/ExchangePlugin.cpp b/src/plugins/exchange/ExchangePlugin.cpp
new file mode 100644 (file)
index 0000000..d0231ad
--- /dev/null
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "ExchangePlugin.h"
+
+#include "plugins/PluginRegistry.h"
+
+ExchangePlugin::ExchangePlugin()
+{
+}
+
+void ExchangePlugin::initialize(Wallet *wallet, QObject *parent) {
+    this->setParent(parent);
+    m_tab = new ExchangeWidget(nullptr);
+}
+
+QString ExchangePlugin::id() {
+    return "exchange";
+}
+
+int ExchangePlugin::idx() const {
+    return 50;
+}
+
+QString ExchangePlugin::parent() {
+    return "";
+}
+
+QString ExchangePlugin::displayName() {
+    return "Exchange";
+}
+
+QString ExchangePlugin::description() {
+    return {};
+}
+QString ExchangePlugin::icon() {
+    return "update.png";
+}
+
+QStringList ExchangePlugin::socketData() {
+    return {};
+}
+
+Plugin::PluginType ExchangePlugin::type() {
+    return Plugin::PluginType::TAB;
+}
+
+QWidget* ExchangePlugin::tab() {
+    return m_tab;
+}
+
+void ExchangePlugin::addSubPlugin(Plugin* plugin) {
+    m_tab->addTab(plugin);
+}
+
+bool ExchangePlugin::implicitEnable() {
+    return true;
+}
+
+const bool ExchangePlugin::registered = [] {
+    PluginRegistry::registerPlugin(ExchangePlugin::create());
+    PluginRegistry::getInstance().registerPluginCreator(&ExchangePlugin::create);
+    return true;
+}();
diff --git a/src/plugins/exchange/ExchangePlugin.h b/src/plugins/exchange/ExchangePlugin.h
new file mode 100644 (file)
index 0000000..d9a1e42
--- /dev/null
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef EXCHANGEPLUGIN_H
+#define EXCHANGEPLUGIN_H
+
+#include "plugins/Plugin.h"
+#include "ExchangeWidget.h"
+
+class ExchangePlugin : public Plugin {
+    Q_OBJECT
+
+public:
+    explicit ExchangePlugin();
+
+    QString id() override;
+    int idx() const override;
+    QString parent() override;
+    QString displayName() override;
+    QString description() override;
+    QString icon() override;
+    QStringList socketData() override;
+    PluginType type() override;
+    QWidget* tab() override;
+    bool implicitEnable() override;
+    void addSubPlugin(Plugin* plugin) override;
+
+    void initialize(Wallet *wallet, QObject *parent) override;
+
+    static ExchangePlugin* create() { return new ExchangePlugin(); }
+
+private:
+    ExchangeWidget* m_tab = nullptr;
+    static const bool registered;
+};
+
+
+#endif //EXCHANGEPLUGIN_H
diff --git a/src/plugins/exchange/ExchangeWidget.cpp b/src/plugins/exchange/ExchangeWidget.cpp
new file mode 100644 (file)
index 0000000..d112efb
--- /dev/null
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "ExchangeWidget.h"
+#include "ui_ExchangeWidget.h"
+
+#include "utils/Icons.h"
+
+ExchangeWidget::ExchangeWidget(QWidget *parent)
+        : QWidget(parent)
+        , ui(new Ui::ExchangeWidget)
+{
+    ui->setupUi(this);
+}
+
+void ExchangeWidget::addTab(Plugin *plugin) {
+    QWidget* tab = plugin->tab();
+    auto icon = icons()->icon(plugin->icon());
+    QString name = plugin->displayName();
+
+    ui->tabWidget->addTab(tab, icon, name);
+}
+
+ExchangeWidget::~ExchangeWidget() = default;
\ No newline at end of file
diff --git a/src/plugins/exchange/ExchangeWidget.h b/src/plugins/exchange/ExchangeWidget.h
new file mode 100644 (file)
index 0000000..1c2f8b9
--- /dev/null
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef EXCHANGEWIDGET_H
+#define EXCHANGEWIDGET_H
+
+#include <QWidget>
+#include <QTabWidget>
+
+#include "plugins/Plugin.h"
+
+namespace Ui {
+    class ExchangeWidget;
+}
+
+class ExchangeWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit ExchangeWidget(QWidget *parent = nullptr);
+    ~ExchangeWidget();
+
+    void addTab(Plugin *plugin);
+
+private:
+    QScopedPointer<Ui::ExchangeWidget> ui;
+};
+
+
+#endif //EXCHANGEWIDGET_H
similarity index 68%
copy from src/plugins/bounties/BountiesWidget.ui
copy to src/plugins/exchange/ExchangeWidget.ui
index d4e985afb798784f2ee0a540f045a594a5ff3c26..19d75acaafdeb8cc9dc14d03a9a067fc8a5f579a 100644 (file)
@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>BountiesWidget</class>
- <widget class="QWidget" name="BountiesWidget">
+ <class>ExchangeWidget</class>
+ <widget class="QWidget" name="ExchangeWidget">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>566</width>
-    <height>372</height>
+    <width>400</width>
+    <height>300</height>
    </rect>
   </property>
   <property name="windowTitle">
     <number>0</number>
    </property>
    <item>
-    <widget class="QTableView" name="tableView">
-     <property name="contextMenuPolicy">
-      <enum>Qt::CustomContextMenu</enum>
-     </property>
-    </widget>
+    <widget class="QTabWidget" name="tabWidget"/>
    </item>
   </layout>
  </widget>
diff --git a/src/plugins/home/HomePlugin.cpp b/src/plugins/home/HomePlugin.cpp
new file mode 100644 (file)
index 0000000..c4f1f66
--- /dev/null
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "HomePlugin.h"
+
+#include "plugins/PluginRegistry.h"
+
+HomePlugin::HomePlugin()
+{
+}
+
+void HomePlugin::initialize(Wallet *wallet, QObject *parent) {
+    this->setParent(parent);
+    m_tab = new HomeWidget(nullptr);
+}
+
+QString HomePlugin::id() {
+    return "home";
+}
+
+int HomePlugin::idx() const {
+    return 0;
+}
+
+QString HomePlugin::parent() {
+    return "";
+}
+
+QString HomePlugin::displayName() {
+    return "Home";
+}
+
+QString HomePlugin::description() {
+    return {};
+}
+QString HomePlugin::icon() {
+    return "tab_home.png";
+}
+
+QStringList HomePlugin::socketData() {
+    return {};
+}
+
+Plugin::PluginType HomePlugin::type() {
+    return Plugin::PluginType::TAB;
+}
+
+QWidget* HomePlugin::tab() {
+    return m_tab;
+}
+
+void HomePlugin::addSubPlugin(Plugin* plugin) {
+    m_tab->addPlugin(plugin);
+}
+
+bool HomePlugin::implicitEnable() {
+    return true;
+}
+
+bool HomePlugin::insertFirst() {
+    return true;
+}
+
+void HomePlugin::aboutToQuit() {
+    m_tab->aboutToQuit();
+}
+
+void HomePlugin::uiSetup() {
+    m_tab->uiSetup();
+}
+
+const bool HomePlugin::registered = [] {
+    PluginRegistry::registerPlugin(HomePlugin::create());
+    PluginRegistry::getInstance().registerPluginCreator(&HomePlugin::create);
+    return true;
+}();
diff --git a/src/plugins/home/HomePlugin.h b/src/plugins/home/HomePlugin.h
new file mode 100644 (file)
index 0000000..a2d8477
--- /dev/null
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef HOMEPLUGIN_H
+#define HOMEPLUGIN_H
+
+#include "plugins/Plugin.h"
+#include "HomeWidget.h"
+
+class HomePlugin : public Plugin {
+    Q_OBJECT
+
+public:
+    explicit HomePlugin();
+
+    QString id() override;
+    int idx() const override;
+    QString parent() override;
+    QString displayName() override;
+    QString description() override;
+    QString icon() override;
+    QStringList socketData() override;
+    PluginType type() override;
+    QWidget* tab() override;
+    bool implicitEnable() override;
+    bool insertFirst() override;
+    void addSubPlugin(Plugin* plugin) override;
+
+    void initialize(Wallet *wallet, QObject *parent) override;
+
+    static HomePlugin* create() { return new HomePlugin(); }
+
+public slots:
+    void aboutToQuit() override;
+    void uiSetup() override;
+
+private:
+    HomeWidget* m_tab = nullptr;
+    static const bool registered;
+};
+
+
+#endif //HOMEPLUGIN_H
diff --git a/src/plugins/home/HomeWidget.cpp b/src/plugins/home/HomeWidget.cpp
new file mode 100644 (file)
index 0000000..03b14cf
--- /dev/null
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "HomeWidget.h"
+#include "ui_HomeWidget.h"
+
+#include "utils/config.h"
+
+HomeWidget::HomeWidget(QWidget *parent)
+        : QWidget(parent)
+        , ui(new Ui::HomeWidget)
+{
+    ui->setupUi(this);
+}
+
+void HomeWidget::addPlugin(Plugin *plugin)
+{
+    if (plugin->type() == Plugin::TAB) {
+        ui->tabHomeWidget->addTab(plugin->tab(), plugin->displayName());
+    }
+    else if (plugin->type() == Plugin::WIDGET) {
+        ui->widgetLayout->addWidget(plugin->tab());
+    }
+}
+
+void HomeWidget::uiSetup() {
+    ui->tabHomeWidget->setCurrentIndex(conf()->get(Config::homeWidget).toInt());
+}
+
+void HomeWidget::aboutToQuit() {
+    conf()->set(Config::homeWidget, ui->tabHomeWidget->currentIndex());
+}
+
+HomeWidget::~HomeWidget() = default;
\ No newline at end of file
diff --git a/src/plugins/home/HomeWidget.h b/src/plugins/home/HomeWidget.h
new file mode 100644 (file)
index 0000000..bef6457
--- /dev/null
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef HOMEWIDGET_H
+#define HOMEWIDGET_H
+
+#include <QWidget>
+
+#include "plugins/Plugin.h"
+
+namespace Ui {
+    class HomeWidget;
+}
+
+class HomeWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit HomeWidget(QWidget *parent = nullptr);
+    ~HomeWidget();
+
+    void addPlugin(Plugin *plugin);
+    void aboutToQuit();
+    void uiSetup();
+
+private:
+    QScopedPointer<Ui::HomeWidget> ui;
+};
+
+#endif //HOMEWIDGET_H
similarity index 51%
copy from src/plugins/bounties/BountiesWidget.ui
copy to src/plugins/home/HomeWidget.ui
index d4e985afb798784f2ee0a540f045a594a5ff3c26..ad44b9961e84af50e8a88377bcdb75d3908636a2 100644 (file)
@@ -1,35 +1,35 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>BountiesWidget</class>
- <widget class="QWidget" name="BountiesWidget">
+ <class>HomeWidget</class>
+ <widget class="QWidget" name="HomeWidget">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>566</width>
-    <height>372</height>
+    <width>743</width>
+    <height>460</height>
    </rect>
   </property>
   <property name="windowTitle">
    <string>Form</string>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
-   <property name="leftMargin">
-    <number>0</number>
-   </property>
    <property name="topMargin">
-    <number>0</number>
-   </property>
-   <property name="rightMargin">
-    <number>0</number>
+    <number>5</number>
    </property>
    <property name="bottomMargin">
-    <number>0</number>
+    <number>5</number>
    </property>
    <item>
-    <widget class="QTableView" name="tableView">
-     <property name="contextMenuPolicy">
-      <enum>Qt::CustomContextMenu</enum>
+    <layout class="QVBoxLayout" name="widgetLayout"/>
+   </item>
+   <item>
+    <widget class="QTabWidget" name="tabHomeWidget">
+     <property name="currentIndex">
+      <number>-1</number>
+     </property>
+     <property name="documentMode">
+      <bool>true</bool>
      </property>
     </widget>
    </item>
diff --git a/src/plugins/localmonero/LocalMoneroPlugin.cpp b/src/plugins/localmonero/LocalMoneroPlugin.cpp
new file mode 100644 (file)
index 0000000..73ad519
--- /dev/null
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "LocalMoneroPlugin.h"
+
+LocalMoneroPlugin::LocalMoneroPlugin()
+{
+}
+
+void LocalMoneroPlugin::initialize(Wallet *wallet, QObject *parent) {
+    this->setParent(parent);
+    m_tab = new LocalMoneroWidget(nullptr, wallet);
+}
+
+QString LocalMoneroPlugin::id() {
+    return "localmonero";
+}
+
+int LocalMoneroPlugin::idx() const {
+    return 0;
+}
+
+QString LocalMoneroPlugin::parent() {
+    return "exchange";
+}
+
+QString LocalMoneroPlugin::displayName() {
+    return "LocalMonero";
+}
+
+QString LocalMoneroPlugin::description() {
+    return {};
+}
+QString LocalMoneroPlugin::icon() {
+    return "localMonero_logo.png";
+}
+
+QStringList LocalMoneroPlugin::socketData() {
+    return {"localmonero_countries", "localmonero_currencies", "localmonero_payment_methods"};
+}
+
+Plugin::PluginType LocalMoneroPlugin::type() {
+    return Plugin::PluginType::TAB;
+}
+
+QWidget* LocalMoneroPlugin::tab() {
+    return m_tab;
+}
+
+void LocalMoneroPlugin::skinChanged() {
+    m_tab->skinChanged();
+}
+
+const bool LocalMoneroPlugin::registered = [] {
+    PluginRegistry::registerPlugin(LocalMoneroPlugin::create());
+    PluginRegistry::getInstance().registerPluginCreator(&LocalMoneroPlugin::create);
+    return true;
+}();
diff --git a/src/plugins/localmonero/LocalMoneroPlugin.h b/src/plugins/localmonero/LocalMoneroPlugin.h
new file mode 100644 (file)
index 0000000..10db13f
--- /dev/null
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef LOCALMONEROPLUGIN_H
+#define LOCALMONEROPLUGIN_H
+
+#include "plugins/Plugin.h"
+#include "LocalMoneroWidget.h"
+#include "plugins/PluginRegistry.h"
+
+class LocalMoneroPlugin : public Plugin {
+    Q_OBJECT
+
+public:
+    explicit LocalMoneroPlugin();
+
+    QString id() override;
+    int idx() const override;
+    QString parent() override;
+    QString displayName() override;
+    QString description() override;
+    QString icon() override;
+    QStringList socketData() override;
+    PluginType type() override;
+    QWidget* tab() override;
+
+    void initialize(Wallet *wallet, QObject *parent) override;
+
+    static LocalMoneroPlugin* create() { return new LocalMoneroPlugin(); }
+
+public slots:
+    void skinChanged() override;
+
+private:
+    LocalMoneroWidget* m_tab = nullptr;
+    static const bool registered;
+};
+
+
+#endif //LOCALMONEROPLUGIN_H
index 036547752cbdb0d8fac5c50c5c4a67760234b86a..b54b18274b99edaf8b30abb6a3c52b540217fabe 100644 (file)
@@ -50,14 +50,18 @@ QVariant RedditModel::data(const QModelIndex &index, int role) const
 
     QSharedPointer<RedditPost> post = m_posts.at(index.row());
 
-    if(role == Qt::DisplayRole) {
+    if(role == Qt::DisplayRole || role == Qt::UserRole) {
         switch(index.column()) {
             case Title:
                 return post->title;
             case Author:
                 return post->author;
-            case Comments:
+            case Comments: {
+                if (role == Qt::UserRole) {
+                    return post->comments;
+                }
                 return QString::number(post->comments);
+            }
             default:
                 return QVariant();
         }
diff --git a/src/plugins/reddit/RedditPlugin.cpp b/src/plugins/reddit/RedditPlugin.cpp
new file mode 100644 (file)
index 0000000..63e1bcf
--- /dev/null
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "RedditPlugin.h"
+
+#include "plugins/PluginRegistry.h"
+#include "RedditWidget.h"
+
+RedditPlugin::RedditPlugin()
+{
+}
+
+void RedditPlugin::initialize(Wallet *wallet, QObject *parent) {
+    this->setParent(parent);
+    m_redditWidget = new RedditWidget(nullptr);
+    connect(m_redditWidget, &RedditWidget::setStatusText, this, &Plugin::setStatusText);
+}
+
+QString RedditPlugin::id() {
+    return "reddit";
+}
+
+int RedditPlugin::idx() const {
+    return 30;
+}
+
+QString RedditPlugin::parent() {
+    return "home";
+}
+
+QString RedditPlugin::displayName() {
+    return "Reddit";
+}
+
+QString RedditPlugin::description() {
+    return {};
+}
+
+QString RedditPlugin::icon() {
+    return {};
+}
+
+QStringList RedditPlugin::socketData() {
+    return {"reddit"};
+}
+
+Plugin::PluginType RedditPlugin::type() {
+    return Plugin::PluginType::TAB;
+}
+
+QWidget* RedditPlugin::tab() {
+    return m_redditWidget;
+}
+
+const bool RedditPlugin::registered = [] {
+    PluginRegistry::registerPlugin(RedditPlugin::create());
+    PluginRegistry::getInstance().registerPluginCreator(&RedditPlugin::create);
+    return true;
+}();
diff --git a/src/plugins/reddit/RedditPlugin.h b/src/plugins/reddit/RedditPlugin.h
new file mode 100644 (file)
index 0000000..83e6409
--- /dev/null
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef REDDITPLUGIN_H
+#define REDDITPLUGIN_H
+
+#include "plugins/Plugin.h"
+#include "RedditWidget.h"
+#include "plugins/PluginRegistry.h"
+
+class RedditPlugin : public Plugin {
+    Q_OBJECT
+
+public:
+    explicit RedditPlugin();
+
+    QString id() override;
+    int idx() const override;
+    QString parent() override;
+    QString displayName() override;
+    QString description() override;
+    QString icon() override;
+    QStringList socketData() override;
+    PluginType type() override;
+    QWidget* tab() override;
+
+    void initialize(Wallet *wallet, QObject *parent) override;
+
+    static RedditPlugin* create() { return new RedditPlugin(); }
+
+private:
+    RedditWidget* m_redditWidget = nullptr;
+    static const bool registered;
+};
+
+
+
+
+#endif //REDDITPLUGIN_H
diff --git a/src/plugins/reddit/RedditProxyModel.cpp b/src/plugins/reddit/RedditProxyModel.cpp
new file mode 100644 (file)
index 0000000..880b51b
--- /dev/null
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "RedditProxyModel.h"
+
+RedditProxyModel::RedditProxyModel(QObject *parent)
+        : QSortFilterProxyModel(parent)
+{
+    setSortRole(Qt::UserRole);
+}
diff --git a/src/plugins/reddit/RedditProxyModel.h b/src/plugins/reddit/RedditProxyModel.h
new file mode 100644 (file)
index 0000000..3a0528f
--- /dev/null
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef REDDITPROXYMODEL_H
+#define REDDITPROXYMODEL_H
+
+#include <QSortFilterProxyModel>
+
+class RedditProxyModel : public QSortFilterProxyModel
+{
+    Q_OBJECT
+
+public:
+    explicit RedditProxyModel(QObject* parent = nullptr);
+};
+
+#endif //REDDITPROXYMODEL_H
index e2cf12ba252ee0ec9ff0f0aed725ba65f83dd764..366001999d48fe4573397281f6764e8469de851a 100644 (file)
@@ -4,13 +4,12 @@
 #include "RedditWidget.h"
 #include "ui_RedditWidget.h"
 
-#include <QDesktopServices>
-#include <QStandardItemModel>
 #include <QTableWidget>
 
 #include "RedditModel.h"
 #include "utils/Utils.h"
 #include "utils/config.h"
+#include "utils/WebsocketNotifier.h"
 
 RedditWidget::RedditWidget(QWidget *parent)
         : QWidget(parent)
@@ -19,7 +18,13 @@ RedditWidget::RedditWidget(QWidget *parent)
         , m_contextMenu(new QMenu(this))
 {
     ui->setupUi(this);
-    ui->tableView->setModel(m_model);
+
+    m_proxyModel = new RedditProxyModel(this);
+    m_proxyModel->setSourceModel(m_model);
+
+    ui->tableView->setModel(m_proxyModel);
+    ui->tableView->setSortingEnabled(true);
+    ui->tableView->sortByColumn(2, Qt::DescendingOrder);
     this->setupTable();
 
     m_contextMenu->addAction("View thread", this, &RedditWidget::linkClicked);
@@ -29,14 +34,30 @@ RedditWidget::RedditWidget(QWidget *parent)
     connect(ui->tableView, &QTableView::doubleClicked, this, &RedditWidget::linkClicked);
 
     ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
-}
 
-RedditModel* RedditWidget::model() {
-    return m_model;
+    connect(websocketNotifier(), &WebsocketNotifier::dataReceived, this, [this](const QString& type, const QJsonValue& json) {
+        if (type == "reddit") {
+            QJsonArray reddit_data = json.toArray();
+            QList<QSharedPointer<RedditPost>> l;
+
+            for (auto &&entry: reddit_data) {
+                auto obj = entry.toObject();
+                auto redditPost = new RedditPost(
+                        obj.value("title").toString(),
+                        obj.value("author").toString(),
+                        obj.value("permalink").toString(),
+                        obj.value("comments").toInt());
+                QSharedPointer<RedditPost> r = QSharedPointer<RedditPost>(redditPost);
+                l.append(r);
+            }
+
+            m_model->updatePosts(l);
+        }
+    });
 }
 
 void RedditWidget::linkClicked() {
-    QModelIndex index = ui->tableView->currentIndex();
+    QModelIndex index = m_proxyModel->mapToSource(ui->tableView->currentIndex());
     auto post = m_model->post(index.row());
 
     if (post)
@@ -44,7 +65,7 @@ void RedditWidget::linkClicked() {
 }
 
 void RedditWidget::copyUrl() {
-    QModelIndex index = ui->tableView->currentIndex();
+    QModelIndex index = m_proxyModel->mapToSource(ui->tableView->currentIndex());
     auto post = m_model->post(index.row());
 
     if (post) {
index 950700ed16e853d8ff272b69e7dc5d213d8e6048..09e2779118fd03920c491b03628f0cd397a83da6 100644 (file)
@@ -9,6 +9,7 @@
 #include <QWidget>
 
 #include "RedditModel.h"
+#include "RedditProxyModel.h"
 
 namespace Ui {
     class RedditWidget;
@@ -21,7 +22,6 @@ class RedditWidget : public QWidget
 public:
     explicit RedditWidget(QWidget *parent = nullptr);
     ~RedditWidget();
-    RedditModel* model();
 
 public slots:
     void linkClicked();
@@ -37,6 +37,7 @@ private:
 
     QScopedPointer<Ui::RedditWidget> ui;
     RedditModel* const m_model;
+    RedditProxyModel* m_proxyModel;
     QMenu *m_contextMenu;
 };
 
diff --git a/src/plugins/revuo/RevuoPlugin.cpp b/src/plugins/revuo/RevuoPlugin.cpp
new file mode 100644 (file)
index 0000000..26ee507
--- /dev/null
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "RevuoPlugin.h"
+
+#include "plugins/PluginRegistry.h"
+#include "RevuoWidget.h"
+
+RevuoPlugin::RevuoPlugin()
+{
+}
+
+void RevuoPlugin::initialize(Wallet *wallet, QObject *parent) {
+    this->setParent(parent);
+    m_tab = new RevuoWidget(nullptr);
+    connect(m_tab, &RevuoWidget::donate, this, &Plugin::fillSendTab);
+}
+
+QString RevuoPlugin::id() {
+    return "revuo";
+}
+
+int RevuoPlugin::idx() const {
+    return 40;
+}
+
+QString RevuoPlugin::parent() {
+    return "home";
+}
+
+QString RevuoPlugin::displayName() {
+    return "Revuo";
+}
+
+QString RevuoPlugin::description() {
+    return {};
+}
+
+QString RevuoPlugin::icon() {
+    return {};
+}
+
+QStringList RevuoPlugin::socketData() {
+    return {"revuo"};
+}
+
+Plugin::PluginType RevuoPlugin::type() {
+    return Plugin::PluginType::TAB;
+}
+
+QWidget* RevuoPlugin::tab() {
+    return m_tab;
+}
+
+void RevuoPlugin::skinChanged() {
+    m_tab->skinChanged();
+}
+
+const bool RevuoPlugin::registered = [] {
+    PluginRegistry::registerPlugin(RevuoPlugin::create());
+    PluginRegistry::getInstance().registerPluginCreator(&RevuoPlugin::create);
+    return true;
+}();
diff --git a/src/plugins/revuo/RevuoPlugin.h b/src/plugins/revuo/RevuoPlugin.h
new file mode 100644 (file)
index 0000000..00d829d
--- /dev/null
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef REVUOPLUGIN_H
+#define REVUOPLUGIN_H
+
+#include "plugins/Plugin.h"
+#include "RevuoWidget.h"
+
+class RevuoPlugin : public Plugin {
+    Q_OBJECT
+
+public:
+    explicit RevuoPlugin();
+
+    QString id() override;
+    int idx() const override;
+    QString parent() override;
+    QString displayName() override;
+    QString description() override;
+    QString icon() override;
+    QStringList socketData() override;
+    PluginType type() override;
+    QWidget* tab() override;
+
+    void initialize(Wallet *wallet, QObject *parent) override;
+
+    static RevuoPlugin* create() { return new RevuoPlugin(); }
+
+public slots:
+    void skinChanged() override;
+
+private:
+    RevuoWidget* m_tab = nullptr;
+    static const bool registered;
+};
+
+#endif //REVUOPLUGIN_H
index 3fba9d9d42ab54f4dac37c2bdaf4186e331f308d..62f40d800efd7ca8706b6e07982313b9dc255aea 100644 (file)
@@ -6,6 +6,7 @@
 
 #include "utils/ColorScheme.h"
 #include "Utils.h"
+#include "utils/WebsocketNotifier.h"
 
 RevuoWidget::RevuoWidget(QWidget *parent)
         : QWidget(parent)
@@ -27,6 +28,32 @@ RevuoWidget::RevuoWidget(QWidget *parent)
 
     connect(ui->listWidget, &QListWidget::currentTextChanged, this, &RevuoWidget::onSelectItem);
     connect(ui->listWidget, &QListWidget::customContextMenuRequested, this, &RevuoWidget::showContextMenu);
+
+    connect(websocketNotifier(), &WebsocketNotifier::dataReceived, this, [this](const QString& type, const QJsonValue& json) {
+        if (type == "revuo") {
+            QJsonArray revuo_data = json.toArray();
+            QList<QSharedPointer<RevuoItem>> l;
+
+            for (const auto &entry: revuo_data) {
+                auto obj = entry.toObject();
+
+                QStringList newsbytes;
+                for (const auto &n : obj.value("newsbytes").toArray()) {
+                    newsbytes.append(n.toString());
+                }
+
+                auto revuoItem = new RevuoItem(
+                        obj.value("title").toString(),
+                        obj.value("url").toString(),
+                        newsbytes);
+
+                QSharedPointer<RevuoItem> r = QSharedPointer<RevuoItem>(revuoItem);
+                l.append(r);
+            }
+
+            this->updateItems(l);
+        }
+    });
 }
 
 void RevuoWidget::updateItems(const QList<QSharedPointer<RevuoItem>> &items) {
@@ -48,6 +75,7 @@ void RevuoWidget::updateItems(const QList<QSharedPointer<RevuoItem>> &items) {
     ui->listWidget->clear();
     ui->listWidget->addItems(titles);
     ui->listWidget->setCurrentRow(0);
+    ui->listWidget->setMinimumWidth(ui->listWidget->sizeHintForColumn(0) + 10);
 }
 
 void RevuoWidget::onSelectItem(const QString &item) {
diff --git a/src/plugins/tickers/TickersConfigAddDialog.cpp b/src/plugins/tickers/TickersConfigAddDialog.cpp
new file mode 100644 (file)
index 0000000..d5dae04
--- /dev/null
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "TickersConfigAddDialog.h"
+#include "ui_TickersConfigAddDialog.h"
+
+#include "utils/AppData.h"
+#include "utils/config.h"
+
+TickersConfigAddDialog::TickersConfigAddDialog(QWidget *parent)
+        : WindowModalDialog(parent)
+        , ui(new Ui::TickersConfigAddDialog)
+{
+    ui->setupUi(this);
+
+    QStringList cryptoCurrencies = appData()->prices.markets.keys();
+    QStringList fiatCurrencies = appData()->prices.rates.keys();
+
+    QStringList allCurrencies;
+    allCurrencies << cryptoCurrencies << fiatCurrencies;
+
+    ui->comboTicker->addItems(cryptoCurrencies);
+    ui->comboRatio1->addItems(cryptoCurrencies);
+    ui->comboRatio2->addItems(cryptoCurrencies);
+
+    connect(ui->combo_type, &QComboBox::currentIndexChanged, [this](int index) {
+        ui->stackedWidget->setCurrentIndex(index);
+    });
+
+    this->adjustSize();
+}
+
+QString TickersConfigAddDialog::getTicker() {
+    int type = ui->combo_type->currentIndex();
+
+    if (type == TickersType::TICKER) {
+        return ui->comboTicker->currentText();
+    }
+
+    if (type == TickersType::RATIO) {
+        return QString("%1/%2").arg(ui->comboRatio1->currentText(), ui->comboRatio2->currentText());
+    }
+
+    return {};
+}
+
+TickersConfigAddDialog::~TickersConfigAddDialog() = default;
\ No newline at end of file
diff --git a/src/plugins/tickers/TickersConfigAddDialog.h b/src/plugins/tickers/TickersConfigAddDialog.h
new file mode 100644 (file)
index 0000000..943a282
--- /dev/null
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef TICKERSCONFIGADDDIALOG_H
+#define TICKERSCONFIGADDDIALOG_H
+
+#include <QDialog>
+#include <QListWidget>
+
+#include "components.h"
+
+namespace Ui {
+    class TickersConfigAddDialog;
+}
+
+class TickersConfigAddDialog : public WindowModalDialog
+{
+    Q_OBJECT
+
+public:
+    explicit TickersConfigAddDialog(QWidget *parent = nullptr);
+    ~TickersConfigAddDialog() override;
+
+    enum TickersType {
+        TICKER = 0,
+        RATIO
+    };
+
+    QString getTicker();
+
+private:
+    QScopedPointer<Ui::TickersConfigAddDialog> ui;
+    TickersType m_type;
+};
+
+#endif //TICKERSCONFIGADDDIALOG_H
diff --git a/src/plugins/tickers/TickersConfigAddDialog.ui b/src/plugins/tickers/TickersConfigAddDialog.ui
new file mode 100644 (file)
index 0000000..4fadfd4
--- /dev/null
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TickersConfigAddDialog</class>
+ <widget class="QDialog" name="TickersConfigAddDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>431</width>
+    <height>122</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Add ticker</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Type:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="combo_type">
+       <item>
+        <property name="text">
+         <string>Ticker</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Ratio</string>
+        </property>
+       </item>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QStackedWidget" name="stackedWidget">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="pageTicker">
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <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>
+        <layout class="QHBoxLayout" name="horizontalLayout_2">
+         <item>
+          <widget class="QComboBox" name="comboTicker"/>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="pageRatio">
+      <layout class="QVBoxLayout" name="verticalLayout_3">
+       <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>
+        <layout class="QHBoxLayout" name="horizontalLayout_3">
+         <item>
+          <widget class="QComboBox" name="comboRatio1"/>
+         </item>
+         <item>
+          <widget class="QLabel" name="label_3">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string>/</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QComboBox" name="comboRatio2"/>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="page_3"/>
+    </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>TickersConfigAddDialog</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>TickersConfigAddDialog</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>
diff --git a/src/plugins/tickers/TickersConfigDialog.cpp b/src/plugins/tickers/TickersConfigDialog.cpp
new file mode 100644 (file)
index 0000000..fb862ee
--- /dev/null
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "TickersConfigDialog.h"
+#include "ui_TickersConfigDialog.h"
+
+#include "utils/config.h"
+
+#include "TickersConfigAddDialog.h"
+
+TickersConfigDialog::TickersConfigDialog(QWidget *parent)
+        : WindowModalDialog(parent)
+        , ui(new Ui::TickersConfigDialog)
+{
+    ui->setupUi(this);
+
+    QStringList tickers = conf()->get(Config::tickers).toStringList();
+    ui->tickerList->addItems(tickers);
+
+    ui->check_showFiatBalance->setChecked(conf()->get(Config::tickersShowFiatBalance).toBool());
+    connect(ui->check_showFiatBalance, &QCheckBox::toggled, [this](bool toggled) {
+        conf()->set(Config::tickersShowFiatBalance, toggled);
+    });
+
+    connect(ui->btn_addTicker, &QPushButton::clicked, this, &TickersConfigDialog::addTicker);
+    connect(ui->btn_removeTicker, &QPushButton::clicked, this, &TickersConfigDialog::removeTicker);
+
+    connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &TickersConfigDialog::saveConfig);
+
+    this->adjustSize();
+}
+
+void TickersConfigDialog::addTicker() {
+    auto dialog = new TickersConfigAddDialog{this};
+    switch (dialog->exec()) {
+        case Accepted: {
+            ui->tickerList->addItem(dialog->getTicker());
+        }
+        default:
+            return;
+    }
+}
+
+void TickersConfigDialog::removeTicker() {
+    int currentRow = ui->tickerList->currentRow();
+    if (currentRow < 0) {
+        return;
+    }
+
+    ui->tickerList->takeItem(currentRow);
+}
+
+void TickersConfigDialog::saveConfig() {
+    QStringList tickers;
+    for (int i = 0; i < ui->tickerList->count(); i++) {
+        tickers << ui->tickerList->item(i)->text();
+    }
+    conf()->set(Config::tickers, tickers);
+}
+
+TickersConfigDialog::~TickersConfigDialog() = default;
\ No newline at end of file
diff --git a/src/plugins/tickers/TickersConfigDialog.h b/src/plugins/tickers/TickersConfigDialog.h
new file mode 100644 (file)
index 0000000..6fe5388
--- /dev/null
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef TICKERCONFIGDIALOG_H
+#define TICKERCONFIGDIALOG_H
+
+#include <QDialog>
+#include <QListWidget>
+
+#include "components.h"
+
+namespace Ui {
+    class TickersConfigDialog;
+}
+
+class TickersConfigDialog : public WindowModalDialog
+{
+    Q_OBJECT
+
+public:
+    explicit TickersConfigDialog(QWidget *parent = nullptr);
+    ~TickersConfigDialog() override;
+
+private:
+    void addTicker();
+    void removeTicker();
+    void saveConfig();
+
+    QScopedPointer<Ui::TickersConfigDialog> ui;
+};
+
+#endif //TICKERCONFIGDIALOG_H
diff --git a/src/plugins/tickers/TickersConfigDialog.ui b/src/plugins/tickers/TickersConfigDialog.ui
new file mode 100644 (file)
index 0000000..03b97ac
--- /dev/null
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TickersConfigDialog</class>
+ <widget class="QDialog" name="TickersConfigDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>426</width>
+    <height>392</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Tickers config</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QListWidget" name="tickerList">
+     <property name="dragEnabled">
+      <bool>true</bool>
+     </property>
+     <property name="dragDropMode">
+      <enum>QAbstractItemView::InternalMove</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QCheckBox" name="check_showFiatBalance">
+     <property name="text">
+      <string>Show fiat balance</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QPushButton" name="btn_removeTicker">
+       <property name="text">
+        <string>Remove Ticker</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="btn_addTicker">
+       <property name="text">
+        <string>Add Ticker</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Ok</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>TickersConfigDialog</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>TickersConfigDialog</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>
diff --git a/src/plugins/tickers/TickersPlugin.cpp b/src/plugins/tickers/TickersPlugin.cpp
new file mode 100644 (file)
index 0000000..334b391
--- /dev/null
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "TickersPlugin.h"
+
+#include "plugins/PluginRegistry.h"
+
+#include "TickersConfigDialog.h"
+
+TickersPlugin::TickersPlugin()
+{
+}
+
+void TickersPlugin::initialize(Wallet *wallet, QObject *parent) {
+    this->setParent(parent);
+    m_tab = new TickersWidget(nullptr, wallet);
+}
+
+QString TickersPlugin::id() {
+    return "tickers";
+}
+
+int TickersPlugin::idx() const {
+    return 0;
+}
+
+QString TickersPlugin::parent() {
+    return "home";
+}
+
+QString TickersPlugin::displayName() {
+    return "Tickers";
+}
+
+QString TickersPlugin::description() {
+    return {};
+}
+
+QString TickersPlugin::icon() {
+    return {};
+}
+
+QStringList TickersPlugin::socketData() {
+    return {};
+}
+
+Plugin::PluginType TickersPlugin::type() {
+    return Plugin::PluginType::WIDGET;
+}
+
+QWidget* TickersPlugin::tab() {
+    return m_tab;
+}
+
+bool TickersPlugin::configurable() {
+    return true;
+}
+
+QDialog* TickersPlugin::configDialog(QWidget *parent) {
+    return new TickersConfigDialog{parent};
+}
+
+const bool TickersPlugin::registered = [] {
+    PluginRegistry::registerPlugin(TickersPlugin::create());
+    PluginRegistry::getInstance().registerPluginCreator(&TickersPlugin::create);
+    return true;
+}();
diff --git a/src/plugins/tickers/TickersPlugin.h b/src/plugins/tickers/TickersPlugin.h
new file mode 100644 (file)
index 0000000..c73015d
--- /dev/null
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef TICKERPLUGIN_H
+#define TICKERPLUGIN_H
+
+#include "plugins/Plugin.h"
+#include "TickersWidget.h"
+
+class TickersPlugin : public Plugin {
+    Q_OBJECT
+
+public:
+    explicit TickersPlugin();
+
+    QString id() override;
+    int idx() const override;
+    QString parent() override;
+    QString displayName() override;
+    QString description() override;
+    QString icon() override;
+    QStringList socketData() override;
+    PluginType type() override;
+    QWidget* tab() override;
+    bool configurable() override;
+    QDialog* configDialog(QWidget *parent) override;
+
+    void initialize(Wallet *wallet, QObject *parent) override;
+
+    static TickersPlugin* create() { return new TickersPlugin(); }
+
+private:
+    TickersWidget* m_tab = nullptr;
+    static const bool registered;
+};
+
+
+#endif //TICKERPLUGIN_H
diff --git a/src/plugins/tickers/TickersWidget.cpp b/src/plugins/tickers/TickersWidget.cpp
new file mode 100644 (file)
index 0000000..62f3f37
--- /dev/null
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "TickersWidget.h"
+#include "ui_TickersWidget.h"
+
+#include "utils/config.h"
+#include "WindowManager.h"
+
+TickersWidget::TickersWidget(QWidget *parent, Wallet *wallet)
+    : QWidget(parent)
+    , ui(new Ui::TickersWidget)
+    , m_wallet(wallet)
+{
+    ui->setupUi(this);
+    this->setup();
+
+    // TODO: this is a hack: find a better way to route settings signals to plugins
+    connect(windowManager(), &WindowManager::updateBalance, this, &TickersWidget::updateBalance);
+    connect(windowManager(), &WindowManager::preferredFiatCurrencyChanged, this, &TickersWidget::updateDisplay);
+    connect(windowManager(), &WindowManager::pluginConfigured, [this](const QString &id) {
+       if (id == "tickers") {
+           this->setup();
+       }
+    });
+    this->updateBalance();
+}
+
+void TickersWidget::setup() {
+    QStringList tickers = conf()->get(Config::tickers).toStringList();
+
+    Utils::clearLayout(ui->tickerLayout);
+    Utils::clearLayout(ui->fiatTickerLayout);
+
+    m_tickerWidgets.clear();
+    m_balanceTickerWidget.reset(nullptr);
+
+    for (const auto &ticker : tickers) {
+        if (ticker.contains("/")) { // ratio
+            QStringList symbols = ticker.split("/");
+            if (symbols.length() != 2) {
+                qWarning() << "Invalid ticker in config: " << ticker;
+            }
+            auto* tickerWidget = new RatioTickerWidget(this, m_wallet, symbols[0], symbols[1]);
+            m_tickerWidgets.append(tickerWidget);
+            ui->tickerLayout->addWidget(tickerWidget);
+        } else {
+            auto* tickerWidget = new PriceTickerWidget(this, m_wallet, ticker);
+            m_tickerWidgets.append(tickerWidget);
+            ui->tickerLayout->addWidget(tickerWidget);
+        }
+    }
+
+    if (conf()->get(Config::tickersShowFiatBalance).toBool()) {
+        m_balanceTickerWidget.reset(new BalanceTickerWidget(this, m_wallet, false));
+        ui->fiatTickerLayout->addWidget(m_balanceTickerWidget.data());
+    }
+
+    this->updateBalance();
+    this->updateDisplay();
+}
+
+void TickersWidget::updateBalance() {
+    ui->frame_fiatTickerLayout->setHidden(conf()->get(Config::hideBalance).toBool());
+}
+
+void TickersWidget::updateDisplay() {
+    for (const auto &widget : m_tickerWidgets) {
+        widget->updateDisplay();
+    }
+    if (m_balanceTickerWidget) {
+        m_balanceTickerWidget->updateDisplay();
+    }
+}
+
+TickersWidget::~TickersWidget() = default;
\ No newline at end of file
diff --git a/src/plugins/tickers/TickersWidget.h b/src/plugins/tickers/TickersWidget.h
new file mode 100644 (file)
index 0000000..de36088
--- /dev/null
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef TICKERSWIDGET_H
+#define TICKERSWIDGET_H
+
+#include <QWidget>
+
+#include "Wallet.h"
+#include "widgets/TickerWidget.h"
+
+namespace Ui {
+    class TickersWidget;
+}
+
+class TickersWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit TickersWidget(QWidget *parent, Wallet *wallet);
+    ~TickersWidget() override;
+
+private slots:
+    void updateBalance();
+    void updateDisplay();
+
+private:
+    void setup();
+
+    QScopedPointer<Ui::TickersWidget> ui;
+    Wallet *m_wallet;
+
+    QList<TickerWidgetBase*> m_tickerWidgets;
+    QScopedPointer<BalanceTickerWidget> m_balanceTickerWidget;
+};
+
+#endif //TICKERSWIDGET_H
diff --git a/src/plugins/tickers/TickersWidget.ui b/src/plugins/tickers/TickersWidget.ui
new file mode 100644 (file)
index 0000000..17d91bc
--- /dev/null
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TickersWidget</class>
+ <widget class="QWidget" name="TickersWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>618</width>
+    <height>161</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <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>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <layout class="QHBoxLayout" name="tickerLayout"/>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>0</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QFrame" name="frame_fiatTickerLayout">
+       <property name="frameShape">
+        <enum>QFrame::NoFrame</enum>
+       </property>
+       <property name="frameShadow">
+        <enum>QFrame::Raised</enum>
+       </property>
+       <property name="lineWidth">
+        <number>0</number>
+       </property>
+       <layout class="QHBoxLayout" name="fiatTickerLayout">
+        <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>
+       </layout>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="Line" name="line">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/plugins/xmrig/XMRigPlugin.cpp b/src/plugins/xmrig/XMRigPlugin.cpp
new file mode 100644 (file)
index 0000000..9723964
--- /dev/null
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "XMRigPlugin.h"
+
+#include "plugins/PluginRegistry.h"
+
+XMRigPlugin::XMRigPlugin()
+{
+}
+
+
+void XMRigPlugin::initialize(Wallet *wallet, QObject *parent) {
+    this->setParent(parent);
+    m_tab = new XMRigWidget(wallet, nullptr);
+}
+
+QString XMRigPlugin::id() {
+    return "xmrig";
+}
+
+int XMRigPlugin::idx() const {
+    return 70;
+}
+
+QString XMRigPlugin::parent() {
+    return {};
+}
+
+QString XMRigPlugin::displayName() {
+    return "Mining";
+}
+
+QString XMRigPlugin::description() {
+    return {};
+}
+
+QString XMRigPlugin::icon() {
+    return "mining.png";
+}
+
+QStringList XMRigPlugin::socketData() {
+    return {"xmrig"};
+}
+
+Plugin::PluginType XMRigPlugin::type() {
+    return Plugin::PluginType::TAB;
+}
+
+QWidget* XMRigPlugin::tab() {
+    return m_tab;
+}
+
+bool XMRigPlugin::requiresWebsocket() {
+    return false;
+}
+
+const bool XMRigPlugin::registered = [] {
+    PluginRegistry::registerPlugin(XMRigPlugin::create());
+    PluginRegistry::getInstance().registerPluginCreator(&XMRigPlugin::create);
+    return true;
+}();
diff --git a/src/plugins/xmrig/XMRigPlugin.h b/src/plugins/xmrig/XMRigPlugin.h
new file mode 100644 (file)
index 0000000..f6a5573
--- /dev/null
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef XMRIGPLUGIN_H
+#define XMRIGPLUGIN_H
+
+#include "plugins/Plugin.h"
+#include "XMRigWidget.h"
+
+class XMRigPlugin : public Plugin {
+    Q_OBJECT
+
+public:
+    explicit XMRigPlugin();
+
+    QString id() override;
+    int idx() const override;
+    QString parent() override;
+    QString displayName() override;
+    QString description() override;
+    QString icon() override;
+    QStringList socketData() override;
+    PluginType type() override;
+    QWidget* tab() override;
+    bool requiresWebsocket() override;
+
+    void initialize(Wallet *wallet, QObject *parent) override;
+
+    static XMRigPlugin* create() { return new XMRigPlugin(); }
+
+private:
+    XMRigWidget* m_tab = nullptr;
+    static const bool registered;
+};
+
+#endif //XMRIGPLUGIN_H
index 618b772565201db19c9b5af83446af59935ad7b4..969ff0000338eb9c5819f82c7b04bdb67b2a1138 100644 (file)
 #include <QStandardItemModel>
 #include <QTableWidget>
 
+#include "WebsocketNotifier.h"
 #include "utils/Icons.h"
 #include "utils/Utils.h"
+#include "WindowManager.h"
 
 XMRigWidget::XMRigWidget(Wallet *wallet, QWidget *parent)
         : QWidget(parent)
@@ -128,6 +130,14 @@ XMRigWidget::XMRigWidget(Wallet *wallet, QWidget *parent)
     ui->label_status->hide();
 
     this->printConsoleInfo();
+
+    connect(windowManager(), &WindowManager::websocketStatusChanged, this, &XMRigWidget::setDownloadsTabEnabled);
+    connect(websocketNotifier(), &WebsocketNotifier::dataReceived, this, [this](const QString &type, const QJsonValue &json) {
+        if (type == "xmrig") {
+            QJsonObject xmrig_data = json.toObject();
+            this->onDownloads(xmrig_data);
+        }
+    });
 }
 
 bool XMRigWidget::isMining() {
index 86cfe8cb9b8b30d1043de0207c503de226f56a34..b022eb2022a48095a8d057b6a09ec43dac3e9db5 100644 (file)
@@ -445,7 +445,7 @@ QString amountToCurrencyString(double amount, const QString &currencyCode) {
     if (currencyCode == "USD")
         return locale.toCurrencyString(amount, "$").remove("\xC2\xA0");
 
-    return locale.toCurrencyString(amount).remove("\xC2\xA0");
+    return QString("%1%2").arg(locale.currencySymbol(), QString::number(amount, 'f', 2));
 }
 
 QStandardItem *qStandardItem(const QString& text) {
@@ -693,4 +693,19 @@ QWindow *windowForQObject(QObject* object) {
     }
     return nullptr;
 }
+
+void clearLayout(QLayout* layout, bool deleteWidgets)
+{
+    while (QLayoutItem *item = layout->takeAt(0)) {
+        if (deleteWidgets) {
+            if (QWidget *widget = item->widget()) {
+                widget->deleteLater();
+            }
+        }
+        if (QLayout *childLayout = item->layout()) {
+            clearLayout(childLayout, deleteWidgets);
+        }
+        delete item;
+    }
+}
 }
index 2367bd48a02850b7477c39d5e31fdd20c96ccbdb..ba530e92c4f46d5a7021b91963bda91bab6bd952 100644 (file)
@@ -113,6 +113,7 @@ namespace Utils
     void openDir(QWidget *parent, const QString &message, const QString& dir);
 
     QWindow* windowForQObject(QObject* object);
+    void clearLayout(QLayout *layout, bool deleteWidgets = true);
 }
 
 #endif //FEATHER_UTILS_H
index 346a8b6014d5527a085fc8bfb2b8e626d26421d3..8f18d70d8a90d1b8e605a13bfd14b4eace4037d5 100644 (file)
@@ -5,6 +5,7 @@
 #include "Utils.h"
 #include "utils/os/tails.h"
 #include "utils/os/whonix.h"
+#include "plugins/PluginRegistry.h"
 
 #include <QJsonObject>
 
@@ -13,6 +14,10 @@ WebsocketNotifier::WebsocketNotifier(QObject *parent)
     , websocketClient(new WebsocketClient(this))
 {
     connect(websocketClient, &WebsocketClient::WSMessage, this, &WebsocketNotifier::onWSMessage);
+
+    for (const auto& plugin : PluginRegistry::getPlugins()) {
+        m_pluginSubscriptions << plugin->socketData();
+    }
 }
 
 QPointer<WebsocketNotifier> WebsocketNotifier::m_instance(nullptr);
@@ -45,56 +50,20 @@ void WebsocketNotifier::onWSMessage(const QJsonObject &msg) {
         emit FiatRatesReceived(fiat_rates);
     }
 
-    else if(cmd == "reddit") {
-        QJsonArray reddit_data = msg.value("data").toArray();
-        this->onWSReddit(reddit_data);
-    }
-
-    else if(cmd == "ccs") {
-        auto ccs_data = msg.value("data").toArray();
-        this->onWSCCS(ccs_data);
-    }
-
-    else if(cmd == "bounties") {
-        auto data = msg.value("data").toArray();
-        this->onWSBounties(data);
-    }
-
     else if(cmd == "txFiatHistory") {
         auto txFiatHistory_data = msg.value("data").toObject();
         emit TxFiatHistoryReceived(txFiatHistory_data);
     }
 
-    else if(cmd == "revuo") {
-        auto revuo_data = msg.value("data").toArray();
-        this->onWSRevuo(revuo_data);
-    }
-
 #if defined(CHECK_UPDATES)
     else if (cmd == "updates") {
         this->onWSUpdates(msg.value("data").toObject());
     }
 #endif
 
-#if defined(HAS_XMRIG)
-    else if(cmd == "xmrig") {
-        this->onWSXMRigDownloads(msg.value("data").toObject());
-    }
-#endif
-
-#if defined(HAS_LOCALMONERO)
-    else if (cmd == "localmonero_countries") {
-        emit LocalMoneroCountriesReceived(msg.value("data").toArray());
-    }
-
-    else if (cmd == "localmonero_currencies") {
-        emit LocalMoneroCurrenciesReceived(msg.value("data").toArray());
+    else if (m_pluginSubscriptions.contains(cmd)) {
+        emit dataReceived(cmd, msg.value("data"));
     }
-
-    else if (cmd == "localmonero_payment_methods") {
-        emit LocalMoneroPaymentMethodsReceived(msg.value("data").toObject());
-    }
-#endif
 }
 
 void WebsocketNotifier::emitCache() {
@@ -136,103 +105,6 @@ void WebsocketNotifier::onWSNodes(const QJsonArray &nodes) {
     emit NodesReceived(l);
 }
 
-void WebsocketNotifier::onWSReddit(const QJsonArray& reddit_data) {
-    QList<QSharedPointer<RedditPost>> l;
-
-    for (auto &&entry: reddit_data) {
-        auto obj = entry.toObject();
-        auto redditPost = new RedditPost(
-                obj.value("title").toString(),
-                obj.value("author").toString(),
-                obj.value("permalink").toString(),
-                obj.value("comments").toInt());
-        QSharedPointer<RedditPost> r = QSharedPointer<RedditPost>(redditPost);
-        l.append(r);
-    }
-
-    emit RedditReceived(l);
-}
-
-void WebsocketNotifier::onWSCCS(const QJsonArray &ccs_data) {
-    QList<QSharedPointer<CCSEntry>> l;
-
-    for (const auto& entry: ccs_data) {
-        auto obj = entry.toObject();
-        auto c = QSharedPointer<CCSEntry>(new CCSEntry());
-
-        if (obj.value("state").toString() != "FUNDING-REQUIRED")
-            continue;
-
-        c->state = obj.value("state").toString();
-        c->address = obj.value("address").toString();
-        c->author = obj.value("author").toString();
-        c->date = obj.value("date").toString();
-        c->title = obj.value("title").toString();
-        c->target_amount = obj.value("target_amount").toDouble();
-        c->raised_amount = obj.value("raised_amount").toDouble();
-        c->percentage_funded = obj.value("percentage_funded").toDouble();
-        c->contributions = obj.value("contributions").toInt();
-        c->organizer = obj.value("organizer").toString();
-        c->currency = obj.value("currency").toString();
-
-        QString urlpath = obj.value("urlpath").toString();
-        if (c->organizer == "CCS") {
-            c->url = QString("https://ccs.getmonero.org/%1").arg(urlpath);
-        }
-        else if (c->organizer == "MAGIC") {
-            c->url = QString("https://monerofund.org/%1").arg(urlpath);
-        }
-        else {
-            continue;
-        }
-
-        l.append(c);
-    }
-
-    emit CCSReceived(l);
-}
-
-void WebsocketNotifier::onWSRevuo(const QJsonArray &revuo_data) {
-    QList<QSharedPointer<RevuoItem>> l;
-
-    for (auto &&entry: revuo_data) {
-        auto obj = entry.toObject();
-
-        QStringList newsbytes;
-        for (const auto &n : obj.value("newsbytes").toArray()) {
-            newsbytes.append(n.toString());
-        }
-
-        auto revuoItem = new RevuoItem(
-                obj.value("title").toString(),
-                obj.value("url").toString(),
-                newsbytes);
-
-        QSharedPointer<RevuoItem> r = QSharedPointer<RevuoItem>(revuoItem);
-        l.append(r);
-    }
-
-    emit RevuoReceived(l);
-}
-
-void WebsocketNotifier::onWSBounties(const QJsonArray &bounties_data) {
-    QList<QSharedPointer<BountyEntry>> l;
-
-    for (const auto& entry : bounties_data) {
-        QJsonObject obj = entry.toObject();
-        auto bounty = new BountyEntry(obj.value("votes").toInt(),
-                                      obj.value("title").toString(),
-                                      obj.value("amount").toDouble(),
-                                      obj.value("link").toString(),
-                                      obj.value("address").toString(),
-                                      obj.value("status").toString());
-        QSharedPointer<BountyEntry> b = QSharedPointer<BountyEntry>(bounty);
-        l.append(b);
-    }
-
-    emit BountyReceived(l);
-}
-
 void WebsocketNotifier::onWSUpdates(const QJsonObject &updates) {
     emit UpdatesReceived(updates);
 }
index ce131ca38c980826fbe0bc8fa7233e9496053788..34c9fd90343822fbf5266e0519cd2f229949345e 100644 (file)
 #include "networktype.h"
 #include "nodes.h"
 #include "prices.h"
-#include "plugins/bounties/Bounty.h"
-#include "plugins/reddit/RedditPost.h"
-#include "plugins/ccs/CCSEntry.h"
-#include "plugins/revuo/RevuoItem.h"
 #include "TxFiatHistory.h"
 
 class WebsocketNotifier : public QObject {
@@ -36,31 +32,25 @@ signals:
     void NodesReceived(QList<FeatherNode> &L);
     void CryptoRatesReceived(const QJsonArray &data);
     void FiatRatesReceived(const QJsonObject &fiat_rates);
-    void RedditReceived(QList<QSharedPointer<RedditPost>> L);
-    void CCSReceived(QList<QSharedPointer<CCSEntry>> L);
-    void BountyReceived(QList<QSharedPointer<BountyEntry>> L);
-    void RevuoReceived(QList<QSharedPointer<RevuoItem>> L);
     void TxFiatHistoryReceived(const QJsonObject &data);
     void UpdatesReceived(const QJsonObject &updates);
     void XMRigDownloadsReceived(const QJsonObject &downloads);
     void LocalMoneroCountriesReceived(const QJsonArray &countries);
     void LocalMoneroCurrenciesReceived(const QJsonArray &currencies);
     void LocalMoneroPaymentMethodsReceived(const QJsonObject &payment_methods);
+    void dataReceived(const QString &type, const QJsonValue &json);
 
 private slots:
     void onWSMessage(const QJsonObject &msg);
 
     void onWSNodes(const QJsonArray &nodes);
-    void onWSReddit(const QJsonArray &reddit_data);
-    void onWSCCS(const QJsonArray &ccs_data);
-    void onWSBounties(const QJsonArray &bounties_data);
-    void onWSRevuo(const QJsonArray &revuo_data);
     void onWSUpdates(const QJsonObject &updates);
     void onWSXMRigDownloads(const QJsonObject &downloads);
 
 private:
     static QPointer<WebsocketNotifier> m_instance;
 
+    QStringList m_pluginSubscriptions;
     QHash<QString, QJsonObject> m_cache;
     QDateTime m_lastMessageReceived;
 };
index 3f463152c99dfb0cb867d90000432c1bc2402185..1ad5f387d7c92c6e3b7fe87c4bed4e18095cc0d8 100644 (file)
@@ -43,11 +43,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
         {Config::useOnionNodes,{QS("useOnionNodes"), false}},
 
         // Tabs
-        {Config::showTabHome,{QS("showTabHome"), true}},
-        {Config::showTabCoins,{QS("showTabCoins"), false}},
-        {Config::showTabExchange, {QS("showTabExchange"), false}},
-        {Config::showTabXMRig,{QS("showTabXMRig"), false}},
-        {Config::showTabCalc,{QS("showTabCalc"), true}},
+        {Config::enabledTabs, {QS("enabledTabs"), QStringList{"Home", "History", "Send", "Receive", "Calc"}}},
         {Config::showSearchbar,{QS("showSearchbar"), true}},
 
         // Receive
@@ -120,7 +116,13 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
         {Config::socks5Pass, {QS("socks5Pass"), ""}}, // Unused
         {Config::torManagedPort, {QS("torManagedPort"), "19450"}},
         {Config::useLocalTor, {QS("useLocalTor"), false}},
-        {Config::initSyncThreshold, {QS("initSyncThreshold"), 360}}
+        {Config::initSyncThreshold, {QS("initSyncThreshold"), 360}},
+
+        {Config::enabledPlugins, {QS("enabledPlugins"), QStringList{"tickers", "crowdfunding", "bounties", "reddit", "revuo", "localmonero", "calc", "xmrig"}}},
+        {Config::restartRequired, {QS("restartRequired"), false}},
+
+        {Config::tickers, {QS("tickers"), QStringList{"XMR", "BTC", "XMR/BTC"}}},
+        {Config::tickersShowFiatBalance, {QS("tickersShowFiatBalance"), true}},
 };
 
 
index 546a5d4044151366e8c5e70b05f5541b7751e250..224e3053a95c621e1221e8f4858a60edd48b19f5 100644 (file)
@@ -46,11 +46,7 @@ public:
         useOnionNodes,
 
         // Tabs
-        showTabHome,
-        showTabCoins,
-        showTabExchange,
-        showTabCalc,
-        showTabXMRig,
+        enabledTabs,
         showSearchbar,
         
         // Receive
@@ -141,6 +137,13 @@ public:
 
         fiatSymbols,
         cryptoSymbols,
+
+        enabledPlugins,
+        restartRequired,
+
+        // Tickers
+        tickers,
+        tickersShowFiatBalance,
     };
 
     enum PrivacyLevel {
diff --git a/src/widgets/PluginWidget.cpp b/src/widgets/PluginWidget.cpp
new file mode 100644 (file)
index 0000000..d40e10a
--- /dev/null
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "PluginWidget.h"
+#include "ui_PluginWidget.h"
+
+#include <QTreeWidget>
+#include "utils/config.h"
+#include "utils/Icons.h"
+#include "plugins/PluginRegistry.h"
+
+PluginWidget::PluginWidget(QWidget *parent)
+        : QWidget(parent)
+        , ui(new Ui::PluginWidget)
+{
+    ui->setupUi(this);
+
+    ui->frameRestart->setVisible(conf()->get(Config::restartRequired).toBool());
+    ui->frameRestart->setInfo(icons()->icon("settings_disabled_32px.png"), "Restart required to enable/disable plugins.");
+
+    const QStringList enabledPlugins = conf()->get(Config::enabledPlugins).toStringList();
+
+    for (const auto plugin : PluginRegistry::getPlugins()) {
+        if (!plugin->parent().isEmpty()) {
+            continue;
+        }
+
+        auto* item = new QTreeWidgetItem(ui->plugins);
+
+        this->setupItem(item, plugin);
+        if (enabledPlugins.contains(plugin->id()) && !plugin->implicitEnable()) {
+            item->setCheckState(0, Qt::Checked);
+        }
+
+        m_topLevelPlugins[plugin->id()] = item;
+    }
+
+    for (const auto plugin : PluginRegistry::getPlugins()) {
+        QTreeWidgetItem *item;
+
+        if (plugin->parent().isEmpty()) {
+            continue;
+        }
+
+        if (m_topLevelPlugins.contains(plugin->parent())) {
+            item = new QTreeWidgetItem(m_topLevelPlugins[plugin->parent()]);
+        } else {
+            qWarning() << "Top level plugin not found: " << plugin->id();
+            continue;
+        }
+
+        this->setupItem(item, plugin);
+        if (enabledPlugins.contains(plugin->id())) {
+            item->setCheckState(0, Qt::Checked);
+        }
+    }
+
+    ui->plugins->expandAll();
+    ui->plugins->setHeaderHidden(true);
+
+    connect(ui->plugins, &QTreeWidget::itemClicked, this, &PluginWidget::pluginSelected);
+    connect(ui->plugins, &QTreeWidget::itemClicked, this, &PluginWidget::pluginToggled);
+
+    connect(ui->btn_deselectAll, &QPushButton::clicked, this, &PluginWidget::deselectAll);
+    connect(ui->btn_selectAll, &QPushButton::clicked, this, &PluginWidget::selectAll);
+    connect(ui->btn_configure, &QPushButton::clicked, this, &PluginWidget::configurePlugin);
+}
+
+void PluginWidget::pluginSelected(QTreeWidgetItem* item, int column) {
+    QString pluginID = item->data(0, Qt::UserRole).toString();
+    Plugin* plugin = PluginRegistry::getPlugin(pluginID);
+
+    bool enable = false;
+    if (plugin && plugin->configurable()) {
+        enable = true;
+    }
+
+    ui->btn_configure->setEnabled(enable);
+    m_selectedPlugin = plugin;
+}
+
+void PluginWidget::pluginToggled(QTreeWidgetItem* item, int column) {
+    QStringList enabledPlugins = conf()->get(Config::enabledPlugins).toStringList();
+
+    QString pluginID = item->data(0, Qt::UserRole).toString();
+    bool checked = (item->checkState(0) == Qt::Checked);
+    bool toggled = checked ^ enabledPlugins.contains(pluginID);
+    if (!toggled) {
+        return;
+    }
+
+    if (checked) {
+        enabledPlugins.append(pluginID);
+    } else {
+        enabledPlugins.removeAll(pluginID);
+    }
+
+    conf()->set(Config::enabledPlugins, enabledPlugins);
+    qDebug() << "Enabled plugins: " << enabledPlugins;
+
+    if (toggled) {
+        conf()->set(Config::restartRequired, true);
+        ui->frameRestart->show();
+    }
+}
+
+void PluginWidget::setupItem(QTreeWidgetItem *item, Plugin *plugin) {
+    item->setIcon(0, icons()->icon(plugin->icon()));
+    item->setText(0, plugin->displayName());
+    item->setData(0, Qt::UserRole, plugin->id());
+
+    if (!plugin->implicitEnable()) {
+        m_checkable.append(plugin->id());
+        item->setCheckState(0, Qt::Unchecked);
+    }
+
+    // if (plugin->requiresWebsocket() && conf()->get(Config::disableWebsocket).toBool()) {
+    //     item->setDisabled(true);
+    //     item->setToolTip(0, "This plugin requires the websocket connection to be enabled. Go to Network -> Websocket.");
+    //
+    //     if (!plugin->implicitEnable()) {
+    //         item->setCheckState(0, Qt::Unchecked);
+    //     }
+    // }
+}
+
+void PluginWidget::deselectAll() {
+    this->selectTreeItems(ui->plugins->invisibleRootItem(), false);
+}
+
+void PluginWidget::selectAll() {
+    this->selectTreeItems(ui->plugins->invisibleRootItem(), true);
+}
+
+void PluginWidget::configurePlugin() {
+    if (!m_selectedPlugin) {
+        return;
+    }
+    m_selectedPlugin->configDialog(this)->exec();
+    emit pluginConfigured(m_selectedPlugin->id());
+}
+
+void PluginWidget::selectTreeItems(QTreeWidgetItem *item, bool select) {
+    if (!item) return;
+
+    // Truly demented Qt API
+    if (m_checkable.contains(item->data(0, Qt::UserRole).toString())) {
+        item->setCheckState(0, select ? Qt::Checked : Qt::Unchecked);
+        this->pluginToggled(item, 0);
+    }
+
+    for (int i = 0; i < item->childCount(); ++i)
+    {
+        selectTreeItems(item->child(i), select);
+    }
+}
+
+PluginWidget::~PluginWidget() = default;
\ No newline at end of file
diff --git a/src/widgets/PluginWidget.h b/src/widgets/PluginWidget.h
new file mode 100644 (file)
index 0000000..d481560
--- /dev/null
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef PLUGINWIDGET_H
+#define PLUGINWIDGET_H
+
+#include <QWidget>
+#include <QTreeWidgetItem>
+
+#include "plugins/Plugin.h"
+
+namespace Ui {
+    class PluginWidget;
+}
+
+class PluginWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit PluginWidget(QWidget *parent = nullptr);
+    ~PluginWidget();
+
+private slots:
+    void pluginSelected(QTreeWidgetItem* item, int column);
+    void pluginToggled(QTreeWidgetItem* item, int column);
+    void deselectAll();
+    void selectAll();
+    void selectTreeItems(QTreeWidgetItem *item, bool select);
+    void configurePlugin();
+
+signals:
+    void pluginConfigured(const QString &id);
+
+private:
+    void setupItem(QTreeWidgetItem *item, Plugin *plugin);
+
+    QScopedPointer<Ui::PluginWidget> ui;
+    QMap<QString, QTreeWidgetItem*> m_topLevelPlugins;
+    QList<QString> m_checkable;
+    Plugin* m_selectedPlugin = nullptr;
+};
+
+
+#endif //PLUGINWIDGET_H
diff --git a/src/widgets/PluginWidget.ui b/src/widgets/PluginWidget.ui
new file mode 100644 (file)
index 0000000..aafd1e5
--- /dev/null
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PluginWidget</class>
+ <widget class="QWidget" name="PluginWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>463</width>
+    <height>390</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <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="QTreeWidget" name="plugins">
+     <property name="selectionMode">
+      <enum>QAbstractItemView::SingleSelection</enum>
+     </property>
+     <property name="selectionBehavior">
+      <enum>QAbstractItemView::SelectRows</enum>
+     </property>
+     <column>
+      <property name="text">
+       <string>Plugin</string>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QPushButton" name="btn_deselectAll">
+       <property name="text">
+        <string>Deselect all</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="btn_selectAll">
+       <property name="text">
+        <string>Select all</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>0</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="btn_configure">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="text">
+        <string>Configure</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="InfoFrame" name="frameRestart">
+     <property name="frameShape">
+      <enum>QFrame::StyledPanel</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>InfoFrame</class>
+   <extends>QFrame</extends>
+   <header>components.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/wizard/PagePlugins.cpp b/src/wizard/PagePlugins.cpp
new file mode 100644 (file)
index 0000000..6689767
--- /dev/null
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "PagePlugins.h"
+#include "ui_PagePlugins.h"
+
+#include "WalletWizard.h"
+
+PagePlugins::PagePlugins(QWidget *parent)
+    : QWizardPage(parent)
+    , ui(new Ui::PagePlugins)
+{
+    ui->setupUi(this);
+}
+
+int PagePlugins::nextId() const {
+    return WalletWizard::Page_Menu;
+}
+
+bool PagePlugins::validatePage() {
+    return true;
+}
+
+bool PagePlugins::isComplete() const {
+    return true;
+}
\ No newline at end of file
diff --git a/src/wizard/PagePlugins.h b/src/wizard/PagePlugins.h
new file mode 100644 (file)
index 0000000..4fde0ba
--- /dev/null
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef PAGEPLUGINS_H
+#define PAGEPLUGINS_H
+
+#include <QWizardPage>
+
+namespace Ui {
+    class PagePlugins;
+}
+
+class PagePlugins : public QWizardPage
+{
+    Q_OBJECT
+
+public:
+    explicit PagePlugins(QWidget *parent = nullptr);
+    bool validatePage() override;
+    int nextId() const override;
+    bool isComplete() const override;
+
+private:
+    Ui::PagePlugins *ui;
+};
+
+#endif //PAGEPLUGINS_H
diff --git a/src/wizard/PagePlugins.ui b/src/wizard/PagePlugins.ui
new file mode 100644 (file)
index 0000000..942bbd8
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PagePlugins</class>
+ <widget class="QWizardPage" name="PagePlugins">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>505</width>
+    <height>431</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>WizardPage</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;Do you want to enable any plugins?&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="PluginWidget" name="pluginsWidget" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>PluginWidget</class>
+   <extends>QWidget</extends>
+   <header>widgets/PluginWidget.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
index 6aae57ed2950aa43cba5efbe9dd7019a70256ec7..1acfbb9e37a96dca6867b096057a22492dece9ef 100644 (file)
@@ -6,6 +6,7 @@
 #include "WalletWizard.h"
 #include "PageMenu.h"
 #include "PageOpenWallet.h"
+#include "PagePlugins.h"
 #include "PageWalletFile.h"
 #include "PageNetwork.h"
 #include "PageWalletSeed.h"
@@ -58,6 +59,7 @@ WalletWizard::WalletWizard(QWidget *parent)
     setPage(Page_HardwareDevice, new PageHardwareDevice(&m_wizardFields, this));
     setPage(Page_SetSeedPassphrase, walletSetSeedPassphrasePage);
     setPage(Page_SetSubaddressLookahead, walletSetSubaddressLookaheadPage);
+    setPage(Page_Plugins, new PagePlugins(this));
 
     setStartId(Page_Menu);
 
index b9cbe28b99897f8b77dd81f8263d156858741581..d9d72f4b3f59fc29cc24a92bd65dc3d079b4b0c9 100644 (file)
@@ -83,7 +83,8 @@ public:
         Page_SetRestoreHeight,
         Page_HardwareDevice,
         Page_NetworkProxy,
-        Page_NetworkWebsocket
+        Page_NetworkWebsocket,
+        Page_Plugins
     };
 
     explicit WalletWizard(QWidget *parent = nullptr);