Merge branch 'CURA-7129_license_window_design' of github.com:Ultimaker/Cura

This commit is contained in:
Jaime van Kessel 2020-01-24 16:56:23 +01:00
commit 61a3122a78
No known key found for this signature in database
GPG key ID: 3710727397403C91
6 changed files with 112 additions and 41 deletions

View file

@ -5,6 +5,7 @@ import QtQuick 2.10
import QtQuick.Dialogs 1.1 import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles // TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
@ -20,31 +21,63 @@ UM.Dialog
minimumHeight: UM.Theme.getSize("license_window_minimum").height minimumHeight: UM.Theme.getSize("license_window_minimum").height
width: minimumWidth width: minimumWidth
height: minimumHeight height: minimumHeight
backgroundColor: UM.Theme.getColor("main_background")
margin: screenScaleFactor * 10
Item ColumnLayout
{ {
anchors.fill: parent anchors.fill: parent
spacing: UM.Theme.getSize("thick_margin").height
UM.I18nCatalog{id: catalog; name: "cura"} UM.I18nCatalog{id: catalog; name: "cura"}
Label Label
{ {
id: licenseHeader id: licenseHeader
anchors.top: parent.top Layout.fillWidth: true
anchors.left: parent.left text: catalog.i18nc("@label", "You need to accept the license to install the package")
anchors.right: parent.right
text: licenseModel.headerText
wrapMode: Text.Wrap wrapMode: Text.Wrap
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
Row {
id: packageRow
anchors.left: parent.left
anchors.right: parent.right
height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").width
leftPadding: UM.Theme.getSize("narrow_margin").width
Image
{
id: icon
width: 30 * screenScaleFactor
height: width
fillMode: Image.PreserveAspectFit
source: licenseModel.iconUrl || "../../images/logobot.svg"
mipmap: true
}
Label
{
id: packageName
text: licenseModel.packageName
font.bold: true
anchors.verticalCenter: icon.verticalCenter
height: contentHeight
wrapMode: Text.Wrap
renderType: Text.NativeRendering
}
}
TextArea TextArea
{ {
id: licenseText id: licenseText
anchors.top: licenseHeader.bottom Layout.fillWidth: true
anchors.bottom: parent.bottom Layout.fillHeight: true
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.topMargin: UM.Theme.getSize("default_margin").height
readOnly: true readOnly: true
text: licenseModel.licenseText text: licenseModel.licenseText
@ -57,7 +90,7 @@ UM.Dialog
leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width
rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width
text: catalog.i18nc("@button", "Agree") text: licenseModel.acceptButtonText
onClicked: { handler.onLicenseAccepted() } onClicked: { handler.onLicenseAccepted() }
} }
] ]
@ -67,7 +100,7 @@ UM.Dialog
Cura.SecondaryButton Cura.SecondaryButton
{ {
id: declineButton id: declineButton
text: catalog.i18nc("@button", "Decline and remove from account") text: licenseModel.declineButtonText
onClicked: { handler.onLicenseDeclined() } onClicked: { handler.onLicenseDeclined() }
} }
] ]

View file

@ -62,7 +62,8 @@ class DownloadPresenter:
"received": 0, "received": 0,
"total": 1, # make sure this is not considered done yet. Also divByZero-safe "total": 1, # make sure this is not considered done yet. Also divByZero-safe
"file_written": None, "file_written": None,
"request_data": request_data "request_data": request_data,
"package_model": item
} }
self._started = True self._started = True
@ -128,7 +129,14 @@ class DownloadPresenter:
if not item["file_written"]: if not item["file_written"]:
return False return False
success_items = {package_id : value["file_written"] for package_id, value in self._progress.items()} success_items = {
package_id:
{
"package_path": value["file_written"],
"icon_url": value["package_model"]["icon_url"]
}
for package_id, value in self._progress.items()
}
error_items = [package_id for package_id in self._error] error_items = [package_id for package_id in self._error]
self._progress_message.hide() self._progress_message.hide()

View file

