diff --git a/bl_pkg/__init__.py b/bl_pkg/__init__.py index 07a5d03..78d5359 100644 --- a/bl_pkg/__init__.py +++ b/bl_pkg/__init__.py @@ -180,20 +180,13 @@ def extenion_repos_upgrade(*_): def extenion_url_drop(url): from .bl_extension_ui import ( extension_drop_url_popover, - extension_drop_filepath_popover, + extension_drop_file_popover, ) - wm = bpy.context.window_manager if url.startswith(("http://", "https://", "file://")): - wm.popover( - lambda panel, context: extension_drop_url_popover(panel, context, url), - ui_units_x=14, - ) + extension_drop_url_popover(url) else: - wm.popover( - lambda panel, context: extension_drop_filepath_popover(panel, context, url), - ui_units_x=14, - ) + extension_drop_file_popover(url) # ----------------------------------------------------------------------------- @@ -411,6 +404,19 @@ def register(): default=True, ) + # Use for drag & drop operators. + from .bl_extension_ops import rna_prop_repo_enum_local_only_itemf + WindowManager.extension_local_repos = EnumProperty( + name="Local Repository", + description="Local repository", + items=rna_prop_repo_enum_local_only_itemf, + ) + WindowManager.extension_enable_on_install = BoolProperty( + name="Enable Add-on", + description="Enable on install", + default=True, + ) + from bl_ui.space_userpref import USERPREF_MT_interface_theme_presets USERPREF_MT_interface_theme_presets.append(theme_preset_draw) diff --git a/bl_pkg/bl_extension_ops.py b/bl_pkg/bl_extension_ops.py index 3b6221f..69cf6cd 100644 --- a/bl_pkg/bl_extension_ops.py +++ b/bl_pkg/bl_extension_ops.py @@ -50,10 +50,6 @@ from .bl_extension_utils import ( RepoLockContext, ) -# Enable extensions when they're installed. -USE_ENABLE_ON_INSTALL = True - - rna_prop_url = StringProperty(name="URL", subtype='FILE_PATH', options={'HIDDEN'}) rna_prop_directory = StringProperty(name="Repo Directory", subtype='FILE_PATH') rna_prop_repo_index = IntProperty(name="Repo Index", default=-1) @@ -126,6 +122,13 @@ class CheckSIGINT_Context: # Internal Utilities # +def wm_close_popup_hack(): + # Setting the workspace closes popup, we may want an API for this. + from bpy import context + window = context.window + window.workspace = window.workspace + + def online_user_agent_from_blender(): # NOTE: keep this brief and avoid `platform.platform()` which could identify individual users. # Produces something like this: `Blender/4.2.0 (Linux x86_64; cycle=alpha)` or similar. @@ -1095,10 +1098,20 @@ class BlPkgPkgInstallFiles(Operator, _BlPkgCmdMixIn): description="The local repository to install extensions into", ) + enable_on_install: BoolProperty( + name="Enable Add-on", + description="Enable add-ons after installing", + default=True, + ) + def exec_command_iter(self, is_modal): from .bl_extension_utils import ( pkg_manifest_dict_from_file_or_error, ) + from .bl_extension_ui import ( + extension_drop_file_popover_close_as_needed, + ) + extension_drop_file_popover_close_as_needed() self._addon_restore = [] @@ -1223,7 +1236,7 @@ class BlPkgPkgInstallFiles(Operator, _BlPkgCmdMixIn): pkg_id_sequence_installed = pkg_id_sequence # Install. - if USE_ENABLE_ON_INSTALL: + if self.enable_on_install: import addon_utils # TODO: it would be nice to include this message in the banner. @@ -1272,7 +1285,18 @@ class BlPkgPkgInstall(Operator, _BlPkgCmdMixIn): pkg_id: rna_prop_pkg_id + enable_on_install: BoolProperty( + name="Enable Add-on", + description="Enable add-ons after installing", + default=True, + ) + def exec_command_iter(self, is_modal): + from .bl_extension_ui import ( + extension_drop_url_popover_close_as_needed, + ) + extension_drop_url_popover_close_as_needed() + self._addon_restore = [] directory = _repo_dir_and_index_get(self.repo_index, self.repo_directory, self.report) @@ -1352,7 +1376,7 @@ class BlPkgPkgInstall(Operator, _BlPkgCmdMixIn): ) else: # Install. - if USE_ENABLE_ON_INSTALL: + if self.enable_on_install: import addon_utils # TODO: it would be nice to include this message in the banner. @@ -1678,6 +1702,16 @@ class BlPkgEnableNotInstalled(Operator): return {'CANCELLED'} +class BlPkgPopupCancel(Operator): + """Close the popup""" + bl_idname = "bl_pkg.popup_cancel" + bl_label = "Cancel" + + def execute(self, context): + wm_close_popup_hack() + return {'CANCELLED'} + + # ----------------------------------------------------------------------------- # Register # @@ -1709,6 +1743,7 @@ classes = ( # Dummy, just shows a message. BlPkgEnableNotInstalled, + BlPkgPopupCancel, # Dummy commands (for testing). BlPkgDummyProgress, diff --git a/bl_pkg/bl_extension_ui.py b/bl_pkg/bl_extension_ui.py index 178cc3f..00c4a37 100644 --- a/bl_pkg/bl_extension_ui.py +++ b/bl_pkg/bl_extension_ui.py @@ -9,8 +9,10 @@ Written to allow a UI without modifying Blender. __all__ = ( "display_errors", - "extension_drop_filepath_popover", + "extension_drop_file_popover", + "extension_drop_file_popover_close_as_needed", "extension_drop_url_popover", + "extension_drop_url_popover_close_as_needed", "register", "unregister", ) @@ -113,55 +115,57 @@ def extension_url_find_repo_index_and_pkg_id(url): return -1, "", "", None, None -def extension_drop_url_popover(panel, context, url): - layout = panel.layout +def wm_error(title, body, *, icon): + def fn(panel, context): + layout = panel.layout + layout.label(text=title, icon=icon) + if body: + layout.label(text=body) + wm = bpy.context.window_manager + wm.popover(fn, ui_units_x=14) + + +def extension_drop_url_popover(url): repo_index, repo_name, pkg_id, item_remote, item_local = extension_url_find_repo_index_and_pkg_id(url) if repo_index == -1: - layout.label(text="Extension: URL not found in remote repositories!", icon='ERROR') - layout.label(text=url) + wm_error("Extension: URL not found in remote repositories!", url, icon='ERROR') return if item_local is not None: - layout.label(text="Extension: {:s}".format(pkg_id), icon='INFO') - layout.label(text="Already installed!") + wm_error("Extension: {:s}".format(pkg_id), "Already installed!", icon='ERROR') return - layout.label(text="Install Extension") - layout.separator(type='LINE') - layout.label(text="Do you want to install the following {:s}?".format(item_remote["type"])) - - col = layout.column(align=True) - col.label(text="Name: {:s}".format(item_remote["name"])) - col.label(text="Repository: {:s}".format(repo_name)) - col.label(text="Size: {:s}".format(size_as_fmt_string(item_remote["archive_size"], precision=0))) - del col - - layout.separator() - - # if item_remote["type"] == "add-on": - # # TODO: option to enable. - # pass - - row = layout.row() - - props = row.operator("bl_pkg.pkg_install", text="Install & Enable") - props.repo_index = repo_index - props.pkg_id = pkg_id + USERPREF_PT_extensions_bl_pkg_drop_url.drop_variables = repo_index, repo_name, pkg_id, item_remote + bpy.ops.wm.call_panel(name="USERPREF_PT_extensions_bl_pkg_drop_url", keep_open=True) -def extension_drop_filepath_popover(panel, context, url): - layout = panel.layout +def extension_drop_url_popover_close_as_needed(): + if USERPREF_PT_extensions_bl_pkg_drop_url.drop_variables is None: + return + USERPREF_PT_extensions_bl_pkg_drop_url.drop_variables = None + from .bl_extension_ops import wm_close_popup_hack + wm_close_popup_hack() - # TODO: this UI isn't so nice, ideally the repo can be selected with an OK button. - # This is more complicated than it might seem as calling `bpy.ops.*` doesn't forward errors to the window-manager. - # The API may need to be extended to better support this use-case. - layout.operator_context = 'EXEC_DEFAULT' - layout.label(text="Install from Disk") - layout.separator(type='LINE') - props = layout.operator_menu_enum("bl_pkg.pkg_install_files", "repo", text="Local Repository") - props.filepath = url + +def extension_drop_file_popover(url): + from .bl_extension_ops import repo_iter_valid_local_only + + if not list(repo_iter_valid_local_only(bpy.context)): + wm_error("Error", "No Local Repositories", icon='ERROR') + return + + USERPREF_PT_extensions_bl_pkg_drop_file.drop_variables = url + bpy.ops.wm.call_panel(name="USERPREF_PT_extensions_bl_pkg_drop_file", keep_open=True) + + +def extension_drop_file_popover_close_as_needed(): + if USERPREF_PT_extensions_bl_pkg_drop_file.drop_variables is None: + return + USERPREF_PT_extensions_bl_pkg_drop_file.drop_variables = None + from .bl_extension_ops import wm_close_popup_hack + wm_close_popup_hack() # ----------------------------------------------------------------------------- @@ -794,10 +798,101 @@ def extensions_panel_draw(panel, context): ) +class USERPREF_PT_extensions_bl_pkg_drop_url(Panel): + bl_label = "Drop URL" + + bl_space_type = 'TOPBAR' # dummy. + bl_region_type = 'HEADER' + bl_ui_units_x = 13 + + # WARNING: workaround for not being able to pass arguments to a popup. + drop_variables = None + + def draw(self, context): + layout = self.layout + + repo_index, repo_name, pkg_id, item_remote = USERPREF_PT_extensions_bl_pkg_drop_url.drop_variables + + layout.label(text="Install Extension") + layout.separator(type='LINE') + layout.label(text="Do you want to install the following {:s}?".format(item_remote["type"])) + + col = layout.column(align=True) + col.label(text="Name: {:s}".format(item_remote["name"])) + col.label(text="Repository: {:s}".format(repo_name)) + col.label(text="Size: {:s}".format(size_as_fmt_string(item_remote["archive_size"], precision=0))) + del col + + layout.separator() + + if item_remote["type"] == "add-on": + wm = context.window_manager + layout.prop(wm, "extension_enable_on_install") + enable_on_install = wm.extension_enable_on_install + else: + enable_on_install = False + + layout.separator() + + row = layout.row() + + row.operator("bl_pkg.popup_cancel", text="Cancel") + + props = row.operator("bl_pkg.pkg_install", text="Install") + props.repo_index = repo_index + props.pkg_id = pkg_id + props.enable_on_install = enable_on_install + + +class USERPREF_PT_extensions_bl_pkg_drop_file(Panel): + bl_label = "Drop File" + + bl_space_type = 'TOPBAR' # dummy. + bl_region_type = 'HEADER' + bl_ui_units_x = 13 + + # WARNING: workaround for not being able to pass arguments to a popup. + drop_variables = None + + def draw(self, context): + layout = self.layout + + url = USERPREF_PT_extensions_bl_pkg_drop_file.drop_variables + + # TODO: this UI isn't so nice, ideally the repo can be selected with an OK button. + # This is more complicated than it might seem as calling `bpy.ops.*` doesn't forward errors to the window-manager. + # The API may need to be extended to better support this use-case. + layout.operator_context = 'EXEC_DEFAULT' + layout.label(text="Install from Disk") + layout.separator(type='LINE') + + wm = context.window_manager + layout.label(text="Local Repository") + layout.prop(wm, "extension_local_repos", text="") + + # TODO: inspect the ZIP and find if the type is an add-on. + if True: + wm = context.window_manager + layout.prop(wm, "extension_enable_on_install") + enable_on_install = wm.extension_enable_on_install + + row = layout.row() + + row.operator("bl_pkg.popup_cancel", text="Cancel") + + props = row.operator("bl_pkg.pkg_install_files", text="Install") + props.repo = wm.extension_local_repos + props.filepath = url + props.enable_on_install = enable_on_install + + classes = ( # Pop-overs. USERPREF_PT_extensions_bl_pkg_filter, USERPREF_MT_extensions_bl_pkg_settings, + + USERPREF_PT_extensions_bl_pkg_drop_url, + USERPREF_PT_extensions_bl_pkg_drop_file, )