Addon Manager: Reformat with new Black line length

This commit is contained in:
Chris Hennes
2023-09-02 12:27:25 -05:00
committed by Chris Hennes
parent c989a8506e
commit 89579cff6e
67 changed files with 654 additions and 1233 deletions

View File

@@ -63,9 +63,7 @@ class AddonManagerOptions:
QIcon.fromTheme("remove", QIcon(":/icons/list-remove.svg"))
)
self.form.customRepositoriesTableView.horizontalHeader().setStretchLastSection(
False
)
self.form.customRepositoriesTableView.horizontalHeader().setStretchLastSection(False)
self.form.customRepositoriesTableView.horizontalHeader().setSectionResizeMode(
0, QHeaderView.Stretch
)
@@ -73,15 +71,9 @@ class AddonManagerOptions:
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
)
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,
@@ -99,9 +91,7 @@ class AddonManagerOptions:
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_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()
@@ -143,9 +133,7 @@ class AddonManagerOptions:
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_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")))
@@ -313,9 +301,7 @@ class CustomRepositoryDialog:
def __init__(self):
self.dialog = FreeCADGui.PySideUic.loadUi(
os.path.join(
os.path.dirname(__file__), "AddonManagerOptions_AddCustomRepository.ui"
)
os.path.join(os.path.dirname(__file__), "AddonManagerOptions_AddCustomRepository.ui")
)
def exec(self):

View File

@@ -148,9 +148,7 @@ class MockMacro:
with open(os.path.join(location, self.icon), "wb") as f:
f.write(b"Fake icon data - nothing to see here\n")
if self.xpm:
with open(
os.path.join(location, "MockMacro_icon.xpm"), "w", encoding="utf-8"
) as f:
with open(os.path.join(location, "MockMacro_icon.xpm"), "w", encoding="utf-8") as f:
f.write(self.xpm)
for name in self.other_files:
if "/" in name:
@@ -233,12 +231,8 @@ class MockGitManager:
self.current_branch_response = "main"
self.get_remote_response = "No remote set"
self.get_branches_response = ["main"]
self.get_last_committers_response = {
"John Doe": {"email": "jdoe@freecad.org", "count": 1}
}
self.get_last_authors_response = {
"Jane Doe": {"email": "jdoe@freecad.org", "count": 1}
}
self.get_last_committers_response = {"John Doe": {"email": "jdoe@freecad.org", "count": 1}}
self.get_last_authors_response = {"Jane Doe": {"email": "jdoe@freecad.org", "count": 1}}
self.should_fail = False
self.fail_once = False # Switch back to success after the simulated failure
@@ -252,9 +246,7 @@ class MockGitManager:
self.called_methods.append("clone")
self._check_for_failure()
def async_clone(
self, _remote, _local_path, _progress_monitor, _args: List[str] = None
):
def async_clone(self, _remote, _local_path, _progress_monitor, _args: List[str] = None):
self.called_methods.append("async_clone")
self._check_for_failure()

View File

