Merge pull request #7831 from Ultimaker/CURA-7437_no_internet_cloud_status

CURA-7437_no_internet_cloud_status
This commit is contained in:
Konstantinos Karmas 2020-05-28 10:02:15 +02:00 committed by GitHub
commit 56e8827d00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 198 additions and 32 deletions

View File

@ -0,0 +1,64 @@
from typing import Optional
from PyQt5.QtCore import QObject, pyqtSignal, QTimer, pyqtProperty
from PyQt5.QtNetwork import QNetworkReply
from UM.TaskManagement.HttpRequestManager import HttpRequestManager
from cura.UltimakerCloud import UltimakerCloudAuthentication
class ConnectionStatus(QObject):
"""Status info for some web services"""
UPDATE_INTERVAL = 10.0 # seconds
ULTIMAKER_CLOUD_STATUS_URL = UltimakerCloudAuthentication.CuraCloudAPIRoot + "/connect/v1/"
__instance = None # type: Optional[ConnectionStatus]
internetReachableChanged = pyqtSignal()
umCloudReachableChanged = pyqtSignal()
@classmethod
def getInstance(cls, *args, **kwargs) -> "ConnectionStatus":
if cls.__instance is None:
cls.__instance = cls(*args, **kwargs)
return cls.__instance
def __init__(self, parent: Optional["QObject"] = None):
super().__init__(parent)
self._http = HttpRequestManager.getInstance()
self._statuses = {
self.ULTIMAKER_CLOUD_STATUS_URL: True,
"http://example.com": True
}
# Create a timer for automatic updates
self._update_timer = QTimer()
self._update_timer.setInterval(int(self.UPDATE_INTERVAL * 1000))
# The timer is restarted automatically
self._update_timer.setSingleShot(False)
self._update_timer.timeout.connect(self._update)
self._update_timer.start()
@pyqtProperty(bool, notify=internetReachableChanged)
def isInternetReachable(self) -> bool:
# Is any of the test urls reachable?
return any(self._statuses.values())
def _update(self):
for url in self._statuses.keys():
self._http.get(
url = url,
callback = self._statusCallback,
error_callback = self._statusCallback,
timeout = 5
)
def _statusCallback(self, reply: QNetworkReply, error: QNetworkReply.NetworkError = None):
url = reply.request().url().toString()
prev_statuses = self._statuses.copy()
self._statuses[url] = HttpRequestManager.replyIndicatesSuccess(reply, error)
if any(self._statuses.values()) != any(prev_statuses.values()):
self.internetReachableChanged.emit()

View File

@ -5,6 +5,7 @@ from typing import Optional, TYPE_CHECKING
from PyQt5.QtCore import QObject, pyqtProperty from PyQt5.QtCore import QObject, pyqtProperty
from cura.API.Backups import Backups from cura.API.Backups import Backups
from cura.API.ConnectionStatus import ConnectionStatus
from cura.API.Interface import Interface from cura.API.Interface import Interface
from cura.API.Account import Account from cura.API.Account import Account
@ -45,6 +46,8 @@ class CuraAPI(QObject):
# Backups API # Backups API
self._backups = Backups(self._application) self._backups = Backups(self._application)
self._connectionStatus = ConnectionStatus()
# Interface API # Interface API
self._interface = Interface(self._application) self._interface = Interface(self._application)
@ -55,6 +58,10 @@ class CuraAPI(QObject):
def account(self) -> "Account": def account(self) -> "Account":
return self._account return self._account
@pyqtProperty(QObject, constant = True)
def connectionStatus(self) -> "ConnectionStatus":
return self._connectionStatus
@property @property
def backups(self) -> "Backups": def backups(self) -> "Backups":
return self._backups return self._backups

View File

@ -491,6 +491,10 @@ class MachineManager(QObject):
# A cloud connection is only available if any output device actually is a cloud connected device. # A cloud connection is only available if any output device actually is a cloud connected device.
return any(d.connectionType == ConnectionType.CloudConnection for d in self._printer_output_devices) return any(d.connectionType == ConnectionType.CloudConnection for d in self._printer_output_devices)
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
def activeMachineHasCloudRegistration(self) -> bool:
return self.activeMachine is not None and ConnectionType.CloudConnection in self.activeMachine.configuredConnectionTypes
@pyqtProperty(bool, notify = printerConnectedStatusChanged) @pyqtProperty(bool, notify = printerConnectedStatusChanged)
def activeMachineIsUsingCloudConnection(self) -> bool: def activeMachineIsUsingCloudConnection(self) -> bool:
return self.activeMachineHasCloudConnection and not self.activeMachineHasNetworkConnection return self.activeMachineHasCloudConnection and not self.activeMachineHasNetworkConnection

View File

