Addon Manager: Refactor progress reporting

This commit is contained in:
Chris Hennes
2024-12-05 22:08:47 -06:00
committed by Yorik van Havre
parent 1cf2bacf1f
commit 30177e2cf2
6 changed files with 293 additions and 93 deletions

View File

@@ -56,6 +56,7 @@ import addonmanager_freecad_interface as fci
import AddonManager_rc # pylint: disable=unused-import
from composite_view import CompositeView
from Widgets.addonmanager_widget_global_buttons import WidgetGlobalButtonBar
from Widgets.addonmanager_widget_progress_bar import Progress
from package_list import PackageListItemModel
from Addon import Addon
from manage_python_dependencies import (
@@ -310,7 +311,7 @@ class CommandAddonManager(QtCore.QObject):
)
self.button_bar.python_dependencies.clicked.connect(self.show_python_updates_dialog)
self.button_bar.developer_tools.clicked.connect(self.show_developer_tools)
self.composite_view.package_list.ui.progressBar.stop_clicked.connect(self.stop_update)
self.composite_view.package_list.stop_loading.connect(self.stop_update)
self.composite_view.package_list.setEnabled(False)
self.composite_view.execute.connect(self.execute_macro)
self.composite_view.install.connect(self.launch_installer_gui)
@@ -328,9 +329,6 @@ class CommandAddonManager(QtCore.QObject):
# begin populating the table in a set of sub-threads
self.startup()
# set the label text to start with
self.show_information(translate("AddonsInstaller", "Loading addon information"))
# rock 'n roll!!!
self.dialog.exec()
@@ -522,9 +520,8 @@ class CommandAddonManager(QtCore.QObject):
self.update_cache = True # Make sure to trigger the other cache updates, if the json
# file was missing
self.create_addon_list_worker = CreateAddonListWorker()
self.create_addon_list_worker.status_message.connect(self.show_information)
self.create_addon_list_worker.addon_repo.connect(self.add_addon_repo)
self.update_progress_bar(10, 100)
self.update_progress_bar(translate("AddonsInstaller", "Creating addon list"), 10, 100)
self.create_addon_list_worker.finished.connect(
self.do_next_startup_phase
) # Link to step 2
@@ -534,7 +531,7 @@ class CommandAddonManager(QtCore.QObject):
utils.get_cache_file_name("package_cache.json")
)
self.create_addon_list_worker.addon_repo.connect(self.add_addon_repo)
self.update_progress_bar(10, 100)
self.update_progress_bar(translate("AddonsInstaller", "Loading addon list"), 10, 100)
self.create_addon_list_worker.finished.connect(
self.do_next_startup_phase
) # Link to step 2
@@ -568,9 +565,10 @@ class CommandAddonManager(QtCore.QObject):
self.update_cache = True # Make sure to trigger the other cache updates, if the
# json file was missing
self.create_addon_list_worker = CreateAddonListWorker()
self.create_addon_list_worker.status_message.connect(self.show_information)
self.create_addon_list_worker.addon_repo.connect(self.add_addon_repo)
self.update_progress_bar(10, 100)
self.update_progress_bar(
translate("AddonsInstaller", "Creating macro list"), 10, 100
)
self.create_addon_list_worker.finished.connect(
self.do_next_startup_phase
) # Link to step 2
@@ -608,7 +606,6 @@ class CommandAddonManager(QtCore.QObject):
def update_metadata_cache(self) -> None:
if self.update_cache:
self.update_metadata_cache_worker = UpdateMetadataCacheWorker(self.item_model.repos)
self.update_metadata_cache_worker.status_message.connect(self.show_information)
self.update_metadata_cache_worker.finished.connect(
self.do_next_startup_phase
) # Link to step 4
@@ -644,7 +641,6 @@ class CommandAddonManager(QtCore.QObject):
def load_macro_metadata(self) -> None:
if self.update_cache:
self.load_macro_metadata_worker = CacheMacroCodeWorker(self.item_model.repos)
self.load_macro_metadata_worker.status_message.connect(self.show_information)
self.load_macro_metadata_worker.update_macro.connect(self.on_package_updated)
self.load_macro_metadata_worker.progress_made.connect(self.update_progress_bar)
self.load_macro_metadata_worker.finished.connect(self.do_next_startup_phase)
@@ -798,12 +794,6 @@ class CommandAddonManager(QtCore.QObject):
return
self.item_model.append_item(addon_repo)
def show_information(self, message: str) -> None:
"""shows generic text in the information pane"""
self.composite_view.package_list.ui.progressBar.set_status(message)
self.composite_view.package_list.ui.progressBar.repaint()
def append_to_repos_list(self, repo: Addon) -> None:
"""this function allows threads to update the main list of workbenches"""
self.item_model.append_item(repo)
@@ -862,15 +852,12 @@ class CommandAddonManager(QtCore.QObject):
def hide_progress_widgets(self) -> None:
"""hides the progress bar and related widgets"""
self.composite_view.package_list.ui.progressBar.hide()
self.composite_view.package_list.ui.view_bar.search.setFocus()
self.composite_view.package_list.set_loading(False)
def show_progress_widgets(self) -> None:
if self.composite_view.package_list.ui.progressBar.isHidden():
self.composite_view.package_list.ui.progressBar.show()
self.composite_view.package_list.set_loading(True)
def update_progress_bar(self, current_value: int, max_value: int) -> None:
def update_progress_bar(self, message: str, current_value: int, max_value: int) -> None:
"""Update the progress bar, showing it if it's hidden"""
max_value = max_value if max_value > 0 else 1
@@ -881,14 +868,14 @@ class CommandAddonManager(QtCore.QObject):
current_value = max_value
self.show_progress_widgets()
region_size = 100.0 / self.number_of_progress_regions
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.composite_view.package_list.ui.progressBar.set_value(
value * 10
) # Out of 1000 segments, so it moves sort of smoothly
self.composite_view.package_list.ui.progressBar.repaint()
progress = Progress(
status_text=message,
number_of_tasks=self.number_of_progress_regions,
current_task=self.current_progress_region - 1,
current_task_progress=current_value / max_value,
)
self.composite_view.package_list.update_loading_progress(progress)
def stop_update(self) -> None:
self.cleanup_workers()

