blender-addons/development_icon_get.py
Philipp Oeser 483cdf6e91 Fix T82453: Icon Viewer: No button on console header
Caused by rB4b20eebb85cc.

Apparently the Addon misused the API though and it is not allowed to
register multiple headers, instead, we need to append to the existing
one.

Maniphest Tasks: T82453

Differential Revision: https://developer.blender.org/D9579
2020-11-26 18:12:23 +01:00

511 lines
15 KiB
Python

# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
bl_info = {
"name": "Icon Viewer",
"description": "Click an icon to copy its name to the clipboard",
"author": "roaoao",
"version": (1, 4, 0),
"blender": (2, 80, 0),
"location": "Text Editor > Dev Tab > Icon Viewer",
"doc_url": "{BLENDER_MANUAL_URL}/addons/development/icon_viewer.html",
"category": "Development",
}
import bpy
import math
from bpy.props import (
BoolProperty,
StringProperty,
)
DPI = 72
POPUP_PADDING = 10
PANEL_PADDING = 44
WIN_PADDING = 32
ICON_SIZE = 20
HISTORY_SIZE = 100
HISTORY = []
def ui_scale():
prefs = bpy.context.preferences.system
return prefs.dpi * prefs.pixel_size / DPI
def prefs():
return bpy.context.preferences.addons[__name__].preferences
class Icons:
def __init__(self, is_popup=False):
self._filtered_icons = None
self._filter = ""
self.filter = ""
self.selected_icon = ""
self.is_popup = is_popup
@property
def filter(self):
return self._filter
@filter.setter
def filter(self, value):
if self._filter == value:
return
self._filter = value
self.update()
@property
def filtered_icons(self):
if self._filtered_icons is None:
self._filtered_icons = []
icon_filter = self._filter.upper()
self.filtered_icons.clear()
pr = prefs()
icons = bpy.types.UILayout.bl_rna.functions[
"prop"].parameters["icon"].enum_items.keys()
for icon in icons:
if icon == 'NONE' or \
icon_filter and icon_filter not in icon or \
not pr.show_brush_icons and "BRUSH_" in icon and \
icon != 'BRUSH_DATA' or \
not pr.show_matcap_icons and "MATCAP_" in icon or \
not pr.show_event_icons and (
"EVENT_" in icon or "MOUSE_" in icon
) or \
not pr.show_colorset_icons and "COLORSET_" in icon:
continue
self._filtered_icons.append(icon)
return self._filtered_icons
@property
def num_icons(self):
return len(self.filtered_icons)
def update(self):
if self._filtered_icons is not None:
self._filtered_icons.clear()
self._filtered_icons = None
def draw(self, layout, num_cols=0, icons=None):
if icons:
filtered_icons = reversed(icons)
else:
filtered_icons = self.filtered_icons
column = layout.column(align=True)
row = column.row(align=True)
row.alignment = 'CENTER'
selected_icon = self.selected_icon if self.is_popup else \
bpy.context.window_manager.clipboard
col_idx = 0
for i, icon in enumerate(filtered_icons):
p = row.operator(
IV_OT_icon_select.bl_idname, text="",
icon=icon, emboss=icon == selected_icon)
p.icon = icon
p.force_copy_on_select = not self.is_popup
col_idx += 1
if col_idx > num_cols - 1:
if icons:
break
col_idx = 0
if i < len(filtered_icons) - 1:
row = column.row(align=True)
row.alignment = 'CENTER'
if col_idx != 0 and not icons and i >= num_cols:
for _ in range(num_cols - col_idx):
row.label(text="", icon='BLANK1')
if not filtered_icons:
row.label(text="No icons were found")
class IV_Preferences(bpy.types.AddonPreferences):
bl_idname = __name__
panel_icons = Icons()
popup_icons = Icons(is_popup=True)
def update_icons(self, context):
self.panel_icons.update()
self.popup_icons.update()
def set_panel_filter(self, value):
self.panel_icons.filter = value
panel_filter: StringProperty(
description="Filter",
default="",
get=lambda s: s.panel_icons.filter,
set=set_panel_filter,
options={'TEXTEDIT_UPDATE'})
show_panel_icons: BoolProperty(
name="Show Icons",
description="Show icons", default=True)
show_history: BoolProperty(
name="Show History",
description="Show history", default=True)
show_brush_icons: BoolProperty(
name="Show Brush Icons",
description="Show brush icons", default=True,
update=update_icons)
show_matcap_icons: BoolProperty(
name="Show Matcap Icons",
description="Show matcap icons", default=True,
update=update_icons)
show_event_icons: BoolProperty(
name="Show Event Icons",
description="Show event icons", default=True,
update=update_icons)
show_colorset_icons: BoolProperty(
name="Show Colorset Icons",
description="Show colorset icons", default=True,
update=update_icons)
copy_on_select: BoolProperty(
name="Copy Icon On Click",
description="Copy icon on click", default=True)
close_on_select: BoolProperty(
name="Close Popup On Click",
description=(
"Close the popup on click.\n"
"Not supported by some windows (User Preferences, Render)"
),
default=False)
auto_focus_filter: BoolProperty(
name="Auto Focus Input Field",
description="Auto focus input field", default=True)
show_panel: BoolProperty(
name="Show Panel",
description="Show the panel in the Text Editor", default=True)
show_header: BoolProperty(
name="Show Header",
description="Show the header in the Python Console",
default=True)
def draw(self, context):
layout = self.layout
row = layout.row()
row.scale_y = 1.5
row.operator(IV_OT_icons_show.bl_idname)
row = layout.row()
col = row.column(align=True)
col.label(text="Icons:")
col.prop(self, "show_matcap_icons")
col.prop(self, "show_brush_icons")
col.prop(self, "show_colorset_icons")
col.prop(self, "show_event_icons")
col.separator()
col.prop(self, "show_history")
col = row.column(align=True)
col.label(text="Popup:")
col.prop(self, "auto_focus_filter")
col.prop(self, "copy_on_select")
if self.copy_on_select:
col.prop(self, "close_on_select")
col = row.column(align=True)
col.label(text="Panel:")
col.prop(self, "show_panel")
if self.show_panel:
col.prop(self, "show_panel_icons")
col.separator()
col.label(text="Header:")
col.prop(self, "show_header")
class IV_PT_icons(bpy.types.Panel):
bl_space_type = "TEXT_EDITOR"
bl_region_type = "UI"
bl_label = "Icon Viewer"
bl_category = "Dev"
bl_options = {'DEFAULT_CLOSED'}
@staticmethod
def tag_redraw():
wm = bpy.context.window_manager
if not wm:
return
for w in wm.windows:
for a in w.screen.areas:
if a.type == 'TEXT_EDITOR':
for r in a.regions:
if r.type == 'UI':
r.tag_redraw()
def draw(self, context):
pr = prefs()
row = self.layout.row(align=True)
if pr.show_panel_icons:
row.prop(pr, "panel_filter", text="", icon='VIEWZOOM')
else:
row.operator(IV_OT_icons_show.bl_idname)
row.operator(
IV_OT_panel_menu_call.bl_idname, text="", icon='COLLAPSEMENU')
_, y0 = context.region.view2d.region_to_view(0, 0)
_, y1 = context.region.view2d.region_to_view(0, 10)
region_scale = 10 / abs(y0 - y1)
num_cols = max(
1,
(context.region.width - PANEL_PADDING) //
math.ceil(ui_scale() * region_scale * ICON_SIZE))
col = None
if HISTORY and pr.show_history:
col = self.layout.column(align=True)
pr.panel_icons.draw(col.box(), num_cols, HISTORY)
if pr.show_panel_icons:
col = col or self.layout.column(align=True)
pr.panel_icons.draw(col.box(), num_cols)
@classmethod
def poll(cls, context):
return prefs().show_panel
class IV_OT_panel_menu_call(bpy.types.Operator):
bl_idname = "iv.panel_menu_call"
bl_label = ""
bl_description = "Menu"
bl_options = {'INTERNAL'}
def menu(self, menu, context):
pr = prefs()
layout = menu.layout
layout.prop(pr, "show_panel_icons")
layout.prop(pr, "show_history")
if not pr.show_panel_icons:
return
layout.separator()
layout.prop(pr, "show_matcap_icons")
layout.prop(pr, "show_brush_icons")
layout.prop(pr, "show_colorset_icons")
layout.prop(pr, "show_event_icons")
def execute(self, context):
context.window_manager.popup_menu(self.menu, title="Icon Viewer")
return {'FINISHED'}
class IV_OT_icon_select(bpy.types.Operator):
bl_idname = "iv.icon_select"
bl_label = ""
bl_description = "Select the icon"
bl_options = {'INTERNAL'}
icon: StringProperty()
force_copy_on_select: BoolProperty()
def execute(self, context):
pr = prefs()
pr.popup_icons.selected_icon = self.icon
if pr.copy_on_select or self.force_copy_on_select:
context.window_manager.clipboard = self.icon
self.report({'INFO'}, self.icon)
if pr.close_on_select and IV_OT_icons_show.instance:
IV_OT_icons_show.instance.close()
if pr.show_history:
if self.icon in HISTORY:
HISTORY.remove(self.icon)
if len(HISTORY) >= HISTORY_SIZE:
HISTORY.pop(0)
HISTORY.append(self.icon)
return {'FINISHED'}
class IV_OT_icons_show(bpy.types.Operator):
bl_idname = "iv.icons_show"
bl_label = "Icon Viewer"
bl_description = "Icon viewer"
bl_property = "filter_auto_focus"
instance = None
def set_filter(self, value):
prefs().popup_icons.filter = value
def set_selected_icon(self, value):
if IV_OT_icons_show.instance:
IV_OT_icons_show.instance.auto_focusable = False
filter_auto_focus: StringProperty(
description="Filter",
get=lambda s: prefs().popup_icons.filter,
set=set_filter,
options={'TEXTEDIT_UPDATE', 'SKIP_SAVE'})
filter: StringProperty(
description="Filter",
get=lambda s: prefs().popup_icons.filter,
set=set_filter,
options={'TEXTEDIT_UPDATE'})
selected_icon: StringProperty(
description="Selected Icon",
get=lambda s: prefs().popup_icons.selected_icon,
set=set_selected_icon)
def get_num_cols(self, num_icons):
return round(1.3 * math.sqrt(num_icons))
def draw_header(self, layout):
pr = prefs()
header = layout.box()
header = header.split(factor=0.75) if self.selected_icon else \
header.row()
row = header.row(align=True)
row.prop(pr, "show_matcap_icons", text="", icon='SHADING_RENDERED')
row.prop(pr, "show_brush_icons", text="", icon='BRUSH_DATA')
row.prop(pr, "show_colorset_icons", text="", icon='COLOR')
row.prop(pr, "show_event_icons", text="", icon='HAND')
row.separator()
row.prop(
pr, "copy_on_select", text="",
icon='COPYDOWN', toggle=True)
if pr.copy_on_select:
sub = row.row(align=True)
if bpy.context.window.screen.name == "temp":
sub.alert = True
sub.prop(
pr, "close_on_select", text="",
icon='RESTRICT_SELECT_OFF', toggle=True)
row.prop(
pr, "auto_focus_filter", text="",
icon='OUTLINER_DATA_FONT', toggle=True)
row.separator()
if self.auto_focusable and pr.auto_focus_filter:
row.prop(self, "filter_auto_focus", text="", icon='VIEWZOOM')
else:
row.prop(self, "filter", text="", icon='VIEWZOOM')
if self.selected_icon:
row = header.row()
row.prop(self, "selected_icon", text="", icon=self.selected_icon)
def draw(self, context):
pr = prefs()
col = self.layout
self.draw_header(col)
history_num_cols = int(
(self.width - POPUP_PADDING) / (ui_scale() * ICON_SIZE))
num_cols = min(
self.get_num_cols(len(pr.popup_icons.filtered_icons)),
history_num_cols)
subcol = col.column(align=True)
if HISTORY and pr.show_history:
pr.popup_icons.draw(subcol.box(), history_num_cols, HISTORY)
pr.popup_icons.draw(subcol.box(), num_cols)
def close(self):
bpy.context.window.screen = bpy.context.window.screen
def check(self, context):
return True
def cancel(self, context):
IV_OT_icons_show.instance = None
IV_PT_icons.tag_redraw()
def execute(self, context):
if not IV_OT_icons_show.instance:
return {'CANCELLED'}
IV_OT_icons_show.instance = None
pr = prefs()
if self.selected_icon and not pr.copy_on_select:
context.window_manager.clipboard = self.selected_icon
self.report({'INFO'}, self.selected_icon)
pr.popup_icons.selected_icon = ""
IV_PT_icons.tag_redraw()
return {'FINISHED'}
def invoke(self, context, event):
pr = prefs()
pr.popup_icons.selected_icon = ""
pr.popup_icons.filter = ""
IV_OT_icons_show.instance = self
self.auto_focusable = True
num_cols = self.get_num_cols(len(pr.popup_icons.filtered_icons))
self.width = min(
ui_scale() * (num_cols * ICON_SIZE + POPUP_PADDING),
context.window.width - WIN_PADDING)
return context.window_manager.invoke_props_dialog(
self, width=self.width)
def draw_console_header(self, context):
if not prefs().show_header:
return
self.layout.operator(IV_OT_icons_show.bl_idname)
classes = (
IV_PT_icons,
IV_OT_panel_menu_call,
IV_OT_icon_select,
IV_OT_icons_show,
IV_Preferences,
)
def register():
if bpy.app.background:
return
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.CONSOLE_HT_header.append(draw_console_header)
def unregister():
if bpy.app.background:
return
bpy.types.CONSOLE_HT_header.remove(draw_console_header)
for cls in classes:
bpy.utils.unregister_class(cls)