diff --git a/src/mainApp.py b/src/mainApp.py index e0b9419..8609be3 100644 --- a/src/mainApp.py +++ b/src/mainApp.py @@ -19,7 +19,7 @@ from constants import user_dir, SECONDS_IN_2_MONTHS from qt.dlg_configureRPCservers import ConfigureRPCservers_dlg from qt.dlg_signmessage import SignMessage_dlg - +from qt.dlg_configureExplorer import ConfigureExplorerServers_dlg class ServiceExit(Exception): """ @@ -99,6 +99,9 @@ def initUI(self, imgDir): self.rpcConfMenu = QAction(self.pivx_icon, 'RPC Servers config...', self) self.rpcConfMenu.triggered.connect(self.onEditRPCServer) confMenu.addAction(self.rpcConfMenu) + self.explorerConfMenu = QAction(self.pivx_icon, 'Explorer Servers config...', self) + self.explorerConfMenu.triggered.connect(self.onEditExplorerServer) + confMenu.addAction(self.explorerConfMenu) toolsMenu = mainMenu.addMenu('Tools') self.signVerifyAction = QAction('Sign/Verify message', self) self.signVerifyAction.triggered.connect(self.onSignVerifyMessage) @@ -141,8 +144,14 @@ def onEditRPCServer(self): if ui.exec(): printDbg("Configuring RPC Servers...") + def onEditExplorerServer(self): + # Create Dialog + ui = ConfigureExplorerServers_dlg(self) + if ui.exec(): + printDbg("Configuring Explorer Servers...") + def onSignVerifyMessage(self): # Create Dialog ui = SignMessage_dlg(self.mainWindow) if ui.exec(): - printDbg("Sign/Verify message...") + printDbg("Sign/Verify message...") \ No newline at end of file diff --git a/src/mainWindow.py b/src/mainWindow.py index 727b793..ecb9866 100644 --- a/src/mainWindow.py +++ b/src/mainWindow.py @@ -13,7 +13,7 @@ from PyQt5.QtCore import pyqtSignal, Qt, QThread from PyQt5.QtGui import QPixmap, QColor, QPalette, QTextCursor, QFont, QIcon from PyQt5.QtWidgets import QWidget, QPushButton, QHBoxLayout, QGroupBox, QVBoxLayout, \ - QFileDialog, QTextEdit, QTabWidget, QLabel, QSplitter + QFileDialog, QTextEdit, QTabWidget, QLabel, QSplitter, QAction, QMenuBar from apiClient import ApiClient from constants import starting_height, DefaultCache, wqueue @@ -38,6 +38,9 @@ class MainWindow(QWidget): # signal: RPC list has been reloaded (emitted by updateRPClist) sig_RPClistReloaded = pyqtSignal() + # signal: Explorer list has been reloaded (emitted by updateExplorerList) + sig_ExplorerListReloaded = pyqtSignal() + # signal: UTXO list loading percent (emitted by load_utxos_thread in tabRewards) sig_UTXOsLoading = pyqtSignal(int) @@ -49,6 +52,10 @@ def __init__(self, parent, imgDir): self.runInThread = ThreadFuns.runInThread self.lock = threading.Lock() + # Set default value for selectedExplorer_index if it does not exist + if 'selectedExplorer_index' not in self.parent.cache: + self.parent.cache['selectedExplorer_index'] = 0 + # -- Create clients and statuses self.hwStatus = 0 self.hwModel = 0 @@ -61,6 +68,12 @@ def __init__(self, parent, imgDir): # Changes when an RPC client is connected (affecting API client) self.isTestnetRPC = self.parent.cache['isTestnetRPC'] + # -- Default explorer servers list + self.explorerServersList = [ + {"id": 1, "url": "https://explorer.duddino.com/", "isCustom": False}, + {"id": 2, "url": "https://testnet.duddino.com/", "isCustom": False} + ] + # -- Load icons & images self.loadIcons() # -- Create main layout @@ -79,7 +92,7 @@ def __init__(self, parent, imgDir): self.hwdevice = HWdevice(self) # -- init Api Client - self.apiClient = ApiClient(self.isTestnetRPC) + self.apiClient = ApiClient(self) # Pass 'self' as main_wnd reference # -- Create Queue to redirect stdout self.queue = wqueue @@ -130,6 +143,9 @@ def __init__(self, parent, imgDir): self.mnode_to_change = None printOK("Hello! Welcome to " + parent.title) + # Load Explorer Servers list + self.updateExplorerList() + def append_to_console(self, text): self.consoleArea.moveCursor(QTextCursor.End) self.consoleArea.insertHtml(text) @@ -175,6 +191,9 @@ def getRPCserver(self): def getServerListIndex(self, server): return self.header.rpcClientsBox.findData(server) + + def getExplorerListIndex(self, server): + return self.header.explorerClientsBox.findData(server) def initConsole(self): self.console = QGroupBox() @@ -425,6 +444,50 @@ def updateRPClist(self): # reload servers in configure dialog self.sig_RPClistReloaded.emit() + def updateExplorerList(self): + # Clear old stuff + self.header.explorerClientsBox.clear() + public_servers = self.parent.db.getExplorerServers(custom=False) + custom_servers = self.parent.db.getExplorerServers(custom=True) + self.explorerServersList = public_servers + custom_servers + # Add public servers (italics) + italicsFont = QFont("Times", italic=True) + for s in public_servers: + url = s["url"] + self.header.explorerClientsBox.addItem(url, s) + self.header.explorerClientsBox.setItemData(self.getExplorerListIndex(s), italicsFont, Qt.FontRole) + # Add custom servers + for s in custom_servers: + url = s["url"] + self.header.explorerClientsBox.addItem(url, s) + # reset index + if self.parent.cache['selectedExplorer_index'] >= self.header.explorerClientsBox.count(): + # (if manually removed from the config files) replace default index + self.parent.cache['selectedExplorer_index'] = persistCacheSetting('cache_Explorerindex', DefaultCache["selectedExplorer_index"]) + + self.header.explorerClientsBox.setCurrentIndex(self.parent.cache['selectedExplorer_index']) + # reload servers in configure dialog + self.sig_ExplorerListReloaded.emit() + + def getExplorerURL(self, network): + for server in self.explorerServersList: + if (network == 'mainnet' and 'mainnet' in server['url']) or (network == 'testnet' and 'testnet' in server['url']): + return server['url'] + # Fallback if no matching server found + return self.explorerServersList[0]['url'] + + def saveExplorerServers(self): + self.parent.cache['explorerServersList'] = self.explorerServersList + saveCacheSettings(self.parent.cache) + self.updateExplorerList() + + def loadExplorerServers(self): + if 'explorerServersList' in self.parent.cache: + self.explorerServersList = self.parent.cache['explorerServersList'] + else: + self.explorerServersList = [] + self.updateExplorerList() + def updateRPCstatus(self, ctrl, fDebug=False): rpc_index, rpc_protocol, rpc_host, rpc_user, rpc_password = self.getRPCserver() if fDebug: diff --git a/src/qt/dlg_configureExplorer.py b/src/qt/dlg_configureExplorer.py new file mode 100644 index 0000000..b269189 --- /dev/null +++ b/src/qt/dlg_configureExplorer.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright (c) 2024 Liquid369 (https://github.com/liquid369/) +# Distributed under the MIT software license, see the accompanying +# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php. + +from PyQt5.QtWidgets import QDialog, QHBoxLayout, QVBoxLayout, QLabel, \ + QListWidget, QFrame, QFormLayout, QComboBox, QLineEdit, QListWidgetItem, \ + QWidget, QPushButton, QMessageBox + +from misc import myPopUp + +class ConfigureExplorerServers_dlg(QDialog): + def __init__(self, main_wnd): + QDialog.__init__(self, parent=main_wnd) + self.main_wnd = main_wnd + self.setWindowTitle('Explorer Servers Configuration') + self.changing_index = None + self.initUI() + self.loadServers() + self.main_wnd.mainWindow.sig_ExplorerListReloaded.connect(self.loadServers) + + def clearEditFrame(self): + self.ui.url_edt.clear() + + def initUI(self): + self.ui = Ui_ConfigureExplorerServersDlg() + self.ui.setupUi(self) + + def insert_server_list(self, server): + id = server['id'] + index = self.main_wnd.mainWindow.getExplorerListIndex(server) + server_line = QWidget() + server_row = QHBoxLayout() + server_text = server['url'] + if not server['isCustom']: + server_text = "%s" % server_text + server_row.addWidget(QLabel(server_text)) + server_row.addStretch(1) + # -- Edit button + editBtn = QPushButton() + editBtn.setIcon(self.main_wnd.mainWindow.editMN_icon) + editBtn.setToolTip("Edit server configuration") + if not server['isCustom']: + editBtn.setDisabled(True) + editBtn.setToolTip('Default servers are not editable') + editBtn.clicked.connect(lambda: self.onAddServer(index)) + server_row.addWidget(editBtn) + # -- Remove button + removeBtn = QPushButton() + removeBtn.setIcon(self.main_wnd.mainWindow.removeMN_icon) + removeBtn.setToolTip("Remove server configuration") + if not server['isCustom']: + removeBtn.setDisabled(True) + removeBtn.setToolTip('Cannot remove default servers') + removeBtn.clicked.connect(lambda: self.onRemoveServer(index)) + server_row.addWidget(removeBtn) + # -- + server_line.setLayout(server_row) + self.serverItems[id] = QListWidgetItem() + self.serverItems[id].setSizeHint(server_line.sizeHint()) + self.ui.serversBox.addItem(self.serverItems[id]) + self.ui.serversBox.setItemWidget(self.serverItems[id], server_line) + + def loadServers(self): + # Clear serversBox + self.ui.serversBox.clear() + # Fill serversBox + self.serverItems = {} + for server in self.main_wnd.mainWindow.explorerServersList: + self.insert_server_list(server) + + def loadEditFrame(self, index): + server = self.main_wnd.mainWindow.explorerServersList[index] + self.ui.url_edt.setText(server['url']) + + def onAddServer(self, index=None): + # Save current index (None for new entry) + self.changing_index = index + # Hide 'Add' and 'Close' buttons and disable serversBox + self.ui.addServer_btn.hide() + self.ui.close_btn.hide() + self.ui.serversBox.setEnabled(False) + # Show edit-frame + self.ui.editFrame.setHidden(False) + # If we are adding a new server, clear edit-frame + if index is None: + self.clearEditFrame() + # else pre-load data + else: + self.loadEditFrame(index) + + def onCancel(self): + # Show 'Add' and 'Close' buttons and enable serversBox + self.ui.addServer_btn.show() + self.ui.close_btn.show() + self.ui.serversBox.setEnabled(True) + # Hide edit-frame + self.ui.editFrame.setHidden(True) + # Clear edit-frame + self.clearEditFrame() + + def onClose(self): + # close dialog + self.close() + + def onRemoveServer(self, index): + mess = "Are you sure you want to remove server with index %d (%s) from list?" % ( + index, self.main_wnd.mainWindow.explorerServersList[index].get('url')) + ans = myPopUp(self, QMessageBox.Question, 'PET4L - remove server', mess) + if ans == QMessageBox.Yes: + # Remove entry from database + id = self.main_wnd.mainWindow.explorerServersList[index].get('id') + self.main_wnd.db.removeExplorerServer(id) + + def onSave(self): + # Get new config data + url = self.ui.url_edt.text() + # Check malformed URL + if url: + if self.changing_index is None: + # Save new entry in DB. + self.main_wnd.db.addExplorerServer(url) + else: + # Edit existing entry in DB. + id = self.main_wnd.mainWindow.explorerServersList[self.changing_index].get('id') + self.main_wnd.db.editExplorerServer(url, id) + + # call onCancel + self.onCancel() + + +class Ui_ConfigureExplorerServersDlg(object): + def setupUi(self, ConfigureExplorerServersDlg): + ConfigureExplorerServersDlg.setModal(True) + # -- Layout + self.layout = QVBoxLayout(ConfigureExplorerServersDlg) + self.layout.setSpacing(10) + # -- Servers List + self.serversBox = QListWidget() + self.layout.addWidget(self.serversBox) + # -- 'Add Server' button + self.addServer_btn = QPushButton("Add Explorer Server") + self.layout.addWidget(self.addServer_btn) + # -- 'Close' button + hBox = QHBoxLayout() + hBox.addStretch(1) + self.close_btn = QPushButton("Close") + hBox.addWidget(self.close_btn) + self.layout.addLayout(hBox) + # -- Edit section + self.editFrame = QFrame() + frameLayout = QFormLayout() + frameLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) + frameLayout.setContentsMargins(5, 10, 5, 5) + frameLayout.setSpacing(7) + self.url_edt = QLineEdit() + frameLayout.addRow(QLabel("Explorer URL"), self.url_edt) + hBox2 = QHBoxLayout() + self.cancel_btn = QPushButton("Cancel") + self.save_btn = QPushButton("Save") + hBox2.addWidget(self.cancel_btn) + hBox2.addWidget(self.save_btn) + frameLayout.addRow(hBox2) + self.editFrame.setLayout(frameLayout) + self.layout.addWidget(self.editFrame) + self.editFrame.setHidden(True) + ConfigureExplorerServersDlg.setMinimumWidth(500) + ConfigureExplorerServersDlg.setMinimumHeight(500) + # Connect main buttons + self.addServer_btn.clicked.connect(lambda: ConfigureExplorerServersDlg.onAddServer()) + self.close_btn.clicked.connect(lambda: ConfigureExplorerServersDlg.onClose()) + self.cancel_btn.clicked.connect(lambda: ConfigureExplorerServersDlg.onCancel()) + self.save_btn.clicked.connect(lambda: ConfigureExplorerServersDlg.onSave()) diff --git a/src/qt/guiHeader.py b/src/qt/guiHeader.py index fdaf92e..e54c5aa 100644 --- a/src/qt/guiHeader.py +++ b/src/qt/guiHeader.py @@ -68,6 +68,15 @@ def __init__(self, caller, *args, **kwargs): self.hwLed.setToolTip("status: %s" % caller.hwStatusMess) self.hwLed.setPixmap(caller.ledGrayH_icon) self.centralBox.addWidget(self.hwLed, 1, 3) + # -- 1c) Select & Check Explorer + label4 = QLabel("Explorer") + self.centralBox.addWidget(label4, 2, 0) + self.explorerClientsBox = QComboBox() + self.explorerClientsBox.setToolTip("Select Explorer Server") + #self.explorerClientsBox.setItemDelegate(AlignDelegate()) + self.centralBox.addWidget(self.explorerClientsBox, 2, 1) + layout.addLayout(self.centralBox) layout.addStretch(1) + self.setLayout(layout)