diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 7a2680ce67..8d80b689fa 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -214,9 +214,6 @@ class CommandAddonManager: self.dialog.buttonClose.setIcon( QtGui.QIcon.fromTheme("close", QtGui.QIcon(":/icons/process-stop.svg")) ) - self.dialog.buttonPauseUpdate.setIcon( - QtGui.QIcon.fromTheme("pause", QtGui.QIcon(":/icons/media-playback-stop.svg")) - ) pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") dev_mode_active = pref.GetBool("developerMode", False) @@ -236,12 +233,12 @@ class CommandAddonManager: self.dialog.buttonUpdateAll.clicked.connect(self.update_all) self.dialog.buttonClose.clicked.connect(self.dialog.reject) self.dialog.buttonUpdateCache.clicked.connect(self.on_buttonUpdateCache_clicked) - self.dialog.buttonPauseUpdate.clicked.connect(self.stop_update) self.dialog.buttonCheckForUpdates.clicked.connect( lambda: self.force_check_updates(standalone=True) ) self.dialog.buttonUpdateDependencies.clicked.connect(self.show_python_updates_dialog) self.dialog.buttonDevTools.clicked.connect(self.show_developer_tools) + 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) @@ -257,9 +254,6 @@ class CommandAddonManager: mw.frameGeometry().topLeft() + mw.rect().center() - self.dialog.rect().center() ) - # set info for the progress bar: - self.dialog.progressBar.setMaximum(1000) - # begin populating the table in a set of sub-threads self.startup() @@ -407,8 +401,8 @@ class CommandAddonManager: if selection: self.startup_sequence.insert(2, functools.partial(self.select_addon, selection)) pref.SetString("SelectedAddon", "") - self.current_progress_region = 0 self.number_of_progress_regions = len(self.startup_sequence) + self.current_progress_region = 0 self.do_next_startup_phase() def do_next_startup_phase(self) -> None: @@ -750,8 +744,8 @@ class CommandAddonManager: def show_information(self, message: str) -> None: """shows generic text in the information pane""" - self.dialog.labelStatusInfo.setText(message) - self.dialog.labelStatusInfo.repaint() + self.packageList.ui.progressBar.set_status(message) + self.packageList.ui.progressBar.repaint() def show_workbench(self, repo: Addon) -> None: self.packageList.hide() @@ -821,16 +815,12 @@ class CommandAddonManager: def hide_progress_widgets(self) -> None: """hides the progress bar and related widgets""" - self.dialog.labelStatusInfo.hide() - self.dialog.progressBar.hide() - self.dialog.buttonPauseUpdate.hide() + self.packageList.ui.progressBar.hide() self.packageList.ui.view_bar.search.setFocus() def show_progress_widgets(self) -> None: - if self.dialog.progressBar.isHidden(): - self.dialog.progressBar.show() - self.dialog.buttonPauseUpdate.show() - self.dialog.labelStatusInfo.show() + if self.packageList.ui.progressBar.isHidden(): + self.packageList.ui.progressBar.show() def update_progress_bar(self, current_value: int, max_value: int) -> None: """Update the progress bar, showing it if it's hidden""" @@ -847,10 +837,10 @@ class CommandAddonManager: completed_region_portion = (self.current_progress_region - 1) * region_size current_region_portion = (float(current_value) / float(max_value)) * region_size value = completed_region_portion + current_region_portion - self.dialog.progressBar.setValue( + self.packageList.ui.progressBar.set_value( value * 10 ) # Out of 1000 segments, so it moves sort of smoothly - self.dialog.progressBar.repaint() + self.packageList.ui.progressBar.repaint() def stop_update(self) -> None: self.cleanup_workers() diff --git a/src/Mod/AddonManager/AddonManager.ui b/src/Mod/AddonManager/AddonManager.ui index bc75e323fa..8d394a589c 100644 --- a/src/Mod/AddonManager/AddonManager.ui +++ b/src/Mod/AddonManager/AddonManager.ui @@ -6,7 +6,7 @@ 0 0 - 800 + 928 600 @@ -24,74 +24,6 @@ - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 12 - - - - - 0 - 0 - - - - 0 - - - false - - - Downloading info... - - - - - - - Stop the cache update - - - - - - - - - - - - - 0 - 0 - - - - labelStatusInfo - - - - - diff --git a/src/Mod/AddonManager/TODO.md b/src/Mod/AddonManager/TODO.md new file mode 100644 index 0000000000..2c51912303 --- /dev/null +++ b/src/Mod/AddonManager/TODO.md @@ -0,0 +1,14 @@ +# Addon Manager Future Work + +* Restructure widgets into logical groups to better enable showing and hiding those groups all at once. +* Reduce coupling between data and UI, switching logical groupings of widgets into a MVC or similar framework. + * Particularly in the addons list +* Download Addon statistics from central location. + * Allow sorting on those statistics. +* Download a "rank" from user-specified locations. + * Allow sorting on that rank. +* Implement a server-side cache of Addon metadata. +* Implement an "offline mode" that does not attempt to use remote data for anything. +* When installing a Preference Pack, offer to apply it once installed, and to undo after that. +* Better support "headless" mode, with no GUI. +* Add "Composite" display mode, showing compact list and details at the same time diff --git a/src/Mod/AddonManager/Widgets/CMakeLists.txt b/src/Mod/AddonManager/Widgets/CMakeLists.txt index 61d8d613b1..99b4ee4617 100644 --- a/src/Mod/AddonManager/Widgets/CMakeLists.txt +++ b/src/Mod/AddonManager/Widgets/CMakeLists.txt @@ -1,6 +1,7 @@ SET(AddonManagerWidget_SRCS __init__.py addonmanager_widget_filter_selector.py + addonmanager_widget_progress_bar.py addonmanager_widget_search.py addonmanager_widget_view_control_bar.py addonmanager_widget_view_selector.py diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py new file mode 100644 index 0000000000..3f61950ba5 --- /dev/null +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py @@ -0,0 +1,94 @@ +# 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 * +# * . * +# * * +# *************************************************************************** + +""" Defines a QWidget-derived class for displaying the cache load status. """ + +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, QtGui, QtWidgets + +_TOTAL_INCREMENTS = 1000 + + +class WidgetProgressBar(QtWidgets.QWidget): + + """A multipart progress bar widget, including a stop button and a status label. Defaults to a + single range with 100 increments, but can be configured with any number of major and minor + ranges. Clicking the stop button will emit a signal, but does not otherwise affect the + widget.""" + + stop_clicked = QtCore.Signal() + + def __init__(self, parent: QtWidgets.QWidget = None): + super().__init__(parent) + self.vertical_layout = None + self.horizontal_layout = None + self.progress_bar = None + self.status_label = None + self.stop_button = None + self._setup_ui() + + def _setup_ui(self): + self.vertical_layout = QtWidgets.QVBoxLayout(self) + self.horizontal_layout = QtWidgets.QHBoxLayout(self) + self.progress_bar = QtWidgets.QProgressBar(self) + self.status_label = QtWidgets.QLabel(self) + self.stop_button = QtWidgets.QToolButton(self) + self.progress_bar.setMaximum(_TOTAL_INCREMENTS) + self.stop_button.clicked.connect(self.stop_clicked) + self.stop_button.setIcon( + QtGui.QIcon.fromTheme("stop", QtGui.QIcon(":/icons/media-playback-stop.svg")) + ) + self.vertical_layout.addLayout(self.horizontal_layout) + self.vertical_layout.addWidget(self.status_label) + self.horizontal_layout.addWidget(self.progress_bar) + self.horizontal_layout.addWidget(self.stop_button) + self.setLayout(self.vertical_layout) + + def set_status(self, status: str): + self.status_label.setText(status) + + def set_value(self, value: int): + self.progress_bar.setValue(value) diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index b4e2a9d1ce..6b443994b1 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -40,6 +40,7 @@ from addonmanager_metadata import get_first_supported_freecad_version, Version from Widgets.addonmanager_widget_view_control_bar import WidgetViewControlBar from Widgets.addonmanager_widget_view_selector import AddonManagerDisplayStyle from Widgets.addonmanager_widget_filter_selector import StatusFilter, Filter, ContentFilter +from Widgets.addonmanager_widget_progress_bar import WidgetProgressBar from addonmanager_licenses import get_license_manager, SPDXLicenseManager translate = FreeCAD.Qt.translate @@ -643,4 +644,7 @@ class Ui_PackageList: self.verticalLayout.addWidget(self.listPackages) + self.progressBar = WidgetProgressBar() + self.verticalLayout.addWidget(self.progressBar) + QtCore.QMetaObject.connectSlotsByName(form)