View File

@@ -0,0 +1,145 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2022 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/>. *
# * *
# ***************************************************************************
import sys
import unittest
sys.path.append("../..")
from Widgets.addonmanager_widget_progress_bar import Progress
class TestProgress(unittest.TestCase):
def test_default_construction(self):
"""Given no parameters, a single-task Progress object is initialized with zero progress"""
progress = Progress()
self.assertEqual(progress.status_text, "")
self.assertEqual(progress.number_of_tasks, 1)
self.assertEqual(progress.current_task, 0)
self.assertEqual(progress.current_task_progress, 0.0)
def test_good_parameters(self):
"""Given good parameters, no exception is raised"""
_ = Progress(
status_text="Some text", number_of_tasks=1, current_task=0, current_task_progress=0.0
)
def test_zero_task_count(self):
with self.assertRaises(ValueError):
_ = Progress(number_of_tasks=0)
def test_negative_task_count(self):
with self.assertRaises(ValueError):
_ = Progress(number_of_tasks=-1)
def test_setting_status_post_creation(self):
progress = Progress()
self.assertEqual(progress.status_text, "")
progress.status_text = "Some status"
self.assertEqual(progress.status_text, "Some status")
def test_setting_task_count(self):
progress = Progress()
progress.number_of_tasks = 10
self.assertEqual(progress.number_of_tasks, 10)
def test_setting_negative_task_count(self):
progress = Progress()
with self.assertRaises(ValueError):
progress.number_of_tasks = -1
def test_setting_invalid_task_count(self):
progress = Progress()
with self.assertRaises(TypeError):
progress.number_of_tasks = 3.14159
def test_setting_current_task(self):
progress = Progress(number_of_tasks=10)
progress.number_of_tasks = 5
self.assertEqual(progress.number_of_tasks, 5)
def test_setting_current_task_greater_than_task_count(self):
progress = Progress()
progress.number_of_tasks = 10
with self.assertRaises(ValueError):
progress.current_task = 11
def test_setting_current_task_equal_to_task_count(self):
"""current_task is zero-indexed, so this is too high"""
progress = Progress()
progress.number_of_tasks = 10
with self.assertRaises(ValueError):
progress.current_task = 10
def test_setting_current_task_negative(self):
progress = Progress()
with self.assertRaises(ValueError):
progress.current_task = -1
def test_setting_current_task_invalid(self):
progress = Progress()
with self.assertRaises(TypeError):
progress.current_task = 2.718281
def test_setting_current_task_progress(self):
progress = Progress()
progress.current_task_progress = 50.0
self.assertEqual(progress.current_task_progress, 50.0)
def test_setting_current_task_progress_too_low(self):
progress = Progress()
progress.current_task_progress = -0.01
self.assertEqual(progress.current_task_progress, 0.0)
def test_setting_current_task_progress_too_high(self):
progress = Progress()
progress.current_task_progress = 100.001
self.assertEqual(progress.current_task_progress, 100.0)
def test_incrementing_task(self):
progress = Progress(number_of_tasks=10, current_task_progress=100.0)
progress.next_task()
self.assertEqual(progress.current_task, 1)
self.assertEqual(progress.current_task_progress, 0.0)
def test_incrementing_task_too_high(self):
progress = Progress(number_of_tasks=10, current_task=9, current_task_progress=100.0)
with self.assertRaises(ValueError):
progress.next_task()
def test_overall_progress_simple(self):
progress = Progress()
self.assertEqual(progress.overall_progress(), 0.0)
def test_overall_progress_with_ranges(self):
progress = Progress(number_of_tasks=2, current_task=1, current_task_progress=0.0)
self.assertAlmostEqual(progress.overall_progress(), 0.5)
def test_overall_progress_with_ranges_and_progress(self):
progress = Progress(number_of_tasks=10, current_task=5, current_task_progress=50.0)
self.assertAlmostEqual(progress.overall_progress(), 0.55)
if __name__ == "__main__":
unittest.main()

