Browse Source

qml: add server connect wizard

patch-4
Sander van Grieken 3 years ago
parent
commit
d13f5d0da0
  1. 40
      electrum/gui/qml/__init__.py
  2. 2
      electrum/gui/qml/components/History.qml
  3. 223
      electrum/gui/qml/components/ServerConnectWizard.qml
  4. 0
      electrum/gui/qml/components/WalletMainView.qml
  5. 2
      electrum/gui/qml/components/WizardComponents.qml
  6. 35
      electrum/gui/qml/components/main.qml
  7. 47
      electrum/gui/qml/qeconfig.py
  8. 26
      electrum/gui/qml/qenetwork.py

40
electrum/gui/qml/__init__.py

@ -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
electrum/gui/qml/components/History.qml

@ -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

@ -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
}
}
}
}
}
}

0
electrum/gui/qml/components/landing.qml → electrum/gui/qml/components/WalletMainView.qml

2
electrum/gui/qml/components/WizardComponents.qml

@ -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
}

35
electrum/gui/qml/components/main.qml

@ -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

@ -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()

26
electrum/gui/qml/qenetwork.py

@ -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()

Loading…
Cancel
Save