315 lines
13 KiB
Python
315 lines
13 KiB
Python
# 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/>. *
|
|
# * *
|
|
# ***************************************************************************
|
|
|
|
""" Contains a the Addon Manager's preferences dialog management class """
|
|
|
|
import os
|
|
|
|
import FreeCAD
|
|
import FreeCADGui
|
|
|
|
from PySide import QtCore
|
|
from PySide.QtGui import QIcon
|
|
from PySide.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."""
|
|
|
|
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."""
|
|
if isinstance(widget, QWidget):
|
|
# See if it's one of ours:
|
|
pref_path = widget.property("prefPath")
|
|
pref_entry = widget.property("prefEntry")
|
|
if pref_path and pref_entry:
|
|
pref_path = pref_path.data()
|
|
pref_entry = pref_entry.data()
|
|
pref_access_string = f"User parameter:BaseApp/Preferences/{str(pref_path,'utf-8')}"
|
|
pref = FreeCAD.ParamGet(pref_access_string)
|
|
if isinstance(widget, QCheckBox):
|
|
checked = widget.isChecked()
|
|
pref.SetBool(str(pref_entry, "utf-8"), checked)
|
|
elif isinstance(widget, QRadioButton):
|
|
checked = widget.isChecked()
|
|
pref.SetBool(str(pref_entry, "utf-8"), checked)
|
|
elif isinstance(widget, QComboBox):
|
|
new_index = widget.currentIndex()
|
|
pref.SetInt(str(pref_entry, "utf-8"), new_index)
|
|
elif isinstance(widget, QTextEdit):
|
|
text = widget.toPlainText()
|
|
pref.SetString(str(pref_entry, "utf-8"), text)
|
|
elif isinstance(widget, QLineEdit):
|
|
text = widget.text()
|
|
pref.SetString(str(pref_entry, "utf-8"), text)
|
|
elif widget.metaObject().className() == "Gui::PrefFileChooser":
|
|
filename = str(widget.property("fileName"))
|
|
filename = pref.SetString(str(pref_entry, "utf-8"), filename)
|
|
|
|
# Recurse over children
|
|
if isinstance(widget, QtCore.QObject):
|
|
for child in widget.children():
|
|
self.recursive_widget_saver(child)
|
|
|
|
def loadSettings(self):
|
|
"""Required function: called by the preferences dialog when it is launched,
|
|
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."""
|
|
if isinstance(widget, QWidget):
|
|
# See if it's one of ours:
|
|
pref_path = widget.property("prefPath")
|
|
pref_entry = widget.property("prefEntry")
|
|
if pref_path and pref_entry:
|
|
pref_path = pref_path.data()
|
|
pref_entry = pref_entry.data()
|
|
pref_access_string = f"User parameter:BaseApp/Preferences/{str(pref_path,'utf-8')}"
|
|
pref = FreeCAD.ParamGet(pref_access_string)
|
|
if isinstance(widget, QCheckBox):
|
|
widget.setChecked(pref.GetBool(str(pref_entry, "utf-8")))
|
|
elif isinstance(widget, QRadioButton):
|
|
if pref.GetBool(str(pref_entry, "utf-8")):
|
|
widget.setChecked(True)
|
|
elif isinstance(widget, QComboBox):
|
|
new_index = pref.GetInt(str(pref_entry, "utf-8"))
|
|
widget.setCurrentIndex(new_index)
|
|
elif isinstance(widget, QTextEdit):
|
|
text = pref.GetString(str(pref_entry, "utf-8"))
|
|
widget.setText(text)
|
|
elif isinstance(widget, QLineEdit):
|
|
text = pref.GetString(str(pref_entry, "utf-8"))
|
|
widget.setText(text)
|
|
elif widget.metaObject().className() == "Gui::PrefFileChooser":
|
|
filename = pref.GetString(str(pref_entry, "utf-8"))
|
|
widget.setProperty("fileName", filename)
|
|
|
|
# Recurse over children
|
|
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)
|