qml: initial plugin support, with labelsync mostly implemented
This commit is contained in:
@@ -63,7 +63,7 @@ class ElectrumGui(Logger):
|
|||||||
|
|
||||||
self.gui_thread = threading.current_thread()
|
self.gui_thread = threading.current_thread()
|
||||||
self.plugins = plugins
|
self.plugins = plugins
|
||||||
self.app = ElectrumQmlApplication(sys.argv, config, daemon)
|
self.app = ElectrumQmlApplication(sys.argv, config, daemon, plugins)
|
||||||
# timer
|
# timer
|
||||||
self.timer = QTimer(self.app)
|
self.timer = QTimer(self.app)
|
||||||
self.timer.setSingleShot(False)
|
self.timer.setSingleShot(False)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import QtQuick 2.6
|
import QtQuick 2.6
|
||||||
import QtQuick.Layouts 1.0
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.0
|
import QtQuick.Controls 2.3
|
||||||
import QtQuick.Controls.Material 2.0
|
import QtQuick.Controls.Material 2.0
|
||||||
|
|
||||||
import org.electrum 1.0
|
import org.electrum 1.0
|
||||||
@@ -12,175 +12,212 @@ Pane {
|
|||||||
|
|
||||||
property string title: qsTr("Preferences")
|
property string title: qsTr("Preferences")
|
||||||
|
|
||||||
|
padding: 0
|
||||||
|
|
||||||
property var _baseunits: ['BTC','mBTC','bits','sat']
|
property var _baseunits: ['BTC','mBTC','bits','sat']
|
||||||
|
|
||||||
Flickable {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
contentHeight: rootLayout.height
|
|
||||||
interactive: height < contentHeight
|
|
||||||
|
|
||||||
GridLayout {
|
TabBar {
|
||||||
id: rootLayout
|
id: tabbar
|
||||||
columns: 2
|
Layout.fillWidth: true
|
||||||
width: parent.width
|
currentIndex: swipeview.currentIndex
|
||||||
|
TabButton {
|
||||||
Label {
|
text: qsTr('Preferences')
|
||||||
text: qsTr('Language')
|
font.pixelSize: constants.fontSizeLarge
|
||||||
}
|
}
|
||||||
|
TabButton {
|
||||||
ElComboBox {
|
text: qsTr('Plugins')
|
||||||
id: language
|
font.pixelSize: constants.fontSizeLarge
|
||||||
enabled: false
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Label {
|
SwipeView {
|
||||||
text: qsTr('Base unit')
|
id: swipeview
|
||||||
}
|
|
||||||
|
|
||||||
ElComboBox {
|
Layout.fillHeight: true
|
||||||
id: baseUnit
|
Layout.fillWidth: true
|
||||||
model: _baseunits
|
currentIndex: tabbar.currentIndex
|
||||||
onCurrentValueChanged: {
|
|
||||||
if (activeFocus)
|
|
||||||
Config.baseUnit = currentValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Switch {
|
Flickable {
|
||||||
id: thousands
|
contentHeight: prefsPane.height
|
||||||
Layout.columnSpan: 2
|
interactive: height < contentHeight
|
||||||
text: qsTr('Add thousands separators to bitcoin amounts')
|
clip: true
|
||||||
onCheckedChanged: {
|
|
||||||
if (activeFocus)
|
|
||||||
Config.thousandsSeparator = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Switch {
|
Pane {
|
||||||
id: checkSoftware
|
id: prefsPane
|
||||||
Layout.columnSpan: 2
|
GridLayout {
|
||||||
text: qsTr('Automatically check for software updates')
|
columns: 2
|
||||||
enabled: false
|
width: parent.width
|
||||||
}
|
|
||||||
|
|
||||||
Switch {
|
Label {
|
||||||
id: fiatEnable
|
text: qsTr('Language')
|
||||||
text: qsTr('Fiat Currency')
|
}
|
||||||
onCheckedChanged: {
|
|
||||||
if (activeFocus)
|
|
||||||
Daemon.fx.enabled = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ElComboBox {
|
ElComboBox {
|
||||||
id: currencies
|
id: language
|
||||||
model: Daemon.fx.currencies
|
enabled: false
|
||||||
enabled: Daemon.fx.enabled
|
}
|
||||||
onCurrentValueChanged: {
|
|
||||||
if (activeFocus)
|
|
||||||
Daemon.fx.fiatCurrency = currentValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Switch {
|
Label {
|
||||||
id: historicRates
|
text: qsTr('Base unit')
|
||||||
text: qsTr('Historic rates')
|
}
|
||||||
enabled: Daemon.fx.enabled
|
|
||||||
Layout.columnSpan: 2
|
|
||||||
onCheckedChanged: {
|
|
||||||
if (activeFocus)
|
|
||||||
Daemon.fx.historicRates = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
ElComboBox {
|
||||||
text: qsTr('Source')
|
id: baseUnit
|
||||||
enabled: Daemon.fx.enabled
|
model: _baseunits
|
||||||
}
|
onCurrentValueChanged: {
|
||||||
|
if (activeFocus)
|
||||||
|
Config.baseUnit = currentValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ElComboBox {
|
Switch {
|
||||||
id: rateSources
|
id: thousands
|
||||||
enabled: Daemon.fx.enabled
|
Layout.columnSpan: 2
|
||||||
model: Daemon.fx.rateSources
|
text: qsTr('Add thousands separators to bitcoin amounts')
|
||||||
onModelChanged: {
|
onCheckedChanged: {
|
||||||
currentIndex = rateSources.indexOfValue(Daemon.fx.rateSource)
|
if (activeFocus)
|
||||||
}
|
Config.thousandsSeparator = checked
|
||||||
onCurrentValueChanged: {
|
}
|
||||||
if (activeFocus)
|
}
|
||||||
Daemon.fx.rateSource = currentValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
id: spendUnconfirmed
|
id: checkSoftware
|
||||||
text: qsTr('Spend unconfirmed')
|
Layout.columnSpan: 2
|
||||||
Layout.columnSpan: 2
|
text: qsTr('Automatically check for software updates')
|
||||||
onCheckedChanged: {
|
enabled: false
|
||||||
if (activeFocus)
|
}
|
||||||
Config.spendUnconfirmed = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
Switch {
|
||||||
text: qsTr('PIN')
|
id: fiatEnable
|
||||||
}
|
text: qsTr('Fiat Currency')
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (activeFocus)
|
||||||
|
Daemon.fx.enabled = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
ElComboBox {
|
||||||
Label {
|
id: currencies
|
||||||
text: Config.pinCode == '' ? qsTr('Off'): qsTr('On')
|
model: Daemon.fx.currencies
|
||||||
color: Material.accentColor
|
enabled: Daemon.fx.enabled
|
||||||
Layout.rightMargin: constants.paddingMedium
|
onCurrentValueChanged: {
|
||||||
}
|
if (activeFocus)
|
||||||
Button {
|
Daemon.fx.fiatCurrency = currentValue
|
||||||
text: qsTr('Enable')
|
}
|
||||||
visible: Config.pinCode == ''
|
}
|
||||||
onClicked: {
|
|
||||||
var dialog = pinSetup.createObject(preferences, {mode: 'enter'})
|
Switch {
|
||||||
dialog.accepted.connect(function() {
|
id: historicRates
|
||||||
Config.pinCode = dialog.pincode
|
text: qsTr('Historic rates')
|
||||||
dialog.close()
|
enabled: Daemon.fx.enabled
|
||||||
})
|
Layout.columnSpan: 2
|
||||||
dialog.open()
|
onCheckedChanged: {
|
||||||
}
|
if (activeFocus)
|
||||||
}
|
Daemon.fx.historicRates = checked
|
||||||
Button {
|
}
|
||||||
text: qsTr('Change')
|
}
|
||||||
visible: Config.pinCode != ''
|
|
||||||
onClicked: {
|
Label {
|
||||||
var dialog = pinSetup.createObject(preferences, {mode: 'change', pincode: Config.pinCode})
|
text: qsTr('Source')
|
||||||
dialog.accepted.connect(function() {
|
enabled: Daemon.fx.enabled
|
||||||
Config.pinCode = dialog.pincode
|
}
|
||||||
dialog.close()
|
|
||||||
})
|
ElComboBox {
|
||||||
dialog.open()
|
id: rateSources
|
||||||
}
|
enabled: Daemon.fx.enabled
|
||||||
}
|
model: Daemon.fx.rateSources
|
||||||
Button {
|
onModelChanged: {
|
||||||
text: qsTr('Remove')
|
currentIndex = rateSources.indexOfValue(Daemon.fx.rateSource)
|
||||||
visible: Config.pinCode != ''
|
}
|
||||||
onClicked: {
|
onCurrentValueChanged: {
|
||||||
Config.pinCode = ''
|
if (activeFocus)
|
||||||
|
Daemon.fx.rateSource = currentValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
id: spendUnconfirmed
|
||||||
|
text: qsTr('Spend unconfirmed')
|
||||||
|
Layout.columnSpan: 2
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (activeFocus)
|
||||||
|
Config.spendUnconfirmed = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('PIN')
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Label {
|
||||||
|
text: Config.pinCode == '' ? qsTr('Off'): qsTr('On')
|
||||||
|
color: Material.accentColor
|
||||||
|
Layout.rightMargin: constants.paddingMedium
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: qsTr('Enable')
|
||||||
|
visible: Config.pinCode == ''
|
||||||
|
onClicked: {
|
||||||
|
var dialog = pinSetup.createObject(preferences, {mode: 'enter'})
|
||||||
|
dialog.accepted.connect(function() {
|
||||||
|
Config.pinCode = dialog.pincode
|
||||||
|
dialog.close()
|
||||||
|
})
|
||||||
|
dialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: qsTr('Change')
|
||||||
|
visible: Config.pinCode != ''
|
||||||
|
onClicked: {
|
||||||
|
var dialog = pinSetup.createObject(preferences, {mode: 'change', pincode: Config.pinCode})
|
||||||
|
dialog.accepted.connect(function() {
|
||||||
|
Config.pinCode = dialog.pincode
|
||||||
|
dialog.close()
|
||||||
|
})
|
||||||
|
dialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: qsTr('Remove')
|
||||||
|
visible: Config.pinCode != ''
|
||||||
|
onClicked: {
|
||||||
|
Config.pinCode = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('Lightning Routing')
|
||||||
|
}
|
||||||
|
|
||||||
|
ElComboBox {
|
||||||
|
id: lnRoutingType
|
||||||
|
enabled: Daemon.currentWallet && Daemon.currentWallet.isLightning
|
||||||
|
|
||||||
|
valueRole: 'key'
|
||||||
|
textRole: 'label'
|
||||||
|
model: ListModel {
|
||||||
|
ListElement { key: 'gossip'; label: qsTr('Gossip') }
|
||||||
|
ListElement { key: 'trampoline'; label: qsTr('Trampoline') }
|
||||||
|
}
|
||||||
|
onCurrentValueChanged: {
|
||||||
|
if (activeFocus)
|
||||||
|
Config.useGossip = currentValue == 'gossip'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Pane {
|
||||||
text: qsTr('Lightning Routing')
|
ColumnLayout {
|
||||||
}
|
id: pluginsRootLayout
|
||||||
|
|
||||||
ElComboBox {
|
|
||||||
id: lnRoutingType
|
|
||||||
enabled: Daemon.currentWallet && Daemon.currentWallet.isLightning
|
|
||||||
|
|
||||||
valueRole: 'key'
|
|
||||||
textRole: 'label'
|
|
||||||
model: ListModel {
|
|
||||||
ListElement { key: 'gossip'; label: qsTr('Gossip') }
|
|
||||||
ListElement { key: 'trampoline'; label: qsTr('Trampoline') }
|
|
||||||
}
|
|
||||||
onCurrentValueChanged: {
|
|
||||||
if (activeFocus)
|
|
||||||
Config.useGossip = currentValue == 'gossip'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,6 +229,19 @@ Pane {
|
|||||||
Pin {}
|
Pin {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: pluginHeader
|
||||||
|
RowLayout {
|
||||||
|
property QtObject plugin
|
||||||
|
Switch {
|
||||||
|
checked: plugin.pluginEnabled
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: plugin.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
baseUnit.currentIndex = _baseunits.indexOf(Config.baseUnit)
|
baseUnit.currentIndex = _baseunits.indexOf(Config.baseUnit)
|
||||||
thousands.checked = Config.thousandsSeparator
|
thousands.checked = Config.thousandsSeparator
|
||||||
@@ -201,5 +251,15 @@ Pane {
|
|||||||
fiatEnable.checked = Daemon.fx.enabled
|
fiatEnable.checked = Daemon.fx.enabled
|
||||||
spendUnconfirmed.checked = Config.spendUnconfirmed
|
spendUnconfirmed.checked = Config.spendUnconfirmed
|
||||||
lnRoutingType.currentIndex = Config.useGossip ? 0 : 1
|
lnRoutingType.currentIndex = Config.useGossip ? 0 : 1
|
||||||
|
|
||||||
|
var labelsPlugin = AppController.plugin('labels')
|
||||||
|
if (labelsPlugin) {
|
||||||
|
pluginHeader.createObject(pluginsRootLayout, { plugin: labelsPlugin })
|
||||||
|
// console.log(Qt.resolvedUrl(labelsPlugin.settingsComponent()))
|
||||||
|
if (labelsPlugin.settingsComponent()) {
|
||||||
|
var component = Qt.createComponent(Qt.resolvedUrl(labelsPlugin.settingsComponent()))
|
||||||
|
component.createObject(pluginsRootLayout, {plugin: labelsPlugin})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,11 +34,12 @@ notification = None
|
|||||||
class QEAppController(QObject):
|
class QEAppController(QObject):
|
||||||
userNotify = pyqtSignal(str)
|
userNotify = pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, qedaemon):
|
def __init__(self, qedaemon, plugins):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.logger = get_logger(__name__)
|
self.logger = get_logger(__name__)
|
||||||
|
|
||||||
self._qedaemon = qedaemon
|
self._qedaemon = qedaemon
|
||||||
|
self._plugins = plugins
|
||||||
|
|
||||||
# set up notification queue and notification_timer
|
# set up notification queue and notification_timer
|
||||||
self.user_notification_queue = queue.Queue()
|
self.user_notification_queue = queue.Queue()
|
||||||
@@ -131,11 +132,22 @@ class QEAppController(QObject):
|
|||||||
def clipboardToText(self):
|
def clipboardToText(self):
|
||||||
return QGuiApplication.clipboard().text()
|
return QGuiApplication.clipboard().text()
|
||||||
|
|
||||||
|
@pyqtSlot(str, result=QObject)
|
||||||
|
def plugin(self, plugin_name):
|
||||||
|
self.logger.warning(f'now {self._plugins.count()} plugins loaded')
|
||||||
|
plugin = self._plugins.get(plugin_name)
|
||||||
|
self.logger.debug(f'plugin with name {plugin_name} is {str(type(plugin))}')
|
||||||
|
if plugin:
|
||||||
|
return plugin.so
|
||||||
|
else:
|
||||||
|
self.logger.debug('None!')
|
||||||
|
return None
|
||||||
|
|
||||||
class ElectrumQmlApplication(QGuiApplication):
|
class ElectrumQmlApplication(QGuiApplication):
|
||||||
|
|
||||||
_valid = True
|
_valid = True
|
||||||
|
|
||||||
def __init__(self, args, config, daemon):
|
def __init__(self, args, config, daemon, plugins):
|
||||||
super().__init__(args)
|
super().__init__(args)
|
||||||
|
|
||||||
self.logger = get_logger(__name__)
|
self.logger = get_logger(__name__)
|
||||||
@@ -162,7 +174,6 @@ class ElectrumQmlApplication(QGuiApplication):
|
|||||||
qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')
|
qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')
|
||||||
|
|
||||||
self.engine = QQmlApplicationEngine(parent=self)
|
self.engine = QQmlApplicationEngine(parent=self)
|
||||||
self.engine.addImportPath('./qml')
|
|
||||||
|
|
||||||
screensize = self.primaryScreen().size()
|
screensize = self.primaryScreen().size()
|
||||||
|
|
||||||
@@ -181,13 +192,13 @@ class ElectrumQmlApplication(QGuiApplication):
|
|||||||
self.context = self.engine.rootContext()
|
self.context = self.engine.rootContext()
|
||||||
self._qeconfig = QEConfig(config)
|
self._qeconfig = QEConfig(config)
|
||||||
self._qenetwork = QENetwork(daemon.network, self._qeconfig)
|
self._qenetwork = QENetwork(daemon.network, self._qeconfig)
|
||||||
self._qedaemon = QEDaemon(daemon)
|
self.daemon = QEDaemon(daemon)
|
||||||
self._appController = QEAppController(self._qedaemon)
|
self.appController = QEAppController(self.daemon, plugins)
|
||||||
self._maxAmount = QEAmount(is_max=True)
|
self._maxAmount = QEAmount(is_max=True)
|
||||||
self.context.setContextProperty('AppController', self._appController)
|
self.context.setContextProperty('AppController', self.appController)
|
||||||
self.context.setContextProperty('Config', self._qeconfig)
|
self.context.setContextProperty('Config', self._qeconfig)
|
||||||
self.context.setContextProperty('Network', self._qenetwork)
|
self.context.setContextProperty('Network', self._qenetwork)
|
||||||
self.context.setContextProperty('Daemon', self._qedaemon)
|
self.context.setContextProperty('Daemon', self.daemon)
|
||||||
self.context.setContextProperty('FixedFont', self.fixedFont)
|
self.context.setContextProperty('FixedFont', self.fixedFont)
|
||||||
self.context.setContextProperty('MAX', self._maxAmount)
|
self.context.setContextProperty('MAX', self._maxAmount)
|
||||||
self.context.setContextProperty('QRIP', self.qr_ip_h)
|
self.context.setContextProperty('QRIP', self.qr_ip_h)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from electrum.i18n import _
|
|||||||
from electrum.logging import get_logger
|
from electrum.logging import get_logger
|
||||||
from electrum.util import WalletFileException, standardize_path
|
from electrum.util import WalletFileException, standardize_path
|
||||||
from electrum.wallet import Abstract_Wallet
|
from electrum.wallet import Abstract_Wallet
|
||||||
|
from electrum.plugin import run_hook
|
||||||
from electrum.lnchannel import ChannelState
|
from electrum.lnchannel import ChannelState
|
||||||
|
|
||||||
from .auth import AuthMixin, auth_protect
|
from .auth import AuthMixin, auth_protect
|
||||||
@@ -179,6 +180,7 @@ class QEDaemon(AuthMixin, QObject):
|
|||||||
self._logger.info('use single password disabled by config')
|
self._logger.info('use single password disabled by config')
|
||||||
|
|
||||||
self.daemon.config.save_last_wallet(wallet)
|
self.daemon.config.save_last_wallet(wallet)
|
||||||
|
run_hook('load_wallet', wallet)
|
||||||
else:
|
else:
|
||||||
self._logger.info('could not open wallet')
|
self._logger.info('could not open wallet')
|
||||||
self.walletOpenError.emit('could not open wallet')
|
self.walletOpenError.emit('could not open wallet')
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
|
|||||||
transactionSigned = pyqtSignal([str], arguments=['txid'])
|
transactionSigned = pyqtSignal([str], arguments=['txid'])
|
||||||
#broadcastSucceeded = pyqtSignal([str], arguments=['txid'])
|
#broadcastSucceeded = pyqtSignal([str], arguments=['txid'])
|
||||||
broadcastFailed = pyqtSignal([str,str,str], arguments=['txid','code','reason'])
|
broadcastFailed = pyqtSignal([str,str,str], arguments=['txid','code','reason'])
|
||||||
|
labelsUpdated = pyqtSignal()
|
||||||
|
|
||||||
_network_signal = pyqtSignal(str, object)
|
_network_signal = pyqtSignal(str, object)
|
||||||
|
|
||||||
|
|||||||
45
electrum/plugins/labels/Labels.qml
Normal file
45
electrum/plugins/labels/Labels.qml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import QtQuick 2.6
|
||||||
|
import QtQuick.Layouts 1.0
|
||||||
|
import QtQuick.Controls 2.14
|
||||||
|
import QtQuick.Controls.Material 2.0
|
||||||
|
|
||||||
|
import org.electrum 1.0
|
||||||
|
|
||||||
|
//import "controls"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: rootLayout.height
|
||||||
|
|
||||||
|
property QtObject plugin
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: rootLayout
|
||||||
|
Button {
|
||||||
|
text: 'Force upload'
|
||||||
|
enabled: !plugin.busy
|
||||||
|
onClicked: plugin.upload()
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: 'Force download'
|
||||||
|
enabled: !plugin.busy
|
||||||
|
onClicked: plugin.download()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: plugin
|
||||||
|
function onUploadSuccess() {
|
||||||
|
console.log('upload success')
|
||||||
|
}
|
||||||
|
function onUploadFailed() {
|
||||||
|
console.log('upload failed')
|
||||||
|
}
|
||||||
|
function onDownloadSuccess() {
|
||||||
|
console.log('download success')
|
||||||
|
}
|
||||||
|
function onDownloadFailed() {
|
||||||
|
console.log('download failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,5 +5,5 @@ description = ' '.join([
|
|||||||
_("Save your wallet labels on a remote server, and synchronize them across multiple devices where you use Electrum."),
|
_("Save your wallet labels on a remote server, and synchronize them across multiple devices where you use Electrum."),
|
||||||
_("Labels, transactions IDs and addresses are encrypted before they are sent to the remote server.")
|
_("Labels, transactions IDs and addresses are encrypted before they are sent to the remote server.")
|
||||||
])
|
])
|
||||||
available_for = ['qt', 'kivy', 'cmdline']
|
available_for = ['qt', 'qml', 'kivy', 'cmdline']
|
||||||
|
|
||||||
|
|||||||
138
electrum/plugins/labels/qml.py
Normal file
138
electrum/plugins/labels/qml.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import threading
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
|
||||||
|
|
||||||
|
from electrum.i18n import _
|
||||||
|
from electrum.plugin import hook
|
||||||
|
|
||||||
|
from electrum.gui.qml.qewallet import QEWallet
|
||||||
|
|
||||||
|
from .labels import LabelsPlugin
|
||||||
|
|
||||||
|
class Plugin(LabelsPlugin):
|
||||||
|
|
||||||
|
class QSignalObject(QObject):
|
||||||
|
pluginChanged = pyqtSignal()
|
||||||
|
pluginEnabledChanged = pyqtSignal()
|
||||||
|
labelsChanged = pyqtSignal()
|
||||||
|
busyChanged = pyqtSignal()
|
||||||
|
uploadSuccess = pyqtSignal()
|
||||||
|
uploadFailed = pyqtSignal()
|
||||||
|
downloadSuccess = pyqtSignal()
|
||||||
|
downloadFailed = pyqtSignal()
|
||||||
|
|
||||||
|
_busy = False
|
||||||
|
|
||||||
|
def __init__(self, plugin, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.plugin = plugin
|
||||||
|
|
||||||
|
@pyqtProperty(str, notify=pluginChanged)
|
||||||
|
def name(self): return _('Labels Plugin')
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=busyChanged)
|
||||||
|
def busy(self): return self._busy
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=pluginEnabledChanged)
|
||||||
|
def pluginEnabled(self): return self.plugin.is_enabled()
|
||||||
|
|
||||||
|
@pyqtSlot(result=str)
|
||||||
|
def settingsComponent(self): return '../../../plugins/labels/Labels.qml'
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def upload(self):
|
||||||
|
assert self.plugin
|
||||||
|
|
||||||
|
self._busy = True
|
||||||
|
self.busyChanged.emit()
|
||||||
|
|
||||||
|
self.plugin.push_async()
|
||||||
|
|
||||||
|
def upload_finished(self, result):
|
||||||
|
if result:
|
||||||
|
self.uploadSuccess.emit()
|
||||||
|
else:
|
||||||
|
self.uploadFailed.emit()
|
||||||
|
self._busy = False
|
||||||
|
self.busyChanged.emit()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def download(self):
|
||||||
|
assert self.plugin
|
||||||
|
|
||||||
|
self._busy = True
|
||||||
|
self.busyChanged.emit()
|
||||||
|
|
||||||
|
self.plugin.pull_async()
|
||||||
|
|
||||||
|
def download_finished(self, result):
|
||||||
|
if result:
|
||||||
|
self.downloadSuccess.emit()
|
||||||
|
else:
|
||||||
|
self.downloadFailed.emit()
|
||||||
|
self._busy = False
|
||||||
|
self.busyChanged.emit()
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
LabelsPlugin.__init__(self, *args)
|
||||||
|
|
||||||
|
@hook
|
||||||
|
def load_wallet(self, wallet):
|
||||||
|
self.logger.info(f'load_wallet hook for wallet {str(type(wallet))}')
|
||||||
|
self.start_wallet(wallet)
|
||||||
|
|
||||||
|
def push_async(self):
|
||||||
|
if not self._app.daemon.currentWallet:
|
||||||
|
self.logger.error('No current wallet')
|
||||||
|
self.so.download_finished(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
wallet = self._app.daemon.currentWallet.wallet
|
||||||
|
|
||||||
|
def push_thread(wallet):
|
||||||
|
try:
|
||||||
|
self.push(wallet)
|
||||||
|
self.so.upload_finished(True)
|
||||||
|
self._app.appController.userNotify.emit(_('Labels uploaded'))
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(repr(e))
|
||||||
|
self.so.upload_finished(False)
|
||||||
|
self._app.appController.userNotify.emit(repr(e))
|
||||||
|
|
||||||
|
threading.Thread(target=push_thread,args=[wallet]).start()
|
||||||
|
|
||||||
|
def pull_async(self):
|
||||||
|
if not self._app.daemon.currentWallet:
|
||||||
|
self.logger.error('No current wallet')
|
||||||
|
self.so.download_finished(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
wallet = self._app.daemon.currentWallet.wallet
|
||||||
|
def pull_thread(wallet):
|
||||||
|
try:
|
||||||
|
self.pull(wallet, True)
|
||||||
|
self.so.download_finished(True)
|
||||||
|
self._app.appController.userNotify.emit(_('Labels downloaded'))
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(repr(e))
|
||||||
|
self.so.download_finished(False)
|
||||||
|
self._app.appController.userNotify.emit(repr(e))
|
||||||
|
|
||||||
|
threading.Thread(target=pull_thread,args=[wallet]).start()
|
||||||
|
|
||||||
|
|
||||||
|
def on_pulled(self, wallet):
|
||||||
|
self.logger.info('on pulled')
|
||||||
|
_wallet = QEWallet.getInstanceFor(wallet)
|
||||||
|
self.logger.debug('wallet ' + ('found' if _wallet else 'not found'))
|
||||||
|
if _wallet:
|
||||||
|
_wallet.labelsUpdated.emit()
|
||||||
|
|
||||||
|
@hook
|
||||||
|
def init_qml(self, gui: 'ElectrumGui'):
|
||||||
|
self.logger.debug('init_qml hook called')
|
||||||
|
self.logger.debug(f'gui={str(type(gui))}')
|
||||||
|
self._app = gui.app
|
||||||
|
# important: QSignalObject needs to be parented, as keeping a ref
|
||||||
|
# in the plugin is not enough to avoid gc
|
||||||
|
self.so = Plugin.QSignalObject(self, self._app)
|
||||||
@@ -10,8 +10,6 @@ class Plugin(BasePlugin):
|
|||||||
def __init__(self, parent, config, name):
|
def __init__(self, parent, config, name):
|
||||||
BasePlugin.__init__(self, parent, config, name)
|
BasePlugin.__init__(self, parent, config, name)
|
||||||
|
|
||||||
_logger = get_logger(__name__)
|
|
||||||
|
|
||||||
@hook
|
@hook
|
||||||
def init_qml(self, gui: 'ElectrumGui'):
|
def init_qml(self, gui: 'ElectrumGui'):
|
||||||
self._logger.debug('init_qml hook called')
|
self.logger.debug('init_qml hook called')
|
||||||
|
|||||||
Reference in New Issue
Block a user