diff --git a/DiscoverOctoPrintAction.py b/DiscoverOctoPrintAction.py index c4b4888..c674c2d 100644 --- a/DiscoverOctoPrintAction.py +++ b/DiscoverOctoPrintAction.py @@ -3,6 +3,7 @@ from UM.i18n import i18nCatalog from UM.Logger import Logger +from UM.Version import Version from UM.Settings.DefinitionContainer import DefinitionContainer from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.Settings.ContainerRegistry import ContainerRegistry @@ -12,7 +13,6 @@ from cura.Settings.CuraStackBuilder import CuraStackBuilder from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QUrl, QObject, QTimer -from PyQt5.QtQml import QQmlComponent, QQmlContext from PyQt5.QtGui import QDesktopServices from PyQt5.QtNetwork import ( QNetworkRequest, @@ -45,13 +45,22 @@ def __init__(self, parent: QObject = None) -> None: "DiscoverOctoPrintAction", catalog.i18nc("@action", "Connect OctoPrint") ) - self._qml_url = os.path.join("qml", "DiscoverOctoPrintAction.qml") - self._application = CuraApplication.getInstance() self._network_plugin = None # type: Optional[OctoPrintOutputDevicePlugin] - # QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly - # hook itself into the event loop, which results in events never being fired / done. + try: + use_controls1 = False + if self._application.getAPIVersion() < Version(8) and self._application.getVersion() != "master": + use_controls1 = True + except AttributeError: + # UM.Application.getAPIVersion was added for API > 6 (Cura 4) + use_controls1 = True + qml_folder = "qml" if not use_controls1 else "qml_controls1" + + self._qml_url = os.path.join(qml_folder, "DiscoverOctoPrintAction.qml") + + # QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly + # hook itself into the event loop, which results in events never being fired / done. self._network_manager = QNetworkAccessManager() self._network_manager.finished.connect(self._onRequestFinished) diff --git a/OctoPrintOutputDevice.py b/OctoPrintOutputDevice.py index 6460442..e8d561a 100644 --- a/OctoPrintOutputDevice.py +++ b/OctoPrintOutputDevice.py @@ -149,9 +149,10 @@ def __init__( plugin_version = "Unknown" Logger.logException("w", "Could not get version information for the plugin") + application = CuraApplication.getInstance() self._user_agent = "%s/%s %s/%s" % ( - CuraApplication.getInstance().getApplicationName(), - CuraApplication.getInstance().getVersion(), + application.getApplicationName(), + application.getVersion(), "OctoPrintPlugin", plugin_version, ) # NetworkedPrinterOutputDevice defines this as string, so we encode this later @@ -182,23 +183,32 @@ def __init__( basic_auth_password, ) + use_controls1 = False try: - major_api_version = CuraApplication.getInstance().getAPIVersion().getMajor() + if application.getAPIVersion() < Version(8) and application.getVersion() != "master": + use_controls1 = True + + major_api_version = application.getAPIVersion().getMajor() except AttributeError: # UM.Application.getAPIVersion was added for API > 6 (Cura 4) # Since this plugin version is only compatible with Cura 3.5 and newer, it is safe to assume API 5 major_api_version = 5 + use_controls1 = True - if major_api_version <= 5: - # In Cura 3.x, the monitor item only shows the camera stream - self._monitor_view_qml_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "qml", "MonitorItem3x.qml" - ) - else: + qml_folder = "qml" if not use_controls1 else "qml_controls1" + if not use_controls1: + # In Cura 5.x, the monitor item can only contain QtQuick Controls 2 items + qml_file = "MonitorItem.qml" + elif major_api_version > 5: # In Cura 4.x, the monitor item shows the camera stream as well as the monitor sidebar - self._monitor_view_qml_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "qml", "MonitorItem4x.qml" - ) + qml_file = "MonitorItem4x.qml" + else: + # In Cura 3.x, the monitor item only shows the camera stream + qml_file = "MonitorItem3x.qml" + + self._monitor_view_qml_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), qml_folder, qml_file + ) name = self._id matches = re.search(r"^\"(.*)\"\._octoprint\._tcp.local$", name) diff --git a/UploadOptions.py b/UploadOptions.py index d6850b9..f78cc29 100644 --- a/UploadOptions.py +++ b/UploadOptions.py @@ -2,6 +2,7 @@ # OctoPrintPlugin is released under the terms of the AGPLv3 or higher. from UM.Application import Application +from UM.Version import Version from UM.Util import parseBool from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot @@ -24,6 +25,17 @@ def __init__(self) -> None: self._auto_select = False self._auto_print = False + use_controls1 = False + try: + use_controls1 = False + if self._application.getAPIVersion() < Version(8) and self._application.getVersion() != "master": + use_controls1 = True + except AttributeError: + # UM.Application.getAPIVersion was added for API > 6 (Cura 4) + use_controls1 = True + self._qml_folder = "qml" if not use_controls1 else "qml_controls1" + + def configure(self, global_container_stack, file_name) -> None: self.setAutoPrint( parseBool( @@ -45,7 +57,7 @@ def setProceedCallback(self, callback: Callable) -> None: def showOptionsDialog(self) -> None: path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "qml", "UploadOptions.qml" + os.path.dirname(os.path.abspath(__file__)), self._qml_folder, "UploadOptions.qml" ) self._settings_dialog = self._application.createQmlComponent( diff --git a/qml/DiscoverOctoPrintAction.qml b/qml/DiscoverOctoPrintAction.qml index 56a805c..30eeb4e 100644 --- a/qml/DiscoverOctoPrintAction.qml +++ b/qml/DiscoverOctoPrintAction.qml @@ -1,11 +1,11 @@ // Copyright (c) 2021 Aldo Hoeben / fieldOfView // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. -import UM 1.2 as UM -import Cura 1.0 as Cura +import QtQuick 2.1 +import QtQuick.Controls 2.0 -import QtQuick 2.2 -import QtQuick.Controls 1.1 +import UM 1.5 as UM +import Cura 1.0 as Cura Cura.MachineAction @@ -63,7 +63,6 @@ Cura.MachineAction spacing: UM.Theme.getSize("default_margin").height width: parent.width - SystemPalette { id: palette } UM.I18nCatalog { id: catalog; name:"octoprint" } Item @@ -71,30 +70,27 @@ Cura.MachineAction width: parent.width height: pageTitle.height - Label + UM.Label { id: pageTitle text: catalog.i18nc("@title", "Connect to OctoPrint") - wrapMode: Text.WordWrap - font.pointSize: 18 + font: UM.Theme.getFont("large_bold") } - Label + UM.Label { id: pluginVersion anchors.bottom: pageTitle.bottom anchors.right: parent.right text: manager.pluginVersion - wrapMode: Text.WordWrap font.pointSize: 8 } } - Label + UM.Label { id: pageDescription width: parent.width - wrapMode: Text.WordWrap text: catalog.i18nc("@label", "Select your OctoPrint instance from the list below.") } @@ -102,7 +98,7 @@ Cura.MachineAction { spacing: UM.Theme.getSize("default_lining").width - Button + Cura.SecondaryButton { id: addButton text: catalog.i18nc("@action:button", "Add"); @@ -112,7 +108,7 @@ Cura.MachineAction } } - Button + Cura.SecondaryButton { id: editButton text: catalog.i18nc("@action:button", "Edit") @@ -128,7 +124,7 @@ Cura.MachineAction } } - Button + Cura.SecondaryButton { id: removeButton text: catalog.i18nc("@action:button", "Remove") @@ -136,7 +132,7 @@ Cura.MachineAction onClicked: manager.removeManualInstance(base.selectedInstance.name) } - Button + Cura.SecondaryButton { id: rediscoverButton text: catalog.i18nc("@action:button", "Refresh") @@ -150,76 +146,72 @@ Cura.MachineAction width: parent.width spacing: UM.Theme.getSize("default_margin").width - Item + Rectangle { width: Math.floor(parent.width * 0.4) - height: base.height - parent.y + height: base.height - (parent.y + UM.Theme.getSize("default_margin").height) + + color: UM.Theme.getColor("main_background") + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("thick_lining") - ScrollView + ListView { - id: objectListContainer - frameVisible: true - width: parent.width - anchors.top: parent.top - anchors.bottom: objectListFooter.top - anchors.bottomMargin: UM.Theme.getSize("default_margin").height + id: listview - Rectangle - { - parent: viewport - anchors.fill: parent - color: palette.light - } + clip: true + ScrollBar.vertical: UM.ScrollBar {} + + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_lining").width - ListView + model: manager.discoveredInstances + onModelChanged: { - id: listview - model: manager.discoveredInstances - onModelChanged: - { - var selectedId = manager.instanceId; - for(var i = 0; i < model.length; i++) { - if(model[i].getId() == selectedId) - { - currentIndex = i; - return - } + var selectedId = manager.instanceId; + for(var i = 0; i < model.length; i++) { + if(model[i].getId() == selectedId) + { + currentIndex = i; + return } - currentIndex = -1; } + currentIndex = -1; + } + + currentIndex: activeIndex + onCurrentIndexChanged: + { + base.selectedInstance = listview.model[currentIndex]; + apiCheckDelay.throttledCheck(); + } + + Component.onCompleted: manager.startDiscovery() + + delegate: Rectangle + { + height: childrenRect.height + color: ListView.isCurrentItem ? UM.Theme.getColor("text_selection") : UM.Theme.getColor("main_background") width: parent.width - currentIndex: activeIndex - onCurrentIndexChanged: + UM.Label { - base.selectedInstance = listview.model[currentIndex]; - apiCheckDelay.throttledCheck(); + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + anchors.right: parent.right + text: listview.model[index].name + elide: Text.ElideRight + font.italic: listview.model[index].key == manager.instanceId + wrapMode: Text.NoWrap } - Component.onCompleted: manager.startDiscovery() - delegate: Rectangle - { - height: childrenRect.height - color: ListView.isCurrentItem ? palette.highlight : index % 2 ? palette.base : palette.alternateBase - width: parent.width - Label - { - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_margin").width - anchors.right: parent.right - text: listview.model[index].name - color: parent.ListView.isCurrentItem ? palette.highlightedText : palette.text - elide: Text.ElideRight - font.italic: listview.model[index].key == manager.instanceId - } - MouseArea + MouseArea + { + anchors.fill: parent; + onClicked: { - anchors.fill: parent; - onClicked: + if(!parent.ListView.isCurrentItem) { - if(!parent.ListView.isCurrentItem) - { - parent.ListView.view.currentIndex = index; - } + parent.ListView.view.currentIndex = index; } } } @@ -233,7 +225,7 @@ Cura.MachineAction width: parent.width anchors.bottom: parent.bottom - CheckBox + UM.CheckBox { id: useZeroconf text: catalog.i18nc("@label", "Automatically discover local OctoPrint instances") @@ -254,13 +246,12 @@ Cura.MachineAction { width: Math.floor(parent.width * 0.6) spacing: UM.Theme.getSize("default_margin").height - Label + UM.Label { visible: base.selectedInstance != null width: parent.width - wrapMode: Text.WordWrap text: base.selectedInstance ? base.selectedInstance.name : "" - font.pointSize: 16 + font: UM.Theme.getFont("large_bold") elide: Text.ElideRight } Grid @@ -270,48 +261,46 @@ Cura.MachineAction columns: 2 rowSpacing: UM.Theme.getSize("default_lining").height verticalItemAlignment: Grid.AlignVCenter - Label + UM.Label { width: Math.floor(parent.width * 0.2) - wrapMode: Text.WordWrap text: catalog.i18nc("@label", "Address") } - Label + UM.Label { width: Math.floor(parent.width * 0.75) wrapMode: Text.WordWrap text: base.selectedInstance ? "%1:%2".arg(base.selectedInstance.ipAddress).arg(String(base.selectedInstance.port)) : "" } - Label + UM.Label { width: Math.floor(parent.width * 0.2) - wrapMode: Text.WordWrap text: catalog.i18nc("@label", "Version") } - Label + UM.Label { width: Math.floor(parent.width * 0.75) - wrapMode: Text.WordWrap text: base.selectedInstance ? base.selectedInstance.octoPrintVersion : "" } - Label + UM.Label { width: Math.floor(parent.width * 0.2) - wrapMode: Text.WordWrap text: catalog.i18nc("@label", "API Key") } Row { - spacing: UM.Theme.getSize("default_lining").width - TextField + spacing: UM.Theme.getSize("default_margin").width + Cura.TextField { id: apiKey width: Math.floor(parent.parent.width * (requestApiKey.visible ? 0.5 : 0.8) - UM.Theme.getSize("default_margin").width) + leftPadding: 0 + rightPadding: 0 echoMode: activeFocus ? TextInput.Normal : TextInput.Password onTextChanged: apiCheckDelay.throttledCheck() } - Button + Cura.SecondaryButton { id: requestApiKey visible: manager.instanceSupportsAppKeys @@ -324,16 +313,14 @@ Cura.MachineAction } } - Label + UM.Label { width: Math.floor(parent.width * 0.2) - wrapMode: Text.WordWrap text: catalog.i18nc("@label", "Username") } - Label + UM.Label { width: Math.floor(parent.width * 0.75) - wrapMode: Text.WordWrap text: base.selectedInstance ? base.selectedInstance.octoPrintUserName : "" } @@ -394,7 +381,7 @@ Cura.MachineAction } } - Label + UM.Label { visible: base.selectedInstance != null && text != "" text: @@ -430,7 +417,6 @@ Cura.MachineAction return result; } width: parent.width - UM.Theme.getSize("default_margin").width - wrapMode: Text.WordWrap } Column @@ -439,7 +425,7 @@ Cura.MachineAction width: parent.width spacing: UM.Theme.getSize("default_lining").height - CheckBox + UM.CheckBox { id: autoPrintCheckBox text: catalog.i18nc("@label", "Start print job after uploading") @@ -450,7 +436,7 @@ Cura.MachineAction manager.setContainerMetaDataEntry(activeMachineId, "octoprint_auto_print", String(checked)) } } - CheckBox + UM.CheckBox { id: autoSelectCheckBox text: catalog.i18nc("@label", "Select print job after uploading") @@ -465,7 +451,7 @@ Cura.MachineAction { spacing: UM.Theme.getSize("default_margin").width - CheckBox + UM.CheckBox { id: autoPowerControlCheckBox text: catalog.i18nc("@label", "Turn on printer with") @@ -484,7 +470,7 @@ Cura.MachineAction onInstanceAvailablePowerPluginsChanged: autoPowerControlPlugsModel.populateModel() } - ComboBox + Cura.ComboBox { id: autoPowerControlPlugs visible: manager.instanceApiKeyAccepted && model.count > 0 @@ -546,7 +532,7 @@ Cura.MachineAction } } - CheckBox + UM.CheckBox { id: autoConnectCheckBox text: catalog.i18nc("@label", "Connect to printer before sending print job") @@ -557,7 +543,7 @@ Cura.MachineAction manager.setContainerMetaDataEntry(activeMachineId, "octoprint_auto_connect", String(checked)) } } - CheckBox + UM.CheckBox { id: storeOnSdCheckBox text: catalog.i18nc("@label", "Store G-code on the SD card of the printer") @@ -568,14 +554,13 @@ Cura.MachineAction manager.setContainerMetaDataEntry(activeMachineId, "octoprint_store_sd", String(checked)) } } - Label + UM.Label { visible: storeOnSdCheckBox.checked - wrapMode: Text.WordWrap width: parent.width text: catalog.i18nc("@label", "Note: Transferring files to the printer SD card takes very long. Using this option is not recommended.") } - CheckBox + UM.CheckBox { id: confirmUploadOptionsCheckBox text: catalog.i18nc("@label", "Confirm print job options before sending") @@ -585,7 +570,7 @@ Cura.MachineAction manager.setContainerMetaDataEntry(activeMachineId, "octoprint_confirm_upload_options", String(checked)) } } - CheckBox + UM.CheckBox { id: showCameraCheckBox text: catalog.i18nc("@label", "Show webcam image") @@ -596,18 +581,17 @@ Cura.MachineAction manager.setContainerMetaDataEntry(activeMachineId, "octoprint_show_camera", String(checked)) } } - CheckBox + UM.CheckBox { id: fixGcodeFlavor text: catalog.i18nc("@label", "Set G-code flavor to \"Marlin\"") checked: true visible: machineGCodeFlavorProvider.properties.value == "UltiGCode" } - Label + UM.Label { text: catalog.i18nc("@label", "Note: Printing UltiGCode using OctoPrint does not work. Setting G-code flavor to \"Marlin\" fixes this, but overrides material settings on your printer.") width: parent.width - UM.Theme.getSize("default_margin").width - wrapMode: Text.WordWrap visible: fixGcodeFlavor.visible } } @@ -617,13 +601,13 @@ Cura.MachineAction visible: base.selectedInstance != null spacing: UM.Theme.getSize("default_margin").width - Button + Cura.SecondaryButton { text: catalog.i18nc("@action", "Open in browser...") onClicked: manager.openWebPage(base.selectedInstance.baseURL) } - Button + Cura.SecondaryButton { text: { diff --git a/qml/ManualInstanceDialog.qml b/qml/ManualInstanceDialog.qml index d945963..4ebee6d 100644 --- a/qml/ManualInstanceDialog.qml +++ b/qml/ManualInstanceDialog.qml @@ -1,11 +1,11 @@ // Copyright (c) 2021 Aldo Hoeben / fieldOfView // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. -import UM 1.2 as UM -import Cura 1.0 as Cura +import QtQuick 2.1 +import QtQuick.Controls 2.0 -import QtQuick 2.2 -import QtQuick.Controls 1.1 +import UM 1.5 as UM +import Cura 1.0 as Cura UM.Dialog @@ -23,11 +23,15 @@ UM.Dialog title: catalog.i18nc("@title:window", "Manually added OctoPrint instance") + buttonSpacing: UM.Theme.getSize("default_margin").width minimumWidth: 400 * screenScaleFactor - minimumHeight: 280 * screenScaleFactor + minimumHeight: 300 * screenScaleFactor width: minimumWidth height: minimumHeight + property int firstColumnWidth: Math.floor(width * 0.4) - 2 * margin + property int secondColumnWidth: Math.floor(width * 0.6) - 2 * margin + signal showDialog(string name, string address, string port, string path_, bool useHttps, string userName, string password) onShowDialog: { @@ -157,34 +161,34 @@ UM.Dialog rowSpacing: UM.Theme.getSize("default_lining").height columnSpacing: UM.Theme.getSize("default_margin").width - Label + UM.Label { text: catalog.i18nc("@label","Instance Name") - width: Math.floor(parent.width * 0.4) + width: manualInstanceDialog.firstColumnWidth } - TextField + Cura.TextField { id: nameField maximumLength: 20 - width: Math.floor(parent.width * 0.6) + width: manualInstanceDialog.secondColumnWidth validator: RegExpValidator { regExp: /[a-zA-Z0-9\.\-\_\:\[\]]*/ } } - Label + UM.Label { text: catalog.i18nc("@label","IP Address or Hostname") - width: Math.floor(parent.width * 0.4) + width: manualInstanceDialog.firstColumnWidth } - TextField + Cura.TextField { id: addressField maximumLength: 253 - width: Math.floor(parent.width * 0.6) + width: manualInstanceDialog.secondColumnWidth validator: RegExpValidator { regExp: /[a-zA-Z0-9\.\-\_\:\/\@]*/ @@ -192,17 +196,17 @@ UM.Dialog onTextChanged: parseAddressFieldTimer.restart() } - Label + UM.Label { text: catalog.i18nc("@label","Port Number") - width: Math.floor(parent.width * 0.4) + width: manualInstanceDialog.firstColumnWidth } - TextField + Cura.TextField { id: portField maximumLength: 5 - width: Math.floor(parent.width * 0.6) + width: manualInstanceDialog.secondColumnWidth validator: RegExpValidator { regExp: /[0-9]*/ @@ -220,17 +224,17 @@ UM.Dialog } } - Label + UM.Label { text: catalog.i18nc("@label","Path") - width: Math.floor(parent.width * 0.4) + width: manualInstanceDialog.firstColumnWidth } - TextField + Cura.TextField { id: pathField maximumLength: 30 - width: Math.floor(parent.width * 0.6) + width: manualInstanceDialog.secondColumnWidth validator: RegExpValidator { regExp: /[a-zA-Z0-9\.\-\_\/]*/ @@ -255,20 +259,20 @@ UM.Dialog height: 1 } - Label + UM.Label { wrapMode: Text.WordWrap - width: Math.floor(parent.width * 0.6) + width: manualInstanceDialog.secondColumnWidth text: catalog.i18nc("@label","In order to use HTTPS or a HTTP username and password, you need to configure a reverse proxy or another service.") } - Label + UM.Label { text: catalog.i18nc("@label","Use HTTPS") - width: Math.floor(parent.width * 0.4) + width: manualInstanceDialog.firstColumnWidth } - CheckBox + UM.CheckBox { id: httpsCheckbox width: height @@ -286,36 +290,36 @@ UM.Dialog } } - Label + UM.Label { text: catalog.i18nc("@label","HTTP username") - width: Math.floor(parent.width * 0.4) + width: manualInstanceDialog.firstColumnWidth } - TextField + Cura.TextField { id: userNameField maximumLength: 64 - width: Math.floor(parent.width * 0.6) + width: manualInstanceDialog.secondColumnWidth } - Label + UM.Label { text: catalog.i18nc("@label","HTTP password") - width: Math.floor(parent.width * 0.4) + width: manualInstanceDialog.firstColumnWidth } - TextField + Cura.TextField { id: passwordField maximumLength: 64 - width: Math.floor(parent.width * 0.6) + width: manualInstanceDialog.secondColumnWidth echoMode: TextInput.PasswordEchoOnEdit } } rightButtons: [ - Button { + Cura.SecondaryButton { text: catalog.i18nc("@action:button","Cancel") onClicked: { @@ -323,7 +327,7 @@ UM.Dialog manualInstanceDialog.hide() } }, - Button { + Cura.PrimaryButton { text: catalog.i18nc("@action:button", "Ok") onClicked: { @@ -336,7 +340,6 @@ UM.Dialog manualInstanceDialog.hide() } enabled: manualInstanceDialog.nameText.trim() != "" && manualInstanceDialog.addressText.trim() != "" - isDefault: true } ] } \ No newline at end of file diff --git a/qml/MonitorItem.qml b/qml/MonitorItem.qml new file mode 100644 index 0000000..1804168 --- /dev/null +++ b/qml/MonitorItem.qml @@ -0,0 +1,225 @@ +// Copyright (c) 2021 Aldo Hoeben / fieldOfView +// OctoPrintPlugin is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.7 +import QtQuick.Controls 2.3 + +import UM 1.3 as UM +import Cura 1.0 as Cura +import OctoPrintPlugin 1.0 as OctoPrintPlugin + +Component +{ + id: monitorItem + + Item + { + property var webcamsModel: OutputDevice != null ? OutputDevice.webcamsModel : null + property int activeIndex: 0 + + OctoPrintPlugin.NetworkMJPGImage + { + id: cameraImage + visible: OutputDevice != null ? OutputDevice.showCamera : false + + source: (OutputDevice != null && activeIndex < webcamsModel.count) ? webcamsModel.items[activeIndex].stream_url : "" + rotation: (OutputDevice != null && activeIndex < webcamsModel.count) ? webcamsModel.items[activeIndex].rotation : 0 + mirror: (OutputDevice != null && activeIndex < webcamsModel.count) ? webcamsModel.items[activeIndex].mirror : false + + property real maximumWidthMinusSidebar: maximumWidth - sidebar.width - 2 * UM.Theme.getSize("default_margin").width + property real maximumZoom: 2 + property bool rotatedImage: (rotation / 90) % 2 + property bool proportionalHeight: + { + if (imageHeight == 0 || maximumHeight == 0) + { + return true; + } + if (!rotatedImage) + { + return (imageWidth / imageHeight) > (maximumWidthMinusSidebar / maximumHeight); + } + else + { + return (imageWidth / imageHeight) > (maximumHeight / maximumWidthMinusSidebar); + } + } + property real _width: + { + if (!rotatedImage) + { + return Math.min(maximumWidthMinusSidebar, imageWidth * screenScaleFactor * maximumZoom); + } + else + { + return Math.min(maximumHeight, imageWidth * screenScaleFactor * maximumZoom); + } + } + property real _height: + { + if (!rotatedImage) + { + return Math.min(maximumHeight, imageHeight * screenScaleFactor * maximumZoom); + } + else + { + return Math.min(maximumWidth, imageHeight * screenScaleFactor * maximumZoom); + } + } + width: proportionalHeight ? _width : imageWidth * _height / imageHeight + height: !proportionalHeight ? _height : imageHeight * _width / imageWidth + anchors.horizontalCenter: horizontalCenterItem.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + + Component.onCompleted: + { + if (visible) + { + start(); + } + } + onVisibleChanged: + { + if (visible) + { + start(); + } else + { + stop(); + } + } + } + + Row + { + id: webcamSelectorContainer + spacing: Math.round(UM.Theme.getSize("default_margin").width / 2) + visible: (webcamsModel != null) ? webcamsModel.count > 1 : false + + anchors + { + horizontalCenter: cameraImage.horizontalCenter + top: cameraImage.top + topMargin: UM.Theme.getSize("default_margin").height + } + + Repeater + { + id: webcamSelector + model: webcamsModel + + delegate: UM.SimpleButton + { + id: control + text: model.name.toUpperCase() + checkable: true + checked: cameraImage.source == model.stream_url + + anchors.verticalCenter: parent.verticalCenter + ButtonGroup.group: webcamSelectorGroup + height: UM.Theme.getSize("main_window_header_button").height + + onClicked: activeIndex = index + + background: Rectangle + { + id: backgroundRectangle + implicitHeight: control.height + radius: UM.Theme.getSize("action_button_radius").width + + color: (control.checked) ? UM.Theme.getColor("main_window_header_button_background_active") : UM.Theme.getColor("main_window_header_button_background_hovered") + opacity: (control.checked || control.hovered) ? 1 : 0.5 + } + + contentItem: Label + { + id: contents + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + height: control.height + leftPadding: UM.Theme.getSize("default_margin").width + rightPadding: leftPadding + + text: control.text + font: UM.Theme.getFont("medium") + color: (control.checked) ? UM.Theme.getColor("main_window_header_button_text_active") : (control.hovered) ? UM.Theme.getColor("main_window_header_button_text_hovered") : UM.Theme.getColor("main_window_header_button_text_inactive") + } + } + } + + ButtonGroup { id: webcamSelectorGroup } + } + + Item + { + id: horizontalCenterItem + anchors.left: parent.left + anchors.right: sidebar.left + } + + Cura.RoundedRectangle + { + id: sidebarBackground + + width: UM.Theme.getSize("print_setup_widget").width + anchors + { + right: parent.right + top: parent.top + topMargin: UM.Theme.getSize("default_margin").height + bottom: actionsPanel.top + bottomMargin: UM.Theme.getSize("default_margin").height + } + + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("lining") + color: UM.Theme.getColor("main_background") + + cornerSide: Cura.RoundedRectangle.Direction.Left + radius: UM.Theme.getSize("default_radius").width + } + + Cura.PrintMonitor { + id: sidebar + + width: UM.Theme.getSize("print_setup_widget").width - UM.Theme.getSize("default_margin").height * 2 + anchors + { + top: parent.top + bottom: actionsPanel.top + leftMargin: UM.Theme.getSize("default_margin").width + right: parent.right + rightMargin: UM.Theme.getSize("default_margin").width + } + } + + Cura.RoundedRectangle + { + id: actionsPanel + + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("lining") + color: UM.Theme.getColor("main_background") + + cornerSide: Cura.RoundedRectangle.Direction.Left + radius: UM.Theme.getSize("default_radius").width + + anchors.bottom: parent.bottom + anchors.right: parent.right + + anchors.bottomMargin: UM.Theme.getSize("default_margin").width + anchors.topMargin: UM.Theme.getSize("default_margin").height + + width: UM.Theme.getSize("print_setup_widget").width + height: monitorButton.height + + // MonitorButton is actually the bottom footer panel. + Cura.MonitorButton + { + id: monitorButton + width: parent.width + anchors.top: parent.top + } + } + } +} \ No newline at end of file diff --git a/qml/OctoPrintComponents.qml b/qml/OctoPrintComponents.qml index 466cf74..20fd0b7 100644 --- a/qml/OctoPrintComponents.qml +++ b/qml/OctoPrintComponents.qml @@ -5,9 +5,7 @@ import UM 1.2 as UM import Cura 1.0 as Cura import QtQuick 2.2 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.1 +import QtQuick.Controls 2.0 Item { @@ -16,22 +14,12 @@ Item property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 property bool octoPrintConnected: printerConnected && Cura.MachineManager.printerOutputDevices[0].toString().indexOf("OctoPrintOutputDevice") == 0 - Button + Cura.SecondaryButton { objectName: "openOctoPrintButton" height: UM.Theme.getSize("save_button_save_to_button").height tooltip: catalog.i18nc("@info:tooltip", "Open the OctoPrint web interface") text: catalog.i18nc("@action:button", "OctoPrint...") - style: - { - if(UM.Theme.styles.hasOwnProperty("print_setup_action_button")) { - return UM.Theme.styles.print_setup_action_button - } - else - { - return UM.Theme.styles.sidebar_action_button - } - } onClicked: manager.openWebPage(Cura.MachineManager.printerOutputDevices[0].baseURL) visible: octoPrintConnected } diff --git a/qml/UploadOptions.qml b/qml/UploadOptions.qml index 723e267..e7aaa24 100644 --- a/qml/UploadOptions.qml +++ b/qml/UploadOptions.qml @@ -1,11 +1,11 @@ // Copyright (c) 2021 Aldo Hoeben / fieldOfView // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. -import UM 1.2 as UM -import Cura 1.0 as Cura +import QtQuick 2.1 +import QtQuick.Controls 2.0 -import QtQuick 2.2 -import QtQuick.Controls 1.1 +import UM 1.5 as UM +import Cura 1.0 as Cura UM.Dialog { @@ -16,6 +16,8 @@ UM.Dialog minimumWidth: screenScaleFactor * 400 minimumHeight: screenScaleFactor * 150 + buttonSpacing: UM.Theme.getSize("default_margin").width + onAccepted: manager.acceptOptionsDialog() Column { @@ -31,13 +33,13 @@ UM.Dialog rowSpacing: UM.Theme.getSize("default_lining").height columnSpacing: UM.Theme.getSize("default_margin").width - Label + UM.Label { id: pathLabel text: catalog.i18nc("@label", "Path") } - TextField { + Cura.TextField { id: pathField text: manager.filePath maximumLength: 256 @@ -50,13 +52,13 @@ UM.Dialog onTextChanged: manager.filePath = text } - Label + UM.Label { id: fileLabel text: catalog.i18nc("@label", "Filename") } - TextField { + Cura.TextField { id: nameField text: manager.fileName maximumLength: 100 @@ -73,7 +75,7 @@ UM.Dialog width: 1 height: UM.Theme.getSize("default_margin").height } - Label + UM.Label { text: catalog.i18nc("@label", "A file extenstion will be added automatically.") } @@ -85,14 +87,14 @@ UM.Dialog height: UM.Theme.getSize("default_margin").height } - CheckBox + UM.CheckBox { id: autoPrintCheckBox text: catalog.i18nc("@label", "Start print job after uploading") checked: manager.autoPrint onClicked: manager.autoPrint = checked } - CheckBox + UM.CheckBox { id: autoSelectCheckBox text: catalog.i18nc("@label", "Select print job after uploading") @@ -103,14 +105,13 @@ UM.Dialog } rightButtons: [ - Button { + Cura.SecondaryButton { text: catalog.i18nc("@action:button", "Cancel") onClicked: uploadOptions.reject() }, - Button { + Cura.PrimaryButton { text: catalog.i18nc("@action:button", "OK") onClicked: uploadOptions.accept() - isDefault: true } ] } diff --git a/qml_controls1/DiscoverOctoPrintAction.qml b/qml_controls1/DiscoverOctoPrintAction.qml new file mode 100644 index 0000000..56a805c --- /dev/null +++ b/qml_controls1/DiscoverOctoPrintAction.qml @@ -0,0 +1,677 @@ +// Copyright (c) 2021 Aldo Hoeben / fieldOfView +// OctoPrintPlugin is released under the terms of the AGPLv3 or higher. + +import UM 1.2 as UM +import Cura 1.0 as Cura + +import QtQuick 2.2 +import QtQuick.Controls 1.1 + + +Cura.MachineAction +{ + id: base + + readonly property string defaultHTTP: "80" + readonly property string defaultHTTPS: "443" + + anchors.fill: parent; + property var selectedInstance: null + property string activeMachineId: + { + if (Cura.MachineManager.activeMachineId != undefined) + { + return Cura.MachineManager.activeMachineId; + } + else if (Cura.MachineManager.activeMachine !== null) + { + return Cura.MachineManager.activeMachine.id; + } + + CuraApplication.log("There does not seem to be an active machine"); + return ""; + } + + onVisibleChanged: + { + if(!visible) + { + manager.cancelApiKeyRequest(); + } + } + + function boolCheck(value) //Hack to ensure a good match between python and qml. + { + if(value == "True") + { + return true + }else if(value == "False" || value == undefined) + { + return false + } + else + { + return value + } + } + + Column + { + anchors.fill: parent; + id: discoverOctoPrintAction + + spacing: UM.Theme.getSize("default_margin").height + width: parent.width + + SystemPalette { id: palette } + UM.I18nCatalog { id: catalog; name:"octoprint" } + + Item + { + width: parent.width + height: pageTitle.height + + Label + { + id: pageTitle + text: catalog.i18nc("@title", "Connect to OctoPrint") + wrapMode: Text.WordWrap + font.pointSize: 18 + } + + Label + { + id: pluginVersion + anchors.bottom: pageTitle.bottom + anchors.right: parent.right + text: manager.pluginVersion + wrapMode: Text.WordWrap + font.pointSize: 8 + } + } + + Label + { + id: pageDescription + width: parent.width + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Select your OctoPrint instance from the list below.") + } + + Row + { + spacing: UM.Theme.getSize("default_lining").width + + Button + { + id: addButton + text: catalog.i18nc("@action:button", "Add"); + onClicked: + { + manualInstanceDialog.showDialog("", "", base.defaultHTTP, "/", false, "", ""); + } + } + + Button + { + id: editButton + text: catalog.i18nc("@action:button", "Edit") + enabled: base.selectedInstance != null && base.selectedInstance.getProperty("manual") == "true" + onClicked: + { + manualInstanceDialog.showDialog( + base.selectedInstance.name, base.selectedInstance.ipAddress, + base.selectedInstance.port, base.selectedInstance.path, + base.selectedInstance.getProperty("useHttps") == "true", + base.selectedInstance.getProperty("userName"), base.selectedInstance.getProperty("password") + ); + } + } + + Button + { + id: removeButton + text: catalog.i18nc("@action:button", "Remove") + enabled: base.selectedInstance != null && base.selectedInstance.getProperty("manual") == "true" + onClicked: manager.removeManualInstance(base.selectedInstance.name) + } + + Button + { + id: rediscoverButton + text: catalog.i18nc("@action:button", "Refresh") + enabled: useZeroconf.checked + onClicked: manager.startDiscovery() + } + } + + Row + { + width: parent.width + spacing: UM.Theme.getSize("default_margin").width + + Item + { + width: Math.floor(parent.width * 0.4) + height: base.height - parent.y + + ScrollView + { + id: objectListContainer + frameVisible: true + width: parent.width + anchors.top: parent.top + anchors.bottom: objectListFooter.top + anchors.bottomMargin: UM.Theme.getSize("default_margin").height + + Rectangle + { + parent: viewport + anchors.fill: parent + color: palette.light + } + + ListView + { + id: listview + model: manager.discoveredInstances + onModelChanged: + { + var selectedId = manager.instanceId; + for(var i = 0; i < model.length; i++) { + if(model[i].getId() == selectedId) + { + currentIndex = i; + return + } + } + currentIndex = -1; + } + width: parent.width + currentIndex: activeIndex + onCurrentIndexChanged: + { + base.selectedInstance = listview.model[currentIndex]; + apiCheckDelay.throttledCheck(); + } + Component.onCompleted: manager.startDiscovery() + delegate: Rectangle + { + height: childrenRect.height + color: ListView.isCurrentItem ? palette.highlight : index % 2 ? palette.base : palette.alternateBase + width: parent.width + Label + { + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + anchors.right: parent.right + text: listview.model[index].name + color: parent.ListView.isCurrentItem ? palette.highlightedText : palette.text + elide: Text.ElideRight + font.italic: listview.model[index].key == manager.instanceId + } + + MouseArea + { + anchors.fill: parent; + onClicked: + { + if(!parent.ListView.isCurrentItem) + { + parent.ListView.view.currentIndex = index; + } + } + } + } + } + } + + Item + { + id: objectListFooter + + width: parent.width + anchors.bottom: parent.bottom + + CheckBox + { + id: useZeroconf + text: catalog.i18nc("@label", "Automatically discover local OctoPrint instances") + checked: boolCheck(UM.Preferences.getValue("octoprint/use_zeroconf")) + onClicked: + { + if(checked != boolCheck(UM.Preferences.getValue("octoprint/use_zeroconf"))) + { + UM.Preferences.setValue("octoprint/use_zeroconf", checked); + manager.startDiscovery(); + } + } + } + } + } + + Column + { + width: Math.floor(parent.width * 0.6) + spacing: UM.Theme.getSize("default_margin").height + Label + { + visible: base.selectedInstance != null + width: parent.width + wrapMode: Text.WordWrap + text: base.selectedInstance ? base.selectedInstance.name : "" + font.pointSize: 16 + elide: Text.ElideRight + } + Grid + { + visible: base.selectedInstance != null + width: parent.width + columns: 2 + rowSpacing: UM.Theme.getSize("default_lining").height + verticalItemAlignment: Grid.AlignVCenter + Label + { + width: Math.floor(parent.width * 0.2) + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Address") + } + Label + { + width: Math.floor(parent.width * 0.75) + wrapMode: Text.WordWrap + text: base.selectedInstance ? "%1:%2".arg(base.selectedInstance.ipAddress).arg(String(base.selectedInstance.port)) : "" + } + Label + { + width: Math.floor(parent.width * 0.2) + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Version") + } + Label + { + width: Math.floor(parent.width * 0.75) + wrapMode: Text.WordWrap + text: base.selectedInstance ? base.selectedInstance.octoPrintVersion : "" + } + Label + { + width: Math.floor(parent.width * 0.2) + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "API Key") + } + Row + { + spacing: UM.Theme.getSize("default_lining").width + TextField + { + id: apiKey + width: Math.floor(parent.parent.width * (requestApiKey.visible ? 0.5 : 0.8) - UM.Theme.getSize("default_margin").width) + echoMode: activeFocus ? TextInput.Normal : TextInput.Password + onTextChanged: apiCheckDelay.throttledCheck() + } + + Button + { + id: requestApiKey + visible: manager.instanceSupportsAppKeys + enabled: !manager.instanceApiKeyAccepted + text: catalog.i18nc("@action", "Request...") + onClicked: + { + manager.requestApiKey(base.selectedInstance.getId()); + } + } + + } + Label + { + width: Math.floor(parent.width * 0.2) + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Username") + } + Label + { + width: Math.floor(parent.width * 0.75) + wrapMode: Text.WordWrap + text: base.selectedInstance ? base.selectedInstance.octoPrintUserName : "" + } + + Connections + { + target: base + onSelectedInstanceChanged: + { + if(base.selectedInstance) + { + manager.probeAppKeySupport(base.selectedInstance.getId()); + apiCheckDelay.lastKey = "\0"; + apiKey.text = manager.getApiKey(base.selectedInstance.getId()); + apiKey.select(0,0); + } + } + } + Connections + { + target: manager + onAppKeyReceived: + { + apiCheckDelay.lastKey = "\0"; + apiKey.text = manager.getApiKey(base.selectedInstance.getId()) + apiKey.select(0,0); + } + } + Timer + { + id: apiCheckDelay + interval: 500 + + property bool checkOnTrigger: false + property string lastKey: "\0" + + function throttledCheck() + { + checkOnTrigger = true; + restart(); + } + function check() + { + if(apiKey.text != lastKey) + { + lastKey = apiKey.text; + manager.testApiKey(base.selectedInstance.getId(), apiKey.text); + checkOnTrigger = false; + restart(); + } + } + onTriggered: + { + if(checkOnTrigger) + { + check(); + } + } + } + } + + Label + { + visible: base.selectedInstance != null && text != "" + text: + { + var result = "" + if (apiKey.text == "") + { + result = catalog.i18nc("@label", "Please enter the API key to access OctoPrint."); + } + else + { + if(manager.instanceInError) + { + return catalog.i18nc("@label", "OctoPrint is not available.") + } + if(manager.instanceResponded) + { + if(manager.instanceApiKeyAccepted) + { + return ""; + } + else + { + result = catalog.i18nc("@label", "The API key is invalid."); + } + } + else + { + return catalog.i18nc("@label", "Checking the API key...") + } + } + result += " " + catalog.i18nc("@label", "You can get the API key through the OctoPrint web page."); + return result; + } + width: parent.width - UM.Theme.getSize("default_margin").width + wrapMode: Text.WordWrap + } + + Column + { + visible: base.selectedInstance != null + width: parent.width + spacing: UM.Theme.getSize("default_lining").height + + CheckBox + { + id: autoPrintCheckBox + text: catalog.i18nc("@label", "Start print job after uploading") + enabled: manager.instanceApiKeyAccepted + checked: Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_auto_print") != "false" + onClicked: + { + manager.setContainerMetaDataEntry(activeMachineId, "octoprint_auto_print", String(checked)) + } + } + CheckBox + { + id: autoSelectCheckBox + text: catalog.i18nc("@label", "Select print job after uploading") + enabled: manager.instanceApiKeyAccepted && !autoPrintCheckBox.checked + checked: Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_auto_select") == "true" + onClicked: + { + manager.setContainerMetaDataEntry(activeMachineId, "octoprint_auto_select", String(checked)) + } + } + Row + { + spacing: UM.Theme.getSize("default_margin").width + + CheckBox + { + id: autoPowerControlCheckBox + text: catalog.i18nc("@label", "Turn on printer with") + visible: autoPowerControlPlugs.visible + enabled: autoPrintCheckBox.checked + anchors.verticalCenter: autoPowerControlPlugs.verticalCenter + checked: manager.instanceApiKeyAccepted && Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_power_control") == "true" + onClicked: + { + manager.setContainerMetaDataEntry(activeMachineId, "octoprint_power_control", String(checked)) + } + } + Connections + { + target: manager + onInstanceAvailablePowerPluginsChanged: autoPowerControlPlugsModel.populateModel() + } + + ComboBox + { + id: autoPowerControlPlugs + visible: manager.instanceApiKeyAccepted && model.count > 0 + enabled: autoPrintCheckBox.checked + property bool populatingModel: false + textRole: "text" + model: ListModel + { + id: autoPowerControlPlugsModel + + Component.onCompleted: populateModel() + + function populateModel() + { + autoPowerControlPlugs.populatingModel = true; + clear(); + + var current_index = -1; + + var power_plugs = manager.instanceAvailablePowerPlugins; + var current_key = Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_power_plug"); + if (current_key == "" && power_plugs.length > 0) + { + current_key = power_plugs[0].key; + manager.setContainerMetaDataEntry(activeMachineId, "octoprint_power_plug", current_key); + } + + for(var i in power_plugs) + { + append({ + key: power_plugs[i].key, + text: power_plugs[i].text + }); + if(power_plugs[i].key == current_key) + { + current_index = i; + } + } + if(current_index == -1 && current_key != "") + { + append({ + key: current_key, + text: catalog.i18nc("@label", "Unknown plug") + }); + current_index = count - 1; + } + + autoPowerControlPlugs.currentIndex = current_index; + autoPowerControlPlugs.populatingModel = false; + } + } + onActivated: + { + if(!populatingModel && model.get(index)) + { + manager.setContainerMetaDataEntry(activeMachineId, "octoprint_power_plug", model.get(index).key); + } + } + + } + } + CheckBox + { + id: autoConnectCheckBox + text: catalog.i18nc("@label", "Connect to printer before sending print job") + enabled: manager.instanceApiKeyAccepted && autoPrintCheckBox.checked && !autoPowerControlCheckBox.checked + checked: enabled && Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_auto_connect") == "true" + onClicked: + { + manager.setContainerMetaDataEntry(activeMachineId, "octoprint_auto_connect", String(checked)) + } + } + CheckBox + { + id: storeOnSdCheckBox + text: catalog.i18nc("@label", "Store G-code on the SD card of the printer") + enabled: manager.instanceSupportsSd + checked: manager.instanceApiKeyAccepted && Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_store_sd") == "true" + onClicked: + { + manager.setContainerMetaDataEntry(activeMachineId, "octoprint_store_sd", String(checked)) + } + } + Label + { + visible: storeOnSdCheckBox.checked + wrapMode: Text.WordWrap + width: parent.width + text: catalog.i18nc("@label", "Note: Transferring files to the printer SD card takes very long. Using this option is not recommended.") + } + CheckBox + { + id: confirmUploadOptionsCheckBox + text: catalog.i18nc("@label", "Confirm print job options before sending") + checked: Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_confirm_upload_options") == "true" + onClicked: + { + manager.setContainerMetaDataEntry(activeMachineId, "octoprint_confirm_upload_options", String(checked)) + } + } + CheckBox + { + id: showCameraCheckBox + text: catalog.i18nc("@label", "Show webcam image") + enabled: manager.instanceSupportsCamera + checked: manager.instanceApiKeyAccepted && Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_show_camera") != "false" + onClicked: + { + manager.setContainerMetaDataEntry(activeMachineId, "octoprint_show_camera", String(checked)) + } + } + CheckBox + { + id: fixGcodeFlavor + text: catalog.i18nc("@label", "Set G-code flavor to \"Marlin\"") + checked: true + visible: machineGCodeFlavorProvider.properties.value == "UltiGCode" + } + Label + { + text: catalog.i18nc("@label", "Note: Printing UltiGCode using OctoPrint does not work. Setting G-code flavor to \"Marlin\" fixes this, but overrides material settings on your printer.") + width: parent.width - UM.Theme.getSize("default_margin").width + wrapMode: Text.WordWrap + visible: fixGcodeFlavor.visible + } + } + + Flow + { + visible: base.selectedInstance != null + spacing: UM.Theme.getSize("default_margin").width + + Button + { + text: catalog.i18nc("@action", "Open in browser...") + onClicked: manager.openWebPage(base.selectedInstance.baseURL) + } + + Button + { + text: + { + if (base.selectedInstance !== null) + { + if (base.selectedInstance.getId() == manager.instanceId && manager.instanceApiKeyAccepted) + { + return catalog.i18nc("@action:button", "Disconnect"); + } + } + return catalog.i18nc("@action:button", "Connect") + } + enabled: base.selectedInstance !== null && apiKey.text != "" && manager.instanceApiKeyAccepted + onClicked: + { + if(base.selectedInstance.getId() == manager.instanceId && manager.instanceApiKeyAccepted) { + manager.setInstanceId("") + } + else + { + manager.setInstanceId(base.selectedInstance.getId()) + manager.setApiKey(apiKey.text) + + if(fixGcodeFlavor.visible) + { + manager.applyGcodeFlavorFix(fixGcodeFlavor.checked) + } + } + completed() + } + } + } + } + } + } + + UM.SettingPropertyProvider + { + id: machineGCodeFlavorProvider + + containerStackId: activeMachineId + key: "machine_gcode_flavor" + watchedProperties: [ "value" ] + storeIndex: 4 + } + + ManualInstanceDialog + { + id: manualInstanceDialog + } +} \ No newline at end of file diff --git a/qml_controls1/ManualInstanceDialog.qml b/qml_controls1/ManualInstanceDialog.qml new file mode 100644 index 0000000..d945963 --- /dev/null +++ b/qml_controls1/ManualInstanceDialog.qml @@ -0,0 +1,342 @@ +// Copyright (c) 2021 Aldo Hoeben / fieldOfView +// OctoPrintPlugin is released under the terms of the AGPLv3 or higher. + +import UM 1.2 as UM +import Cura 1.0 as Cura + +import QtQuick 2.2 +import QtQuick.Controls 1.1 + + +UM.Dialog +{ + id: manualInstanceDialog + property string previousName + property string previousAddress + property alias nameText: nameField.text + property alias addressText: addressField.text + property alias portText: portField.text + property alias pathText: pathField.text + property alias userNameText: userNameField.text + property alias passwordText: passwordField.text + property alias httpsChecked: httpsCheckbox.checked + + title: catalog.i18nc("@title:window", "Manually added OctoPrint instance") + + minimumWidth: 400 * screenScaleFactor + minimumHeight: 280 * screenScaleFactor + width: minimumWidth + height: minimumHeight + + signal showDialog(string name, string address, string port, string path_, bool useHttps, string userName, string password) + onShowDialog: + { + previousName = name; + nameText = name; + addressText = address; + previousAddress = address; + portText = port; + pathText = path_; + httpsChecked = useHttps; + userNameText = userName; + passwordText = password; + + manualInstanceDialog.show(); + if (nameText != "") + { + nameField.forceActiveFocus(); + } + else + { + addressField.forceActiveFocus(); + } + } + + onAccepted: + { + if(previousName != nameText) + { + manager.removeManualInstance(previousName); + } + if(portText == "") + { + portText = (!httpsChecked) ? base.defaultHTTP : base.defaultHTTPS; // default http or https port + } + if(pathText.substr(0,1) != "/") + { + pathText = "/" + pathText; // ensure absolute path + } + manager.setManualInstance( + nameText, + addressText, + parseInt(portText), + pathText, + httpsChecked, + userNameText, + passwordText + ); + } + + function parseAddressField() + { + var useAddressForName = false + if (nameText == manualInstanceDialog.previousAddress || nameText == "") + { + useAddressForName = true + } + manualInstanceDialog.previousAddress = addressText + + var index = addressText.indexOf("://") + if(index >= 0) + { + var protocol = addressText.substr(0,index) + if(protocol.toLowerCase() == "http" && httpsChecked) { + httpsChecked = false + if(portField.text == defaultHTTPS) + { + portField.text = defaultHTTP + } + } + else if(protocol.toLowerCase() == "https" && !httpsChecked) { + httpsChecked = true + if(portField.text == defaultHTTP) + { + portField.text = defaultHTTPS + } + } + addressText = addressText.substr(index + 3) + } + + index = addressText.indexOf("@") + if(index >= 0) + { + var auth = addressText.substr(0,index).split(":") + userNameText = auth[0] + if(auth.length>1) + { + passwordText = auth[1] + } + addressText = addressText.substr(index+1) + } + + index = addressText.indexOf("/") + if(index >= 0) + { + pathField.text = addressText.substr(index) + addressText = addressText.substr(0,index) + } + + index = addressText.indexOf(":") + if(index >= 0) + { + var port = parseInt(addressText.substr(index+1)) + if (!isNaN(port)) { + portField.text = port.toString() + } + addressText = addressText.substr(0,index) + } + + if(useAddressForName) + { + nameText = addressText + } + } + + Grid + { + Timer + { + id: parseAddressFieldTimer + interval: 1000 + onTriggered: manualInstanceDialog.parseAddressField() + } + + columns: 2 + width: parent.width + verticalItemAlignment: Grid.AlignVCenter + rowSpacing: UM.Theme.getSize("default_lining").height + columnSpacing: UM.Theme.getSize("default_margin").width + + Label + { + text: catalog.i18nc("@label","Instance Name") + width: Math.floor(parent.width * 0.4) + } + + TextField + { + id: nameField + maximumLength: 20 + width: Math.floor(parent.width * 0.6) + validator: RegExpValidator + { + regExp: /[a-zA-Z0-9\.\-\_\:\[\]]*/ + } + } + + Label + { + text: catalog.i18nc("@label","IP Address or Hostname") + width: Math.floor(parent.width * 0.4) + } + + TextField + { + id: addressField + maximumLength: 253 + width: Math.floor(parent.width * 0.6) + validator: RegExpValidator + { + regExp: /[a-zA-Z0-9\.\-\_\:\/\@]*/ + } + onTextChanged: parseAddressFieldTimer.restart() + } + + Label + { + text: catalog.i18nc("@label","Port Number") + width: Math.floor(parent.width * 0.4) + } + + TextField + { + id: portField + maximumLength: 5 + width: Math.floor(parent.width * 0.6) + validator: RegExpValidator + { + regExp: /[0-9]*/ + } + onTextChanged: + { + if(httpsChecked && text == base.defaultHTTP) + { + httpsChecked = false + } + else if(!httpsChecked && text == base.defaultHTTPS) + { + httpsChecked = true + } + } + } + + Label + { + text: catalog.i18nc("@label","Path") + width: Math.floor(parent.width * 0.4) + } + + TextField + { + id: pathField + maximumLength: 30 + width: Math.floor(parent.width * 0.6) + validator: RegExpValidator + { + regExp: /[a-zA-Z0-9\.\-\_\/]*/ + } + } + + Item + { + width: 1 + height: UM.Theme.getSize("default_margin").height + } + + Item + { + width: 1 + height: UM.Theme.getSize("default_margin").height + } + + Item + { + width: 1 + height: 1 + } + + Label + { + wrapMode: Text.WordWrap + width: Math.floor(parent.width * 0.6) + text: catalog.i18nc("@label","In order to use HTTPS or a HTTP username and password, you need to configure a reverse proxy or another service.") + } + + Label + { + text: catalog.i18nc("@label","Use HTTPS") + width: Math.floor(parent.width * 0.4) + } + + CheckBox + { + id: httpsCheckbox + width: height + height: userNameField.height + onClicked: + { + if(checked && portField.text == base.defaultHTTP) + { + portField.text = base.defaultHTTPS + } + else if(!checked && portField.text == base.defaultHTTPS) + { + portField.text = base.defaultHTTP + } + } + } + + Label + { + text: catalog.i18nc("@label","HTTP username") + width: Math.floor(parent.width * 0.4) + } + + TextField + { + id: userNameField + maximumLength: 64 + width: Math.floor(parent.width * 0.6) + } + + Label + { + text: catalog.i18nc("@label","HTTP password") + width: Math.floor(parent.width * 0.4) + } + + TextField + { + id: passwordField + maximumLength: 64 + width: Math.floor(parent.width * 0.6) + echoMode: TextInput.PasswordEchoOnEdit + } + } + + rightButtons: [ + Button { + text: catalog.i18nc("@action:button","Cancel") + onClicked: + { + manualInstanceDialog.reject() + manualInstanceDialog.hide() + } + }, + Button { + text: catalog.i18nc("@action:button", "Ok") + onClicked: + { + if (parseAddressFieldTimer.running) + { + parseAddressFieldTimer.stop() + manualInstanceDialog.parseAddressField() + } + manualInstanceDialog.accept() + manualInstanceDialog.hide() + } + enabled: manualInstanceDialog.nameText.trim() != "" && manualInstanceDialog.addressText.trim() != "" + isDefault: true + } + ] +} \ No newline at end of file diff --git a/qml/MonitorItem3x.qml b/qml_controls1/MonitorItem3x.qml similarity index 100% rename from qml/MonitorItem3x.qml rename to qml_controls1/MonitorItem3x.qml diff --git a/qml/MonitorItem4x.qml b/qml_controls1/MonitorItem4x.qml similarity index 100% rename from qml/MonitorItem4x.qml rename to qml_controls1/MonitorItem4x.qml diff --git a/qml_controls1/OctoPrintComponents.qml b/qml_controls1/OctoPrintComponents.qml new file mode 100644 index 0000000..466cf74 --- /dev/null +++ b/qml_controls1/OctoPrintComponents.qml @@ -0,0 +1,40 @@ +// Copyright (c) 2021 Aldo Hoeben / fieldOfView +// OctoPrintPlugin is released under the terms of the AGPLv3 or higher. + +import UM 1.2 as UM +import Cura 1.0 as Cura + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +Item +{ + id: base + + property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 + property bool octoPrintConnected: printerConnected && Cura.MachineManager.printerOutputDevices[0].toString().indexOf("OctoPrintOutputDevice") == 0 + + Button + { + objectName: "openOctoPrintButton" + height: UM.Theme.getSize("save_button_save_to_button").height + tooltip: catalog.i18nc("@info:tooltip", "Open the OctoPrint web interface") + text: catalog.i18nc("@action:button", "OctoPrint...") + style: + { + if(UM.Theme.styles.hasOwnProperty("print_setup_action_button")) { + return UM.Theme.styles.print_setup_action_button + } + else + { + return UM.Theme.styles.sidebar_action_button + } + } + onClicked: manager.openWebPage(Cura.MachineManager.printerOutputDevices[0].baseURL) + visible: octoPrintConnected + } + + UM.I18nCatalog{id: catalog; name:"octoprint"} +} \ No newline at end of file diff --git a/qml_controls1/UploadOptions.qml b/qml_controls1/UploadOptions.qml new file mode 100644 index 0000000..723e267 --- /dev/null +++ b/qml_controls1/UploadOptions.qml @@ -0,0 +1,116 @@ +// Copyright (c) 2021 Aldo Hoeben / fieldOfView +// OctoPrintPlugin is released under the terms of the AGPLv3 or higher. + +import UM 1.2 as UM +import Cura 1.0 as Cura + +import QtQuick 2.2 +import QtQuick.Controls 1.1 + +UM.Dialog +{ + id: uploadOptions + + title: catalog.i18nc("@action:button", "Upload to OctoPrint Options") + + minimumWidth: screenScaleFactor * 400 + minimumHeight: screenScaleFactor * 150 + + onAccepted: manager.acceptOptionsDialog() + + Column { + anchors.fill: parent + + UM.I18nCatalog{id: catalog; name:"octoprint"} + + Grid + { + columns: 2 + width: parent.width + verticalItemAlignment: Grid.AlignVCenter + rowSpacing: UM.Theme.getSize("default_lining").height + columnSpacing: UM.Theme.getSize("default_margin").width + + Label + { + id: pathLabel + text: catalog.i18nc("@label", "Path") + } + + TextField { + id: pathField + text: manager.filePath + maximumLength: 256 + width: parent.width - Math.max(pathLabel.width, fileLabel.width) - UM.Theme.getSize("default_margin").width + horizontalAlignment: TextInput.AlignLeft + validator: RegExpValidator + { + regExp: /.*/ + } + onTextChanged: manager.filePath = text + } + + Label + { + id: fileLabel + text: catalog.i18nc("@label", "Filename") + } + + TextField { + id: nameField + text: manager.fileName + maximumLength: 100 + width: parent.width - Math.max(pathLabel.width, fileLabel.width) - UM.Theme.getSize("default_margin").width + horizontalAlignment: TextInput.AlignLeft + validator: RegExpValidator + { + regExp: /[^\/]*/ + } + onTextChanged: manager.fileName = text + } + Item + { + width: 1 + height: UM.Theme.getSize("default_margin").height + } + Label + { + text: catalog.i18nc("@label", "A file extenstion will be added automatically.") + } + } + + Item + { + width: 1 + height: UM.Theme.getSize("default_margin").height + } + + CheckBox + { + id: autoPrintCheckBox + text: catalog.i18nc("@label", "Start print job after uploading") + checked: manager.autoPrint + onClicked: manager.autoPrint = checked + } + CheckBox + { + id: autoSelectCheckBox + text: catalog.i18nc("@label", "Select print job after uploading") + enabled: !autoPrintCheckBox.checked + checked: autoPrintCheckBox.checked || manager.autoSelect + onClicked: manager.autoSelect = checked + } + } + + rightButtons: [ + Button { + text: catalog.i18nc("@action:button", "Cancel") + onClicked: uploadOptions.reject() + }, + Button { + text: catalog.i18nc("@action:button", "OK") + onClicked: uploadOptions.accept() + isDefault: true + } + ] +}