Addon Manager: Reformat with new Black line length
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -34,4 +34,4 @@ __Help__ = ""
|
||||
__Status__ = ""
|
||||
__Requires__ = ""
|
||||
__Communication__ = ""
|
||||
__Files__ = ""
|
||||
__Files__ = ""
|
||||
|
||||
@@ -25,4 +25,4 @@
|
||||
</preferencepack>
|
||||
</content>
|
||||
|
||||
</package>
|
||||
</package>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -34,4 +34,4 @@ __Help__ = ""
|
||||
__Status__ = ""
|
||||
__Requires__ = ""
|
||||
__Communication__ = ""
|
||||
__Files__ = ""
|
||||
__Files__ = ""
|
||||
|
||||
@@ -22,4 +22,4 @@
|
||||
</workbench>
|
||||
</content>
|
||||
|
||||
</package>
|
||||
</package>
|
||||
|
||||
@@ -19,4 +19,4 @@
|
||||
</macro>
|
||||
</content>
|
||||
|
||||
</package>
|
||||
</package>
|
||||
|
||||
@@ -34,4 +34,4 @@ __Help__ = "HELP"
|
||||
__Status__ = "STATUS"
|
||||
__Requires__ = "REQUIRES"
|
||||
__Communication__ = "COMMUNICATION"
|
||||
__Files__ = "FILES"
|
||||
__Files__ = "FILES"
|
||||
|
||||
@@ -22,4 +22,4 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
# This file contains no metadata
|
||||
# This file contains no metadata
|
||||
|
||||
@@ -21,4 +21,4 @@
|
||||
</preferencepack>
|
||||
</content>
|
||||
|
||||
</package>
|
||||
</package>
|
||||
|
||||
@@ -30,4 +30,4 @@
|
||||
</macro>
|
||||
</content>
|
||||
|
||||
</package>
|
||||
</package>
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
</workbench>
|
||||
</content>
|
||||
|
||||
</package>
|
||||
</package>
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}",
|
||||
|
||||
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
@@ -209,4 +209,4 @@
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -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 |
@@ -142,4 +142,4 @@
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
@@ -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 |
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, _):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"))
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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] + "…"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||