@ -6,31 +6,52 @@ catalog = i18nCatalog("cura")
# Model for the ToolboxLicenseDialog # Model for the ToolboxLicenseDialog
class LicenseModel(QObject): class LicenseModel(QObject):
dialogTitleChanged = pyqtSignal() DEFAULT_DECLINE_BUTTON_TEXT = catalog.i18nc("@button", "Decline")
headerChanged = pyqtSignal() ACCEPT_BUTTON_TEXT = catalog.i18nc("@button", "Agree")
licenseTextChanged = pyqtSignal()
def __init__(self) -> None: dialogTitleChanged = pyqtSignal()
packageNameChanged = pyqtSignal()
licenseTextChanged = pyqtSignal()
iconChanged = pyqtSignal()
def __init__(self, decline_button_text: str = DEFAULT_DECLINE_BUTTON_TEXT) -> None:
super().__init__() super().__init__()
self._current_page_idx = 0 self._current_page_idx = 0
self._page_count = 1 self._page_count = 1
self._dialogTitle = "" self._dialogTitle = ""
self._header_text = ""
self._license_text = "" self._license_text = ""
self._package_name = "" self._package_name = ""
self._icon_url = ""
self._decline_button_text = decline_button_text
@pyqtProperty(str, constant = True)
def acceptButtonText(self):
return self.ACCEPT_BUTTON_TEXT
@pyqtProperty(str, constant = True)
def declineButtonText(self):
return self._decline_button_text
@pyqtProperty(str, notify=dialogTitleChanged) @pyqtProperty(str, notify=dialogTitleChanged)
def dialogTitle(self) -> str: def dialogTitle(self) -> str:
return self._dialogTitle return self._dialogTitle
@pyqtProperty(str, notify=headerChanged) @pyqtProperty(str, notify=packageNameChanged)
def headerText(self) -> str: def packageName(self) -> str:
return self._header_text return self._package_name
def setPackageName(self, name: str) -> None: def setPackageName(self, name: str) -> None:
self._header_text = name + ": " + catalog.i18nc("@label", "This plugin contains a license.\nYou need to accept this license to install this plugin.\nDo you agree with the terms below?") self._package_name = name
self.headerChanged.emit() self.packageNameChanged.emit()
@pyqtProperty(str, notify=iconChanged)
def iconUrl(self) -> str:
return self._icon_url
def setIconUrl(self, url: str):
self._icon_url = url
self.iconChanged.emit()
@pyqtProperty(str, notify=licenseTextChanged) @pyqtProperty(str, notify=licenseTextChanged)
def licenseText(self) -> str: def licenseText(self) -> str:

View file

@ -17,6 +17,7 @@ class LicensePresenter(QObject):
def __init__(self, app: CuraApplication) -> None: def __init__(self, app: CuraApplication) -> None:
super().__init__() super().__init__()
self._catalog = i18nCatalog("cura")
self._dialog = None # type: Optional[QObject] self._dialog = None # type: Optional[QObject]
self._package_manager = app.getPackageManager() # type: PackageManager self._package_manager = app.getPackageManager() # type: PackageManager
# Emits List[Dict[str, [Any]] containing for example # Emits List[Dict[str, [Any]] containing for example
@ -25,7 +26,8 @@ class LicensePresenter(QObject):
self._current_package_idx = 0 self._current_package_idx = 0
self._package_models = [] # type: List[Dict] self._package_models = [] # type: List[Dict]
self._license_model = LicenseModel() # type: LicenseModel decline_button_text = self._catalog.i18nc("@button", "Decline and remove from account")
self._license_model = LicenseModel(decline_button_text=decline_button_text) # type: LicenseModel
self._app = app self._app = app
@ -34,7 +36,7 @@ class LicensePresenter(QObject):
## Show a license dialog for multiple packages where users can read a license and accept or decline them ## Show a license dialog for multiple packages where users can read a license and accept or decline them
# \param plugin_path: Root directory of the Toolbox plugin # \param plugin_path: Root directory of the Toolbox plugin
# \param packages: Dict[package id, file path] # \param packages: Dict[package id, file path]
def present(self, plugin_path: str, packages: Dict[str, str]) -> None: def present(self, plugin_path: str, packages: Dict[str, Dict[str, str]]) -> None:
path = os.path.join(plugin_path, self._compatibility_dialog_path) path = os.path.join(plugin_path, self._compatibility_dialog_path)
self._initState(packages) self._initState(packages)
@ -42,7 +44,7 @@ class LicensePresenter(QObject):
if self._dialog is None: if self._dialog is None:
context_properties = { context_properties = {
"catalog": i18nCatalog("cura"), "catalog": self._catalog,
"licenseModel": self._license_model, "licenseModel": self._license_model,
"handler": self "handler": self
} }
@ -60,18 +62,20 @@ class LicensePresenter(QObject):
self._package_models[self._current_package_idx]["accepted"] = False self._package_models[self._current_package_idx]["accepted"] = False
self._checkNextPage() self._checkNextPage()
def _initState(self, packages: Dict[str, str]) -> None: def _initState(self, packages: Dict[str, Dict[str, str]]) -> None:
self._package_models = [ self._package_models = [
{ {
"package_id" : package_id, "package_id" : package_id,
"package_path" : package_path, "package_path" : item["package_path"],
"icon_url" : item["icon_url"],
"accepted" : None #: None: no answer yet "accepted" : None #: None: no answer yet
} }
for package_id, package_path in packages.items() for package_id, item in packages.items()
] ]
def _presentCurrentPackage(self) -> None: def _presentCurrentPackage(self) -> None:
package_model = self._package_models[self._current_package_idx] package_model = self._package_models[self._current_package_idx]
package_info = self._package_manager.getPackageInfo(package_model["package_path"])
license_content = self._package_manager.getPackageLicense(package_model["package_path"]) license_content = self._package_manager.getPackageLicense(package_model["package_path"])
if license_content is None: if license_content is None:
# Implicitly accept when there is no license # Implicitly accept when there is no license
@ -79,7 +83,8 @@ class LicensePresenter(QObject):
return return
self._license_model.setCurrentPageIdx(self._current_package_idx) self._license_model.setCurrentPageIdx(self._current_package_idx)
self._license_model.setPackageName(package_model["package_id"]) self._license_model.setPackageName(package_info["display_name"])
self._license_model.setIconUrl(package_model["icon_url"])
self._license_model.setLicenseText(license_content) self._license_model.setLicenseText(license_content)
if self._dialog: if self._dialog:
self._dialog.open() # Does nothing if already open self._dialog.open() # Does nothing if already open

