# 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 * # * . * # * * # *************************************************************************** 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: 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) 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"

{message}

") 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 = "

" message_string += translate( "AddonsInstaller", "Changed to branch '{}' -- please restart to use Addon." ).format(branch) message_string += "

" 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"

{message}

") 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"

{message_text}

") 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"

{warnings}

") 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") + "." 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 ""