qml: add server connect wizard
This commit is contained in:
@@ -15,7 +15,7 @@ try:
|
||||
except Exception:
|
||||
sys.exit("Error: Could not import PyQt5.QtQml on Linux systems, you may try 'sudo apt-get install python3-pyqt5.qtquick'")
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QLocale, QTimer
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QLocale, QTimer, qInstallMessageHandler
|
||||
from PyQt5.QtGui import QGuiApplication
|
||||
from PyQt5.QtQml import qmlRegisterType, QQmlComponent, QQmlApplicationEngine
|
||||
from PyQt5.QtQuick import QQuickView
|
||||
@@ -36,39 +36,36 @@ if TYPE_CHECKING:
|
||||
from electrum.simple_config import SimpleConfig
|
||||
from electrum.plugin import Plugins
|
||||
|
||||
from .qeconfig import QEConfig
|
||||
from .qedaemon import QEDaemon, QEWalletListModel
|
||||
from .qenetwork import QENetwork
|
||||
from .qewallet import QEWallet
|
||||
from .qeqr import QEQR
|
||||
|
||||
class ElectrumQmlApplication(QGuiApplication):
|
||||
def __init__(self, args, daemon):
|
||||
def __init__(self, args, config, daemon):
|
||||
super().__init__(args)
|
||||
|
||||
self.logger = get_logger(__name__)
|
||||
self.logger = get_logger(__name__ + '.engine')
|
||||
|
||||
qmlRegisterType(QEWalletListModel, 'Electrum', 1, 0, 'WalletListModel')
|
||||
qmlRegisterType(QEWallet, 'Electrum', 1, 0, 'Wallet')
|
||||
qmlRegisterType(QEWalletListModel, 'org.electrum', 1, 0, 'WalletListModel')
|
||||
qmlRegisterType(QEWallet, 'org.electrum', 1, 0, 'Wallet')
|
||||
|
||||
self.engine = QQmlApplicationEngine(parent=self)
|
||||
self.engine.addImportPath('./qml')
|
||||
|
||||
self.logger.info('importPathList() :')
|
||||
for i in self.engine.importPathList():
|
||||
self.logger.info(i)
|
||||
|
||||
self.logger.info('pluginPathList() :')
|
||||
for i in self.engine.pluginPathList():
|
||||
self.logger.info(i)
|
||||
|
||||
self.context = self.engine.rootContext()
|
||||
self._singletons['config'] = QEConfig(config)
|
||||
self._singletons['network'] = QENetwork(daemon.network)
|
||||
self._singletons['daemon'] = QEDaemon(daemon)
|
||||
self._singletons['qr'] = QEQR()
|
||||
self.context.setContextProperty('Config', self._singletons['config'])
|
||||
self.context.setContextProperty('Network', self._singletons['network'])
|
||||
self.context.setContextProperty('Daemon', self._singletons['daemon'])
|
||||
self.context.setContextProperty('QR', self._singletons['qr'])
|
||||
|
||||
qInstallMessageHandler(self.message_handler)
|
||||
|
||||
# get notified whether root QML document loads or not
|
||||
self.engine.objectCreated.connect(self.objectCreated)
|
||||
|
||||
@@ -82,14 +79,17 @@ class ElectrumQmlApplication(QGuiApplication):
|
||||
self._valid = False
|
||||
self.engine.objectCreated.disconnect(self.objectCreated)
|
||||
|
||||
def message_handler(self, line, funct, file):
|
||||
self.logger.warning(file)
|
||||
|
||||
class ElectrumGui(Logger):
|
||||
|
||||
@profiler
|
||||
def __init__(self, config: 'SimpleConfig', daemon: 'Daemon', plugins: 'Plugins'):
|
||||
set_language(config.get('language', self.get_default_language()))
|
||||
Logger.__init__(self)
|
||||
os.environ['QML_IMPORT_TRACE'] = '1'
|
||||
os.environ['QT_DEBUG_PLUGINS'] = '1'
|
||||
#os.environ['QML_IMPORT_TRACE'] = '1'
|
||||
#os.environ['QT_DEBUG_PLUGINS'] = '1'
|
||||
|
||||
self.logger.info(f"Qml GUI starting up... Qt={QtCore.QT_VERSION_STR}, PyQt={QtCore.PYQT_VERSION_STR}")
|
||||
self.logger.info("CWD=%s" % os.getcwd())
|
||||
@@ -112,7 +112,7 @@ class ElectrumGui(Logger):
|
||||
self.config = config
|
||||
self.daemon = daemon
|
||||
self.plugins = plugins
|
||||
self.app = ElectrumQmlApplication(sys.argv, self.daemon)
|
||||
self.app = ElectrumQmlApplication(sys.argv, self.config, self.daemon)
|
||||
# timer
|
||||
self.timer = QTimer(self.app)
|
||||
self.timer.setSingleShot(False)
|
||||
@@ -124,14 +124,6 @@ class ElectrumGui(Logger):
|
||||
self.app.engine.load('electrum/gui/qml/components/main.qml')
|
||||
|
||||
def close(self):
|
||||
# for window in self.windows:
|
||||
# window.close()
|
||||
# if self.network_dialog:
|
||||
# self.network_dialog.close()
|
||||
# if self.lightning_dialog:
|
||||
# self.lightning_dialog.close()
|
||||
# if self.watchtower_dialog:
|
||||
# self.watchtower_dialog.close()
|
||||
self.app.quit()
|
||||
|
||||
def main(self):
|
||||
|
||||
@@ -2,7 +2,7 @@ import QtQuick 2.6
|
||||
import QtQuick.Layouts 1.0
|
||||
import QtQuick.Controls 2.0
|
||||
|
||||
import Electrum 1.0
|
||||
import org.electrum 1.0
|
||||
|
||||
Pane {
|
||||
id: rootItem
|
||||
|
||||
223
electrum/gui/qml/components/ServerConnectWizard.qml
Normal file
223
electrum/gui/qml/components/ServerConnectWizard.qml
Normal file
@@ -0,0 +1,223 @@
|
||||
import QtQuick 2.6
|
||||
import QtQuick.Layouts 1.0
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.15
|
||||
|
||||
Wizard {
|
||||
id: serverconnectwizard
|
||||
|
||||
title: qsTr('How do you want to connect to a server?')
|
||||
|
||||
enter: null // disable transition
|
||||
|
||||
onAccepted: {
|
||||
var proxy = wizard_data['proxy']
|
||||
if (proxy && proxy['enabled'] == true) {
|
||||
Network.proxy = proxy
|
||||
} else {
|
||||
Network.proxy = {'enabled': false}
|
||||
}
|
||||
Config.autoConnect = wizard_data['autoconnect']
|
||||
if (!wizard_data['autoconnect']) {
|
||||
Network.server = wizard_data['server']
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
var start = _loadNextComponent(autoconnect)
|
||||
start.next.connect(function() {autoconnectDone()})
|
||||
}
|
||||
|
||||
function autoconnectDone() {
|
||||
var page = _loadNextComponent(proxyconfig, wizard_data)
|
||||
page.next.connect(function() {proxyconfigDone()})
|
||||
}
|
||||
|
||||
function proxyconfigDone() {
|
||||
var page = _loadNextComponent(serverconfig, wizard_data)
|
||||
}
|
||||
|
||||
property Component autoconnect: Component {
|
||||
WizardComponent {
|
||||
valid: true
|
||||
last: serverconnectgroup.checkedButton.connecttype === 'auto'
|
||||
|
||||
onAccept: {
|
||||
wizard_data['autoconnect'] = serverconnectgroup.checkedButton.connecttype === 'auto'
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Text {
|
||||
text: qsTr('Electrum communicates with remote servers to get information about your transactions and addresses. The servers all fulfill the same purpose only differing in hardware. In most cases you simply want to let Electrum pick one at random. However if you prefer feel free to select a server manually.')
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
color: Material.primaryTextColor
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: serverconnectgroup
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
ButtonGroup.group: serverconnectgroup
|
||||
property string connecttype: 'auto'
|
||||
text: qsTr('Auto connect')
|
||||
}
|
||||
RadioButton {
|
||||
ButtonGroup.group: serverconnectgroup
|
||||
property string connecttype: 'manual'
|
||||
checked: true
|
||||
text: qsTr('Select servers manually')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
property Component proxyconfig: Component {
|
||||
WizardComponent {
|
||||
valid: true
|
||||
last: false
|
||||
|
||||
onAccept: {
|
||||
var p = {}
|
||||
p['enabled'] = proxy_enabled.checked
|
||||
if (proxy_enabled.checked) {
|
||||
var type = proxytype.currentValue.toLowerCase()
|
||||
if (type == 'tor')
|
||||
type = 'socks5'
|
||||
p['mode'] = type
|
||||
p['host'] = address.text
|
||||
p['port'] = port.text
|
||||
p['user'] = username.text
|
||||
p['password'] = password.text
|
||||
}
|
||||
wizard_data['proxy'] = p
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Text {
|
||||
text: qsTr('Proxy settings')
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
color: Material.primaryTextColor
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: proxy_enabled
|
||||
text: qsTr('Enable Proxy')
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: proxytype
|
||||
enabled: proxy_enabled.checked
|
||||
model: ['TOR', 'SOCKS5', 'SOCKS4']
|
||||
onCurrentIndexChanged: {
|
||||
if (currentIndex == 0) {
|
||||
address.text = "127.0.0.1"
|
||||
port.text = "9050"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
columns: 4
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
text: qsTr("Address")
|
||||
enabled: address.enabled
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: address
|
||||
enabled: proxytype.enabled && proxytype.currentIndex > 0
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Port")
|
||||
enabled: port.enabled
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: port
|
||||
enabled: proxytype.enabled && proxytype.currentIndex > 0
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Username")
|
||||
enabled: username.enabled
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: username
|
||||
enabled: proxytype.enabled && proxytype.currentIndex > 0
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Password")
|
||||
enabled: password.enabled
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: password
|
||||
enabled: proxytype.enabled && proxytype.currentIndex > 0
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
property Component serverconfig: Component {
|
||||
WizardComponent {
|
||||
valid: true
|
||||
last: true
|
||||
|
||||
onAccept: {
|
||||
wizard_data['oneserver'] = !auto_server.checked
|
||||
wizard_data['server'] = address.text
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Text {
|
||||
text: qsTr('Server settings')
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
color: Material.primaryTextColor
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: auto_server
|
||||
text: qsTr('Select server automatically')
|
||||
checked: true
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
columns: 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
text: qsTr("Server")
|
||||
enabled: address.enabled
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: address
|
||||
enabled: !auto_server.checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@ Item {
|
||||
property Component walletname: Component {
|
||||
WizardComponent {
|
||||
valid: wallet_name.text.length > 0
|
||||
//property alias wallet_name: wallet_name.text
|
||||
|
||||
onAccept: {
|
||||
wizard_data['wallet_name'] = wallet_name.text
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.0
|
||||
import QtQuick.Controls.Material 2.0
|
||||
|
||||
@@ -10,6 +10,8 @@ ApplicationWindow
|
||||
{
|
||||
id: app
|
||||
visible: true
|
||||
|
||||
// dimensions ignored on android
|
||||
width: 480
|
||||
height: 800
|
||||
|
||||
@@ -55,7 +57,7 @@ ApplicationWindow
|
||||
Label {
|
||||
id: networkNameLabel
|
||||
text: Network.networkName
|
||||
color: Material.accentColor //'orange'
|
||||
color: Material.accentColor
|
||||
font.pointSize: 5
|
||||
}
|
||||
}
|
||||
@@ -85,7 +87,7 @@ ApplicationWindow
|
||||
id: mainStackView
|
||||
anchors.fill: parent
|
||||
|
||||
initialItem: Qt.resolvedUrl('landing.qml')
|
||||
initialItem: Qt.resolvedUrl('WalletMainView.qml')
|
||||
}
|
||||
|
||||
Timer {
|
||||
@@ -124,6 +126,22 @@ ApplicationWindow
|
||||
}
|
||||
}
|
||||
|
||||
property alias serverConnectWizard: _serverConnectWizard
|
||||
Component {
|
||||
id: _serverConnectWizard
|
||||
ServerConnectWizard {
|
||||
parent: Overlay.overlay
|
||||
x: 12
|
||||
y: 12
|
||||
width: parent.width - 24
|
||||
height: parent.height - 24
|
||||
|
||||
Overlay.modal: Rectangle {
|
||||
color: "#aa000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property alias messageDialog: _messageDialog
|
||||
Component {
|
||||
id: _messageDialog
|
||||
@@ -144,8 +162,17 @@ ApplicationWindow
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Daemon.load_wallet()
|
||||
//Daemon.load_wallet()
|
||||
splashTimer.start()
|
||||
if (!Config.autoConnectDefined) {
|
||||
var dialog = serverConnectWizard.createObject(app)
|
||||
// without completed serverConnectWizard we can't start
|
||||
dialog.rejected.connect(function() {
|
||||
app.visible = false
|
||||
Qt.callLater(Qt.quit)
|
||||
})
|
||||
dialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
onClosing: {
|
||||
|
||||
47
electrum/gui/qml/qeconfig.py
Normal file
47
electrum/gui/qml/qeconfig.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||
|
||||
from electrum.logging import get_logger
|
||||
|
||||
class QEConfig(QObject):
|
||||
def __init__(self, config, parent=None):
|
||||
super().__init__(parent)
|
||||
self.config = config
|
||||
|
||||
_logger = get_logger(__name__)
|
||||
|
||||
autoConnectChanged = pyqtSignal()
|
||||
serverStringChanged = pyqtSignal()
|
||||
manualServerChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(bool, notify=autoConnectChanged)
|
||||
def autoConnect(self):
|
||||
return self.config.get('auto_connect')
|
||||
|
||||
@autoConnect.setter
|
||||
def autoConnect(self, auto_connect):
|
||||
self.config.set_key('auto_connect', auto_connect, True)
|
||||
self.autoConnectChanged.emit()
|
||||
|
||||
# auto_connect is actually a tri-state, expose the undefined case
|
||||
@pyqtProperty(bool, notify=autoConnectChanged)
|
||||
def autoConnectDefined(self):
|
||||
return self.config.get('auto_connect') is not None
|
||||
|
||||
@pyqtProperty('QString', notify=serverStringChanged)
|
||||
def serverString(self):
|
||||
return self.config.get('server')
|
||||
|
||||
@serverString.setter
|
||||
def serverString(self, server):
|
||||
self.config.set_key('server', server, True)
|
||||
self.serverStringChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, notify=manualServerChanged)
|
||||
def manualServer(self):
|
||||
return self.config.get('oneserver')
|
||||
|
||||
@manualServer.setter
|
||||
def manualServer(self, oneserver):
|
||||
self.config.set_key('oneserver', oneserver, True)
|
||||
self.manualServerChanged.emit()
|
||||
|
||||
@@ -3,6 +3,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||
from electrum.util import register_callback
|
||||
from electrum.logging import get_logger
|
||||
from electrum import constants
|
||||
from electrum.interface import ServerAddr
|
||||
|
||||
class QENetwork(QObject):
|
||||
def __init__(self, network, parent=None):
|
||||
@@ -20,6 +21,7 @@ class QENetwork(QObject):
|
||||
blockchainUpdated = pyqtSignal()
|
||||
defaultServerChanged = pyqtSignal()
|
||||
proxySet = pyqtSignal()
|
||||
proxyChanged = pyqtSignal()
|
||||
statusUpdated = pyqtSignal()
|
||||
|
||||
dataChanged = pyqtSignal() # dummy to silence warnings
|
||||
@@ -64,6 +66,17 @@ class QENetwork(QObject):
|
||||
def server(self):
|
||||
return self._server
|
||||
|
||||
@server.setter
|
||||
def server(self, server):
|
||||
net_params = self.network.get_parameters()
|
||||
try:
|
||||
server = ServerAddr.from_str_with_inference(server)
|
||||
if not server: raise Exception("failed to parse")
|
||||
except Exception:
|
||||
return
|
||||
net_params = net_params._replace(server=server)
|
||||
self.network.run_from_another_thread(self.network.set_parameters(net_params))
|
||||
|
||||
@pyqtProperty('QString',notify=statusUpdated)
|
||||
def status(self):
|
||||
return self._status
|
||||
@@ -76,3 +89,16 @@ class QENetwork(QObject):
|
||||
def networkName(self):
|
||||
return constants.net.__name__.replace('Bitcoin','')
|
||||
|
||||
@pyqtProperty('QVariantMap', notify=proxyChanged)
|
||||
def proxy(self):
|
||||
net_params = self.network.get_parameters()
|
||||
return net_params
|
||||
|
||||
@proxy.setter
|
||||
def proxy(self, proxy_settings):
|
||||
net_params = self.network.get_parameters()
|
||||
if not proxy_settings['enabled']:
|
||||
proxy_settings = None
|
||||
net_params = net_params._replace(proxy=proxy_settings)
|
||||
self.network.run_from_another_thread(self.network.set_parameters(net_params))
|
||||
self.proxyChanged.emit()
|
||||
|
||||
Reference in New Issue
Block a user