Merge pull request #5460 from Ultimaker/WIP_onboarding_by_ip

'WIP-onboarding-by-ip' into 'parent' branch
This commit is contained in:
Remco Burema 2019-03-21 09:52:39 +01:00 committed by GitHub
commit 139edbb0b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 130 additions and 247 deletions

View file

@ -242,8 +242,6 @@ class CuraApplication(QtApplication):
self._update_platform_activity_timer = None
self._need_to_show_user_agreement = True
self._sidebar_custom_menu_items = [] # type: list # Keeps list of custom menu items for the side bar
self._plugins_loaded = False
@ -455,7 +453,6 @@ class CuraApplication(QtApplication):
# Misc.:
"ConsoleLogger", #You want to be able to read the log if something goes wrong.
"CuraEngineBackend", #Cura is useless without this one since you can't slice.
"UserAgreement", #Our lawyers want every user to see this at least once.
"FileLogger", #You want to be able to read the log if something goes wrong.
"XmlMaterialProfile", #Cura crashes without this one.
"Toolbox", #This contains the interface to enable/disable plug-ins, so if you disable it you can't enable it back.
@ -527,7 +524,7 @@ class CuraApplication(QtApplication):
preferences.addPreference("cura/expanded_brands", "")
preferences.addPreference("cura/expanded_types", "")
self._need_to_show_user_agreement = not preferences.getValue("general/accepted_user_agreement")
preferences.addPreference("general/accepted_user_agreement", False)
for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
@ -550,13 +547,20 @@ class CuraApplication(QtApplication):
@pyqtProperty(bool)
def needToShowUserAgreement(self) -> bool:
return self._need_to_show_user_agreement
return not self.getPreferences().getValue("general/accepted_user_agreement")
@pyqtSlot(bool)
def setNeedToShowUserAgreement(self, set_value = True) -> None:
self._need_to_show_user_agreement = set_value
self.getPreferences().setValue("general/accepted_user_agreement", not set_value)
@pyqtSlot(str, str)
def writeToLog(self, severity: str, message: str) -> None:
Logger.log(severity, message)
# DO NOT call this function to close the application, use checkAndExitApplication() instead which will perform
# pre-exit checks such as checking for in-progress USB printing, etc.
# Except for the 'Decline and close' in the 'User Agreement'-step in the Welcome-pages, that should be a hard exit.
@pyqtSlot()
def closeApplication(self) -> None:
Logger.log("i", "Close application")
main_window = self.getMainWindow()

View file

@ -128,3 +128,11 @@ class DiscoveredPrintersModel(QObject):
@pyqtSlot("QVariant")
def createMachineFromDiscoveredPrinter(self, discovered_printer: "DiscoveredPrinter") -> None:
discovered_printer.create_callback(discovered_printer.getKey())
@pyqtSlot(str)
def createMachineFromDiscoveredPrinterAddress(self, ip_address: str) -> None:
if ip_address not in self._discovered_printer_by_ip_dict:
Logger.log("i", "Key [%s] does not exist in the discovered printers list.", ip_address)
return
self.createMachineFromDiscoveredPrinter(self._discovered_printer_by_ip_dict[ip_address])

View file

@ -18,6 +18,7 @@ class WelcomePagesModel(ListModel):
PageUrlRole = Qt.UserRole + 2 # URL to the page's QML file
NextPageIdRole = Qt.UserRole + 3 # The next page ID it should go to
def __init__(self, parent: Optional["QObject"] = None) -> None:
super().__init__(parent)

View file

