Addon Manager: Break up ReadmeViewer into view and controller
Addon Manager: Cleanup enable/disable message
This commit is contained in:
committed by
Chris Hennes
parent
20a01cfc9c
commit
9812548b68
@@ -52,7 +52,8 @@ from addonmanager_update_all_gui import UpdateAllGUI
|
||||
import addonmanager_utilities as utils
|
||||
import AddonManager_rc # This is required by Qt, it's not unused
|
||||
from package_list import PackageList, PackageListItemModel
|
||||
from package_details import PackageDetails
|
||||
from addonmanager_package_details_controller import PackageDetailsController
|
||||
from Widgets.addonmanager_widget_package_details_view import PackageDetailsView
|
||||
from Widgets.addonmanager_widget_global_buttons import WidgetGlobalButtonBar
|
||||
from Addon import Addon
|
||||
from manage_python_dependencies import (
|
||||
@@ -204,7 +205,8 @@ class CommandAddonManager:
|
||||
self.dialog.layout().addWidget(self.button_bar)
|
||||
|
||||
# Package details start out hidden
|
||||
self.packageDetails = PackageDetails(self.dialog)
|
||||
self.packageDetails = PackageDetailsView(self.dialog)
|
||||
self.package_details_controller = PackageDetailsController(self.packageDetails)
|
||||
self.packageDetails.hide()
|
||||
index = self.dialog.layout().indexOf(self.packageList)
|
||||
self.dialog.layout().insertWidget(index, self.packageDetails)
|
||||
@@ -238,12 +240,12 @@ class CommandAddonManager:
|
||||
self.packageList.ui.progressBar.stop_clicked.connect(self.stop_update)
|
||||
self.packageList.itemSelected.connect(self.table_row_activated)
|
||||
self.packageList.setEnabled(False)
|
||||
self.packageDetails.execute.connect(self.executemacro)
|
||||
self.packageDetails.install.connect(self.launch_installer_gui)
|
||||
self.packageDetails.uninstall.connect(self.remove)
|
||||
self.packageDetails.update.connect(self.update)
|
||||
self.packageDetails.back.connect(self.on_buttonBack_clicked)
|
||||
self.packageDetails.update_status.connect(self.status_updated)
|
||||
self.package_details_controller.execute.connect(self.executemacro)
|
||||
self.package_details_controller.install.connect(self.launch_installer_gui)
|
||||
self.package_details_controller.uninstall.connect(self.remove)
|
||||
self.package_details_controller.update.connect(self.update)
|
||||
self.package_details_controller.back.connect(self.on_buttonBack_clicked)
|
||||
self.package_details_controller.update_status.connect(self.status_updated)
|
||||
|
||||
# center the dialog over the FreeCAD window
|
||||
mw = FreeCADGui.getMainWindow()
|
||||
@@ -735,7 +737,7 @@ class CommandAddonManager:
|
||||
|
||||
self.packageList.hide()
|
||||
self.packageDetails.show()
|
||||
self.packageDetails.show_repo(selected_repo)
|
||||
self.package_details_controller.show_repo(selected_repo)
|
||||
|
||||
def show_information(self, message: str) -> None:
|
||||
"""shows generic text in the information pane"""
|
||||
@@ -746,7 +748,7 @@ class CommandAddonManager:
|
||||
def show_workbench(self, repo: Addon) -> None:
|
||||
self.packageList.hide()
|
||||
self.packageDetails.show()
|
||||
self.packageDetails.show_repo(repo)
|
||||
self.package_details_controller.show_repo(repo)
|
||||
|
||||
def on_buttonBack_clicked(self) -> None:
|
||||
self.packageDetails.hide()
|
||||
@@ -765,7 +767,7 @@ class CommandAddonManager:
|
||||
else:
|
||||
repo.set_status(Addon.Status.NO_UPDATE_AVAILABLE)
|
||||
self.item_model.reload_item(repo)
|
||||
self.packageDetails.show_repo(repo)
|
||||
self.package_details_controller.show_repo(repo)
|
||||
|
||||
def launch_installer_gui(self, addon: Addon) -> None:
|
||||
if self.installer_gui is not None:
|
||||
@@ -860,7 +862,7 @@ class CommandAddonManager:
|
||||
if repo.status() == Addon.Status.PENDING_RESTART:
|
||||
self.restart_required = True
|
||||
self.item_model.reload_item(repo)
|
||||
self.packageDetails.show_repo(repo)
|
||||
self.package_details_controller.show_repo(repo)
|
||||
if repo in self.packages_with_updates:
|
||||
self.packages_with_updates.remove(repo)
|
||||
self.enable_updates(len(self.packages_with_updates))
|
||||
|
||||
@@ -30,8 +30,9 @@ SET(AddonManager_SRCS
|
||||
addonmanager_macro.py
|
||||
addonmanager_macro_parser.py
|
||||
addonmanager_metadata.py
|
||||
addonmanager_package_details_controller.py
|
||||
addonmanager_pyside_interface.py
|
||||
addonmanager_readme_viewer.py
|
||||
addonmanager_readme_controller.py
|
||||
addonmanager_update_all_gui.py
|
||||
addonmanager_uninstaller.py
|
||||
addonmanager_uninstaller_gui.py
|
||||
@@ -68,7 +69,7 @@ SET(AddonManager_SRCS
|
||||
loading.html
|
||||
manage_python_dependencies.py
|
||||
NetworkManager.py
|
||||
package_details.py
|
||||
addonmanager_package_details_controller.py
|
||||
package_list.py
|
||||
PythonDependencyUpdateDialog.ui
|
||||
select_toolbar_dialog.ui
|
||||
|
||||
@@ -472,19 +472,21 @@ if HAVE_QTNETWORK:
|
||||
sender.abort()
|
||||
self.__launch_request(current_index, self.__create_get_request(url))
|
||||
|
||||
def __on_ssl_error(self, reply: str, errors: List[str]):
|
||||
def __on_ssl_error(self, reply: str, errors: List[str] = None):
|
||||
"""Called when an SSL error occurs: prints the error information."""
|
||||
if HAVE_FREECAD:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
translate("AddonsInstaller", "Error with encrypted connection") + "\n:"
|
||||
)
|
||||
FreeCAD.Console.PrintWarning(reply)
|
||||
for error in errors:
|
||||
FreeCAD.Console.PrintWarning(error)
|
||||
if errors is not None:
|
||||
for error in errors:
|
||||
FreeCAD.Console.PrintWarning(error)
|
||||
else:
|
||||
print("Error with encrypted connection")
|
||||
for error in errors:
|
||||
print(error)
|
||||
if errors is not None:
|
||||
for error in errors:
|
||||
print(error)
|
||||
|
||||
def __download_progress(self, bytesReceived: int, bytesTotal: int) -> None:
|
||||
"""Monitors download progress and emits a progress_made signal"""
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
SET(AddonManagerWidget_SRCS
|
||||
__init__.py
|
||||
addonmanager_colors.py
|
||||
addonmanager_widget_addon_buttons.py
|
||||
addonmanager_widget_filter_selector.py
|
||||
addonmanager_widget_global_buttons.py
|
||||
addonmanager_widget_package_details_view.py
|
||||
addonmanager_widget_progress_bar.py
|
||||
addonmanager_widget_readme_browser.py
|
||||
addonmanager_widget_search.py
|
||||
addonmanager_widget_view_control_bar.py
|
||||
addonmanager_widget_view_selector.py
|
||||
|
||||
48
src/Mod/AddonManager/Widgets/addonmanager_colors.py
Normal file
48
src/Mod/AddonManager/Widgets/addonmanager_colors.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2022-2024 FreeCAD Project Association *
|
||||
# * *
|
||||
# * 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 enum import Enum, auto
|
||||
|
||||
import FreeCADGui
|
||||
from PySide import QtGui
|
||||
|
||||
|
||||
def is_darkmode() -> bool:
|
||||
"""Heuristics to determine if we are in a darkmode stylesheet"""
|
||||
pl = FreeCADGui.getMainWindow().palette()
|
||||
return pl.color(QtGui.QPalette.Window).lightness() < 128
|
||||
|
||||
|
||||
def warning_color_string() -> str:
|
||||
"""A shade of red, adapted to darkmode if possible. Targets a minimum 7:1 contrast ratio."""
|
||||
return "rgb(255,105,97)" if is_darkmode() else "rgb(215,0,21)"
|
||||
|
||||
|
||||
def bright_color_string() -> str:
|
||||
"""A shade of green, adapted to darkmode if possible. Targets a minimum 7:1 contrast ratio."""
|
||||
return "rgb(48,219,91)" if is_darkmode() else "rgb(36,138,61)"
|
||||
|
||||
|
||||
def attention_color_string() -> str:
|
||||
"""A shade of orange, adapted to darkmode if possible. Targets a minimum 7:1 contrast ratio."""
|
||||
return "rgb(255,179,64)" if is_darkmode() else "rgb(255,149,0)"
|
||||
@@ -77,39 +77,37 @@ class WidgetAddonButtons(QtWidgets.QWidget):
|
||||
def _setup_ui(self):
|
||||
self.horizontal_layout = QtWidgets.QHBoxLayout()
|
||||
self.horizontal_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.back_button = QtWidgets.QToolButton(self)
|
||||
self.install_button = QtWidgets.QPushButton(self)
|
||||
self.uninstall_button = QtWidgets.QPushButton(self)
|
||||
self.enable_button = QtWidgets.QPushButton(self)
|
||||
self.disable_button = QtWidgets.QPushButton(self)
|
||||
self.update_button = QtWidgets.QPushButton(self)
|
||||
self.run_macro_button = QtWidgets.QPushButton(self)
|
||||
self.change_branch_button = QtWidgets.QPushButton(self)
|
||||
self.check_for_update_button = QtWidgets.QPushButton(self)
|
||||
self.horizontal_layout.addWidget(self.back_button)
|
||||
self.back = QtWidgets.QToolButton(self)
|
||||
self.install = QtWidgets.QPushButton(self)
|
||||
self.uninstall = QtWidgets.QPushButton(self)
|
||||
self.enable = QtWidgets.QPushButton(self)
|
||||
self.disable = QtWidgets.QPushButton(self)
|
||||
self.update = QtWidgets.QPushButton(self)
|
||||
self.run_macro = QtWidgets.QPushButton(self)
|
||||
self.change_branch = QtWidgets.QPushButton(self)
|
||||
self.check_for_update = QtWidgets.QPushButton(self)
|
||||
self.horizontal_layout.addWidget(self.back)
|
||||
self.horizontal_layout.addStretch()
|
||||
self.horizontal_layout.addWidget(self.check_for_update_button)
|
||||
self.horizontal_layout.addWidget(self.install_button)
|
||||
self.horizontal_layout.addWidget(self.uninstall_button)
|
||||
self.horizontal_layout.addWidget(self.enable_button)
|
||||
self.horizontal_layout.addWidget(self.disable_button)
|
||||
self.horizontal_layout.addWidget(self.update_button)
|
||||
self.horizontal_layout.addWidget(self.run_macro_button)
|
||||
self.horizontal_layout.addWidget(self.change_branch_button)
|
||||
self.horizontal_layout.addWidget(self.check_for_update)
|
||||
self.horizontal_layout.addWidget(self.install)
|
||||
self.horizontal_layout.addWidget(self.uninstall)
|
||||
self.horizontal_layout.addWidget(self.enable)
|
||||
self.horizontal_layout.addWidget(self.disable)
|
||||
self.horizontal_layout.addWidget(self.update)
|
||||
self.horizontal_layout.addWidget(self.run_macro)
|
||||
self.horizontal_layout.addWidget(self.change_branch)
|
||||
self.setLayout(self.horizontal_layout)
|
||||
|
||||
def _set_icons(self):
|
||||
self.back_button.setIcon(
|
||||
QtGui.QIcon.fromTheme("back", QtGui.QIcon(":/icons/button_left.svg"))
|
||||
)
|
||||
self.back.setIcon(QtGui.QIcon.fromTheme("back", QtGui.QIcon(":/icons/button_left.svg")))
|
||||
|
||||
def retranslateUi(self, _):
|
||||
self.check_for_update_button.setText(translate("AddonsInstaller", "Check for update"))
|
||||
self.install_button.setText(translate("AddonsInstaller", "Install"))
|
||||
self.uninstall_button.setText(translate("AddonsInstaller", "Uninstall"))
|
||||
self.disable_button.setText(translate("AddonsInstaller", "Disable"))
|
||||
self.enable_button.setText(translate("AddonsInstaller", "Enable"))
|
||||
self.update_button.setText(translate("AddonsInstaller", "Update"))
|
||||
self.run_macro_button.setText(translate("AddonsInstaller", "Run"))
|
||||
self.change_branch_button.setText(translate("AddonsInstaller", "Change branch..."))
|
||||
self.back_button.setToolTip(translate("AddonsInstaller", "Return to package list"))
|
||||
self.check_for_update.setText(translate("AddonsInstaller", "Check for update"))
|
||||
self.install.setText(translate("AddonsInstaller", "Install"))
|
||||
self.uninstall.setText(translate("AddonsInstaller", "Uninstall"))
|
||||
self.disable.setText(translate("AddonsInstaller", "Disable"))
|
||||
self.enable.setText(translate("AddonsInstaller", "Enable"))
|
||||
self.update.setText(translate("AddonsInstaller", "Update"))
|
||||
self.run_macro.setText(translate("AddonsInstaller", "Run"))
|
||||
self.change_branch.setText(translate("AddonsInstaller", "Change branch..."))
|
||||
self.back.setToolTip(translate("AddonsInstaller", "Return to package list"))
|
||||
|
||||
@@ -109,5 +109,5 @@ class WidgetGlobalButtonBar(QtWidgets.QWidget):
|
||||
else:
|
||||
self.update_all_addons.setEnabled(True)
|
||||
self.update_all_addons.setText(
|
||||
translate("AddonsInstaller", "Apply %1 available updates").format(updates)
|
||||
translate("AddonsInstaller", "Apply {} available updates").format(updates)
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2022-2024 FreeCAD Project Association *
|
||||
# * Copyright (c) 2022-2024 The FreeCAD Project Association AISBL *
|
||||
# * *
|
||||
# * This file is part of FreeCAD. *
|
||||
# * *
|
||||
@@ -21,6 +21,10 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
try:
|
||||
import FreeCAD
|
||||
@@ -49,28 +53,286 @@ except ImportError:
|
||||
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"""
|
||||
|
||||
install_clicked = QtCore.Signal()
|
||||
uninstall_clicked = QtCore.Signal()
|
||||
enable_clicked = QtCore.Signal()
|
||||
disable_clicked = QtCore.Signal()
|
||||
update_clicked = QtCore.Signal()
|
||||
check_for_updates = QtCore.Signal()
|
||||
run_clicked = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent: QtWidgets.QWidget = None):
|
||||
super().__init__(parent)
|
||||
self.button_bar = None
|
||||
self.text_browser = 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.text_browser = QtWidgets.QTextBrowser(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.text_browser)
|
||||
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"<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") + "."
|
||||
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 ""
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# 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/>. *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
|
||||
# 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, QtGui, QtWidgets
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class WidgetReadmeBrowser(QtWidgets.QTextBrowser):
|
||||
"""A QTextBrowser widget that emits signals for each requested image resource, allowing an external controller
|
||||
to load and re-deliver those images. Once all resources have been re-delivered, the original data is redisplayed
|
||||
with the images in-line. Call setUrl prior to calling setMarkdown or setHtml to ensure URLs are resolved
|
||||
correctly."""
|
||||
|
||||
load_resource = QtCore.Signal(str) # Str is a URL to a resource
|
||||
|
||||
def __init__(self, parent: QtWidgets.QWidget = None):
|
||||
super().__init__(parent)
|
||||
self.image_map = {}
|
||||
self.url = ""
|
||||
self.stop = False
|
||||
self.setOpenExternalLinks(True)
|
||||
|
||||
def setUrl(self, url: str):
|
||||
"""Set the base URL of the page. Used to resolve relative URLs in the page source."""
|
||||
self.url = url
|
||||
|
||||
def setMarkdown(self, md: str):
|
||||
"""Provides an optional fallback to the markdown library for older versions of Qt (prior to 5.15) that did not
|
||||
have native markdown support. Lacking that, plaintext is displayed."""
|
||||
if hasattr(super(), "setMarkdown"):
|
||||
super().setMarkdown(md)
|
||||
else:
|
||||
try:
|
||||
import markdown
|
||||
|
||||
html = markdown.markdown(md)
|
||||
self.setHtml(html)
|
||||
except ImportError:
|
||||
self.setText(md)
|
||||
FreeCAD.Console.Warning(
|
||||
"Qt < 5.15 and no `import markdown` -- falling back to plain text display\n"
|
||||
)
|
||||
|
||||
def set_resource(self, resource_url: str, image: Optional[QtGui.QImage]):
|
||||
"""Once a resource has been fetched (or the fetch has failed), this method should be used to inform the widget
|
||||
that the resource has been loaded. Note that the incoming image is scaled to 97% of the widget width if it is
|
||||
larger than that."""
|
||||
self.image_map[resource_url] = self._ensure_appropriate_width(image)
|
||||
|
||||
def loadResource(self, resource_type: int, name: QtCore.QUrl) -> object:
|
||||
"""Callback for resource loading. Called automatically by underlying Qt
|
||||
code when external resources are needed for rendering. In particular,
|
||||
here it is used to download and cache (in RAM) the images needed for the
|
||||
README and Wiki pages."""
|
||||
if resource_type == QtGui.QTextDocument.ImageResource and not self.stop:
|
||||
full_url = self._create_full_url(name.toString())
|
||||
if full_url not in self.image_map:
|
||||
self.load_resource.emit(full_url)
|
||||
self.image_map[full_url] = None
|
||||
return self.image_map[full_url]
|
||||
return super().loadResource(resource_type, name)
|
||||
|
||||
def _ensure_appropriate_width(self, image: QtGui.QImage) -> QtGui.QImage:
|
||||
ninety_seven_percent = self.width() * 0.97
|
||||
if image.width() < ninety_seven_percent:
|
||||
return image
|
||||
return image.scaledToWidth(ninety_seven_percent)
|
||||
|
||||
def _create_full_url(self, url: str) -> str:
|
||||
if url.startswith("http"):
|
||||
return url
|
||||
if not self.url:
|
||||
return url
|
||||
lhs, slash, _ = self.url.rpartition("/")
|
||||
return lhs + slash + url
|
||||
@@ -119,6 +119,7 @@ class WidgetViewSelector(QtWidgets.QWidget):
|
||||
self.composite_button.setIcon(
|
||||
QtGui.QIcon.fromTheme("composite_button", QtGui.QIcon(":/icons/composite_view.svg"))
|
||||
)
|
||||
self.composite_button.hide() # TODO: Implement this view
|
||||
|
||||
self.horizontal_layout.addWidget(self.compact_button)
|
||||
self.horizontal_layout.addWidget(self.expanded_button)
|
||||
|
||||
0
src/Mod/AddonManager/__init__.py
Normal file
0
src/Mod/AddonManager/__init__.py
Normal file
258
src/Mod/AddonManager/addonmanager_package_details_controller.py
Normal file
258
src/Mod/AddonManager/addonmanager_package_details_controller.py
Normal file
@@ -0,0 +1,258 @@
|
||||
# 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/>. *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
""" Provides the PackageDetails widget. """
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from PySide import QtCore, QtWidgets
|
||||
|
||||
import addonmanager_freecad_interface as fci
|
||||
|
||||
import addonmanager_utilities as utils
|
||||
from addonmanager_metadata import (
|
||||
Version,
|
||||
get_first_supported_freecad_version,
|
||||
get_branch_from_metadata,
|
||||
)
|
||||
from addonmanager_workers_startup import GetMacroDetailsWorker, CheckSingleUpdateWorker
|
||||
from addonmanager_git import GitManager, NoGitFound
|
||||
from Addon import Addon
|
||||
from change_branch import ChangeBranchDialog
|
||||
from addonmanager_readme_controller import ReadmeController
|
||||
from Widgets.addonmanager_widget_package_details_view import UpdateInformation, WarningFlags
|
||||
|
||||
translate = fci.translate
|
||||
|
||||
|
||||
class PackageDetailsController(QtCore.QObject):
|
||||
"""Manages the display of the package README information."""
|
||||
|
||||
back = QtCore.Signal()
|
||||
install = QtCore.Signal(Addon)
|
||||
uninstall = QtCore.Signal(Addon)
|
||||
update = QtCore.Signal(Addon)
|
||||
execute = QtCore.Signal(Addon)
|
||||
update_status = QtCore.Signal(Addon)
|
||||
check_for_update = QtCore.Signal(Addon)
|
||||
|
||||
def __init__(self, widget=None):
|
||||
super().__init__()
|
||||
self.ui = widget
|
||||
self.readme_controller = ReadmeController(self.ui.readme_browser)
|
||||
self.worker = None
|
||||
self.addon = None
|
||||
self.status_update_thread = None
|
||||
self.original_disabled_state = None
|
||||
self.original_status = None
|
||||
try:
|
||||
self.git_manager = GitManager()
|
||||
except NoGitFound:
|
||||
self.git_manager = None
|
||||
|
||||
self.ui.button_bar.back.clicked.connect(self.back.emit)
|
||||
self.ui.button_bar.run_macro.clicked.connect(lambda: self.execute.emit(self.addon))
|
||||
self.ui.button_bar.install.clicked.connect(lambda: self.install.emit(self.addon))
|
||||
self.ui.button_bar.uninstall.clicked.connect(lambda: self.uninstall.emit(self.addon))
|
||||
self.ui.button_bar.update.clicked.connect(lambda: self.update.emit(self.addon))
|
||||
self.ui.button_bar.check_for_update.clicked.connect(
|
||||
lambda: self.check_for_update.emit(self.addon)
|
||||
)
|
||||
self.ui.button_bar.change_branch.clicked.connect(self.change_branch_clicked)
|
||||
self.ui.button_bar.enable.clicked.connect(self.enable_clicked)
|
||||
self.ui.button_bar.disable.clicked.connect(self.disable_clicked)
|
||||
|
||||
def show_repo(self, repo: Addon) -> None:
|
||||
"""The main entry point for this class, shows the package details and related buttons
|
||||
for the provided repo."""
|
||||
self.addon = repo
|
||||
self.readme_controller.set_addon(repo)
|
||||
self.original_disabled_state = self.addon.is_disabled()
|
||||
|
||||
if self.worker is not None:
|
||||
if not self.worker.isFinished():
|
||||
self.worker.requestInterruption()
|
||||
self.worker.wait()
|
||||
|
||||
installed = self.addon.status() != Addon.Status.NOT_INSTALLED
|
||||
self.ui.set_installed(installed)
|
||||
update_info = UpdateInformation()
|
||||
if installed:
|
||||
update_info.update_available = self.addon.status() == Addon.Status.UPDATE_AVAILABLE
|
||||
update_info.check_in_progress = False # TODO: Implement the "check in progress" status
|
||||
if repo.metadata:
|
||||
update_info.branch = get_branch_from_metadata(repo.metadata)
|
||||
update_info.version = repo.metadata.version
|
||||
elif repo.macro:
|
||||
update_info.version = repo.macro.version
|
||||
self.ui.set_update_available(update_info)
|
||||
self.ui.set_location(os.path.join(self.addon.mod_directory, self.addon.name))
|
||||
self.ui.set_location(os.path.join(self.addon.mod_directory, self.addon.name))
|
||||
self.ui.set_disabled(self.addon.is_disabled())
|
||||
self.ui.allow_running(repo.repo_type == Addon.Kind.MACRO)
|
||||
self.ui.allow_disabling(repo.repo_type != Addon.Kind.MACRO)
|
||||
|
||||
if repo.repo_type == Addon.Kind.MACRO:
|
||||
self.update_macro_info(repo)
|
||||
|
||||
if repo.status() == Addon.Status.UNCHECKED:
|
||||
if not self.status_update_thread:
|
||||
self.status_update_thread = QtCore.QThread()
|
||||
self.status_create_addon_list_worker = CheckSingleUpdateWorker(repo)
|
||||
self.status_create_addon_list_worker.moveToThread(self.status_update_thread)
|
||||
self.status_update_thread.finished.connect(
|
||||
self.status_create_addon_list_worker.deleteLater
|
||||
)
|
||||
self.check_for_update.connect(self.status_create_addon_list_worker.do_work)
|
||||
self.status_create_addon_list_worker.update_status.connect(self.display_repo_status)
|
||||
self.status_update_thread.start()
|
||||
update_info.check_in_progress = True
|
||||
self.ui.set_update_available(update_info)
|
||||
self.check_for_update.emit(self.addon)
|
||||
|
||||
flags = WarningFlags()
|
||||
flags.required_freecad_version = self.requires_newer_freecad()
|
||||
flags.obsolete = repo.obsolete
|
||||
flags.python2 = repo.python2
|
||||
self.ui.set_warning_flags(flags)
|
||||
|
||||
def requires_newer_freecad(self) -> Optional[Version]:
|
||||
"""If the current package is not installed, returns the first supported version of
|
||||
FreeCAD, if one is set, or None if no information is available (or if the package is
|
||||
already installed)."""
|
||||
|
||||
# If it's not installed, check to see if it's for a newer version of FreeCAD
|
||||
if self.addon.status() == Addon.Status.NOT_INSTALLED and self.addon.metadata:
|
||||
# Only hide if ALL content items require a newer version, otherwise
|
||||
# it's possible that this package actually provides versions of itself
|
||||
# for newer and older versions
|
||||
|
||||
first_supported_version = get_first_supported_freecad_version(self.addon.metadata)
|
||||
if first_supported_version is not None:
|
||||
fc_version = Version(from_list=fci.Version())
|
||||
if first_supported_version > fc_version:
|
||||
return first_supported_version
|
||||
return None
|
||||
|
||||
def set_change_branch_button_state(self):
|
||||
"""The change branch button is only available for installed Addons that have a .git directory
|
||||
and in runs where the git is available."""
|
||||
|
||||
self.ui.button_bar.change_branch_button.hide()
|
||||
|
||||
pref = fci.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
show_switcher = pref.GetBool("ShowBranchSwitcher", False)
|
||||
if not show_switcher:
|
||||
return
|
||||
|
||||
# Is this repo installed? If not, return.
|
||||
if self.addon.status() == Addon.Status.NOT_INSTALLED:
|
||||
return
|
||||
|
||||
# Is it a Macro? If so, return:
|
||||
if self.addon.repo_type == Addon.Kind.MACRO:
|
||||
return
|
||||
|
||||
# Can we actually switch branches? If not, return.
|
||||
if not self.git_manager:
|
||||
return
|
||||
|
||||
# Is there a .git subdirectory? If not, return.
|
||||
basedir = fci.getUserAppDataDir()
|
||||
path_to_git = os.path.join(basedir, "Mod", self.addon.name, ".git")
|
||||
if not os.path.isdir(path_to_git):
|
||||
return
|
||||
|
||||
# If all four above checks passed, then it's possible for us to switch
|
||||
# branches, if there are any besides the one we are on: show the button
|
||||
self.ui.button_bar.change_branch_button.show()
|
||||
|
||||
def update_macro_info(self, repo: Addon) -> None:
|
||||
if not repo.macro.url:
|
||||
# We need to populate the macro information... may as well do it while the user reads
|
||||
# the wiki page
|
||||
self.worker = GetMacroDetailsWorker(repo)
|
||||
self.worker.readme_updated.connect(self.macro_readme_updated)
|
||||
self.worker.start()
|
||||
|
||||
def change_branch_clicked(self) -> None:
|
||||
"""Loads the branch-switching dialog"""
|
||||
basedir = fci.getUserAppDataDir()
|
||||
path_to_repo = os.path.join(basedir, "Mod", self.addon.name)
|
||||
change_branch_dialog = ChangeBranchDialog(path_to_repo, self.ui)
|
||||
change_branch_dialog.branch_changed.connect(self.branch_changed)
|
||||
change_branch_dialog.exec()
|
||||
|
||||
def enable_clicked(self) -> None:
|
||||
"""Called by the Enable button, enables this Addon and updates GUI to reflect
|
||||
that status."""
|
||||
self.addon.enable()
|
||||
self.ui.set_disabled(False)
|
||||
if self.original_disabled_state:
|
||||
self.ui.set_new_disabled_status(False)
|
||||
self.original_status = self.addon.status()
|
||||
self.addon.set_status(Addon.Status.PENDING_RESTART)
|
||||
else:
|
||||
self.addon.set_status(self.original_status)
|
||||
self.update_status.emit(self.addon)
|
||||
|
||||
def disable_clicked(self) -> None:
|
||||
"""Called by the Disable button, disables this Addon and updates the GUI to
|
||||
reflect that status."""
|
||||
self.addon.disable()
|
||||
self.ui.set_disabled(True)
|
||||
if not self.original_disabled_state:
|
||||
self.ui.set_new_disabled_status(True)
|
||||
self.original_status = self.addon.status()
|
||||
self.addon.set_status(Addon.Status.PENDING_RESTART)
|
||||
else:
|
||||
self.addon.set_status(self.original_status)
|
||||
self.update_status.emit(self.addon)
|
||||
|
||||
def branch_changed(self, name: str) -> None:
|
||||
"""Displays a dialog confirming the branch changed, and tries to access the
|
||||
metadata file from that branch."""
|
||||
QtWidgets.QMessageBox.information(
|
||||
self.ui,
|
||||
translate("AddonsInstaller", "Success"),
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"Branch change succeeded, please restart to use the new version.",
|
||||
),
|
||||
)
|
||||
# See if this branch has a package.xml file:
|
||||
basedir = fci.getUserAppDataDir()
|
||||
path_to_metadata = os.path.join(basedir, "Mod", self.addon.name, "package.xml")
|
||||
if os.path.isfile(path_to_metadata):
|
||||
self.addon.load_metadata_file(path_to_metadata)
|
||||
self.addon.installed_version = self.addon.metadata.version
|
||||
else:
|
||||
self.addon.repo_type = Addon.Kind.WORKBENCH
|
||||
self.addon.metadata = None
|
||||
self.addon.installed_version = None
|
||||
self.addon.updated_timestamp = QtCore.QDateTime.currentDateTime().toSecsSinceEpoch()
|
||||
self.addon.branch = name
|
||||
self.addon.set_status(Addon.Status.PENDING_RESTART)
|
||||
self.ui.set_new_branch(name)
|
||||
self.update_status.emit(self.addon)
|
||||
@@ -23,50 +23,66 @@
|
||||
|
||||
""" A Qt Widget for displaying Addon README information """
|
||||
|
||||
import Addon
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
from enum import Enum, auto
|
||||
from html.parser import HTMLParser
|
||||
|
||||
import addonmanager_freecad_interface as fci
|
||||
import FreeCAD
|
||||
from Addon import Addon
|
||||
import addonmanager_utilities as utils
|
||||
|
||||
from enum import IntEnum, Enum, auto
|
||||
from html.parser import HTMLParser
|
||||
from typing import Optional
|
||||
|
||||
import NetworkManager
|
||||
|
||||
translate = fci.translate
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
|
||||
class ReadmeViewer(QtWidgets.QTextBrowser):
|
||||
class ReadmeDataType(IntEnum):
|
||||
PlainText = 0
|
||||
Markdown = 1
|
||||
Html = 2
|
||||
|
||||
"""A QTextBrowser widget that, when given an Addon, downloads the README data as appropriate
|
||||
and renders it with whatever technology is available (usually Qt's Markdown renderer for
|
||||
workbenches and its HTML renderer for Macros)."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
class ReadmeController(QtCore.QObject):
|
||||
|
||||
"""A class that can provide README data from an Addon, possibly loading external resources such
|
||||
as images"""
|
||||
|
||||
def __init__(self, widget):
|
||||
super().__init__()
|
||||
NetworkManager.InitializeNetworkManager()
|
||||
NetworkManager.AM_NETWORK_MANAGER.completed.connect(self._download_completed)
|
||||
self.readme_request_index = 0
|
||||
self.resource_requests = {}
|
||||
self.resource_failures = []
|
||||
self.url = ""
|
||||
self.repo: Addon.Addon = None
|
||||
self.setOpenExternalLinks(True)
|
||||
self.setOpenLinks(True)
|
||||
self.image_map = {}
|
||||
self.readme_data = None
|
||||
self.readme_data_type = None
|
||||
self.addon: Optional[Addon] = None
|
||||
self.stop = True
|
||||
self.widget = widget
|
||||
self.widget.load_resource.connect(self.loadResource)
|
||||
|
||||
def set_addon(self, repo: Addon):
|
||||
"""Set which Addon's information is displayed"""
|
||||
|
||||
self.setPlainText(translate("AddonsInstaller", "Loading README data..."))
|
||||
self.repo = repo
|
||||
self.addon = repo
|
||||
self.stop = False
|
||||
if self.repo.repo_type == Addon.Addon.Kind.MACRO:
|
||||
self.url = self.repo.macro.wiki
|
||||
self.readme_data = None
|
||||
if self.addon.repo_type == Addon.Kind.MACRO:
|
||||
self.url = self.addon.macro.wiki
|
||||
if not self.url:
|
||||
self.url = self.repo.macro.url
|
||||
self.url = self.addon.macro.url
|
||||
else:
|
||||
self.url = utils.get_readme_url(repo)
|
||||
self.widget.setUrl(self.url)
|
||||
|
||||
self.widget.setText(
|
||||
translate("AddonsInstaller", "Loading page for {} from {}...").format(
|
||||
self.addon.display_name, self.url
|
||||
)
|
||||
)
|
||||
self.readme_request_index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(
|
||||
self.url
|
||||
)
|
||||
@@ -77,7 +93,7 @@ class ReadmeViewer(QtWidgets.QTextBrowser):
|
||||
if code == 200: # HTTP success
|
||||
self._process_package_download(data.data().decode("utf-8"))
|
||||
else:
|
||||
self.setPlainText(
|
||||
self.widget.setText(
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"Failed to download data from {} -- received response code {}.",
|
||||
@@ -87,47 +103,42 @@ class ReadmeViewer(QtWidgets.QTextBrowser):
|
||||
if code == 200:
|
||||
self._process_resource_download(self.resource_requests[index], data.data())
|
||||
else:
|
||||
self.image_map[self.resource_requests[index]] = None
|
||||
FreeCAD.Console.PrintLog(f"Failed to load {self.resource_requests[index]}\n")
|
||||
self.resource_failures.append(self.resource_requests[index])
|
||||
del self.resource_requests[index]
|
||||
if not self.resource_requests:
|
||||
self.set_addon(self.repo) # Trigger a reload of the page now with resources
|
||||
if self.readme_data:
|
||||
if self.readme_data_type == ReadmeDataType.Html:
|
||||
self.widget.setHtml(self.readme_data)
|
||||
elif self.readme_data_type == ReadmeDataType.Markdown:
|
||||
self.widget.setMarkdown(self.readme_data)
|
||||
else:
|
||||
self.widget.setText(self.readme_data)
|
||||
else:
|
||||
self.set_addon(self.addon) # Trigger a reload of the page now with resources
|
||||
|
||||
def _process_package_download(self, data: str):
|
||||
if self.repo.repo_type == Addon.Addon.Kind.MACRO:
|
||||
if self.addon.repo_type == Addon.Kind.MACRO:
|
||||
parser = WikiCleaner()
|
||||
parser.feed(data)
|
||||
self.setHtml(parser.final_html)
|
||||
self.readme_data = parser.final_html
|
||||
self.readme_data_type = ReadmeDataType.Html
|
||||
self.widget.setHtml(parser.final_html)
|
||||
else:
|
||||
# Check for recent Qt (e.g. Qt5.15 or later). Check can be removed when
|
||||
# we no longer support Ubuntu 20.04LTS for compiling.
|
||||
if hasattr(self, "setMarkdown"):
|
||||
self.setMarkdown(data)
|
||||
else:
|
||||
self.setPlainText(data)
|
||||
self.readme_data = data
|
||||
self.readme_data_type = ReadmeDataType.Markdown
|
||||
self.widget.setMarkdown(data)
|
||||
|
||||
def _process_resource_download(self, resource_name: str, resource_data: bytes):
|
||||
image = QtGui.QImage.fromData(resource_data)
|
||||
if image:
|
||||
self.image_map[resource_name] = self._ensure_appropriate_width(image)
|
||||
else:
|
||||
self.image_map[resource_name] = None
|
||||
self.widget.set_resource(resource_name, image)
|
||||
|
||||
def loadResource(self, resource_type: int, name: QtCore.QUrl) -> object:
|
||||
"""Callback for resource loading. Called automatically by underlying Qt
|
||||
code when external resources are needed for rendering. In particular,
|
||||
here it is used to download and cache (in RAM) the images needed for the
|
||||
README and Wiki pages."""
|
||||
if resource_type == QtGui.QTextDocument.ImageResource and not self.stop:
|
||||
full_url = self._create_full_url(name.toString())
|
||||
if full_url not in self.image_map:
|
||||
self.image_map[full_url] = None
|
||||
fci.Console.PrintMessage(f"Downloading image from {full_url}...\n")
|
||||
index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(full_url)
|
||||
self.resource_requests[index] = full_url
|
||||
return self.image_map[full_url]
|
||||
return super().loadResource(resource_type, name)
|
||||
def loadResource(self, full_url: str):
|
||||
if full_url not in self.resource_failures:
|
||||
index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(full_url)
|
||||
self.resource_requests[index] = full_url
|
||||
|
||||
def hideEvent(self, event: QtGui.QHideEvent):
|
||||
def cancel_resource_loading(self):
|
||||
self.stop = True
|
||||
for request in self.resource_requests:
|
||||
NetworkManager.AM_NETWORK_MANAGER.abort(request)
|
||||
@@ -141,12 +152,6 @@ class ReadmeViewer(QtWidgets.QTextBrowser):
|
||||
lhs, slash, _ = self.url.rpartition("/")
|
||||
return lhs + slash + url
|
||||
|
||||
def _ensure_appropriate_width(self, image: QtGui.QImage) -> QtGui.QImage:
|
||||
ninety_seven_percent = self.width() * 0.97
|
||||
if image.width() < ninety_seven_percent:
|
||||
return image
|
||||
return image.scaledToWidth(ninety_seven_percent)
|
||||
|
||||
|
||||
class WikiCleaner(HTMLParser):
|
||||
"""This HTML parser cleans up FreeCAD Macro Wiki Page for display in a
|
||||
@@ -1,559 +0,0 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2022-2023 FreeCAD Project Association *
|
||||
# * *
|
||||
# * 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/>. *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
""" Provides the PackageDetails widget. """
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
|
||||
import addonmanager_freecad_interface as fci
|
||||
|
||||
import addonmanager_utilities as utils
|
||||
from addonmanager_metadata import (
|
||||
Version,
|
||||
get_first_supported_freecad_version,
|
||||
get_branch_from_metadata,
|
||||
)
|
||||
from addonmanager_workers_startup import GetMacroDetailsWorker, CheckSingleUpdateWorker
|
||||
from addonmanager_readme_viewer import ReadmeViewer
|
||||
from addonmanager_git import GitManager, NoGitFound
|
||||
from Addon import Addon
|
||||
from change_branch import ChangeBranchDialog
|
||||
from Widgets.addonmanager_widget_addon_buttons import WidgetAddonButtons
|
||||
|
||||
translate = fci.translate
|
||||
|
||||
|
||||
class PackageDetails(QtWidgets.QWidget):
|
||||
"""The PackageDetails QWidget shows package README information and provides
|
||||
install, uninstall, and update buttons."""
|
||||
|
||||
back = QtCore.Signal()
|
||||
install = QtCore.Signal(Addon)
|
||||
uninstall = QtCore.Signal(Addon)
|
||||
update = QtCore.Signal(Addon)
|
||||
execute = QtCore.Signal(Addon)
|
||||
update_status = QtCore.Signal(Addon)
|
||||
check_for_update = QtCore.Signal(Addon)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_PackageDetails()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.worker = None
|
||||
self.repo = None
|
||||
self.status_update_thread = None
|
||||
try:
|
||||
self.git_manager = GitManager()
|
||||
except NoGitFound:
|
||||
self.git_manager = None
|
||||
|
||||
self.ui.button_bar.back_button.clicked.connect(self.back.emit)
|
||||
self.ui.button_bar.run_macro_button.clicked.connect(lambda: self.execute.emit(self.repo))
|
||||
self.ui.button_bar.install_button.clicked.connect(lambda: self.install.emit(self.repo))
|
||||
self.ui.button_bar.uninstall_button.clicked.connect(lambda: self.uninstall.emit(self.repo))
|
||||
self.ui.button_bar.update_button.clicked.connect(lambda: self.update.emit(self.repo))
|
||||
self.ui.button_bar.check_for_update_button.clicked.connect(
|
||||
lambda: self.check_for_update.emit(self.repo)
|
||||
)
|
||||
self.ui.button_bar.change_branch_button.clicked.connect(self.change_branch_clicked)
|
||||
self.ui.button_bar.enable_button.clicked.connect(self.enable_clicked)
|
||||
self.ui.button_bar.disable_button.clicked.connect(self.disable_clicked)
|
||||
|
||||
def show_repo(self, repo: Addon, reload: bool = False) -> None:
|
||||
"""The main entry point for this class, shows the package details and related buttons
|
||||
for the provided repo. If reload is true, then even if this is already the current repo
|
||||
the data is reloaded."""
|
||||
|
||||
# If this is the same repo we were already showing, we do not have to do the
|
||||
# expensive refetch unless reload is true
|
||||
if True or self.repo != repo or reload:
|
||||
self.repo = repo
|
||||
|
||||
if self.worker is not None:
|
||||
if not self.worker.isFinished():
|
||||
self.worker.requestInterruption()
|
||||
self.worker.wait()
|
||||
|
||||
if repo.repo_type == Addon.Kind.MACRO:
|
||||
self.show_macro(repo)
|
||||
self.ui.button_bar.run_macro_button.show()
|
||||
elif repo.repo_type == Addon.Kind.WORKBENCH:
|
||||
self.show_workbench(repo)
|
||||
self.ui.button_bar.run_macro_button.hide()
|
||||
elif repo.repo_type == Addon.Kind.PACKAGE:
|
||||
self.show_package(repo)
|
||||
self.ui.button_bar.run_macro_button.hide()
|
||||
|
||||
if repo.status() == Addon.Status.UNCHECKED:
|
||||
if not self.status_update_thread:
|
||||
self.status_update_thread = QtCore.QThread()
|
||||
self.status_create_addon_list_worker = CheckSingleUpdateWorker(repo)
|
||||
self.status_create_addon_list_worker.moveToThread(self.status_update_thread)
|
||||
self.status_update_thread.finished.connect(
|
||||
self.status_create_addon_list_worker.deleteLater
|
||||
)
|
||||
self.check_for_update.connect(self.status_create_addon_list_worker.do_work)
|
||||
self.status_create_addon_list_worker.update_status.connect(self.display_repo_status)
|
||||
self.status_update_thread.start()
|
||||
self.check_for_update.emit(self.repo)
|
||||
|
||||
self.display_repo_status(self.repo.update_status)
|
||||
|
||||
def display_repo_status(self, status):
|
||||
"""Updates the contents of the widget to display the current install status of the widget."""
|
||||
repo = self.repo
|
||||
self.set_change_branch_button_state()
|
||||
self.set_disable_button_state()
|
||||
if status != Addon.Status.NOT_INSTALLED:
|
||||
version = repo.installed_version
|
||||
date = ""
|
||||
installed_version_string = "<h3>"
|
||||
if repo.updated_timestamp:
|
||||
date = QtCore.QLocale().toString(
|
||||
QtCore.QDateTime.fromSecsSinceEpoch(int(round(repo.updated_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") + ". "
|
||||
|
||||
if status == Addon.Status.UPDATE_AVAILABLE:
|
||||
if repo.metadata:
|
||||
name_change = False
|
||||
if repo.installed_metadata:
|
||||
old_branch = get_branch_from_metadata(repo.installed_metadata)
|
||||
new_branch = get_branch_from_metadata(repo.metadata)
|
||||
if old_branch != new_branch:
|
||||
installed_version_string += (
|
||||
"<b>"
|
||||
+ translate(
|
||||
"AddonsInstaller",
|
||||
"Currently on branch {}, name changed to {}",
|
||||
).format(old_branch, new_branch)
|
||||
) + ".</b> "
|
||||
name_change = True
|
||||
if not name_change:
|
||||
installed_version_string += (
|
||||
"<b>"
|
||||
+ translate(
|
||||
"AddonsInstaller",
|
||||
"On branch {}, update available to version",
|
||||
).format(repo.branch)
|
||||
+ " "
|
||||
)
|
||||
installed_version_string += str(repo.metadata.version)
|
||||
installed_version_string += ".</b>"
|
||||
elif repo.macro and repo.macro.version:
|
||||
installed_version_string += (
|
||||
"<b>" + translate("AddonsInstaller", "Update available to version") + " "
|
||||
)
|
||||
installed_version_string += repo.macro.version
|
||||
installed_version_string += ".</b>"
|
||||
else:
|
||||
installed_version_string += (
|
||||
"<b>"
|
||||
+ translate(
|
||||
"AddonsInstaller",
|
||||
"An update is available",
|
||||
)
|
||||
+ ".</b>"
|
||||
)
|
||||
elif status == Addon.Status.NO_UPDATE_AVAILABLE:
|
||||
detached_head = False
|
||||
branch = repo.branch
|
||||
if self.git_manager and repo.repo_type != Addon.Kind.MACRO:
|
||||
basedir = fci.getUserAppDataDir()
|
||||
moddir = os.path.join(basedir, "Mod", repo.name)
|
||||
repo_path = os.path.join(moddir, ".git")
|
||||
if os.path.exists(repo_path):
|
||||
branch = self.git_manager.current_branch(repo_path)
|
||||
if self.git_manager.detached_head(repo_path):
|
||||
tag = self.git_manager.current_tag(repo_path)
|
||||
branch = tag
|
||||
detached_head = True
|
||||
if detached_head:
|
||||
installed_version_string += (
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"Git tag '{}' checked out, no updates possible",
|
||||
).format(branch)
|
||||
+ "."
|
||||
)
|
||||
else:
|
||||
installed_version_string += (
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"This is the latest version available for branch {}",
|
||||
).format(branch)
|
||||
+ "."
|
||||
)
|
||||
elif status == Addon.Status.PENDING_RESTART:
|
||||
installed_version_string += (
|
||||
translate("AddonsInstaller", "Updated, please restart FreeCAD to use") + "."
|
||||
)
|
||||
elif status == Addon.Status.UNCHECKED:
|
||||
pref = fci.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
autocheck = pref.GetBool("AutoCheck", False)
|
||||
if autocheck:
|
||||
installed_version_string += (
|
||||
translate("AddonsInstaller", "Update check in progress") + "."
|
||||
)
|
||||
else:
|
||||
installed_version_string += (
|
||||
translate("AddonsInstaller", "Automatic update checks disabled") + "."
|
||||
)
|
||||
|
||||
installed_version_string += "</h3>"
|
||||
self.ui.labelPackageDetails.setText(installed_version_string)
|
||||
if repo.status() == Addon.Status.UPDATE_AVAILABLE:
|
||||
self.ui.labelPackageDetails.setStyleSheet("color:" + utils.attention_color_string())
|
||||
else:
|
||||
self.ui.labelPackageDetails.setStyleSheet("color:" + utils.bright_color_string())
|
||||
self.ui.labelPackageDetails.show()
|
||||
|
||||
if repo.macro is not None:
|
||||
moddir = fci.getUserMacroDir(True)
|
||||
else:
|
||||
basedir = fci.getUserAppDataDir()
|
||||
moddir = os.path.join(basedir, "Mod", repo.name)
|
||||
installationLocationString = (
|
||||
translate("AddonsInstaller", "Installation location")
|
||||
+ ": "
|
||||
+ os.path.normpath(moddir)
|
||||
)
|
||||
|
||||
self.ui.labelInstallationLocation.setText(installationLocationString)
|
||||
self.ui.labelInstallationLocation.show()
|
||||
else:
|
||||
self.ui.labelPackageDetails.hide()
|
||||
self.ui.labelInstallationLocation.hide()
|
||||
|
||||
if status == Addon.Status.NOT_INSTALLED:
|
||||
self.ui.button_bar.install_button.show()
|
||||
self.ui.button_bar.uninstall_button.hide()
|
||||
self.ui.button_bar.update_button.hide()
|
||||
self.ui.button_bar.check_for_update_button.hide()
|
||||
elif status == Addon.Status.NO_UPDATE_AVAILABLE:
|
||||
self.ui.button_bar.install_button.hide()
|
||||
self.ui.button_bar.uninstall_button.show()
|
||||
self.ui.button_bar.update_button.hide()
|
||||
self.ui.button_bar.check_for_update_button.hide()
|
||||
elif status == Addon.Status.UPDATE_AVAILABLE:
|
||||
self.ui.button_bar.install_button.hide()
|
||||
self.ui.button_bar.uninstall_button.show()
|
||||
self.ui.button_bar.update_button.show()
|
||||
self.ui.button_bar.check_for_update_button.hide()
|
||||
elif status == Addon.Status.UNCHECKED:
|
||||
self.ui.button_bar.install_button.hide()
|
||||
self.ui.button_bar.uninstall_button.show()
|
||||
self.ui.button_bar.update_button.hide()
|
||||
self.ui.button_bar.check_for_update_button.show()
|
||||
elif status == Addon.Status.PENDING_RESTART:
|
||||
self.ui.button_bar.install_button.hide()
|
||||
self.ui.button_bar.uninstall_button.show()
|
||||
self.ui.button_bar.update_button.hide()
|
||||
self.ui.button_bar.check_for_update_button.hide()
|
||||
elif status == Addon.Status.CANNOT_CHECK:
|
||||
self.ui.button_bar.install_button.hide()
|
||||
self.ui.button_bar.uninstall_button.show()
|
||||
self.ui.button_bar.update_button.show()
|
||||
self.ui.button_bar.check_for_update_button.hide()
|
||||
|
||||
required_version = self.requires_newer_freecad()
|
||||
if repo.obsolete:
|
||||
self.ui.labelWarningInfo.show()
|
||||
self.ui.labelWarningInfo.setText(
|
||||
"<h1>" + translate("AddonsInstaller", "WARNING: This addon is obsolete") + "</h1>"
|
||||
)
|
||||
self.ui.labelWarningInfo.setStyleSheet("color:" + utils.warning_color_string())
|
||||
elif repo.python2:
|
||||
self.ui.labelWarningInfo.show()
|
||||
self.ui.labelWarningInfo.setText(
|
||||
"<h1>"
|
||||
+ translate("AddonsInstaller", "WARNING: This addon is Python 2 Only")
|
||||
+ "</h1>"
|
||||
)
|
||||
self.ui.labelWarningInfo.setStyleSheet("color:" + utils.warning_color_string())
|
||||
elif required_version:
|
||||
self.ui.labelWarningInfo.show()
|
||||
self.ui.labelWarningInfo.setText(
|
||||
"<h1>"
|
||||
+ translate("AddonsInstaller", "WARNING: This addon requires FreeCAD ")
|
||||
+ required_version
|
||||
+ "</h1>"
|
||||
)
|
||||
self.ui.labelWarningInfo.setStyleSheet("color:" + utils.warning_color_string())
|
||||
elif repo.is_disabled():
|
||||
self.ui.labelWarningInfo.show()
|
||||
self.ui.labelWarningInfo.setText(
|
||||
"<h2>"
|
||||
+ translate(
|
||||
"AddonsInstaller",
|
||||
"WARNING: This addon is currently installed, but disabled. Use the 'enable' button to re-enable.",
|
||||
)
|
||||
+ "</h2>"
|
||||
)
|
||||
self.ui.labelWarningInfo.setStyleSheet("color:" + utils.warning_color_string())
|
||||
|
||||
else:
|
||||
self.ui.labelWarningInfo.hide()
|
||||
|
||||
def requires_newer_freecad(self) -> Optional[Version]:
|
||||
"""If the current package is not installed, returns the first supported version of
|
||||
FreeCAD, if one is set, or None if no information is available (or if the package is
|
||||
already installed)."""
|
||||
|
||||
# If it's not installed, check to see if it's for a newer version of FreeCAD
|
||||
if self.repo.status() == Addon.Status.NOT_INSTALLED and self.repo.metadata:
|
||||
# Only hide if ALL content items require a newer version, otherwise
|
||||
# it's possible that this package actually provides versions of itself
|
||||
# for newer and older versions
|
||||
|
||||
first_supported_version = get_first_supported_freecad_version(self.repo.metadata)
|
||||
if first_supported_version is not None:
|
||||
fc_version = Version(from_list=fci.Version())
|
||||
if first_supported_version > fc_version:
|
||||
return first_supported_version
|
||||
return None
|
||||
|
||||
def set_change_branch_button_state(self):
|
||||
"""The change branch button is only available for installed Addons that have a .git directory
|
||||
and in runs where the git is available."""
|
||||
|
||||
self.ui.button_bar.change_branch_button.hide()
|
||||
|
||||
pref = fci.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
show_switcher = pref.GetBool("ShowBranchSwitcher", False)
|
||||
if not show_switcher:
|
||||
return
|
||||
|
||||
# Is this repo installed? If not, return.
|
||||
if self.repo.status() == Addon.Status.NOT_INSTALLED:
|
||||
return
|
||||
|
||||
# Is it a Macro? If so, return:
|
||||
if self.repo.repo_type == Addon.Kind.MACRO:
|
||||
return
|
||||
|
||||
# Can we actually switch branches? If not, return.
|
||||
if not self.git_manager:
|
||||
return
|
||||
|
||||
# Is there a .git subdirectory? If not, return.
|
||||
basedir = fci.getUserAppDataDir()
|
||||
path_to_git = os.path.join(basedir, "Mod", self.repo.name, ".git")
|
||||
if not os.path.isdir(path_to_git):
|
||||
return
|
||||
|
||||
# If all four above checks passed, then it's possible for us to switch
|
||||
# branches, if there are any besides the one we are on: show the button
|
||||
self.ui.button_bar.change_branch_button.show()
|
||||
|
||||
def set_disable_button_state(self):
|
||||
"""Set up the enable/disable button based on the enabled/disabled state of the addon"""
|
||||
self.ui.button_bar.enable_button.hide()
|
||||
self.ui.button_bar.disable_button.hide()
|
||||
status = self.repo.status()
|
||||
if status != Addon.Status.NOT_INSTALLED:
|
||||
disabled = self.repo.is_disabled()
|
||||
if disabled:
|
||||
self.ui.button_bar.enable_button.show()
|
||||
else:
|
||||
self.ui.button_bar.disable_button.show()
|
||||
|
||||
def show_workbench(self, repo: Addon) -> None:
|
||||
"""loads information of a given workbench"""
|
||||
|
||||
self.ui.textBrowserReadMe.set_addon(repo)
|
||||
|
||||
def show_package(self, repo: Addon) -> None:
|
||||
"""Show the details for a package (a repo with a package.xml metadata file)"""
|
||||
|
||||
self.ui.textBrowserReadMe.set_addon(repo)
|
||||
|
||||
def show_macro(self, repo: Addon) -> None:
|
||||
"""loads information of a given macro"""
|
||||
|
||||
if not repo.macro.url:
|
||||
# We need to populate the macro information... may as well do it while the user reads the wiki page
|
||||
self.worker = GetMacroDetailsWorker(repo)
|
||||
self.worker.readme_updated.connect(self.macro_readme_updated)
|
||||
self.worker.start()
|
||||
else:
|
||||
self.macro_readme_updated()
|
||||
|
||||
def macro_readme_updated(self):
|
||||
"""Update the display of a Macro's README data."""
|
||||
|
||||
self.ui.textBrowserReadMe.set_addon(self.repo)
|
||||
|
||||
def change_branch_clicked(self) -> None:
|
||||
"""Loads the branch-switching dialog"""
|
||||
basedir = fci.getUserAppDataDir()
|
||||
path_to_repo = os.path.join(basedir, "Mod", self.repo.name)
|
||||
change_branch_dialog = ChangeBranchDialog(path_to_repo, self)
|
||||
change_branch_dialog.branch_changed.connect(self.branch_changed)
|
||||
change_branch_dialog.exec()
|
||||
|
||||
def enable_clicked(self) -> None:
|
||||
"""Called by the Enable button, enables this Addon and updates GUI to reflect
|
||||
that status."""
|
||||
self.repo.enable()
|
||||
self.repo.set_status(Addon.Status.PENDING_RESTART)
|
||||
self.set_disable_button_state()
|
||||
self.update_status.emit(self.repo)
|
||||
self.ui.labelWarningInfo.show()
|
||||
self.ui.labelWarningInfo.setText(
|
||||
"<h3>"
|
||||
+ translate(
|
||||
"AddonsInstaller",
|
||||
"This Addon will be enabled next time you restart FreeCAD.",
|
||||
)
|
||||
+ "</h3>"
|
||||
)
|
||||
self.ui.labelWarningInfo.setStyleSheet("color:" + utils.bright_color_string())
|
||||
|
||||
def disable_clicked(self) -> None:
|
||||
"""Called by the Disable button, disables this Addon and updates the GUI to
|
||||
reflect that status."""
|
||||
self.repo.disable()
|
||||
self.repo.set_status(Addon.Status.PENDING_RESTART)
|
||||
self.set_disable_button_state()
|
||||
self.update_status.emit(self.repo)
|
||||
self.ui.labelWarningInfo.show()
|
||||
self.ui.labelWarningInfo.setText(
|
||||
"<h3>"
|
||||
+ translate(
|
||||
"AddonsInstaller",
|
||||
"This Addon will be disabled next time you restart FreeCAD.",
|
||||
)
|
||||
+ "</h3>"
|
||||
)
|
||||
self.ui.labelWarningInfo.setStyleSheet("color:" + utils.attention_color_string())
|
||||
|
||||
def branch_changed(self, name: str) -> None:
|
||||
"""Displays a dialog confirming the branch changed, and tries to access the
|
||||
metadata file from that branch."""
|
||||
QtWidgets.QMessageBox.information(
|
||||
self,
|
||||
translate("AddonsInstaller", "Success"),
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"Branch change succeeded, please restart to use the new version.",
|
||||
),
|
||||
)
|
||||
# See if this branch has a package.xml file:
|
||||
basedir = fci.getUserAppDataDir()
|
||||
path_to_metadata = os.path.join(basedir, "Mod", self.repo.name, "package.xml")
|
||||
if os.path.isfile(path_to_metadata):
|
||||
self.repo.load_metadata_file(path_to_metadata)
|
||||
self.repo.installed_version = self.repo.metadata.version
|
||||
else:
|
||||
self.repo.repo_type = Addon.Kind.WORKBENCH
|
||||
self.repo.metadata = None
|
||||
self.repo.installed_version = None
|
||||
self.repo.updated_timestamp = QtCore.QDateTime.currentDateTime().toSecsSinceEpoch()
|
||||
self.repo.branch = name
|
||||
self.repo.set_status(Addon.Status.PENDING_RESTART)
|
||||
|
||||
installed_version_string = "<h3>"
|
||||
installed_version_string += translate(
|
||||
"AddonsInstaller", "Changed to git ref '{}' -- please restart to use Addon."
|
||||
).format(name)
|
||||
installed_version_string += "</h3>"
|
||||
self.ui.labelPackageDetails.setText(installed_version_string)
|
||||
self.ui.labelPackageDetails.setStyleSheet("color:" + utils.attention_color_string())
|
||||
self.update_status.emit(self.repo)
|
||||
|
||||
|
||||
class Ui_PackageDetails(object):
|
||||
"""The generated UI from the Qt Designer UI file"""
|
||||
|
||||
def setupUi(self, PackageDetails):
|
||||
if not PackageDetails.objectName():
|
||||
PackageDetails.setObjectName("PackageDetails")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(PackageDetails)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.layoutDetailsBackButton = QtWidgets.QHBoxLayout()
|
||||
self.layoutDetailsBackButton.setObjectName("layoutDetailsBackButton")
|
||||
|
||||
self.button_bar = WidgetAddonButtons(PackageDetails)
|
||||
self.layoutDetailsBackButton.addWidget(self.button_bar)
|
||||
|
||||
self.verticalLayout_2.addLayout(self.layoutDetailsBackButton)
|
||||
|
||||
self.labelPackageDetails = QtWidgets.QLabel(PackageDetails)
|
||||
self.labelPackageDetails.hide()
|
||||
|
||||
self.verticalLayout_2.addWidget(self.labelPackageDetails)
|
||||
|
||||
self.labelInstallationLocation = QtWidgets.QLabel(PackageDetails)
|
||||
self.labelInstallationLocation.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
|
||||
self.labelInstallationLocation.hide()
|
||||
|
||||
self.verticalLayout_2.addWidget(self.labelInstallationLocation)
|
||||
|
||||
self.labelWarningInfo = QtWidgets.QLabel(PackageDetails)
|
||||
self.labelWarningInfo.hide()
|
||||
|
||||
self.verticalLayout_2.addWidget(self.labelWarningInfo)
|
||||
|
||||
sizePolicy1 = QtWidgets.QSizePolicy(
|
||||
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
|
||||
)
|
||||
sizePolicy1.setHorizontalStretch(0)
|
||||
sizePolicy1.setVerticalStretch(0)
|
||||
|
||||
self.textBrowserReadMe = ReadmeViewer(PackageDetails)
|
||||
self.textBrowserReadMe.setObjectName("textBrowserReadMe")
|
||||
|
||||
self.verticalLayout_2.addWidget(self.textBrowserReadMe)
|
||||
|
||||
self.retranslateUi(PackageDetails)
|
||||
|
||||
QtCore.QMetaObject.connectSlotsByName(PackageDetails)
|
||||
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, _):
|
||||
pass
|
||||
|
||||
# retranslateUi
|
||||
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
Reference in New Issue
Block a user