From 21c360248fe9bb5b51fe8ace3418be02acacc7a9 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 20 Sep 2022 15:32:12 -0500 Subject: [PATCH] Addon Manager: Switch custom repo prefs to a table --- src/Mod/AddonManager/AddonManagerOptions.py | 198 +++++++++++++++++- src/Mod/AddonManager/AddonManagerOptions.ui | 79 ++++--- ...AddonManagerOptions_AddCustomRepository.ui | 84 ++++++++ src/Mod/AddonManager/CMakeLists.txt | 1 + 4 files changed, 328 insertions(+), 34 deletions(-) create mode 100644 src/Mod/AddonManager/AddonManagerOptions_AddCustomRepository.ui diff --git a/src/Mod/AddonManager/AddonManagerOptions.py b/src/Mod/AddonManager/AddonManagerOptions.py index 88fde58910..81b5ab6fb6 100644 --- a/src/Mod/AddonManager/AddonManagerOptions.py +++ b/src/Mod/AddonManager/AddonManagerOptions.py @@ -28,29 +28,65 @@ import FreeCAD import FreeCADGui from PySide2 import QtCore +from PySide2.QtGui import QIcon from PySide2.QtWidgets import ( QWidget, QCheckBox, QComboBox, + QDialog, + QHeaderView, QRadioButton, QLineEdit, QTextEdit, ) +translate = FreeCAD.Qt.translate + +#pylint: disable=too-few-public-methods class AddonManagerOptions: - """ A class containing a form element that is inserted as a FreeCAD preference page. """ + """A class containing a form element that is inserted as a FreeCAD preference page.""" def __init__(self, _=None): self.form = FreeCADGui.PySideUic.loadUi( os.path.join(os.path.dirname(__file__), "AddonManagerOptions.ui") ) + self.table_model = CustomRepoDataModel() + self.form.customRepositoriesTableView.setModel(self.table_model) + + self.form.addCustomRepositoryButton.setIcon( + QIcon.fromTheme("add", QIcon(":/icons/list-add.svg")) + ) + self.form.removeCustomRepositoryButton.setIcon( + QIcon.fromTheme("remove", QIcon(":/icons/list-remove.svg")) + ) + + self.form.customRepositoriesTableView.horizontalHeader().setStretchLastSection( + False + ) + self.form.customRepositoriesTableView.horizontalHeader().setSectionResizeMode( + 0, QHeaderView.Stretch + ) + self.form.customRepositoriesTableView.horizontalHeader().setSectionResizeMode( + 1, QHeaderView.ResizeToContents + ) + + self.form.addCustomRepositoryButton.clicked.connect( + self._add_custom_repo_clicked + ) + self.form.removeCustomRepositoryButton.clicked.connect( + self._remove_custom_repo_clicked + ) + self.form.customRepositoriesTableView.doubleClicked.connect( + self._row_double_clicked + ) def saveSettings(self): """Required function: called by the preferences dialog when Apply or Save is clicked, saves out the preference data by reading it from the widgets.""" for widget in self.form.children(): self.recursive_widget_saver(widget) + self.table_model.save_model() def recursive_widget_saver(self, widget): """Writes out the data for this widget and all of its children, recursively.""" @@ -79,7 +115,7 @@ class AddonManagerOptions: text = widget.text() pref.SetString(str(pref_entry, "utf-8"), text) elif widget.metaObject().className() == "Gui::PrefFileChooser": - filename = str(widget.property("fileName"), "utf-8") + filename = str(widget.property("fileName")) filename = pref.SetString(str(pref_entry, "utf-8"), filename) # Recurse over children @@ -92,6 +128,7 @@ class AddonManagerOptions: loads the preference data and assigns it to the widgets.""" for widget in self.form.children(): self.recursive_widget_loader(widget) + self.table_model.load_model() def recursive_widget_loader(self, widget): """Loads the data for this widget and all of its children, recursively.""" @@ -126,3 +163,160 @@ class AddonManagerOptions: if isinstance(widget, QtCore.QObject): for child in widget.children(): self.recursive_widget_loader(child) + + def _add_custom_repo_clicked(self): + """Callback: show the Add custom repo dialog""" + dlg = CustomRepositoryDialog() + url, branch = dlg.exec() + if url and branch: + self.table_model.appendData(url, branch) + + def _remove_custom_repo_clicked(self): + """Callback: when the remove button is clicked, get the current selection and remove it.""" + item = self.form.customRepositoriesTableView.currentIndex() + if not item.isValid(): + return + row = item.row() + self.table_model.removeRows(row, 1, QtCore.QModelIndex()) + + def _row_double_clicked(self, item): + """Edit the row that was double-clicked""" + row = item.row() + dlg = CustomRepositoryDialog() + url_index = self.table_model.createIndex(row, 0) + branch_index = self.table_model.createIndex(row, 1) + dlg.dialog.urlLineEdit.setText(self.table_model.data(url_index)) + dlg.dialog.branchLineEdit.setText(self.table_model.data(branch_index)) + url, branch = dlg.exec() + if url and branch: + self.table_model.setData(url_index, url) + self.table_model.setData(branch_index, branch) + + +class CustomRepoDataModel(QtCore.QAbstractTableModel): + """The model for the custom repositories: wraps the underlying preference data and uses that + as its main data store.""" + + def __init__(self): + super().__init__() + pref_access_string = "User parameter:BaseApp/Preferences/Addons" + self.pref = FreeCAD.ParamGet(pref_access_string) + self.load_model() + + def load_model(self): + """Load the data from the preferences entry""" + pref_entry: str = self.pref.GetString("CustomRepositories", "") + + # The entry is saved as a space- and newline-delimited text block: break it into its + # constituent parts + lines = pref_entry.split("\n") + self.model = [] + for line in lines: + if not line: + continue + split_data = line.split() + if len(split_data) > 1: + branch = split_data[1] + else: + branch = "master" + url = split_data[0] + self.model.append([url, branch]) + + def save_model(self): + """Save the data into a preferences entry""" + entry = "" + for row in self.model: + entry += f"{row[0]} {row[1]}\n" + self.pref.SetString("CustomRepositories", entry) + + def rowCount(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()) -> int: + """The number of rows""" + if parent.isValid(): + return 0 + return len(self.model) + + def columnCount(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()) -> int: + """The number of columns (which is always 2)""" + if parent.isValid(): + return 0 + return 2 + + def data(self, index, role=QtCore.Qt.DisplayRole): + """The data at an index.""" + if role != QtCore.Qt.DisplayRole: + return None + row = index.row() + column = index.column() + if row > len(self.model): + return None + if column > 1: + return None + return self.model[row][column] + + def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): + """Get the row and column header data.""" + if role != QtCore.Qt.DisplayRole: + return None + if orientation == QtCore.Qt.Vertical: + return section + 1 + if section == 0: + return translate( + "AddonsInstaller", + "Repository URL", + "Preferences header for custom repositories", + ) + if section == 1: + return translate( + "AddonsInstaller", + "Branch name", + "Preferences header for custom repositories", + ) + return None + + def removeRows(self, row, count, parent): + """Remove rows""" + self.beginRemoveRows(parent, row, row + count - 1) + for _ in range(count): + self.model.pop(row) + self.endRemoveRows() + + def insertRows(self, row, count, parent): + """Insert blank rows""" + self.beginInsertRows(parent, row, row + count - 1) + for _ in range(count): + self.model.insert(["", ""]) + self.endInsertRows() + + def appendData(self, url, branch): + """Append this url and branch to the end of the list""" + row = self.rowCount() + self.beginInsertRows(QtCore.QModelIndex(), row, row) + self.model.append([url, branch]) + self.endInsertRows() + + def setData(self, index, value, role=QtCore.Qt.EditRole): + """Set the data at this index""" + if role != QtCore.Qt.EditRole: + return + self.model[index.row()][index.column()] = value + self.dataChanged.emit(index, index) + + +class CustomRepositoryDialog: + """A dialog for setting up a custom repository, with branch information""" + + def __init__(self): + self.dialog = FreeCADGui.PySideUic.loadUi( + os.path.join( + os.path.dirname(__file__), "AddonManagerOptions_AddCustomRepository.ui" + ) + ) + + def exec(self): + """Run the dialog modally, and return either None or a tuple or (url,branch)""" + result = self.dialog.exec() + if result == QDialog.Accepted: + url = self.dialog.urlLineEdit.text() + branch = self.dialog.branchLineEdit.text() + return (url, branch) + return (None, None) diff --git a/src/Mod/AddonManager/AddonManagerOptions.ui b/src/Mod/AddonManager/AddonManagerOptions.ui index 8964e6de7e..fbf98dccd7 100644 --- a/src/Mod/AddonManager/AddonManagerOptions.ui +++ b/src/Mod/AddonManager/AddonManagerOptions.ui @@ -140,44 +140,64 @@ installed addons will be checked for available updates + + + 75 + true + + - Custom repositories (one per line): + Custom repositories - - - - 0 - 0 - + + + true - - - 0 - 24 - + + QAbstractItemView::SingleSelection - - - 0 - 48 - + + QAbstractItemView::SelectRows - - You can use this window to specify additional addon repositories -to be scanned for available addons. To include a specific branch, add it to the end -of the line after a space (e.g. https://github.com/FreeCAD/FreeCAD master). - - - CustomRepositories - - - Addons + + false + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + ... + + + + + + + ... + + + + + @@ -399,11 +419,6 @@ of the line after a space (e.g. https://github.com/FreeCAD/FreeCAD master).QComboBox
Gui/PrefWidgets.h
- - Gui::PrefTextEdit - QTextEdit -
Gui/PrefWidgets.h
-
Gui::PrefRadioButton QRadioButton diff --git a/src/Mod/AddonManager/AddonManagerOptions_AddCustomRepository.ui b/src/Mod/AddonManager/AddonManagerOptions_AddCustomRepository.ui new file mode 100644 index 0000000000..17d9190b49 --- /dev/null +++ b/src/Mod/AddonManager/AddonManagerOptions_AddCustomRepository.ui @@ -0,0 +1,84 @@ + + + AddCustomRepositoryDialog + + + + 0 + 0 + 400 + 95 + + + + Custom repository + + + + + + Repository URL + + + + + + + + + + Branch + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + buttonBox + accepted() + AddCustomRepositoryDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AddCustomRepositoryDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/Mod/AddonManager/CMakeLists.txt b/src/Mod/AddonManager/CMakeLists.txt index ee5f014aba..472b6ff544 100644 --- a/src/Mod/AddonManager/CMakeLists.txt +++ b/src/Mod/AddonManager/CMakeLists.txt @@ -22,6 +22,7 @@ SET(AddonManager_SRCS addonmanager_workers_startup.py addonmanager_workers_utility.py AddonManagerOptions.ui + AddonManagerOptions_AddCustomRepository.ui AddonManagerOptions.py ALLOWED_PYTHON_PACKAGES.txt change_branch.py