@@ -89,9 +89,7 @@ class TestAddon(unittest.TestCase):
Addon.Status.NOT_INSTALLED,
"master",
)
addon_with_workbench.load_metadata_file(
os.path.join(self.test_dir, "workbench_only.xml")
)
addon_with_workbench.load_metadata_file(os.path.join(self.test_dir, "workbench_only.xml"))
self.assertTrue(addon_with_workbench.contains_workbench())
self.assertFalse(addon_with_workbench.contains_macro())
self.assertFalse(addon_with_workbench.contains_preference_pack())
@@ -103,9 +101,7 @@ class TestAddon(unittest.TestCase):
Addon.Status.NOT_INSTALLED,
"master",
)
addon_with_macro.load_metadata_file(
os.path.join(self.test_dir, "macro_only.xml")
)
addon_with_macro.load_metadata_file(os.path.join(self.test_dir, "macro_only.xml"))
self.assertFalse(addon_with_macro.contains_workbench())
self.assertTrue(addon_with_macro.contains_macro())
self.assertFalse(addon_with_macro.contains_preference_pack())
@@ -117,9 +113,7 @@ class TestAddon(unittest.TestCase):
Addon.Status.NOT_INSTALLED,
"master",
)
addon_with_prefpack.load_metadata_file(
os.path.join(self.test_dir, "prefpack_only.xml")
)
addon_with_prefpack.load_metadata_file(os.path.join(self.test_dir, "prefpack_only.xml"))
self.assertFalse(addon_with_prefpack.contains_workbench())
self.assertFalse(addon_with_prefpack.contains_macro())
self.assertTrue(addon_with_prefpack.contains_preference_pack())
@@ -131,9 +125,7 @@ class TestAddon(unittest.TestCase):
Addon.Status.NOT_INSTALLED,
"master",
)
addon_with_all.load_metadata_file(
os.path.join(self.test_dir, "combination.xml")
)
addon_with_all.load_metadata_file(os.path.join(self.test_dir, "combination.xml"))
self.assertTrue(addon_with_all.contains_workbench())
self.assertTrue(addon_with_all.contains_macro())
self.assertTrue(addon_with_all.contains_preference_pack())
@@ -263,9 +255,7 @@ class TestAddon(unittest.TestCase):
Addon.Status.NOT_INSTALLED,
"master",
)
addon.load_metadata_file(
os.path.join(self.test_dir, "depends_on_all_workbenches.xml")
)
addon.load_metadata_file(os.path.join(self.test_dir, "depends_on_all_workbenches.xml"))
deps = Addon.Dependencies()
addon.walk_dependency_tree({}, deps)
self.assertEqual(len(deps.internal_workbenches), len(INTERNAL_WORKBENCHES))
@@ -277,9 +267,7 @@ class TestAddon(unittest.TestCase):
Addon.Status.NOT_INSTALLED,
"master",
)
addon.load_metadata_file(
os.path.join(self.test_dir, "test_version_detection.xml")
)
addon.load_metadata_file(os.path.join(self.test_dir, "test_version_detection.xml"))
self.assertEqual(
len(addon.tags),

View File

@@ -74,19 +74,11 @@ class TestDependencyInstaller(unittest.TestCase):
def setUp(self):
self.subprocess_mock = SubprocessMock()
self.test_object = DependencyInstaller(
[], ["required_py_package"], ["optional_py_package"]
)
self.test_object._subprocess_wrapper = (
self.subprocess_mock.subprocess_interceptor
)
self.test_object = DependencyInstaller([], ["required_py_package"], ["optional_py_package"])
self.test_object._subprocess_wrapper = self.subprocess_mock.subprocess_interceptor
self.signals_caught = []
self.test_object.failure.connect(
functools.partial(self.catch_signal, "failure")
)
self.test_object.finished.connect(
functools.partial(self.catch_signal, "finished")
)
self.test_object.failure.connect(functools.partial(self.catch_signal, "failure"))
self.test_object.finished.connect(functools.partial(self.catch_signal, "finished"))
self.test_object.no_pip.connect(functools.partial(self.catch_signal, "no_pip"))
self.test_object.no_python_exe.connect(
functools.partial(self.catch_signal, "no_python_exe")

View File

@@ -64,9 +64,7 @@ class TestConsole(unittest.TestCase):
"""Test that if the FreeCAD import fails, the logger is set up correctly, and
implements PrintLog"""
sys.modules["FreeCAD"] = None
with patch(
"addonmanager_freecad_interface.logging", new=MagicMock()
) as mock_logging:
with patch("addonmanager_freecad_interface.logging", new=MagicMock()) as mock_logging:
import addonmanager_freecad_interface as fc
fc.Console.PrintLog("Test output")
@@ -76,9 +74,7 @@ class TestConsole(unittest.TestCase):
def test_message_no_freecad(self):
"""Test that if the FreeCAD import fails the logger implements PrintMessage"""
sys.modules["FreeCAD"] = None
with patch(
"addonmanager_freecad_interface.logging", new=MagicMock()
) as mock_logging:
with patch("addonmanager_freecad_interface.logging", new=MagicMock()) as mock_logging:
import addonmanager_freecad_interface as fc
fc.Console.PrintMessage("Test output")
@@ -87,9 +83,7 @@ class TestConsole(unittest.TestCase):
def test_warning_no_freecad(self):
"""Test that if the FreeCAD import fails the logger implements PrintWarning"""
sys.modules["FreeCAD"] = None
with patch(
"addonmanager_freecad_interface.logging", new=MagicMock()
) as mock_logging:
with patch("addonmanager_freecad_interface.logging", new=MagicMock()) as mock_logging:
import addonmanager_freecad_interface as fc
fc.Console.PrintWarning("Test output")
@@ -98,9 +92,7 @@ class TestConsole(unittest.TestCase):
def test_error_no_freecad(self):
"""Test that if the FreeCAD import fails the logger implements PrintError"""
sys.modules["FreeCAD"] = None
with patch(
"addonmanager_freecad_interface.logging", new=MagicMock()
) as mock_logging:
with patch("addonmanager_freecad_interface.logging", new=MagicMock()) as mock_logging:
import addonmanager_freecad_interface as fc
fc.Console.PrintError("Test output")

View File

@@ -78,9 +78,7 @@ class TestGit(unittest.TestCase):
checkout_dir = self._clone_test_repo()
self.assertTrue(os.path.exists(checkout_dir))
self.assertTrue(os.path.exists(os.path.join(checkout_dir, ".git")))
self.assertEqual(
os.getcwd(), self.cwd, "We should be left in the same CWD we started"
)
self.assertEqual(os.getcwd(), self.cwd, "We should be left in the same CWD we started")
def test_checkout(self):
"""Test git checkout"""
@@ -91,9 +89,7 @@ class TestGit(unittest.TestCase):
expected_status = "## HEAD (no branch)"
self.assertEqual(status, expected_status)
self.assertEqual(
os.getcwd(), self.cwd, "We should be left in the same CWD we started"
)
self.assertEqual(os.getcwd(), self.cwd, "We should be left in the same CWD we started")
def test_update(self):
"""Test using git to update the local repo"""
@@ -103,9 +99,7 @@ class TestGit(unittest.TestCase):
self.assertTrue(self.git.update_available(checkout_dir))
self.git.update(checkout_dir)
self.assertFalse(self.git.update_available(checkout_dir))
self.assertEqual(
os.getcwd(), self.cwd, "We should be left in the same CWD we started"
)
self.assertEqual(os.getcwd(), self.cwd, "We should be left in the same CWD we started")
def test_tag_and_branch(self):
"""Test checking the currently checked-out tag"""
@@ -129,9 +123,7 @@ class TestGit(unittest.TestCase):
self.assertEqual(found_branch, expected_branch)
self.assertFalse(self.git.update_available(checkout_dir))
self.assertEqual(
os.getcwd(), self.cwd, "We should be left in the same CWD we started"
)
self.assertEqual(os.getcwd(), self.cwd, "We should be left in the same CWD we started")
def test_get_remote(self):
"""Test getting the remote location"""
@@ -139,9 +131,7 @@ class TestGit(unittest.TestCase):
expected_remote = self.test_repo_remote
returned_remote = self.git.get_remote(checkout_dir)
self.assertEqual(expected_remote, returned_remote)
self.assertEqual(
os.getcwd(), self.cwd, "We should be left in the same CWD we started"
)
self.assertEqual(os.getcwd(), self.cwd, "We should be left in the same CWD we started")
def test_repair(self):
"""Test the repair feature (and some exception throwing)"""
@@ -158,9 +148,7 @@ class TestGit(unittest.TestCase):
self.git.repair(remote, checkout_dir)
status = self.git.status(checkout_dir)
self.assertEqual(status, "## main...origin/main\n")
self.assertEqual(
os.getcwd(), self.cwd, "We should be left in the same CWD we started"
)
self.assertEqual(os.getcwd(), self.cwd, "We should be left in the same CWD we started")
def _rmdir(self, path):
try:

View File

@@ -116,9 +116,7 @@ class TestAddonInstaller(unittest.TestCase):
os.path.join(self.test_data_dir, "good_package.xml"),
os.path.join(addon_dir, "package.xml"),
)
good_metadata = MetadataReader.from_file(
os.path.join(addon_dir, "package.xml")
)
good_metadata = MetadataReader.from_file(os.path.join(addon_dir, "package.xml"))
installer._update_metadata()
self.assertEqual(self.real_addon.installed_version, good_metadata.version)
@@ -133,34 +131,24 @@ class TestAddonInstaller(unittest.TestCase):
installer.installation_path = temp_dir
installer._finalize_zip_installation(test_simple_repo)
expected_location = os.path.join(temp_dir, non_gh_mock.name, "README")
self.assertTrue(
os.path.isfile(expected_location), "Non-GitHub zip extraction failed"
)
self.assertTrue(os.path.isfile(expected_location), "Non-GitHub zip extraction failed")
def test_finalize_zip_installation_github(self):
with tempfile.TemporaryDirectory() as temp_dir:
test_github_style_repo = os.path.join(
self.test_data_dir, "test_github_style_repo.zip"
)
test_github_style_repo = os.path.join(self.test_data_dir, "test_github_style_repo.zip")
self.mock_addon.url = test_github_style_repo
self.mock_addon.branch = "master"
installer = AddonInstaller(self.mock_addon, [])
installer.installation_path = temp_dir
installer._finalize_zip_installation(test_github_style_repo)
expected_location = os.path.join(temp_dir, self.mock_addon.name, "README")
self.assertTrue(
os.path.isfile(expected_location), "GitHub zip extraction failed"
)
self.assertTrue(os.path.isfile(expected_location), "GitHub zip extraction failed")
def test_code_in_branch_subdirectory_true(self):
"""When there is a subdirectory with the branch name in it, find it"""
installer = AddonInstaller(self.mock_addon, [])
with tempfile.TemporaryDirectory() as temp_dir:
os.mkdir(
os.path.join(
temp_dir, f"{self.mock_addon.name}-{self.mock_addon.branch}"
)
)
os.mkdir(os.path.join(temp_dir, f"{self.mock_addon.name}-{self.mock_addon.branch}"))
result = installer._code_in_branch_subdirectory(temp_dir)
self.assertTrue(result, "Failed to find ZIP subdirectory")
@@ -176,30 +164,20 @@ class TestAddonInstaller(unittest.TestCase):
"""When there are multiple subdirectories, never find a branch subdirectory"""
installer = AddonInstaller(self.mock_addon, [])
with tempfile.TemporaryDirectory() as temp_dir:
os.mkdir(
os.path.join(
temp_dir, f"{self.mock_addon.name}-{self.mock_addon.branch}"
)
)
os.mkdir(os.path.join(temp_dir, f"{self.mock_addon.name}-{self.mock_addon.branch}"))
os.mkdir(os.path.join(temp_dir, "AnotherSubdir"))
result = installer._code_in_branch_subdirectory(temp_dir)
self.assertFalse(
result, "Found ZIP subdirectory when there were multiple subdirs"
)
self.assertFalse(result, "Found ZIP subdirectory when there were multiple subdirs")
def test_move_code_out_of_subdirectory(self):
"""All files are moved out and the subdirectory is deleted"""
installer = AddonInstaller(self.mock_addon, [])
with tempfile.TemporaryDirectory() as temp_dir:
subdir = os.path.join(
temp_dir, f"{self.mock_addon.name}-{self.mock_addon.branch}"
)
subdir = os.path.join(temp_dir, f"{self.mock_addon.name}-{self.mock_addon.branch}")
os.mkdir(subdir)
with open(os.path.join(subdir, "README.txt"), "w", encoding="utf-8") as f:
f.write("# Test file for unit testing")
with open(
os.path.join(subdir, "AnotherFile.txt"), "w", encoding="utf-8"
) as f:
with open(os.path.join(subdir, "AnotherFile.txt"), "w", encoding="utf-8") as f:
f.write("# Test file for unit testing")
installer._move_code_out_of_subdirectory(temp_dir)
self.assertTrue(os.path.isfile(os.path.join(temp_dir, "README.txt")))
@@ -260,23 +238,15 @@ class TestAddonInstaller(unittest.TestCase):
with tempfile.TemporaryDirectory() as temp_dir:
installer = AddonInstaller(self.mock_addon, [])
method = installer._determine_install_method(
temp_dir, InstallationMethod.COPY
)
method = installer._determine_install_method(temp_dir, InstallationMethod.COPY)
self.assertEqual(method, InstallationMethod.COPY)
git_manager = initialize_git()
if git_manager:
method = installer._determine_install_method(
temp_dir, InstallationMethod.GIT
)
method = installer._determine_install_method(temp_dir, InstallationMethod.GIT)
self.assertEqual(method, InstallationMethod.GIT)
method = installer._determine_install_method(
temp_dir, InstallationMethod.ZIP
)
method = installer._determine_install_method(temp_dir, InstallationMethod.ZIP)
self.assertIsNone(method)
method = installer._determine_install_method(
temp_dir, InstallationMethod.ANY
)
method = installer._determine_install_method(temp_dir, InstallationMethod.ANY)
self.assertEqual(method, InstallationMethod.COPY)
def test_determine_install_method_file_url(self):
@@ -285,23 +255,15 @@ class TestAddonInstaller(unittest.TestCase):
with tempfile.TemporaryDirectory() as temp_dir:
installer = AddonInstaller(self.mock_addon, [])
temp_dir = "file://" + temp_dir.replace(os.path.sep, "/")
method = installer._determine_install_method(
temp_dir, InstallationMethod.COPY
)
method = installer._determine_install_method(temp_dir, InstallationMethod.COPY)
self.assertEqual(method, InstallationMethod.COPY)
git_manager = initialize_git()
if git_manager:
method = installer._determine_install_method(
temp_dir, InstallationMethod.GIT
)
method = installer._determine_install_method(temp_dir, InstallationMethod.GIT)
self.assertEqual(method, InstallationMethod.GIT)
method = installer._determine_install_method(
temp_dir, InstallationMethod.ZIP
)
method = installer._determine_install_method(temp_dir, InstallationMethod.ZIP)
self.assertIsNone(method)
method = installer._determine_install_method(
temp_dir, InstallationMethod.ANY
)
method = installer._determine_install_method(temp_dir, InstallationMethod.ANY)
self.assertEqual(method, InstallationMethod.COPY)
def test_determine_install_method_local_zip(self):
@@ -310,21 +272,13 @@ class TestAddonInstaller(unittest.TestCase):
with tempfile.TemporaryDirectory() as temp_dir:
installer = AddonInstaller(self.mock_addon, [])
temp_file = os.path.join(temp_dir, "dummy.zip")
method = installer._determine_install_method(
temp_file, InstallationMethod.COPY
)
method = installer._determine_install_method(temp_file, InstallationMethod.COPY)
self.assertEqual(method, InstallationMethod.ZIP)
method = installer._determine_install_method(
temp_file, InstallationMethod.GIT
)
method = installer._determine_install_method(temp_file, InstallationMethod.GIT)
self.assertIsNone(method)
method = installer._determine_install_method(
temp_file, InstallationMethod.ZIP
)
method = installer._determine_install_method(temp_file, InstallationMethod.ZIP)
self.assertEqual(method, InstallationMethod.ZIP)
method = installer._determine_install_method(
temp_file, InstallationMethod.ANY
)
method = installer._determine_install_method(temp_file, InstallationMethod.ANY)
self.assertEqual(method, InstallationMethod.ZIP)
def test_determine_install_method_remote_zip(self):
@@ -351,12 +305,8 @@ class TestAddonInstaller(unittest.TestCase):
for site in ["github.org", "gitlab.org", "framagit.org", "salsa.debian.org"]:
with self.subTest(site=site):
temp_file = (
f"https://{site}/dummy/dummy" # Doesn't have to actually exist!
)
method = installer._determine_install_method(
temp_file, InstallationMethod.COPY
)
temp_file = f"https://{site}/dummy/dummy" # Doesn't have to actually exist!
method = installer._determine_install_method(temp_file, InstallationMethod.COPY)
self.assertIsNone(method, f"Allowed copying from {site} URL")
def test_determine_install_method_https_known_sites_git(self):
@@ -367,12 +317,8 @@ class TestAddonInstaller(unittest.TestCase):
for site in ["github.org", "gitlab.org", "framagit.org", "salsa.debian.org"]:
with self.subTest(site=site):
temp_file = (
f"https://{site}/dummy/dummy" # Doesn't have to actually exist!
)
method = installer._determine_install_method(
temp_file, InstallationMethod.GIT
)
temp_file = f"https://{site}/dummy/dummy" # Doesn't have to actually exist!
method = installer._determine_install_method(temp_file, InstallationMethod.GIT)
self.assertEqual(
method,
InstallationMethod.GIT,
@@ -387,12 +333,8 @@ class TestAddonInstaller(unittest.TestCase):
for site in ["github.org", "gitlab.org", "framagit.org", "salsa.debian.org"]:
with self.subTest(site=site):
temp_file = (
f"https://{site}/dummy/dummy" # Doesn't have to actually exist!
)
method = installer._determine_install_method(
temp_file, InstallationMethod.ZIP
)
temp_file = f"https://{site}/dummy/dummy" # Doesn't have to actually exist!
method = installer._determine_install_method(temp_file, InstallationMethod.ZIP)
self.assertEqual(
method,
InstallationMethod.ZIP,
@@ -407,12 +349,8 @@ class TestAddonInstaller(unittest.TestCase):
for site in ["github.org", "gitlab.org", "framagit.org", "salsa.debian.org"]:
with self.subTest(site=site):
temp_file = (
f"https://{site}/dummy/dummy" # Doesn't have to actually exist!
)
method = installer._determine_install_method(
temp_file, InstallationMethod.ANY
)
temp_file = f"https://{site}/dummy/dummy" # Doesn't have to actually exist!
method = installer._determine_install_method(temp_file, InstallationMethod.ANY)
self.assertEqual(
method,
InstallationMethod.GIT,
@@ -427,12 +365,8 @@ class TestAddonInstaller(unittest.TestCase):
for site in ["github.org", "gitlab.org", "framagit.org", "salsa.debian.org"]:
with self.subTest(site=site):
temp_file = (
f"https://{site}/dummy/dummy" # Doesn't have to actually exist!
)
method = installer._determine_install_method(
temp_file, InstallationMethod.ANY
)
temp_file = f"https://{site}/dummy/dummy" # Doesn't have to actually exist!
method = installer._determine_install_method(temp_file, InstallationMethod.ANY)
self.assertEqual(
method,
InstallationMethod.ZIP,
@@ -442,9 +376,7 @@ class TestAddonInstaller(unittest.TestCase):
def test_fcmacro_copying(self):
with tempfile.TemporaryDirectory() as temp_dir:
mock_addon = MockAddon()
mock_addon.url = os.path.join(
self.test_data_dir, "test_addon_with_fcmacro.zip"
)
mock_addon.url = os.path.join(self.test_data_dir, "test_addon_with_fcmacro.zip")
installer = AddonInstaller(mock_addon, [])
installer.installation_path = temp_dir
installer.macro_installation_path = os.path.join(temp_dir, "Macros")
@@ -475,6 +407,4 @@ class TestMacroInstaller(unittest.TestCase):
installer.installation_path = temp_dir
installation_succeeded = installer.run()
self.assertTrue(installation_succeeded)
self.assertTrue(
os.path.exists(os.path.join(temp_dir, self.mock.macro.filename))
)
self.assertTrue(os.path.exists(os.path.join(temp_dir, self.mock.macro.filename)))

View File

@@ -100,9 +100,7 @@ class TestMacroParser(unittest.TestCase):
catcher = CallCatcher()
self.test_object._process_key = catcher.catch_call
self.test_object._process_line(read_in_line, content_lines)
self.assertTrue(
catcher.called, "_process_key was not called for a known key"
)
self.assertTrue(catcher.called, "_process_key was not called for a known key")
def test_process_line_unknown_lines(self):
"""Lines starting with non-keys are not processed"""
@@ -123,9 +121,7 @@ class TestMacroParser(unittest.TestCase):
catcher = CallCatcher()
self.test_object._process_key = catcher.catch_call
self.test_object._process_line(read_in_line, content_lines)
self.assertFalse(
catcher.called, "_process_key was called for an unknown key"
)
self.assertFalse(catcher.called, "_process_key was called for an unknown key")
def test_process_key_standard(self):
"""Normal expected data is processed"""

View File

@@ -51,15 +51,9 @@ class TestAddonUninstaller(unittest.TestCase):
self.signals_caught = []
self.test_object = AddonUninstaller(self.mock_addon)
self.test_object.finished.connect(
functools.partial(self.catch_signal, "finished")
)
self.test_object.success.connect(
functools.partial(self.catch_signal, "success")
)
self.test_object.failure.connect(
functools.partial(self.catch_signal, "failure")
)
self.test_object.finished.connect(functools.partial(self.catch_signal, "finished"))
self.test_object.success.connect(functools.partial(self.catch_signal, "success"))
self.test_object.failure.connect(functools.partial(self.catch_signal, "failure"))
def tearDown(self):
"""Finalize the test."""
@@ -149,9 +143,7 @@ class TestAddonUninstaller(unittest.TestCase):
self.assertNotIn("failure", self.signals_caught)
self.assertIn("success", self.signals_caught)
self.assertIn("finished", self.signals_caught)
self.assertFalse(
os.path.exists(os.path.join(macro_directory, "FakeMacro.FCMacro"))
)
self.assertFalse(os.path.exists(os.path.join(macro_directory, "FakeMacro.FCMacro")))
self.assertTrue(os.path.exists(macro_directory))
def test_uninstall_calls_script(self):
@@ -218,9 +210,7 @@ class TestAddonUninstaller(unittest.TestCase):
os.path.join(toplevel_path, "AM_INSTALLATION_DIGEST.txt"),
)
self.test_object.remove_extra_files(toplevel_path) # Shouldn't throw
self.assertFalse(
os.path.exists(os.path.join(macro_directory, "FakeMacro.FCMacro"))
)
self.assertFalse(os.path.exists(os.path.join(macro_directory, "FakeMacro.FCMacro")))
def test_remove_extra_files_normal_case(self):
"""Test that a digest that is a "normal" case removes the requested files"""
@@ -244,35 +234,21 @@ class TestAddonUninstaller(unittest.TestCase):
)
# Make sure the setup worked as expected, otherwise the test is meaningless
self.assertTrue(
os.path.exists(os.path.join(macro_directory, "FakeMacro1.FCMacro"))
)
self.assertTrue(
os.path.exists(os.path.join(macro_directory, "FakeMacro2.FCMacro"))
)
self.assertTrue(
os.path.exists(os.path.join(macro_directory, "FakeMacro3.FCMacro"))
)
self.assertTrue(os.path.exists(os.path.join(macro_directory, "FakeMacro1.FCMacro")))
self.assertTrue(os.path.exists(os.path.join(macro_directory, "FakeMacro2.FCMacro")))
self.assertTrue(os.path.exists(os.path.join(macro_directory, "FakeMacro3.FCMacro")))
self.test_object.remove_extra_files(toplevel_path) # Shouldn't throw
self.assertFalse(
os.path.exists(os.path.join(macro_directory, "FakeMacro1.FCMacro"))
)
self.assertFalse(
os.path.exists(os.path.join(macro_directory, "FakeMacro2.FCMacro"))
)
self.assertFalse(
os.path.exists(os.path.join(macro_directory, "FakeMacro3.FCMacro"))
)
self.assertFalse(os.path.exists(os.path.join(macro_directory, "FakeMacro1.FCMacro")))
self.assertFalse(os.path.exists(os.path.join(macro_directory, "FakeMacro2.FCMacro")))
self.assertFalse(os.path.exists(os.path.join(macro_directory, "FakeMacro3.FCMacro")))
def test_runs_uninstaller_script_successful(self):
"""Tests that the uninstall.py script is called"""
with tempfile.TemporaryDirectory() as temp_dir:
toplevel_path = self.setup_dummy_installation(temp_dir)
with open(
os.path.join(toplevel_path, "uninstall.py"), "w", encoding="utf-8"
) as f:
with open(os.path.join(toplevel_path, "uninstall.py"), "w", encoding="utf-8") as f:
double_escaped = temp_dir.replace("\\", "\\\\")
f.write(
f"""# Mock uninstaller script
@@ -282,28 +258,20 @@ with open(os.path.join(path,"RAN_UNINSTALLER.txt"),"w",encoding="utf-8") as f:
f.write("File created by uninstall.py from unit tests")
"""
)
self.test_object.run_uninstall_script(
toplevel_path
) # The exception does not leak out
self.assertTrue(
os.path.exists(os.path.join(temp_dir, "RAN_UNINSTALLER.txt"))
)
self.test_object.run_uninstall_script(toplevel_path) # The exception does not leak out
self.assertTrue(os.path.exists(os.path.join(temp_dir, "RAN_UNINSTALLER.txt")))
def test_runs_uninstaller_script_failure(self):
"""Tests that exceptions in the uninstall.py script do not leak out"""
with tempfile.TemporaryDirectory() as temp_dir:
toplevel_path = self.setup_dummy_installation(temp_dir)
with open(
os.path.join(toplevel_path, "uninstall.py"), "w", encoding="utf-8"
) as f:
with open(os.path.join(toplevel_path, "uninstall.py"), "w", encoding="utf-8") as f:
f.write(
f"""# Mock uninstaller script
raise RuntimeError("Fake exception for unit testing")
"""
)
self.test_object.run_uninstall_script(
toplevel_path
) # The exception does not leak out
self.test_object.run_uninstall_script(toplevel_path) # The exception does not leak out
class TestMacroUninstaller(unittest.TestCase):
@@ -317,15 +285,9 @@ class TestMacroUninstaller(unittest.TestCase):
self.test_object = MacroUninstaller(self.mock_addon)
self.signals_caught = []
self.test_object.finished.connect(
functools.partial(self.catch_signal, "finished")
)
self.test_object.success.connect(
functools.partial(self.catch_signal, "success")
)
self.test_object.failure.connect(
functools.partial(self.catch_signal, "failure")
)
self.test_object.finished.connect(functools.partial(self.catch_signal, "finished"))
self.test_object.success.connect(functools.partial(self.catch_signal, "success"))
self.test_object.failure.connect(functools.partial(self.catch_signal, "failure"))
def tearDown(self):
pass
@@ -339,13 +301,9 @@ class TestMacroUninstaller(unittest.TestCase):
self.test_object.installation_location = temp_dir
self.mock_addon.macro.install(temp_dir)
# Make sure the setup worked, otherwise the test is meaningless
self.assertTrue(
os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename))
)
self.assertTrue(os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename)))
self.test_object.run()
self.assertFalse(
os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename))
)
self.assertFalse(os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename)))
self.assertNotIn("failure", self.signals_caught)
self.assertIn("success", self.signals_caught)
self.assertIn("finished", self.signals_caught)
@@ -356,19 +314,11 @@ class TestMacroUninstaller(unittest.TestCase):
self.mock_addon.macro.icon = "mock_icon_test.svg"
self.mock_addon.macro.install(temp_dir)
# Make sure the setup worked, otherwise the test is meaningless
self.assertTrue(
os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename))
)
self.assertTrue(
os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.icon))
)
self.assertTrue(os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename)))
self.assertTrue(os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.icon)))
self.test_object.run()
self.assertFalse(
os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename))
)
self.assertFalse(
os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.icon))
)
self.assertFalse(os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename)))
self.assertFalse(os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.icon)))
self.assertNotIn("failure", self.signals_caught)
self.assertIn("success", self.signals_caught)
self.assertIn("finished", self.signals_caught)
@@ -379,19 +329,11 @@ class TestMacroUninstaller(unittest.TestCase):
self.mock_addon.macro.xpm = "/*Fake XPM data*/"
self.mock_addon.macro.install(temp_dir)
# Make sure the setup worked, otherwise the test is meaningless
self.assertTrue(
os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename))
)
self.assertTrue(
os.path.exists(os.path.join(temp_dir, "MockMacro_icon.xpm"))
)
self.assertTrue(os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename)))
self.assertTrue(os.path.exists(os.path.join(temp_dir, "MockMacro_icon.xpm")))
self.test_object.run()
self.assertFalse(
os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename))
)
self.assertFalse(
os.path.exists(os.path.join(temp_dir, "MockMacro_icon.xpm"))
)
self.assertFalse(os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename)))
self.assertFalse(os.path.exists(os.path.join(temp_dir, "MockMacro_icon.xpm")))
self.assertNotIn("failure", self.signals_caught)
self.assertIn("success", self.signals_caught)
self.assertIn("finished", self.signals_caught)
@@ -430,13 +372,9 @@ class TestMacroUninstaller(unittest.TestCase):
self.test_object.installation_location = temp_dir
# Don't run the installer:
self.assertFalse(
os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename))
)
self.assertFalse(os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename)))
self.test_object.run() # Should not raise an exception
self.assertFalse(
os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename))
)
self.assertFalse(os.path.exists(os.path.join(temp_dir, self.mock_addon.macro.filename)))
self.assertNotIn("failure", self.signals_caught)
self.assertIn("success", self.signals_caught)
self.assertIn("finished", self.signals_caught)
@@ -489,9 +427,7 @@ class TestMacroUninstaller(unittest.TestCase):
full_path = os.path.join(temp_dir, directory)
os.mkdir(full_path)
full_paths.add(full_path)
with open(
os.path.join(full_path, "test.txt"), "w", encoding="utf-8"
) as f:
with open(os.path.join(full_path, "test.txt"), "w", encoding="utf-8") as f:
f.write("Unit test dummy data\n")
for directory in full_paths:

View File

