freecad-cam/Mod/AddonManager/Widgets/addonmanager_widget_package_details_view.py
2026-02-01 01:59:24 +01:00

343 lines
14 KiB
Python

# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2022-2024 The FreeCAD Project Association AISBL *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
from dataclasses import dataclass
from enum import Enum, auto
import os
from typing import Optional
try:
import FreeCAD
translate = FreeCAD.Qt.translate
except ImportError:
FreeCAD = None
def translate(_: str, text: str):
return text
# Get whatever version of PySide we can
try:
import PySide # Use the FreeCAD wrapper
except ImportError:
try:
import PySide6 # Outside FreeCAD, try Qt6 first
PySide = PySide6
except ImportError:
import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import)
PySide = PySide2
from PySide import QtCore, QtWidgets
from .addonmanager_widget_addon_buttons import WidgetAddonButtons
from .addonmanager_widget_readme_browser import WidgetReadmeBrowser
from .addonmanager_colors import warning_color_string, attention_color_string, bright_color_string
class MessageType(Enum):
Message = auto()
Warning = auto()
Error = auto()
@dataclass
class UpdateInformation:
unchecked: bool = True
check_in_progress: bool = False
update_available: bool = False
detached_head: bool = False
version: str = ""
tag: str = ""
branch: Optional[str] = None
@dataclass
class WarningFlags:
obsolete: bool = False
python2: bool = False
required_freecad_version: Optional[str] = None
non_osi_approved = False
non_fsf_libre = False
class PackageDetailsView(QtWidgets.QWidget):
"""The view class for the package details"""
def __init__(self, parent: QtWidgets.QWidget = None):
super().__init__(parent)
self.button_bar = None
self.readme_browser = None
self.message_label = None
self.location_label = None
self.installed = False
self.disabled = False
self.update_info = UpdateInformation()
self.warning_flags = WarningFlags()
self.installed_version = None
self.installed_branch = None
self.installed_timestamp = None
self.can_disable = True
self._setup_ui()
def _setup_ui(self):
self.vertical_layout = QtWidgets.QVBoxLayout(self)
self.button_bar = WidgetAddonButtons(self)
self.readme_browser = WidgetReadmeBrowser(self)
self.message_label = QtWidgets.QLabel(self)
self.location_label = QtWidgets.QLabel(self)
self.location_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.vertical_layout.addWidget(self.button_bar)
self.vertical_layout.addWidget(self.message_label)
self.vertical_layout.addWidget(self.location_label)
self.vertical_layout.addWidget(self.readme_browser)
self.button_bar.hide() # Start with no bar
def set_location(self, location: Optional[str]):
if location is not None:
text = (
translate("AddonsInstaller", "Installation location")
+ ": "
+ os.path.normpath(location)
)
self.location_label.setText(text)
self.location_label.show()
else:
self.location_label.hide()
def set_installed(
self,
installed: bool,
on_date: Optional[str] = None,
version: Optional[str] = None,
branch: Optional[str] = None,
):
self.installed = installed
self.installed_timestamp = on_date
self.installed_version = version
self.installed_branch = branch
if not self.installed:
self.set_location(None)
self._sync_ui_state()
def set_update_available(self, info: UpdateInformation):
self.update_info = info
self._sync_ui_state()
def set_disabled(self, disabled: bool):
self.disabled = disabled
self._sync_ui_state()
def allow_disabling(self, allow: bool):
self.can_disable = allow
self._sync_ui_state()
def allow_running(self, show: bool):
self.button_bar.run_macro.setVisible(show)
def set_warning_flags(self, flags: WarningFlags):
self.warning_flags = flags
self._sync_ui_state()
def set_new_disabled_status(self, disabled: bool):
"""If the user just changed the enabled/disabled state of the addon, display a message
indicating that will not take place until restart. Do not call except in a case of a
state change during this run."""
if disabled:
message = translate(
"AddonsInstaller", "This Addon will be disabled next time you restart FreeCAD."
)
else:
message = translate(
"AddonsInstaller", "This Addon will be enabled next time you restart FreeCAD."
)
self.message_label.setText(f"<h3>{message}</h3>")
self.message_label.setStyleSheet("color:" + attention_color_string())
def set_new_branch(self, branch: str):
"""If the user just changed branches, update the message to show that a restart is
needed."""
message_string = "<h3>"
message_string += translate(
"AddonsInstaller", "Changed to branch '{}' -- please restart to use Addon."
).format(branch)
message_string += "</h3>"
self.message_label.setText(message_string)
self.message_label.setStyleSheet("color:" + attention_color_string())
def set_updated(self):
"""If the user has just updated the addon but not yet restarted, show an indication that
we are awaiting a restart."""
message = translate(
"AddonsInstaller", "This Addon has been updated. Restart FreeCAD to see changes."
)
self.message_label.setText(f"<h3>{message}</h3>")
self.message_label.setStyleSheet("color:" + attention_color_string())
def _sync_ui_state(self):
self._sync_button_state()
self._create_status_label_text()
def _sync_button_state(self):
self.button_bar.install.setVisible(not self.installed)
self.button_bar.uninstall.setVisible(self.installed)
if not self.installed:
self.button_bar.disable.hide()
self.button_bar.enable.hide()
self.button_bar.update.hide()
self.button_bar.check_for_update.hide()
else:
self.button_bar.update.setVisible(self.update_info.update_available)
if self.update_info.detached_head:
self.button_bar.check_for_update.hide()
else:
self.button_bar.check_for_update.setVisible(not self.update_info.update_available)
if self.can_disable:
self.button_bar.enable.setVisible(self.disabled)
self.button_bar.disable.setVisible(not self.disabled)
else:
self.button_bar.enable.hide()
self.button_bar.disable.hide()
def _create_status_label_text(self):
if self.installed:
installation_details = self._get_installation_details_string()
update_details = self._get_update_status_string()
message_text = f"{installation_details} {update_details}"
if self.disabled:
message_text += " [" + translate("AddonsInstaller", "Disabled") + "]"
self.message_label.setText(f"<h3>{message_text}</h3>")
if self.disabled:
self.message_label.setStyleSheet("color:" + warning_color_string())
elif self.update_info.update_available:
self.message_label.setStyleSheet("color:" + attention_color_string())
else:
self.message_label.setStyleSheet("color:" + bright_color_string())
self.message_label.show()
elif self._there_are_warnings_to_show():
warnings = self._get_warning_string()
self.message_label.setText(f"<h3>{warnings}</h3>")
self.message_label.setStyleSheet("color:" + warning_color_string())
self.message_label.show()
else:
self.message_label.hide()
def _get_installation_details_string(self) -> str:
version = self.installed_version
date = ""
installed_version_string = ""
if self.installed_timestamp:
date = QtCore.QLocale().toString(
QtCore.QDateTime.fromSecsSinceEpoch(int(round(self.installed_timestamp, 0))),
QtCore.QLocale.ShortFormat,
)
if version and date:
installed_version_string += (
translate("AddonsInstaller", "Version {version} installed on {date}").format(
version=version, date=date
)
+ ". "
)
elif version:
installed_version_string += (
translate("AddonsInstaller", "Version {version} installed") + "."
).format(version=version)
elif date:
installed_version_string += (
translate("AddonsInstaller", "Installed on {date}") + "."
).format(date=date)
else:
installed_version_string += translate("AddonsInstaller", "Installed") + "."
return installed_version_string
def _get_update_status_string(self) -> str:
if self.update_info.check_in_progress:
return translate("AddonsInstaller", "Update check in progress") + "."
elif self.update_info.unchecked:
return ""
if self.update_info.detached_head:
return (
translate(
"AddonsInstaller", "Git tag '{}' checked out, no updates possible"
).format(self.update_info.tag)
+ "."
)
if self.update_info.update_available:
if self.installed_branch and self.update_info.branch:
if self.installed_branch != self.update_info.branch:
return (
translate(
"AddonsInstaller", "Currently on branch {}, name changed to {}"
).format(self.installed_branch, self.update_info.branch)
+ "."
)
if self.update_info.version:
return (
translate(
"AddonsInstaller",
"Currently on branch {}, update available to version {}",
).format(self.installed_branch, str(self.update_info.version).strip())
+ "."
)
return translate("AddonsInstaller", "Update available") + "."
if self.update_info.version:
return (
translate("AddonsInstaller", "Update available to version {}").format(
str(self.update_info.version).strip()
)
+ "."
)
return translate("AddonsInstaller", "Update available") + "."
return translate("AddonsInstaller", "This is the latest version available") + "."
def _there_are_warnings_to_show(self) -> bool:
if self.disabled:
return True
if (
self.warning_flags.obsolete
or self.warning_flags.python2
or self.warning_flags.required_freecad_version
):
return True
return False # TODO: Someday support optional warnings on license types
def _get_warning_string(self) -> str:
if self.installed and self.disabled:
return translate(
"AddonsInstaller",
"WARNING: This addon is currently installed, but disabled. Use the 'enable' "
"button to re-enable.",
)
if self.warning_flags.obsolete:
return translate("AddonsInstaller", "WARNING: This addon is obsolete")
if self.warning_flags.python2:
return translate("AddonsInstaller", "WARNING: This addon is Python 2 only")
if self.warning_flags.required_freecad_version:
return translate("AddonsInstaller", "WARNING: This addon requires FreeCAD {}").format(
self.warning_flags.required_freecad_version
)
return ""