@ -13,6 +13,9 @@ from PyQt5.QtGui import QDesktopServices
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
from cura.Settings.GlobalStack import GlobalStack # typing
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from UM.OutputDevice.OutputDeviceManager import ManualDeviceAdditionAttempt
from UM.i18n import i18nCatalog
from UM.Logger import Logger
@ -38,8 +41,8 @@ i18n_catalog = i18nCatalog("cura")
# If we discover a printer that has the same key as the active machine instance a connection is made.
@signalemitter
class UM3OutputDevicePlugin(OutputDevicePlugin):
addDeviceSignal = Signal()
removeDeviceSignal = Signal()
addDeviceSignal = Signal() # Called '...Signal' to avoid confusion with function-names.
removeDeviceSignal = Signal() # Ditto ^^^.
discoveredDevicesChanged = Signal()
cloudFlowIsPossible = Signal()
@ -179,6 +182,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self.checkCloudFlowIsPossible()
else:
self.getOutputDeviceManager().removeOutputDevice(key)
if key.startswith("manual:"):
self.removeManualDeviceSignal.emit(self.getPluginId(), key, self._discovered_devices[key].address)
def stop(self):
if self._zero_conf is not None:
@ -186,6 +191,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._zero_conf.close()
self._cloud_output_device_manager.stop()
def canAddManualDevice(self, address: str = "") -> ManualDeviceAdditionAttempt:
# This plugin should always be the fallback option (at least try it):
return ManualDeviceAdditionAttempt.POSSIBLE
def removeManualDevice(self, key, address = None):
if key in self._discovered_devices:
if not address:
@ -197,6 +206,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._manual_instances.remove(address)
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
self.removeManualDeviceSignal.emit(self.getPluginId(), key, address)
def addManualDevice(self, address):
if address not in self._manual_instances:
self._manual_instances.append(address)
@ -219,8 +230,6 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._checkManualDevice(address)
def _createMachineFromDiscoveredPrinter(self, key: str) -> None:
# TODO: This needs to be implemented. It's supposed to create a machine given a unique key as already discovered
# by this plugin.
discovered_device = self._discovered_devices.get(key)
if discovered_device is None:
Logger.log("e", "Could not find discovered device with key [%s]", key)
@ -249,6 +258,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
def _onNetworkRequestFinished(self, reply):
reply_url = reply.url().toString()
address = ""
device = None
properties = {} # type: Dict[bytes, bytes]
if "system" in reply_url:
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
# Something went wrong with checking the firmware version!
@ -308,6 +321,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._onRemoveDevice(instance_name)
self._onAddDevice(instance_name, address, properties)
if device and address in self._manual_instances:
self.getOutputDeviceManager().addOutputDevice(device)
self.addManualDeviceSignal.emit(self.getPluginId(), device.getId(), address, properties)
def _onRemoveDevice(self, device_id):
device = self._discovered_devices.pop(device_id, None)
if device:

View file

@ -1,46 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
from PyQt5.QtCore import QObject, pyqtSlot
from UM.Extension import Extension
from UM.Logger import Logger
class UserAgreement(QObject, Extension):
def __init__(self, application):
super(UserAgreement, self).__init__()
self._application = application
self._user_agreement_window = None
self._user_agreement_context = None
self._application.engineCreatedSignal.connect(self._onEngineCreated)
self._application.getPreferences().addPreference("general/accepted_user_agreement", False)
def _onEngineCreated(self):
if not self._application.getPreferences().getValue("general/accepted_user_agreement"):
self.showUserAgreement()
def showUserAgreement(self):
if not self._user_agreement_window:
self.createUserAgreementWindow()
self._user_agreement_window.show()
@pyqtSlot(bool)
def didAgree(self, user_choice):
if user_choice:
Logger.log("i", "User agreed to the user agreement")
self._application.getPreferences().setValue("general/accepted_user_agreement", True)
self._user_agreement_window.hide()
else:
Logger.log("i", "User did NOT agree to the user agreement")
self._application.getPreferences().setValue("general/accepted_user_agreement", False)
self._application.quit()
self._application.setNeedToShowUserAgreement(False)
def createUserAgreementWindow(self):
path = os.path.join(self._application.getPluginRegistry().getPluginPath(self.getPluginId()), "UserAgreement.qml")
self._user_agreement_window = self._application.createQmlComponent(path, {"manager": self})

View file