View file

@ -63,9 +63,9 @@ class SyncOrchestrator(Extension):
self._download_presenter.download(mutations) self._download_presenter.download(mutations)
## Called when a set of packages have finished downloading ## Called when a set of packages have finished downloading
# \param success_items: Dict[package_id, file_path] # \param success_items: Dict[package_id, Dict[str, str]]
# \param error_items: List[package_id] # \param error_items: List[package_id]
def _onDownloadFinished(self, success_items: Dict[str, str], error_items: List[str]) -> None: def _onDownloadFinished(self, success_items: Dict[str, Dict[str, str]], error_items: List[str]) -> None:
if error_items: if error_items:
message = i18n_catalog.i18nc("@info:generic", "{} plugins failed to download".format(len(error_items))) message = i18n_catalog.i18nc("@info:generic", "{} plugins failed to download".format(len(error_items)))
self._showErrorMessage(message) self._showErrorMessage(message)

View file

@ -154,10 +154,11 @@ class Toolbox(QObject, Extension):
def getLicenseDialogPluginFileLocation(self) -> str: def getLicenseDialogPluginFileLocation(self) -> str:
return self._license_dialog_plugin_file_location return self._license_dialog_plugin_file_location
def openLicenseDialog(self, plugin_name: str, license_content: str, plugin_file_location: str) -> None: def openLicenseDialog(self, plugin_name: str, license_content: str, plugin_file_location: str, icon_url: str) -> None:
# Set page 1/1 when opening the dialog for a single package # Set page 1/1 when opening the dialog for a single package
self._license_model.setCurrentPageIdx(0) self._license_model.setCurrentPageIdx(0)
self._license_model.setPageCount(1) self._license_model.setPageCount(1)
self._license_model.setIconUrl(icon_url)
self._license_model.setPackageName(plugin_name) self._license_model.setPackageName(plugin_name)
self._license_model.setLicenseText(license_content) self._license_model.setLicenseText(license_content)
@ -670,14 +671,17 @@ class Toolbox(QObject, Extension):
return return
license_content = self._package_manager.getPackageLicense(file_path) license_content = self._package_manager.getPackageLicense(file_path)
package_id = package_info["package_id"]
if license_content is not None: if license_content is not None:
self.openLicenseDialog(package_info["package_id"], license_content, file_path) # get the icon url for package_id, make sure the result is a string, never None
icon_url = next((x["icon_url"] for x in self.packagesModel.items if x["id"] == package_id), None) or ""
self.openLicenseDialog(package_info["display_name"], license_content, file_path, icon_url)
return return
package_id = self.install(file_path) installed_id = self.install(file_path)
if package_id != package_info["package_id"]: if installed_id != package_id:
Logger.error("Installed package {} does not match {}".format(package_id, package_info["package_id"])) Logger.error("Installed package {} does not match {}".format(installed_id, package_id))
self.subscribe(package_id) self.subscribe(installed_id)
# Getter & Setters for Properties: # Getter & Setters for Properties:
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@ -699,14 +703,14 @@ class Toolbox(QObject, Extension):
def isDownloading(self) -> bool: def isDownloading(self) -> bool:
return self._is_downloading return self._is_downloading
def setActivePackage(self, package: Dict[str, Any]) -> None: def setActivePackage(self, package: QObject) -> None:
if self._active_package != package: if self._active_package != package:
self._active_package = package self._active_package = package
self.activePackageChanged.emit() self.activePackageChanged.emit()
## The active package is the package that is currently being downloaded ## The active package is the package that is currently being downloaded
@pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged) @pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged)
def activePackage(self) -> Optional[Dict[str, Any]]: def activePackage(self) -> Optional[QObject]:
return self._active_package return self._active_package
def setViewCategory(self, category: str = "plugin") -> None: def setViewCategory(self, category: str = "plugin") -> None: