From 1a7fcd575efa6120089ce50cf6df59cbbaf39705 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 14 Feb 2022 21:57:56 -0600 Subject: [PATCH] Addon Manager: Auto-create toolbar button When installing a macro, prompt user to install a toolbar button automatically. Fills in the details of the button using the macro's metadata, including an icon if the __icon__ metadata variable points to a file. Also: * Support XPM data for macro icon * Support online icons * Fix bug in macro uninstall * Cleaned up macro code --- src/Mod/AddonManager/AddonManager.py | 44 ++- src/Mod/AddonManager/CMakeLists.txt | 19 +- .../AddonManager/add_toolbar_button_dialog.ui | 105 +++++++ src/Mod/AddonManager/addonmanager_macro.py | 130 +++++++- src/Mod/AddonManager/addonmanager_workers.py | 4 +- src/Mod/AddonManager/install_to_toolbar.py | 278 ++++++++++++++++++ src/Mod/AddonManager/package_details.py | 10 +- src/Mod/AddonManager/select_toolbar_dialog.ui | 87 ++++++ src/Mod/AddonManager/toolbar_button.ui | 56 ++++ 9 files changed, 696 insertions(+), 37 deletions(-) create mode 100644 src/Mod/AddonManager/add_toolbar_button_dialog.ui create mode 100644 src/Mod/AddonManager/install_to_toolbar.py create mode 100644 src/Mod/AddonManager/select_toolbar_dialog.ui create mode 100644 src/Mod/AddonManager/toolbar_button.ui 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 + + + + + + + + + +