@ -265,7 +265,7 @@ class LocalClusterOutputDeviceManager:
## Nudge the user to start using Ultimaker Cloud. ## Nudge the user to start using Ultimaker Cloud.
@staticmethod @staticmethod
def _showCloudFlowMessage(device: LocalClusterOutputDevice) -> None: def _showCloudFlowMessage(device: LocalClusterOutputDevice) -> None:
if CuraApplication.getInstance().getMachineManager().activeMachineIsUsingCloudConnection: if CuraApplication.getInstance().getMachineManager().activeMachineHasCloudRegistration:
# This printer is already cloud connected, so we do not bother the user anymore. # This printer is already cloud connected, so we do not bother the user anymore.
return return
if not CuraApplication.getInstance().getCuraAPI().account.isLoggedIn: if not CuraApplication.getInstance().getCuraAPI().account.isLoggedIn:

View File

@ -35,7 +35,8 @@ Item
property color headerActiveColor: UM.Theme.getColor("secondary") property color headerActiveColor: UM.Theme.getColor("secondary")
property color headerHoverColor: UM.Theme.getColor("action_button_hovered") property color headerHoverColor: UM.Theme.getColor("action_button_hovered")
property alias enabled: mouseArea.enabled property alias mouseArea: headerMouseArea
property alias enabled: headerMouseArea.enabled
// Text to show when this component is disabled // Text to show when this component is disabled
property alias disabledText: disabledLabel.text property alias disabledText: disabledLabel.text
@ -139,6 +140,16 @@ Item
anchors.fill: parent anchors.fill: parent
visible: base.enabled visible: base.enabled
MouseArea
{
id: headerMouseArea
anchors.fill: parent
onClicked: toggleContent()
hoverEnabled: true
onEntered: background.color = headerHoverColor
onExited: background.color = base.enabled ? headerBackgroundColor : UM.Theme.getColor("disabled")
}
Loader Loader
{ {
id: headerItemLoader id: headerItemLoader
@ -180,15 +191,6 @@ Item
} }
} }
MouseArea
{
id: mouseArea
anchors.fill: parent
onClicked: toggleContent()
hoverEnabled: true
onEntered: background.color = headerHoverColor
onExited: background.color = base.enabled ? headerBackgroundColor : UM.Theme.getColor("disabled")
}
} }
DropShadow DropShadow

View File

@ -5,16 +5,49 @@ import QtQuick 2.7
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.1 as Cura
Cura.ExpandablePopup Cura.ExpandablePopup
{ {
id: machineSelector id: machineSelector
property bool isNetworkPrinter: Cura.MachineManager.activeMachineHasNetworkConnection property bool isNetworkPrinter: Cura.MachineManager.activeMachineHasNetworkConnection
property bool isCloudPrinter: Cura.MachineManager.activeMachineHasCloudConnection property bool isConnectedCloudPrinter: Cura.MachineManager.activeMachineHasCloudConnection
property bool isCloudRegistered: Cura.MachineManager.activeMachineHasCloudRegistration
property bool isGroup: Cura.MachineManager.activeMachineIsGroup property bool isGroup: Cura.MachineManager.activeMachineIsGroup
readonly property string connectionStatus: {
if (isNetworkPrinter)
{
return "printer_connected"
}
else if (isConnectedCloudPrinter && Cura.API.connectionStatus.isInternetReachable)
{
return "printer_cloud_connected"
}
else if (isCloudRegistered)
{
return "printer_cloud_not_available"
}
else
{
return ""
}
}
readonly property string connectionStatusMessage: {
if (connectionStatus == "printer_cloud_not_available")
{
if(Cura.API.connectionStatus.isInternetReachable){
return catalog.i18nc("@status", "The cloud connection is currently unavailable. Please check your internet connection and sign in to connect to the cloud printer.")
} else {
return catalog.i18nc("@status", "The cloud connection is currently unavailable. Please check your internet connection.")
}
} else {
return ""
}
}
contentPadding: UM.Theme.getSize("default_lining").width contentPadding: UM.Theme.getSize("default_lining").width
contentAlignment: Cura.ExpandablePopup.ContentAlignment.AlignLeft contentAlignment: Cura.ExpandablePopup.ContentAlignment.AlignLeft
@ -44,7 +77,7 @@ Cura.ExpandablePopup
{ {
return UM.Theme.getIcon("printer_group") return UM.Theme.getIcon("printer_group")
} }
else if (isNetworkPrinter || isCloudPrinter) else if (isNetworkPrinter || isCloudRegistered)
{ {
return UM.Theme.getIcon("printer_single") return UM.Theme.getIcon("printer_single")
} }
@ -59,6 +92,7 @@ Cura.ExpandablePopup
UM.RecolorImage UM.RecolorImage
{ {
id: connectionStatusImage
anchors anchors
{ {
bottom: parent.bottom bottom: parent.bottom
@ -66,27 +100,14 @@ Cura.ExpandablePopup
leftMargin: UM.Theme.getSize("thick_margin").width leftMargin: UM.Theme.getSize("thick_margin").width
} }
source: source: UM.Theme.getIcon(connectionStatus)
{
if (isNetworkPrinter)
{
return UM.Theme.getIcon("printer_connected")
}
else if (isCloudPrinter)
{
return UM.Theme.getIcon("printer_cloud_connected")
}
else
{
return ""
}
}
width: UM.Theme.getSize("printer_status_icon").width width: UM.Theme.getSize("printer_status_icon").width
height: UM.Theme.getSize("printer_status_icon").height height: UM.Theme.getSize("printer_status_icon").height
color: UM.Theme.getColor("primary") color: connectionStatus == "printer_cloud_not_available" ? UM.Theme.getColor("cloud_unavailable") : UM.Theme.getColor("primary")
visible: isNetworkPrinter || isCloudPrinter
visible: isNetworkPrinter || isCloudRegistered
// Make a themable circle in the background so we can change it in other themes // Make a themable circle in the background so we can change it in other themes
Rectangle Rectangle
@ -100,6 +121,38 @@ Cura.ExpandablePopup
color: UM.Theme.getColor("main_background") color: UM.Theme.getColor("main_background")
z: parent.z - 1 z: parent.z - 1
} }
}
MouseArea // Connection status tooltip hover area
{
id: connectionStatusTooltipHoverArea
anchors.fill: parent
hoverEnabled: connectionStatusMessage !== ""
acceptedButtons: Qt.NoButton // react to hover only, don't steal clicks
onEntered:
{
machineSelector.mouseArea.entered() // we want both this and the outer area to be entered
tooltip.show()
}
onExited: { tooltip.hide() }
}
Cura.ToolTip
{
id: tooltip
width: 250 * screenScaleFactor
tooltipText: connectionStatusMessage
arrowSize: UM.Theme.getSize("button_tooltip_arrow").width
x: connectionStatusImage.x - UM.Theme.getSize("narrow_margin").width
y: connectionStatusImage.y + connectionStatusImage.height + UM.Theme.getSize("narrow_margin").height
z: popup.z + 1
targetPoint: Qt.point(
connectionStatusImage.x + Math.round(connectionStatusImage.width / 2),
connectionStatusImage.y
)
} }
} }

