diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py
index 53f787ab59..49ea30ace8 100644
--- a/src/Mod/AddonManager/AddonManager.py
+++ b/src/Mod/AddonManager/AddonManager.py
@@ -41,6 +41,10 @@ import AddonManager_rc
from package_list import PackageList, PackageListItemModel
from package_details import PackageDetails
from AddonManagerRepo import AddonManagerRepo
+from install_to_toolbar import (
+ ask_to_install_toolbar_button,
+ remove_custom_toolbar_button,
+)
from NetworkManager import HAVE_QTNETWORK, InitializeNetworkManager
@@ -839,8 +843,27 @@ class CommandAddonManager:
path += "_workbench_icon.svg"
default_icon = QtGui.QIcon(":/icons/document-package.svg")
elif repo.repo_type == AddonManagerRepo.RepoType.MACRO:
- path += "_macro_icon.svg"
- default_icon = QtGui.QIcon(":/icons/document-python.svg")
+ if repo.macro and repo.macro.icon:
+ if os.path.isabs(repo.macro.icon):
+ path = repo.macro.icon
+ default_icon = QtGui.QIcon(":/icons/document-python.svg")
+ else:
+ path = os.path.join(
+ os.path.dirname(repo.macro.src_filename), repo.macro.icon
+ )
+ default_icon = QtGui.QIcon(":/icons/document-python.svg")
+ elif repo.macro and repo.macro.xpm:
+ cache_path = FreeCAD.getUserCachePath()
+ am_path = os.path.join(cache_path, "AddonManager", "MacroIcons")
+ os.makedirs(am_path, exist_ok=True)
+ path = os.path.join(am_path, repo.name + "_icon.xpm")
+ if not os.path.exists(path):
+ with open(path, "w") as f:
+ f.write(repo.macro.xpm)
+ default_icon = QtGui.QIcon(repo.macro.xpm)
+ else:
+ path += "_macro_icon.svg"
+ default_icon = QtGui.QIcon(":/icons/document-python.svg")
elif repo.repo_type == AddonManagerRepo.RepoType.PACKAGE:
# The cache might not have been downloaded yet, check to see if it's there...
if os.path.isfile(repo.get_cached_icon_filename()):
@@ -1399,6 +1422,8 @@ class CommandAddonManager:
repo.set_status(AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE)
self.item_model.reload_item(repo)
self.packageDetails.show_repo(repo)
+ if repo.repo_type == AddonManagerRepo.RepoType.MACRO:
+ ask_to_install_toolbar_button(repo)
def on_installation_failed(self, _: AddonManagerRepo, message: str) -> None:
self.hide_progress_widgets()
@@ -1526,13 +1551,24 @@ class CommandAddonManager:
elif repo.repo_type == AddonManagerRepo.RepoType.MACRO:
macro = repo.macro
if macro.remove():
+ remove_custom_toolbar_button(repo)
+ FreeCAD.Console.PrintMessage(
+ translate("AddonsInstaller", "Successfully uninstalled {}").format(
+ repo.name
+ )
+ + "\n"
+ )
self.item_model.update_item_status(
repo.name, AddonManagerRepo.UpdateStatus.NOT_INSTALLED
)
self.packageDetails.show_repo(repo)
else:
- self.dialog.textBrowserReadMe.setText(
- translate("AddonsInstaller", "Macro could not be removed.")
+ FreeCAD.Console.PrintMessage(
+ translate(
+ "AddonsInstaller",
+ "Failed to uninstall {}. Please remove manually.",
+ ).format(repo.name)
+ + "\n"
)
diff --git a/src/Mod/AddonManager/CMakeLists.txt b/src/Mod/AddonManager/CMakeLists.txt
index dc7bb74ed4..928b9c752b 100644
--- a/src/Mod/AddonManager/CMakeLists.txt
+++ b/src/Mod/AddonManager/CMakeLists.txt
@@ -3,27 +3,30 @@ IF (BUILD_GUI)
ENDIF (BUILD_GUI)
SET(AddonManager_SRCS
- Init.py
- InitGui.py
+ add_toolbar_button_dialog.ui
AddonManager.py
- AddonManagerRepo.py
+ AddonManager.ui
addonmanager_macro.py
addonmanager_utilities.py
addonmanager_workers.py
- AddonManager.ui
AddonManagerOptions.ui
+ AddonManagerRepo.py
ALLOWED_PYTHON_PACKAGES.txt
change_branch.py
change_branch.ui
- first_run.ui
compact_view.py
dependency_resolution_dialog.ui
expanded_view.py
- NetworkManager.py
- package_list.py
- package_details.py
+ first_run.ui
+ Init.py
+ InitGui.py
+ install_to_toolbar.py
loading.html
+ NetworkManager.py
+ package_details.py
+ package_list.py
TestAddonManagerApp.py
+ select_toolbar_dialog.ui
)
IF (BUILD_GUI)
LIST(APPEND AddonManager_SRCS TestAddonManagerGui.py)
diff --git a/src/Mod/AddonManager/add_toolbar_button_dialog.ui b/src/Mod/AddonManager/add_toolbar_button_dialog.ui
new file mode 100644
index 0000000000..bba50c3e53
--- /dev/null
+++ b/src/Mod/AddonManager/add_toolbar_button_dialog.ui
@@ -0,0 +1,105 @@
+
+
+ add_toolbar_button_dialog
+
+
+
+ 0
+ 0
+ 257
+ 62
+
+
+
+ Add button?
+
+
+ -
+
+
+ Add a toolbar button for this macro?
+
+
+ true
+
+
+
+ -
+
+
-
+
+
+ Yes
+
+
+
+ -
+
+
+ No
+
+
+
+ -
+
+
+ Never
+
+
+
+
+
+
+
+
+
+
+ buttonYes
+ clicked()
+ add_toolbar_button_dialog
+ accept()
+
+
+ 47
+ 40
+
+
+ 128
+ 30
+
+
+
+
+ buttonNo
+ clicked()
+ add_toolbar_button_dialog
+ reject()
+
+
+ 128
+ 40
+
+
+ 128
+ 30
+
+
+
+
+ buttonNever
+ clicked()
+ add_toolbar_button_dialog
+ reject()
+
+
+ 209
+ 40
+
+
+ 128
+ 30
+
+
+
+
+
diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py
index aca8cf4f3d..06f0561f88 100644
--- a/src/Mod/AddonManager/addonmanager_macro.py
+++ b/src/Mod/AddonManager/addonmanager_macro.py
@@ -28,11 +28,11 @@ import codecs
import shutil
import time
from urllib.parse import urlparse
-import tempfile
from typing import Dict, Tuple, List, Union
import FreeCAD
import NetworkManager
+from PySide2 import QtCore
translate = FreeCAD.Qt.translate
@@ -68,6 +68,7 @@ class Macro(object):
self.src_filename = ""
self.author = ""
self.icon = ""
+ self.xpm = "" # Possible alternate icon data
self.other_files = []
self.parsed = False
@@ -115,9 +116,9 @@ class Macro(object):
# __Files__
# __Author__
# __Date__
+ # __Icon__
max_lines_to_search = 200
line_counter = 0
- ic = re.IGNORECASE # Shorten the line for Black
string_search_mapping = {
"__comment__": "comment",
@@ -127,23 +128,29 @@ class Macro(object):
"__author__": "author",
"__date__": "date",
"__icon__": "icon",
+ "__xpm__": "xpm",
}
string_search_regex = re.compile(r"\s*(['\"])(.*)\1")
f = io.StringIO(code)
while f and line_counter < max_lines_to_search:
line = f.readline()
+ if not line:
+ break
+ if QtCore.QThread.currentThread().isInterruptionRequested():
+ return
line_counter += 1
- # if not line.startswith("__"):
- # # Speed things up a bit... this comparison is very cheap
- # continue
+ if not line.startswith("__"):
+ # Speed things up a bit... this comparison is very cheap
+ continue
lowercase_line = line.lower()
for key, value in string_search_mapping.items():
if lowercase_line.startswith(key):
_, _, after_equals = line.partition("=")
match = re.match(string_search_regex, after_equals)
- if match:
+ # We do NOT support triple-quoted strings, except for the icon XPM data
+ if match and '"""' not in after_equals:
if type(self.__dict__[value]) == str:
self.__dict__[value] = match.group(2)
elif type(self.__dict__[value]) == list:
@@ -177,6 +184,32 @@ class Macro(object):
)
self.version = str(after_equals).strip()
break
+ elif key == "__icon__" or key == "__xpm__":
+ # If this is an icon, it's possible that the icon was actually directly specified
+ # in the file as XPM data. This data **must** be between triple double quotes in
+ # order for the Addon Manager to recognize it.
+ if '"""' in after_equals:
+ _, _, xpm_data = after_equals.partition('"""')
+ while True:
+ line = f.readline()
+ if not line:
+ FreeCAD.Console.PrintError(
+ translate(
+ "AddonsInstaller",
+ "Syntax error while reading {} from macro {}",
+ ).format(key, self.name)
+ + "\n"
+ )
+ break
+ if '"""' in line:
+ last_line, _, _ = line.partition('"""')
+ xpm_data += last_line
+ break
+ else:
+ xpm_data += line
+ self.xpm = xpm_data
+ break
+
FreeCAD.Console.PrintError(
translate(
"AddonsInstaller",
@@ -273,6 +306,27 @@ class Macro(object):
self.author = self.parse_desc("Author: ")
if not self.date:
self.date = self.parse_desc("Last modified: ")
+ if self.icon.startswith("http://") or self.icon.startswith("https://"):
+ # Technically we don't claim to support this, but some macro authors are
+ # doing it anyway, so let's give it a shot...
+ FreeCAD.Console.PrintMessage(
+ translate(
+ "AddonsInstaller", "Attempting to fetch macro icon from {}"
+ ).format(self.icon)
+ + "\n"
+ )
+ p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(self.icon)
+ if p:
+ cache_path = FreeCAD.getUserCachePath()
+ am_path = os.path.join(cache_path, "AddonManager", "MacroIcons")
+ os.makedirs(am_path, exist_ok=True)
+ _, _, filename = self.icon.rpartition("/")
+ base, _, extension = filename.rpartition(".")
+ constructed_name = os.path.join(am_path, base + "." + extension)
+ with open(constructed_name, "wb") as f:
+ f.write(p.data())
+ self.icon_source = self.icon
+ self.icon = constructed_name
def parse_desc(self, line_start: str) -> Union[str, None]:
components = self.desc.split(">")
@@ -306,15 +360,43 @@ class Macro(object):
# self.src_filename.
base_dir = os.path.dirname(self.src_filename)
warnings = []
+
+ if self.xpm:
+ xpm_file = os.path.join(base_dir, self.name + "_icon.xpm")
+ with open(xpm_file, "w") as f:
+ 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))
+ )
+ try:
+ shutil.copy(self.icon, dst_file)
+ except IOError:
+ warnings.append(f"Failed to copy icon to {dst_file}")
+ elif self.icon not in self.other_files:
+ self.other_files.append(self.icon)
+
for other_file in self.other_files:
- dst_dir = os.path.join(macro_dir, os.path.dirname(other_file))
+ if not other_file:
+ continue
+ if os.path.isabs(other_file):
+ dst_dir = macro_dir
+ else:
+ dst_dir = os.path.join(macro_dir, os.path.dirname(other_file))
if not os.path.isdir(dst_dir):
try:
os.makedirs(dst_dir)
except OSError:
return False, [f"Failed to create {dst_dir}"]
- src_file = os.path.normpath(os.path.join(base_dir, other_file))
- dst_file = os.path.normpath(os.path.join(macro_dir, other_file))
+ 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))
+ )
+ 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))
if not os.path.isfile(src_file):
warnings.append(
translate(
@@ -351,19 +433,35 @@ class Macro(object):
os.remove(macro_path_with_macro_prefix)
# Remove related files, which are supposed to be given relative to
# self.src_filename.
+ if self.xpm:
+ xpm_file = os.path.join(macro_dir, self.name + "_icon.xpm")
+ if os.path.exists(xpm_file):
+ os.remove(xpm_file)
for other_file in self.other_files:
+ if not other_file:
+ continue
+ FreeCAD.Console.PrintMessage(f"{other_file}...")
dst_file = os.path.join(macro_dir, other_file)
+ if not dst_file or not os.path.exists(dst_file):
+ FreeCAD.Console.PrintMessage(f"X\n")
+ continue
try:
os.remove(dst_file)
remove_directory_if_empty(os.path.dirname(dst_file))
+ FreeCAD.Console.PrintMessage("✓\n")
except Exception:
- FreeCAD.Console.PrintWarning(
- translate(
- "AddonsInstaller",
- "Failed to remove macro file '{}': it might not exist, or its permissions changed",
- ).format(dst_file)
- + "\n"
- )
+ FreeCAD.Console.PrintMessage(f"?\n")
+ if os.path.isabs(self.icon):
+ dst_file = os.path.normpath(
+ os.path.join(macro_dir, os.path.basename(self.icon))
+ )
+ if os.path.exists(dst_file):
+ try:
+ FreeCAD.Console.PrintMessage(f"{os.path.basename(self.icon)}...")
+ os.remove(dst_file)
+ FreeCAD.Console.PrintMessage("✓\n")
+ except Exception:
+ FreeCAD.Console.PrintMessage(f"?\n")
return True
diff --git a/src/Mod/AddonManager/addonmanager_workers.py b/src/Mod/AddonManager/addonmanager_workers.py
index dc313a03a3..18af01564b 100644
--- a/src/Mod/AddonManager/addonmanager_workers.py
+++ b/src/Mod/AddonManager/addonmanager_workers.py
@@ -528,7 +528,7 @@ class UpdateChecker:
macro_wrapper.macro.fill_details_from_file(
macro_wrapper.macro.src_filename
)
- if not macro_wrapper.macro.parsed and macro_wrapper.macro.on_wiki:
+ elif not macro_wrapper.macro.parsed and macro_wrapper.macro.on_wiki:
mac = macro_wrapper.macro.name.replace(" ", "_")
mac = mac.replace("&", "%26")
mac = mac.replace("+", "%2B")
@@ -647,7 +647,7 @@ class FillMacroListWorker(QtCore.QThread):
except Exception as e:
FreeCAD.Console.PrintWarning(
translate(
- "AddonsInstaller", "An error occurred fetching macros from GitHub"
+ "AddonsInstaller", "An error occurred updating macros from GitHub"
)
+ f":\n{e}\n"
)
diff --git a/src/Mod/AddonManager/install_to_toolbar.py b/src/Mod/AddonManager/install_to_toolbar.py
new file mode 100644
index 0000000000..6b0e6a9939
--- /dev/null
+++ b/src/Mod/AddonManager/install_to_toolbar.py
@@ -0,0 +1,278 @@
+# ***************************************************************************
+# * *
+# * Copyright (c) 2022 FreeCAD Project Association *
+# * *
+# * This program is free software; you can redistribute it and/or *
+# * modify it under the terms of the GNU Lesser General Public *
+# * License as published by the Free Software Foundation; either *
+# * version 2.1 of the License, or (at your option) any later version. *
+# * *
+# * This program is distributed in the hope that it will be useful, *
+# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+# * Lesser General Public License for more details. *
+# * *
+# * You should have received a copy of the GNU Lesser General Public *
+# * License along with this library; if not, write to the Free Software *
+# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
+# * 02110-1301 USA *
+# * *
+# ***************************************************************************
+
+import FreeCAD
+import FreeCADGui
+from PySide2 import QtCore, QtWidgets
+import AddonManagerRepo
+
+import os
+
+translate = FreeCAD.Qt.translate
+
+
+def ask_to_install_toolbar_button(repo: AddonManagerRepo) -> None:
+ pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
+ do_not_show_dialog = pref.GetBool("dontShowAddMacroButtonDialog", False)
+ if not do_not_show_dialog:
+ add_toolbar_button_dialog = FreeCADGui.PySideUic.loadUi(
+ os.path.join(os.path.dirname(__file__), "add_toolbar_button_dialog.ui")
+ )
+ add_toolbar_button_dialog.buttonYes.clicked.connect(
+ lambda: install_toolbar_button(repo)
+ )
+ add_toolbar_button_dialog.buttonNever.clicked.connect(
+ lambda: pref.SetBool("dontShowAddMacroButtonDialog", True)
+ )
+ add_toolbar_button_dialog.exec()
+
+
+def ask_for_toolbar(
+ repo: AddonManagerRepo, custom_toolbars
+) -> object: # Returns the pref group for the new toolbar
+ pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
+
+ # In this one spot, default True: if this is the first time we got to
+ # this chunk of code, we are always going to ask.
+ ask = pref.GetBool("alwaysAskForToolbar", True)
+
+ if ask:
+ select_toolbar_dialog = FreeCADGui.PySideUic.loadUi(
+ os.path.join(os.path.dirname(__file__), "select_toolbar_dialog.ui")
+ )
+
+ select_toolbar_dialog.comboBox.clear()
+
+ for group in custom_toolbars:
+ ref = FreeCAD.ParamGet(
+ "User parameter:BaseApp/Workbench/Global/Toolbar/" + group
+ )
+ name = ref.GetString("Name", "")
+ if name:
+ select_toolbar_dialog.comboBox.addItem(name)
+ else:
+ FreeCAD.Console.PrintWarning(
+ f"Custom toolbar {group} does not have a Name element\n"
+ )
+ new_menubar_option_text = translate("AddonsInstaller", "Create new toolbar")
+ select_toolbar_dialog.comboBox.addItem(new_menubar_option_text)
+
+ result = select_toolbar_dialog.exec()
+ if result == QtWidgets.QDialog.Accepted:
+ selection = select_toolbar_dialog.comboBox.currentText()
+ if select_toolbar_dialog.checkBox.checkState() == QtCore.Qt.Unchecked:
+ pref.SetBool("alwaysAskForToolbar", False)
+ else:
+ pref.SetBool("alwaysAskForToolbar", True)
+ if selection == new_menubar_option_text:
+ return create_new_custom_toolbar()
+ else:
+ return get_toolbar_with_name(selection)
+ else:
+ return None
+ else:
+ 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!
+ ask = pref.RemBool("alwaysAskForToolbar")
+ return ask_for_toolbar(repo, custom_toolbars)
+ else:
+ return toolbar
+
+
+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_name = group.GetString("Name", "")
+ if group_name == name:
+ return group
+ return None
+
+
+def create_new_custom_toolbar() -> object:
+
+ # We need two names: the name of the auto-created toolbar, as it will be displayed to the
+ # user in various menus, and the underlying name of the toolbar group. Both must be
+ # unique.
+
+ # First, the displayed name
+ custom_toolbar_name = "Auto-Created Macro Toolbar"
+ pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
+ top_group = FreeCAD.ParamGet("User parameter:BaseApp/Workbench/Global/Toolbar")
+ custom_toolbars = top_group.GetGroups()
+ name_taken = check_for_toolbar(custom_toolbars, custom_toolbar_name)
+ if name_taken:
+ i = 2 # Don't use (1), start at (2)
+ while True:
+ test_name = custom_toolbar_name + f" ({i})"
+ if not check_for_toolbar(custom_toolbars, test_name):
+ custom_toolbar_name = test_name
+ i = i + 1
+
+ # Second, the toolbar preference group name
+ i = 1
+ while True:
+ new_group_name = "Custom_" + str(i)
+ if new_group_name not in custom_toolbars:
+ break
+ i = i + 1
+
+ custom_toolbar = FreeCAD.ParamGet(
+ "User parameter:BaseApp/Workbench/Global/Toolbar/" + new_group_name
+ )
+ custom_toolbar.SetString("Name", custom_toolbar_name)
+ custom_toolbar.SetBool("Active", True)
+ return custom_toolbar
+
+
+def check_for_toolbar(custom_toolbars, toolbar_name: str) -> bool:
+ tb = get_toolbar_with_name(toolbar_name)
+ if tb:
+ return True
+ else:
+ return False
+
+
+def install_toolbar_button(repo: AddonManagerRepo) -> None:
+ pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
+ 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
+ # and never ask at all.
+ ask = pref.GetBool("alwaysAskForToolbar", False)
+
+ # See if there is already a custom toolbar for macros:
+ top_group = FreeCAD.ParamGet("User parameter:BaseApp/Workbench/Global/Toolbar")
+ custom_toolbars = top_group.GetGroups()
+ if custom_toolbars:
+ # If there are already custom toolbars, see if one of them is the one we used last time
+ found_toolbar = False
+ for toolbar_name in custom_toolbars:
+ test_toolbar = FreeCAD.ParamGet(
+ "User parameter:BaseApp/Workbench/Global/Toolbar/" + toolbar_name
+ )
+ name = test_toolbar.GetString("Name", "")
+ if name == custom_toolbar_name:
+ custom_toolbar = test_toolbar
+ found_toolbar = True
+ break
+ if ask or not found_toolbar:
+ # We have to ask the user what to do...
+ custom_toolbar = ask_for_toolbar(repo, custom_toolbars)
+ if custom_toolbar:
+ custom_toolbar_name = custom_toolbar.GetString("Name")
+ pref.SetString("CustomToolbarName", custom_toolbar_name)
+ else:
+ # Create a custom toolbar
+ custom_toolbar = FreeCAD.ParamGet(
+ "User parameter:BaseApp/Workbench/Global/Toolbar/Custom_1"
+ )
+ custom_toolbar.SetString("Name", custom_toolbar_name)
+ custom_toolbar.SetBool("Active", True)
+
+ if custom_toolbar:
+ install_macro_to_toolbar(repo, custom_toolbar)
+ else:
+ FreeCAD.Console.PrintMessage(
+ f"In the end, no custom toolbar was set, bailing out\n"
+ )
+
+
+def install_macro_to_toolbar(repo: AddonManagerRepo, toolbar: object) -> None:
+ menuText = repo.display_name
+ tooltipText = f"{repo.display_name}"
+ if repo.macro.comment:
+ tooltipText += f"
{repo.macro.comment}
"
+ whatsThisText = repo.macro.comment
+ else:
+ whatsThisText = translate(
+ "AddonsInstaller", "A macro installed with the FreeCAD Addon Manager"
+ )
+ statustipText = (
+ translate("AddonsInstaller", "Run", "Indicates a macro that can be 'run'")
+ + " "
+ + repo.display_name
+ )
+ if repo.macro.icon:
+ if os.path.isabs(repo.macro.icon):
+ pixmapText = os.path.normpath(repo.macro.icon)
+ else:
+ macro_repo_dir = FreeCAD.getUserMacroDir(True)
+ 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")
+ )
+ with open(icon_file, "w") as f:
+ f.write(repo.macro.xpm)
+ pixmapText = icon_file
+ else:
+ pixmapText = None
+
+ # Add this command to that toolbar
+ command_name = FreeCADGui.Command.createCustomCommand(
+ repo.macro.filename,
+ menuText,
+ tooltipText,
+ whatsThisText,
+ statustipText,
+ pixmapText,
+ )
+ toolbar.SetString(command_name, "FreeCAD")
+
+ # Force the toolbars to be recreated
+ wb = FreeCADGui.activeWorkbench()
+ wb.reloadActive()
+
+
+def remove_custom_toolbar_button(repo: AddonManagerRepo) -> None:
+ """If this repo contains a macro, look through the custom commands and
+ see if one is set up for this macro. If so, remove it, including any
+ toolbar entries."""
+
+ command = FreeCADGui.Command.findCustomCommand(repo.macro.filename)
+ if not command:
+ return
+ 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)
+ if toolbar.GetString(command, "*") != "*":
+ toolbar.RemString(command)
+
+ FreeCADGui.Command.removeCustomCommand(command)
+
+ # Force the toolbars to be recreated
+ wb = FreeCADGui.activeWorkbench()
+ wb.reloadActive()
diff --git a/src/Mod/AddonManager/package_details.py b/src/Mod/AddonManager/package_details.py
index 1220b1b61c..fab2ea8e59 100644
--- a/src/Mod/AddonManager/package_details.py
+++ b/src/Mod/AddonManager/package_details.py
@@ -146,14 +146,10 @@ class PackageDetails(QWidget):
self.show_package(repo)
self.ui.buttonExecute.hide()
- if self.status_update_thread is not None:
- if not self.status_update_thread.isFinished():
- self.status_update_thread.requestInterruption()
- self.status_update_thread.wait()
-
if repo.status() == AddonManagerRepo.UpdateStatus.UNCHECKED:
- self.status_update_thread = QThread()
- self.status_update_worker = CheckSingleUpdateWorker(repo, self)
+ if not self.status_update_thread:
+ self.status_update_thread = QThread()
+ self.status_update_worker = CheckSingleUpdateWorker(repo)
self.status_update_worker.moveToThread(self.status_update_thread)
self.status_update_thread.finished.connect(
self.status_update_worker.deleteLater
diff --git a/src/Mod/AddonManager/select_toolbar_dialog.ui b/src/Mod/AddonManager/select_toolbar_dialog.ui
new file mode 100644
index 0000000000..2dda0cb038
--- /dev/null
+++ b/src/Mod/AddonManager/select_toolbar_dialog.ui
@@ -0,0 +1,87 @@
+
+
+ select_toolbar_dialog
+
+
+
+ 0
+ 0
+ 308
+ 109
+
+
+
+ Select Toolbar
+
+
+ true
+
+
+ true
+
+
+ -
+
+
+ Select a toolbar to add this macro to:
+
+
+
+ -
+
+
+ -
+
+
+ Ask every time
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ select_toolbar_dialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ select_toolbar_dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/src/Mod/AddonManager/toolbar_button.ui b/src/Mod/AddonManager/toolbar_button.ui
new file mode 100644
index 0000000000..5765542490
--- /dev/null
+++ b/src/Mod/AddonManager/toolbar_button.ui
@@ -0,0 +1,56 @@
+
+
+ toolbar_button
+
+
+
+ 0
+ 0
+ 257
+ 62
+
+
+
+ Add button?
+
+
+ -
+
+
+ Add a toolbar button for this macro?
+
+
+ true
+
+
+
+ -
+
+
-
+
+
+ Yes
+
+
+
+ -
+
+
+ No
+
+
+
+ -
+
+
+ Never
+
+
+
+
+
+
+
+
+
+