@ -1,63 +0,0 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import UM 1.3 as UM
UM.Dialog
{
id: baseDialog
minimumWidth: Math.round(UM.Theme.getSize("modal_window_minimum").width * 0.75)
minimumHeight: Math.round(UM.Theme.getSize("modal_window_minimum").height * 0.5)
width: minimumWidth
height: minimumHeight
title: catalog.i18nc("@title:window", "User Agreement")
TextArea
{
anchors.top: parent.top
width: parent.width
anchors.bottom: buttonRow.top
text: ' <center><h3>DISCLAIMER BY ULTIMAKER</h3></center>
<p>PLEASE READ THIS DISCLAIMER CAREFULLY.</p>
<p>EXCEPT WHEN OTHERWISE STATED IN WRITING, ULTIMAKER PROVIDES ANY ULTIMAKER SOFTWARE OR THIRD PARTY SOFTWARE AS IS WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF ULTIMAKER SOFTWARE IS WITH YOU.</p>
<p>UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, IN NO EVENT WILL ULTIMAKER BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE ANY ULTIMAKER SOFTWARE OR THIRD PARTY SOFTWARE.</p>
'
readOnly: true;
textFormat: TextEdit.RichText
}
Item
{
id: buttonRow
anchors.bottom: parent.bottom
width: parent.width
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
UM.I18nCatalog { id: catalog; name: "cura" }
Button
{
anchors.right: parent.right
text: catalog.i18nc("@action:button", "I understand and agree")
onClicked: {
baseDialog.accepted()
}
}
Button
{
anchors.left: parent.left
text: catalog.i18nc("@action:button", "I don't agree")
onClicked: {
baseDialog.rejected()
}
}
}
onAccepted: manager.didAgree(true)
onRejected: manager.didAgree(false)
onClosing: manager.didAgree(false)
}

View file

@ -1,10 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import UserAgreement
def getMetaData():
return {}
def register(app):
return {"extension": UserAgreement.UserAgreement(app)}

View file

@ -1,8 +0,0 @@
{
"name": "UserAgreement",
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Ask the user once if he/she agrees with our license.",
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -560,23 +560,6 @@
}
}
},
"UserAgreement": {
"package_info": {
"package_id": "UserAgreement",
"package_type": "plugin",
"display_name": "User Agreement",
"description": "Ask the user once if he/she agrees with our license.",
"package_version": "1.0.1",
"sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"VersionUpgrade21to22": {
"package_info": {
"package_id": "VersionUpgrade21to22",

View file

@ -45,7 +45,7 @@ UM.MainWindow
WelcomeDialog
{
id: welcomeDialog
visible: false
visible: true // True, so if somehow no preferences are found/loaded, it's shown anyway.
}
Rectangle
@ -73,6 +73,18 @@ UM.MainWindow
// This has been fixed for QtQuick Controls 2 since the Shortcut item has a context property.
Cura.Actions.parent = backgroundItem
CuraApplication.purgeWindows()
if (CuraApplication.needToShowUserAgreement)
{
welcomeDialog.visible = true;
welcomeDialog.currentStep = 0;
}
else
{
welcomeDialog.visible = false;
}
// TODO: While the new onboarding process contains the user-agreement,
// it should probably not entirely rely on 'needToShowUserAgreement' for show/hide.
}
Item
@ -828,16 +840,6 @@ UM.MainWindow
{
base.visible = true;
}
// check later if the user agreement dialog has been closed
if (CuraApplication.needToShowUserAgreement)
{
restart();
}
else if(Cura.MachineManager.activeMachine == null)
{
addMachineDialog.open();
}
}
}

View file

