Clean up action button display, and add enabling/disabling

This commit is contained in:
Ellwood Zwovic
2017-07-26 23:36:14 -07:00
parent 8386d6ecb9
commit 57ff2a5664
3 changed files with 213 additions and 82 deletions

View File

@@ -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):# {{{
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)

View File

@@ -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

View File

@@ -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