@@ -53,9 +53,7 @@ class TestUtilities(unittest.TestCase):
]
for url in recognized_urls:
repo = Addon("Test Repo", url, Addon.Status.NOT_INSTALLED, "branch")
self.assertTrue(
recognized_git_location(repo), f"{url} was unexpectedly not recognized"
)
self.assertTrue(recognized_git_location(repo), f"{url} was unexpectedly not recognized")
unrecognized_urls = [
"https://google.com",
@@ -65,9 +63,7 @@ class TestUtilities(unittest.TestCase):
]
for url in unrecognized_urls:
repo = Addon("Test Repo", url, Addon.Status.NOT_INSTALLED, "branch")
self.assertFalse(
recognized_git_location(repo), f"{url} was unexpectedly recognized"
)
self.assertFalse(recognized_git_location(repo), f"{url} was unexpectedly recognized")
def test_get_readme_url(self):
github_urls = [

View File

@@ -27,4 +27,4 @@ static char * blarg_xpm[] = {
".............**."
};"""
print("Well, not quite *nothing*... it does print this line out.")
print("Well, not quite *nothing*... it does print this line out.")

File diff suppressed because one or more lines are too long

View File

@@ -34,4 +34,4 @@ __Help__ = ""
__Status__ = ""
__Requires__ = ""
__Communication__ = ""
__Files__ = ""
__Files__ = ""

View File

@@ -25,4 +25,4 @@
</preferencepack>
</content>
</package>
</package>

View File

@@ -20,4 +20,4 @@
url = https://github.com/tomate44/CurvesWB.git
[submodule "Defeaturing"]
path = Defeaturing
url = https://github.com/easyw/Defeaturing_WB.git
url = https://github.com/easyw/Defeaturing_WB.git

View File

@@ -34,4 +34,4 @@ __Help__ = ""
__Status__ = ""
__Requires__ = ""
__Communication__ = ""
__Files__ = ""
__Files__ = ""

View File

@@ -22,4 +22,4 @@
</workbench>
</content>
</package>
</package>

View File

@@ -19,4 +19,4 @@
</macro>
</content>
</package>
</package>

View File

@@ -34,4 +34,4 @@ __Help__ = "HELP"
__Status__ = "STATUS"
__Requires__ = "REQUIRES"
__Communication__ = "COMMUNICATION"
__Files__ = "FILES"
__Files__ = "FILES"

View File

@@ -22,4 +22,4 @@
# * *
# ***************************************************************************
# This file contains no metadata
# This file contains no metadata

View File

@@ -21,4 +21,4 @@
</preferencepack>
</content>
</package>
</package>

View File

@@ -30,4 +30,4 @@
</macro>
</content>
</package>
</package>

View File

@@ -24,4 +24,4 @@
</workbench>
</content>
</package>
</package>

View File

@@ -55,12 +55,8 @@ class TestInstallerGui(unittest.TestCase):
QtWidgets.QDialogButtonBox.Ok,
)
self.installer_gui._installation_succeeded()
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_failure_dialog(self):
# Pop the modal dialog and verify that it opens, and responds to a Cancel click
@@ -71,12 +67,8 @@ class TestInstallerGui(unittest.TestCase):
self.installer_gui._installation_failed(
self.addon_to_install, "Test of installation failure"
)
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_no_python_dialog(self):
# Pop the modal dialog and verify that it opens, and responds to a No click
@@ -85,12 +77,8 @@ class TestInstallerGui(unittest.TestCase):
QtWidgets.QDialogButtonBox.No,
)
self.installer_gui._report_no_python_exe()
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_no_pip_dialog(self):
# Pop the modal dialog and verify that it opens, and responds to a No click
@@ -99,12 +87,8 @@ class TestInstallerGui(unittest.TestCase):
QtWidgets.QDialogButtonBox.No,
)
self.installer_gui._report_no_pip("pip not actually run, this was a test")
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_dependency_failure_dialog(self):
# Pop the modal dialog and verify that it opens, and responds to a No click
@@ -115,12 +99,8 @@ class TestInstallerGui(unittest.TestCase):
self.installer_gui._report_dependency_failure(
"Unit test", "Nothing really failed, this is a test of the dialog box"
)
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_install(self):
# Run the installation code and make sure it puts the directory in place
@@ -130,9 +110,7 @@ class TestInstallerGui(unittest.TestCase):
self.installer_gui.installer.success.disconnect(
self.installer_gui._installation_succeeded
)
self.installer_gui.installer.failure.disconnect(
self.installer_gui._installation_failed
)
self.installer_gui.installer.failure.disconnect(self.installer_gui._installation_failed)
while not self.installer_gui.worker_thread.isFinished():
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 100)
self.assertTrue(
@@ -147,12 +125,8 @@ class TestInstallerGui(unittest.TestCase):
QtWidgets.QDialogButtonBox.Cancel,
)
self.installer_gui._handle_disallowed_python(disallowed_packages)
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_handle_disallowed_python_long_list(self):
"""A separate test for when there are MANY packages, which takes a separate code path."""
@@ -164,12 +138,8 @@ class TestInstallerGui(unittest.TestCase):
QtWidgets.QDialogButtonBox.Cancel,
)
self.installer_gui._handle_disallowed_python(disallowed_packages)
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_report_missing_workbenches_single(self):
"""Test only missing one workbench"""
@@ -179,12 +149,8 @@ class TestInstallerGui(unittest.TestCase):
QtWidgets.QDialogButtonBox.Cancel,
)
self.installer_gui._report_missing_workbenches(wbs)
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_report_missing_workbenches_multiple(self):
"""Test only missing one workbench"""
@@ -194,12 +160,8 @@ class TestInstallerGui(unittest.TestCase):
QtWidgets.QDialogButtonBox.Cancel,
)
self.installer_gui._report_missing_workbenches(wbs)
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_resolve_dependencies_then_install(self):
class MissingDependenciesMock:
@@ -214,12 +176,8 @@ class TestInstallerGui(unittest.TestCase):
QtWidgets.QDialogButtonBox.Cancel,
)
self.installer_gui._resolve_dependencies_then_install(missing)
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_check_python_version_bad(self):
class MissingDependenciesMock:
@@ -232,15 +190,9 @@ class TestInstallerGui(unittest.TestCase):
QtWidgets.QDialogButtonBox.Cancel,
)
stop_installing = self.installer_gui._check_python_version(missing)
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(
stop_installing, "Failed to halt installation on bad Python version"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
self.assertTrue(stop_installing, "Failed to halt installation on bad Python version")
def test_check_python_version_good(self):
class MissingDependenciesMock:
@@ -249,9 +201,7 @@ class TestInstallerGui(unittest.TestCase):
missing = MissingDependenciesMock()
stop_installing = self.installer_gui._check_python_version(missing)
self.assertFalse(
stop_installing, "Failed to continue installation on good Python version"
)
self.assertFalse(stop_installing, "Failed to continue installation on good Python version")
def test_clean_up_optional(self):
class MissingDependenciesMock:
@@ -270,9 +220,7 @@ class TestInstallerGui(unittest.TestCase):
self.assertTrue("allowed_packages_2" in missing.python_optional)
self.assertFalse("disallowed_package" in missing.python_optional)
def intercept_run_dependency_installer(
self, addons, python_requires, python_optional
):
def intercept_run_dependency_installer(self, addons, python_requires, python_optional):
self.assertEqual(python_requires, ["py_req_1", "py_req_2"])
self.assertEqual(python_optional, ["py_opt_1", "py_opt_2"])
self.assertEqual(addons[0].name, "addon_1")
@@ -294,9 +242,7 @@ class TestInstallerGui(unittest.TestCase):
def __init__(self, items):
self.list = []
for item in items:
self.list.append(
DialogMock.ListWidgetMock.ListWidgetItemMock(item)
)
self.list.append(DialogMock.ListWidgetMock.ListWidgetItemMock(item))
def count(self):
return len(self.list)
@@ -305,15 +251,9 @@ class TestInstallerGui(unittest.TestCase):
return self.list[i]
def __init__(self):
self.listWidgetAddons = DialogMock.ListWidgetMock(
["addon_1", "addon_2"]
)
self.listWidgetPythonRequired = DialogMock.ListWidgetMock(
["py_req_1", "py_req_2"]
)
self.listWidgetPythonOptional = DialogMock.ListWidgetMock(
["py_opt_1", "py_opt_2"]
)
self.listWidgetAddons = DialogMock.ListWidgetMock(["addon_1", "addon_2"])
self.listWidgetPythonRequired = DialogMock.ListWidgetMock(["py_req_1", "py_req_2"])
self.listWidgetPythonOptional = DialogMock.ListWidgetMock(["py_opt_1", "py_opt_2"])
class AddonMock:
def __init__(self, name):
@@ -321,9 +261,7 @@ class TestInstallerGui(unittest.TestCase):
self.installer_gui.dependency_dialog = DialogMock()
self.installer_gui.addons = [AddonMock("addon_1"), AddonMock("addon_2")]
self.installer_gui._run_dependency_installer = (
self.intercept_run_dependency_installer
)
self.installer_gui._run_dependency_installer = self.intercept_run_dependency_installer
self.installer_gui._dependency_dialog_yes_clicked()
@@ -481,9 +419,7 @@ class TestMacroInstallerGui(unittest.TestCase):
self.assertEqual(name, "UnitTestCustomToolbar")
self.assertIn("alwaysAskForToolbar", self.installer.addon_params.params)
self.assertFalse(self.installer.addon_params.get("alwaysAskForToolbar", True))
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_ask_for_toolbar_with_dialog_selection(self):
@@ -530,9 +466,7 @@ class TestMacroInstallerGui(unittest.TestCase):
def test_macro_button_exists_true(self):
# Test 2: Macro is in the list of buttons
ut_tb_1 = self.installer.toolbar_params.GetGroup("UnitTestCommand")
ut_tb_1.set(
"UnitTestCommand", "FreeCAD"
) # This is what the real thing looks like...
ut_tb_1.set("UnitTestCommand", "FreeCAD") # This is what the real thing looks like...
self.installer._find_custom_command = lambda _: "UnitTestCommand"
self.assertTrue(self.installer._macro_button_exists())

View File

@@ -62,12 +62,8 @@ class TestUninstallerGUI(unittest.TestCase):
QtWidgets.QDialogButtonBox.Yes,
)
answer = self.uninstaller_gui._confirm_uninstallation()
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
self.assertTrue(answer, "Expected a 'Yes' click to return True, but got False")
def test_confirmation_dialog_cancel(self):
@@ -76,15 +72,9 @@ class TestUninstallerGUI(unittest.TestCase):
QtWidgets.QDialogButtonBox.Cancel,
)
answer = self.uninstaller_gui._confirm_uninstallation()
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertFalse(
answer, "Expected a 'Cancel' click to return False, but got True"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
self.assertFalse(answer, "Expected a 'Cancel' click to return False, but got True")
def test_progress_dialog(self):
dialog_watcher = DialogWatcher(
@@ -95,12 +85,8 @@ class TestUninstallerGUI(unittest.TestCase):
# That call isn't modal, so spin our own event loop:
while self.uninstaller_gui.progress_dialog.isVisible():
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 100)
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_timer_launches_progress_dialog(self):
worker = FakeWorker()
@@ -108,22 +94,14 @@ class TestUninstallerGUI(unittest.TestCase):
translate("AddonsInstaller", "Removing Addon"),
QtWidgets.QDialogButtonBox.Cancel,
)
QtCore.QTimer.singleShot(
1000, worker.stop
) # If the test fails, this kills the "worker"
QtCore.QTimer.singleShot(1000, worker.stop) # If the test fails, this kills the "worker"
self.uninstaller_gui._confirm_uninstallation = lambda: True
self.uninstaller_gui._run_uninstaller = worker.work
self.uninstaller_gui._finalize = lambda: None
self.uninstaller_gui.dialog_timer.setInterval(
1
) # To speed up the test, only wait 1ms
self.uninstaller_gui.dialog_timer.setInterval(1) # To speed up the test, only wait 1ms
self.uninstaller_gui.run() # Blocks once it hits the fake worker
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
worker.stop()
def test_success_dialog(self):
@@ -132,12 +110,8 @@ class TestUninstallerGUI(unittest.TestCase):
QtWidgets.QDialogButtonBox.Ok,
)
self.uninstaller_gui._succeeded(self.addon_to_remove)
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_failure_dialog(self):
dialog_watcher = DialogWatcher(
@@ -147,17 +121,11 @@ class TestUninstallerGUI(unittest.TestCase):
self.uninstaller_gui._failed(
self.addon_to_remove, "Some failure message\nAnother failure message"
)
self.assertTrue(
dialog_watcher.dialog_found, "Failed to find the expected dialog box"
)
self.assertTrue(
dialog_watcher.button_found, "Failed to find the expected button"
)
self.assertTrue(dialog_watcher.dialog_found, "Failed to find the expected dialog box")
self.assertTrue(dialog_watcher.button_found, "Failed to find the expected button")
def test_finalize(self):
self.uninstaller_gui.finished.connect(
functools.partial(self.catch_signal, "finished")
)
self.uninstaller_gui.finished.connect(functools.partial(self.catch_signal, "finished"))
self.uninstaller_gui.worker_thread = MockThread()
self.uninstaller_gui._finalize()
self.assertIn("finished", self.signals_caught)

View File

@@ -43,9 +43,7 @@ class MockUpdater(QtCore.QObject):
self.addons = addons
self.has_run = False
self.emit_success = True
self.work_function = (
None # Set to some kind of callable to make this function take time
)
self.work_function = None # Set to some kind of callable to make this function take time
def run(self):
self.has_run = True
@@ -136,9 +134,7 @@ class TestUpdateAllGui(unittest.TestCase):
self.test_object.run()
cancel_timer = QtCore.QTimer()
cancel_timer.timeout.connect(
self.test_object.dialog.buttonBox.button(
QtWidgets.QDialogButtonBox.Cancel
).click
self.test_object.dialog.buttonBox.button(QtWidgets.QDialogButtonBox.Cancel).click
)
cancel_timer.start(90)
while self.test_object.is_running():

View File

@@ -45,9 +45,7 @@ class TestWorkersStartup(unittest.TestCase):
MODULE = "test_workers_startup" # file name without extension
@unittest.skipUnless(
run_slow_tests, "This integration test is slow and uses the network"
)
@unittest.skipUnless(run_slow_tests, "This integration test is slow and uses the network")
def setUp(self):
"""Set up the test"""
self.test_dir = os.path.join(
@@ -56,12 +54,8 @@ class TestWorkersStartup(unittest.TestCase):
self.saved_mod_directory = Addon.mod_directory
self.saved_cache_directory = Addon.cache_directory
Addon.mod_directory = os.path.join(
tempfile.gettempdir(), "FreeCADTesting", "Mod"
)
Addon.cache_directory = os.path.join(
tempfile.gettempdir(), "FreeCADTesting", "Cache"
)
Addon.mod_directory = os.path.join(tempfile.gettempdir(), "FreeCADTesting", "Mod")
Addon.cache_directory = os.path.join(tempfile.gettempdir(), "FreeCADTesting", "Cache")
os.makedirs(Addon.mod_directory, mode=0o777, exist_ok=True)
os.makedirs(Addon.cache_directory, mode=0o777, exist_ok=True)
@@ -82,9 +76,7 @@ class TestWorkersStartup(unittest.TestCase):
self.package_cache = {}
self.macro_cache = []
self.package_cache_filename = os.path.join(
Addon.cache_directory, "packages.json"
)
self.package_cache_filename = os.path.join(Addon.cache_directory, "packages.json")
self.macro_cache_filename = os.path.join(Addon.cache_directory, "macros.json")
# Store the user's preference for whether git is enabled or disabled
@@ -135,9 +127,7 @@ class TestWorkersStartup(unittest.TestCase):
# Now try loading the same data from the cache we just created
worker = LoadPackagesFromCacheWorker(self.package_cache_filename)
worker.override_metadata_cache_path(
os.path.join(Addon.cache_directory, "PackageMetadata")
)
worker.override_metadata_cache_path(os.path.join(Addon.cache_directory, "PackageMetadata"))
worker.addon_repo.connect(self._addon_added)
worker.start()

View File

@@ -69,9 +69,7 @@ class TestWorkersUtility(unittest.TestCase):
while worker.isRunning():
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
self.assertIsNone(
self.last_result, "Requesting interruption of thread failed to interrupt"
)
self.assertIsNone(self.last_result, "Requesting interruption of thread failed to interrupt")
def connection_succeeded(self):
self.last_result = "SUCCESS"

View File

@@ -1,3 +1,3 @@
## Unit tests for the Addon Manager
Data files are located in the `data/` subdirectory.
Data files are located in the `data/` subdirectory.

View File

@@ -105,9 +105,7 @@ if HAVE_QTNETWORK:
class QueueItem:
"""A container for information about an item in the network queue."""
def __init__(
self, index: int, request: QtNetwork.QNetworkRequest, track_progress: bool
):
def __init__(self, index: int, request: QtNetwork.QNetworkRequest, track_progress: bool):
self.index = index
self.request = request
self.original_url = request.url()
@@ -126,9 +124,7 @@ if HAVE_QTNETWORK:
# Connect to progress_made and progress_complete for large amounts of data, which get buffered into a temp file
# That temp file should be deleted when your code is done with it
progress_made = QtCore.Signal(
int, int, int
) # Index, bytes read, total bytes (may be None)
progress_made = QtCore.Signal(int, int, int) # Index, bytes read, total bytes (may be None)
progress_complete = QtCore.Signal(
int, int, os.PathLike
@@ -153,18 +149,14 @@ if HAVE_QTNETWORK:
# Make sure we exit nicely on quit
if QtCore.QCoreApplication.instance() is not None:
QtCore.QCoreApplication.instance().aboutToQuit.connect(
self.__aboutToQuit
)
QtCore.QCoreApplication.instance().aboutToQuit.connect(self.__aboutToQuit)
# Create the QNAM on this thread:
self.QNAM = QtNetwork.QNetworkAccessManager()
self.QNAM.proxyAuthenticationRequired.connect(self.__authenticate_proxy)
self.QNAM.authenticationRequired.connect(self.__authenticate_resource)
qnam_cache = QtCore.QStandardPaths.writableLocation(
QtCore.QStandardPaths.CacheLocation
)
qnam_cache = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.CacheLocation)
os.makedirs(qnam_cache, exist_ok=True)
self.diskCache = QtNetwork.QNetworkDiskCache()
self.diskCache.setCacheDirectory(qnam_cache)
@@ -206,9 +198,7 @@ if HAVE_QTNETWORK:
)
proxy = QtNetwork.QNetworkProxyFactory.systemProxyForQuery(query)
if proxy and proxy[0]:
self.QNAM.setProxy(
proxy[0]
) # This may still be QNetworkProxy.NoProxy
self.QNAM.setProxy(proxy[0]) # This may still be QNetworkProxy.NoProxy
elif userProxyCheck:
host, _, port_string = proxy_string.rpartition(":")
try:
@@ -223,9 +213,7 @@ if HAVE_QTNETWORK:
)
port = 0
# For now assume an HttpProxy, but eventually this should be a parameter
proxy = QtNetwork.QNetworkProxy(
QtNetwork.QNetworkProxy.HttpProxy, host, port
)
proxy = QtNetwork.QNetworkProxy(QtNetwork.QNetworkProxy.HttpProxy, host, port)
self.QNAM.setProxy(proxy)
def _setup_proxy_freecad(self):
@@ -314,9 +302,7 @@ if HAVE_QTNETWORK:
except queue.Empty:
pass
def __launch_request(
self, index: int, request: QtNetwork.QNetworkRequest
) -> None:
def __launch_request(self, index: int, request: QtNetwork.QNetworkRequest) -> None:
"""Given a network request, ask the QNetworkAccessManager to begin processing it."""
reply = self.QNAM.get(request)
self.replies[index] = reply
@@ -338,9 +324,7 @@ if HAVE_QTNETWORK:
current_index = next(self.counting_iterator) # A thread-safe counter
# Use a queue because we can only put things on the QNAM from the main event loop thread
self.queue.put(
QueueItem(
current_index, self.__create_get_request(url), track_progress=False
)
QueueItem(current_index, self.__create_get_request(url), track_progress=False)
)
self.__request_queued.emit()
return current_index
@@ -356,9 +340,7 @@ if HAVE_QTNETWORK:
current_index = next(self.counting_iterator) # A thread-safe counter
# Use a queue because we can only put things on the QNAM from the main event loop thread
self.queue.put(
QueueItem(
current_index, self.__create_get_request(url), track_progress=True
)
QueueItem(current_index, self.__create_get_request(url), track_progress=True)
)
self.__request_queued.emit()
return current_index
@@ -371,9 +353,7 @@ if HAVE_QTNETWORK:
self.synchronous_complete[current_index] = False
self.queue.put(
QueueItem(
current_index, self.__create_get_request(url), track_progress=False
)
QueueItem(current_index, self.__create_get_request(url), track_progress=False)
)
self.__request_queued.emit()
while True:
@@ -415,9 +395,7 @@ if HAVE_QTNETWORK:
QtNetwork.QNetworkRequest.RedirectPolicyAttribute,
QtNetwork.QNetworkRequest.UserVerifiedRedirectPolicy,
)
request.setAttribute(
QtNetwork.QNetworkRequest.CacheSaveControlAttribute, True
)
request.setAttribute(QtNetwork.QNetworkRequest.CacheSaveControlAttribute, True)
request.setAttribute(
QtNetwork.QNetworkRequest.CacheLoadControlAttribute,
QtNetwork.QNetworkRequest.PreferNetwork,
@@ -457,9 +435,7 @@ if HAVE_QTNETWORK:
)
proxy_authentication.setWindowFlag(QtCore.Qt.WindowStaysOnTopHint, True)
# Show the right labels, etc.
proxy_authentication.labelProxyAddress.setText(
f"{reply.hostName()}:{reply.port()}"
)
proxy_authentication.labelProxyAddress.setText(f"{reply.hostName()}:{reply.port()}")
if authenticator.realm():
proxy_authentication.labelProxyRealm.setText(authenticator.realm())
else:
@@ -468,9 +444,7 @@ if HAVE_QTNETWORK:
result = proxy_authentication.exec()
if result == QtWidgets.QDialogButtonBox.Ok:
authenticator.setUser(proxy_authentication.lineEditUsername.text())
authenticator.setPassword(
proxy_authentication.lineEditPassword.text()
)
authenticator.setPassword(proxy_authentication.lineEditPassword.text())
else:
username = input("Proxy username: ")
import getpass
@@ -502,8 +476,7 @@ if HAVE_QTNETWORK:
"""Called when an SSL error occurs: prints the error information."""
if HAVE_FREECAD:
FreeCAD.Console.PrintWarning(
translate("AddonsInstaller", "Error with encrypted connection")
+ "\n:"
translate("AddonsInstaller", "Error with encrypted connection") + "\n:"
)
FreeCAD.Console.PrintWarning(reply)
for error in errors:
@@ -549,9 +522,7 @@ if HAVE_QTNETWORK:
f.write(buffer.data())
except OSError as e:
if HAVE_FREECAD:
FreeCAD.Console.PrintError(
f"Network Manager internal error: {str(e)}"
)
FreeCAD.Console.PrintError(f"Network Manager internal error: {str(e)}")
else:
print(f"Network Manager internal error: {str(e)}")
@@ -560,15 +531,10 @@ if HAVE_QTNETWORK:
any notifications have been called."""
reply = self.sender()
if not reply:
print(
"Network Manager Error: __reply_finished not called by a Qt signal"
)
print("Network Manager Error: __reply_finished not called by a Qt signal")
return
if (
reply.error()
== QtNetwork.QNetworkReply.NetworkError.OperationCanceledError
):
if reply.error() == QtNetwork.QNetworkReply.NetworkError.OperationCanceledError:
# Silently do nothing
return
@@ -581,9 +547,7 @@ if HAVE_QTNETWORK:
print(f"Lost net request for {reply.url()}")
return
response_code = reply.attribute(
QtNetwork.QNetworkRequest.HttpStatusCodeAttribute
)
response_code = reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
self.queue.task_done()
if reply.error() == QtNetwork.QNetworkReply.NetworkError.NoError:
if index in self.monitored_connections:
@@ -611,9 +575,7 @@ else: # HAVE_QTNETWORK is false:
completed = QtCore.Signal(
int, int, bytes
) # Emitted as soon as the request is made, with a connection failed error
progress_made = QtCore.Signal(
int, int, int
) # Never emitted, no progress is made here
progress_made = QtCore.Signal(int, int, int) # Never emitted, no progress is made here
progress_complete = QtCore.Signal(
int, int, os.PathLike
) # Emitted as soon as the request is made, with a connection failed error
@@ -675,9 +637,7 @@ if __name__ == "__main__":
"""Attached to the completion signal, prints diagnostic information about the network access"""
global count
if code == 200:
print(
f"For request {index+1}, response was {data.size()} bytes.", flush=True
)
print(f"For request {index+1}, response was {data.size()} bytes.", flush=True)
else:
print(
f"For request {index+1}, request failed with HTTP result code {code}",

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -209,4 +209,4 @@
</g>
</g>
</g>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1062,4 +1062,4 @@
id="path7525"
d="M 10.394317,26.928236 C 6.0245941,22.283194 6.0765809,17.114547 10.538806,12.562787 16.543902,6.4372003 29.103274,3.5349553 40.872882,5.5531278 c 1.244209,0.2133484 3.085054,0.6218847 4.090769,0.9078584 l 1.828571,0.5199524 -1.260833,0.1285101 c -5.827548,0.5939709 -13.018922,3.5878883 -19.489477,8.1138723 -4.131135,2.88962 -8.415065,6.667234 -12.013782,10.593878 -1.20896,1.319123 -2.248653,2.398408 -2.310428,2.398408 -0.06178,0 -0.657299,-0.579317 -1.323385,-1.287371 z"
style="opacity:1;fill:url(#linearGradient7541);fill-opacity:1;stroke:#000000;stroke-width:0.75590551;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
inkscape:connector-curvature="0" /></g></svg>
inkscape:connector-curvature="0" /></g></svg>

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -142,4 +142,4 @@
</g>
</g>
</g>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -748,4 +748,4 @@
id="path7555"
d="M 1.1006857,295.7803 15.528592,281.35239 v 7.21396 l -7.2139532,7.21395 z"
style="fill:url(#linearGradient4474);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" /></g></svg>
inkscape:connector-curvature="0" /></g></svg>

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -1,20 +1,20 @@
Copyright <%%YEAR%%> <%%COPYRIGHT HOLDER%%>
Redistribution and use in source and binary forms, with or without modification, are permitted
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions
1. Redistributions of source code must retain the above copyright notice, this list of conditions
and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials provided with
2. Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials provided with
the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,23 +1,23 @@
Copyright <%%YEAR%%> <%%COPYRIGHT HOLDER%%>
Redistribution and use in source and binary forms, with or without modification, are permitted
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions
1. Redistributions of source code must retain the above copyright notice, this list of conditions
and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials provided with
2. Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials provided with
the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to
3. Neither the name of the copyright holder nor the names of its contributors may be used to
endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,97 +1,97 @@
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF
THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS
DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES
RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and
Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner")
The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and
Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner")
of an original work of authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the purpose of
contributing to a commons of creative, cultural and scientific works ("Commons") that the public
can reliably and without fear of later claims of infringement build upon, modify, incorporate in
other works, reuse and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may contribute to the
Commons to promote the ideal of a free culture and the further production of creative, cultural
and scientific works, or to gain reputation or greater distribution for their Work in part through
contributing to a commons of creative, cultural and scientific works ("Commons") that the public
can reliably and without fear of later claims of infringement build upon, modify, incorporate in
other works, reuse and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may contribute to the
Commons to promote the ideal of a free culture and the further production of creative, cultural
and scientific works, or to gain reputation or greater distribution for their Work in part through
the use and efforts of others.
For these and/or other purposes and motivations, and without any expectation of additional
consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the
extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects
For these and/or other purposes and motivations, and without any expectation of additional
consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the
extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects
to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or
her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on
her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on
those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and
related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights
1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and
related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights
include, but are not limited to, the following:
the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
moral rights retained by the original author(s) and/or performer(s);
publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;
rights protecting against unfair competition in regards to a Work, subject to the limitations in
rights protecting against unfair competition in regards to a Work, subject to the limitations in
paragraph 4(a), below;
rights protecting the extraction, dissemination, use and reuse of data in a Work;
database rights (such as those arising under Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases, and under any national
database rights (such as those arising under Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases, and under any national
implementation thereof, including any amended or successor version of such directive); and
other similar, equivalent or corresponding rights throughout the world based on applicable
other similar, equivalent or corresponding rights throughout the world based on applicable
law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law,
Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons,
and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as future claims and causes
of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided
by applicable law or treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the
Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's
heirs and successors, fully intending that such Waiver shall not be subject to revocation,
rescission, cancellation, termination, or any other legal or equitable action to disrupt the
quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of
2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law,
Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons,
and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as future claims and causes
of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided
by applicable law or treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the
Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's
heirs and successors, fully intending that such Waiver shall not be subject to revocation,
rescission, cancellation, termination, or any other legal or equitable action to disrupt the
quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of
Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be judged legally
invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum
3. Public License Fallback. Should any part of the Waiver for any reason be judged legally
invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum
extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free,
non-transferable, non sublicensable, non exclusive, irrevocable and unconditional license to
exercise Affirmer's Copyright and Related Rights in the Work
(i) in all territories worldwide,
(ii) for the maximum duration provided by applicable law or treaty (including future time
extensions),
(iii) in any current or future medium and for any number of copies, and
(iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional
extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free,
non-transferable, non sublicensable, non exclusive, irrevocable and unconditional license to
exercise Affirmer's Copyright and Related Rights in the Work
(i) in all territories worldwide,
(ii) for the maximum duration provided by applicable law or treaty (including future time
extensions),
(iii) in any current or future medium and for any number of copies, and
(iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional
purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by
Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or
ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate
the remainder of the License, and in such case Affirmer hereby affirms that he or she will not
(i) exercise any of his or her remaining Copyright and Related Rights in the Work or
(ii) assert any associated claims and causes of action with respect to the Work, in either case
Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or
ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate
the remainder of the License, and in such case Affirmer hereby affirms that he or she will not
(i) exercise any of his or her remaining Copyright and Related Rights in the Work or
(ii) assert any associated claims and causes of action with respect to the Work, in either case
contrary to Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or
No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or
otherwise affected by this document.
Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning
the Work, express, implied, statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non infringement, or the absence of
latent or other defects, accuracy, or the present or absence of errors, whether or not
Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning
the Work, express, implied, statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non infringement, or the absence of
latent or other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work
or any use thereof, including without limitation any person's Copyright and Related Rights in the
Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions
or any use thereof, including without limitation any person's Copyright and Related Rights in the
Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
Affirmer understands and acknowledges that Creative Commons is not a party to this document and has
Affirmer understands and acknowledges that Creative Commons is not a party to this document and has
no duty or obligation with respect to this CC0 or use of the Work.

View File

@@ -55,7 +55,7 @@ modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
@@ -111,7 +111,7 @@ modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
@@ -158,7 +158,7 @@ Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
@@ -216,7 +216,7 @@ instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
@@ -267,7 +267,7 @@ Library will still fall under Section 6.)
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
@@ -329,7 +329,7 @@ restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
@@ -370,7 +370,7 @@ subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
@@ -422,7 +422,7 @@ conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is

View File

@@ -69,9 +69,7 @@ class ConnectionCheckerGUI(QtCore.QObject):
translate("AddonsInstaller", "Checking for connection to GitHub..."),
QtWidgets.QMessageBox.Cancel,
)
self.connection_check_message.buttonClicked.connect(
self.cancel_network_check
)
self.connection_check_message.buttonClicked.connect(self.cancel_network_check)
self.connection_check_message.show()
def cancel_network_check(self, _):

View File

@@ -151,9 +151,7 @@ class DependencyInstaller(QObject):
fci.Console.PrintMessage(proc.stdout + "\n")
except subprocess.CalledProcessError as e:
fci.Console.PrintError(
translate(
"AddonsInstaller", "Installation of optional package failed"
)
translate("AddonsInstaller", "Installation of optional package failed")
+ ":\n"
+ str(e)
+ "\n"
@@ -182,23 +180,19 @@ class DependencyInstaller(QObject):
if is_interruption_requested():
return
fci.Console.PrintMessage(
translate(
"AddonsInstaller", "Installing required dependency {}"
).format(addon.name)
translate("AddonsInstaller", "Installing required dependency {}").format(addon.name)
+ "\n"
)
if addon.macro is None:
installer = AddonInstaller(addon)
else:
installer = MacroInstaller(addon)
result = (
installer.run()
) # Run in this thread, which should be off the GUI thread
result = installer.run() # Run in this thread, which should be off the GUI thread
if not result:
self.failure.emit(
translate(
"AddonsInstaller", "Installation of Addon {} failed"
).format(addon.name),
translate("AddonsInstaller", "Installation of Addon {} failed").format(
addon.name
),
"",
)
return

View File

@@ -70,9 +70,7 @@ class AddonGitInterface:
try:
AddonGitInterface.git_manager = GitManager()
except NoGitFound:
FreeCAD.Console.PrintLog(
"No git found, Addon Manager Developer Mode disabled."
)
FreeCAD.Console.PrintLog("No git found, Addon Manager Developer Mode disabled.")
return
self.path = path
@@ -127,12 +125,8 @@ class DeveloperMode:
small_size_policy.setHorizontalStretch(1)
self.people_table.widget.setSizePolicy(large_size_policy)
self.licenses_table.widget.setSizePolicy(small_size_policy)
self.dialog.peopleAndLicenseshorizontalLayout.addWidget(
self.people_table.widget
)
self.dialog.peopleAndLicenseshorizontalLayout.addWidget(
self.licenses_table.widget
)
self.dialog.peopleAndLicenseshorizontalLayout.addWidget(self.people_table.widget)
self.dialog.peopleAndLicenseshorizontalLayout.addWidget(self.licenses_table.widget)
self.pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
self.current_mod: str = ""
self.git_interface = None
@@ -267,27 +261,17 @@ class DeveloperMode:
if item.Name:
info.append(translate("AddonsInstaller", "Name") + ": " + item.Name)
if item.Classname:
info.append(
translate("AddonsInstaller", "Class") + ": " + item.Classname
)
info.append(translate("AddonsInstaller", "Class") + ": " + item.Classname)
if item.Description:
info.append(
translate("AddonsInstaller", "Description")
+ ": "
+ item.Description
translate("AddonsInstaller", "Description") + ": " + item.Description
)
if item.Subdirectory:
info.append(
translate("AddonsInstaller", "Subdirectory")
+ ": "
+ item.Subdirectory
translate("AddonsInstaller", "Subdirectory") + ": " + item.Subdirectory
)
if item.File:
info.append(
translate("AddonsInstaller", "Files")
+ ": "
+ ", ".join(item.File)
)
info.append(translate("AddonsInstaller", "Files") + ": " + ", ".join(item.File))
contents_string += ", ".join(info)
item = QListWidgetItem(contents_string)
@@ -357,24 +341,14 @@ class DeveloperMode:
def _setup_dialog_signals(self):
"""Set up the signal and slot connections for the main dialog."""
self.dialog.addonPathBrowseButton.clicked.connect(
self._addon_browse_button_clicked
)
self.dialog.pathToAddonComboBox.editTextChanged.connect(
self._addon_combo_text_changed
)
self.dialog.detectMinPythonButton.clicked.connect(
self._detect_min_python_clicked
)
self.dialog.addonPathBrowseButton.clicked.connect(self._addon_browse_button_clicked)
self.dialog.pathToAddonComboBox.editTextChanged.connect(self._addon_combo_text_changed)
self.dialog.detectMinPythonButton.clicked.connect(self._detect_min_python_clicked)
self.dialog.iconBrowseButton.clicked.connect(self._browse_for_icon_clicked)
self.dialog.addContentItemToolButton.clicked.connect(self._add_content_clicked)
self.dialog.removeContentItemToolButton.clicked.connect(
self._remove_content_clicked
)
self.dialog.contentsListWidget.itemSelectionChanged.connect(
self._content_selection_changed
)
self.dialog.removeContentItemToolButton.clicked.connect(self._remove_content_clicked)
self.dialog.contentsListWidget.itemSelectionChanged.connect(self._content_selection_changed)
self.dialog.contentsListWidget.itemDoubleClicked.connect(self._edit_content)
self.dialog.versionToTodayButton.clicked.connect(self._set_to_today_clicked)
@@ -393,17 +367,13 @@ class DeveloperMode:
self.metadata = FreeCAD.Metadata()
self.metadata.Name = self.dialog.displayNameLineEdit.text()
self.metadata.Description = (
self.dialog.descriptionTextEdit.document().toPlainText()
)
self.metadata.Description = self.dialog.descriptionTextEdit.document().toPlainText()
self.metadata.Version = self.dialog.versionLineEdit.text()
self.metadata.Icon = self.dialog.iconPathLineEdit.text()
urls = []
if self.dialog.websiteURLLineEdit.text():
urls.append(
{"location": self.dialog.websiteURLLineEdit.text(), "type": "website"}
)
urls.append({"location": self.dialog.websiteURLLineEdit.text(), "type": "website"})
if self.dialog.repositoryURLLineEdit.text():
urls.append(
{
@@ -420,9 +390,7 @@ class DeveloperMode:
}
)
if self.dialog.readmeURLLineEdit.text():
urls.append(
{"location": self.dialog.readmeURLLineEdit.text(), "type": "readme"}
)
urls.append({"location": self.dialog.readmeURLLineEdit.text(), "type": "readme"})
if self.dialog.documentationURLLineEdit.text():
urls.append(
{
@@ -594,9 +562,7 @@ class DeveloperMode:
)
return
FreeCAD.Console.PrintMessage(
translate(
"AddonsInstaller", "Scanning Addon for Python version compatibility"
)
translate("AddonsInstaller", "Scanning Addon for Python version compatibility")
+ "...\n"
)
# pylint: disable=import-outside-toplevel
@@ -608,9 +574,7 @@ class DeveloperMode:
if filename.endswith(".py"):
with open(os.path.join(dir_path, filename), encoding="utf-8") as f:
contents = f.read()
version_strings = vermin.version_strings(
vermin.detect(contents)
)
version_strings = vermin.version_strings(vermin.detect(contents))
version = version_strings.split(",")
if len(version) >= 2:
# Only care about Py3, and only if there is a dot in the version:
@@ -622,9 +586,7 @@ class DeveloperMode:
FreeCAD.Console.PrintLog(
f"Detected Python 3.{minor} required by {filename}\n"
)
required_minor_version = max(
required_minor_version, minor
)
required_minor_version = max(required_minor_version, minor)
self.dialog.minPythonLineEdit.setText(f"3.{required_minor_version}")
QMessageBox.information(
self.dialog,
@@ -654,13 +616,10 @@ class DeveloperMode:
if response == QMessageBox.Cancel:
return False
FreeCAD.Console.PrintMessage(
translate("AddonsInstaller", "Attempting to install Vermin from PyPi")
+ "...\n"
translate("AddonsInstaller", "Attempting to install Vermin from PyPi") + "...\n"
)
python_exe = utils.get_python_exe()
vendor_path = os.path.join(
FreeCAD.getUserAppDataDir(), "AdditionalPythonPackages"
)
vendor_path = os.path.join(FreeCAD.getUserAppDataDir(), "AdditionalPythonPackages")
if not os.path.exists(vendor_path):
os.makedirs(vendor_path)

View File

@@ -80,32 +80,22 @@ class AddContent:
small_size_policy.setHorizontalStretch(1)
self.people_table.widget.setSizePolicy(large_size_policy)
self.licenses_table.widget.setSizePolicy(small_size_policy)
self.dialog.peopleAndLicenseshorizontalLayout.addWidget(
self.people_table.widget
)
self.dialog.peopleAndLicenseshorizontalLayout.addWidget(
self.licenses_table.widget
)
self.dialog.peopleAndLicenseshorizontalLayout.addWidget(self.people_table.widget)
self.dialog.peopleAndLicenseshorizontalLayout.addWidget(self.licenses_table.widget)
self.toplevel_metadata = toplevel_metadata
self.metadata = None
self.path_to_addon = path_to_addon.replace("/", os.path.sep)
if self.path_to_addon[-1] != os.path.sep:
self.path_to_addon += (
os.path.sep
) # Make sure the path ends with a separator
self.path_to_addon += os.path.sep # Make sure the path ends with a separator
self.dialog.iconLabel.hide() # Until we have an icon to display
self.dialog.iconBrowseButton.clicked.connect(self._browse_for_icon_clicked)
self.dialog.subdirectoryBrowseButton.clicked.connect(
self._browse_for_subdirectory_clicked
)
self.dialog.subdirectoryBrowseButton.clicked.connect(self._browse_for_subdirectory_clicked)
self.dialog.tagsButton.clicked.connect(self._tags_clicked)
self.dialog.dependenciesButton.clicked.connect(self._dependencies_clicked)
self.dialog.freecadVersionsButton.clicked.connect(
self._freecad_versions_clicked
)
self.dialog.freecadVersionsButton.clicked.connect(self._freecad_versions_clicked)
self.dialog.versionLineEdit.setValidator(VersionValidator())
self.dialog.prefPackNameLineEdit.setValidator(NameValidator())
@@ -134,9 +124,7 @@ class AddContent:
if index == -1:
index = 2 # Workbench
FreeCAD.Console.PrintWarning(
translate("AddonsInstaller", "Unrecognized content kind '{}'").format(
content_kind
)
translate("AddonsInstaller", "Unrecognized content kind '{}'").format(content_kind)
+ "\n"
)
self.dialog.addonKindComboBox.setCurrentIndex(index)
@@ -189,9 +177,7 @@ class AddContent:
def _set_icon(self, icon_relative_path):
"""Load the icon and display it, and its path, in the dialog."""
icon_path = os.path.join(
self.path_to_addon, icon_relative_path.replace("/", os.path.sep)
)
icon_path = os.path.join(self.path_to_addon, icon_relative_path.replace("/", os.path.sep))
if os.path.isfile(icon_path):
icon_data = QIcon(icon_path)
if not icon_data.isNull():
@@ -199,10 +185,7 @@ class AddContent:
self.dialog.iconLabel.show()
else:
FreeCAD.Console.PrintError(
translate("AddonsInstaller", "Unable to locate icon at {}").format(
icon_path
)
+ "\n"
translate("AddonsInstaller", "Unable to locate icon at {}").format(icon_path) + "\n"
)
self.dialog.iconLineEdit.setText(icon_relative_path)
@@ -234,9 +217,7 @@ class AddContent:
return current_data, self.metadata
# Otherwise, process the rest of the metadata (display name is already done)
self.metadata.Description = (
self.dialog.descriptionTextEdit.document().toPlainText()
)
self.metadata.Description = self.dialog.descriptionTextEdit.document().toPlainText()
self.metadata.Version = self.dialog.versionLineEdit.text()
maintainers = []
@@ -407,16 +388,10 @@ class EditDependencies:
self.dialog.removeDependencyToolButton.setIcon(
QIcon.fromTheme("remove", QIcon(":/icons/list-remove.svg"))
)
self.dialog.addDependencyToolButton.clicked.connect(
self._add_dependency_clicked
)
self.dialog.removeDependencyToolButton.clicked.connect(
self._remove_dependency_clicked
)
self.dialog.addDependencyToolButton.clicked.connect(self._add_dependency_clicked)
self.dialog.removeDependencyToolButton.clicked.connect(self._remove_dependency_clicked)
self.dialog.tableWidget.itemDoubleClicked.connect(self._edit_dependency)
self.dialog.tableWidget.itemSelectionChanged.connect(
self._current_index_changed
)
self.dialog.tableWidget.itemSelectionChanged.connect(self._current_index_changed)
self.dialog.removeDependencyToolButton.setDisabled(True)
self.metadata = None
@@ -485,9 +460,7 @@ class EditDependencies:
dep_type = self.dialog.tableWidget.item(row, 0).data(Qt.UserRole)
dep_name = self.dialog.tableWidget.item(row, 1).text()
dep_optional = bool(self.dialog.tableWidget.item(row, 2))
new_dep_type, new_dep_name, new_dep_optional = dlg.exec(
dep_type, dep_name, dep_optional
)
new_dep_type, new_dep_name, new_dep_optional = dlg.exec(dep_type, dep_name, dep_optional)
if dep_type and dep_name:
self.metadata.removeDepend(
{"package": dep_name, "type": dep_type, "optional": dep_optional}
@@ -520,16 +493,10 @@ class EditDependency:
self.dialog.typeComboBox.addItem(
translate("AddonsInstaller", "Internal Workbench"), "workbench"
)
self.dialog.typeComboBox.addItem(
translate("AddonsInstaller", "External Addon"), "addon"
)
self.dialog.typeComboBox.addItem(
translate("AddonsInstaller", "Python Package"), "python"
)
self.dialog.typeComboBox.addItem(translate("AddonsInstaller", "External Addon"), "addon")
self.dialog.typeComboBox.addItem(translate("AddonsInstaller", "Python Package"), "python")
self.dialog.typeComboBox.currentIndexChanged.connect(
self._type_selection_changed
)
self.dialog.typeComboBox.currentIndexChanged.connect(self._type_selection_changed)
self.dialog.dependencyComboBox.currentIndexChanged.connect(
self._dependency_selection_changed
)
@@ -539,9 +506,7 @@ class EditDependency:
self.dialog.layout().setSizeConstraint(QLayout.SetFixedSize)
def exec(
self, dep_type="", dep_name="", dep_optional=False
) -> Tuple[str, str, bool]:
def exec(self, dep_type="", dep_name="", dep_optional=False) -> Tuple[str, str, bool]:
"""Execute the dialog, returning a tuple of the type of dependency (workbench, addon, or
python), the name of the dependency, and a boolean indicating whether this is optional.
"""
@@ -590,12 +555,8 @@ class EditDependency:
repo_dict[repo.display_name.lower()] = (repo.display_name, repo.name)
sorted_keys = sorted(repo_dict)
for item in sorted_keys:
self.dialog.dependencyComboBox.addItem(
repo_dict[item][0], repo_dict[item][1]
)
self.dialog.dependencyComboBox.addItem(
translate("AddonsInstaller", "Other..."), "other"
)
self.dialog.dependencyComboBox.addItem(repo_dict[item][0], repo_dict[item][1])
self.dialog.dependencyComboBox.addItem(translate("AddonsInstaller", "Other..."), "other")
def _populate_allowed_python_packages(self):
"""Add all allowed python packages to the list"""
@@ -606,9 +567,7 @@ class EditDependency:
packages = sorted(AM_INSTANCE.allowed_packages)
for package in packages:
self.dialog.dependencyComboBox.addItem(package, package)
self.dialog.dependencyComboBox.addItem(
translate("AddonsInstaller", "Other..."), "other"
)
self.dialog.dependencyComboBox.addItem(translate("AddonsInstaller", "Other..."), "other")
def _type_selection_changed(self, _):
"""Callback: The type of dependency has been changed"""
@@ -637,9 +596,7 @@ class EditFreeCADVersions:
def __init__(self):
self.dialog = FreeCADGui.PySideUic.loadUi(
os.path.join(
os.path.dirname(__file__), "developer_mode_freecad_versions.ui"
)
os.path.join(os.path.dirname(__file__), "developer_mode_freecad_versions.ui")
)
def exec(self, metadata: FreeCAD.Metadata):
@@ -666,9 +623,7 @@ class EditAdvancedVersions:
def __init__(self):
self.dialog = FreeCADGui.PySideUic.loadUi(
os.path.join(
os.path.dirname(__file__), "developer_mode_advanced_freecad_versions.ui"
)
os.path.join(os.path.dirname(__file__), "developer_mode_advanced_freecad_versions.ui")
)
def exec(self):

View File

@@ -117,9 +117,7 @@ class LicenseSelector:
)
self.pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
for short_code, details in LicenseSelector.licenses.items():
self.dialog.comboBox.addItem(
f"{short_code}: {details[0]}", userData=short_code
)
self.dialog.comboBox.addItem(f"{short_code}: {details[0]}", userData=short_code)
self.dialog.comboBox.addItem(self.other_label)
self.dialog.otherLineEdit.hide()
self.dialog.otherLabel.hide()
@@ -134,9 +132,7 @@ class LicenseSelector:
short_code = self.pref.GetString("devModeLastSelectedLicense", "LGPLv2.1")
self.set_license(short_code)
def exec(
self, short_code: str = None, license_path: str = ""
) -> Optional[Tuple[str, str]]:
def exec(self, short_code: str = None, license_path: str = "") -> Optional[Tuple[str, str]]:
"""The main method for executing this dialog, as a modal that returns a tuple of the
license's "short code" and optionally the path to the license file. Returns a tuple
of None,None if the user cancels the operation."""
@@ -252,10 +248,7 @@ class LicenseSelector:
string_data = str(byte_data, encoding="utf-8")
if (
"<%%YEAR%%>" in string_data
or "<%%COPYRIGHT HOLDER%%>" in string_data
):
if "<%%YEAR%%>" in string_data or "<%%COPYRIGHT HOLDER%%>" in string_data:
info_dlg = FreeCADGui.PySideUic.loadUi(
os.path.join(
os.path.dirname(__file__),
@@ -279,6 +272,4 @@ class LicenseSelector:
with open(license_path, "w", encoding="utf-8") as f:
f.write(string_data)
else:
FreeCAD.Console.PrintError(
f"Cannot create license file of type {short_code}\n"
)
FreeCAD.Console.PrintError(f"Cannot create license file of type {short_code}\n")

View File

@@ -46,9 +46,7 @@ class LicensesTable:
os.path.join(os.path.dirname(__file__), "developer_mode_licenses_table.ui")
)
self.widget.addButton.setIcon(
QIcon.fromTheme("add", QIcon(":/icons/list-add.svg"))
)
self.widget.addButton.setIcon(QIcon.fromTheme("add", QIcon(":/icons/list-add.svg")))
self.widget.removeButton.setIcon(
QIcon.fromTheme("remove", QIcon(":/icons/list-remove.svg"))
)

View File

@@ -75,9 +75,7 @@ class MetadataValidators:
errors.extend(self.validate_content(addon))
if len(errors) > 0:
FreeCAD.Console.PrintError(
f"Errors found in package.xml file for '{addon.name}'\n"
)
FreeCAD.Console.PrintError(f"Errors found in package.xml file for '{addon.name}'\n")
for error in errors:
FreeCAD.Console.PrintError(f" * {error}\n")
@@ -111,17 +109,12 @@ class MetadataValidators:
"""Check for the presence of the required top-level elements"""
errors = []
if not addon.metadata.name or len(addon.metadata.name) == 0:
errors.append(
"No top-level <name> element found, or <name> element is empty"
)
errors.append("No top-level <name> element found, or <name> element is empty")
if not addon.metadata.version:
errors.append(
"No top-level <version> element found, or <version> element is invalid"
)
errors.append("No top-level <version> element found, or <version> element is invalid")
if not addon.metadata.description or len(addon.metadata.description) == 0:
errors.append(
"No top-level <description> element found, or <description> element "
"is invalid"
"No top-level <description> element found, or <description> element " "is invalid"
)
maintainers = addon.metadata.maintainer
@@ -129,9 +122,7 @@ class MetadataValidators:
errors.append("No top-level <maintainers> found, at least one is required")
for maintainer in maintainers:
if len(maintainer.email) == 0:
errors.append(
f"No email address specified for maintainer '{maintainer.name}'"
)
errors.append(f"No email address specified for maintainer '{maintainer.name}'")
licenses = addon.metadata.license
if len(licenses) == 0:
@@ -146,9 +137,7 @@ class MetadataValidators:
"""Check the URLs provided by the addon"""
errors = []
if len(urls) == 0:
errors.append(
"No <url> elements found, at least a repo url must be provided"
)
errors.append("No <url> elements found, at least a repo url must be provided")
else:
found_repo = False
found_readme = False
@@ -156,17 +145,13 @@ class MetadataValidators:
if url["type"] == "repository":
found_repo = True
if len(url["branch"]) == 0:
errors.append(
"<repository> element is missing the 'branch' attribute"
)
errors.append("<repository> element is missing the 'branch' attribute")
elif url["type"] == "readme":
found_readme = True
location = url["location"]
p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(location)
if not p:
errors.append(
f"Could not access specified readme at {location}"
)
errors.append(f"Could not access specified readme at {location}")
else:
p = p.data().decode("utf8")
if "<html" in p or "<!DOCTYPE html>" in p:
@@ -179,9 +164,7 @@ class MetadataValidators:
if not found_repo:
errors.append("No repo url specified")
if not found_readme:
errors.append(
"No readme url specified (not required, but highly recommended)"
)
errors.append("No readme url specified (not required, but highly recommended)")
return errors
@staticmethod

View File

@@ -47,9 +47,7 @@ class PeopleTable:
os.path.join(os.path.dirname(__file__), "developer_mode_people_table.ui")
)
self.widget.addButton.setIcon(
QIcon.fromTheme("add", QIcon(":/icons/list-add.svg"))
)
self.widget.addButton.setIcon(QIcon.fromTheme("add", QIcon(":/icons/list-add.svg")))
self.widget.removeButton.setIcon(
QIcon.fromTheme("remove", QIcon(":/icons/list-remove.svg"))
)

View File

@@ -46,9 +46,7 @@ class PersonEditor:
self.dialog.comboBox.addItem(
translate("AddonsInstaller", "Maintainer"), userData="maintainer"
)
self.dialog.comboBox.addItem(
translate("AddonsInstaller", "Author"), userData="author"
)
self.dialog.comboBox.addItem(translate("AddonsInstaller", "Author"), userData="author")
def exec(self) -> Tuple[str, str, str]:
"""Run the dialog, and return a tuple of the person's record type, their name, and their
@@ -63,15 +61,11 @@ class PersonEditor:
)
return "", "", ""
def setup(
self, person_type: str = "maintainer", name: str = "", email: str = ""
) -> None:
def setup(self, person_type: str = "maintainer", name: str = "", email: str = "") -> None:
"""Configure the dialog"""
index = self.dialog.comboBox.findData(person_type)
if index == -1:
FreeCAD.Console.PrintWarning(
f"Internal Error: unrecognized person type {person_type}"
)
FreeCAD.Console.PrintWarning(f"Internal Error: unrecognized person type {person_type}")
index = 0
self.dialog.comboBox.setCurrentIndex(index)
self.dialog.nameLineEdit.setText(name)

View File

@@ -87,9 +87,7 @@ class PythonIdentifierValidator(QValidator):
return QValidator.Invalid # Includes an illegal character of some sort
if keyword.iskeyword(value):
return (
QValidator.Intermediate
) # They can keep typing and it might become valid
return QValidator.Intermediate # They can keep typing and it might become valid
return QValidator.Acceptable

View File

@@ -84,9 +84,7 @@ class FirstRunDialog:
if warning_dialog.exec() == QtWidgets.QDialog.Accepted:
self.readWarning = True
self.pref.SetBool("readWarning2022", True)
self.pref.SetBool(
"AutoCheck", warning_dialog.checkBoxAutoCheck.isChecked()
)
self.pref.SetBool("AutoCheck", warning_dialog.checkBoxAutoCheck.isChecked())
self.pref.SetBool(
"DownloadMacros",
warning_dialog.checkBoxDownloadMacroMetadata.isChecked(),

View File

@@ -107,9 +107,7 @@ class GitManager:
+ "...\n"
)
remote = self.get_remote(local_path)
with open(
os.path.join(local_path, "ADDON_DISABLED"), "w", encoding="utf-8"
) as f:
with open(os.path.join(local_path, "ADDON_DISABLED"), "w", encoding="utf-8") as f:
f.write(
"This is a backup of an addon that failed to update cleanly so "
"was re-cloned. It was disabled by the Addon Manager's git update "
@@ -185,9 +183,7 @@ class GitManager:
# branch = self._synchronous_call_git(["branch", "--show-current"]).strip()
# This is more universal (albeit more opaque to the reader):
branch = self._synchronous_call_git(
["rev-parse", "--abbrev-ref", "HEAD"]
).strip()
branch = self._synchronous_call_git(["rev-parse", "--abbrev-ref", "HEAD"]).strip()
except GitFailed as e:
os.chdir(old_dir)
raise e
@@ -213,9 +209,9 @@ class GitManager:
self.clone(remote, local_path)
except GitFailed as e:
fci.Console.PrintError(
translate(
"AddonsInstaller", "Failed to clone {} into {} using git"
).format(remote, local_path)
translate("AddonsInstaller", "Failed to clone {} into {} using git").format(
remote, local_path
)
)
os.chdir(original_cwd)
raise e
@@ -242,9 +238,7 @@ class GitManager:
if len(segments) == 3:
result = segments[1]
break
fci.Console.PrintWarning(
"Error parsing the results from git remote -v show:\n"
)
fci.Console.PrintWarning("Error parsing the results from git remote -v show:\n")
fci.Console.PrintWarning(line + "\n")
os.chdir(old_dir)
return result
@@ -254,9 +248,7 @@ class GitManager:
old_dir = os.getcwd()
os.chdir(local_path)
try:
stdout = self._synchronous_call_git(
["branch", "-a", "--format=%(refname:lstrip=2)"]
)
stdout = self._synchronous_call_git(["branch", "-a", "--format=%(refname:lstrip=2)"])
except GitFailed as e:
os.chdir(old_dir)
raise e
@@ -273,12 +265,8 @@ class GitManager:
"""
old_dir = os.getcwd()
os.chdir(local_path)
authors = self._synchronous_call_git(["log", f"-{n}", "--format=%cN"]).split(
"\n"
)
emails = self._synchronous_call_git(["log", f"-{n}", "--format=%cE"]).split(
"\n"
)
authors = self._synchronous_call_git(["log", f"-{n}", "--format=%cN"]).split("\n")
emails = self._synchronous_call_git(["log", f"-{n}", "--format=%cE"]).split("\n")
os.chdir(old_dir)
result_dict = {}

View File

@@ -116,9 +116,7 @@ class Macro:
return False
return os.path.exists(
os.path.join(fci.DataPaths().macro_dir, self.filename)
) or os.path.exists(
os.path.join(fci.DataPaths().macro_dir, "Macro_" + self.filename)
)
) or os.path.exists(os.path.join(fci.DataPaths().macro_dir, "Macro_" + self.filename))
def fill_details_from_file(self, filename: str) -> None:
"""Opens the given Macro file and parses it for its metadata"""
@@ -160,8 +158,7 @@ class Macro:
code = self._read_code_from_wiki(p)
if not code:
self._console.PrintWarning(
translate("AddonsInstaller", "Unable to fetch the code of this macro.")
+ "\n"
translate("AddonsInstaller", "Unable to fetch the code of this macro.") + "\n"
)
return
@@ -312,9 +309,7 @@ class Macro:
f.write(self.xpm)
if self.icon:
if os.path.isabs(self.icon):
dst_file = os.path.normpath(
os.path.join(macro_dir, os.path.basename(self.icon))
)
dst_file = os.path.normpath(os.path.join(macro_dir, os.path.basename(self.icon)))
try:
shutil.copy(self.icon, dst_file)
except OSError:
@@ -340,9 +335,7 @@ class Macro:
return False
if os.path.isabs(other_file):
src_file = other_file
dst_file = os.path.normpath(
os.path.join(macro_dir, os.path.basename(other_file))
)
dst_file = os.path.normpath(os.path.join(macro_dir, os.path.basename(other_file)))
else:
src_file = os.path.normpath(os.path.join(base_dir, other_file))
dst_file = os.path.normpath(os.path.join(macro_dir, other_file))
@@ -409,9 +402,7 @@ class Macro:
icon_regex = re.compile(r'.*img.*?src="(.*?)"', re.IGNORECASE)
if wiki_icon.startswith("http"):
# It's a File: wiki link. We can load THAT page and get the image from it...
self._console.PrintLog(
f"Found a File: link for macro {self.name} -- {wiki_icon}\n"
)
self._console.PrintLog(f"Found a File: link for macro {self.name} -- {wiki_icon}\n")
p = Macro.blocking_get(wiki_icon)
if p:
p = p.decode("utf8")

View File

@@ -68,9 +68,7 @@ class MacroParser:
}
self.remaining_item_map = {}
self.console = None if FreeCAD is None else FreeCAD.Console
self.current_thread = (
DummyThread() if QtCore is None else QtCore.QThread.currentThread()
)
self.current_thread = DummyThread() if QtCore is None else QtCore.QThread.currentThread()
if code:
self.fill_details_from_code(code)
@@ -177,9 +175,7 @@ class MacroParser:
stripped_of_quotes = None
if line.startswith('"""') and line.endswith('"""'):
stripped_of_quotes = line[3:-3]
elif (line[0] == '"' and line[-1] == '"') or (
line[0] == "'" and line[-1] == "'"
):
elif (line[0] == '"' and line[-1] == '"') or (line[0] == "'" and line[-1] == "'"):
stripped_of_quotes = line[1:-1]
return stripped_of_quotes
@@ -197,9 +193,7 @@ class MacroParser:
def _cleanup_comment(self):
"""Remove HTML from the comment line, and truncate it at 512 characters."""
self.parse_results["comment"] = re.sub(
"<.*?>", "", self.parse_results["comment"]
)
self.parse_results["comment"] = re.sub("<.*?>", "", self.parse_results["comment"])
if len(self.parse_results["comment"]) > 512:
self.parse_results["comment"] = self.parse_results["comment"][:511] + ""

View File

@@ -163,17 +163,13 @@ class AddonUninstaller(QObject):
lines = f.readlines()
for line in lines:
stripped = line.strip()
if (
len(stripped) > 0
and stripped[0] != "#"
and os.path.exists(stripped)
):
if len(stripped) > 0 and stripped[0] != "#" and os.path.exists(stripped):
try:
os.unlink(stripped)
fci.Console.PrintMessage(
translate(
"AddonsInstaller", "Removed extra installed file {}"
).format(stripped)
translate("AddonsInstaller", "Removed extra installed file {}").format(
stripped
)
+ "\n"
)
except FileNotFoundError:
@@ -266,9 +262,7 @@ class MacroUninstaller(QObject):
if self.addon_to_remove.macro.icon:
files_to_remove.append(self.addon_to_remove.macro.icon)
if self.addon_to_remove.macro.xpm:
files_to_remove.append(
self.addon_to_remove.macro.name.replace(" ", "_") + "_icon.xpm"
)
files_to_remove.append(self.addon_to_remove.macro.name.replace(" ", "_") + "_icon.xpm")
for f in self.addon_to_remove.macro.other_files:
files_to_remove.append(f)
return files_to_remove

View File

@@ -44,10 +44,7 @@ class AddonUninstallerGUI(QtCore.QObject):
def __init__(self, addon_to_remove):
super().__init__()
self.addon_to_remove = addon_to_remove
if (
hasattr(self.addon_to_remove, "macro")
and self.addon_to_remove.macro is not None
):
if hasattr(self.addon_to_remove, "macro") and self.addon_to_remove.macro is not None:
self.uninstaller = MacroUninstaller(self.addon_to_remove)
else:
self.uninstaller = AddonUninstaller(self.addon_to_remove)
@@ -61,9 +58,7 @@ class AddonUninstallerGUI(QtCore.QObject):
self.dialog_timer = QtCore.QTimer()
self.dialog_timer.timeout.connect(self._show_progress_dialog)
self.dialog_timer.setSingleShot(True)
self.dialog_timer.setInterval(
1000
) # Can override from external (e.g. testing) code
self.dialog_timer.setInterval(1000) # Can override from external (e.g. testing) code
def run(self):
"""Begin the user interaction: asynchronous, only blocks while showing the initial modal
@@ -82,9 +77,9 @@ class AddonUninstallerGUI(QtCore.QObject):
confirm = QtWidgets.QMessageBox.question(
utils.get_main_am_window(),
translate("AddonsInstaller", "Confirm remove"),
translate(
"AddonsInstaller", "Are you sure you want to uninstall {}?"
).format(self.addon_to_remove.display_name),
translate("AddonsInstaller", "Are you sure you want to uninstall {}?").format(
self.addon_to_remove.display_name
),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel,
)
return confirm == QtWidgets.QMessageBox.Yes
@@ -93,9 +88,7 @@ class AddonUninstallerGUI(QtCore.QObject):
self.progress_dialog = QtWidgets.QMessageBox(
QtWidgets.QMessageBox.NoIcon,
translate("AddonsInstaller", "Removing Addon"),
translate("AddonsInstaller", "Removing {}").format(
self.addon_to_remove.display_name
)
translate("AddonsInstaller", "Removing {}").format(self.addon_to_remove.display_name)
+ "...",
QtWidgets.QMessageBox.Cancel,
parent=utils.get_main_am_window(),
@@ -119,9 +112,7 @@ class AddonUninstallerGUI(QtCore.QObject):
QtWidgets.QMessageBox.information(
utils.get_main_am_window(),
translate("AddonsInstaller", "Uninstall complete"),
translate("AddonInstaller", "Finished removing {}").format(
addon.display_name
),
translate("AddonInstaller", "Finished removing {}").format(addon.display_name),
)
self._finalize()
@@ -133,9 +124,7 @@ class AddonUninstallerGUI(QtCore.QObject):
QtWidgets.QMessageBox.critical(
utils.get_main_am_window(),
translate("AddonsInstaller", "Uninstall failed"),
translate("AddonInstaller", "Failed to remove some files")
+ ":\n"
+ message,
translate("AddonInstaller", "Failed to remove some files") + ":\n" + message,
)
self._finalize()

View File

@@ -130,9 +130,7 @@ class UpdateAllGUI(QtCore.QObject):
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, 0, QtWidgets.QTableWidgetItem(addon.display_name))
self.dialog.tableWidget.setItem(new_row, 1, QtWidgets.QTableWidgetItem(""))
self.row_map[addon.name] = new_row
@@ -144,9 +142,7 @@ class UpdateAllGUI(QtCore.QObject):
"""Grab the next addon in the list and start its updater."""
if self.addons_with_update:
addon = self.addons_with_update.pop(0)
self.in_process_row = (
self.row_map[addon.name] if addon.name in self.row_map else None
)
self.in_process_row = self.row_map[addon.name] if addon.name in self.row_map else None
self._update_addon_status(self.in_process_row, AddonStatus.INSTALLING)
self.dialog.tableWidget.scrollToItem(
self.dialog.tableWidget.item(self.in_process_row, 0)

View File

@@ -76,9 +76,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
NetworkManager.AM_NETWORK_MANAGER.completed.connect(self.download_completed)
self.requests_completed = 0
self.total_requests = 0
self.store = os.path.join(
FreeCAD.getUserCachePath(), "AddonManager", "PackageMetadata"
)
self.store = os.path.join(FreeCAD.getUserCachePath(), "AddonManager", "PackageMetadata")
FreeCAD.Console.PrintLog(f"Storing Addon Manager cache data in {self.store}\n")
self.updated_repos = set()
@@ -122,9 +120,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
while self.requests:
if current_thread.isInterruptionRequested():
NetworkManager.AM_NETWORK_MANAGER.completed.disconnect(
self.download_completed
)
NetworkManager.AM_NETWORK_MANAGER.completed.disconnect(self.download_completed)
for request in self.requests:
NetworkManager.AM_NETWORK_MANAGER.abort(request)
return
@@ -137,9 +133,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
for repo in self.updated_repos:
self.package_updated.emit(repo)
def download_completed(
self, index: int, code: int, data: QtCore.QByteArray
) -> None:
def download_completed(self, index: int, code: int, data: QtCore.QByteArray) -> None:
"""Callback for handling a completed metadata file download."""
if index in self.requests:
self.requests_completed += 1
@@ -151,9 +145,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
self.process_package_xml(request[0], data)
elif request[1] == UpdateMetadataCacheWorker.RequestType.METADATA_TXT:
self.process_metadata_txt(request[0], data)
elif (
request[1] == UpdateMetadataCacheWorker.RequestType.REQUIREMENTS_TXT
):
elif request[1] == UpdateMetadataCacheWorker.RequestType.REQUIREMENTS_TXT:
self.process_requirements_txt(request[0], data)
elif request[1] == UpdateMetadataCacheWorker.RequestType.ICON:
self.process_icon(request[0], data)
@@ -171,9 +163,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
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
)
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
@@ -220,9 +210,7 @@ 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
)
translate("AddonsInstaller", "Downloaded metadata.txt for {}").format(repo.display_name)
)
f = self._decode_data(data.data(), repo.name, "metadata.txt")
@@ -283,9 +271,7 @@ 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
)
translate("AddonsInstaller", "Downloaded icon for {}").format(repo.display_name)
)
cache_file = repo.get_cached_icon_filename()
with open(cache_file, "wb") as icon_file:

View File

@@ -162,9 +162,7 @@ class CreateAddonListWorker(QtCore.QThread):
for addon in addon_list:
if " " in addon:
addon_and_branch = addon.split(" ")
custom_addons.append(
{"url": addon_and_branch[0], "branch": addon_and_branch[1]}
)
custom_addons.append({"url": addon_and_branch[0], "branch": addon_and_branch[1]})
else:
custom_addons.append({"url": addon, "branch": "master"})
for addon in custom_addons:
@@ -178,9 +176,9 @@ class CreateAddonListWorker(QtCore.QThread):
if name in self.package_names:
# We already have something with this name, skip this one
FreeCAD.Console.PrintWarning(
translate(
"AddonsInstaller", "WARNING: Duplicate addon {} ignored"
).format(name)
translate("AddonsInstaller", "WARNING: Duplicate addon {} ignored").format(
name
)
)
continue
FreeCAD.Console.PrintLog(
@@ -251,9 +249,7 @@ class CreateAddonListWorker(QtCore.QThread):
repo.obsolete = True
self.addon_repo.emit(repo)
self.status_message.emit(
translate("AddonsInstaller", "Workbenches list was updated.")
)
self.status_message.emit(translate("AddonsInstaller", "Workbenches list was updated."))
def _retrieve_macros_from_git(self):
"""Retrieve macros from FreeCAD-macros.git
@@ -338,8 +334,7 @@ class CreateAddonListWorker(QtCore.QThread):
)
FreeCAD.Console.PrintMessage(f"{macro_cache_location}\n")
FreeCAD.Console.PrintMessage(
translate("AddonsInstaller", "Attempting to do a clean checkout...")
+ "\n"
translate("AddonsInstaller", "Attempting to do a clean checkout...") + "\n"
)
try:
os.chdir(
@@ -459,13 +454,9 @@ class LoadPackagesFromCacheWorker(QtCore.QThread):
try:
repo.load_metadata_file(repo_metadata_cache_path)
repo.installed_version = repo.metadata.version
repo.updated_timestamp = os.path.getmtime(
repo_metadata_cache_path
)
repo.updated_timestamp = os.path.getmtime(repo_metadata_cache_path)
except Exception as e:
FreeCAD.Console.PrintLog(
f"Failed loading {repo_metadata_cache_path}\n"
)
FreeCAD.Console.PrintLog(f"Failed loading {repo_metadata_cache_path}\n")
FreeCAD.Console.PrintLog(str(e) + "\n")
self.addon_repo.emit(repo)
@@ -612,9 +603,7 @@ class UpdateChecker:
wb.set_status(Addon.Status.NO_UPDATE_AVAILABLE)
except GitFailed:
FreeCAD.Console.PrintWarning(
translate(
"AddonsInstaller", "git status failed for {}"
).format(wb.name)
translate("AddonsInstaller", "git status failed for {}").format(wb.name)
+ "\n"
)
wb.set_status(Addon.Status.CANNOT_CHECK)
@@ -670,9 +659,7 @@ class UpdateChecker:
# Make sure this macro has its code downloaded:
try:
if not macro_wrapper.macro.parsed and macro_wrapper.macro.on_git:
macro_wrapper.macro.fill_details_from_file(
macro_wrapper.macro.src_filename
)
macro_wrapper.macro.fill_details_from_file(macro_wrapper.macro.src_filename)
elif not macro_wrapper.macro.parsed and macro_wrapper.macro.on_wiki:
mac = macro_wrapper.macro.name.replace(" ", "_")
mac = mac.replace("&", "%26")
@@ -694,9 +681,7 @@ class UpdateChecker:
hasher2 = hashlib.sha1()
hasher1.update(macro_wrapper.macro.code.encode("utf-8"))
new_sha1 = hasher1.hexdigest()
test_file_one = os.path.join(
FreeCAD.getUserMacroDir(True), macro_wrapper.macro.filename
)
test_file_one = os.path.join(FreeCAD.getUserMacroDir(True), macro_wrapper.macro.filename)
test_file_two = os.path.join(
FreeCAD.getUserMacroDir(True), "Macro_" + macro_wrapper.macro.filename
)
@@ -823,9 +808,7 @@ class CacheMacroCodeWorker(QtCore.QThread):
if QtCore.QThread.currentThread().isInterruptionRequested():
return
self.progress_made.emit(
len(self.repos) - self.repo_queue.qsize(), len(self.repos)
)
self.progress_made.emit(len(self.repos) - self.repo_queue.qsize(), len(self.repos))
try:
next_repo = self.repo_queue.get_nowait()
@@ -886,18 +869,12 @@ 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...")
)
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.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")
)
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

@@ -67,18 +67,10 @@ class ChangeBranchDialog(QtWidgets.QWidget):
if ref == current_ref:
index = self.item_filter.mapFromSource(self.item_model.index(row, 0))
selection_model.select(index, QtCore.QItemSelectionModel.ClearAndSelect)
selection_model.select(
index.siblingAtColumn(1), QtCore.QItemSelectionModel.Select
)
selection_model.select(
index.siblingAtColumn(2), QtCore.QItemSelectionModel.Select
)
selection_model.select(
index.siblingAtColumn(3), QtCore.QItemSelectionModel.Select
)
selection_model.select(
index.siblingAtColumn(4), QtCore.QItemSelectionModel.Select
)
selection_model.select(index.siblingAtColumn(1), QtCore.QItemSelectionModel.Select)
selection_model.select(index.siblingAtColumn(2), QtCore.QItemSelectionModel.Select)
selection_model.select(index.siblingAtColumn(3), QtCore.QItemSelectionModel.Select)
selection_model.select(index.siblingAtColumn(4), QtCore.QItemSelectionModel.Select)
break
row += 1
@@ -260,9 +252,7 @@ class ChangeBranchDialogModel(QtCore.QAbstractTableModel):
"Table header for git ref type (e.g. either Tag or Branch)",
)
elif section == 1:
return translate(
"AddonsInstaller", "Local name", "Table header for git ref name"
)
return translate("AddonsInstaller", "Local name", "Table header for git ref name")
elif section == 2:
return translate(
"AddonsInstaller",

View File

@@ -74,7 +74,7 @@ class Ui_CompactView(object):
# setupUi
def retranslateUi(self, CompactView):
# CompactView.setWindowTitle(QCoreApplication.translate("CompactView", "Form", None))
# CompactView.setWindowTitle(QCoreApplication.translate("CompactView", "Form", None))
self.labelIcon.setText(QCoreApplication.translate("CompactView", "Icon", None))
self.labelPackageName.setText(
QCoreApplication.translate("CompactView", "<b>Package Name</b>", None)

View File

@@ -113,7 +113,7 @@ class Ui_ExpandedView(object):
# setupUi
def retranslateUi(self, ExpandedView):
# ExpandedView.setWindowTitle(QCoreApplication.translate("ExpandedView", "Form", None))
# ExpandedView.setWindowTitle(QCoreApplication.translate("ExpandedView", "Form", None))
self.labelIcon.setText(QCoreApplication.translate("ExpandedView", "Icon", None))
self.labelPackageName.setText(
QCoreApplication.translate("ExpandedView", "<h1>Package Name</h1>", None)

View File

@@ -44,9 +44,7 @@ def ask_to_install_toolbar_button(repo: Addon) -> None:
os.path.join(os.path.dirname(__file__), "add_toolbar_button_dialog.ui")
)
add_toolbar_button_dialog.setWindowFlag(QtCore.Qt.WindowStaysOnTopHint, True)
add_toolbar_button_dialog.buttonYes.clicked.connect(
lambda: install_toolbar_button(repo)
)
add_toolbar_button_dialog.buttonYes.clicked.connect(lambda: install_toolbar_button(repo))
add_toolbar_button_dialog.buttonNever.clicked.connect(
lambda: pref.SetBool("dontShowAddMacroButtonDialog", True)
)
@@ -58,9 +56,7 @@ def check_for_button(repo: Addon) -> bool:
command = FreeCADGui.Command.findCustomCommand(repo.macro.filename)
if not command:
return False
custom_toolbars = FreeCAD.ParamGet(
"User parameter:BaseApp/Workbench/Global/Toolbar"
)
custom_toolbars = FreeCAD.ParamGet("User parameter:BaseApp/Workbench/Global/Toolbar")
toolbar_groups = custom_toolbars.GetGroups()
for group in toolbar_groups:
toolbar = custom_toolbars.GetGroup(group)
@@ -88,9 +84,7 @@ def ask_for_toolbar(repo: Addon, custom_toolbars) -> object:
select_toolbar_dialog.comboBox.clear()
for group in custom_toolbars:
ref = FreeCAD.ParamGet(
"User parameter:BaseApp/Workbench/Global/Toolbar/" + group
)
ref = FreeCAD.ParamGet("User parameter:BaseApp/Workbench/Global/Toolbar/" + group)
name = ref.GetString("Name", "")
if name:
select_toolbar_dialog.comboBox.addItem(name)
@@ -114,9 +108,7 @@ def ask_for_toolbar(repo: Addon, custom_toolbars) -> object:
return None
# If none of the above code returned...
custom_toolbar_name = pref.GetString(
"CustomToolbarName", "Auto-Created Macro Toolbar"
)
custom_toolbar_name = pref.GetString("CustomToolbarName", "Auto-Created Macro Toolbar")
toolbar = get_toolbar_with_name(custom_toolbar_name)
if not toolbar:
# They told us not to ask, but then the toolbar got deleted... ask anyway!
@@ -131,9 +123,7 @@ def get_toolbar_with_name(name: str) -> object:
top_group = FreeCAD.ParamGet("User parameter:BaseApp/Workbench/Global/Toolbar")
custom_toolbars = top_group.GetGroups()
for toolbar in custom_toolbars:
group = FreeCAD.ParamGet(
"User parameter:BaseApp/Workbench/Global/Toolbar/" + toolbar
)
group = FreeCAD.ParamGet("User parameter:BaseApp/Workbench/Global/Toolbar/" + toolbar)
group_name = group.GetString("Name", "")
if group_name == name:
return group
@@ -185,9 +175,7 @@ def install_toolbar_button(repo: Addon) -> None:
"""If the user has requested a toolbar button be installed, this function is called
to continue the process and request any additional required information."""
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
custom_toolbar_name = pref.GetString(
"CustomToolbarName", "Auto-Created Macro Toolbar"
)
custom_toolbar_name = pref.GetString("CustomToolbarName", "Auto-Created Macro Toolbar")
# Default to false here: if the variable hasn't been set, we don't assume
# that we have to ask, because the simplest is to just create a new toolbar
@@ -226,9 +214,7 @@ def install_toolbar_button(repo: Addon) -> None:
if custom_toolbar:
install_macro_to_toolbar(repo, custom_toolbar)
else:
FreeCAD.Console.PrintMessage(
"In the end, no custom toolbar was set, bailing out\n"
)
FreeCAD.Console.PrintMessage("In the end, no custom toolbar was set, bailing out\n")
def install_macro_to_toolbar(repo: Addon, toolbar: object) -> None:
@@ -255,9 +241,7 @@ def install_macro_to_toolbar(repo: Addon, toolbar: object) -> None:
pixmapText = os.path.normpath(os.path.join(macro_repo_dir, repo.macro.icon))
elif repo.macro.xpm:
macro_repo_dir = FreeCAD.getUserMacroDir(True)
icon_file = os.path.normpath(
os.path.join(macro_repo_dir, repo.macro.name + "_icon.xpm")
)
icon_file = os.path.normpath(os.path.join(macro_repo_dir, repo.macro.name + "_icon.xpm"))
with open(icon_file, "w", encoding="utf-8") as f:
f.write(repo.macro.xpm)
pixmapText = icon_file
@@ -288,9 +272,7 @@ def remove_custom_toolbar_button(repo: Addon) -> None:
command = FreeCADGui.Command.findCustomCommand(repo.macro.filename)
if not command:
return
custom_toolbars = FreeCAD.ParamGet(
"User parameter:BaseApp/Workbench/Global/Toolbar"
)
custom_toolbars = FreeCAD.ParamGet("User parameter:BaseApp/Workbench/Global/Toolbar")
toolbar_groups = custom_toolbars.GetGroups()
for group in toolbar_groups:
toolbar = custom_toolbars.GetGroup(group)

View File

@@ -134,12 +134,8 @@ class PythonPackageManager:
def process(self):
"""Execute this object."""
try:
self.all_packages_stdout = call_pip(
["list", "--path", self.vendor_path]
)
self.outdated_packages_stdout = call_pip(
["list", "-o", "--path", self.vendor_path]
)
self.all_packages_stdout = call_pip(["list", "--path", self.vendor_path])
self.outdated_packages_stdout = call_pip(["list", "-o", "--path", self.vendor_path])
except PipFailed as e:
FreeCAD.Console.PrintError(str(e) + "\n")
self.error.emit(str(e))
@@ -197,9 +193,7 @@ class PythonPackageManager:
self.dlg.tableWidget.setItem(
0,
0,
QtWidgets.QTableWidgetItem(
translate("AddonsInstaller", "Processing, please wait...")
),
QtWidgets.QTableWidgetItem(translate("AddonsInstaller", "Processing, please wait...")),
)
self.dlg.tableWidget.horizontalHeader().setSectionResizeMode(
0, QtWidgets.QHeaderView.ResizeToContents
@@ -230,9 +224,7 @@ class PythonPackageManager:
dependencies.append(addon["name"] + "*")
else:
dependencies.append(addon["name"])
self.dlg.tableWidget.setItem(
counter, 0, QtWidgets.QTableWidgetItem(package_name)
)
self.dlg.tableWidget.setItem(counter, 0, QtWidgets.QTableWidgetItem(package_name))
self.dlg.tableWidget.setItem(
counter,
1,
@@ -249,13 +241,9 @@ class PythonPackageManager:
QtWidgets.QTableWidgetItem(", ".join(dependencies)),
)
if len(package_details["available_version"]) > 0:
updateButtons.append(
QtWidgets.QPushButton(translate("AddonsInstaller", "Update"))
)
updateButtons.append(QtWidgets.QPushButton(translate("AddonsInstaller", "Update")))
updateButtons[-1].setIcon(QtGui.QIcon(":/icons/button_up.svg"))
updateButtons[-1].clicked.connect(
partial(self._update_package, package_name)
)
updateButtons[-1].clicked.connect(partial(self._update_package, package_name))
self.dlg.tableWidget.setCellWidget(counter, 4, updateButtons[-1])
update_counter += 1
else:
@@ -292,9 +280,7 @@ class PythonPackageManager:
dependent_addons.append({"name": addon.name, "optional": True})
return dependent_addons
def _parse_pip_list_output(
self, all_packages, outdated_packages
) -> Dict[str, Dict[str, str]]:
def _parse_pip_list_output(self, all_packages, outdated_packages) -> Dict[str, Dict[str, str]]:
"""Parses the output from pip into a dictionary with update information in it. The pip
output should be an array of lines of text."""
@@ -350,17 +336,13 @@ class PythonPackageManager:
self.dlg.tableWidget.setItem(
line,
2,
QtWidgets.QTableWidgetItem(
translate("AddonsInstaller", "Updating...")
),
QtWidgets.QTableWidgetItem(translate("AddonsInstaller", "Updating...")),
)
break
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
try:
call_pip(
["install", "--upgrade", package_name, "--target", self.vendor_path]
)
call_pip(["install", "--upgrade", package_name, "--target", self.vendor_path])
self._create_list_from_pip()
except PipFailed as e:
FreeCAD.Console.PrintError(str(e) + "\n")
@@ -373,8 +355,7 @@ class PythonPackageManager:
for package_name, package_details in package_list.items():
if (
len(package_details["available_version"]) > 0
and package_details["available_version"]
!= package_details["installed_version"]
and package_details["available_version"] != package_details["installed_version"]
):
updates.append(package_name)
@@ -389,9 +370,7 @@ class PythonPackageManager:
migrated = False
old_directory = os.path.join(
FreeCAD.getUserAppDataDir(), "AdditionalPythonPackages"
)
old_directory = os.path.join(FreeCAD.getUserAppDataDir(), "AdditionalPythonPackages")
new_directory = utils.get_pip_target_directory()
new_directory_name = new_directory.rsplit(os.path.sep, 1)[1]
@@ -420,12 +399,8 @@ class PythonPackageManager:
sys.path.append(new_directory)
cls._add_current_python_version()
with open(
os.path.join(old_directory, "MIGRATION_COMPLETE"), "w", encoding="utf-8"
) as f:
f.write(
"Files originally installed in this directory have been migrated to:\n"
)
with open(os.path.join(old_directory, "MIGRATION_COMPLETE"), "w", encoding="utf-8") as f:
f.write("Files originally installed in this directory have been migrated to:\n")
f.write(new_directory)
f.write(
"\nThe existence of this file prevents the Addon Manager from "

View File

@@ -91,9 +91,7 @@ class PackageDetails(QtWidgets.QWidget):
self.ui.buttonInstall.clicked.connect(lambda: self.install.emit(self.repo))
self.ui.buttonUninstall.clicked.connect(lambda: self.uninstall.emit(self.repo))
self.ui.buttonUpdate.clicked.connect(lambda: self.update.emit(self.repo))
self.ui.buttonCheckForUpdate.clicked.connect(
lambda: self.check_for_update.emit(self.repo)
)
self.ui.buttonCheckForUpdate.clicked.connect(lambda: self.check_for_update.emit(self.repo))
self.ui.buttonChangeBranch.clicked.connect(self.change_branch_clicked)
self.ui.buttonEnable.clicked.connect(self.enable_clicked)
self.ui.buttonDisable.clicked.connect(self.disable_clicked)
@@ -125,13 +123,9 @@ class PackageDetails(QtWidgets.QWidget):
self.ui.webView.setHtml("<html><body>Loading...</body></html>")
self.ui.webView.hide()
self.ui.progressBar.show()
self.timeout = QtCore.QTimer.singleShot(
6000, self.long_load_running
) # Six seconds
self.timeout = QtCore.QTimer.singleShot(6000, self.long_load_running) # Six seconds
else:
self.ui.missingWebViewLabel.setStyleSheet(
"color:" + utils.warning_color_string()
)
self.ui.missingWebViewLabel.setStyleSheet("color:" + utils.warning_color_string())
if self.worker is not None:
if not self.worker.isFinished():
@@ -157,9 +151,7 @@ class PackageDetails(QtWidgets.QWidget):
self.status_create_addon_list_worker.deleteLater
)
self.check_for_update.connect(self.status_create_addon_list_worker.do_work)
self.status_create_addon_list_worker.update_status.connect(
self.display_repo_status
)
self.status_create_addon_list_worker.update_status.connect(self.display_repo_status)
self.status_update_thread.start()
self.check_for_update.emit(self.repo)
@@ -182,9 +174,9 @@ class PackageDetails(QtWidgets.QWidget):
)
if version and date:
installed_version_string += (
translate(
"AddonsInstaller", "Version {version} installed on {date}"
).format(version=version, date=date)
translate("AddonsInstaller", "Version {version} installed on {date}").format(
version=version, date=date
)
+ ". "
)
elif version:
@@ -196,9 +188,7 @@ class PackageDetails(QtWidgets.QWidget):
translate("AddonsInstaller", "Installed on {date}") + ". "
).format(date=date)
else:
installed_version_string += (
translate("AddonsInstaller", "Installed") + ". "
)
installed_version_string += translate("AddonsInstaller", "Installed") + ". "
if status == Addon.Status.UPDATE_AVAILABLE:
if repo.metadata:
@@ -214,9 +204,7 @@ class PackageDetails(QtWidgets.QWidget):
installed_version_string += ".</b>"
elif repo.macro and repo.macro.version:
installed_version_string += (
"<b>"
+ translate("AddonsInstaller", "Update available to version")
+ " "
"<b>" + translate("AddonsInstaller", "Update available to version") + " "
)
installed_version_string += repo.macro.version
installed_version_string += ".</b>"
@@ -258,10 +246,7 @@ class PackageDetails(QtWidgets.QWidget):
)
elif status == Addon.Status.PENDING_RESTART:
installed_version_string += (
translate(
"AddonsInstaller", "Updated, please restart FreeCAD to use"
)
+ "."
translate("AddonsInstaller", "Updated, please restart FreeCAD to use") + "."
)
elif status == Addon.Status.UNCHECKED:
pref = fci.ParamGet("User parameter:BaseApp/Preferences/Addons")
@@ -272,20 +257,15 @@ class PackageDetails(QtWidgets.QWidget):
)
else:
installed_version_string += (
translate("AddonsInstaller", "Automatic update checks disabled")
+ "."
translate("AddonsInstaller", "Automatic update checks disabled") + "."
)
installed_version_string += "</h3>"
self.ui.labelPackageDetails.setText(installed_version_string)
if repo.status() == Addon.Status.UPDATE_AVAILABLE:
self.ui.labelPackageDetails.setStyleSheet(
"color:" + utils.attention_color_string()
)
self.ui.labelPackageDetails.setStyleSheet("color:" + utils.attention_color_string())
else:
self.ui.labelPackageDetails.setStyleSheet(
"color:" + utils.bright_color_string()
)
self.ui.labelPackageDetails.setStyleSheet("color:" + utils.bright_color_string())
self.ui.labelPackageDetails.show()
if repo.macro is not None:
@@ -340,13 +320,9 @@ class PackageDetails(QtWidgets.QWidget):
if repo.obsolete:
self.ui.labelWarningInfo.show()
self.ui.labelWarningInfo.setText(
"<h1>"
+ translate("AddonsInstaller", "WARNING: This addon is obsolete")
+ "</h1>"
)
self.ui.labelWarningInfo.setStyleSheet(
"color:" + utils.warning_color_string()
"<h1>" + translate("AddonsInstaller", "WARNING: This addon is obsolete") + "</h1>"
)
self.ui.labelWarningInfo.setStyleSheet("color:" + utils.warning_color_string())
elif repo.python2:
self.ui.labelWarningInfo.show()
self.ui.labelWarningInfo.setText(
@@ -354,9 +330,7 @@ class PackageDetails(QtWidgets.QWidget):
+ translate("AddonsInstaller", "WARNING: This addon is Python 2 Only")
+ "</h1>"
)
self.ui.labelWarningInfo.setStyleSheet(
"color:" + utils.warning_color_string()
)
self.ui.labelWarningInfo.setStyleSheet("color:" + utils.warning_color_string())
elif required_version:
self.ui.labelWarningInfo.show()
self.ui.labelWarningInfo.setText(
@@ -365,9 +339,7 @@ class PackageDetails(QtWidgets.QWidget):
+ required_version
+ "</h1>"
)
self.ui.labelWarningInfo.setStyleSheet(
"color:" + utils.warning_color_string()
)
self.ui.labelWarningInfo.setStyleSheet("color:" + utils.warning_color_string())
elif repo.is_disabled():
self.ui.labelWarningInfo.show()
self.ui.labelWarningInfo.setText(
@@ -378,9 +350,7 @@ class PackageDetails(QtWidgets.QWidget):
)
+ "</h2>"
)
self.ui.labelWarningInfo.setStyleSheet(
"color:" + utils.warning_color_string()
)
self.ui.labelWarningInfo.setStyleSheet("color:" + utils.warning_color_string())
else:
self.ui.labelWarningInfo.hide()
@@ -396,9 +366,7 @@ class PackageDetails(QtWidgets.QWidget):
# it's possible that this package actually provides versions of itself
# for newer and older versions
first_supported_version = get_first_supported_freecad_version(
self.repo.metadata
)
first_supported_version = get_first_supported_freecad_version(self.repo.metadata)
if first_supported_version is not None:
fc_version = Version(from_list=fci.Version())
if first_supported_version > fc_version:
@@ -504,9 +472,7 @@ class PackageDetails(QtWidgets.QWidget):
else:
self.ui.urlBar.setText(
"("
+ translate(
"AddonsInstaller", "No URL or wiki page provided by this macro"
)
+ translate("AddonsInstaller", "No URL or wiki page provided by this macro")
+ ")"
)
else:
@@ -517,9 +483,7 @@ class PackageDetails(QtWidgets.QWidget):
else:
self.ui.textBrowserReadMe.setHtml(
"("
+ translate(
"AddonsInstaller", "No URL or wiki page provided by this macro"
)
+ translate("AddonsInstaller", "No URL or wiki page provided by this macro")
+ ")"
)
@@ -622,9 +586,9 @@ class PackageDetails(QtWidgets.QWidget):
def show_error_for(self, url: QtCore.QUrl) -> None:
"""Displays error information."""
m = translate(
"AddonsInstaller", "Could not load README data from URL {}"
).format(url.toString())
m = translate("AddonsInstaller", "Could not load README data from URL {}").format(
url.toString()
)
html = f"<html><body><p>{m}</p></body></html>"
self.ui.webView.setHtml(html)
@@ -670,9 +634,7 @@ class PackageDetails(QtWidgets.QWidget):
)
+ "</h3>"
)
self.ui.labelWarningInfo.setStyleSheet(
"color:" + utils.attention_color_string()
)
self.ui.labelWarningInfo.setStyleSheet("color:" + utils.attention_color_string())
def branch_changed(self, name: str) -> None:
"""Displays a dialog confirming the branch changed, and tries to access the
@@ -695,9 +657,7 @@ class PackageDetails(QtWidgets.QWidget):
self.repo.repo_type = Addon.Kind.WORKBENCH
self.repo.metadata = None
self.repo.installed_version = None
self.repo.updated_timestamp = (
QtCore.QDateTime.currentDateTime().toSecsSinceEpoch()
)
self.repo.updated_timestamp = QtCore.QDateTime.currentDateTime().toSecsSinceEpoch()
self.repo.branch = name
self.repo.set_status(Addon.Status.PENDING_RESTART)
@@ -707,9 +667,7 @@ class PackageDetails(QtWidgets.QWidget):
).format(name)
installed_version_string += "</h3>"
self.ui.labelPackageDetails.setText(installed_version_string)
self.ui.labelPackageDetails.setStyleSheet(
"color:" + utils.attention_color_string()
)
self.ui.labelPackageDetails.setStyleSheet("color:" + utils.attention_color_string())
self.update_status.emit(self.repo)
@@ -738,9 +696,7 @@ if HAS_QTWEBENGINE:
requested_url.host() == "wiki.freecad.org"
or requested_url.host() == "wiki.freecad.org"
):
return super().acceptNavigationRequest(
requested_url, _type, isMainFrame
)
return super().acceptNavigationRequest(requested_url, _type, isMainFrame)
QtGui.QDesktopServices.openUrl(requested_url)
self.stored_url = self.url()
QtCore.QTimer.singleShot(0, self._reload_stored_url)
@@ -838,9 +794,7 @@ class Ui_PackageDetails(object):
self.verticalLayout_2.addWidget(self.labelPackageDetails)
self.labelInstallationLocation = QtWidgets.QLabel(PackageDetails)
self.labelInstallationLocation.setTextInteractionFlags(
QtCore.Qt.TextSelectableByMouse
)
self.labelInstallationLocation.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.labelInstallationLocation.hide()
self.verticalLayout_2.addWidget(self.labelInstallationLocation)
@@ -917,9 +871,7 @@ class Ui_PackageDetails(object):
QtCore.QCoreApplication.translate("AddonsInstaller", "Update", None)
)
self.buttonCheckForUpdate.setText(
QtCore.QCoreApplication.translate(
"AddonsInstaller", "Check for Update", None
)
QtCore.QCoreApplication.translate("AddonsInstaller", "Check for Update", None)
)
self.buttonExecute.setText(
QtCore.QCoreApplication.translate("AddonsInstaller", "Run Macro", None)
@@ -934,9 +886,7 @@ class Ui_PackageDetails(object):
QtCore.QCoreApplication.translate("AddonsInstaller", "Disable", None)
)
self.buttonBack.setToolTip(
QtCore.QCoreApplication.translate(
"AddonsInstaller", "Return to package list", None
)
QtCore.QCoreApplication.translate("AddonsInstaller", "Return to package list", None)
)
if not HAS_QTWEBENGINE:
self.missingWebViewLabel.setText(

View File

@@ -116,9 +116,7 @@ class PackageList(QtWidgets.QWidget):
self.item_filter.setHidePy2(pref.GetBool("HidePy2", True))
self.item_filter.setHideObsolete(pref.GetBool("HideObsolete", True))
self.item_filter.setHideNewerFreeCADRequired(
pref.GetBool("HideNewerFreeCADRequired", True)
)
self.item_filter.setHideNewerFreeCADRequired(pref.GetBool("HideNewerFreeCADRequired", True))
def on_listPackages_clicked(self, index: QtCore.QModelIndex):
"""Determine what addon was selected and emit the itemSelected signal with it as
@@ -155,9 +153,7 @@ class PackageList(QtWidgets.QWidget):
"""filter name and description by the regex specified by text_filter"""
if text_filter:
if hasattr(
self.item_filter, "setFilterRegularExpression"
): # Added in Qt 5.12
if hasattr(self.item_filter, "setFilterRegularExpression"): # Added in Qt 5.12
test_regex = QtCore.QRegularExpression(text_filter)
else:
test_regex = QtCore.QRegExp(text_filter)
@@ -171,9 +167,7 @@ class PackageList(QtWidgets.QWidget):
self.ui.labelFilterValidity.setToolTip(
translate("AddonsInstaller", "Filter regular expression is invalid")
)
icon = QtGui.QIcon.fromTheme(
"cancel", QtGui.QIcon(":/icons/edit_Cancel.svg")
)
icon = QtGui.QIcon.fromTheme("cancel", QtGui.QIcon(":/icons/edit_Cancel.svg"))
self.ui.labelFilterValidity.setPixmap(icon.pixmap(16, 16))
self.ui.labelFilterValidity.show()
else:
@@ -227,17 +221,17 @@ class PackageListItemModel(QtCore.QAbstractListModel):
if role == QtCore.Qt.ToolTipRole:
tooltip = ""
if self.repos[row].repo_type == Addon.Kind.PACKAGE:
tooltip = translate(
"AddonsInstaller", "Click for details about package {}"
).format(self.repos[row].display_name)
tooltip = translate("AddonsInstaller", "Click for details about package {}").format(
self.repos[row].display_name
)
elif self.repos[row].repo_type == Addon.Kind.WORKBENCH:
tooltip = translate(
"AddonsInstaller", "Click for details about workbench {}"
).format(self.repos[row].display_name)
elif self.repos[row].repo_type == Addon.Kind.MACRO:
tooltip = translate(
"AddonsInstaller", "Click for details about macro {}"
).format(self.repos[row].display_name)
tooltip = translate("AddonsInstaller", "Click for details about macro {}").format(
self.repos[row].display_name
)
return tooltip
if role == PackageListItemModel.DataAccessRole:
return self.repos[row]
@@ -246,9 +240,7 @@ class PackageListItemModel(QtCore.QAbstractListModel):
"""No header in this implementation: always returns None."""
return None
def setData(
self, index: QtCore.QModelIndex, value, role=QtCore.Qt.EditRole
) -> None:
def setData(self, index: QtCore.QModelIndex, value, role=QtCore.Qt.EditRole) -> None:
"""Set the data for this row. The column of the index is ignored."""
row = index.row()
@@ -290,18 +282,14 @@ class PackageListItemModel(QtCore.QAbstractListModel):
"""Set the status of addon with name to status."""
for row, item in enumerate(self.repos):
if item.name == name:
self.setData(
self.index(row, 0), status, PackageListItemModel.StatusUpdateRole
)
self.setData(self.index(row, 0), status, PackageListItemModel.StatusUpdateRole)
return
def update_item_icon(self, name: str, icon: QtGui.QIcon) -> None:
"""Set the icon for Addon with name to icon"""
for row, item in enumerate(self.repos):
if item.name == name:
self.setData(
self.index(row, 0), icon, PackageListItemModel.IconUpdateRole
)
self.setData(self.index(row, 0), icon, PackageListItemModel.IconUpdateRole)
return
def reload_item(self, repo: Addon) -> None:
@@ -432,9 +420,7 @@ class PackageListItemDelegate(QtWidgets.QStyledItemDelegate):
if self.displayStyle == ListDisplayStyle.EXPANDED:
if repo.macro.author:
caption = translate("AddonsInstaller", "Author")
self.widget.ui.labelMaintainer.setText(
caption + ": " + repo.macro.author
)
self.widget.ui.labelMaintainer.setText(caption + ": " + repo.macro.author)
else:
self.widget.ui.labelMaintainer.setText("")
@@ -454,14 +440,8 @@ class PackageListItemDelegate(QtWidgets.QStyledItemDelegate):
result = translate("AddonsInstaller", "Pending restart")
if repo.is_disabled():
style = (
"style='color:" + utils.warning_color_string() + "; font-weight:bold;'"
)
result += (
f"<span {style}> ["
+ translate("AddonsInstaller", "DISABLED")
+ "]</span>"
)
style = "style='color:" + utils.warning_color_string() + "; font-weight:bold;'"
result += f"<span {style}> [" + translate("AddonsInstaller", "DISABLED") + "]</span>"
return result
@@ -480,15 +460,11 @@ class PackageListItemDelegate(QtWidgets.QStyledItemDelegate):
)
installed_version_string += str(repo.installed_version)
else:
installed_version_string = "<br/>" + translate(
"AddonsInstaller", "Unknown version"
)
installed_version_string = "<br/>" + translate("AddonsInstaller", "Unknown version")
installed_date_string = ""
if repo.updated_timestamp:
installed_date_string = (
"<br/>" + translate("AddonsInstaller", "Installed on") + ": "
)
installed_date_string = "<br/>" + translate("AddonsInstaller", "Installed on") + ": "
installed_date_string += (
QtCore.QDateTime.fromTime_t(repo.updated_timestamp)
.date()
@@ -519,13 +495,9 @@ class PackageListItemDelegate(QtWidgets.QStyledItemDelegate):
result = translate("AddonsInstaller", "Pending restart")
if repo.is_disabled():
style = (
"style='color:" + utils.warning_color_string() + "; font-weight:bold;'"
)
style = "style='color:" + utils.warning_color_string() + "; font-weight:bold;'"
result += (
f"<br/><span {style}>["
+ translate("AddonsInstaller", "DISABLED")
+ "]</span>"
f"<br/><span {style}>[" + translate("AddonsInstaller", "DISABLED") + "]</span>"
)
return result
@@ -623,19 +595,11 @@ class PackageListFilter(QtCore.QSortFilterProxyModel):
return False
# If it's not installed, check to see if it's Py2 only
if (
data.status() == Addon.Status.NOT_INSTALLED
and self.hide_py2
and data.python2
):
if data.status() == Addon.Status.NOT_INSTALLED and self.hide_py2 and data.python2:
return False
# If it's not installed, check to see if it's marked obsolete
if (
data.status() == Addon.Status.NOT_INSTALLED
and self.hide_obsolete
and data.obsolete
):
if data.status() == Addon.Status.NOT_INSTALLED and self.hide_obsolete and data.obsolete:
return False
# If it's not installed, check to see if it's for a newer version of FreeCAD
@@ -664,11 +628,7 @@ class PackageListFilter(QtCore.QSortFilterProxyModel):
return True
if re.match(desc).hasMatch():
return True
if (
data.macro
and data.macro.comment
and re.match(data.macro.comment).hasMatch()
):
if data.macro and data.macro.comment and re.match(data.macro.comment).hasMatch():
return True
for tag in data.tags:
if re.match(tag).hasMatch():
@@ -682,11 +642,7 @@ class PackageListFilter(QtCore.QSortFilterProxyModel):
return True
if re.indexIn(desc) != -1:
return True
if (
data.macro
and data.macro.comment
and re.indexIn(data.macro.comment) != -1
):
if data.macro and data.macro.comment and re.indexIn(data.macro.comment) != -1:
return True
for tag in data.tags:
if re.indexIn(tag) != -1:
@@ -712,9 +668,7 @@ class Ui_PackageList:
self.buttonCompactLayout.setCheckable(True)
self.buttonCompactLayout.setAutoExclusive(True)
self.buttonCompactLayout.setIcon(
QtGui.QIcon.fromTheme(
"expanded_view", QtGui.QIcon(":/icons/compact_view.svg")
)
QtGui.QIcon.fromTheme("expanded_view", QtGui.QIcon(":/icons/compact_view.svg"))
)
self.horizontalLayout_6.addWidget(self.buttonCompactLayout)
@@ -725,9 +679,7 @@ class Ui_PackageList:
self.buttonExpandedLayout.setChecked(True)
self.buttonExpandedLayout.setAutoExclusive(True)
self.buttonExpandedLayout.setIcon(
QtGui.QIcon.fromTheme(
"expanded_view", QtGui.QIcon(":/icons/expanded_view.svg")
)
QtGui.QIcon.fromTheme("expanded_view", QtGui.QIcon(":/icons/expanded_view.svg"))
)
self.horizontalLayout_6.addWidget(self.buttonExpandedLayout)
@@ -791,9 +743,7 @@ class Ui_PackageList:
def retranslateUi(self, _):
self.labelPackagesContaining.setText(
QtCore.QCoreApplication.translate(
"AddonsInstaller", "Show Addons containing:", None
)
QtCore.QCoreApplication.translate("AddonsInstaller", "Show Addons containing:", None)
)
self.comboPackageType.setItemText(
0, QtCore.QCoreApplication.translate("AddonsInstaller", "All", None)
@@ -806,9 +756,7 @@ class Ui_PackageList:
)
self.comboPackageType.setItemText(
3,
QtCore.QCoreApplication.translate(
"AddonsInstaller", "Preference Packs", None
),
QtCore.QCoreApplication.translate("AddonsInstaller", "Preference Packs", None),
)
self.labelStatus.setText(
QtCore.QCoreApplication.translate("AddonsInstaller", "Status:", None)
@@ -827,9 +775,7 @@ class Ui_PackageList:
)
self.comboStatus.setItemText(
StatusFilter.UPDATE_AVAILABLE,
QtCore.QCoreApplication.translate(
"AddonsInstaller", "Update available", None
),
QtCore.QCoreApplication.translate("AddonsInstaller", "Update available", None),
)
self.lineEditFilter.setPlaceholderText(
QtCore.QCoreApplication.translate("AddonsInstaller", "Filter", None)