@ -18,72 +18,30 @@ Item
id: addPrinterByIpScreen
property bool hasPushedAdd: false
property bool hasSentRequest: false
property bool haveConnection: false
Timer
{
id: tempTimerButton
interval: 1200
running: false
repeat: false
onTriggered:
{
hasPushedAdd = true
tempTimerRequest.running = true
}
}
// TODO: Remove timers after review interface!
Timer
{
id: tempTimerRequest
interval: 1200
running: false
repeat: false
onTriggered:
{
hasSentRequest = true
tempTimerConnection.running = true
}
}
// TODO: Remove timers after review interface!
Timer
{
id: tempTimerConnection
interval: 1200
running: false
repeat: false
onTriggered: haveConnection = true
}
// TODO: Remove timers after review interface!
Label
{
id: titleLabel
anchors.top: parent.top
anchors.topMargin: 40
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: catalog.i18nc("@label", "Add printer by IP adress")
text: catalog.i18nc("@label", "Add printer by IP address")
color: UM.Theme.getColor("primary_button")
font: UM.Theme.getFont("large_bold")
renderType: Text.NativeRendering
}
Rectangle
Item
{
anchors.top: titleLabel.bottom
anchors.bottom: connectButton.top
anchors.topMargin: 40
anchors.bottomMargin: 40
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width * 3 / 4
width: Math.floor(parent.width * 3 / 4)
Item
{
@ -95,8 +53,7 @@ Item
height: contentHeight
width: parent.width
anchors.top: parent.top
anchors.margins: 20
//anchors.bottomMargin: 20
anchors.margins: UM.Theme.getSize("default_margin").width
font: UM.Theme.getFont("default")
text: catalog.i18nc("@label", "Enter the IP address or hostname of your printer on the network.")
@ -116,14 +73,12 @@ Item
anchors.left: parent.left
height: addPrinterButton.height
anchors.right: addPrinterButton.left
anchors.margins: 20
anchors.margins: UM.Theme.getSize("default_margin").width
font: UM.Theme.getFont("default")
text: ""
validator: RegExpValidator
{
regExp: /[a-zA-Z0-9\.\-\_]*/
regExp: /[a-fA-F0-9\.\:]*/
}
onAccepted: addPrinterButton.clicked()
@ -134,28 +89,29 @@ Item
id: addPrinterButton
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: 20
width: 140
anchors.margins: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("action_button").width
fixedWidthMode: true
text: catalog.i18nc("@button", "Add")
onClicked:
{
// TEMP: Simulate successfull connection to printer with 127.0.0.1 or unsuccessful with anything else
// TODO, alter after review interface, now it just starts the timers.
if (hostnameField.text.trim() != "")
{
addPrinterByIpScreen.hasPushedAdd = true
tempTimerRequest.running = true
enabled = false;
UM.OutputDeviceManager.addManualDevice(hostnameField.text, hostnameField.text);
}
}
enabled: ! addPrinterByIpScreen.hasPushedAdd
BusyIndicator
{
anchors.fill: parent
running: { ! parent.enabled && ! addPrinterByIpScreen.hasSentRequest }
running:
{
! parent.enabled &&
! addPrinterByIpScreen.hasSentRequest &&
! addPrinterByIpScreen.haveConnection
}
}
}
}
@ -164,13 +120,13 @@ Item
{
width: parent.width
anchors.top: userInputFields.bottom
anchors.margins: 20
anchors.margins: UM.Theme.getSize("default_margin").width
Label
{
id: waitResponseLabel
anchors.top: parent.top
anchors.margins: 20
anchors.margins: UM.Theme.getSize("default_margin").width
font: UM.Theme.getFont("default")
visible: { addPrinterByIpScreen.hasSentRequest && ! addPrinterByIpScreen.haveConnection }
@ -181,7 +137,7 @@ Item
{
id: printerInfoLabels
anchors.top: parent.top
anchors.margins: 20
anchors.margins: UM.Theme.getSize("default_margin").width
visible: addPrinterByIpScreen.haveConnection
@ -191,23 +147,46 @@ Item
anchors.top: parent.top
font: UM.Theme.getFont("large")
text: "Davids-desktop" // TODO: placeholder, alter after interface review.
text: "???"
}
GridLayout
{
id: printerInfoGrid
anchors.top: printerNameLabel.bottom
anchors.margins: UM.Theme.getSize("default_margin").width
columns: 2
columnSpacing: 20
columnSpacing: UM.Theme.getSize("default_margin").width
Text { font: UM.Theme.getFont("default"); text: "Type" }
Text { font: UM.Theme.getFont("default"); text: "Ultimaker S5" } // TODO: placeholder, alter after interface review.
Label { font: UM.Theme.getFont("default"); text: catalog.i18nc("@label", "Type") }
Label { id: typeText; font: UM.Theme.getFont("default"); text: "?" }
Text { font: UM.Theme.getFont("default"); text: "Firmware version" }
Text { font: UM.Theme.getFont("default"); text: "4.3.3.20180529" } // TODO: placeholder, alter after interface review.
Label { font: UM.Theme.getFont("default"); text: catalog.i18nc("@label", "Firmware version") }
Label { id: firmwareText; font: UM.Theme.getFont("default"); text: "0.0.0.0" }
Text { font: UM.Theme.getFont("default"); text: "Address" }
Text { font: UM.Theme.getFont("default"); text: "10.183.1.115" } // TODO: placeholder, alter after interface review.
Label { font: UM.Theme.getFont("default"); text: catalog.i18nc("@label", "Address") }
Label { id: addressText; font: UM.Theme.getFont("default"); text: "0.0.0.0" }
Connections
{
target: UM.OutputDeviceManager
onManualDeviceChanged:
{
typeText.text = UM.OutputDeviceManager.manualDeviceProperty("printer_type")
firmwareText.text = UM.OutputDeviceManager.manualDeviceProperty("firmware_version")
addressText.text = UM.OutputDeviceManager.manualDeviceProperty("address")
}
}
}
Connections
{
target: UM.OutputDeviceManager
onManualDeviceChanged:
{
printerNameLabel.text = UM.OutputDeviceManager.manualDeviceProperty("name")
addPrinterByIpScreen.haveConnection = true
}
}
}
}
@ -219,9 +198,9 @@ Item
id: backButton
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: 40
text: catalog.i18nc("@button", "Back")
width: 140
anchors.margins: UM.Theme.getSize("default_margin").width
text: catalog.i18nc("@button", "Cancel")
width: UM.Theme.getSize("action_button").width
fixedWidthMode: true
onClicked: base.gotoPage("add_printer_by_selection")
@ -233,11 +212,17 @@ Item
id: connectButton
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 40
anchors.margins: UM.Theme.getSize("default_margin").width
text: catalog.i18nc("@button", "Connect")
width: 140
width: UM.Theme.getSize("action_button").width
fixedWidthMode: true
onClicked: base.showNextPage()
onClicked:
{
CuraApplication.getDiscoveredPrintersModel().createMachineFromDiscoveredPrinterAddress(
UM.OutputDeviceManager.manualDeviceProperty("address"))
UM.OutputDeviceManager.setActiveDevice(UM.OutputDeviceManager.manualDeviceProperty("device_id"))
base.showNextPage()
}
enabled: addPrinterByIpScreen.haveConnection
}

View file

@ -62,7 +62,12 @@ Item
text: catalog.i18nc("@button", "Agree")
width: 140
fixedWidthMode: true
onClicked: base.showNextPage()
onClicked:
{
CuraApplication.writeToLog("i", "User accepted the User-Agreement.")
CuraApplication.setNeedToShowUserAgreement(false)
base.showNextPage()
}
}
Cura.SecondaryButton
@ -74,6 +79,11 @@ Item
text: catalog.i18nc("@button", "Decline and close")
width: 140
fixedWidthMode: true
onClicked: base.showNextPage() // TODO: quit
onClicked:
{
CuraApplication.writeToLog("i", "User declined the User Agreement.")
base.passLastPage()
CuraApplication.closeApplication() // NOTE: Hard exit, don't use if anything needs to be saved!
}
}
}