View File

@@ -36,27 +36,103 @@ except ImportError:
# Get whatever version of PySide we can
try:
import PySide # Use the FreeCAD wrapper
from PySide import QtCore, QtGui, QtWidgets # Use the FreeCAD wrapper
except ImportError:
try:
import PySide6 # Outside FreeCAD, try Qt6 first
PySide = PySide6
from PySide6 import QtCore, QtGui, QtWidgets # Outside FreeCAD, try Qt6 first
except ImportError:
import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import)
from PySide2 import QtCore, QtGui, QtWidgets # Fall back to Qt5
PySide = PySide2
from PySide import QtCore, QtGui, QtWidgets
from dataclasses import dataclass
_TOTAL_INCREMENTS = 1000
class Progress:
"""Represents progress through a process composed of multiple sub-tasks."""
def __init__(
self,
*,
status_text: str = "",
number_of_tasks: int = 1,
current_task: int = 0,
current_task_progress: float = 0.0,
):
if number_of_tasks < 1:
raise ValueError(f"Number of tasks must be at least one, not {number_of_tasks}")
if current_task < 0 or current_task >= number_of_tasks:
raise ValueError(
"Current task must be between 0 and the number of tasks "
f"({number_of_tasks}), not {current_task}"
)
if current_task_progress < 0.0:
current_task_progress = 0.0
elif current_task_progress > 100.0:
current_task_progress = 100.0
self.status_text: str = status_text
self._number_of_tasks: int = number_of_tasks
self._current_task: int = current_task
self._current_task_progress: float = current_task_progress
@property
def number_of_tasks(self):
return self._number_of_tasks
@number_of_tasks.setter
def number_of_tasks(self, value: int):
if not isinstance(value, int):
raise TypeError("Number of tasks must be an integer")
if value < 1:
raise ValueError("Number of tasks must be at least one")
self._number_of_tasks = value
@property
def current_task(self):
"""The current task (zero-indexed, always less than the number of tasks)"""
return self._current_task
@current_task.setter
def current_task(self, value: int):
if not isinstance(value, int):
raise TypeError("Current task must be an integer")
if value < 0:
raise ValueError("Current task must be at least zero")
if value >= self._number_of_tasks:
raise ValueError("Current task must be less than the total number of tasks")
self._current_task = value
@property
def current_task_progress(self):
"""Current task progress, guaranteed to be in the range [0.0, 100.0]. Attempts to set a
value outside that range are clamped to the range."""
return self._current_task_progress
@current_task_progress.setter
def current_task_progress(self, value: float):
"""Set the current task's progress. Rather than raising an exception when the value is
outside the expected range of [0,100], clamp the task progress to allow for some
floating point imprecision in its calculation."""
if value < 0.0:
value = 0.0
elif value > 100.0:
value = 100.0
self._current_task_progress = value
def next_task(self) -> None:
"""Increment the task counter and reset the progress"""
self.current_task += 1
self.current_task_progress = 0.0
def overall_progress(self) -> float:
"""Gets the overall progress as a fractional value in the range [0, 1]"""
base = self._current_task / self._number_of_tasks
fraction = self._current_task_progress / (100.0 * self._number_of_tasks)
return base + fraction
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."""
"""A multipart progress bar widget, including a stop button and a status label."""
stop_clicked = QtCore.Signal()
@@ -87,8 +163,6 @@ class WidgetProgressBar(QtWidgets.QWidget):
self.vertical_layout.setContentsMargins(0, 0, 0, 0)
self.horizontal_layout.setContentsMargins(0, 0, 0, 0)
def set_status(self, status: str):
self.status_label.setText(status)
def set_value(self, value: int):
self.progress_bar.setValue(value)
def set_progress(self, progress: Progress) -> None:
self.status_label.setText(progress.status_text)
self.progress_bar.setValue(progress.overall_progress() * _TOTAL_INCREMENTS)

View File

@@ -53,8 +53,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
"""Scan through all available packages and see if our local copy of package.xml needs to be
updated"""
status_message = QtCore.Signal(str)
progress_made = QtCore.Signal(int, int)
progress_made = QtCore.Signal(str, int, int)
package_updated = QtCore.Signal(Addon)
class RequestType(Enum):
@@ -166,18 +165,26 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
"""Callback for handling a completed metadata file download."""
if index in self.requests:
self.requests_completed += 1
self.progress_made.emit(self.requests_completed, self.total_requests)
request = self.requests.pop(index)
if code == 200: # HTTP success
self.updated_repos.add(request[0]) # mark this repo as updated
file = "unknown"
if request[1] == UpdateMetadataCacheWorker.RequestType.PACKAGE_XML:
self.process_package_xml(request[0], data)
file = "package.xml"
elif request[1] == UpdateMetadataCacheWorker.RequestType.METADATA_TXT:
self.process_metadata_txt(request[0], data)
file = "metadata.txt"
elif request[1] == UpdateMetadataCacheWorker.RequestType.REQUIREMENTS_TXT:
self.process_requirements_txt(request[0], data)
file = "requirements.txt"
elif request[1] == UpdateMetadataCacheWorker.RequestType.ICON:
self.process_icon(request[0], data)
file = "icon"
message = translate("AddonsInstaller", "Downloaded {} for {}").format(
file, request[0].display_name
)
self.progress_made.emit(message, self.requests_completed, self.total_requests)
def process_package_xml(self, repo: Addon, data: QtCore.QByteArray):
"""Process the package.xml metadata file"""
@@ -197,9 +204,6 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
return
repo.set_metadata(metadata)
FreeCAD.Console.PrintLog(f"Downloaded package.xml for {repo.name}\n")
self.status_message.emit(
translate("AddonsInstaller", "Downloaded package.xml for {}").format(repo.name)
)
# Grab a new copy of the icon as well: we couldn't enqueue this earlier because
# we didn't know the path to it, which is stored in the package.xml file.
@@ -251,10 +255,6 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
def process_metadata_txt(self, repo: Addon, data: QtCore.QByteArray):
"""Process the metadata.txt metadata file"""
self.status_message.emit(
translate("AddonsInstaller", "Downloaded metadata.txt for {}").format(repo.display_name)
)
f = self._ensure_string(data, repo.name, "metadata.txt")
lines = f.splitlines()
for line in lines:
@@ -291,12 +291,6 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
def process_requirements_txt(self, repo: Addon, data: QtCore.QByteArray):
"""Process the requirements.txt metadata file"""
self.status_message.emit(
translate(
"AddonsInstaller",
"Downloaded requirements.txt for {}",
).format(repo.display_name)
)
f = self._ensure_string(data, repo.name, "requirements.txt")
lines = f.splitlines()
@@ -312,9 +306,6 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
def process_icon(self, repo: Addon, data: QtCore.QByteArray):
"""Convert icon data into a valid icon file and store it"""
self.status_message.emit(
translate("AddonsInstaller", "Downloaded icon for {}").format(repo.display_name)
)
cache_file = repo.get_cached_icon_filename()
with open(cache_file, "wb") as icon_file:
icon_file.write(data.data())

View File

@@ -57,8 +57,8 @@ class CreateAddonListWorker(QtCore.QThread):
"""This worker updates the list of available workbenches, emitting an "addon_repo"
signal for each Addon as they are processed."""
status_message = QtCore.Signal(str)
addon_repo = QtCore.Signal(object)
progress_made = QtCore.Signal(str, int, int)
def __init__(self):
QtCore.QThread.__init__(self)
@@ -121,7 +121,6 @@ class CreateAddonListWorker(QtCore.QThread):
"Failed to connect to GitHub. Check your connection and proxy settings.",
)
FreeCAD.Console.PrintError(message + "\n")
self.status_message.emit(message)
raise ConnectionError
def _process_deprecated(self, deprecated_addons):
@@ -265,8 +264,6 @@ class CreateAddonListWorker(QtCore.QThread):
repo.obsolete = True
self.addon_repo.emit(repo)
self.status_message.emit(translate("AddonsInstaller", "Workbenches list was updated."))
def _retrieve_macros_from_git(self):
"""Retrieve macros from FreeCAD-macros.git
@@ -281,7 +278,6 @@ class CreateAddonListWorker(QtCore.QThread):
"AddonsInstaller",
"Git is disabled, skipping Git macros",
)
self.status_message.emit(message)
FreeCAD.Console.PrintWarning(message + "\n")
return
@@ -529,7 +525,7 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
"""This worker checks for available updates for all workbenches"""
update_status = QtCore.Signal(Addon)
progress_made = QtCore.Signal(int, int)
progress_made = QtCore.Signal(str, int, int)
def __init__(self, repos: List[Addon]):
@@ -549,7 +545,10 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
for repo in self.repos:
if self.current_thread.isInterruptionRequested():
return
self.progress_made.emit(count, len(self.repos))
message = translate("AddonsInstaller", "Checking {} for update").format(
repo.display_name
)
self.progress_made.emit(message, count, len(self.repos))
count += 1
if repo.status() == Addon.Status.UNCHECKED:
if repo.repo_type == Addon.Kind.WORKBENCH:
@@ -743,9 +742,8 @@ class UpdateChecker:
class CacheMacroCodeWorker(QtCore.QThread):
"""Download and cache the macro code, and parse its internal metadata"""
status_message = QtCore.Signal(str)
update_macro = QtCore.Signal(Addon)
progress_made = QtCore.Signal(int, int)
progress_made = QtCore.Signal(str, int, int)
def __init__(self, repos: List[Addon]) -> None:
QtCore.QThread.__init__(self)
@@ -761,8 +759,6 @@ class CacheMacroCodeWorker(QtCore.QThread):
"""Rarely called directly: create an instance and call start() on it instead to
launch in a new thread"""
self.status_message.emit(translate("AddonsInstaller", "Caching macro code..."))
self.repo_queue = queue.Queue()
num_macros = 0
for repo in self.repos:
@@ -846,7 +842,8 @@ class CacheMacroCodeWorker(QtCore.QThread):
if QtCore.QThread.currentThread().isInterruptionRequested():
return
self.progress_made.emit(len(self.repos) - self.repo_queue.qsize(), len(self.repos))
message = translate("AddonsInstaller", "Caching {} macro").format(repo.display_name)
self.progress_made.emit(message, len(self.repos) - self.repo_queue.qsize(), len(self.repos))
try:
next_repo = self.repo_queue.get_nowait()
@@ -857,12 +854,6 @@ class CacheMacroCodeWorker(QtCore.QThread):
self.terminators.append(
QtCore.QTimer.singleShot(10000, lambda: self.terminate(worker))
)
self.status_message.emit(
translate(
"AddonsInstaller",
"Getting metadata from macro {}",
).format(next_repo.macro.name)
)
worker.start()
except queue.Empty:
pass
@@ -895,7 +886,6 @@ class CacheMacroCodeWorker(QtCore.QThread):
class GetMacroDetailsWorker(QtCore.QThread):
"""Retrieve the macro details for a macro"""
status_message = QtCore.Signal(str)
readme_updated = QtCore.Signal(str)
def __init__(self, repo):
@@ -907,12 +897,9 @@ class GetMacroDetailsWorker(QtCore.QThread):
"""Rarely called directly: create an instance and call start() on it instead to
launch in a new thread"""
self.status_message.emit(translate("AddonsInstaller", "Retrieving macro description..."))
if not self.macro.parsed and self.macro.on_git:
self.status_message.emit(translate("AddonsInstaller", "Retrieving info from Git"))
self.macro.fill_details_from_file(self.macro.src_filename)
if not self.macro.parsed and self.macro.on_wiki:
self.status_message.emit(translate("AddonsInstaller", "Retrieving info from wiki"))
mac = self.macro.name.replace(" ", "_")
mac = mac.replace("&", "%26")
mac = mac.replace("+", "%2B")

