diff --git a/src/Gui/Action.cpp b/src/Gui/Action.cpp index bd0e4113c3..32f5ecb0aa 100644 --- a/src/Gui/Action.cpp +++ b/src/Gui/Action.cpp @@ -800,36 +800,12 @@ void WorkbenchGroup::setWorkbenchData(int index, const QString& wb) void WorkbenchGroup::refreshWorkbenchList() { - QStringList items = Application::Instance->workbenches(); QStringList enabled_wbs_list = DlgSettingsWorkbenchesImp::getEnabledWorkbenches(); - QStringList disabled_wbs_list = DlgSettingsWorkbenchesImp::getDisabledWorkbenches(); - QStringList enable_wbs; - // Go through the list of enabled workbenches and verify that they really exist because - // it might be possible that a workbench has been removed after setting up the list of - // enabled workbenches. - for (const auto& it : enabled_wbs_list) { - int index = items.indexOf(it); - if (index >= 0) { - enable_wbs << it; - items.removeAt(index); - } - } - - // Filter out the actively disabled workbenches - for (const auto& it : disabled_wbs_list) { - int index = items.indexOf(it); - if (index >= 0) { - items.removeAt(index); - } - } - - // Now add the remaining workbenches of 'items'. They have been added to the application - // after setting up the list of enabled workbenches. - enable_wbs.append(items); + // Resize the action group. QList workbenches = groupAction()->actions(); int numActions = workbenches.size(); - int extend = enable_wbs.size() - numActions; + int extend = enabled_wbs_list.size() - numActions; if (extend > 0) { for (int i=0; iaddAction(QLatin1String("")); @@ -840,7 +816,7 @@ void WorkbenchGroup::refreshWorkbenchList() // Show all enabled wb int index = 0; - for (const auto& it : enable_wbs) { + for (const auto& it : enabled_wbs_list) { setWorkbenchData(index++, it); } } diff --git a/src/Gui/ApplicationPy.cpp b/src/Gui/ApplicationPy.cpp index d0d5678179..6b04456f78 100644 --- a/src/Gui/ApplicationPy.cpp +++ b/src/Gui/ApplicationPy.cpp @@ -1377,8 +1377,6 @@ PyObject* Application::sShowPreferences(PyObject * /*self*/, PyObject *args) wc.restoreCursor(); cDlg.exec(); wc.setWaitCursor(); - cDlg.isRebootRequired(); //The user may have applied first, then clicked the cancel button so it's not in the if(cDlg.exec()) - wc.setWaitCursor(); Py_Return; } diff --git a/src/Gui/CommandStd.cpp b/src/Gui/CommandStd.cpp index 9d58e84e72..92bb6b846e 100644 --- a/src/Gui/CommandStd.cpp +++ b/src/Gui/CommandStd.cpp @@ -385,7 +385,6 @@ void StdCmdDlgPreferences::activated(int iMsg) if (cDlg.exec()) { cDlg.activeGroupPage(groupName, index); } - cDlg.isRebootRequired(); //The user may have applied first, then clicked the cancel button so it's not in the if(cDlg.exec()) } //=========================================================================== diff --git a/src/Gui/DlgPreferencesImp.cpp b/src/Gui/DlgPreferencesImp.cpp index 4133b00636..78030622c5 100644 --- a/src/Gui/DlgPreferencesImp.cpp +++ b/src/Gui/DlgPreferencesImp.cpp @@ -71,7 +71,7 @@ DlgPreferencesImp* DlgPreferencesImp::_activeDialog = nullptr; */ DlgPreferencesImp::DlgPreferencesImp(QWidget* parent, Qt::WindowFlags fl) : QDialog(parent, fl), ui(new Ui_DlgPreferences), - invalidParameter(false), canEmbedScrollArea(true), rebootRequired(false) + invalidParameter(false), canEmbedScrollArea(true), restartRequired(false) { ui->setupUi(this); QFontMetrics fm(font()); @@ -337,8 +337,16 @@ void DlgPreferencesImp::accept() { this->invalidParameter = false; applyChanges(); - if (!this->invalidParameter) + if (!this->invalidParameter) { QDialog::accept(); + restartIfRequired(); + } +} + +void DlgPreferencesImp::reject() +{ + QDialog::reject(); + restartIfRequired(); } void DlgPreferencesImp::onButtonBoxClicked(QAbstractButton* btn) @@ -464,7 +472,7 @@ void DlgPreferencesImp::applyChanges() auto page = qobject_cast(tabWidget->widget(j)); if (page) { page->saveSettings(); - rebootRequired = rebootRequired || page->isRebootRequired(); + restartRequired = restartRequired || page->isRestartRequired(); } } } @@ -477,9 +485,9 @@ void DlgPreferencesImp::applyChanges() } } -void DlgPreferencesImp::isRebootRequired() +void DlgPreferencesImp::restartIfRequired() { - if (rebootRequired) { + if (restartRequired) { QMessageBox* restartBox = new QMessageBox(); restartBox->setIcon(QMessageBox::Warning); restartBox->setWindowTitle(tr("Restart required")); @@ -498,6 +506,7 @@ void DlgPreferencesImp::isRebootRequired() QTimer::singleShot(1000, []() { QStringList args = QApplication::arguments(); + args.pop_front(); if (getMainWindow()->close()) QProcess::startDetached(QApplication::applicationFilePath(), args); }); diff --git a/src/Gui/DlgPreferencesImp.h b/src/Gui/DlgPreferencesImp.h index ec3fccfc00..f734128d50 100644 --- a/src/Gui/DlgPreferencesImp.h +++ b/src/Gui/DlgPreferencesImp.h @@ -124,10 +124,10 @@ public: ~DlgPreferencesImp() override; void accept() override; + void reject() override; void reload(); void activateGroupPage(const QString& group, int index); void activeGroupPage(QString& group, int& index) const; - void isRebootRequired(); protected: void changeEvent(QEvent *e) override; @@ -150,6 +150,7 @@ private: void applyChanges(); void restoreDefaults(); QString longestGroupName() const; + void restartIfRequired(); //@} private: @@ -163,7 +164,7 @@ private: std::unique_ptr ui; bool invalidParameter; bool canEmbedScrollArea; - bool rebootRequired; + bool restartRequired; static const int GroupNameRole; /**< A name for our Qt::UserRole, used when storing user data in a list item */ diff --git a/src/Gui/DlgSettingsWorkbenchesImp.cpp b/src/Gui/DlgSettingsWorkbenchesImp.cpp index cfe3638e08..a66ab4b9ed 100644 --- a/src/Gui/DlgSettingsWorkbenchesImp.cpp +++ b/src/Gui/DlgSettingsWorkbenchesImp.cpp @@ -253,7 +253,7 @@ DlgSettingsWorkbenchesImp::~DlgSettingsWorkbenchesImp() void DlgSettingsWorkbenchesImp::saveSettings() { - std::ostringstream enabledStr, disabledStr, autoloadStr; + std::ostringstream orderedStr, disabledStr, autoloadStr; auto addStrToOss = [](std::string wbName, std::ostringstream& oss) { if (!oss.str().empty()) @@ -268,7 +268,7 @@ void DlgSettingsWorkbenchesImp::saveSettings() std::string wbName = wbItem->objectName().toStdString(); if (wbItem->isEnabled()) { - addStrToOss(wbName, enabledStr); + addStrToOss(wbName, orderedStr); } else { addStrToOss(wbName, disabledStr); @@ -279,14 +279,17 @@ void DlgSettingsWorkbenchesImp::saveSettings() } } - if (enabledStr.str().empty()) //make sure that we have at least one enabled workbench. - enabledStr << "NoneWorkbench"; + if (orderedStr.str().empty()) //make sure that we have at least one enabled workbench. This should not be necessary because startup wb cannot be disabled. + orderedStr << "NoneWorkbench"; else { - addStrToOss("NoneWorkbench", disabledStr); //Note, NoneWorkbench is not in the table so it's not added before. + if (!disabledStr.str().empty()) + disabledStr << ","; + disabledStr << "NoneWorkbench"; } - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Workbenches"); - hGrp->SetASCII("Enabled", enabledStr.str().c_str()); + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); + hGrp->SetASCII("Ordered", orderedStr.str().c_str()); hGrp->SetASCII("Disabled", disabledStr.str().c_str()); App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> @@ -342,26 +345,16 @@ void DlgSettingsWorkbenchesImp::buildWorkbenchList() { QSignalBlocker sigblk(ui->wbList); - QStringList workbenches = Application::Instance->workbenches(); QStringList enabledWbs = getEnabledWorkbenches(); QStringList disabledWbs = getDisabledWorkbenches(); //First we add the enabled wbs in their saved order. for (const auto& wbName : enabledWbs) { - if (workbenches.contains(wbName)) { - addWorkbench(wbName, true); - } - else { - Base::Console().Warning("Ignoring unknown %s workbench found in user preferences.", wbName.toStdString().c_str()); - } + addWorkbench(wbName, true); } - //Second we add workbench in alphabetical order that are either Disabled, or !enabled && !disabled, ie newly added wb. - for (const auto& wbName : workbenches) { - if (disabledWbs.contains(wbName)) { - addWorkbench(wbName, false); - } - else if (!enabledWbs.contains(wbName)) { - Base::Console().Warning("Adding unknown %s workbench.", wbName.toStdString().c_str()); + //Second we add workbenches that are disabled in alphabetical order. + for (const auto& wbName : disabledWbs) { + if (wbName.toStdString() != "NoneWorkbench") { addWorkbench(wbName, false); } } @@ -369,9 +362,6 @@ void DlgSettingsWorkbenchesImp::buildWorkbenchList() void DlgSettingsWorkbenchesImp::addWorkbench(const QString& wbName, bool enabled) { - if (wbName.toStdString() == "NoneWorkbench") - return; // Do not list the default empty Workbench - bool isStartupWb = wbName.toStdString() == _startupModule; bool autoLoad = std::find(_backgroundAutoloadedModules.begin(), _backgroundAutoloadedModules.end(), wbName.toStdString()) != _backgroundAutoloadedModules.end(); @@ -383,47 +373,72 @@ void DlgSettingsWorkbenchesImp::addWorkbench(const QString& wbName, bool enabled ui->wbList->setItemWidget(wItem, widget); } - QStringList DlgSettingsWorkbenchesImp::getEnabledWorkbenches() { - QString enabled_wbs; + QStringList disabled_wbs_list = getDisabledWorkbenches(); QStringList enabled_wbs_list; + QStringList wbs_ordered_list; + QString wbs_ordered; ParameterGrp::handle hGrp; - QString allWorkbenches = QString::fromLatin1("ALL"); - hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Workbenches"); - enabled_wbs = QString::fromStdString(hGrp->GetASCII("Enabled", allWorkbenches.toStdString().c_str()).c_str()); + hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); + wbs_ordered = QString::fromStdString(hGrp->GetASCII("Ordered", "")); #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) - enabled_wbs_list = enabled_wbs.split(QLatin1String(","), Qt::SkipEmptyParts); + wbs_ordered_list = wbs_ordered.split(QLatin1String(","), Qt::SkipEmptyParts); #else - enabled_wbs_list = enabled_wbs.split(QLatin1String(","), QString::SkipEmptyParts); + wbs_ordered_list = wbs_ordered.split(QLatin1String(","), QString::SkipEmptyParts); #endif - if (enabled_wbs_list.at(0) == allWorkbenches) { - enabled_wbs_list.removeFirst(); - QStringList workbenches = Application::Instance->workbenches(); - for (QStringList::Iterator it = workbenches.begin(); it != workbenches.end(); ++it) { - enabled_wbs_list.append(*it); + QStringList workbenches = Application::Instance->workbenches(); + workbenches.sort(); + + //First we add the wb that are ordered. + for(auto& wbName : wbs_ordered_list) { + if (workbenches.contains(wbName) && !disabled_wbs_list.contains(wbName)) { //Some wb may have been removed + enabled_wbs_list.append(wbName); + } + else { + Base::Console().Log("Ignoring unknown %s workbench found in user preferences.\n", wbName.toStdString().c_str()); } - enabled_wbs_list.sort(); } + + //Then we add the wbs that are not ordered and not disabled in alphabetical order + for(auto& wbName : workbenches) { + if (!enabled_wbs_list.contains(wbName) && !disabled_wbs_list.contains(wbName)) + enabled_wbs_list.append(wbName); + } + return enabled_wbs_list; } QStringList DlgSettingsWorkbenchesImp::getDisabledWorkbenches() { QString disabled_wbs; + QStringList unfiltered_disabled_wbs_list; QStringList disabled_wbs_list; ParameterGrp::handle hGrp; - hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Workbenches"); - disabled_wbs = QString::fromStdString(hGrp->GetASCII("Disabled", "")); + hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); + disabled_wbs = QString::fromStdString(hGrp->GetASCII("Disabled", "NoneWorkbench,TestWorkbench")); #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) - disabled_wbs_list = disabled_wbs.split(QLatin1String(","), Qt::SkipEmptyParts); + unfiltered_disabled_wbs_list = disabled_wbs.split(QLatin1String(","), Qt::SkipEmptyParts); #else - disabled_wbs_list = disabled_wbs.split(QLatin1String(","), QString::SkipEmptyParts); + unfiltered_disabled_wbs_list = disabled_wbs.split(QLatin1String(","), QString::SkipEmptyParts); #endif + QStringList workbenches = Application::Instance->workbenches(); + + for (auto& wbName : unfiltered_disabled_wbs_list) { + if (workbenches.contains(wbName)) { //Some wb may have been removed + disabled_wbs_list.append(wbName); + } + else { + Base::Console().Log("Ignoring unknown %s workbench found in user preferences.\n", wbName.toStdString().c_str()); + } + } + + disabled_wbs_list.sort(); + return disabled_wbs_list; } @@ -461,7 +476,7 @@ void DlgSettingsWorkbenchesImp::loadWorkbenchSelector() void DlgSettingsWorkbenchesImp::wbToggled(const QString& wbName, bool enabled) { - requireReboot(); + requireRestart(); setStartWorkbenchComboItems(); @@ -535,7 +550,7 @@ void DlgSettingsWorkbenchesImp::setStartWorkbenchComboItems() void DlgSettingsWorkbenchesImp::wbItemMoved() { - requireReboot(); + requireRestart(); for (int i = 0; i < ui->wbList->count(); i++) { wbListItem* wbItem = dynamic_cast(ui->wbList->itemWidget(ui->wbList->item(i))); if (wbItem) { @@ -563,13 +578,13 @@ void DlgSettingsWorkbenchesImp::onStartWbChanged(int index) void DlgSettingsWorkbenchesImp::onWbSelectorChanged(int index) { Q_UNUSED(index); - requireReboot(); + requireRestart(); } void DlgSettingsWorkbenchesImp::onWbByTabToggled(bool val) { Q_UNUSED(val); - requireReboot(); + requireRestart(); } #include "moc_DlgSettingsWorkbenchesImp.cpp" diff --git a/src/Gui/PropertyPage.cpp b/src/Gui/PropertyPage.cpp index 19b7e0916f..704c08cc4a 100644 --- a/src/Gui/PropertyPage.cpp +++ b/src/Gui/PropertyPage.cpp @@ -98,7 +98,7 @@ void PropertyPage::onReset() // ---------------------------------------------------------------- /** Construction */ -PreferencePage::PreferencePage(QWidget* parent) : QWidget(parent), rebootRequired(false) +PreferencePage::PreferencePage(QWidget* parent) : QWidget(parent), restartRequired(false) { } @@ -107,14 +107,14 @@ void PreferencePage::changeEvent(QEvent* event) QWidget::changeEvent(event); } -bool PreferencePage::isRebootRequired() +bool PreferencePage::isRestartRequired() const { - return rebootRequired; + return restartRequired; } -void PreferencePage::requireReboot() +void PreferencePage::requireRestart() { - rebootRequired = true; + restartRequired = true; } diff --git a/src/Gui/PropertyPage.h b/src/Gui/PropertyPage.h index 702aba60bc..5d17f3bf8f 100644 --- a/src/Gui/PropertyPage.h +++ b/src/Gui/PropertyPage.h @@ -73,8 +73,8 @@ public: explicit PreferencePage(QWidget* parent = nullptr); ~PreferencePage() override = default; - bool isRebootRequired(); - void requireReboot(); + bool isRestartRequired() const; + void requireRestart(); public Q_SLOTS: virtual void loadSettings()=0; @@ -84,7 +84,7 @@ protected: void changeEvent(QEvent* event) override = 0; private: - bool rebootRequired; + bool restartRequired; }; /** Subclass that embeds a form from a UI file. diff --git a/src/Mod/AddonManager/Addon.py b/src/Mod/AddonManager/Addon.py index dc3c2ac843..adb896cd27 100644 --- a/src/Mod/AddonManager/Addon.py +++ b/src/Mod/AddonManager/Addon.py @@ -24,6 +24,7 @@ """ Defines the Addon class to encapsulate information about FreeCAD Addons """ import os +import re from urllib.parse import urlparse from typing import Dict, Set, List, Optional from threading import Lock @@ -592,6 +593,9 @@ class Addon: "The existence of this file prevents FreeCAD from loading this Addon. To re-enable, delete the file." ) + if self.contains_workbench(): + self.disable_workbench() + def enable(self): """Re-enable loading this addon by deleting the stopfile""" @@ -601,6 +605,113 @@ class Addon: except FileNotFoundError: pass + if self.contains_workbench(): + self.enable_workbench() + + def enable_workbench(self): + wbName = self.get_workbench_name() + + # Remove from the list of disabled. + self.remove_from_disabled_wbs(wbName) + + def disable_workbench(self): + pref = fci.ParamGet("User parameter:BaseApp/Preferences/Workbenches") + wbName = self.get_workbench_name() + + # Add the wb to the list of disabled if it was not already + disabled_wbs = pref.GetString("Disabled", "NoneWorkbench,TestWorkbench") + # print(f"start disabling {disabled_wbs}") + disabled_wbs_list = disabled_wbs.split(",") + if not (wbName in disabled_wbs_list): + disabled_wbs += "," + wbName + pref.SetString("Disabled", disabled_wbs) + # print(f"done disabling : {disabled_wbs} \n") + + def desinstall_workbench(self): + pref = fci.ParamGet("User parameter:BaseApp/Preferences/Workbenches") + wbName = self.get_workbench_name() + + # Remove from the list of ordered. + ordered_wbs = pref.GetString("Ordered", "") + # print(f"start remove from ordering {ordered_wbs}") + ordered_wbs_list = ordered_wbs.split(",") + ordered_wbs = "" + for wb in ordered_wbs_list: + if wb != wbName: + if ordered_wbs != "": + ordered_wbs += "," + ordered_wbs += wb + pref.SetString("Ordered", ordered_wbs) + # print(f"end remove from ordering {ordered_wbs}") + + # Remove from the list of disabled. + self.remove_from_disabled_wbs(wbName) + + def remove_from_disabled_wbs(self, wbName: str): + pref = fci.ParamGet("User parameter:BaseApp/Preferences/Workbenches") + + disabled_wbs = pref.GetString("Disabled", "NoneWorkbench,TestWorkbench") + # print(f"start enabling : {disabled_wbs}") + disabled_wbs_list = disabled_wbs.split(",") + disabled_wbs = "" + for wb in disabled_wbs_list: + if wb != wbName: + if disabled_wbs != "": + disabled_wbs += "," + disabled_wbs += wb + pref.SetString("Disabled", disabled_wbs) + # print(f"Done enabling {disabled_wbs} \n") + + def get_workbench_name(self) -> str: + """Find the name of the workbench class (ie the name under which it's + registered in freecad core)'""" + wb_name = "" + + if self.repo_type == Addon.Kind.PACKAGE: + for wb in self.metadata.content[ + "workbench" + ]: # we may have more than one wb. + if wb_name != "": + wb_name += "," + wb_name += wb.classname + if self.repo_type == Addon.Kind.WORKBENCH or wb_name == "": + wb_name = self.try_find_wbname_in_files() + if wb_name == "": + wb_name = self.name + return wb_name + + def try_find_wbname_in_files(self) -> str: + """Attempt to locate a line with an addWorkbench command in the workbench's + Python files. If it is directly instantiating a workbench, then we can use + the line to determine classname for this workbench. If it uses a variable, + or if the line doesn't exist at all, an empty string is returned.""" + mod_dir = os.path.join(self.mod_directory, self.name) + + for root, _, files in os.walk(mod_dir): + for f in files: + current_file = os.path.join(root, f) + if not os.path.isdir(current_file): + filename, extension = os.path.splitext(current_file) + if extension == ".py": + wb_classname = self._find_classname_in_file(current_file) + print(f"Current file: {current_file} ") + if wb_classname: + print(f"Found name {wb_classname} \n") + return wb_classname + return "" + + @staticmethod + def _find_classname_in_file(current_file) -> str: + try: + with open(current_file, "r", encoding="utf-8") as python_file: + content = python_file.read() + search_result = re.search(r"Gui.addWorkbench\s*\(\s*(\w+)\s*\(\s*\)\s*\)", content) + if search_result: + return search_result.group(1) + except OSError: + pass + return "" + # @dataclass(frozen) class MissingDependencies: diff --git a/src/Mod/AddonManager/AddonManagerTest/app/test_addon.py b/src/Mod/AddonManager/AddonManagerTest/app/test_addon.py index 1e1c62b828..9f30521637 100644 --- a/src/Mod/AddonManager/AddonManagerTest/app/test_addon.py +++ b/src/Mod/AddonManager/AddonManagerTest/app/test_addon.py @@ -20,7 +20,7 @@ # * . * # * * # *************************************************************************** - +import tempfile import unittest import os import sys @@ -32,14 +32,12 @@ from addonmanager_macro import Macro class TestAddon(unittest.TestCase): - MODULE = "test_addon" # file name without extension def setUp(self): self.test_dir = os.path.join(os.path.dirname(__file__), "..", "data") def test_display_name(self): - # Case 1: No display name set elsewhere: name == display_name addon = Addon( "FreeCAD", @@ -82,7 +80,6 @@ class TestAddon(unittest.TestCase): self.assertEqual(expected_tags, tags) def test_contains_functions(self): - # Test package.xml combinations: # Workbenches @@ -200,7 +197,6 @@ class TestAddon(unittest.TestCase): self.assertTrue(addon.__dict__, second_addon.__dict__) def test_dependency_resolution(self): - addonA = Addon( "AddonA", "https://github.com/FreeCAD/FakeAddonA", @@ -302,3 +298,114 @@ class TestAddon(unittest.TestCase): "TagC" in addon.tags, "Found 'TagA' in tags, it should have been excluded by version requirement", ) + + def test_try_find_wbname_in_files_empty_dir(self): + with tempfile.TemporaryDirectory() as mod_dir: + # Arrange + test_addon = Addon("test") + test_addon.mod_directory = mod_dir + os.mkdir(os.path.join(mod_dir, test_addon.name)) + + # Act + wb_name = test_addon.try_find_wbname_in_files() + + # Assert + self.assertEqual(wb_name, "") + + def test_try_find_wbname_in_files_non_python_ignored(self): + with tempfile.TemporaryDirectory() as mod_dir: + # Arrange + test_addon = Addon("test") + test_addon.mod_directory = mod_dir + base_path = os.path.join(mod_dir, test_addon.name) + os.mkdir(base_path) + file_path = os.path.join(base_path, "test.txt") + with open(file_path, "w", encoding="utf-8") as f: + f.write("Gui.addWorkbench(TestWorkbench())") + + # Act + wb_name = test_addon.try_find_wbname_in_files() + + # Assert + self.assertEqual(wb_name, "") + + def test_try_find_wbname_in_files_simple(self): + with tempfile.TemporaryDirectory() as mod_dir: + # Arrange + test_addon = Addon("test") + test_addon.mod_directory = mod_dir + base_path = os.path.join(mod_dir, test_addon.name) + os.mkdir(base_path) + file_path = os.path.join(base_path, "test.py") + with open(file_path, "w", encoding="utf-8") as f: + f.write("Gui.addWorkbench(TestWorkbench())") + + # Act + wb_name = test_addon.try_find_wbname_in_files() + + # Assert + self.assertEqual(wb_name, "TestWorkbench") + + def test_try_find_wbname_in_files_subdir(self): + with tempfile.TemporaryDirectory() as mod_dir: + # Arrange + test_addon = Addon("test") + test_addon.mod_directory = mod_dir + base_path = os.path.join(mod_dir, test_addon.name) + os.mkdir(base_path) + subdir = os.path.join(base_path, "subdirectory") + os.mkdir(subdir) + file_path = os.path.join(subdir, "test.py") + with open(file_path, "w", encoding="utf-8") as f: + f.write("Gui.addWorkbench(TestWorkbench())") + + # Act + wb_name = test_addon.try_find_wbname_in_files() + + # Assert + self.assertEqual(wb_name, "TestWorkbench") + + def test_try_find_wbname_in_files_variable_used(self): + with tempfile.TemporaryDirectory() as mod_dir: + # Arrange + test_addon = Addon("test") + test_addon.mod_directory = mod_dir + base_path = os.path.join(mod_dir, test_addon.name) + os.mkdir(base_path) + file_path = os.path.join(base_path, "test.py") + with open(file_path, "w", encoding="utf-8") as f: + f.write("Gui.addWorkbench(wb)") + + # Act + wb_name = test_addon.try_find_wbname_in_files() + + # Assert + self.assertEqual(wb_name, "") + + def test_try_find_wbname_in_files_variants(self): + variants = [ + "Gui.addWorkbench(TestWorkbench())", + "Gui.addWorkbench (TestWorkbench())", + "Gui.addWorkbench( TestWorkbench() )", + "Gui.addWorkbench(TestWorkbench( ))", + "Gui.addWorkbench( TestWorkbench( ) )", + "Gui.addWorkbench( TestWorkbench ( ) )", + "Gui.addWorkbench ( TestWorkbench ( ) )", + ] + for variant in variants: + with self.subTest(variant=variant): + with tempfile.TemporaryDirectory() as mod_dir: + # Arrange + test_addon = Addon("test") + test_addon.mod_directory = mod_dir + base_path = os.path.join(mod_dir, test_addon.name) + os.mkdir(base_path) + file_path = os.path.join(base_path, "test.py") + with open(file_path, "w", encoding="utf-8") as f: + f.write(variant) + + # Act + wb_name = test_addon.try_find_wbname_in_files() + + # Assert + self.assertEqual(wb_name, "TestWorkbench") diff --git a/src/Mod/AddonManager/addonmanager_installer.py b/src/Mod/AddonManager/addonmanager_installer.py index 18c493344b..90b652b375 100644 --- a/src/Mod/AddonManager/addonmanager_installer.py +++ b/src/Mod/AddonManager/addonmanager_installer.py @@ -153,6 +153,11 @@ class AddonInstaller(QtCore.QObject): success = self._install_by_git() elif method_to_use == InstallationMethod.COPY: success = self._install_by_copy() + if ( + hasattr(self.addon_to_install, "contains_workbench") + and self.addon_to_install.contains_workbench() + ): + self.addon_to_install.enable_workbench() except utils.ProcessInterrupted: pass if success: diff --git a/src/Mod/AddonManager/addonmanager_uninstaller.py b/src/Mod/AddonManager/addonmanager_uninstaller.py index 87e9ac0812..94082753a5 100644 --- a/src/Mod/AddonManager/addonmanager_uninstaller.py +++ b/src/Mod/AddonManager/addonmanager_uninstaller.py @@ -113,6 +113,11 @@ class AddonUninstaller(QObject): self.run_uninstall_script(path_to_remove) self.remove_extra_files(path_to_remove) success = utils.rmdir(path_to_remove) + if ( + hasattr(self.addon_to_remove, "contains_workbench") + and self.addon_to_remove.contains_workbench() + ): + self.addon_to_remove.desinstall_workbench() except OSError as e: error_message = str(e) else: