diff --git a/cura/UI/WelcomePagesModel.py b/cura/UI/WelcomePagesModel.py index 2b58218cd6..25765bf777 100644 --- a/cura/UI/WelcomePagesModel.py +++ b/cura/UI/WelcomePagesModel.py @@ -1,10 +1,12 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from collections import deque import os -from typing import TYPE_CHECKING, Optional, List, Dict, Any +from typing import TYPE_CHECKING, Optional, List, Dict, Any, Deque -from PyQt5.QtCore import QUrl, Qt +from PyQt5.QtCore import QUrl, Qt, pyqtSlot, pyqtProperty, pyqtSignal +from UM.Logger import Logger from UM.Qt.ListModel import ListModel from UM.Resources import Resources @@ -18,7 +20,6 @@ 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) @@ -28,6 +29,99 @@ class WelcomePagesModel(ListModel): self._pages = [] # type: List[Dict[str, Any]] + self._current_page_index = 0 + # Store all the previous page indices so it can go back. + self._previous_page_indices_stack = deque() # type: Deque[int] + + allFinished = pyqtSignal() # emitted when all steps have been finished + currentPageIndexChanged = pyqtSignal() + + @pyqtProperty(int, notify = currentPageIndexChanged) + def currentPageIndex(self) -> int: + return self._current_page_index + + # Returns a float number in [0, 1] which indicates the current progress. + @pyqtProperty(float, notify = currentPageIndexChanged) + def currentProgress(self) -> float: + return self._current_page_index / len(self._items) + + # Indicates if the current page is the last page. + @pyqtProperty(bool, notify = currentPageIndexChanged) + def isCurrentPageLast(self) -> bool: + return self._current_page_index == len(self._items) - 1 + + def _setCurrentPageIndex(self, page_index: int) -> None: + if page_index != self._current_page_index: + self._previous_page_indices_stack.append(self._current_page_index) + self._current_page_index = page_index + self.currentPageIndexChanged.emit() + + # Goes to the next page. + @pyqtSlot() + def goToNextPage(self) -> None: + page_item = self._items[self._current_page_index] + # Check if there's a "next_page_id" assigned. If so, go to that page. Otherwise, go to the page with the + # current index + 1. + next_page_id = page_item.get("next_page_id") + next_page_index = self._current_page_index + 1 + if next_page_id: + idx = self.getPageIndexById(next_page_id) + if idx is None: + # FIXME: If we cannot find the next page, we cannot do anything here. + Logger.log("e", "Cannot find page with ID [%s]", next_page_id) + return + next_page_index = idx + + # If we have reached the last page, emit allFinished signal and reset. + if next_page_index == len(self._items): + self.allFinished.emit() + self.resetState() + + # Move to the next page + self._setCurrentPageIndex(next_page_index) + + # Goes to the previous page. If there's no previous page, do nothing. + @pyqtSlot() + def goToPreviousPage(self) -> None: + if len(self._previous_page_indices_stack) == 0: + Logger.log("i", "No previous page, do nothing") + return + + previous_page_index = self._previous_page_indices_stack.pop() + self._current_page_index = previous_page_index + self.currentPageIndexChanged.emit() + + # Sets the current page to the given page ID. If the page ID is not found, do nothing. + @pyqtSlot(str) + def goToPage(self, page_id: str) -> None: + page_index = self.getPageIndexById(page_id) + if page_index is None: + # FIXME: If we cannot find the next page, we cannot do anything here. + Logger.log("e", "Cannot find page with ID [%s]", page_index) + return + + # Move to that page + self._setCurrentPageIndex(page_index) + + # Resets the state of the WelcomePagesModel. This functions does the following: + # - Resets current_page_index to 0 + # - Clears the previous page indices stack + @pyqtSlot() + def resetState(self) -> None: + self._current_page_index = 0 + self._previous_page_indices_stack.clear() + + self.currentPageIndexChanged.emit() + + # Gets the page index with the given page ID. If the page ID doesn't exist, returns None. + def getPageIndexById(self, page_id: str) -> Optional[int]: + page_idx = None + for idx, page_item in enumerate(self._items): + if page_item["id"] == page_id: + page_idx = idx + break + return page_idx + # Convenience function to get QUrl path to pages that's located in "resources/qml/WelcomePages". def _getBuiltinWelcomePagePath(self, page_filename: str) -> "QUrl": from cura.CuraApplication import CuraApplication @@ -35,7 +129,6 @@ class WelcomePagesModel(ListModel): os.path.join("WelcomePages", page_filename))) def initialize(self) -> None: - # Add default welcome pages self._pages.append({"id": "welcome", "page_url": self._getBuiltinWelcomePagePath("WelcomeContent.qml"), @@ -51,9 +144,11 @@ class WelcomePagesModel(ListModel): }) self._pages.append({"id": "add_network_or_local_printer", "page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"), + "next_page_id": "cloud", }) self._pages.append({"id": "add_printer_by_ip", "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), + "next_page_id": "cloud", }) self._pages.append({"id": "cloud", "page_url": self._getBuiltinWelcomePagePath("CloudContent.qml"), diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 37a34ebb52..3572d299bb 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -76,12 +76,11 @@ UM.MainWindow if (CuraApplication.needToShowUserAgreement) { - welcomeDialog.visible = true; - welcomeDialog.currentStep = 0; + welcomeDialog.show() } else { - welcomeDialog.visible = false; + welcomeDialog.close() } // TODO: While the new onboarding process contains the user-agreement, // it should probably not entirely rely on 'needToShowUserAgreement' for show/hide. diff --git a/resources/qml/WelcomePages/AddPrinterByIpContent.qml b/resources/qml/WelcomePages/AddPrinterByIpContent.qml index 1ea0c1f19d..4fdffa5a79 100644 --- a/resources/qml/WelcomePages/AddPrinterByIpContent.qml +++ b/resources/qml/WelcomePages/AddPrinterByIpContent.qml @@ -247,7 +247,7 @@ Item text: catalog.i18nc("@button", "Cancel") width: UM.Theme.getSize("action_button").width fixedWidthMode: true - onClicked: base.goToPage("add_printer_by_selection") + onClicked: base.showPreviousPage() } Cura.PrimaryButton diff --git a/resources/qml/WelcomePages/WelcomeDialog.qml b/resources/qml/WelcomePages/WelcomeDialog.qml index c5c1894764..9704a318b6 100644 --- a/resources/qml/WelcomePages/WelcomeDialog.qml +++ b/resources/qml/WelcomePages/WelcomeDialog.qml @@ -14,6 +14,7 @@ Window { UM.I18nCatalog { id: catalog; name: "cura" } + id: dialog title: catalog.i18nc("@title", "Welcome to Ultimaker Cura") modality: Qt.ApplicationModal flags: Qt.Window | Qt.FramelessWindowHint @@ -24,14 +25,21 @@ Window property int shadowOffset: 1 * screenScaleFactor - property alias currentStep: stepPanel.currentStep + property var model: CuraApplication.getWelcomePagesModel() + + onVisibleChanged: + { + if (visible) + { + model.resetState() + } + } WizardPanel { id: stepPanel anchors.fill: parent - currentStep: 0 - model: CuraApplication.getWelcomePagesModel() + model: dialog.model } // Drop shadow around the panel @@ -50,7 +58,7 @@ Window // Close this dialog when there's no more page to show Connections { - target: stepPanel - onPassLastPage: close() + target: model + onAllFinished: close() } } diff --git a/resources/qml/WelcomePages/WizardPanel.qml b/resources/qml/WelcomePages/WizardPanel.qml index 61d9244b93..57f2873e80 100644 --- a/resources/qml/WelcomePages/WizardPanel.qml +++ b/resources/qml/WelcomePages/WizardPanel.qml @@ -20,70 +20,21 @@ Item clip: true - property int currentStep: 0 - property int totalStepCount: (model == null) ? 0 : model.count - property real progressValue: (totalStepCount == 0) ? 0 : (currentStep / totalStepCount) - - property var currentItem: (model == null) ? null : model.getItem(currentStep) + property var currentItem: (model == null) ? null : model.getItem(model.currentPageIndex) property var model: null + // Convenience properties + property var progressValue: model == null ? 0 : model.currentProgress + property string pageUrl: currentItem == null ? null : currentItem.page_url + signal showNextPage() signal showPreviousPage() - signal passLastPage() // Emitted when there is no more page to show signal goToPage(string page_id) // Go to a specific page by the given page_id. - onShowNextPage: - { - if (currentStep < totalStepCount - 1) - { - currentStep++ - } - else - { - passLastPage() - } - } - - onShowPreviousPage: - { - if (currentStep > 0) - { - currentStep-- - } - } - - onGoToPage: - { - // find the page index - var page_index = -1 - for (var i = 0; i < base.model.count; i++) - { - const item = base.model.getItem(i) - if (item.id == page_id) - { - page_index = i - break - } - } - if (page_index > 0) - { - currentStep = page_index - } - } - - onVisibleChanged: - { - if (visible) - { - base.currentStep = 0 - base.currentItem = base.model.getItem(base.currentStep) - } - } - - onModelChanged: - { - base.currentStep = 0 - } + // Call the corresponding functions in the model + onShowNextPage: model.goToNextPage() + onShowPreviousPage: model.goToPreviousPage() + onGoToPage: model.goToPage(page_id) Rectangle // Panel background { @@ -108,13 +59,13 @@ Item id: contentLoader anchors { - margins: base.contentMargins + margins: UM.Theme.getSize("default_margin").width top: progressBar.bottom bottom: parent.bottom left: parent.left right: parent.right } - source: base.currentItem.page_url + source: base.pageUrl } } }