View File

@@ -39,7 +39,7 @@ from addonmanager_metadata import get_first_supported_freecad_version, Version
from Widgets.addonmanager_widget_view_control_bar import WidgetViewControlBar, SortOptions
from Widgets.addonmanager_widget_view_selector import AddonManagerDisplayStyle
from Widgets.addonmanager_widget_filter_selector import StatusFilter, Filter
from Widgets.addonmanager_widget_progress_bar import WidgetProgressBar
from Widgets.addonmanager_widget_progress_bar import Progress, WidgetProgressBar
from addonmanager_licenses import get_license_manager
translate = FreeCAD.Qt.translate
@@ -50,9 +50,11 @@ translate = FreeCAD.Qt.translate
class PackageList(QtWidgets.QWidget):
"""A widget that shows a list of packages and various widgets to control the
display of the list"""
display of the list, including a progress bar that can display and interrupt the load
process."""
itemSelected = QtCore.Signal(Addon)
stop_loading = QtCore.Signal()
def __init__(self, parent=None):
super().__init__(parent)
@@ -70,6 +72,7 @@ class PackageList(QtWidgets.QWidget):
self.ui.view_bar.sort_changed.connect(self.item_filter.setSortRole)
self.ui.view_bar.sort_changed.connect(self.item_delegate.set_sort)
self.ui.view_bar.sort_order_changed.connect(lambda order: self.item_filter.sort(0, order))
self.ui.progress_bar.stop_clicked.connect(self.stop_loading)
# Set up the view the same as the last time:
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
@@ -157,6 +160,19 @@ class PackageList(QtWidgets.QWidget):
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
pref.SetInt("ViewStyle", style)
def set_loading(self, is_loading: bool) -> None:
"""Set the loading status of this package list: when a package list is loading, it shows
a progress bar. When it is no longer loading, the bar is hidden and the search bar gets
the focus."""
if is_loading:
self.ui.progress_bar.show()
else:
self.ui.progress_bar.hide()
self.ui.view_bar.search.setFocus()
def update_loading_progress(self, progress: Progress) -> None:
self.ui.progress_bar.set_progress(progress)
class PackageListItemModel(QtCore.QAbstractListModel):
"""The model for use with the PackageList class."""
@@ -764,7 +780,7 @@ class Ui_PackageList:
self.verticalLayout.addWidget(self.listPackages)
self.progressBar = WidgetProgressBar()
self.verticalLayout.addWidget(self.progressBar)
self.progress_bar = WidgetProgressBar()
self.verticalLayout.addWidget(self.progress_bar)
QtCore.QMetaObject.connectSlotsByName(form)