Clean up action button display, and add enabling/disabling
This commit is contained in:
@@ -57,6 +57,7 @@ class ConsolidatedPackage:
|
||||
|
||||
def __init__(self, pkg=None):
|
||||
self.versions = []
|
||||
self.updateable = False
|
||||
|
||||
if pkg is not None:
|
||||
self.add_version(pkg)
|
||||
@@ -69,6 +70,14 @@ class ConsolidatedPackage:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""
|
||||
Return name of this package. All package versions in a
|
||||
ConsolidatedPackage should have the same name by definition
|
||||
"""
|
||||
return self.versions[0].name
|
||||
|
||||
def get_latest_installed_version(self) -> bpkg.Package:
|
||||
"""
|
||||
Return the installed package with the highest version number.
|
||||
@@ -84,6 +93,16 @@ class ConsolidatedPackage:
|
||||
"""Return package with highest version number"""
|
||||
return self.versions[0] # this is always sorted with the highest on top
|
||||
|
||||
def get_display_version(self) -> bpkg.Package:
|
||||
"""
|
||||
Return installed package with highest version number.
|
||||
If no version is installed, return highest uninstalled version.
|
||||
"""
|
||||
pkg = self.get_latest_installed_version()
|
||||
if pkg is None:
|
||||
pkg = self.get_latest_version()
|
||||
return pkg
|
||||
|
||||
def add_version(self, pkg: Package):
|
||||
self.versions.append(pkg)
|
||||
self.versions.sort(key=lambda v: v.version, reverse=True)
|
||||
@@ -305,7 +324,7 @@ class PACKAGE_OT_install(SubprocMixin, bpy.types.Operator):
|
||||
class PACKAGE_OT_uninstall(SubprocMixin, bpy.types.Operator):
|
||||
bl_idname = 'package.uninstall'
|
||||
bl_label = 'Install package'
|
||||
bl_description = 'Downloads and installs a Blender add-on package'
|
||||
bl_description = "Remove installed package files from filesystem"
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
package_name = bpy.props.StringProperty(name='package_name', description='The name of the package to uninstall')
|
||||
@@ -607,7 +626,7 @@ class USERPREF_PT_packages(bpy.types.Panel):
|
||||
spl_r = spl.row()
|
||||
spl_r.prop(wm, "package_install_filter", expand=True)
|
||||
|
||||
def filtered_packages(filters: dict, packages: OrderedDict) -> list:
|
||||
def filtered_packages(filters: dict, packages: OrderedDict) -> list:# {{{
|
||||
"""Returns filtered and sorted list of names of packages which match filters"""
|
||||
|
||||
#TODO: using lower() for case-insensitive comparison doesn't work in some languages
|
||||
@@ -673,39 +692,89 @@ class USERPREF_PT_packages(bpy.types.Panel):
|
||||
contains.append(pkgname)
|
||||
continue
|
||||
|
||||
return startswith + contains
|
||||
return startswith + contains# }}}
|
||||
|
||||
def draw_package(metapkg: ConsolidatedPackage, layout: bpy.types.UILayout): #{{{
|
||||
"""Draws the given package"""
|
||||
|
||||
def collapsed():
|
||||
def draw_operators(metapkg, layout): # {{{
|
||||
"""
|
||||
Draws install, uninstall, update, enable, disable, and preferences
|
||||
buttons as applicable for the given package
|
||||
"""
|
||||
pkg = metapkg.get_display_version()
|
||||
|
||||
if metapkg.installed:
|
||||
if metapkg.updateable:
|
||||
layout.operator(
|
||||
PACKAGE_OT_install.bl_idname,
|
||||
text="Update to {}".format(utils.fmt_version(metapkg.get_latest_version().version)),
|
||||
).package_name=metapkg.name
|
||||
layout.separator()
|
||||
|
||||
# layout.operator(
|
||||
# PACKAGE_OT_toggle_preferences.bl_idname,
|
||||
# text="Preferences",
|
||||
# ).package_name=metapkg.name
|
||||
layout.operator(
|
||||
PACKAGE_OT_uninstall.bl_idname,
|
||||
text="Uninstall",
|
||||
).package_name=metapkg.name
|
||||
layout.operator(
|
||||
PACKAGE_OT_toggle_enabled.bl_idname,
|
||||
text="Disable" if pkg.enabled else "Enable",
|
||||
).package_name=metapkg.name
|
||||
else:
|
||||
layout.operator(
|
||||
PACKAGE_OT_install.bl_idname,
|
||||
text="Install",
|
||||
).package_name=metapkg.name
|
||||
# }}}
|
||||
|
||||
def collapsed(metapkg, layout):# {{{
|
||||
"""Draw collapsed version of package layout"""
|
||||
lr1 = leftcol.row()
|
||||
lr2 = leftcol.row()
|
||||
pkg = metapkg.get_display_version()
|
||||
|
||||
# Only 'install' button is shown when package isn't installed,
|
||||
# so allow more space for title/description.
|
||||
spl = layout.split(.5 if pkg.installed else .8)
|
||||
|
||||
metacol = spl.column(align=True)
|
||||
|
||||
buttonrow = spl.row(align=True)
|
||||
buttonrow.scale_y = 1.5
|
||||
buttonrow.alignment = 'RIGHT'
|
||||
|
||||
l1 = metacol.row()
|
||||
l2 = metacol.row()
|
||||
|
||||
draw_operators(metapkg, buttonrow)
|
||||
|
||||
if pkg.installed and not pkg.enabled:
|
||||
metacol.enabled = False
|
||||
|
||||
if pkg.name:
|
||||
lr1.label(text=pkg.name)
|
||||
l1.label(text=pkg.name)
|
||||
if pkg.description:
|
||||
lr2.label(text=pkg.description)
|
||||
lr2.enabled = False #Give name more visual weight
|
||||
l2.label(text=pkg.description)
|
||||
l2.enabled = False #Give name more visual weight
|
||||
# }}}
|
||||
|
||||
|
||||
def expanded():
|
||||
def expanded(metapkg, layout, layoutbox):# {{{
|
||||
"""Draw expanded version of package layout"""
|
||||
def fmt_version(version_number: tuple) -> str:
|
||||
"""Take version number as a tuple and format it as a string"""
|
||||
vstr = str(version_number[0])
|
||||
for component in version_number[1:]:
|
||||
vstr += "." + str(component)
|
||||
return vstr
|
||||
|
||||
row1 = leftcol.row()
|
||||
pkg = metapkg.get_display_version()
|
||||
|
||||
metacol = layoutbox.column(align=True)
|
||||
row1 = layout.row(align=True)
|
||||
row1.label(pkg.name)
|
||||
|
||||
metacol.enabled = row1.enabled = pkg.enabled
|
||||
|
||||
if pkg.description:
|
||||
row2 = leftcol.row()
|
||||
row2.label(pkg.description)
|
||||
# row2.scale_y = 1.2
|
||||
row = metacol.row()
|
||||
row.label(pkg.description)
|
||||
|
||||
def draw_metadatum(label: str, value: str, layout: bpy.types.UILayout):
|
||||
"""Draw the given key value pair in a new row in given layout container"""
|
||||
@@ -717,31 +786,37 @@ class USERPREF_PT_packages(bpy.types.Panel):
|
||||
|
||||
# don't compare against None here; we don't want to display empty arrays/strings either
|
||||
if pkg.location:
|
||||
draw_metadatum("Location", pkg.location, leftcol)
|
||||
draw_metadatum("Location", pkg.location, metacol)
|
||||
if pkg.version:
|
||||
draw_metadatum("Version", fmt_version(pkg.version), leftcol)
|
||||
# if pkg.blender:
|
||||
# draw_metadatum("Compatible blender version", fmt_version(pkg.blender), leftcol)
|
||||
draw_metadatum("Version", utils.fmt_version(pkg.version), metacol)
|
||||
if pkg.blender:
|
||||
draw_metadatum("Blender version", utils.fmt_version(pkg.blender), metacol)
|
||||
if pkg.category:
|
||||
draw_metadatum("Category", pkg.category, leftcol)
|
||||
draw_metadatum("Category", pkg.category, metacol)
|
||||
if pkg.author:
|
||||
draw_metadatum("Author", pkg.author, leftcol)
|
||||
draw_metadatum("Author", pkg.author, metacol)
|
||||
if pkg.support:
|
||||
draw_metadatum("Support level", pkg.support.title(), leftcol)
|
||||
draw_metadatum("Support level", pkg.support.title(), metacol)
|
||||
if pkg.warning:
|
||||
draw_metadatum("Warning", pkg.warning, leftcol)
|
||||
draw_metadatum("Warning", pkg.warning, metacol)
|
||||
|
||||
metacol.separator()
|
||||
|
||||
spl = layoutbox.row().split(.35)
|
||||
urlrow = spl.row()
|
||||
urlrow.scale_y = 1.3
|
||||
buttonrow = spl.row(align=True)
|
||||
|
||||
if pkg.wiki_url or pkg.tracker_url:
|
||||
padrow = leftcol.row()
|
||||
padrow.label()
|
||||
padrow.scale_y = .5
|
||||
urlrow = leftcol.row()
|
||||
urlrow.alignment = 'LEFT'
|
||||
if pkg.wiki_url:
|
||||
urlrow.operator("wm.url_open", text="Documentation", icon='HELP').url=pkg.wiki_url
|
||||
if pkg.tracker_url:
|
||||
urlrow.operator("wm.url_open", text="Report a Bug", icon='URL').url=pkg.tracker_url
|
||||
|
||||
buttonrow.alignment = 'RIGHT'
|
||||
buttonrow.scale_y = 1.3
|
||||
draw_operators(metapkg, buttonrow)
|
||||
|
||||
def draw_version(layout: bpy.types.UILayout, pkg: Package):
|
||||
"""Draw version of package"""
|
||||
spl = layout.split(.9)
|
||||
@@ -749,7 +824,7 @@ class USERPREF_PT_packages(bpy.types.Panel):
|
||||
right = spl.column()
|
||||
right.alignment = 'RIGHT'
|
||||
|
||||
left.label(text=fmt_version(pkg.version))
|
||||
left.label(text=utils.fmt_version(pkg.version))
|
||||
|
||||
if pkg.repository is not None:
|
||||
draw_metadatum("Repository", pkg.repository, left)
|
||||
@@ -761,64 +836,27 @@ class USERPREF_PT_packages(bpy.types.Panel):
|
||||
|
||||
if len(metapkg.versions) > 1:
|
||||
row = pkgbox.row()
|
||||
row.label(text="There are multiple providers of this package:")
|
||||
row.label(text="There are multiple versions of this package:")
|
||||
for version in metapkg.versions:
|
||||
# row = pkgbox.row()
|
||||
subvbox = pkgbox.box()
|
||||
draw_version(subvbox, version)
|
||||
# }}}
|
||||
|
||||
pkg = metapkg.get_latest_version()
|
||||
is_expanded = (pkg.name in self.expanded_packages)
|
||||
is_expanded = (metapkg.name in self.expanded_packages)
|
||||
|
||||
pkgbox = layout.box()
|
||||
spl = pkgbox.split(.8)
|
||||
left = spl.row(align=True)
|
||||
|
||||
# for install/uninstall buttons
|
||||
right = spl.row()
|
||||
right.alignment = 'RIGHT'
|
||||
right.scale_y = 1.5
|
||||
|
||||
left.operator(
|
||||
row = pkgbox.row(align=True)
|
||||
row.operator(
|
||||
WM_OT_package_toggle_expand.bl_idname,
|
||||
icon='TRIA_DOWN' if is_expanded else 'TRIA_RIGHT',
|
||||
emboss=False,
|
||||
).package_name=pkg.name
|
||||
|
||||
# for metadata
|
||||
leftcol = left.column(align=True)
|
||||
|
||||
if pkg.installed:
|
||||
if pkg.url and pkg.user:
|
||||
right.operator(PACKAGE_OT_uninstall.bl_idname,
|
||||
text="Uninstall"
|
||||
).package_name=pkg.name
|
||||
elif pkg.user:
|
||||
right.label("Installed, but not in repo")
|
||||
right.scale_y = 2
|
||||
right.enabled = False
|
||||
elif not pkg.user:
|
||||
l = right.label("System package")
|
||||
right.scale_y = 2
|
||||
right.enabled = False
|
||||
else:
|
||||
if pkg.url:
|
||||
if metapkg.get_latest_installed_version():
|
||||
if metapkg.get_latest_installed_version().version < metapkg.get_latest_version().version:
|
||||
right.operator(PACKAGE_OT_install.bl_idname,
|
||||
text="Update"
|
||||
).package_name=pkg.name
|
||||
else:
|
||||
right.operator(PACKAGE_OT_install.bl_idname,
|
||||
text="Install"
|
||||
).package_name=pkg.name
|
||||
else:
|
||||
right.label("Not installed, but no URL?")
|
||||
).package_name=metapkg.name
|
||||
|
||||
if is_expanded:
|
||||
expanded()
|
||||
expanded(metapkg, row, pkgbox)
|
||||
else:
|
||||
collapsed()# }}}
|
||||
collapsed(metapkg, row)# }}}
|
||||
|
||||
|
||||
def center_message(layout, msg: str):
|
||||
"""draw a label in the center of an extra-tall row"""
|
||||
@@ -877,6 +915,71 @@ class WM_OT_package_toggle_expand(bpy.types.Operator):
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class PACKAGE_OT_toggle_enabled(bpy.types.Operator):
|
||||
bl_idname = "package.toggle_enabled"
|
||||
bl_label = ""
|
||||
bl_description = "Enable given package if it's disabled, and vice versa if it's enabled"
|
||||
|
||||
log = logging.getLogger(__name__ + ".PACKAGE_OT_toggle_enabled")
|
||||
|
||||
package_name = bpy.props.StringProperty(
|
||||
name="Package Name",
|
||||
description="Name of package to enable",
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
import addon_utils
|
||||
global _packages
|
||||
metapkg = _packages[self.package_name]
|
||||
|
||||
|
||||
if not metapkg.installed:
|
||||
self.report({'ERROR'}, "Can't enable package which isn't installed")
|
||||
return {'CANCELLED'}
|
||||
|
||||
# enable/disable all installed versions, just in case there are more than one
|
||||
for pkg in metapkg.versions:
|
||||
if not pkg.installed:
|
||||
continue
|
||||
if not pkg.module_name:
|
||||
self.log.warning("Can't enable package `%s` without a module name", pkg.name)
|
||||
continue
|
||||
|
||||
if pkg.enabled:
|
||||
addon_utils.disable(pkg.module_name, default_set=True)
|
||||
pkg.enabled = False
|
||||
self.log.debug("Disabling")
|
||||
else:
|
||||
self.log.debug("Enabling")
|
||||
addon_utils.enable(pkg.module_name, default_set=True)
|
||||
pkg.enabled = True
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class PACKAGE_OT_disable(bpy.types.Operator):
|
||||
bl_idname = "package.disable"
|
||||
bl_label = ""
|
||||
bl_description = "Disable given package"
|
||||
|
||||
log = logging.getLogger(__name__ + ".PACKAGE_OT_disable")
|
||||
|
||||
package_name = bpy.props.StringProperty(
|
||||
name="Package Name",
|
||||
description="Name of package to disable",
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
global _packages
|
||||
package = _packages[self.package_name].get_display_version()
|
||||
|
||||
if not package.module_name:
|
||||
self.log.error("Can't disable package without a module name")
|
||||
return {'CANCELLED'}
|
||||
|
||||
ret = bpy.ops.wm.addon_disable(package.module_name)
|
||||
if ret == {'FINISHED'}:
|
||||
_packages[self.package_name].enabled = False
|
||||
return ret
|
||||
|
||||
class PackageManagerPreferences(bpy.types.AddonPreferences):
|
||||
bl_idname = __package__
|
||||
@@ -938,6 +1041,13 @@ def build_composite_packagelist(installed: list, available: list) -> OrderedDict
|
||||
|
||||
return in_user or in_prefs
|
||||
|
||||
def is_enabled(pkg: Package) -> bool:
|
||||
"""Check if package is enabled"""
|
||||
if pkg.module_name is not None:
|
||||
return (pkg.module_name in bpy.context.user_preferences.addons)
|
||||
else:
|
||||
raise ValueError("Can't determine if package is enabled without knowing its module name")
|
||||
|
||||
|
||||
for pkg in available:
|
||||
pkgname = pkg.bl_info['name']
|
||||
@@ -948,6 +1058,8 @@ def build_composite_packagelist(installed: list, available: list) -> OrderedDict
|
||||
|
||||
for pkg in installed:
|
||||
pkg.installed = True
|
||||
if pkg.name == 'Node Wrangler':
|
||||
log.debug("node wrangler %s", pkg.module_name)
|
||||
pkg.user = is_user_package(pkg)
|
||||
if pkg.name in masterlist:
|
||||
for masterpkg in masterlist[pkg.name]:
|
||||
@@ -955,17 +1067,24 @@ def build_composite_packagelist(installed: list, available: list) -> OrderedDict
|
||||
masterpkg.installed = True
|
||||
masterpkg.installed_location = pkg.installed_location
|
||||
masterpkg.user = pkg.user
|
||||
masterpkg.module_name = pkg.module_name
|
||||
break
|
||||
else:
|
||||
if masterpkg.version > pkg.version:
|
||||
masterlist[pkg.name].updateable = True
|
||||
masterlist[pkg.name].add_version(pkg)
|
||||
else:
|
||||
masterlist[pkg.name] = ConsolidatedPackage(pkg)
|
||||
|
||||
masterlist[pkg.name].enabled = is_enabled(pkg)
|
||||
|
||||
return OrderedDict(sorted(masterlist.items()))
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(PACKAGE_OT_install)
|
||||
bpy.utils.register_class(PACKAGE_OT_uninstall)
|
||||
bpy.utils.register_class(PACKAGE_OT_toggle_enabled)
|
||||
# bpy.utils.register_class(PACKAGE_OT_disable)
|
||||
bpy.utils.register_class(PACKAGE_OT_refresh_repositories)
|
||||
bpy.utils.register_class(PACKAGE_OT_refresh_packages)
|
||||
bpy.utils.register_class(PACKAGE_OT_refresh)
|
||||
@@ -996,6 +1115,8 @@ def register():
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(PACKAGE_OT_install)
|
||||
bpy.utils.unregister_class(PACKAGE_OT_uninstall)
|
||||
bpy.utils.unregister_class(PACKAGE_OT_toggle_enabled)
|
||||
# bpy.utils.unregister_class(PACKAGE_OT_disable)
|
||||
bpy.utils.unregister_class(PACKAGE_OT_refresh_repositories)
|
||||
bpy.utils.unregister_class(PACKAGE_OT_refresh_packages)
|
||||
bpy.utils.unregister_class(PACKAGE_OT_refresh)
|
||||
|
@@ -18,8 +18,10 @@ class Package:
|
||||
self.set_from_dict(package_dict)
|
||||
|
||||
self.installed = False
|
||||
self.enabled = False
|
||||
self.repository = None
|
||||
self.installed_location = None
|
||||
self.module_name = None
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""
|
||||
@@ -162,6 +164,7 @@ class Package:
|
||||
pkg = cls()
|
||||
pkg.files = [filepath.name]
|
||||
pkg.installed_location = str(filepath)
|
||||
pkg.module_name = module.__name__
|
||||
|
||||
try:
|
||||
pkg.bl_info = module.bl_info
|
||||
|
@@ -1,5 +1,12 @@
|
||||
from pathlib import Path
|
||||
|
||||
def fmt_version(version_number: tuple) -> str:
|
||||
"""Take version number as a tuple and format it as a string"""
|
||||
vstr = str(version_number[0])
|
||||
for component in version_number[1:]:
|
||||
vstr += "." + str(component)
|
||||
return vstr
|
||||
|
||||
def parse_repository_url(url: str) -> str:
|
||||
"""Sanitize repository url"""
|
||||
from urllib.parse import urlsplit, urlunsplit
|
||||
|
Reference in New Issue
Block a user