# 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)
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)
There are some CMake options that you may pass to control how Feather is built:
-- `-DLOCALMONERO=OFF` - disable LocalMonero feature
-- `-DXMRIG=OFF` - disable XMRig feature
- `-DCHECK_UPDATES=ON` - enable checking for updates, only for standalone binaries
- `-DDONATE_BEG=OFF` - disable the dreaded donate requests
- `-DUSE_DEVICE_TREZOR=OFF` - disable Trezor hardware wallet support
- `-DWITH_SCANNER=ON` - enable the webcam QR code scanner
- `-DTOR_DIR=/path/to/tor/` - embed a Tor binary in Feather, argument should be a directory containing the binary
+- `-DWITH_PLUGIN_<NAME>=OFF` - disable a plugin
"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"
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()
#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"
#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"
#ifdef CHECK_UPDATES
#include "utils/updater/UpdateDialog.h"
#endif
-//#include "misc_log_ex.h"
MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *parent)
: QMainWindow(parent)
{
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);
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);
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);
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);
m_coinsWidget = new CoinsWidget(m_wallet, this);
ui->coinsWidgetLayout->addWidget(m_coinsWidget);
-#ifdef HAS_LOCALMONERO
- m_localMoneroWidget = new LocalMoneroWidget(this, m_wallet);
- ui->localMoneroLayout->addWidget(m_localMoneroWidget);
-#else
- ui->tabWidgetExchanges->setTabVisible(0, false);
-#endif
-
-#ifdef HAS_XMRIG
- m_xmrig = new XMRigWidget(m_wallet, this);
- ui->xmrRigLayout->addWidget(m_xmrig);
+ // [Plugins..]
+ for (auto* plugin : m_plugins) {
+ if (!plugin->hasParent()) {
+ qDebug() << "Adding tab: " << plugin->displayName();
- connect(m_xmrig, &XMRigWidget::miningStarted, [this]{ this->updateTitle(); });
- connect(m_xmrig, &XMRigWidget::miningEnded, [this]{ this->updateTitle(); });
-#else
- ui->tabWidget->setTabVisible(Tabs::XMRIG, false);
-#endif
+ if (plugin->insertFirst()) {
+ ui->tabWidget->insertTab(0, plugin->tab(), icons()->icon(plugin->icon()), plugin->displayName());
+ } else {
+ ui->tabWidget->addTab(plugin->tab(), icons()->icon(plugin->icon()), plugin->displayName());
+ }
-#if defined(Q_OS_MACOS)
- ui->line->hide();
-#endif
+ for (auto* child : m_plugins) {
+ if (child->hasParent() && child->parent() == plugin->id()) {
+ plugin->addSubPlugin(child);
+ }
+ }
+ }
+ }
ui->frame_coinControl->setVisible(false);
connect(ui->btn_resetCoinControl, &QPushButton::clicked, [this]{
connect(m_walletUnlockWidget, &WalletUnlockWidget::closeWallet, this, &MainWindow::close);
connect(m_walletUnlockWidget, &WalletUnlockWidget::unlockWallet, this, &MainWindow::unlockWallet);
+ ui->tabWidget->setCurrentIndex(0);
ui->stackedWidget->setCurrentIndex(0);
}
connect(ui->actionShow_Searchbar, &QAction::toggled, this, &MainWindow::toggleSearchbar);
ui->actionShow_Searchbar->setChecked(conf()->get(Config::showSearchbar).toBool());
- // Show/Hide Home
- connect(ui->actionShow_Home, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
- m_tabShowHideMapper["Home"] = new ToggleTab(ui->tabHome, "Home", "Home", ui->actionShow_Home, Config::showTabHome);
- m_tabShowHideSignalMapper->setMapping(ui->actionShow_Home, "Home");
-
// Show/Hide Coins
connect(ui->actionShow_Coins, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
- m_tabShowHideMapper["Coins"] = new ToggleTab(ui->tabCoins, "Coins", "Coins", ui->actionShow_Coins, Config::showTabCoins);
+ m_tabShowHideMapper["Coins"] = new ToggleTab(ui->tabCoins, "Coins", "Coins", ui->actionShow_Coins);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_Coins, "Coins");
- // Show/Hide Calc
- connect(ui->actionShow_calc, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
- m_tabShowHideMapper["Calc"] = new ToggleTab(ui->tabCalc, "Calc", "Calc", ui->actionShow_calc, Config::showTabCalc);
- m_tabShowHideSignalMapper->setMapping(ui->actionShow_calc, "Calc");
-
- // Show/Hide Exchange
-#if defined(HAS_LOCALMONERO)
- connect(ui->actionShow_Exchange, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
- m_tabShowHideMapper["Exchange"] = new ToggleTab(ui->tabExchange, "Exchange", "Exchange", ui->actionShow_Exchange, Config::showTabExchange);
- m_tabShowHideSignalMapper->setMapping(ui->actionShow_Exchange, "Exchange");
-#else
- ui->actionShow_Exchange->setVisible(false);
- ui->tabWidget->setTabVisible(Tabs::EXCHANGES, false);
-#endif
+ // Show/Hide Plugins..
+ for (const auto &plugin : m_plugins) {
+ if (plugin->parent() != "") {
+ continue;
+ }
- // Show/Hide Mining
-#if defined(HAS_XMRIG)
- connect(ui->actionShow_XMRig, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
- m_tabShowHideMapper["Mining"] = new ToggleTab(ui->tabXmrRig, "Mining", "Mining", ui->actionShow_XMRig, Config::showTabXMRig);
- m_tabShowHideSignalMapper->setMapping(ui->actionShow_XMRig, "Mining");
-#else
- ui->actionShow_XMRig->setVisible(false);
-#endif
+ auto* pluginAction = new QAction(QString("Show %1").arg(plugin->displayName()), this);
+ ui->menuView->insertAction(plugin->insertFirst() ? ui->actionPlaceholderBegin : ui->actionPlaceholderEnd, pluginAction);
+ connect(pluginAction, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
+ m_tabShowHideMapper[plugin->displayName()] = new ToggleTab(plugin->tab(), plugin->displayName(), plugin->displayName(), pluginAction);
+ m_tabShowHideSignalMapper->setMapping(pluginAction, plugin->displayName());
+ }
+ ui->actionPlaceholderBegin->setVisible(false);
+ ui->actionPlaceholderEnd->setVisible(false);
+ QStringList enabledTabs = conf()->get(Config::enabledTabs).toStringList();
for (const auto &key: m_tabShowHideMapper.keys()) {
const auto toggleTab = m_tabShowHideMapper.value(key);
- const bool show = conf()->get(toggleTab->configKey).toBool();
+ bool show = enabledTabs.contains(key);
+
toggleTab->menuAction->setText((show ? QString("Hide ") : QString("Show ")) + toggleTab->name);
ui->tabWidget->setTabVisible(ui->tabWidget->indexOf(toggleTab->tab), show);
}
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()) {
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
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);
}
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) {
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() {
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());
}
if (!this->cleanedUp) {
this->cleanedUp = true;
- conf()->set(Config::homeWidget, ui->tabHomeWidget->currentIndex());
+ emit aboutToQuit();
m_historyWidget->resetModel();
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"
"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);
}
void MainWindow::onPreferredFiatCurrencyChanged() {
- for (const auto &widget : m_tickerWidgets) {
- widget->updateDisplay();
- }
- m_balanceTickerWidget->updateDisplay();
m_sendWidget->onPreferredFiatCurrencyChanged();
}
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";
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
#include <QMainWindow>
#include <QSystemTrayIcon>
-#include <QMenu>
#include "components.h"
-#include "CalcWindow.h"
#include "SettingsDialog.h"
#include "dialog/AboutDialog.h"
#include "utils/config.h"
#include "utils/daemonrpc.h"
#include "utils/EventFilter.h"
-#include "plugins/ccs/CCSWidget.h"
-#include "plugins/reddit/RedditWidget.h"
#include "widgets/TickerWidget.h"
#include "widgets/WalletUnlockWidget.h"
#include "wizard/WalletWizard.h"
#include "CoinsWidget.h"
#include "WindowManager.h"
+#include "plugins/Plugin.h"
#ifdef CHECK_UPDATES
#include "utils/updater/Updater.h"
#endif
-#ifdef HAS_LOCALMONERO
-#include "plugins/localmonero/LocalMoneroWidget.h"
-#endif
-
-#ifdef HAS_XMRIG
-#include "plugins/xmrig/XMRigWidget.h"
-#endif
-
namespace Ui {
class MainWindow;
}
struct ToggleTab {
- ToggleTab(QWidget *tab, QString name, QString description, QAction *menuAction, Config::ConfigKey configKey) :
- tab(tab), key(std::move(name)), name(std::move(description)), menuAction(menuAction), configKey(configKey){}
+ ToggleTab(QWidget *tab, QString name, QString description, QAction *menuAction) :
+ tab(tab), key(std::move(name)), name(std::move(description)), menuAction(menuAction) {}
QWidget *tab;
QString key;
QString name;
QAction *menuAction;
- Config::ConfigKey configKey;
};
class WindowManager;
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,
void onHideUpdateNotifications(bool hidden);
signals:
+ void updateIcons();
void closed();
+ void uiSetup();
+ void aboutToQuit();
protected:
void changeEvent(QEvent* event) override;
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);
friend WindowManager;
void initStatusBar();
+ void initPlugins();
void initWidgets();
void initMenu();
- void initHome();
void initOffline();
void initWalletContext();
void lockWallet();
void unlockWallet(const QString &password);
void closeQDialogChildren(QObject *object);
+ int findTab(const QString &title);
QIcon hardwareDevicePairedIcon();
QIcon hardwareDeviceUnpairedIcon();
Nodes *m_nodes;
DaemonRpc *m_rpc;
- CalcWindow *m_windowCalc = nullptr;
SplashDialog *m_splashDialog = nullptr;
AccountSwitcherDialog *m_accountSwitcherDialog = nullptr;
WalletUnlockWidget *m_walletUnlockWidget = nullptr;
-#ifdef HAS_XMRIG
- XMRigWidget *m_xmrig = nullptr;
-#endif
ContactsWidget *m_contactsWidget = nullptr;
HistoryWidget *m_historyWidget = nullptr;
SendWidget *m_sendWidget = nullptr;
ReceiveWidget *m_receiveWidget = nullptr;
CoinsWidget *m_coinsWidget = nullptr;
-#ifdef HAS_LOCALMONERO
- LocalMoneroWidget *m_localMoneroWidget = nullptr;
-#endif
-
- QList<TickerWidgetBase*> m_tickerWidgets;
- BalanceTickerWidget *m_balanceTickerWidget;
QPointer<QAction> m_clearRecentlyOpenAction;
QTimer m_updateBytes;
QTimer m_checkUserActivity;
+ QList<Plugin*> m_plugins;
+
QString m_statusText;
int m_statusDots;
bool m_constructingTransaction = false;
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
- <number>2</number>
+ <number>0</number>
</property>
<widget class="QWidget" name="page_wallet">
<layout class="QVBoxLayout" name="verticalLayout_11">
<height>16</height>
</size>
</property>
- <widget class="QWidget" name="tabHome">
- <attribute name="icon">
- <iconset resource="assets.qrc">
- <normaloff>:/assets/images/tab_home.png</normaloff>:/assets/images/tab_home.png</iconset>
- </attribute>
- <attribute name="title">
- <string>Home</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_3">
- <property name="bottomMargin">
- <number>5</number>
- </property>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <layout class="QHBoxLayout" name="tickerLayout"/>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <layout class="QHBoxLayout" name="fiatTickerLayout"/>
- </item>
- </layout>
- </item>
- <item>
- <widget class="Line" name="line">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QTabWidget" name="tabHomeWidget">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <property name="documentMode">
- <bool>true</bool>
- </property>
- <widget class="QWidget" name="tab">
- <attribute name="title">
- <string>Crowdfunding</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_6">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="CCSWidget" name="ccsWidget" native="true"/>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="tab_4">
- <attribute name="title">
- <string>Bounties</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_10">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="BountiesWidget" name="bountiesWidget" native="true"/>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="tab_2">
- <attribute name="title">
- <string>/r/Monero</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_7">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="RedditWidget" name="redditWidget" native="true"/>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="tab_3">
- <attribute name="title">
- <string>Revuo</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_9">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="RevuoWidget" name="revuoWidget" native="true"/>
- </item>
- </layout>
- </widget>
- </widget>
- </item>
- </layout>
- </widget>
<widget class="QWidget" name="tabHistory">
<attribute name="icon">
<iconset resource="assets.qrc">
</item>
</layout>
</widget>
- <widget class="QWidget" name="tabCalc">
- <attribute name="icon">
- <iconset resource="assets.qrc">
- <normaloff>:/assets/images/gnome-calc.png</normaloff>:/assets/images/gnome-calc.png</iconset>
- </attribute>
- <attribute name="title">
- <string>Calc</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="CalcWidget" name="conversionWidget" native="true"/>
- </item>
- </layout>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="tabExchange">
- <attribute name="icon">
- <iconset resource="assets.qrc">
- <normaloff>:/assets/images/update.png</normaloff>:/assets/images/update.png</iconset>
- </attribute>
- <attribute name="title">
- <string>Exchange</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_8">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QTabWidget" name="tabWidgetExchanges">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <widget class="QWidget" name="tabLocalMonero">
- <attribute name="icon">
- <iconset resource="assets.qrc">
- <normaloff>:/assets/images/localMonero_logo.png</normaloff>:/assets/images/localMonero_logo.png</iconset>
- </attribute>
- <attribute name="title">
- <string>LocalMonero</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_4">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <layout class="QVBoxLayout" name="localMoneroLayout"/>
- </item>
- </layout>
- </widget>
- </widget>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="tabXmrRig">
- <attribute name="icon">
- <iconset resource="assets.qrc">
- <normaloff>:/assets/images/mining.png</normaloff>:/assets/images/mining.png</iconset>
- </attribute>
- <attribute name="title">
- <string>Mining</string>
- </attribute>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="0">
- <layout class="QGridLayout" name="xmrRigLayout"/>
- </item>
- </layout>
- </widget>
</widget>
</item>
<item>
<addaction name="separator"/>
<addaction name="actionPay_to_many"/>
<addaction name="actionAddress_checker"/>
- <addaction name="actionCalculator"/>
<addaction name="actionCreateDesktopEntry"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>View</string>
</property>
- <addaction name="actionShow_Home"/>
+ <addaction name="actionPlaceholderBegin"/>
<addaction name="actionShow_Coins"/>
- <addaction name="actionShow_calc"/>
- <addaction name="actionShow_Exchange"/>
- <addaction name="actionShow_XMRig"/>
+ <addaction name="actionPlaceholderEnd"/>
<addaction name="separator"/>
<addaction name="actionShow_Searchbar"/>
</widget>
<string>Transmit over UR</string>
</property>
</action>
+ <action name="actionPlaceholderEnd">
+ <property name="text">
+ <string>Placeholder</string>
+ </property>
+ </action>
+ <action name="actionPlaceholderBegin">
+ <property name="text">
+ <string>PlaceholderBegin</string>
+ </property>
+ </action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
- <customwidget>
- <class>CalcWidget</class>
- <extends>QWidget</extends>
- <header>CalcWidget.h</header>
- <container>1</container>
- </customwidget>
- <customwidget>
- <class>CCSWidget</class>
- <extends>QWidget</extends>
- <header>plugins/ccs/CCSWidget.h</header>
- <container>1</container>
- </customwidget>
- <customwidget>
- <class>RedditWidget</class>
- <extends>QWidget</extends>
- <header>plugins/reddit/RedditWidget.h</header>
- <container>1</container>
- </customwidget>
- <customwidget>
- <class>RevuoWidget</class>
- <extends>QWidget</extends>
- <header>plugins/revuo/RevuoWidget.h</header>
- <container>1</container>
- </customwidget>
- <customwidget>
- <class>BountiesWidget</class>
- <extends>QWidget</extends>
- <header>plugins/bounties/BountiesWidget.h</header>
- <container>1</container>
- </customwidget>
<customwidget>
<class>ClickableLabel</class>
<extends>QLabel</extends>
#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)
this->setupDisplayTab();
this->setupMemoryTab();
this->setupTransactionsTab();
+ this->setupPluginsTab();
this->setupMiscTab();
connect(ui->selector, &QListWidget::currentItemChanged, [this](QListWidgetItem *current, QListWidgetItem *previous){
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);
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);
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()));
DISPLAY,
MEMORY,
TRANSACTIONS,
+ PLUGINS,
MISC
};
void proxySettingsChanged();
void updateBalance();
void offlineMode(bool offline);
+ void pluginConfigured(const QString &id);
public slots:
// void checkboxExternalLinkWarn();
void setupDisplayTab();
void setupMemoryTab();
void setupTransactionsTab();
+ void setupPluginsTab();
void setupMiscTab();
void setupThemeComboBox();
</item>
</layout>
</widget>
+ <widget class="QWidget" name="page_plugins">
+ <layout class="QVBoxLayout" name="verticalLayout_21">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_20">
+ <property name="text">
+ <string><html><head/><body><p><span style=" font-weight:700;">Plugins</span></p></body></html></string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="PluginWidget" name="pluginWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
<widget class="QWidget" name="page_misc">
<layout class="QVBoxLayout" name="verticalLayout_18">
<property name="leftMargin">
<header>widgets/NetworkProxyWidget.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>PluginWidget</class>
+ <extends>QWidget</extends>
+ <header>widgets/PluginWidget.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<connections>
for (const auto &window : m_windows) {
window->onPreferredFiatCurrencyChanged();
}
+ emit preferredFiatCurrencyChanged();
});
connect(&settings, &Settings::skinChanged, this, &WindowManager::onChangeTheme);
connect(&settings, &Settings::updateBalance, this, &WindowManager::updateBalance);
window->onHideUpdateNotifications(hidden);
}
});
+ connect(&settings, &Settings::pluginConfigured, [this](const QString &id) {
+ emit pluginConfigured(id);
+ });
if (showProxyTab) {
settings.showNetworkProxyTab();
void proxySettingsChanged();
void websocketStatusChanged(bool enabled);
void updateBalance();
+ void preferredFiatCurrencyChanged();
void offlineMode(bool offline);
+ void pluginConfigured(const QString &id);
public slots:
void onProxySettingsChanged();
<file>assets/images/coldcard_unpaired.png</file>
<file>assets/images/confirmed.svg</file>
<file>assets/images/connect.svg</file>
+ <file>assets/images/connect_white.svg</file>
<file>assets/images/copy.png</file>
<file>assets/images/edit.png</file>
<file>assets/images/external-link.svg</file>
--- /dev/null
+<?xml version="1.0" encoding="iso-8859-1"?>\r
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->\r
+<svg fill="#FFFFFF" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"\r
+ viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">\r
+<g>\r
+ <g>\r
+ <path d="M297.448,279.808c-5.533-5.532-14.505-5.532-20.038,0.001l-31.873,31.873l-45.219-45.219l31.873-31.874\r
+ c5.534-5.534,5.534-14.506,0-20.039c-5.533-5.534-14.506-5.534-20.039,0l-31.873,31.874l-25.485-25.485\r
+ c-5.533-5.534-14.506-5.534-20.039,0l-56.36,56.36c-39.275,39.274-41.73,101.64-7.364,143.801\r
+ c-0.46,0.358-0.909,0.738-1.332,1.161l-65.548,65.55c-5.534,5.533-5.534,14.506,0,20.039c2.767,2.767,6.393,4.15,10.019,4.15\r
+ c3.626,0,7.253-1.384,10.019-4.15l65.549-65.549c0.423-0.423,0.803-0.872,1.161-1.332c19.675,16.037,43.75,24.055,67.825,24.055\r
+ c27.515,0,55.029-10.473,75.976-31.42l56.36-56.36c5.534-5.533,5.534-14.506,0-20.039l-25.485-25.485l31.873-31.873\r
+ C302.982,294.314,302.982,285.341,297.448,279.808z M214.661,413.565c-30.845,30.843-81.029,30.843-111.874,0l-4.352-4.352\r
+ c-30.844-30.844-30.844-81.03,0-111.874l46.34-46.34l116.227,116.226L214.661,413.565z"/>\r
+ </g>\r
+</g>\r
+<g>\r
+ <g>\r
+ <path d="M507.849,24.19c5.534-5.533,5.534-14.505,0-20.039c-5.532-5.534-14.505-5.534-20.039,0l-65.549,65.548\r
+ c-0.423,0.422-0.801,0.87-1.159,1.33c-19.112-15.613-42.816-24.104-67.827-24.104c-28.7,0-55.682,11.177-75.976,31.471\r
+ l-56.36,56.36c-5.534,5.534-5.534,14.505,0,20.039L357.206,291.06c2.657,2.658,6.261,4.15,10.019,4.15\r
+ c3.758,0,7.363-1.493,10.019-4.15l56.36-56.36c20.294-20.294,31.47-47.276,31.47-75.975c0-25.011-8.49-48.715-24.104-67.827\r
+ c0.459-0.358,0.907-0.737,1.33-1.159L507.849,24.19z M413.565,214.662l-46.34,46.341L250.998,144.775l46.34-46.341\r
+ c14.942-14.941,34.807-23.17,55.937-23.17c21.131,0,40.996,8.229,55.937,23.17l4.352,4.352\r
+ c14.941,14.941,23.17,34.807,23.17,55.937C436.735,179.855,428.506,199.72,413.565,214.662z"/>\r
+ </g>\r
+</g>\r
+<g>\r
+ <g>\r
+ <path d="M374.062,196.728l-58.79-58.791c-5.533-5.534-14.506-5.534-20.039,0c-5.534,5.534-5.534,14.505,0,20.039l58.791,58.791\r
+ c2.767,2.767,6.393,4.15,10.019,4.15c3.626,0,7.253-1.383,10.019-4.15C379.596,211.233,379.596,202.262,374.062,196.728z"/>\r
+ </g>\r
+</g>\r
+<g>\r
+ <g>\r
+ <path d="M218.149,352.641l-58.791-58.791c-5.533-5.533-14.506-5.533-20.039,0c-5.533,5.534-5.533,14.506,0.001,20.039\r
+ l58.791,58.791c2.767,2.767,6.393,4.15,10.019,4.15c3.626,0,7.253-1.384,10.019-4.15\r
+ C223.683,367.146,223.683,358.173,218.149,352.641z"/>\r
+ </g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+</svg>\r
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) {
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef PLUGIN_H
+#define PLUGIN_H
+
+#include <QString>
+#include <QWidget>
+#include <QTabWidget>
+
+#include "libwalletqt/Wallet.h"
+
+class Plugin : public QObject {
+Q_OBJECT
+
+public:
+ enum PluginType {
+ TAB = 0,
+ WIDGET
+ };
+
+ virtual QString id() = 0;
+
+ // Used for sorting
+ virtual int idx() const = 0;
+
+ // id of parent plugin, plugin is only loaded if parent is available
+ virtual QString parent() = 0;
+ virtual QString displayName() = 0;
+ virtual QString description() = 0;
+ virtual QString icon() = 0;
+
+ // register expected websocket data
+ virtual QStringList socketData() = 0;
+ virtual PluginType type() = 0;
+ virtual QWidget* tab() = 0;
+
+ virtual bool configurable() {return false;}
+ virtual QDialog* configDialog(QWidget *parent) {return nullptr;}
+
+ // the plugin is automatically enabled if it has any enabled children
+ virtual bool implicitEnable() {return false;}
+ virtual bool requiresWebsocket() {return true;}
+
+ // insert tab to the left of standard tabs
+ virtual bool insertFirst() {return false;}
+ virtual void addSubPlugin(Plugin* plugin) {}
+
+ virtual void initialize(Wallet *wallet, QObject *parent) = 0;
+
+ bool hasParent() {return !parent().isEmpty();}
+
+signals:
+ void setStatusText(const QString &text, bool override, int timeout);
+ void fillSendTab(const QString &address, const QString &description);
+
+public slots:
+ virtual void skinChanged() {}
+ virtual void uiSetup() {}
+ virtual void aboutToQuit() {}
+
+protected:
+ Wallet* m_wallet = nullptr;
+};
+
+#endif //PLUGIN_H
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef PLUGINREGISTRY_H
+#define PLUGINREGISTRY_H
+
+#include "Plugin.h"
+#include "utils/config.h"
+
+class PluginRegistry {
+public:
+ static void registerPlugin(Plugin* plugin) {
+ getInstance().plugins.append(plugin);
+ getInstance().pluginMap[plugin->id()] = plugin;
+
+ std::sort(getInstance().plugins.begin(), getInstance().plugins.end(), [](Plugin *a, Plugin *b) {
+ return a->idx() < b->idx();
+ });
+ }
+
+ void registerPluginCreator(const std::function<Plugin*()>& creator) {
+ plugin_creators.append(creator);
+ }
+
+ static const QList<Plugin*>& getPlugins() {
+ return getInstance().plugins;
+ }
+
+ static Plugin* getPlugin(const QString& id) {
+ return getInstance().pluginMap.value(id, nullptr);
+ }
+
+ static const QList<std::function<Plugin*()>>& getPluginCreators() {
+ return getInstance().plugin_creators;
+ }
+
+ bool isPluginEnabled(const QString &id) {
+ if (!pluginMap.contains(id)) {
+ return false;
+ }
+
+ Plugin* plugin = pluginMap[id];
+
+ // Don't load plugins that require the websocket connection if it is disabled
+ bool websocketDisabled = conf()->get(Config::disableWebsocket).toBool();
+ if (websocketDisabled && plugin->requiresWebsocket()) {
+ return false;
+ }
+
+ QStringList enabledPlugins = conf()->get(Config::enabledPlugins).toStringList();
+ if (enabledPlugins.contains(id) && !plugin->implicitEnable()) {
+ return true;
+ }
+
+ bool enabled = false;
+ if (plugin->implicitEnable()) {
+ for (const auto& child : plugins) {
+ if (child->parent() == plugin->id() && enabledPlugins.contains(child->id())) {
+ enabled = true;
+ }
+ }
+ }
+
+ return enabled;
+ }
+
+private:
+ QMap<QString, Plugin*> pluginMap;
+ QList<Plugin*> plugins;
+ QList<std::function<Plugin*()>> plugin_creators;
+
+public:
+ static PluginRegistry& getInstance() {
+ static PluginRegistry instance;
+ return instance;
+ }
+};
+
+#endif //PLUGINREGISTRY_H
QSharedPointer<BountyEntry> post = m_bounties.at(index.row());
- if(role == Qt::DisplayRole) {
+ if(role == Qt::DisplayRole || role == Qt::UserRole) {
switch(index.column()) {
- case Votes:
+ case Votes: {
+ if (role == Qt::UserRole) {
+ return post->votes;
+ }
return QString::number(post->votes);
+ }
case Title:
return post->title;
case Status:
return post->status;
case Bounty: {
+ if (role == Qt::UserRole) {
+ return post->bountyAmount;
+ }
+
if (post->bountyAmount > 0) {
return QString("%1 XMR").arg(QString::number(post->bountyAmount, 'f', 5));
}
{
switch(section) {
case Votes:
- return QString(" 🡅 ");
+ return QString("Score ");
case Title:
return QString("Title");
case Status:
--- /dev/null
+// 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;
+}();
--- /dev/null
+// 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
--- /dev/null
+// 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);
+}
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef BOUNTIESPROXYMODEL_H
+#define BOUNTIESPROXYMODEL_H
+
+#include <QSortFilterProxyModel>
+
+class BountiesProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ explicit BountiesProxyModel(QObject* parent = nullptr);
+};
+
+#endif //BOUNTIESPROXYMODEL_H
#include "BountiesWidget.h"
#include "ui_BountiesWidget.h"
-#include <QDesktopServices>
-#include <QStandardItemModel>
#include <QTableWidget>
#include "BountiesModel.h"
#include "utils/Utils.h"
#include "utils/config.h"
+#include "utils/WebsocketNotifier.h"
BountiesWidget::BountiesWidget(QWidget *parent)
: QWidget(parent)
, 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);
connect(ui->tableView, &QTableView::doubleClicked, this, &BountiesWidget::linkClicked);
- ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
-}
+ connect(websocketNotifier(), &WebsocketNotifier::dataReceived, this, [this](const QString &type, const QJsonValue &json) {
+ if (type == "bounties") {
+ QJsonArray bounties_data = json.toArray();
+ QList<QSharedPointer<BountyEntry>> l;
+
+ for (const auto& entry : bounties_data) {
+ QJsonObject obj = entry.toObject();
+ auto bounty = new BountyEntry(obj.value("votes").toInt(),
+ obj.value("title").toString(),
+ obj.value("amount").toDouble(),
+ obj.value("link").toString(),
+ obj.value("address").toString(),
+ obj.value("status").toString());
+ QSharedPointer<BountyEntry> b = QSharedPointer<BountyEntry>(bounty);
+ l.append(b);
+ }
+
+ m_model->updateBounties(l);
+ }
+ });
-BountiesModel * BountiesWidget::model() {
- return m_model;
+ ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
}
void BountiesWidget::linkClicked() {
- QModelIndex index = ui->tableView->currentIndex();
+ QModelIndex index = m_proxyModel->mapToSource(ui->tableView->currentIndex());
auto post = m_model->post(index.row());
if (post)
}
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) {
#include <QWidget>
#include "BountiesModel.h"
+#include "BountiesProxyModel.h"
namespace Ui {
class BountiesWidget;
public:
explicit BountiesWidget(QWidget *parent = nullptr);
~BountiesWidget() override;
- BountiesModel* model();
public slots:
void linkClicked();
QScopedPointer<Ui::BountiesWidget> ui;
BountiesModel *m_model;
+ BountiesProxyModel *m_proxyModel;
QMenu *m_contextMenu;
};
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="sortingEnabled">
+ <bool>false</bool>
+ </property>
</widget>
</item>
</layout>
#include "CalcConfigDialog.h"
#include "ui_CalcConfigDialog.h"
-#include "AppData.h"
+#include "utils/AppData.h"
#include "utils/config.h"
CalcConfigDialog::CalcConfigDialog(QWidget *parent)
--- /dev/null
+// 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;
+}();
--- /dev/null
+// 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
#include <QList>
-#include "dialog/CalcConfigDialog.h"
+#include "CalcConfigDialog.h"
#include "utils/AppData.h"
#include "utils/ColorScheme.h"
#include "utils/config.h"
<x>0</x>
<y>0</y>
<width>800</width>
- <height>242</height>
+ <height>366</height>
</rect>
</property>
<property name="windowTitle">
</item>
</layout>
</item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>CalcWidget</class>
<extends>QWidget</extends>
- <header>CalcWidget.h</header>
+ <header>plugins/calc/CalcWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
+++ /dev/null
-// SPDX-License-Identifier: BSD-3-Clause
-// SPDX-FileCopyrightText: 2020-2023 The Monero Project
-
-#include "CCSWidget.h"
-#include "ui_CCSWidget.h"
-
-#include <QDesktopServices>
-#include <QStandardItemModel>
-#include <QTableWidget>
-
-#include "CCSProgressDelegate.h"
-#include "utils/Utils.h"
-
-CCSWidget::CCSWidget(QWidget *parent)
- : QWidget(parent)
- , ui(new Ui::CSSWidget)
- , m_model(new CCSModel(this))
- , m_contextMenu(new QMenu(this))
-{
- ui->setupUi(this);
- ui->treeView->setModel(m_model);
-
- m_contextMenu->addAction("View proposal", this, &CCSWidget::linkClicked);
- m_contextMenu->addAction("Donate", this, &CCSWidget::donateClicked);
-
- connect(ui->treeView, &QHeaderView::customContextMenuRequested, this, &CCSWidget::showContextMenu);
- connect(ui->treeView, &QTreeView::doubleClicked, this, &CCSWidget::linkClicked);
-
- ui->treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
- ui->treeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
- ui->treeView->header()->setSectionResizeMode(CCSModel::Title, QHeaderView::Stretch);
-}
-
-CCSModel* CCSWidget::model() {
- return m_model;
-}
-
-void CCSWidget::linkClicked() {
- QModelIndex index = ui->treeView->currentIndex();
- auto entry = m_model->entry(index.row());
-
- if (entry) {
- Utils::externalLinkWarning(this, entry->url);
- }
-}
-
-void CCSWidget::donateClicked() {
- QModelIndex index = ui->treeView->currentIndex();
- auto entry = m_model->entry(index.row());
-
- if (entry)
- emit selected(*entry);
-}
-
-void CCSWidget::showContextMenu(const QPoint &pos) {
- QModelIndex index = ui->treeView->indexAt(pos);
- if (!index.isValid()) {
- return;
- }
-
- m_contextMenu->exec(ui->treeView->viewport()->mapToGlobal(pos));
-}
-
-CCSWidget::~CCSWidget() = default;
\ No newline at end of file
case Title:
return QString("Proposal");
case Organizer:
- return QString("Organizer");
+ return QString("Organizer ");
case Author:
return QString("Author");
case Progress:
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "CCSWidget.h"
+#include "ui_CCSWidget.h"
+
+#include <QTableWidget>
+
+#include "CCSProgressDelegate.h"
+#include "utils/Utils.h"
+#include "utils/WebsocketNotifier.h"
+
+CCSWidget::CCSWidget(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::CSSWidget)
+ , m_model(new CCSModel(this))
+ , m_contextMenu(new QMenu(this))
+{
+ ui->setupUi(this);
+ ui->treeView->setModel(m_model);
+
+ m_contextMenu->addAction("View proposal", this, &CCSWidget::linkClicked);
+ m_contextMenu->addAction("Donate", this, &CCSWidget::donateClicked);
+
+ connect(ui->treeView, &QHeaderView::customContextMenuRequested, this, &CCSWidget::showContextMenu);
+ connect(ui->treeView, &QTreeView::doubleClicked, this, &CCSWidget::linkClicked);
+
+ ui->treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
+ ui->treeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+ ui->treeView->header()->setSectionResizeMode(CCSModel::Title, QHeaderView::Stretch);
+
+ connect(websocketNotifier(), &WebsocketNotifier::dataReceived, this, [this](const QString& type, const QJsonValue& json) {
+ if (type == "ccs") {
+ QJsonArray ccs_data = json.toArray();
+ QList<QSharedPointer<CCSEntry>> l;
+
+ for (const auto& entry: ccs_data) {
+ auto obj = entry.toObject();
+ auto c = QSharedPointer<CCSEntry>(new CCSEntry());
+
+ if (obj.value("state").toString() != "FUNDING-REQUIRED")
+ continue;
+
+ c->state = obj.value("state").toString();
+ c->address = obj.value("address").toString();
+ c->author = obj.value("author").toString();
+ c->date = obj.value("date").toString();
+ c->title = obj.value("title").toString();
+ c->target_amount = obj.value("target_amount").toDouble();
+ c->raised_amount = obj.value("raised_amount").toDouble();
+ c->percentage_funded = obj.value("percentage_funded").toDouble();
+ c->contributions = obj.value("contributions").toInt();
+ c->organizer = obj.value("organizer").toString();
+ c->currency = obj.value("currency").toString();
+
+ QString urlpath = obj.value("urlpath").toString();
+ if (c->organizer == "CCS") {
+ c->url = QString("https://ccs.getmonero.org/%1").arg(urlpath);
+ }
+ else if (c->organizer == "MAGIC") {
+ c->url = QString("https://monerofund.org/%1").arg(urlpath);
+ }
+ else {
+ continue;
+ }
+
+ l.append(c);
+ }
+
+ m_model->updateEntries(l);
+ }
+ });
+}
+
+void CCSWidget::linkClicked() {
+ QModelIndex index = ui->treeView->currentIndex();
+ auto entry = m_model->entry(index.row());
+
+ if (entry) {
+ Utils::externalLinkWarning(this, entry->url);
+ }
+}
+
+void CCSWidget::donateClicked() {
+ QModelIndex index = ui->treeView->currentIndex();
+ auto entry = m_model->entry(index.row());
+
+ if (entry) {
+ emit fillSendTab(entry->address, QString("Donation to %1: %2").arg(entry->organizer, entry->title));
+ }
+}
+
+void CCSWidget::showContextMenu(const QPoint &pos) {
+ QModelIndex index = ui->treeView->indexAt(pos);
+ if (!index.isValid()) {
+ return;
+ }
+
+ m_contextMenu->exec(ui->treeView->viewport()->mapToGlobal(pos));
+}
+
+CCSWidget::~CCSWidget() = default;
\ No newline at end of file
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();
--- /dev/null
+// 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;
+}();
--- /dev/null
+// 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
--- /dev/null
+// 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;
+}();
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef EXCHANGEWIDGET_H
+#define EXCHANGEWIDGET_H
+
+#include <QWidget>
+#include <QTabWidget>
+
+#include "plugins/Plugin.h"
+
+namespace Ui {
+ class ExchangeWidget;
+}
+
+class ExchangeWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit ExchangeWidget(QWidget *parent = nullptr);
+ ~ExchangeWidget();
+
+ void addTab(Plugin *plugin);
+
+private:
+ QScopedPointer<Ui::ExchangeWidget> ui;
+};
+
+
+#endif //EXCHANGEWIDGET_H
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>BountiesWidget</class>
- <widget class="QWidget" name="BountiesWidget">
+ <class>ExchangeWidget</class>
+ <widget class="QWidget" name="ExchangeWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>566</width>
- <height>372</height>
+ <width>400</width>
+ <height>300</height>
</rect>
</property>
<property name="windowTitle">
<number>0</number>
</property>
<item>
- <widget class="QTableView" name="tableView">
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
- </widget>
+ <widget class="QTabWidget" name="tabWidget"/>
</item>
</layout>
</widget>
--- /dev/null
+// 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;
+}();
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef HOMEWIDGET_H
+#define HOMEWIDGET_H
+
+#include <QWidget>
+
+#include "plugins/Plugin.h"
+
+namespace Ui {
+ class HomeWidget;
+}
+
+class HomeWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit HomeWidget(QWidget *parent = nullptr);
+ ~HomeWidget();
+
+ void addPlugin(Plugin *plugin);
+ void aboutToQuit();
+ void uiSetup();
+
+private:
+ QScopedPointer<Ui::HomeWidget> ui;
+};
+
+#endif //HOMEWIDGET_H
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>BountiesWidget</class>
- <widget class="QWidget" name="BountiesWidget">
+ <class>HomeWidget</class>
+ <widget class="QWidget" name="HomeWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>566</width>
- <height>372</height>
+ <width>743</width>
+ <height>460</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
<property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
+ <number>5</number>
</property>
<property name="bottomMargin">
- <number>0</number>
+ <number>5</number>
</property>
<item>
- <widget class="QTableView" name="tableView">
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
+ <layout class="QVBoxLayout" name="widgetLayout"/>
+ </item>
+ <item>
+ <widget class="QTabWidget" name="tabHomeWidget">
+ <property name="currentIndex">
+ <number>-1</number>
+ </property>
+ <property name="documentMode">
+ <bool>true</bool>
</property>
</widget>
</item>
--- /dev/null
+// 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;
+}();
--- /dev/null
+// 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
QSharedPointer<RedditPost> post = m_posts.at(index.row());
- if(role == Qt::DisplayRole) {
+ if(role == Qt::DisplayRole || role == Qt::UserRole) {
switch(index.column()) {
case Title:
return post->title;
case Author:
return post->author;
- case Comments:
+ case Comments: {
+ if (role == Qt::UserRole) {
+ return post->comments;
+ }
return QString::number(post->comments);
+ }
default:
return QVariant();
}
--- /dev/null
+// 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;
+}();
--- /dev/null
+// 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
--- /dev/null
+// 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);
+}
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef REDDITPROXYMODEL_H
+#define REDDITPROXYMODEL_H
+
+#include <QSortFilterProxyModel>
+
+class RedditProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ explicit RedditProxyModel(QObject* parent = nullptr);
+};
+
+#endif //REDDITPROXYMODEL_H
#include "RedditWidget.h"
#include "ui_RedditWidget.h"
-#include <QDesktopServices>
-#include <QStandardItemModel>
#include <QTableWidget>
#include "RedditModel.h"
#include "utils/Utils.h"
#include "utils/config.h"
+#include "utils/WebsocketNotifier.h"
RedditWidget::RedditWidget(QWidget *parent)
: QWidget(parent)
, 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);
connect(ui->tableView, &QTableView::doubleClicked, this, &RedditWidget::linkClicked);
ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
-}
-RedditModel* RedditWidget::model() {
- return m_model;
+ connect(websocketNotifier(), &WebsocketNotifier::dataReceived, this, [this](const QString& type, const QJsonValue& json) {
+ if (type == "reddit") {
+ QJsonArray reddit_data = json.toArray();
+ QList<QSharedPointer<RedditPost>> l;
+
+ for (auto &&entry: reddit_data) {
+ auto obj = entry.toObject();
+ auto redditPost = new RedditPost(
+ obj.value("title").toString(),
+ obj.value("author").toString(),
+ obj.value("permalink").toString(),
+ obj.value("comments").toInt());
+ QSharedPointer<RedditPost> r = QSharedPointer<RedditPost>(redditPost);
+ l.append(r);
+ }
+
+ m_model->updatePosts(l);
+ }
+ });
}
void RedditWidget::linkClicked() {
- QModelIndex index = ui->tableView->currentIndex();
+ QModelIndex index = m_proxyModel->mapToSource(ui->tableView->currentIndex());
auto post = m_model->post(index.row());
if (post)
}
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) {
#include <QWidget>
#include "RedditModel.h"
+#include "RedditProxyModel.h"
namespace Ui {
class RedditWidget;
public:
explicit RedditWidget(QWidget *parent = nullptr);
~RedditWidget();
- RedditModel* model();
public slots:
void linkClicked();
QScopedPointer<Ui::RedditWidget> ui;
RedditModel* const m_model;
+ RedditProxyModel* m_proxyModel;
QMenu *m_contextMenu;
};
--- /dev/null
+// 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;
+}();
--- /dev/null
+// 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
#include "utils/ColorScheme.h"
#include "Utils.h"
+#include "utils/WebsocketNotifier.h"
RevuoWidget::RevuoWidget(QWidget *parent)
: QWidget(parent)
connect(ui->listWidget, &QListWidget::currentTextChanged, this, &RevuoWidget::onSelectItem);
connect(ui->listWidget, &QListWidget::customContextMenuRequested, this, &RevuoWidget::showContextMenu);
+
+ connect(websocketNotifier(), &WebsocketNotifier::dataReceived, this, [this](const QString& type, const QJsonValue& json) {
+ if (type == "revuo") {
+ QJsonArray revuo_data = json.toArray();
+ QList<QSharedPointer<RevuoItem>> l;
+
+ for (const auto &entry: revuo_data) {
+ auto obj = entry.toObject();
+
+ QStringList newsbytes;
+ for (const auto &n : obj.value("newsbytes").toArray()) {
+ newsbytes.append(n.toString());
+ }
+
+ auto revuoItem = new RevuoItem(
+ obj.value("title").toString(),
+ obj.value("url").toString(),
+ newsbytes);
+
+ QSharedPointer<RevuoItem> r = QSharedPointer<RevuoItem>(revuoItem);
+ l.append(r);
+ }
+
+ this->updateItems(l);
+ }
+ });
}
void RevuoWidget::updateItems(const QList<QSharedPointer<RevuoItem>> &items) {
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) {
--- /dev/null
+// 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
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef TICKERSCONFIGADDDIALOG_H
+#define TICKERSCONFIGADDDIALOG_H
+
+#include <QDialog>
+#include <QListWidget>
+
+#include "components.h"
+
+namespace Ui {
+ class TickersConfigAddDialog;
+}
+
+class TickersConfigAddDialog : public WindowModalDialog
+{
+ Q_OBJECT
+
+public:
+ explicit TickersConfigAddDialog(QWidget *parent = nullptr);
+ ~TickersConfigAddDialog() override;
+
+ enum TickersType {
+ TICKER = 0,
+ RATIO
+ };
+
+ QString getTicker();
+
+private:
+ QScopedPointer<Ui::TickersConfigAddDialog> ui;
+ TickersType m_type;
+};
+
+#endif //TICKERSCONFIGADDDIALOG_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TickersConfigAddDialog</class>
+ <widget class="QDialog" name="TickersConfigAddDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>431</width>
+ <height>122</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Add ticker</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Type:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="combo_type">
+ <item>
+ <property name="text">
+ <string>Ticker</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Ratio</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="pageTicker">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QComboBox" name="comboTicker"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="pageRatio">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QComboBox" name="comboRatio1"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>/</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboRatio2"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_3"/>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>TickersConfigAddDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>TickersConfigAddDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
--- /dev/null
+// 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
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef TICKERCONFIGDIALOG_H
+#define TICKERCONFIGDIALOG_H
+
+#include <QDialog>
+#include <QListWidget>
+
+#include "components.h"
+
+namespace Ui {
+ class TickersConfigDialog;
+}
+
+class TickersConfigDialog : public WindowModalDialog
+{
+ Q_OBJECT
+
+public:
+ explicit TickersConfigDialog(QWidget *parent = nullptr);
+ ~TickersConfigDialog() override;
+
+private:
+ void addTicker();
+ void removeTicker();
+ void saveConfig();
+
+ QScopedPointer<Ui::TickersConfigDialog> ui;
+};
+
+#endif //TICKERCONFIGDIALOG_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TickersConfigDialog</class>
+ <widget class="QDialog" name="TickersConfigDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>426</width>
+ <height>392</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Tickers config</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QListWidget" name="tickerList">
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="check_showFiatBalance">
+ <property name="text">
+ <string>Show fiat balance</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="btn_removeTicker">
+ <property name="text">
+ <string>Remove Ticker</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btn_addTicker">
+ <property name="text">
+ <string>Add Ticker</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>TickersConfigDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>TickersConfigDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
--- /dev/null
+// 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;
+}();
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef TICKERSWIDGET_H
+#define TICKERSWIDGET_H
+
+#include <QWidget>
+
+#include "Wallet.h"
+#include "widgets/TickerWidget.h"
+
+namespace Ui {
+ class TickersWidget;
+}
+
+class TickersWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TickersWidget(QWidget *parent, Wallet *wallet);
+ ~TickersWidget() override;
+
+private slots:
+ void updateBalance();
+ void updateDisplay();
+
+private:
+ void setup();
+
+ QScopedPointer<Ui::TickersWidget> ui;
+ Wallet *m_wallet;
+
+ QList<TickerWidgetBase*> m_tickerWidgets;
+ QScopedPointer<BalanceTickerWidget> m_balanceTickerWidget;
+};
+
+#endif //TICKERSWIDGET_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TickersWidget</class>
+ <widget class="QWidget" name="TickersWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>618</width>
+ <height>161</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="tickerLayout"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_fiatTickerLayout">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <layout class="QHBoxLayout" name="fiatTickerLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null
+// 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;
+}();
--- /dev/null
+// 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
#include <QStandardItemModel>
#include <QTableWidget>
+#include "WebsocketNotifier.h"
#include "utils/Icons.h"
#include "utils/Utils.h"
+#include "WindowManager.h"
XMRigWidget::XMRigWidget(Wallet *wallet, QWidget *parent)
: QWidget(parent)
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() {
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) {
}
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;
+ }
+}
}
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
#include "Utils.h"
#include "utils/os/tails.h"
#include "utils/os/whonix.h"
+#include "plugins/PluginRegistry.h"
#include <QJsonObject>
, websocketClient(new WebsocketClient(this))
{
connect(websocketClient, &WebsocketClient::WSMessage, this, &WebsocketNotifier::onWSMessage);
+
+ for (const auto& plugin : PluginRegistry::getPlugins()) {
+ m_pluginSubscriptions << plugin->socketData();
+ }
}
QPointer<WebsocketNotifier> WebsocketNotifier::m_instance(nullptr);
emit FiatRatesReceived(fiat_rates);
}
- else if(cmd == "reddit") {
- QJsonArray reddit_data = msg.value("data").toArray();
- this->onWSReddit(reddit_data);
- }
-
- else if(cmd == "ccs") {
- auto ccs_data = msg.value("data").toArray();
- this->onWSCCS(ccs_data);
- }
-
- else if(cmd == "bounties") {
- auto data = msg.value("data").toArray();
- this->onWSBounties(data);
- }
-
else if(cmd == "txFiatHistory") {
auto txFiatHistory_data = msg.value("data").toObject();
emit TxFiatHistoryReceived(txFiatHistory_data);
}
- else if(cmd == "revuo") {
- auto revuo_data = msg.value("data").toArray();
- this->onWSRevuo(revuo_data);
- }
-
#if defined(CHECK_UPDATES)
else if (cmd == "updates") {
this->onWSUpdates(msg.value("data").toObject());
}
#endif
-#if defined(HAS_XMRIG)
- else if(cmd == "xmrig") {
- this->onWSXMRigDownloads(msg.value("data").toObject());
- }
-#endif
-
-#if defined(HAS_LOCALMONERO)
- else if (cmd == "localmonero_countries") {
- emit LocalMoneroCountriesReceived(msg.value("data").toArray());
- }
-
- else if (cmd == "localmonero_currencies") {
- emit LocalMoneroCurrenciesReceived(msg.value("data").toArray());
+ else if (m_pluginSubscriptions.contains(cmd)) {
+ emit dataReceived(cmd, msg.value("data"));
}
-
- else if (cmd == "localmonero_payment_methods") {
- emit LocalMoneroPaymentMethodsReceived(msg.value("data").toObject());
- }
-#endif
}
void WebsocketNotifier::emitCache() {
emit NodesReceived(l);
}
-void WebsocketNotifier::onWSReddit(const QJsonArray& reddit_data) {
- QList<QSharedPointer<RedditPost>> l;
-
- for (auto &&entry: reddit_data) {
- auto obj = entry.toObject();
- auto redditPost = new RedditPost(
- obj.value("title").toString(),
- obj.value("author").toString(),
- obj.value("permalink").toString(),
- obj.value("comments").toInt());
- QSharedPointer<RedditPost> r = QSharedPointer<RedditPost>(redditPost);
- l.append(r);
- }
-
- emit RedditReceived(l);
-}
-
-void WebsocketNotifier::onWSCCS(const QJsonArray &ccs_data) {
- QList<QSharedPointer<CCSEntry>> l;
-
- for (const auto& entry: ccs_data) {
- auto obj = entry.toObject();
- auto c = QSharedPointer<CCSEntry>(new CCSEntry());
-
- if (obj.value("state").toString() != "FUNDING-REQUIRED")
- continue;
-
- c->state = obj.value("state").toString();
- c->address = obj.value("address").toString();
- c->author = obj.value("author").toString();
- c->date = obj.value("date").toString();
- c->title = obj.value("title").toString();
- c->target_amount = obj.value("target_amount").toDouble();
- c->raised_amount = obj.value("raised_amount").toDouble();
- c->percentage_funded = obj.value("percentage_funded").toDouble();
- c->contributions = obj.value("contributions").toInt();
- c->organizer = obj.value("organizer").toString();
- c->currency = obj.value("currency").toString();
-
- QString urlpath = obj.value("urlpath").toString();
- if (c->organizer == "CCS") {
- c->url = QString("https://ccs.getmonero.org/%1").arg(urlpath);
- }
- else if (c->organizer == "MAGIC") {
- c->url = QString("https://monerofund.org/%1").arg(urlpath);
- }
- else {
- continue;
- }
-
- l.append(c);
- }
-
- emit CCSReceived(l);
-}
-
-void WebsocketNotifier::onWSRevuo(const QJsonArray &revuo_data) {
- QList<QSharedPointer<RevuoItem>> l;
-
- for (auto &&entry: revuo_data) {
- auto obj = entry.toObject();
-
- QStringList newsbytes;
- for (const auto &n : obj.value("newsbytes").toArray()) {
- newsbytes.append(n.toString());
- }
-
- auto revuoItem = new RevuoItem(
- obj.value("title").toString(),
- obj.value("url").toString(),
- newsbytes);
-
- QSharedPointer<RevuoItem> r = QSharedPointer<RevuoItem>(revuoItem);
- l.append(r);
- }
-
- emit RevuoReceived(l);
-}
-
-void WebsocketNotifier::onWSBounties(const QJsonArray &bounties_data) {
- QList<QSharedPointer<BountyEntry>> l;
-
- for (const auto& entry : bounties_data) {
- QJsonObject obj = entry.toObject();
- auto bounty = new BountyEntry(obj.value("votes").toInt(),
- obj.value("title").toString(),
- obj.value("amount").toDouble(),
- obj.value("link").toString(),
- obj.value("address").toString(),
- obj.value("status").toString());
- QSharedPointer<BountyEntry> b = QSharedPointer<BountyEntry>(bounty);
- l.append(b);
- }
-
- emit BountyReceived(l);
-}
-
void WebsocketNotifier::onWSUpdates(const QJsonObject &updates) {
emit UpdatesReceived(updates);
}
#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 {
void NodesReceived(QList<FeatherNode> &L);
void CryptoRatesReceived(const QJsonArray &data);
void FiatRatesReceived(const QJsonObject &fiat_rates);
- void RedditReceived(QList<QSharedPointer<RedditPost>> L);
- void CCSReceived(QList<QSharedPointer<CCSEntry>> L);
- void BountyReceived(QList<QSharedPointer<BountyEntry>> L);
- void RevuoReceived(QList<QSharedPointer<RevuoItem>> L);
void TxFiatHistoryReceived(const QJsonObject &data);
void UpdatesReceived(const QJsonObject &updates);
void XMRigDownloadsReceived(const QJsonObject &downloads);
void LocalMoneroCountriesReceived(const QJsonArray &countries);
void LocalMoneroCurrenciesReceived(const QJsonArray ¤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<WebsocketNotifier> m_instance;
+ QStringList m_pluginSubscriptions;
QHash<QString, QJsonObject> m_cache;
QDateTime m_lastMessageReceived;
};
{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
{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}},
};
useOnionNodes,
// Tabs
- showTabHome,
- showTabCoins,
- showTabExchange,
- showTabCalc,
- showTabXMRig,
+ enabledTabs,
showSearchbar,
// Receive
fiatSymbols,
cryptoSymbols,
+
+ enabledPlugins,
+ restartRequired,
+
+ // Tickers
+ tickers,
+ tickersShowFiatBalance,
};
enum PrivacyLevel {
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "PluginWidget.h"
+#include "ui_PluginWidget.h"
+
+#include <QTreeWidget>
+#include "utils/config.h"
+#include "utils/Icons.h"
+#include "plugins/PluginRegistry.h"
+
+PluginWidget::PluginWidget(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::PluginWidget)
+{
+ ui->setupUi(this);
+
+ ui->frameRestart->setVisible(conf()->get(Config::restartRequired).toBool());
+ ui->frameRestart->setInfo(icons()->icon("settings_disabled_32px.png"), "Restart required to enable/disable plugins.");
+
+ const QStringList enabledPlugins = conf()->get(Config::enabledPlugins).toStringList();
+
+ for (const auto plugin : PluginRegistry::getPlugins()) {
+ if (!plugin->parent().isEmpty()) {
+ continue;
+ }
+
+ auto* item = new QTreeWidgetItem(ui->plugins);
+
+ this->setupItem(item, plugin);
+ if (enabledPlugins.contains(plugin->id()) && !plugin->implicitEnable()) {
+ item->setCheckState(0, Qt::Checked);
+ }
+
+ m_topLevelPlugins[plugin->id()] = item;
+ }
+
+ for (const auto plugin : PluginRegistry::getPlugins()) {
+ QTreeWidgetItem *item;
+
+ if (plugin->parent().isEmpty()) {
+ continue;
+ }
+
+ if (m_topLevelPlugins.contains(plugin->parent())) {
+ item = new QTreeWidgetItem(m_topLevelPlugins[plugin->parent()]);
+ } else {
+ qWarning() << "Top level plugin not found: " << plugin->id();
+ continue;
+ }
+
+ this->setupItem(item, plugin);
+ if (enabledPlugins.contains(plugin->id())) {
+ item->setCheckState(0, Qt::Checked);
+ }
+ }
+
+ ui->plugins->expandAll();
+ ui->plugins->setHeaderHidden(true);
+
+ connect(ui->plugins, &QTreeWidget::itemClicked, this, &PluginWidget::pluginSelected);
+ connect(ui->plugins, &QTreeWidget::itemClicked, this, &PluginWidget::pluginToggled);
+
+ connect(ui->btn_deselectAll, &QPushButton::clicked, this, &PluginWidget::deselectAll);
+ connect(ui->btn_selectAll, &QPushButton::clicked, this, &PluginWidget::selectAll);
+ connect(ui->btn_configure, &QPushButton::clicked, this, &PluginWidget::configurePlugin);
+}
+
+void PluginWidget::pluginSelected(QTreeWidgetItem* item, int column) {
+ QString pluginID = item->data(0, Qt::UserRole).toString();
+ Plugin* plugin = PluginRegistry::getPlugin(pluginID);
+
+ bool enable = false;
+ if (plugin && plugin->configurable()) {
+ enable = true;
+ }
+
+ ui->btn_configure->setEnabled(enable);
+ m_selectedPlugin = plugin;
+}
+
+void PluginWidget::pluginToggled(QTreeWidgetItem* item, int column) {
+ QStringList enabledPlugins = conf()->get(Config::enabledPlugins).toStringList();
+
+ QString pluginID = item->data(0, Qt::UserRole).toString();
+ bool checked = (item->checkState(0) == Qt::Checked);
+ bool toggled = checked ^ enabledPlugins.contains(pluginID);
+ if (!toggled) {
+ return;
+ }
+
+ if (checked) {
+ enabledPlugins.append(pluginID);
+ } else {
+ enabledPlugins.removeAll(pluginID);
+ }
+
+ conf()->set(Config::enabledPlugins, enabledPlugins);
+ qDebug() << "Enabled plugins: " << enabledPlugins;
+
+ if (toggled) {
+ conf()->set(Config::restartRequired, true);
+ ui->frameRestart->show();
+ }
+}
+
+void PluginWidget::setupItem(QTreeWidgetItem *item, Plugin *plugin) {
+ item->setIcon(0, icons()->icon(plugin->icon()));
+ item->setText(0, plugin->displayName());
+ item->setData(0, Qt::UserRole, plugin->id());
+
+ if (!plugin->implicitEnable()) {
+ m_checkable.append(plugin->id());
+ item->setCheckState(0, Qt::Unchecked);
+ }
+
+ // if (plugin->requiresWebsocket() && conf()->get(Config::disableWebsocket).toBool()) {
+ // item->setDisabled(true);
+ // item->setToolTip(0, "This plugin requires the websocket connection to be enabled. Go to Network -> Websocket.");
+ //
+ // if (!plugin->implicitEnable()) {
+ // item->setCheckState(0, Qt::Unchecked);
+ // }
+ // }
+}
+
+void PluginWidget::deselectAll() {
+ this->selectTreeItems(ui->plugins->invisibleRootItem(), false);
+}
+
+void PluginWidget::selectAll() {
+ this->selectTreeItems(ui->plugins->invisibleRootItem(), true);
+}
+
+void PluginWidget::configurePlugin() {
+ if (!m_selectedPlugin) {
+ return;
+ }
+ m_selectedPlugin->configDialog(this)->exec();
+ emit pluginConfigured(m_selectedPlugin->id());
+}
+
+void PluginWidget::selectTreeItems(QTreeWidgetItem *item, bool select) {
+ if (!item) return;
+
+ // Truly demented Qt API
+ if (m_checkable.contains(item->data(0, Qt::UserRole).toString())) {
+ item->setCheckState(0, select ? Qt::Checked : Qt::Unchecked);
+ this->pluginToggled(item, 0);
+ }
+
+ for (int i = 0; i < item->childCount(); ++i)
+ {
+ selectTreeItems(item->child(i), select);
+ }
+}
+
+PluginWidget::~PluginWidget() = default;
\ No newline at end of file
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef PLUGINWIDGET_H
+#define PLUGINWIDGET_H
+
+#include <QWidget>
+#include <QTreeWidgetItem>
+
+#include "plugins/Plugin.h"
+
+namespace Ui {
+ class PluginWidget;
+}
+
+class PluginWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit PluginWidget(QWidget *parent = nullptr);
+ ~PluginWidget();
+
+private slots:
+ void pluginSelected(QTreeWidgetItem* item, int column);
+ void pluginToggled(QTreeWidgetItem* item, int column);
+ void deselectAll();
+ void selectAll();
+ void selectTreeItems(QTreeWidgetItem *item, bool select);
+ void configurePlugin();
+
+signals:
+ void pluginConfigured(const QString &id);
+
+private:
+ void setupItem(QTreeWidgetItem *item, Plugin *plugin);
+
+ QScopedPointer<Ui::PluginWidget> ui;
+ QMap<QString, QTreeWidgetItem*> m_topLevelPlugins;
+ QList<QString> m_checkable;
+ Plugin* m_selectedPlugin = nullptr;
+};
+
+
+#endif //PLUGINWIDGET_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PluginWidget</class>
+ <widget class="QWidget" name="PluginWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>463</width>
+ <height>390</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTreeWidget" name="plugins">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <column>
+ <property name="text">
+ <string>Plugin</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="btn_deselectAll">
+ <property name="text">
+ <string>Deselect all</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btn_selectAll">
+ <property name="text">
+ <string>Select all</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btn_configure">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="InfoFrame" name="frameRestart">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>InfoFrame</class>
+ <extends>QFrame</extends>
+ <header>components.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null
+// 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
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef PAGEPLUGINS_H
+#define PAGEPLUGINS_H
+
+#include <QWizardPage>
+
+namespace Ui {
+ class PagePlugins;
+}
+
+class PagePlugins : public QWizardPage
+{
+ Q_OBJECT
+
+public:
+ explicit PagePlugins(QWidget *parent = nullptr);
+ bool validatePage() override;
+ int nextId() const override;
+ bool isComplete() const override;
+
+private:
+ Ui::PagePlugins *ui;
+};
+
+#endif //PAGEPLUGINS_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PagePlugins</class>
+ <widget class="QWizardPage" name="PagePlugins">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>505</width>
+ <height>431</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>WizardPage</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string><html><head/><body><p><span style=" font-weight:700;">Do you want to enable any plugins?</span></p></body></html></string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="PluginWidget" name="pluginsWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>PluginWidget</class>
+ <extends>QWidget</extends>
+ <header>widgets/PluginWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
#include "WalletWizard.h"
#include "PageMenu.h"
#include "PageOpenWallet.h"
+#include "PagePlugins.h"
#include "PageWalletFile.h"
#include "PageNetwork.h"
#include "PageWalletSeed.h"
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);
Page_SetRestoreHeight,
Page_HardwareDevice,
Page_NetworkProxy,
- Page_NetworkWebsocket
+ Page_NetworkWebsocket,
+ Page_Plugins
};
explicit WalletWizard(QWidget *parent = nullptr);