diff --git a/CMakeLists.txt b/CMakeLists.txt index 02b2c89..596ce24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/HACKING.md b/HACKING.md index 6e8b51b..79bd9ab 100644 --- a/HACKING.md +++ b/HACKING.md @@ -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_=OFF` - disable a plugin diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a3b7c77..ca16460 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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() diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index ef2ce74..252b639 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -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 + // [Plugins..] + for (auto* plugin : m_plugins) { + if (!plugin->hasParent()) { + qDebug() << "Adding tab: " << plugin->displayName(); -#ifdef HAS_XMRIG - m_xmrig = new XMRigWidget(m_wallet, this); - ui->xmrRigLayout->addWidget(m_xmrig); + 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()); + } - 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 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 Plugins.. + for (const auto &plugin : m_plugins) { + if (plugin->parent() != "") { + continue; + } - // 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 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 diff --git a/src/MainWindow.h b/src/MainWindow.h index b7ba34a..9266455 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -6,10 +6,8 @@ #include #include -#include #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" @@ -44,31 +40,23 @@ #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 m_tickerWidgets; - BalanceTickerWidget *m_balanceTickerWidget; QPointer m_clearRecentlyOpenAction; @@ -282,6 +244,8 @@ private: QTimer m_updateBytes; QTimer m_checkUserActivity; + QList m_plugins; + QString m_statusText; int m_statusDots; bool m_constructingTransaction = false; diff --git a/src/MainWindow.ui b/src/MainWindow.ui index c1f355c..477e9f2 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -40,7 +40,7 @@ - 2 + 0 @@ -67,148 +67,6 @@ 16 - - - - :/assets/images/tab_home.png:/assets/images/tab_home.png - - - Home - - - - 5 - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - Qt::Horizontal - - - - - - - 0 - - - true - - - - Crowdfunding - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - Bounties - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - /r/Monero - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - Revuo - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - @@ -300,107 +158,6 @@ - - - - :/assets/images/gnome-calc.png:/assets/images/gnome-calc.png - - - Calc - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - :/assets/images/update.png:/assets/images/update.png - - - Exchange - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - :/assets/images/localMonero_logo.png:/assets/images/localMonero_logo.png - - - LocalMonero - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - :/assets/images/mining.png:/assets/images/mining.png - - - Mining - - - - - - - @@ -787,7 +544,6 @@ - @@ -808,11 +564,9 @@ View - + - - - + @@ -1162,39 +916,19 @@ Transmit over UR + + + Placeholder + + + + + PlaceholderBegin + + - - CalcWidget - QWidget -
CalcWidget.h
- 1 -
- - CCSWidget - QWidget -
plugins/ccs/CCSWidget.h
- 1 -
- - RedditWidget - QWidget -
plugins/reddit/RedditWidget.h
- 1 -
- - RevuoWidget - QWidget -
plugins/revuo/RevuoWidget.h
- 1 -
- - BountiesWidget - QWidget -
plugins/bounties/BountiesWidget.h
- 1 -
ClickableLabel QLabel diff --git a/src/SettingsDialog.cpp b/src/SettingsDialog.cpp index 22a1e5b..5153291 100644 --- a/src/SettingsDialog.cpp +++ b/src/SettingsDialog.cpp @@ -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())); diff --git a/src/SettingsDialog.h b/src/SettingsDialog.h index 77809fb..73c089b 100644 --- a/src/SettingsDialog.h +++ b/src/SettingsDialog.h @@ -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(); diff --git a/src/SettingsDialog.ui b/src/SettingsDialog.ui index a625980..882701c 100644 --- a/src/SettingsDialog.ui +++ b/src/SettingsDialog.ui @@ -973,6 +973,36 @@
+ + + + 0 + + + 0 + + + 0 + + + + + <html><head/><body><p><span style=" font-weight:700;">Plugins</span></p></body></html> + + + + + + + + 0 + 0 + + + + + + @@ -1165,6 +1195,12 @@
widgets/NetworkProxyWidget.h
1 + + PluginWidget + QWidget +
widgets/PluginWidget.h
+ 1 +
diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp index 06a5c5e..a68b880 100644 --- a/src/WindowManager.cpp +++ b/src/WindowManager.cpp @@ -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(); diff --git a/src/WindowManager.h b/src/WindowManager.h index 1ac2127..3008636 100644 --- a/src/WindowManager.h +++ b/src/WindowManager.h @@ -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(); diff --git a/src/assets.qrc b/src/assets.qrc index f6a64da..e84bf88 100644 --- a/src/assets.qrc +++ b/src/assets.qrc @@ -28,6 +28,7 @@ assets/images/coldcard_unpaired.png assets/images/confirmed.svg assets/images/connect.svg + assets/images/connect_white.svg assets/images/copy.png assets/images/edit.png assets/images/external-link.svg diff --git a/src/assets/images/connect_white.svg b/src/assets/images/connect_white.svg new file mode 100644 index 0000000..c36baef --- /dev/null +++ b/src/assets/images/connect_white.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main.cpp b/src/main.cpp index da90b57..7cc85f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 index 0000000..90b43cd --- /dev/null +++ b/src/plugins/Plugin.h @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2023 The Monero Project + +#ifndef PLUGIN_H +#define PLUGIN_H + +#include +#include +#include + +#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 index 0000000..e505c7a --- /dev/null +++ b/src/plugins/PluginRegistry.h @@ -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& creator) { + plugin_creators.append(creator); + } + + static const QList& getPlugins() { + return getInstance().plugins; + } + + static Plugin* getPlugin(const QString& id) { + return getInstance().pluginMap.value(id, nullptr); + } + + static const QList>& 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 pluginMap; + QList plugins; + QList> plugin_creators; + +public: + static PluginRegistry& getInstance() { + static PluginRegistry instance; + return instance; + } +}; + +#endif //PLUGINREGISTRY_H diff --git a/src/plugins/bounties/BountiesModel.cpp b/src/plugins/bounties/BountiesModel.cpp index 7d084a6..12b8b1d 100644 --- a/src/plugins/bounties/BountiesModel.cpp +++ b/src/plugins/bounties/BountiesModel.cpp @@ -50,15 +50,23 @@ QVariant BountiesModel::data(const QModelIndex &index, int role) const QSharedPointer 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 index 0000000..9362cd7 --- /dev/null +++ b/src/plugins/bounties/BountiesPlugin.cpp @@ -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 index 0000000..39fee4f --- /dev/null +++ b/src/plugins/bounties/BountiesPlugin.h @@ -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 index 0000000..513ba6b --- /dev/null +++ b/src/plugins/bounties/BountiesProxyModel.cpp @@ -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 index 0000000..b3a2e15 --- /dev/null +++ b/src/plugins/bounties/BountiesProxyModel.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2023 The Monero Project + +#ifndef BOUNTIESPROXYMODEL_H +#define BOUNTIESPROXYMODEL_H + +#include + +class BountiesProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + explicit BountiesProxyModel(QObject* parent = nullptr); +}; + +#endif //BOUNTIESPROXYMODEL_H diff --git a/src/plugins/bounties/BountiesWidget.cpp b/src/plugins/bounties/BountiesWidget.cpp index 1ae6e51..c0218fb 100644 --- a/src/plugins/bounties/BountiesWidget.cpp +++ b/src/plugins/bounties/BountiesWidget.cpp @@ -4,13 +4,12 @@ #include "BountiesWidget.h" #include "ui_BountiesWidget.h" -#include -#include #include #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); + connect(websocketNotifier(), &WebsocketNotifier::dataReceived, this, [this](const QString &type, const QJsonValue &json) { + if (type == "bounties") { + QJsonArray bounties_data = json.toArray(); + QList> 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 b = QSharedPointer(bounty); + l.append(b); + } + + m_model->updateBounties(l); + } + }); + ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); } -BountiesModel * BountiesWidget::model() { - return m_model; -} - 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) { diff --git a/src/plugins/bounties/BountiesWidget.h b/src/plugins/bounties/BountiesWidget.h index 02e51a4..db526fa 100644 --- a/src/plugins/bounties/BountiesWidget.h +++ b/src/plugins/bounties/BountiesWidget.h @@ -9,6 +9,7 @@ #include #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; BountiesModel *m_model; + BountiesProxyModel *m_proxyModel; QMenu *m_contextMenu; }; diff --git a/src/plugins/bounties/BountiesWidget.ui b/src/plugins/bounties/BountiesWidget.ui index d4e985a..c57cafd 100644 --- a/src/plugins/bounties/BountiesWidget.ui +++ b/src/plugins/bounties/BountiesWidget.ui @@ -31,6 +31,12 @@ Qt::CustomContextMenu + + QAbstractItemView::SingleSelection + + + false +
diff --git a/src/dialog/CalcConfigDialog.cpp b/src/plugins/calc/CalcConfigDialog.cpp similarity index 99% rename from src/dialog/CalcConfigDialog.cpp rename to src/plugins/calc/CalcConfigDialog.cpp index e592bba..c85a240 100644 --- a/src/dialog/CalcConfigDialog.cpp +++ b/src/plugins/calc/CalcConfigDialog.cpp @@ -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/dialog/CalcConfigDialog.h b/src/plugins/calc/CalcConfigDialog.h similarity index 100% rename from src/dialog/CalcConfigDialog.h rename to src/plugins/calc/CalcConfigDialog.h diff --git a/src/dialog/CalcConfigDialog.ui b/src/plugins/calc/CalcConfigDialog.ui similarity index 100% rename from src/dialog/CalcConfigDialog.ui rename to src/plugins/calc/CalcConfigDialog.ui diff --git a/src/plugins/calc/CalcPlugin.cpp b/src/plugins/calc/CalcPlugin.cpp new file mode 100644 index 0000000..092b11e --- /dev/null +++ b/src/plugins/calc/CalcPlugin.cpp @@ -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 index 0000000..5b051ad --- /dev/null +++ b/src/plugins/calc/CalcPlugin.h @@ -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 diff --git a/src/CalcWidget.cpp b/src/plugins/calc/CalcWidget.cpp similarity index 99% rename from src/CalcWidget.cpp rename to src/plugins/calc/CalcWidget.cpp index 65e35eb..9ccc1e0 100644 --- a/src/CalcWidget.cpp +++ b/src/plugins/calc/CalcWidget.cpp @@ -6,7 +6,7 @@ #include -#include "dialog/CalcConfigDialog.h" +#include "CalcConfigDialog.h" #include "utils/AppData.h" #include "utils/ColorScheme.h" #include "utils/config.h" diff --git a/src/CalcWidget.h b/src/plugins/calc/CalcWidget.h similarity index 100% rename from src/CalcWidget.h rename to src/plugins/calc/CalcWidget.h diff --git a/src/CalcWidget.ui b/src/plugins/calc/CalcWidget.ui similarity index 93% rename from src/CalcWidget.ui rename to src/plugins/calc/CalcWidget.ui index ea3c22c..d2e87b3 100644 --- a/src/CalcWidget.ui +++ b/src/plugins/calc/CalcWidget.ui @@ -7,7 +7,7 @@ 0 0 800 - 242 + 366 @@ -155,6 +155,19 @@ + + + + Qt::Vertical + + + + 20 + 0 + + + + diff --git a/src/CalcWindow.cpp b/src/plugins/calc/CalcWindow.cpp similarity index 100% rename from src/CalcWindow.cpp rename to src/plugins/calc/CalcWindow.cpp diff --git a/src/CalcWindow.h b/src/plugins/calc/CalcWindow.h similarity index 100% rename from src/CalcWindow.h rename to src/plugins/calc/CalcWindow.h diff --git a/src/CalcWindow.ui b/src/plugins/calc/CalcWindow.ui similarity index 96% rename from src/CalcWindow.ui rename to src/plugins/calc/CalcWindow.ui index 2447bba..79bed2d 100644 --- a/src/CalcWindow.ui +++ b/src/plugins/calc/CalcWindow.ui @@ -37,7 +37,7 @@ CalcWidget QWidget -
CalcWidget.h
+
plugins/calc/CalcWidget.h
1
diff --git a/src/plugins/ccs/CCSWidget.cpp b/src/plugins/ccs/CCSWidget.cpp deleted file mode 100644 index d2ace60..0000000 --- a/src/plugins/ccs/CCSWidget.cpp +++ /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 -#include -#include - -#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 diff --git a/src/plugins/ccs/CCSEntry.h b/src/plugins/crowdfunding/CCSEntry.h similarity index 100% rename from src/plugins/ccs/CCSEntry.h rename to src/plugins/crowdfunding/CCSEntry.h diff --git a/src/plugins/ccs/CCSModel.cpp b/src/plugins/crowdfunding/CCSModel.cpp similarity index 98% rename from src/plugins/ccs/CCSModel.cpp rename to src/plugins/crowdfunding/CCSModel.cpp index a8e9d1c..101ef7c 100644 --- a/src/plugins/ccs/CCSModel.cpp +++ b/src/plugins/crowdfunding/CCSModel.cpp @@ -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/ccs/CCSModel.h b/src/plugins/crowdfunding/CCSModel.h similarity index 100% rename from src/plugins/ccs/CCSModel.h rename to src/plugins/crowdfunding/CCSModel.h diff --git a/src/plugins/ccs/CCSProgressDelegate.cpp b/src/plugins/crowdfunding/CCSProgressDelegate.cpp similarity index 100% rename from src/plugins/ccs/CCSProgressDelegate.cpp rename to src/plugins/crowdfunding/CCSProgressDelegate.cpp diff --git a/src/plugins/ccs/CCSProgressDelegate.h b/src/plugins/crowdfunding/CCSProgressDelegate.h similarity index 100% rename from src/plugins/ccs/CCSProgressDelegate.h rename to src/plugins/crowdfunding/CCSProgressDelegate.h diff --git a/src/plugins/crowdfunding/CCSWidget.cpp b/src/plugins/crowdfunding/CCSWidget.cpp new file mode 100644 index 0000000..f1fbe5d --- /dev/null +++ b/src/plugins/crowdfunding/CCSWidget.cpp @@ -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 + +#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> l; + + for (const auto& entry: ccs_data) { + auto obj = entry.toObject(); + auto c = QSharedPointer(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 diff --git a/src/plugins/ccs/CCSWidget.h b/src/plugins/crowdfunding/CCSWidget.h similarity index 90% rename from src/plugins/ccs/CCSWidget.h rename to src/plugins/crowdfunding/CCSWidget.h index cd344cf..d5ac1d7 100644 --- a/src/plugins/ccs/CCSWidget.h +++ b/src/plugins/crowdfunding/CCSWidget.h @@ -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/ccs/CCSWidget.ui b/src/plugins/crowdfunding/CCSWidget.ui similarity index 100% rename from src/plugins/ccs/CCSWidget.ui rename to src/plugins/crowdfunding/CCSWidget.ui diff --git a/src/plugins/crowdfunding/CrowdfundingPlugin.cpp b/src/plugins/crowdfunding/CrowdfundingPlugin.cpp new file mode 100644 index 0000000..834d432 --- /dev/null +++ b/src/plugins/crowdfunding/CrowdfundingPlugin.cpp @@ -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 index 0000000..90c8a3f --- /dev/null +++ b/src/plugins/crowdfunding/CrowdfundingPlugin.h @@ -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 index 0000000..d0231ad --- /dev/null +++ b/src/plugins/exchange/ExchangePlugin.cpp @@ -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 index 0000000..d9a1e42 --- /dev/null +++ b/src/plugins/exchange/ExchangePlugin.h @@ -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 index 0000000..d112efb --- /dev/null +++ b/src/plugins/exchange/ExchangeWidget.cpp @@ -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 index 0000000..1c2f8b9 --- /dev/null +++ b/src/plugins/exchange/ExchangeWidget.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2023 The Monero Project + +#ifndef EXCHANGEWIDGET_H +#define EXCHANGEWIDGET_H + +#include +#include + +#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; +}; + + +#endif //EXCHANGEWIDGET_H diff --git a/src/plugins/exchange/ExchangeWidget.ui b/src/plugins/exchange/ExchangeWidget.ui new file mode 100644 index 0000000..19d75ac --- /dev/null +++ b/src/plugins/exchange/ExchangeWidget.ui @@ -0,0 +1,36 @@ + + + ExchangeWidget + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + diff --git a/src/plugins/home/HomePlugin.cpp b/src/plugins/home/HomePlugin.cpp new file mode 100644 index 0000000..c4f1f66 --- /dev/null +++ b/src/plugins/home/HomePlugin.cpp @@ -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 index 0000000..a2d8477 --- /dev/null +++ b/src/plugins/home/HomePlugin.h @@ -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 index 0000000..03b14cf --- /dev/null +++ b/src/plugins/home/HomeWidget.cpp @@ -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 index 0000000..bef6457 --- /dev/null +++ b/src/plugins/home/HomeWidget.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2023 The Monero Project + +#ifndef HOMEWIDGET_H +#define HOMEWIDGET_H + +#include + +#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; +}; + +#endif //HOMEWIDGET_H diff --git a/src/plugins/home/HomeWidget.ui b/src/plugins/home/HomeWidget.ui new file mode 100644 index 0000000..ad44b99 --- /dev/null +++ b/src/plugins/home/HomeWidget.ui @@ -0,0 +1,40 @@ + + + HomeWidget + + + + 0 + 0 + 743 + 460 + + + + Form + + + + 5 + + + 5 + + + + + + + + -1 + + + true + + + + + + + + diff --git a/src/plugins/localmonero/LocalMoneroPlugin.cpp b/src/plugins/localmonero/LocalMoneroPlugin.cpp new file mode 100644 index 0000000..73ad519 --- /dev/null +++ b/src/plugins/localmonero/LocalMoneroPlugin.cpp @@ -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 index 0000000..10db13f --- /dev/null +++ b/src/plugins/localmonero/LocalMoneroPlugin.h @@ -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 diff --git a/src/plugins/reddit/RedditModel.cpp b/src/plugins/reddit/RedditModel.cpp index 0365477..b54b182 100644 --- a/src/plugins/reddit/RedditModel.cpp +++ b/src/plugins/reddit/RedditModel.cpp @@ -50,14 +50,18 @@ QVariant RedditModel::data(const QModelIndex &index, int role) const QSharedPointer 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 index 0000000..63e1bcf --- /dev/null +++ b/src/plugins/reddit/RedditPlugin.cpp @@ -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 index 0000000..83e6409 --- /dev/null +++ b/src/plugins/reddit/RedditPlugin.h @@ -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 index 0000000..880b51b --- /dev/null +++ b/src/plugins/reddit/RedditProxyModel.cpp @@ -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 index 0000000..3a0528f --- /dev/null +++ b/src/plugins/reddit/RedditProxyModel.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2023 The Monero Project + +#ifndef REDDITPROXYMODEL_H +#define REDDITPROXYMODEL_H + +#include + +class RedditProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + explicit RedditProxyModel(QObject* parent = nullptr); +}; + +#endif //REDDITPROXYMODEL_H diff --git a/src/plugins/reddit/RedditWidget.cpp b/src/plugins/reddit/RedditWidget.cpp index e2cf12b..3660019 100644 --- a/src/plugins/reddit/RedditWidget.cpp +++ b/src/plugins/reddit/RedditWidget.cpp @@ -4,13 +4,12 @@ #include "RedditWidget.h" #include "ui_RedditWidget.h" -#include -#include #include #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> 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 r = QSharedPointer(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) { diff --git a/src/plugins/reddit/RedditWidget.h b/src/plugins/reddit/RedditWidget.h index 950700e..09e2779 100644 --- a/src/plugins/reddit/RedditWidget.h +++ b/src/plugins/reddit/RedditWidget.h @@ -9,6 +9,7 @@ #include #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; 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 index 0000000..26ee507 --- /dev/null +++ b/src/plugins/revuo/RevuoPlugin.cpp @@ -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 index 0000000..00d829d --- /dev/null +++ b/src/plugins/revuo/RevuoPlugin.h @@ -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 diff --git a/src/plugins/revuo/RevuoWidget.cpp b/src/plugins/revuo/RevuoWidget.cpp index 3fba9d9..62f40d8 100644 --- a/src/plugins/revuo/RevuoWidget.cpp +++ b/src/plugins/revuo/RevuoWidget.cpp @@ -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> 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 r = QSharedPointer(revuoItem); + l.append(r); + } + + this->updateItems(l); + } + }); } void RevuoWidget::updateItems(const QList> &items) { @@ -48,6 +75,7 @@ void RevuoWidget::updateItems(const QList> &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 index 0000000..d5dae04 --- /dev/null +++ b/src/plugins/tickers/TickersConfigAddDialog.cpp @@ -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 index 0000000..943a282 --- /dev/null +++ b/src/plugins/tickers/TickersConfigAddDialog.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2023 The Monero Project + +#ifndef TICKERSCONFIGADDDIALOG_H +#define TICKERSCONFIGADDDIALOG_H + +#include +#include + +#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; + TickersType m_type; +}; + +#endif //TICKERSCONFIGADDDIALOG_H diff --git a/src/plugins/tickers/TickersConfigAddDialog.ui b/src/plugins/tickers/TickersConfigAddDialog.ui new file mode 100644 index 0000000..4fadfd4 --- /dev/null +++ b/src/plugins/tickers/TickersConfigAddDialog.ui @@ -0,0 +1,165 @@ + + + TickersConfigAddDialog + + + + 0 + 0 + 431 + 122 + + + + Add ticker + + + + + + + + + 0 + 0 + + + + Type: + + + + + + + + Ticker + + + + + Ratio + + + + + + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + 0 + 0 + + + + / + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + TickersConfigAddDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + TickersConfigAddDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/plugins/tickers/TickersConfigDialog.cpp b/src/plugins/tickers/TickersConfigDialog.cpp new file mode 100644 index 0000000..fb862ee --- /dev/null +++ b/src/plugins/tickers/TickersConfigDialog.cpp @@ -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 index 0000000..6fe5388 --- /dev/null +++ b/src/plugins/tickers/TickersConfigDialog.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2023 The Monero Project + +#ifndef TICKERCONFIGDIALOG_H +#define TICKERCONFIGDIALOG_H + +#include +#include + +#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; +}; + +#endif //TICKERCONFIGDIALOG_H diff --git a/src/plugins/tickers/TickersConfigDialog.ui b/src/plugins/tickers/TickersConfigDialog.ui new file mode 100644 index 0000000..03b97ac --- /dev/null +++ b/src/plugins/tickers/TickersConfigDialog.ui @@ -0,0 +1,99 @@ + + + TickersConfigDialog + + + + 0 + 0 + 426 + 392 + + + + Tickers config + + + + + + true + + + QAbstractItemView::InternalMove + + + + + + + Show fiat balance + + + + + + + + + Remove Ticker + + + + + + + Add Ticker + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + TickersConfigDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + TickersConfigDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/plugins/tickers/TickersPlugin.cpp b/src/plugins/tickers/TickersPlugin.cpp new file mode 100644 index 0000000..334b391 --- /dev/null +++ b/src/plugins/tickers/TickersPlugin.cpp @@ -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 index 0000000..c73015d --- /dev/null +++ b/src/plugins/tickers/TickersPlugin.h @@ -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 index 0000000..62f3f37 --- /dev/null +++ b/src/plugins/tickers/TickersWidget.cpp @@ -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 index 0000000..de36088 --- /dev/null +++ b/src/plugins/tickers/TickersWidget.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2023 The Monero Project + +#ifndef TICKERSWIDGET_H +#define TICKERSWIDGET_H + +#include + +#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; + Wallet *m_wallet; + + QList m_tickerWidgets; + QScopedPointer m_balanceTickerWidget; +}; + +#endif //TICKERSWIDGET_H diff --git a/src/plugins/tickers/TickersWidget.ui b/src/plugins/tickers/TickersWidget.ui new file mode 100644 index 0000000..17d91bc --- /dev/null +++ b/src/plugins/tickers/TickersWidget.ui @@ -0,0 +1,87 @@ + + + TickersWidget + + + + 0 + 0 + 618 + 161 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + Qt::Horizontal + + + + + + + + diff --git a/src/plugins/xmrig/XMRigPlugin.cpp b/src/plugins/xmrig/XMRigPlugin.cpp new file mode 100644 index 0000000..9723964 --- /dev/null +++ b/src/plugins/xmrig/XMRigPlugin.cpp @@ -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 index 0000000..f6a5573 --- /dev/null +++ b/src/plugins/xmrig/XMRigPlugin.h @@ -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 diff --git a/src/plugins/xmrig/XMRigWidget.cpp b/src/plugins/xmrig/XMRigWidget.cpp index 618b772..969ff00 100644 --- a/src/plugins/xmrig/XMRigWidget.cpp +++ b/src/plugins/xmrig/XMRigWidget.cpp @@ -11,8 +11,10 @@ #include #include +#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() { diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp index 86cfe8c..b022eb2 100644 --- a/src/utils/Utils.cpp +++ b/src/utils/Utils.cpp @@ -445,7 +445,7 @@ QString amountToCurrencyString(double amount, const QString ¤cyCode) { 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; + } +} } diff --git a/src/utils/Utils.h b/src/utils/Utils.h index 2367bd4..ba530e9 100644 --- a/src/utils/Utils.h +++ b/src/utils/Utils.h @@ -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 diff --git a/src/utils/WebsocketNotifier.cpp b/src/utils/WebsocketNotifier.cpp index 346a8b6..8f18d70 100644 --- a/src/utils/WebsocketNotifier.cpp +++ b/src/utils/WebsocketNotifier.cpp @@ -5,6 +5,7 @@ #include "Utils.h" #include "utils/os/tails.h" #include "utils/os/whonix.h" +#include "plugins/PluginRegistry.h" #include @@ -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::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()); + else if (m_pluginSubscriptions.contains(cmd)) { + emit dataReceived(cmd, msg.value("data")); } -#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 (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> 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 r = QSharedPointer(redditPost); - l.append(r); - } - - emit RedditReceived(l); -} - -void WebsocketNotifier::onWSCCS(const QJsonArray &ccs_data) { - QList> l; - - for (const auto& entry: ccs_data) { - auto obj = entry.toObject(); - auto c = QSharedPointer(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> 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 r = QSharedPointer(revuoItem); - l.append(r); - } - - emit RevuoReceived(l); -} - -void WebsocketNotifier::onWSBounties(const QJsonArray &bounties_data) { - QList> 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 b = QSharedPointer(bounty); - l.append(b); - } - - emit BountyReceived(l); -} - void WebsocketNotifier::onWSUpdates(const QJsonObject &updates) { emit UpdatesReceived(updates); } diff --git a/src/utils/WebsocketNotifier.h b/src/utils/WebsocketNotifier.h index ce131ca..34c9fd9 100644 --- a/src/utils/WebsocketNotifier.h +++ b/src/utils/WebsocketNotifier.h @@ -11,10 +11,6 @@ #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 &L); void CryptoRatesReceived(const QJsonArray &data); void FiatRatesReceived(const QJsonObject &fiat_rates); - void RedditReceived(QList> L); - void CCSReceived(QList> L); - void BountyReceived(QList> L); - void RevuoReceived(QList> 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 ¤cies); 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 m_instance; + QStringList m_pluginSubscriptions; QHash m_cache; QDateTime m_lastMessageReceived; }; diff --git a/src/utils/config.cpp b/src/utils/config.cpp index 3f46315..1ad5f38 100644 --- a/src/utils/config.cpp +++ b/src/utils/config.cpp @@ -43,11 +43,7 @@ static const QHash 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 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}}, }; diff --git a/src/utils/config.h b/src/utils/config.h index 546a5d4..224e305 100644 --- a/src/utils/config.h +++ b/src/utils/config.h @@ -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 index 0000000..d40e10a --- /dev/null +++ b/src/widgets/PluginWidget.cpp @@ -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 +#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 index 0000000..d481560 --- /dev/null +++ b/src/widgets/PluginWidget.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2023 The Monero Project + +#ifndef PLUGINWIDGET_H +#define PLUGINWIDGET_H + +#include +#include + +#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; + QMap m_topLevelPlugins; + QList 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 index 0000000..aafd1e5 --- /dev/null +++ b/src/widgets/PluginWidget.ui @@ -0,0 +1,107 @@ + + + PluginWidget + + + + 0 + 0 + 463 + 390 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + Plugin + + + + + + + + + + Deselect all + + + + + + + Select all + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + false + + + Configure + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + InfoFrame + QFrame +
components.h
+ 1 +
+
+ + +
diff --git a/src/wizard/PagePlugins.cpp b/src/wizard/PagePlugins.cpp new file mode 100644 index 0000000..6689767 --- /dev/null +++ b/src/wizard/PagePlugins.cpp @@ -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 index 0000000..4fde0ba --- /dev/null +++ b/src/wizard/PagePlugins.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2023 The Monero Project + +#ifndef PAGEPLUGINS_H +#define PAGEPLUGINS_H + +#include + +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 index 0000000..942bbd8 --- /dev/null +++ b/src/wizard/PagePlugins.ui @@ -0,0 +1,46 @@ + + + PagePlugins + + + + 0 + 0 + 505 + 431 + + + + WizardPage + + + + + + <html><head/><body><p><span style=" font-weight:700;">Do you want to enable any plugins?</span></p></body></html> + + + + + + + + 0 + 0 + + + + + + + + + PluginWidget + QWidget +
widgets/PluginWidget.h
+ 1 +
+
+ + +
diff --git a/src/wizard/WalletWizard.cpp b/src/wizard/WalletWizard.cpp index 6aae57e..1acfbb9 100644 --- a/src/wizard/WalletWizard.cpp +++ b/src/wizard/WalletWizard.cpp @@ -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); diff --git a/src/wizard/WalletWizard.h b/src/wizard/WalletWizard.h index b9cbe28..d9d72f4 100644 --- a/src/wizard/WalletWizard.h +++ b/src/wizard/WalletWizard.h @@ -83,7 +83,8 @@ public: Page_SetRestoreHeight, Page_HardwareDevice, Page_NetworkProxy, - Page_NetworkWebsocket + Page_NetworkWebsocket, + Page_Plugins }; explicit WalletWizard(QWidget *parent = nullptr);