diff --git a/src/Mod/AddonManager/AddonManagerTest/gui/test_update_all_gui.py b/src/Mod/AddonManager/AddonManagerTest/gui/test_update_all_gui.py index c45d382d20..8cea9f327b 100644 --- a/src/Mod/AddonManager/AddonManagerTest/gui/test_update_all_gui.py +++ b/src/Mod/AddonManager/AddonManagerTest/gui/test_update_all_gui.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * * -# * Copyright (c) 2022 FreeCAD Project Association * +# * Copyright (c) 2022-2025 FreeCAD project association AISBL * # * * # * This file is part of FreeCAD. * # * * @@ -73,6 +73,8 @@ class MockAddon: self.display_name = name self.name = name self.macro = None + self.metadata = None + self.installed_metadata = None def status(self): return Addon.Status.UPDATE_AVAILABLE @@ -144,29 +146,29 @@ class TestUpdateAllGui(unittest.TestCase): def test_add_addon_to_table(self): mock_addon = MockAddon("MockAddon") self.test_object.dialog.tableWidget.clear() - self.test_object._add_addon_to_table(mock_addon) + self.test_object._add_addon_to_table(mock_addon, 1) self.assertEqual(self.test_object.dialog.tableWidget.rowCount(), 1) def test_update_addon_status(self): self.test_object._setup_dialog() self.test_object._update_addon_status(0, AddonStatus.WAITING) self.assertEqual( - self.test_object.dialog.tableWidget.item(0, 1).text(), + self.test_object.dialog.tableWidget.item(0, 2).text(), AddonStatus.WAITING.ui_string(), ) self.test_object._update_addon_status(0, AddonStatus.INSTALLING) self.assertEqual( - self.test_object.dialog.tableWidget.item(0, 1).text(), + self.test_object.dialog.tableWidget.item(0, 2).text(), AddonStatus.INSTALLING.ui_string(), ) self.test_object._update_addon_status(0, AddonStatus.SUCCEEDED) self.assertEqual( - self.test_object.dialog.tableWidget.item(0, 1).text(), + self.test_object.dialog.tableWidget.item(0, 2).text(), AddonStatus.SUCCEEDED.ui_string(), ) self.test_object._update_addon_status(0, AddonStatus.FAILED) self.assertEqual( - self.test_object.dialog.tableWidget.item(0, 1).text(), + self.test_object.dialog.tableWidget.item(0, 2).text(), AddonStatus.FAILED.ui_string(), ) @@ -175,19 +177,19 @@ class TestUpdateAllGui(unittest.TestCase): self.test_object._launch_active_installer = lambda: None self.test_object._process_next_update() self.assertEqual( - self.test_object.dialog.tableWidget.item(0, 1).text(), + self.test_object.dialog.tableWidget.item(0, 2).text(), AddonStatus.INSTALLING.ui_string(), ) self.test_object._process_next_update() self.assertEqual( - self.test_object.dialog.tableWidget.item(1, 1).text(), + self.test_object.dialog.tableWidget.item(1, 2).text(), AddonStatus.INSTALLING.ui_string(), ) self.test_object._process_next_update() self.assertEqual( - self.test_object.dialog.tableWidget.item(2, 1).text(), + self.test_object.dialog.tableWidget.item(2, 2).text(), AddonStatus.INSTALLING.ui_string(), ) @@ -208,7 +210,7 @@ class TestUpdateAllGui(unittest.TestCase): self.test_object._setup_dialog() self.test_object._update_succeeded(self.addons[0]) self.assertEqual( - self.test_object.dialog.tableWidget.item(0, 1).text(), + self.test_object.dialog.tableWidget.item(0, 2).text(), AddonStatus.SUCCEEDED.ui_string(), ) @@ -216,7 +218,7 @@ class TestUpdateAllGui(unittest.TestCase): self.test_object._setup_dialog() self.test_object._update_failed(self.addons[0]) self.assertEqual( - self.test_object.dialog.tableWidget.item(0, 1).text(), + self.test_object.dialog.tableWidget.item(0, 2).text(), AddonStatus.FAILED.ui_string(), ) diff --git a/src/Mod/AddonManager/addonmanager_update_all_gui.py b/src/Mod/AddonManager/addonmanager_update_all_gui.py index 9cfafbd0bf..89fd775ea7 100644 --- a/src/Mod/AddonManager/addonmanager_update_all_gui.py +++ b/src/Mod/AddonManager/addonmanager_update_all_gui.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * * -# * Copyright (c) 2022 FreeCAD Project Association * +# * Copyright (c) 2022-2025 FreeCAD project association AISBL * # * * # * This file is part of FreeCAD. * # * * @@ -85,6 +85,8 @@ class UpdateAllGUI(QtCore.QObject): finished = QtCore.Signal() addon_updated = QtCore.Signal(object) + index_role = QtCore.Qt.UserRole + 1 + def __init__(self, addons: List[Addon]): super().__init__() self.addons = addons @@ -114,28 +116,58 @@ class UpdateAllGUI(QtCore.QObject): self.dialog.tableWidget.clear() self.in_process_row = None self.row_map = {} + self._setup_empty_table() + counter = 0 for addon in self.addons: if addon.status() == Addon.Status.UPDATE_AVAILABLE: - self._add_addon_to_table(addon) + self._add_addon_to_table(addon, counter) self.addons_with_update.append(addon) + counter += 1 def _cancel_installation(self): self.cancelled = True if self.worker_thread and self.worker_thread.isRunning(): self.worker_thread.requestInterruption() - def _add_addon_to_table(self, addon: Addon): - """Add the given addon to the list, with no icon in the first column""" + def _setup_empty_table(self): + self.dialog.tableWidget.setColumnCount(4) + self.dialog.tableWidget.horizontalHeader().setSectionResizeMode( + 0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents + ) + self.dialog.tableWidget.horizontalHeader().setSectionResizeMode( + 1, QtWidgets.QHeaderView.ResizeMode.ResizeToContents + ) + self.dialog.tableWidget.horizontalHeader().setSectionResizeMode( + 2, QtWidgets.QHeaderView.ResizeMode.ResizeToContents + ) + self.dialog.tableWidget.horizontalHeader().setSectionResizeMode( + 3, QtWidgets.QHeaderView.ResizeMode.Stretch + ) + + def _add_addon_to_table(self, addon: Addon, index: int): + """Add the given addon to the list, storing its index as user data in the first column""" new_row = self.dialog.tableWidget.rowCount() - self.dialog.tableWidget.setColumnCount(2) self.dialog.tableWidget.setRowCount(new_row + 1) - self.dialog.tableWidget.setItem(new_row, 0, QtWidgets.QTableWidgetItem(addon.display_name)) - self.dialog.tableWidget.setItem(new_row, 1, QtWidgets.QTableWidgetItem("")) + new_item = QtWidgets.QTableWidgetItem(addon.display_name) + new_item.setData(UpdateAllGUI.index_role, index) # Only first item in each row needs data() + self.dialog.tableWidget.setItem(new_row, 0, new_item) + if addon.installed_metadata and addon.installed_metadata.version: + self.dialog.tableWidget.setItem( + new_row, 1, QtWidgets.QTableWidgetItem(str(addon.installed_metadata.version)) + ) + self.dialog.tableWidget.setItem(new_row, 2, QtWidgets.QTableWidgetItem("")) + self.dialog.tableWidget.setItem(new_row, 3, QtWidgets.QTableWidgetItem("")) self.row_map[addon.name] = new_row def _update_addon_status(self, row: int, status: AddonStatus): """Update the GUI to reflect this addon's new status.""" - self.dialog.tableWidget.item(row, 1).setText(status.ui_string()) + self.dialog.tableWidget.item(row, 2).setText(status.ui_string()) + if status == AddonStatus.SUCCEEDED and self.addons[row].metadata: + self.dialog.tableWidget.item(row, 2).setText(status.ui_string() + " →") + index = self.dialog.tableWidget.item(row, 0).data(UpdateAllGUI.index_role) + addon = self.addons[index] + if addon.metadata and addon.metadata.version: + self.dialog.tableWidget.item(row, 3).setText(str(addon.metadata.version)) def _process_next_update(self): """Grab the next addon in the list and start its updater.""" diff --git a/src/Mod/AddonManager/update_all.ui b/src/Mod/AddonManager/update_all.ui index 94d2a19c81..266b779f9a 100644 --- a/src/Mod/AddonManager/update_all.ui +++ b/src/Mod/AddonManager/update_all.ui @@ -27,7 +27,7 @@ - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::NoEditTriggers false @@ -39,7 +39,10 @@ false - QAbstractItemView::NoSelection + QAbstractItemView::SelectionMode::NoSelection + + + QAbstractItemView::SelectionBehavior::SelectRows false @@ -58,10 +61,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel + QDialogButtonBox::StandardButton::Cancel