View File

@ -19,12 +19,20 @@ ToolTip
property int contentAlignment: Cura.ToolTip.ContentAlignment.AlignRight property int contentAlignment: Cura.ToolTip.ContentAlignment.AlignRight
property alias tooltipText: tooltip.text property alias tooltipText: tooltip.text
property alias arrowSize: backgroundRect.arrowSize
property var targetPoint: Qt.point(parent.x, y + Math.round(height/2)) property var targetPoint: Qt.point(parent.x, y + Math.round(height/2))
id: tooltip id: tooltip
text: "" text: ""
delay: 500 delay: 500
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
visible: opacity != 0.0
opacity: 0.0 // initially hidden
Behavior on opacity
{
NumberAnimation { duration: 100; }
}
// If the text is not set, just set the height to 0 to prevent it from showing // If the text is not set, just set the height to 0 to prevent it from showing
height: text != "" ? label.contentHeight + 2 * UM.Theme.getSize("thin_margin").width: 0 height: text != "" ? label.contentHeight + 2 * UM.Theme.getSize("thin_margin").width: 0
@ -60,4 +68,12 @@ ToolTip
color: UM.Theme.getColor("tooltip_text") color: UM.Theme.getColor("tooltip_text")
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
function show() {
opacity = 1
}
function hide() {
opacity = 0
}
} }

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Artboard Copy 4</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M6,0 C9.3137085,0 12,2.6862915 12,6 C12,9.3137085 9.3137085,12 6,12 C2.6862915,12 0,9.3137085 0,6 C0,2.6862915 2.6862915,0 6,0 Z M5.2,3 C4.08,3 3.2,3.91666667 3.2,5.08333333 L3.2,5.125 L3.2,5.125 C2.52,5.20833333 2,5.83333333 2,6.54166667 C2,7.33333333 2.64,8 3.4,8 L8.6,8 L8.6,8 C9.36,8 10,7.33333333 10,6.54166667 C10,5.79166667 9.48,5.20833333 8.8,5.08333333 C8.72,4.375 8.12,3.83333333 7.4,3.83333333 C7.2,3.83333333 7.04,3.875 6.88,3.95833333 C6.52,3.375 5.88,3 5.2,3 Z" id="path-1"></path>
</defs>
<g id="Artboard-Copy-4" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" fill="#999999" transform="translate(7.187184, 7.187184) scale(-1, 1) rotate(45.000000) translate(-7.187184, -7.187184) " x="6.43718434" y="-0.812815665" width="1.5" height="16" rx="0.75"></rect>
<g id="Path" transform="translate(1.000000, 1.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Combined-Shape" fill="#999999" xlink:href="#path-1"></use>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -438,7 +438,9 @@
"monitor_shadow": [200, 200, 200, 255], "monitor_shadow": [200, 200, 200, 255],
"monitor_carousel_dot": [216, 216, 216, 255], "monitor_carousel_dot": [216, 216, 216, 255],
"monitor_carousel_dot_current": [119, 119, 119, 255] "monitor_carousel_dot_current": [119, 119, 119, 255],
"cloud_unavailable": [153, 153, 153, 255]
}, },
"sizes": { "sizes": {