diff options
29 files changed, 2107 insertions, 162 deletions
diff --git a/release/datafiles/userdef/userdef_default.c b/release/datafiles/userdef/userdef_default.c index df1be29f642..6dde3f3e6fa 100644 --- a/release/datafiles/userdef/userdef_default.c +++ b/release/datafiles/userdef/userdef_default.c @@ -105,6 +105,7 @@ const UserDef U_default = { .autoexec_paths = {NULL}, .user_menus = {NULL}, + .user_menus_group = {NULL}, .keyconfigstr = "blender", .undosteps = 32, @@ -234,5 +235,9 @@ const UserDef U_default = { .runtime = { .is_dirty = 0, + + .um_space_select = 1, + .um_context_select = 0, + .um_item_select = NULL, }, }; diff --git a/release/scripts/modules/rna_user_menus_ui.py b/release/scripts/modules/rna_user_menus_ui.py new file mode 100644 index 00000000000..7b058d0c5c5 --- /dev/null +++ b/release/scripts/modules/rna_user_menus_ui.py @@ -0,0 +1,294 @@ +# ##### 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> + +__all__ = ( + "draw_user_menus", +) + +import bpy +from bpy.app.translations import pgettext_iface as iface_ +from bpy.app.translations import contexts as i18n_contexts + +def _indented_layout(layout, level): + indentpx = 16 + if level == 0: + level = 0.0001 # Tweak so that a percentage of 0 won't split by half + indent = level * indentpx / bpy.context.region.width + + split = layout.split(factor=indent) + col = split.column() + col = split.column() + return col + +def get_keymap(context, idname, ensure): + prefs = context.preferences + um = prefs.user_menus + + if not idname: + idname = um.active_group.idname + + for km in context.window_manager.keyconfigs.user.keymaps: + for kmi in km.keymap_items: + if kmi.idname == "wm.call_user_menu": + if kmi.properties.name == idname: + return kmi + if ensure: + km = context.window_manager.keyconfigs.user.keymaps['Window'] + kmi = km.keymap_items.new("wm.call_user_menu",'NONE', 'ANY', shift=False, ctrl=False, alt=False) + kmi.properties.idname = idname + kmi.active = True + return kmi + +def draw_button(context, box, item, index): + prefs = context.preferences + um = prefs.user_menus + + name = item.name + if name == "": + name = " " + item.is_selected = item == um.active_item + col = box.column(align=True) + row = col.row(align=True) + if item.type == "SEPARATOR": + name = "___________" + #icons = bpy.types.UILayout.bl_rna.functions["prop"].parameters["icon"].enum_items.keys() + #selected_icon = icons[item.icon] + row.prop(item, "is_selected", icon=item.icon, text=name, toggle=1) + if item.type == "SUBMENU": + sm = item.get_submenu() + if um.active_group.type == "PIE" and index >= 0: + row.operator("preferences.pie_menuitem_add", text="", icon='ADD').index = index + row.operator("preferences.menuitem_remove", text="", icon='REMOVE') + row.operator("preferences.menuitem_up", text="", icon='TRIA_UP') + row.operator("preferences.menuitem_down", text="", icon='TRIA_DOWN') + sub_box = col.box() + sub_box = sub_box.column(align=True) + draw_item(context, sub_box, sm.items_list) + +def draw_item(context, box, items): + for umi in items: + draw_button(context, box, umi, -1) + +def draw_item_box(context, row): + prefs = context.preferences + um = prefs.user_menus + + box_line = row.box() + box_col = box_line.column(align=True) + + has_item = um.has_item() + if not has_item: + box_col.label(text="none") + else: + draw_item(context, box_col, um.get_current_menu().menu_items) + + row = row.split(factor=0.9, align=True) + col = row.column(align=True) + + col.operator("preferences.menuitem_add", text="", icon='ADD').index = -1 + col.operator("preferences.menuitem_remove", text="", icon='REMOVE') + col.operator("preferences.menuitem_up", text="", icon='TRIA_UP') + col.operator("preferences.menuitem_down", text="", icon='TRIA_DOWN') + row.separator() + +def draw_pie_item(context, col, items, label, index): + row = col.row() + row.label(text=label) + draw_button(context, row, items, index) + +def draw_pie(context, row): + prefs = context.preferences + um = prefs.user_menus + + cm = um.get_current_menu() + if not cm: + return + col = row.column() + draw_pie_item(context, col, cm.menu_items[0], "Left : ", 0) + draw_pie_item(context, col, cm.menu_items[1], "Right : ", 1) + draw_pie_item(context, col, cm.menu_items[2], "Down : ", 2) + draw_pie_item(context, col, cm.menu_items[3], "Up : ", 3) + draw_pie_item(context, col, cm.menu_items[4], "Upper left : ", 4) + draw_pie_item(context, col, cm.menu_items[5], "Upper right : ", 5) + draw_pie_item(context, col, cm.menu_items[6], "Lower left : ", 6) + draw_pie_item(context, col, cm.menu_items[7], "Lower right : ", 7) + row.separator() + + +def draw_item_editor(context, row): + prefs = context.preferences + um = prefs.user_menus + + col = row.column() + + has_item = um.has_item() + current = um.active_item + if not has_item: + col.label(text="No item in this list.") + col.label(text="Add one or choose another list to get started") + elif current: + col.prop(current, "type") + if (current.type != "SEPARATOR"): + rowsub = col.row(align=True) + rowsub.prop(current, "icon", icon=current.icon, text="") + col.prop(current, "name") + if (current.type == "OPERATOR"): + umi_op = current.get_operator() + col.prop(umi_op, "operator") + box = col.box() + box.template_user_menu_item_properties(umi_op) + if (current.type == "MENU"): + umi_pm = current.get_menu() + col.prop(umi_pm, "id_name", text="ID name") + if (current.type == "PROPERTY"): + umi_prop = current.get_property() + col.prop(umi_prop, "id_name") + col.prop(umi_prop, "context") + else: + col.label(text="No item selected.") + +def draw_user_menu_preference_expanded(context, layout, kmi, map_type): + prefs = context.preferences + um = prefs.user_menus + + box = layout.box() + sub = box.row() + + if kmi: + if map_type not in {'TEXTINPUT', 'TIMER'}: + sub = box.column() + subrow = sub.row(align=True) + + if map_type == 'KEYBOARD': + subrow.prop(kmi, "type", text="", event=True) + subrow.prop(kmi, "value", text="") + subrow_repeat = subrow.row(align=True) + subrow_repeat.active = kmi.value in {'ANY', 'PRESS'} + subrow_repeat.prop(kmi, "repeat", text="Repeat") + elif map_type in {'MOUSE', 'NDOF'}: + subrow.prop(kmi, "type", text="") + subrow.prop(kmi, "value", text="") + + subrow = sub.row() + subrow.scale_x = 0.75 + subrow.prop(kmi, "any", toggle=True) + subrow.prop(kmi, "shift", toggle=True) + subrow.prop(kmi, "ctrl", toggle=True) + subrow.prop(kmi, "alt", toggle=True) + subrow.prop(kmi, "oskey", text="Cmd", toggle=True) + subrow.prop(kmi, "key_modifier", text="", event=True) + else: + sub.label(text="No key set") + +def draw_user_menu_preference(context, layout): + prefs = context.preferences + um = prefs.user_menus + umg = um.active_group + kmi = get_keymap(context, None, False) + map_type = None + + box = layout.box() + row = box.row() + + row.prop(um, "expanded", text="", emboss=False) + + qf = um.menus[0] + if umg != qf: + row.prop(umg, "name") + pie_text = "List" + if umg.type == "PIE": + pie_text = "Pie" + row.prop(umg, "type", text=pie_text, expand=True) + if kmi: + row.prop(kmi, "map_type", text="") + map_type = kmi.map_type + if map_type == 'KEYBOARD': + row.prop(kmi, "type", text="", full_event=True) + elif map_type == 'MOUSE': + row.prop(kmi, "type", text="", full_event=True) + elif map_type == 'NDOF': + row.prop(kmi, "type", text="", full_event=True) + elif map_type == 'TWEAK': + subrow = row.row() + subrow.prop(kmi, "type", text="") + subrow.prop(kmi, "value", text="") + elif map_type == 'TIMER': + row.prop(kmi, "type", text="") + else: + row.label() + + if um.expanded: + draw_user_menu_preference_expanded(context=context, layout=box, kmi=kmi, map_type=map_type) + + +def menu_id(context, umg): + prefs = context.preferences + um = prefs.user_menus + + i = 0 + for item in um.menus: + if item == umg: + return i + i = i + 1 + return -1 + + +def draw_user_menus(context, layout): + prefs = context.preferences + um = prefs.user_menus + + if not um.active_group: + um.active_group = um.menus[0] + + split = layout.split(factor=0.4) + + row = split.row() + + rowsub = row.row(align=True) + rowsub.menu("USERPREF_MT_menu_select", text=um.active_group.name) + rowsub.operator("preferences.usermenus_add", text="", icon='ADD') + rowsub.operator("preferences.usermenus_remove", text="", icon='REMOVE') + + rowsub = split.row(align=True) + rowsub.prop(um, "space_selected", text="") + + rowsub = split.row(align=True) + rowsub.prop(um, "context_selected", text="") + + #rowsub = split.row(align=True) + #rowsub.operator("preferences.keyconfig_import", text="", icon='IMPORT') + #rowsub.operator("preferences.keyconfig_export", text="", icon='EXPORT') + + row = layout.row() + row.separator() + + draw_user_menu_preference(context=context, layout=row) + + row = layout.row() + row.separator() + + if um.active_group.type == "PIE": + draw_pie(context=context, row=row) + else: + draw_item_box(context=context, row=row) + draw_item_editor(context=context, row=row) + + row.separator() + diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index c3c6e77067e..4831f5565ab 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -157,6 +157,8 @@ def op_menu(menu, kmi_args): def op_menu_pie(menu, kmi_args): return ("wm.call_menu_pie", kmi_args, {"properties": [("name", menu)]}) +def op_user_menu(menu, kmi_args): + return ("wm.call_user_menu", kmi_args, {"properties": [("name", menu)]}) def op_panel(menu, kmi_args, kmi_data=()): return ("wm.call_panel", kmi_args, {"properties": [("name", menu), *kmi_data]}) @@ -407,7 +409,7 @@ def km_window(params): ("wm.quit_blender", {"type": 'Q', "value": 'PRESS', "ctrl": True}, None), # Quick menu and toolbar - op_menu("SCREEN_MT_user_menu", {"type": 'Q', "value": 'PRESS'}), + op_user_menu("QUICK_FAVORITES", {"type": 'Q', "value": 'PRESS'}), # Fast editor switching *( diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index bb921565374..5d52eed15dd 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -67,6 +67,8 @@ def op_menu(menu, kmi_args): def op_menu_pie(menu, kmi_args): return ("wm.call_menu_pie", kmi_args, {"properties": [("name", menu)]}) +def op_user_menu(menu, kmi_args): + return ("wm.call_user_menu", kmi_args, {"properties": [("name", menu)]}) def op_panel(menu, kmi_args, kmi_data=()): return ("wm.call_panel", kmi_args, {"properties": [("name", menu), *kmi_data]}) @@ -210,7 +212,7 @@ def km_window(params): ("wm.quit_blender", {"type": 'Q', "value": 'PRESS', "ctrl": True}, None), # Quick menu and toolbar - op_menu("SCREEN_MT_user_menu", {"type": 'TAB', "value": 'PRESS', "shift": True}), + op_user_menu("QUICK_FAVORITES", {"type": 'Q', "value": 'PRESS'}), # NDOF settings op_panel("USERPREF_PT_ndof_settings", {"type": 'NDOF_BUTTON_MENU', "value": 'PRESS'}), diff --git a/release/scripts/startup/bl_operators/presets.py b/release/scripts/startup/bl_operators/presets.py index 2ea93a1aee9..357d08959fc 100644 --- a/release/scripts/startup/bl_operators/presets.py +++ b/release/scripts/startup/bl_operators/presets.py @@ -602,7 +602,6 @@ class AddPresetOperator(AddPresetBase, Operator): prefix, suffix = operator.split("_OT_", 1) return os.path.join("operator", "%s.%s" % (prefix.lower(), suffix)) - class WM_MT_operator_presets(Menu): bl_label = "Operator Presets" diff --git a/release/scripts/startup/bl_operators/userpref.py b/release/scripts/startup/bl_operators/userpref.py index ceef2df63ff..5f10f06f655 100644 --- a/release/scripts/startup/bl_operators/userpref.py +++ b/release/scripts/startup/bl_operators/userpref.py @@ -51,6 +51,8 @@ def module_filesystem_remove(path_base, module_name): # This duplicates shutil.copytree from Python 3.8, with the new dirs_exist_ok # argument that we need. Once we upgrade to 3.8 we can remove this. + + def _preferences_copytree(entries, src, dst): import os import shutil @@ -85,11 +87,13 @@ def _preferences_copytree(entries, src, dst): raise Error(errors) return dst + def preferences_copytree(src, dst): import os with os.scandir(src) as entries: return _preferences_copytree(entries=entries, src=src, dst=dst) + class PREFERENCES_OT_keyconfig_activate(Operator): bl_idname = "preferences.keyconfig_activate" bl_label = "Activate Keyconfig" @@ -241,7 +245,8 @@ class PREFERENCES_OT_keyconfig_import(Operator): config_name = basename(self.filepath) - path = bpy.utils.user_resource('SCRIPTS', os.path.join("presets", "keyconfig"), create=True) + path = bpy.utils.user_resource( + 'SCRIPTS', os.path.join("presets", "keyconfig"), create=True) path = os.path.join(path, config_name) try: @@ -460,7 +465,8 @@ class PREFERENCES_OT_addon_enable(Operator): err_str = traceback.format_exc() print(err_str) - mod = addon_utils.enable(self.module, default_set=True, handle_error=err_cb) + mod = addon_utils.enable( + self.module, default_set=True, handle_error=err_cb) if mod: info = addon_utils.module_bl_info(mod) @@ -544,7 +550,8 @@ class PREFERENCES_OT_theme_install(Operator): xmlfile = self.filepath - path_themes = bpy.utils.user_resource('SCRIPTS', "presets/interface_theme", create=True) + path_themes = bpy.utils.user_resource( + 'SCRIPTS', "presets/interface_theme", create=True) if not path_themes: self.report({'ERROR'}, "Failed to get themes path") @@ -554,7 +561,8 @@ class PREFERENCES_OT_theme_install(Operator): if not self.overwrite: if os.path.exists(path_dest): - self.report({'WARNING'}, "File already installed to %r\n" % path_dest) + self.report( + {'WARNING'}, "File already installed to %r\n" % path_dest) return {'CANCELLED'} try: @@ -638,7 +646,8 @@ class PREFERENCES_OT_addon_install(Operator): if self.target == 'DEFAULT': # don't use bpy.utils.script_paths("addons") because we may not be able to write to it. - path_addons = bpy.utils.user_resource('SCRIPTS', "addons", create=True) + path_addons = bpy.utils.user_resource( + 'SCRIPTS', "addons", create=True) else: path_addons = context.preferences.filepaths.script_directory if path_addons: @@ -661,7 +670,8 @@ class PREFERENCES_OT_addon_install(Operator): pyfile_dir = os.path.dirname(pyfile) for addon_path in addon_utils.paths(): if os.path.samefile(pyfile_dir, addon_path): - self.report({'ERROR'}, "Source file is in the add-on search path: %r" % addon_path) + self.report( + {'ERROR'}, "Source file is in the add-on search path: %r" % addon_path) return {'CANCELLED'} del addon_path del pyfile_dir @@ -684,7 +694,8 @@ class PREFERENCES_OT_addon_install(Operator): for f in file_to_extract.namelist(): path_dest = os.path.join(path_addons, os.path.basename(f)) if os.path.exists(path_dest): - self.report({'WARNING'}, "File already installed to %r\n" % path_dest) + self.report( + {'WARNING'}, "File already installed to %r\n" % path_dest) return {'CANCELLED'} try: # extract the file to "addons" @@ -699,7 +710,8 @@ class PREFERENCES_OT_addon_install(Operator): if self.overwrite: module_filesystem_remove(path_addons, os.path.basename(pyfile)) elif os.path.exists(path_dest): - self.report({'WARNING'}, "File already installed to %r\n" % path_dest) + self.report( + {'WARNING'}, "File already installed to %r\n" % path_dest) return {'CANCELLED'} # if not compressed file just copy into the addon path @@ -709,7 +721,8 @@ class PREFERENCES_OT_addon_install(Operator): traceback.print_exc() return {'CANCELLED'} - addons_new = {mod.__name__ for mod in addon_utils.modules()} - addons_old + addons_new = {mod.__name__ for mod in addon_utils.modules()} - \ + addons_old addons_new.discard("modules") # disable any addons we may have enabled previously and removed. @@ -779,7 +792,8 @@ class PREFERENCES_OT_addon_remove(Operator): path, isdir = PREFERENCES_OT_addon_remove.path_from_addon(self.module) if path is None: - self.report({'WARNING'}, "Add-on path %r could not be found" % path) + self.report( + {'WARNING'}, "Add-on path %r could not be found" % path) return {'CANCELLED'} # in case its enabled @@ -925,9 +939,11 @@ class PREFERENCES_OT_app_template_install(Operator): module_filesystem_remove(path_app_templates, f) else: for f in file_to_extract.namelist(): - path_dest = os.path.join(path_app_templates, os.path.basename(f)) + path_dest = os.path.join( + path_app_templates, os.path.basename(f)) if os.path.exists(path_dest): - self.report({'WARNING'}, "File already installed to %r\n" % path_dest) + self.report( + {'WARNING'}, "File already installed to %r\n" % path_dest) return {'CANCELLED'} try: # extract the file to "bl_app_templates_user" @@ -941,7 +957,8 @@ class PREFERENCES_OT_app_template_install(Operator): self.report({'WARNING'}, "Expected a zip-file %r\n" % filepath) return {'CANCELLED'} - app_templates_new = set(os.listdir(path_app_templates)) - app_templates_old + app_templates_new = set(os.listdir( + path_app_templates)) - app_templates_old # in case a new module path was created to install this addon. bpy.utils.refresh_script_paths() @@ -1001,14 +1018,17 @@ class PREFERENCES_OT_studiolight_install(Operator): prefs = context.preferences path_studiolights = os.path.join("studiolights", self.type.lower()) - path_studiolights = bpy.utils.user_resource('DATAFILES', path_studiolights, create=True) + path_studiolights = bpy.utils.user_resource( + 'DATAFILES', path_studiolights, create=True) if not path_studiolights: self.report({'ERROR'}, "Failed to create Studio Light path") return {'CANCELLED'} for e in self.files: - shutil.copy(os.path.join(self.directory, e.name), path_studiolights) - prefs.studio_lights.load(os.path.join(path_studiolights, e.name), self.type) + shutil.copy(os.path.join(self.directory, e.name), + path_studiolights) + prefs.studio_lights.load(os.path.join( + path_studiolights, e.name), self.type) # print message msg = ( @@ -1047,7 +1067,8 @@ class PREFERENCES_OT_studiolight_new(Operator): wm = context.window_manager filename = bpy.path.ensure_ext(self.filename, ".sl") - path_studiolights = bpy.utils.user_resource('DATAFILES', os.path.join("studiolights", "studio"), create=True) + path_studiolights = bpy.utils.user_resource( + 'DATAFILES', os.path.join("studiolights", "studio"), create=True) if not path_studiolights: self.report({'ERROR'}, "Failed to get Studio Light path") return {'CANCELLED'} @@ -1060,7 +1081,8 @@ class PREFERENCES_OT_studiolight_new(Operator): else: for studio_light in prefs.studio_lights: if studio_light.name == filename: - bpy.ops.preferences.studiolight_uninstall(index=studio_light.index) + bpy.ops.preferences.studiolight_uninstall( + index=studio_light.index) prefs.studio_lights.new(path=filepath_final) @@ -1076,7 +1098,8 @@ class PREFERENCES_OT_studiolight_new(Operator): def draw(self, _context): layout = self.layout if self.ask_overide: - layout.label(text="Warning, file already exists. Overwrite existing file?") + layout.label( + text="Warning, file already exists. Overwrite existing file?") else: layout.prop(self, "filename") @@ -1141,6 +1164,184 @@ class PREFERENCES_OT_studiolight_show(Operator): bpy.ops.screen.userpref_show('INVOKE_DEFAULT') return {'FINISHED'} +# ----------------------------------------------------------------------------- +# User Menus Operators + +class PREFERENCES_OT_usermenus_select(Operator): + bl_idname = "preferences.usermenus_select" + bl_label = "select user menu to edit" + + index: IntProperty() + + def execute(self, _context): + prefs = _context.preferences + um = prefs.user_menus + + i = 0 + for umg in um.menus: + if i == self.index: + um.set_group(new_group=umg) + return {'FINISHED'} + i = i + 1 + return {'CANCELLED'} + +class PREFERENCES_OT_usermenus_add(Operator): + bl_idname = "preferences.usermenus_add" + bl_label = "add an user menus group" + + def execute(self, _context): + prefs = _context.preferences + um = prefs.user_menus + um.add_group() + return {'FINISHED'} + +class PREFERENCES_OT_usermenus_remove(Operator): + bl_idname = "preferences.usermenus_remove" + bl_label = "remove an user menus group" + + @classmethod + def poll(self, context): + prefs = context.preferences + um = prefs.user_menus + + if um.active_group.idname == "QUICK_FAVORITES": + return False + return True + + def execute(self, _context): + prefs = _context.preferences + um = prefs.user_menus + um.remove_group() + return {'FINISHED'} + +class PREFERENCES_OT_menuitem_add(Operator): + """Add user menu item""" + bl_idname = "preferences.menuitem_add" + bl_label = "Add User Menu Item" + + index: IntProperty() + + def execute(self, context): + prefs = context.preferences + um = prefs.user_menus + + um.item_add(index=self.index) + context.preferences.is_dirty = True + return {'FINISHED'} + + +class PREFERENCES_OT_menuitem_remove(Operator): + """Remove user menu item""" + bl_idname = "preferences.menuitem_remove" + bl_label = "Remove User Menu Item" + + @classmethod + def poll(self, context): + prefs = context.preferences + um = prefs.user_menus + can_remove = um.active_item + um_type = um.active_group.type + + if um_type == "PIE" and can_remove: + if not can_remove.parent: + return False + return can_remove + + def execute(self, context): + prefs = context.preferences + um = prefs.user_menus + um.item_remove() + context.preferences.is_dirty = True + return {'FINISHED'} + + +class PREFERENCES_OT_menuitem_up(Operator): + """move up an user menu item""" + bl_idname = "preferences.menuitem_up" + bl_label = "Move Up An User Menu Item" + + @classmethod + def poll(self, context): + prefs = context.preferences + um = prefs.user_menus + current = um.active_item + um_type = um.active_group.type + if not current: + return False + + if um_type == "PIE": + if not current.parent: + return False + prev_item = current.prev + if prev_item: + return True + if current.parent.item.parent: + return True + return False + else: + prev_item = current.prev + if prev_item or current.parent: + return True + return False + + def execute(self, context): + prefs = context.preferences + um = prefs.user_menus + um.item_move(up=True) + context.preferences.is_dirty = True + return {'FINISHED'} + + +class PREFERENCES_OT_menuitem_down(Operator): + """move up an user menu item""" + bl_idname = "preferences.menuitem_down" + bl_label = "Move Up An User Menu Item" + + @classmethod + def poll(self, context): + prefs = context.preferences + um = prefs.user_menus + um_type = um.active_group.type + current = um.active_item + if not current: + return False + + if um_type == "PIE": + if not current.parent: + return False + next_item = current.next + if next_item: + return True + if current.parent.item.parent: + return True + return False + else: + next_item = current.next + if next_item or current.parent: + return True + return False + + def execute(self, context): + prefs = context.preferences + um = prefs.user_menus + um.item_move(up=False) + context.preferences.is_dirty = True + return {'FINISHED'} + +class PREFERENCES_OT_pie_menuitem_add(Operator): + """Add user menu item""" + bl_idname = "preferences.pie_menuitem_add" + bl_label = "Add User Menu Item" + + index: IntProperty() + + def execute(self, context): + prefs = context.preferences + um = prefs.user_menus + + um.pie_item_add(index=self.index) + context.preferences.is_dirty = True + return {'FINISHED'} classes = ( PREFERENCES_OT_addon_disable, @@ -1167,4 +1368,12 @@ classes = ( PREFERENCES_OT_studiolight_uninstall, PREFERENCES_OT_studiolight_copy_settings, PREFERENCES_OT_studiolight_show, + PREFERENCES_OT_usermenus_select, + PREFERENCES_OT_usermenus_add, + PREFERENCES_OT_usermenus_remove, + PREFERENCES_OT_menuitem_add, + PREFERENCES_OT_menuitem_remove, + PREFERENCES_OT_menuitem_up, + PREFERENCES_OT_menuitem_down, + PREFERENCES_OT_pie_menuitem_add, ) diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 59abff12834..6c8d94c874c 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -2633,6 +2633,36 @@ class WM_OT_drop_blend_file(Operator): col.operator("wm.link", text="Link...", icon='LINK_BLEND').filepath = self.filepath col.operator("wm.append", text="Append...", icon='APPEND_BLEND').filepath = self.filepath +class WM_OT_call_user_menu(Operator): + bl_idname = "wm.call_user_menu" + bl_label = "display user menu" + + name: StringProperty() + + def execute(self, context): + return {'FINISHED'} + + def draw_menu(self, menu, context): + prefs = context.preferences + um = prefs.user_menus + umg = um.get_group(idname=self.name) + + layout = menu.layout + if umg.type == "PIE": + layout = layout.menu_pie() + um.draw_menu(context=context, layout=layout, menu=umg) + + def invoke(self, context, event): + prefs = context.preferences + um = prefs.user_menus + wm = context.window_manager + umg = um.get_group(idname=self.name) + + if umg.type == "PIE": + wm.popup_menu_pie(draw_func=self.draw_menu, title=umg.name, event=event) + else: + wm.popup_menu(self.draw_menu, title=umg.name) + return {'FINISHED'} classes = ( WM_OT_context_collection_boolean_set, @@ -2677,4 +2707,5 @@ classes = ( WM_OT_batch_rename, WM_MT_splash, WM_MT_splash_about, + WM_OT_call_user_menu, ) diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 9548de20752..f015a8de295 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -1684,8 +1684,37 @@ class USERPREF_PT_keymap(KeymapPanel, Panel): # Keymap Settings draw_keymaps(context, layout) - # print("runtime", time.time() - start) +# ----------------------------------------------------------------------------- +# Custom Menu Editor Panels + +class UserMenusPanel: + bl_space_type = 'PREFERENCES' + bl_region_type = 'WINDOW' + bl_context = "user_menus" + +class USERPREF_MT_menu_select(Menu): + bl_label = "Menus group select" + + def draw(self, context): + prefs = context.preferences + um = prefs.user_menus + layout = self.layout + index = 0 + for umg in um.menus: + layout.operator("preferences.usermenus_select", text=umg.name).index = index + index = index + 1 + + +class USERPREF_PT_user_menus(UserMenusPanel, Panel): + bl_label = "user_menus" + bl_options = {'HIDE_HEADER'} + + def draw(self, context): + from rna_user_menus_ui import draw_user_menus + + layout = self.layout + draw_user_menus(context, layout) # ----------------------------------------------------------------------------- # Add-On Panels @@ -2278,6 +2307,8 @@ classes = ( USERPREF_PT_navigation_fly_walk_gravity, USERPREF_PT_keymap, + USERPREF_MT_menu_select, + USERPREF_PT_user_menus, USERPREF_PT_addons, USERPREF_PT_studiolight_lights, diff --git a/source/blender/blenkernel/BKE_blender_user_menu.h b/source/blender/blenkernel/BKE_blender_user_menu.h index 8d00cde488e..dd42d45fd67 100644 --- a/source/blender/blenkernel/BKE_blender_user_menu.h +++ b/source/blender/blenkernel/BKE_blender_user_menu.h @@ -27,7 +27,15 @@ extern "C" { struct ListBase; struct bUserMenu; struct bUserMenuItem; +struct wmWindowManager; +void BKE_blender_user_menu_free_list(struct ListBase *lb); +void BKE_blender_user_menus_group_idname_update(struct bUserMenusGroup *umg); +void BKE_blender_user_menus_group_idname_update_keymap(struct wmWindowManager *wm, + const char *old, + const char *new); +struct bUserMenusGroup *BKE_blender_user_menus_group_new(const char *name); +struct bUserMenusGroup *BKE_blender_user_menus_group_find(struct ListBase *lb, const char *idname); struct bUserMenu *BKE_blender_user_menu_find(struct ListBase *lb, char space_type, const char *context); @@ -38,6 +46,7 @@ struct bUserMenu *BKE_blender_user_menu_ensure(struct ListBase *lb, struct bUserMenuItem *BKE_blender_user_menu_item_add(struct ListBase *lb, int type); void BKE_blender_user_menu_item_free(struct bUserMenuItem *umi); void BKE_blender_user_menu_item_free_list(struct ListBase *lb); +struct bUserMenusGroup *BKU_blender_user_menu_default(void); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_context.h b/source/blender/blenkernel/BKE_context.h index eb26f1c3969..355b69b73ef 100644 --- a/source/blender/blenkernel/BKE_context.h +++ b/source/blender/blenkernel/BKE_context.h @@ -255,6 +255,7 @@ struct ViewLayer *CTX_data_view_layer(const bContext *C); struct RenderEngineType *CTX_data_engine_type(const bContext *C); struct ToolSettings *CTX_data_tool_settings(const bContext *C); +const char **CTX_data_list_mode_string(void); const char *CTX_data_mode_string(const bContext *C); enum eContextObjectMode CTX_data_mode_enum_ex(const struct Object *obedit, const struct Object *ob, diff --git a/source/blender/blenkernel/intern/blender.c b/source/blender/blenkernel/intern/blender.c index 1d5c8f76cc5..8a6d30834eb 100644 --- a/source/blender/blenkernel/intern/blender.c +++ b/source/blender/blenkernel/intern/blender.c @@ -238,10 +238,14 @@ static void userdef_free_keyconfig_prefs(UserDef *userdef) static void userdef_free_user_menus(UserDef *userdef) { - for (bUserMenu *um = userdef->user_menus.first, *um_next; um; um = um_next) { - um_next = um->next; - BKE_blender_user_menu_item_free_list(&um->items); - MEM_freeN(um); + for (bUserMenusGroup *umg = userdef->user_menus_group.first, *umg_next; umg; umg = umg_next) { + umg_next = umg->next; + for (bUserMenu *um = umg->menus.first, *um_next; um; um = um_next) { + um_next = um->next; + BKE_blender_user_menu_item_free_list(&um->items); + MEM_freeN(um); + } + MEM_freeN(umg); } } diff --git a/source/blender/blenkernel/intern/blender_user_menu.c b/source/blender/blenkernel/intern/blender_user_menu.c index edd89357fd5..17251e2c71d 100644 --- a/source/blender/blenkernel/intern/blender_user_menu.c +++ b/source/blender/blenkernel/intern/blender_user_menu.c @@ -26,13 +26,98 @@ #include "BLI_listbase.h" #include "BLI_string.h" +#include "BLI_string_utils.h" #include "DNA_userdef_types.h" +#include "DNA_windowmanager_types.h" + +#include "RNA_access.h" #include "BKE_blender_user_menu.h" #include "BKE_idprop.h" /* -------------------------------------------------------------------- */ +/** \name Menu group + * \{ */ + +void BKE_blender_user_menu_free_list(ListBase *lb) +{ + for (bUserMenu *um = lb->first, *um_next; um; um = um_next) { + um_next = um->next; + BKE_blender_user_menu_item_free_list(&um->items); + MEM_freeN(um); + } + BLI_listbase_clear(lb); +} + +bUserMenusGroup *BKE_blender_user_menus_group_find(ListBase *lb, const char *idname) +{ + LISTBASE_FOREACH (bUserMenusGroup *, umg, lb) { + if ((STREQ(idname, umg->idname))) { + return umg; + } + } + return NULL; +} + +void BKE_blender_user_menus_group_idname_update(bUserMenusGroup *umg) +{ + char name[64]; + + STRNCPY(name, umg->name); + for (int i = 0; name[i]; i++) { + if (name[i] == ' ') + name[i] = '_'; + if (name[i] >= 'a' && name[i] <= 'z') + name[i] += 'A' - 'a'; + } + STRNCPY(umg->idname, name); + BLI_uniquename(&U.user_menus_group, + umg, + umg->idname, + '_', + offsetof(bUserMenusGroup, idname), + sizeof(umg->idname)); +} + +void BKE_blender_user_menus_group_idname_update_keymap(wmWindowManager *wm, + const char *old, + const char *new) +{ + wmKeyConfig *kc; + wmKeyMap *km; + + for (kc = wm->keyconfigs.first; kc; kc = kc->next) { + for (km = kc->keymaps.first; km; km = km->next) { + wmKeyMapItem *kmi; + for (kmi = km->items.first; kmi; kmi = kmi->next) { + if (STREQ(kmi->idname, "WM_OT_call_user_menu")) { + IDProperty *idp = IDP_GetPropertyFromGroup(kmi->properties, "name"); + char *index = IDP_String(idp); + if (STREQ(index, old)) { + IDP_AssignString(idp, new, 64); + } + } + } + } + } +} + +bUserMenusGroup *BKE_blender_user_menus_group_new(const char *name) +{ + bUserMenusGroup *umg = MEM_mallocN(sizeof(*umg), __func__); + STRNCPY(umg->name, name); + umg->type = 0; + umg->prev = NULL; + umg->next = NULL; + BLI_listbase_clear(&umg->menus); + BKE_blender_user_menus_group_idname_update(umg); + return umg; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Menu Type * \{ */ @@ -50,8 +135,10 @@ bUserMenu *BKE_blender_user_menu_ensure(ListBase *lb, char space_type, const cha { bUserMenu *um = BKE_blender_user_menu_find(lb, space_type, context); if (um == NULL) { + um = MEM_callocN(sizeof(bUserMenu), __func__); um->space_type = space_type; + BLI_listbase_clear(&um->items); STRNCPY(um->context, context); BLI_addhead(lb, um); } @@ -80,6 +167,9 @@ bUserMenuItem *BKE_blender_user_menu_item_add(ListBase *lb, int type) else if (type == USER_MENU_TYPE_PROP) { size = sizeof(bUserMenuItem_Prop); } + else if (type == USER_MENU_TYPE_SUBMENU) { + size = sizeof(bUserMenuItem_SubMenu); + } else { size = sizeof(bUserMenuItem); BLI_assert(0); @@ -87,7 +177,9 @@ bUserMenuItem *BKE_blender_user_menu_item_add(ListBase *lb, int type) bUserMenuItem *umi = MEM_callocN(size, __func__); umi->type = type; - BLI_addtail(lb, umi); + umi->icon = 0; + if (lb) + BLI_addtail(lb, umi); return umi; } @@ -99,6 +191,10 @@ void BKE_blender_user_menu_item_free(bUserMenuItem *umi) IDP_FreeProperty(umi_op->prop); } } + if (umi->type == USER_MENU_TYPE_SUBMENU) { + bUserMenuItem_SubMenu *umi_sm = (bUserMenuItem_SubMenu *)umi; + BKE_blender_user_menu_item_free_list(&umi_sm->items); + } MEM_freeN(umi); } @@ -110,3 +206,19 @@ void BKE_blender_user_menu_item_free_list(ListBase *lb) } BLI_listbase_clear(lb); } + +/* -------------------------------------------------------------------- */ +/** \name Default Menu + * \{ */ + +bUserMenusGroup *BKU_blender_user_menu_default(void) +{ + bUserMenusGroup *umg = MEM_mallocN(sizeof(*umg), __func__); + STRNCPY(umg->name, "Quick Favorites"); + STRNCPY(umg->idname, "QUICK_FAVORITES"); + umg->type = 0; + umg->prev = NULL; + umg->next = NULL; + BLI_listbase_clear(&umg->menus); + return umg; +}
\ No newline at end of file diff --git a/source/blender/blenkernel/intern/blendfile.c b/source/blender/blenkernel/intern/blendfile.c index ee60bf79611..0decce25e3a 100644 --- a/source/blender/blenkernel/intern/blendfile.c +++ b/source/blender/blenkernel/intern/blendfile.c @@ -57,6 +57,7 @@ #include "BKE_screen.h" #include "BKE_studiolight.h" #include "BKE_workspace.h" +#include "BKE_blender_user_menu.h" #include "BLO_readfile.h" #include "BLO_writefile.h" @@ -614,6 +615,13 @@ UserDef *BKE_blendfile_userdef_from_defaults(void) BLI_addtail(&userdef->themes, btheme); } + /* default user menus. */ + { + bUserMenusGroup *umg = BKU_blender_user_menu_default(); + BLI_addtail(&userdef->user_menus_group, umg); + userdef->runtime.umg_select = umg; + } + #ifdef WITH_PYTHON_SECURITY /* use alternative setting for security nuts * otherwise we'd need to patch the binary blob - startup.blend.c */ diff --git a/source/blender/blenkernel/intern/context.c b/source/blender/blenkernel/intern/context.c index dce14c4c082..29a6f829c35 100644 --- a/source/blender/blenkernel/intern/context.c +++ b/source/blender/blenkernel/intern/context.c @@ -1151,6 +1151,11 @@ const char *CTX_data_mode_string(const bContext *C) return data_mode_strings[CTX_data_mode_enum(C)]; } +const char **CTX_data_list_mode_string(void) +{ + return data_mode_strings; +} + void CTX_data_scene_set(bContext *C, Scene *scene) { C->data.scene = scene; diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 84cb898a426..241ae45ae5e 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -114,6 +114,7 @@ #include "BKE_action.h" #include "BKE_anim_data.h" #include "BKE_armature.h" +#include "BKE_blender_user_menu.h" #include "BKE_brush.h" #include "BKE_collection.h" #include "BKE_colortools.h" @@ -159,6 +160,8 @@ #include "BKE_volume.h" #include "BKE_workspace.h" +#include "BKE_blender_user_menu.h" + #include "DRW_engine.h" #include "DEG_depsgraph.h" @@ -8897,6 +8900,26 @@ static void direct_link_keymapitem(BlendDataReader *reader, wmKeyMapItem *kmi) kmi->flag &= ~KMI_UPDATE; } +static void read_usermenuitems(BlendDataReader *reader, + ListBase *lb, + bUserMenuItem_SubMenu *parent) +{ + LISTBASE_FOREACH (bUserMenuItem *, umi, lb) { + umi->parent = parent; + if (umi->type == USER_MENU_TYPE_OPERATOR) { + bUserMenuItem_Op *umi_op = (bUserMenuItem_Op *)umi; + BLO_read_data_address(reader, &umi_op->prop); + IDP_DirectLinkGroup_OrFree(&umi_op->prop, reader); + } + if (umi->type == USER_MENU_TYPE_SUBMENU) { + bUserMenuItem_SubMenu *umi_sm = (bUserMenuItem_SubMenu *)umi; + BLI_listbase_clear(&umi_sm->items); + BLO_read_list(reader, &umi_sm->items); + read_usermenuitems(reader, &umi_sm->items, umi_sm); + } + } +} + static BHead *read_userdef(BlendFileData *bfd, FileData *fd, BHead *bhead) { UserDef *user; @@ -8918,6 +8941,7 @@ static BHead *read_userdef(BlendFileData *bfd, FileData *fd, BHead *bhead) BLO_read_list(reader, &user->user_menus); BLO_read_list(reader, &user->addons); BLO_read_list(reader, &user->autoexec_paths); + BLO_read_list(reader, &user->user_menus_group); LISTBASE_FOREACH (wmKeyMap *, keymap, &user->user_keymaps) { keymap->modal_items = NULL; @@ -8951,13 +8975,7 @@ static BHead *read_userdef(BlendFileData *bfd, FileData *fd, BHead *bhead) LISTBASE_FOREACH (bUserMenu *, um, &user->user_menus) { BLO_read_list(reader, &um->items); - LISTBASE_FOREACH (bUserMenuItem *, umi, &um->items) { - if (umi->type == USER_MENU_TYPE_OPERATOR) { - bUserMenuItem_Op *umi_op = (bUserMenuItem_Op *)umi; - BLO_read_data_address(reader, &umi_op->prop); - IDP_BlendDataRead(reader, &umi_op->prop); - } - } + read_usermenuitems(reader, &um->items, NULL); } LISTBASE_FOREACH (bAddon *, addon, &user->addons) { @@ -8965,6 +8983,23 @@ static BHead *read_userdef(BlendFileData *bfd, FileData *fd, BHead *bhead) IDP_BlendDataRead(reader, &addon->prop); } + LISTBASE_FOREACH (bUserMenusGroup *, umg, &user->user_menus_group) { + BLO_read_list(reader, &umg->menus); + BKE_blender_user_menus_group_idname_update(umg); + LISTBASE_FOREACH (bUserMenu *, um, &umg->menus) { + BLO_read_list(reader, &um->items); + read_usermenuitems(reader, &um->items, NULL); + } + } + + if (user->user_menus.first != NULL && user->user_menus_group.first == NULL) { + bUserMenusGroup *umg = BKU_blender_user_menu_default(); + BLI_addtail(&user->user_menus_group, umg); + user->runtime.umg_select = umg; + umg->menus = user->user_menus; + BLI_listbase_clear(&user->user_menus); + } + // XXX user->uifonts.first = user->uifonts.last = NULL; @@ -8976,6 +9011,10 @@ static BHead *read_userdef(BlendFileData *bfd, FileData *fd, BHead *bhead) /* Clear runtime data. */ user->runtime.is_dirty = false; user->edit_studio_light = 0; + user->runtime.um_space_select = 1; + user->runtime.um_context_select = 1; + user->runtime.um_item_select = NULL; + user->runtime.umg_select = user->user_menus_group.first; /* free fd->datamap again */ oldnewmap_clear(fd->datamap); diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c index a9c92719a33..15c6e25985b 100644 --- a/source/blender/blenloader/intern/writefile.c +++ b/source/blender/blenloader/intern/writefile.c @@ -980,7 +980,37 @@ static void write_keymapitem(BlendWriter *writer, const wmKeyMapItem *kmi) } } +static void write_usermenuitems(BlendWriter *writer, const ListBase *lb) +{ + LISTBASE_FOREACH (const bUserMenuItem *, umi, lb) { + if (umi->type == USER_MENU_TYPE_OPERATOR) { + const bUserMenuItem_Op *umi_op = (const bUserMenuItem_Op *)umi; + BLO_write_struct(writer, bUserMenuItem_Op, umi_op); + if (umi_op->prop) { + IDP_WriteProperty(umi_op->prop, writer); + } + } + else if (umi->type == USER_MENU_TYPE_MENU) { + const bUserMenuItem_Menu *umi_mt = (const bUserMenuItem_Menu *)umi; + BLO_write_struct(writer, bUserMenuItem_Menu, umi_mt); + } + else if (umi->type == USER_MENU_TYPE_PROP) { + const bUserMenuItem_Prop *umi_pr = (const bUserMenuItem_Prop *)umi; + BLO_write_struct(writer, bUserMenuItem_Prop, umi_pr); + } + else if (umi->type == USER_MENU_TYPE_SUBMENU) { + const bUserMenuItem_SubMenu *umi_sm = (const bUserMenuItem_SubMenu *)umi; + BLO_write_struct(writer, bUserMenuItem_SubMenu, umi_sm); + write_usermenuitems(writer, &umi_sm->items); + } + else { + BLO_write_struct(writer, bUserMenuItem, umi); + } + } +} + static void write_userdef(BlendWriter *writer, const UserDef *userdef) + { writestruct(writer->wd, USER, UserDef, 1, userdef); @@ -1013,30 +1043,6 @@ static void write_userdef(BlendWriter *writer, const UserDef *userdef) } } - LISTBASE_FOREACH (const bUserMenu *, um, &userdef->user_menus) { - BLO_write_struct(writer, bUserMenu, um); - LISTBASE_FOREACH (const bUserMenuItem *, umi, &um->items) { - if (umi->type == USER_MENU_TYPE_OPERATOR) { - const bUserMenuItem_Op *umi_op = (const bUserMenuItem_Op *)umi; - BLO_write_struct(writer, bUserMenuItem_Op, umi_op); - if (umi_op->prop) { - IDP_BlendWrite(writer, umi_op->prop); - } - } - else if (umi->type == USER_MENU_TYPE_MENU) { - const bUserMenuItem_Menu *umi_mt = (const bUserMenuItem_Menu *)umi; - BLO_write_struct(writer, bUserMenuItem_Menu, umi_mt); - } - else if (umi->type == USER_MENU_TYPE_PROP) { - const bUserMenuItem_Prop *umi_pr = (const bUserMenuItem_Prop *)umi; - BLO_write_struct(writer, bUserMenuItem_Prop, umi_pr); - } - else { - BLO_write_struct(writer, bUserMenuItem, umi); - } - } - } - LISTBASE_FOREACH (const bAddon *, bext, &userdef->addons) { BLO_write_struct(writer, bAddon, bext); if (bext->prop) { @@ -1048,6 +1054,14 @@ static void write_userdef(BlendWriter *writer, const UserDef *userdef) BLO_write_struct(writer, bPathCompare, path_cmp); } + LISTBASE_FOREACH (const bUserMenusGroup *, umg, &userdef->user_menus_group) { + BLO_write_struct(writer, bUserMenusGroup, umg); + LISTBASE_FOREACH (const bUserMenu *, um, &umg->menus) { + BLO_write_struct(writer, bUserMenu, um); + write_usermenuitems(writer, &um->items); + } + } + LISTBASE_FOREACH (const uiStyle *, style, &userdef->uistyles) { BLO_write_struct(writer, uiStyle, style); } diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index d9c7128c2ee..017253facd5 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -374,8 +374,12 @@ bool ED_operator_camera(struct bContext *C); /* screen_user_menu.c */ -bUserMenu **ED_screen_user_menus_find(const struct bContext *C, uint *r_len); -struct bUserMenu *ED_screen_user_menu_ensure(struct bContext *C); +struct bUserMenusGroup *ED_screen_user_menus_group_find(int id); +struct bUserMenu **ED_screen_user_menus_find_menu(const struct bContext *C, + uint *r_len, + struct bUserMenusGroup *umg); +struct bUserMenu **ED_screen_user_menus_find(const struct bContext *C, uint *r_len, int id); +struct bUserMenu *ED_screen_user_menu_ensure(struct bContext *C, int id); struct bUserMenuItem_Op *ED_screen_user_menu_item_find_operator(struct ListBase *lb, const struct wmOperatorType *ot, @@ -396,7 +400,8 @@ void ED_screen_user_menu_item_add_operator(struct ListBase *lb, void ED_screen_user_menu_item_add_menu(struct ListBase *lb, const char *ui_name, const struct MenuType *mt); -void ED_screen_user_menu_item_add_prop(ListBase *lb, +void ED_screen_user_menu_item_add_prop(struct bContext *C, + ListBase *lb, const char *ui_name, const char *context_data_path, const char *prop_id, @@ -404,6 +409,14 @@ void ED_screen_user_menu_item_add_prop(ListBase *lb, void ED_screen_user_menu_item_remove(struct ListBase *lb, struct bUserMenuItem *umi); void ED_screen_user_menu_register(void); +bool screen_user_menu_draw_items(const struct bContext *C, + struct uiLayout *layout, + struct ListBase *lb, + char type); +void screen_user_menu_draw_begin(struct bContext *C, + struct uiLayout *layout, + char type, + struct bUserMenusGroup *umg); /* Cache display helpers */ diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 367f7965026..80e81445618 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -2113,6 +2113,7 @@ void uiTemplateCacheFile(uiLayout *layout, const struct bContext *C, struct PointerRNA *ptr, const char *propname); +void uiTemplateUserMenuItemProperties(uiLayout *layout, PointerRNA *ptr); /* Default UIList class name, keep in sync with its declaration in bl_ui/__init__.py */ #define UI_UL_DEFAULT_CLASS_NAME "UI_UL_list" diff --git a/source/blender/editors/interface/interface_context_menu.c b/source/blender/editors/interface/interface_context_menu.c index 02a9c3742d7..3c3cb77fc85 100644 --- a/source/blender/editors/interface/interface_context_menu.c +++ b/source/blender/editors/interface/interface_context_menu.c @@ -437,7 +437,8 @@ static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um) } const char *prop_id = RNA_property_identifier(but->rnaprop); /* Note, ignore 'drawstr', use property idname always. */ - ED_screen_user_menu_item_add_prop(&um->items, "", member_id_data_path, prop_id, but->rnaindex); + ED_screen_user_menu_item_add_prop( + C, &um->items, "", member_id_data_path, prop_id, but->rnaindex); if (data_path) { MEM_freeN((void *)data_path); } @@ -453,7 +454,7 @@ static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um) static void popup_user_menu_add_or_replace_func(bContext *C, void *arg1, void *UNUSED(arg2)) { uiBut *but = arg1; - bUserMenu *um = ED_screen_user_menu_ensure(C); + bUserMenu *um = ED_screen_user_menu_ensure(C, 0); U.runtime.is_dirty = true; ui_but_user_menu_add(C, but, um); } @@ -983,7 +984,7 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but) bool item_found = false; uint um_array_len; - bUserMenu **um_array = ED_screen_user_menus_find(C, &um_array_len); + bUserMenu **um_array = ED_screen_user_menus_find(C, &um_array_len, 0); for (int um_index = 0; um_index < um_array_len; um_index++) { bUserMenu *um = um_array[um_index]; if (um == NULL) { diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 8962755ea90..5f6fe35e98e 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -7390,3 +7390,80 @@ void uiTemplateFileSelectPath(uiLayout *layout, bContext *C, FileSelectParams *p } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Keymap Template + * \{ */ + +static void template_user_menu_item_properties(uiLayout *layout, + const char *title, + PointerRNA *ptr) +{ + uiLayout *flow, *box, *row; + + uiItemS(layout); + + if (title) { + uiItemL(layout, title, ICON_NONE); + } + + flow = uiLayoutColumnFlow(layout, 2, false); + + int i = 0; + RNA_STRUCT_BEGIN_SKIP_RNA_TYPE (ptr, prop) { + const bool is_set = RNA_property_is_set(ptr, prop); + uiBut *but; + + // recurse for nested properties + if (RNA_property_type(prop) == PROP_POINTER) { + PointerRNA propptr = RNA_property_pointer_get(ptr, prop); + + if (propptr.data && RNA_struct_is_a(propptr.type, &RNA_OperatorProperties)) { + const char *name = RNA_property_ui_name(prop); + template_user_menu_item_properties(layout, name, &propptr); + continue; + } + } + + box = uiLayoutBox(flow); + uiLayoutSetActive(box, is_set); + row = uiLayoutRow(box, false); + + // property value + uiItemFullR(row, ptr, prop, -1, 0, 0, NULL, ICON_NONE); + + if (is_set) { + // unset operator + uiBlock *block = uiLayoutGetBlock(row); + UI_block_emboss_set(block, UI_EMBOSS_NONE); + but = uiDefIconButO(block, + UI_BTYPE_BUT, + "UI_OT_unset_property_button", + WM_OP_EXEC_DEFAULT, + ICON_X, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL); + but->rnapoin = *ptr; + but->rnaprop = prop; + UI_block_emboss_set(block, UI_EMBOSS); + } + } + RNA_STRUCT_END; +} + +void uiTemplateUserMenuItemProperties(uiLayout *layout, PointerRNA *ptr) +{ + PointerRNA propptr = RNA_pointer_get(ptr, "prop"); + + if (propptr.data) { + uiBut *but = uiLayoutGetBlock(layout)->buttons.last; + + WM_operator_properties_sanitize(&propptr, false); + template_user_menu_item_properties(layout, NULL, &propptr); + } +} + +/** \} */
\ No newline at end of file diff --git a/source/blender/editors/screen/screen_user_menu.c b/source/blender/editors/screen/screen_user_menu.c index 733e8b694a6..4ae15a8dce1 100644 --- a/source/blender/editors/screen/screen_user_menu.c +++ b/source/blender/editors/screen/screen_user_menu.c @@ -67,10 +67,23 @@ static const char *screen_menu_context_string(const bContext *C, const SpaceLink /** \} */ /* -------------------------------------------------------------------- */ -/** \name Menu Type +/** \name Menu Group * \{ */ -bUserMenu **ED_screen_user_menus_find(const bContext *C, uint *r_len) +bUserMenusGroup *ED_screen_user_menus_group_find(int id) +{ + int index = 0; + LISTBASE_FOREACH (bUserMenusGroup *, umg, &U.user_menus_group) { + if (index == id) + return umg; + index++; + } + return NULL; +} + +/** \} */ + +bUserMenu **ED_screen_user_menus_find_menu(const bContext *C, uint *r_len, bUserMenusGroup *umg) { SpaceLink *sl = CTX_wm_space_data(C); @@ -83,23 +96,33 @@ bUserMenu **ED_screen_user_menus_find(const bContext *C, uint *r_len) const char *context = screen_menu_context_string(C, sl); uint array_len = 3; bUserMenu **um_array = MEM_calloc_arrayN(array_len, sizeof(*um_array), __func__); - um_array[0] = BKE_blender_user_menu_find(&U.user_menus, sl->spacetype, context); + um_array[0] = BKE_blender_user_menu_find(&umg->menus, sl->spacetype, context); um_array[1] = (sl->spacetype != SPACE_TOPBAR) ? - BKE_blender_user_menu_find(&U.user_menus, SPACE_TOPBAR, context_mode) : + BKE_blender_user_menu_find(&umg->menus, SPACE_TOPBAR, context_mode) : NULL; um_array[2] = (sl->spacetype == SPACE_VIEW3D) ? - BKE_blender_user_menu_find(&U.user_menus, SPACE_PROPERTIES, context_mode) : + BKE_blender_user_menu_find(&umg->menus, SPACE_PROPERTIES, context_mode) : NULL; - *r_len = array_len; return um_array; } -bUserMenu *ED_screen_user_menu_ensure(bContext *C) +bUserMenu **ED_screen_user_menus_find(const bContext *C, uint *r_len, int id) +{ + bUserMenusGroup *umg = ED_screen_user_menus_group_find(id); + if (!umg) + return NULL; + return ED_screen_user_menus_find_menu(C, r_len, umg); +} + +bUserMenu *ED_screen_user_menu_ensure(bContext *C, int id) { + bUserMenusGroup *umg = ED_screen_user_menus_group_find(id); + if (!umg) + return NULL; SpaceLink *sl = CTX_wm_space_data(C); const char *context = screen_menu_context_string(C, sl); - return BKE_blender_user_menu_ensure(&U.user_menus, sl->spacetype, context); + return BKE_blender_user_menu_ensure(&umg->menus, sl->spacetype, context); } /** \} */ @@ -169,6 +192,9 @@ void ED_screen_user_menu_item_add_operator(ListBase *lb, STRNCPY(umi_op->item.ui_name, ui_name); } STRNCPY(umi_op->op_idname, ot->idname); + umi_op->ptr = NULL; + umi_op->prop = NULL; + WM_operator_properties_alloc(&(umi_op->ptr), &(umi_op->prop), ot->idname); umi_op->prop = prop ? IDP_CopyProperty(prop) : NULL; } @@ -182,7 +208,8 @@ void ED_screen_user_menu_item_add_menu(ListBase *lb, const char *ui_name, const STRNCPY(umi_mt->mt_idname, mt->idname); } -void ED_screen_user_menu_item_add_prop(ListBase *lb, +void ED_screen_user_menu_item_add_prop(bContext *C, + ListBase *lb, const char *ui_name, const char *context_data_path, const char *prop_id, @@ -194,6 +221,39 @@ void ED_screen_user_menu_item_add_prop(ListBase *lb, STRNCPY(umi_pr->context_data_path, context_data_path); STRNCPY(umi_pr->prop_id, prop_id); umi_pr->prop_index = prop_index; + + /* Copy current context name into ui_name if null */ + if (ui_name && ui_name[0] != '\0') + return; + char *data_path = strchr(umi_pr->context_data_path, '.'); + if (data_path) { + *data_path = '\0'; + } + + PointerRNA ptr = CTX_data_pointer_get(C, umi_pr->context_data_path); + if (ptr.type == NULL) { + PointerRNA ctx_ptr; + RNA_pointer_create(NULL, &RNA_Context, (void *)C, &ctx_ptr); + if (!RNA_path_resolve_full(&ctx_ptr, umi_pr->context_data_path, &ptr, NULL, NULL)) { + ptr.type = NULL; + } + } + if (data_path) { + *data_path = '.'; + data_path += 1; + } + + if (ptr.type != NULL) { + PropertyRNA *prop = NULL; + PointerRNA prop_ptr = ptr; + if ((data_path == NULL) || RNA_path_resolve_full(&ptr, data_path, &prop_ptr, NULL, NULL)) { + prop = RNA_struct_find_property(&prop_ptr, umi_pr->prop_id); + } + if (prop) { + const char *name = RNA_property_ui_name(prop); + BLI_strncpy(umi_pr->item.ui_name, name, FILE_MAX); + } + } } void ED_screen_user_menu_item_remove(ListBase *lb, bUserMenuItem *umi) @@ -208,97 +268,146 @@ void ED_screen_user_menu_item_remove(ListBase *lb, bUserMenuItem *umi) /** \name Menu Definition * \{ */ -static void screen_user_menu_draw(const bContext *C, Menu *menu) +static void screen_user_menu_draw_submenu(bContext *C, uiLayout *layout, void *arg) +{ + ListBase *lb = (ListBase *)arg; + + screen_user_menu_draw_items(C, layout, lb, false); +} + +bool screen_user_menu_draw_items(const bContext *C, uiLayout *layout, ListBase *lb, char type) { /* Enable when we have the ability to edit menus. */ - const bool show_missing = false; char label[512]; - - uint um_array_len; - bUserMenu **um_array = ED_screen_user_menus_find(C, &um_array_len); + const bool show_missing = type; bool is_empty = true; - for (int um_index = 0; um_index < um_array_len; um_index++) { - bUserMenu *um = um_array[um_index]; - if (um == NULL) { - continue; - } - LISTBASE_FOREACH (bUserMenuItem *, umi, &um->items) { - const char *ui_name = umi->ui_name[0] ? umi->ui_name : NULL; - if (umi->type == USER_MENU_TYPE_OPERATOR) { - bUserMenuItem_Op *umi_op = (bUserMenuItem_Op *)umi; - wmOperatorType *ot = WM_operatortype_find(umi_op->op_idname, false); - if (ot != NULL) { - IDProperty *prop = umi_op->prop ? IDP_CopyProperty(umi_op->prop) : NULL; - uiItemFullO_ptr(menu->layout, ot, ui_name, ICON_NONE, prop, umi_op->opcontext, 0, NULL); - is_empty = false; - } - else { - if (show_missing) { - SNPRINTF(label, "Missing: %s", umi_op->op_idname); - uiItemL(menu->layout, label, ICON_NONE); - } - } + + int i = 0; + + LISTBASE_FOREACH (bUserMenuItem *, umi, lb) { + if (type == 1 && i > 7) + return is_empty; + const char *ui_name = umi->ui_name[0] ? umi->ui_name : NULL; + if (umi->type == USER_MENU_TYPE_OPERATOR) { + bUserMenuItem_Op *umi_op = (bUserMenuItem_Op *)umi; + wmOperatorType *ot = WM_operatortype_find(umi_op->op_idname, false); + if (ot != NULL) { + IDProperty *prop = umi_op->prop ? IDP_CopyProperty(umi_op->prop) : NULL; + uiItemFullO_ptr(layout, ot, ui_name, umi->icon, prop, umi_op->opcontext, 0, NULL); + is_empty = false; } - else if (umi->type == USER_MENU_TYPE_MENU) { - bUserMenuItem_Menu *umi_mt = (bUserMenuItem_Menu *)umi; - MenuType *mt = WM_menutype_find(umi_mt->mt_idname, false); - if (mt != NULL) { - uiItemM_ptr(menu->layout, mt, ui_name, ICON_NONE); - is_empty = false; + else { + if (show_missing) { + SNPRINTF(label, "Missing: %s", umi_op->op_idname); + uiItemL(layout, label, ICON_NONE); } - else { - if (show_missing) { - SNPRINTF(label, "Missing: %s", umi_mt->mt_idname); - uiItemL(menu->layout, label, ICON_NONE); - } + i--; + } + } + else if (umi->type == USER_MENU_TYPE_MENU) { + bUserMenuItem_Menu *umi_mt = (bUserMenuItem_Menu *)umi; + MenuType *mt = WM_menutype_find(umi_mt->mt_idname, false); + if (mt != NULL) { + uiItemM_ptr(layout, mt, ui_name, umi->icon); + is_empty = false; + } + else { + if (show_missing) { + SNPRINTF(label, "Missing: %s", umi_mt->mt_idname); + uiItemL(layout, label, ICON_NONE); } + i--; } - else if (umi->type == USER_MENU_TYPE_PROP) { - bUserMenuItem_Prop *umi_pr = (bUserMenuItem_Prop *)umi; + } + else if (umi->type == USER_MENU_TYPE_SUBMENU) { + bUserMenuItem_SubMenu *umi_mt = (bUserMenuItem_SubMenu *)umi; - char *data_path = strchr(umi_pr->context_data_path, '.'); - if (data_path) { - *data_path = '\0'; - } - PointerRNA ptr = CTX_data_pointer_get(C, umi_pr->context_data_path); - if (ptr.type == NULL) { - PointerRNA ctx_ptr; - RNA_pointer_create(NULL, &RNA_Context, (void *)C, &ctx_ptr); - if (!RNA_path_resolve_full(&ctx_ptr, umi_pr->context_data_path, &ptr, NULL, NULL)) { - ptr.type = NULL; - } - } - if (data_path) { - *data_path = '.'; - data_path += 1; - } + uiItemMenuF( + layout, ui_name, umi->icon, &screen_user_menu_draw_submenu, (void *)&umi_mt->items); + is_empty = false; + } + else if (umi->type == USER_MENU_TYPE_PROP) { + bUserMenuItem_Prop *umi_pr = (bUserMenuItem_Prop *)umi; - bool ok = false; - if (ptr.type != NULL) { - PropertyRNA *prop = NULL; - PointerRNA prop_ptr = ptr; - if ((data_path == NULL) || - RNA_path_resolve_full(&ptr, data_path, &prop_ptr, NULL, NULL)) { - prop = RNA_struct_find_property(&prop_ptr, umi_pr->prop_id); - if (prop) { - ok = true; - uiItemFullR( - menu->layout, &prop_ptr, prop, umi_pr->prop_index, 0, 0, ui_name, ICON_NONE); - is_empty = false; - } - } + char *data_path = strchr(umi_pr->context_data_path, '.'); + if (data_path) { + *data_path = '\0'; + } + PointerRNA ptr = CTX_data_pointer_get(C, umi_pr->context_data_path); + if (ptr.type == NULL) { + PointerRNA ctx_ptr; + RNA_pointer_create(NULL, &RNA_Context, (void *)C, &ctx_ptr); + if (!RNA_path_resolve_full(&ctx_ptr, umi_pr->context_data_path, &ptr, NULL, NULL)) { + ptr.type = NULL; } - if (!ok) { - if (show_missing) { - SNPRINTF(label, "Missing: %s.%s", umi_pr->context_data_path, umi_pr->prop_id); - uiItemL(menu->layout, label, ICON_NONE); + } + if (data_path) { + *data_path = '.'; + data_path += 1; + } + + bool ok = false; + if (ptr.type != NULL) { + PropertyRNA *prop = NULL; + PointerRNA prop_ptr = ptr; + if ((data_path == NULL) || RNA_path_resolve_full(&ptr, data_path, &prop_ptr, NULL, NULL)) { + prop = RNA_struct_find_property(&prop_ptr, umi_pr->prop_id); + if (prop) { + ok = true; + uiItemFullR(layout, &prop_ptr, prop, umi_pr->prop_index, 0, 0, ui_name, umi->icon); + is_empty = false; } } } - else if (umi->type == USER_MENU_TYPE_SEP) { - uiItemS(menu->layout); + if (!ok) { + if (show_missing) { + SNPRINTF(label, "Missing: %s.%s", umi_pr->context_data_path, umi_pr->prop_id); + uiItemL(layout, label, ICON_NONE); + } + i--; } } + else if (umi->type == USER_MENU_TYPE_SEP) { + uiItemS(layout); + } + i++; + } + return is_empty; +} + +void screen_user_menu_draw_begin(bContext *C, uiLayout *layout, char type, bUserMenusGroup *umg) +{ + uint um_array_len; + bUserMenu **um_array = ED_screen_user_menus_find_menu(C, &um_array_len, umg); + bool is_empty = true; + for (int um_index = 0; um_index < um_array_len; um_index++) { + bUserMenu *um = um_array[um_index]; + if (um == NULL) { + continue; + } + is_empty = screen_user_menu_draw_items(C, layout, &um->items, true) && is_empty; + } + if (um_array) { + MEM_freeN(um_array); + } + + if (is_empty && type == 0) { + uiItemL(layout, TIP_("No menu items found"), ICON_NONE); + uiItemL(layout, TIP_("Right click on buttons to add them to this menu"), ICON_NONE); + } +} + +static void screen_user_menu_draw(const bContext *C, Menu *menu) +{ + uint um_array_len; + bUserMenu **um_array = ED_screen_user_menus_find(C, &um_array_len, 0); + bool is_empty = true; + for (int um_index = 0; um_index < um_array_len; um_index++) { + bUserMenu *um = um_array[um_index]; + if (um == NULL) { + continue; + } + is_empty = is_empty || screen_user_menu_draw_items(C, menu->layout, &um->items, true); } if (um_array) { MEM_freeN(um_array); diff --git a/source/blender/editors/space_userpref/space_userpref.c b/source/blender/editors/space_userpref/space_userpref.c index 3efdee9cec9..ffa8f8ebcaf 100644 --- a/source/blender/editors/space_userpref/space_userpref.c +++ b/source/blender/editors/space_userpref/space_userpref.c @@ -136,6 +136,7 @@ static void userpref_main_region_layout(const bContext *C, ARegion *region) i = 0; } const char *id = items[i].identifier; + BLI_assert(strlen(id) < sizeof(id_lower)); STRNCPY(id_lower, id); BLI_str_tolower_ascii(id_lower, strlen(id_lower)); diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index ec46d2680ca..ee7af5dd56b 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -515,27 +515,42 @@ typedef struct bPathCompare { char _pad0[7]; } bPathCompare; +typedef struct bUserMenusGroup { + struct bUserMenusGroup *next, *prev; + /* bUserMenu */ + char idname[64]; + char name[64]; + char type; + char _pad0[7]; + struct ListBase menus; +} bUserMenusGroup; + typedef struct bUserMenu { struct bUserMenu *next, *prev; char space_type; char _pad0[7]; char context[64]; /* bUserMenuItem */ - ListBase items; + struct ListBase items; } bUserMenu; /** May be part of #bUserMenu or other list. */ typedef struct bUserMenuItem { struct bUserMenuItem *next, *prev; + struct bUserMenuItem_SubMenu *parent; + int icon; char ui_name[64]; char type; - char _pad0[7]; + // editor + char is_selected; + char _pad0[2]; } bUserMenuItem; typedef struct bUserMenuItem_Op { bUserMenuItem item; char op_idname[64]; struct IDProperty *prop; + struct PointerRNA *ptr; char opcontext; char _pad0[7]; } bUserMenuItem_Op; @@ -545,6 +560,12 @@ typedef struct bUserMenuItem_Menu { char mt_idname[64]; } bUserMenuItem_Menu; +typedef struct bUserMenuItem_SubMenu { + bUserMenuItem item; + struct ListBase items; + char name[64]; +} bUserMenuItem_SubMenu; + typedef struct bUserMenuItem_Prop { bUserMenuItem item; char context_data_path[256]; @@ -558,6 +579,7 @@ enum { USER_MENU_TYPE_OPERATOR = 2, USER_MENU_TYPE_MENU = 3, USER_MENU_TYPE_PROP = 4, + USER_MENU_TYPE_SUBMENU = 5, }; typedef struct SolidLight { @@ -582,7 +604,14 @@ typedef struct WalkNavigation { typedef struct UserDef_Runtime { char is_dirty; - char _pad0[7]; + + /* User menu editor runtime datas */ + char um_space_select; + char um_context_select; + char um_expanded; + char _pad0[4]; + struct bUserMenuItem *um_item_select; + struct bUserMenusGroup *umg_select; } UserDef_Runtime; /** @@ -722,8 +751,10 @@ typedef struct UserDef { struct ListBase user_keyconfig_prefs; struct ListBase addons; struct ListBase autoexec_paths; - /** #bUserMenu. */ + /** #bUserMenu : deprecated, keep for compatibility. */ struct ListBase user_menus; + /** #bUserMenuGroups. */ + struct ListBase user_menus_group; char keyconfigstr[64]; @@ -913,16 +944,17 @@ typedef enum eUserPref_Section { USER_SECTION_ADDONS = 6, USER_SECTION_LIGHT = 7, USER_SECTION_KEYMAP = 8, + USER_SECTION_USER_MENUS = 9, #ifdef WITH_USERDEF_WORKSPACES - USER_SECTION_WORKSPACE_CONFIG = 9, - USER_SECTION_WORKSPACE_ADDONS = 10, - USER_SECTION_WORKSPACE_KEYMAPS = 11, + USER_SECTION_WORKSPACE_CONFIG = 10, + USER_SECTION_WORKSPACE_ADDONS = 11, + USER_SECTION_WORKSPACE_KEYMAPS = 12, #endif - USER_SECTION_VIEWPORT = 12, - USER_SECTION_ANIMATION = 13, - USER_SECTION_NAVIGATION = 14, - USER_SECTION_FILE_PATHS = 15, - USER_SECTION_EXPERIMENTAL = 16, + USER_SECTION_VIEWPORT = 13, + USER_SECTION_ANIMATION = 14, + USER_SECTION_NAVIGATION = 15, + USER_SECTION_FILE_PATHS = 16, + USER_SECTION_EXPERIMENTAL = 17, } eUserPref_Section; /** #UserDef_SpaceData.flag (State of the user preferences UI). */ diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 6acd9d16f80..7586cce2275 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -474,6 +474,7 @@ extern StructRNA RNA_PreferencesEdit; extern StructRNA RNA_PreferencesFilePaths; extern StructRNA RNA_PreferencesInput; extern StructRNA RNA_PreferencesKeymap; +extern StructRNA RNA_PreferencesUserMenus; extern StructRNA RNA_PreferencesSystem; extern StructRNA RNA_PreferencesView; extern StructRNA RNA_Property; diff --git a/source/blender/makesrna/intern/rna_define.c b/source/blender/makesrna/intern/rna_define.c index e94dad59176..7c699de7035 100644 --- a/source/blender/makesrna/intern/rna_define.c +++ b/source/blender/makesrna/intern/rna_define.c @@ -1869,7 +1869,7 @@ void RNA_def_property_enum_items(PropertyRNA *prop, const EnumPropertyItem *item { StructRNA *srna = DefRNA.laststruct; int i, defaultfound = 0; - + switch (prop->type) { case PROP_ENUM: { EnumPropertyRNA *eprop = (EnumPropertyRNA *)prop; diff --git a/source/blender/makesrna/intern/rna_ui_api.c b/source/blender/makesrna/intern/rna_ui_api.c index 8dfa54b95da..8a85b924505 100644 --- a/source/blender/makesrna/intern/rna_ui_api.c +++ b/source/blender/makesrna/intern/rna_ui_api.c @@ -1639,6 +1639,12 @@ void RNA_api_ui_layout(StructRNA *srna) RNA_def_property_ui_text(parm, "Item", ""); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); api_ui_item_common_text(func); + + /* User menu template */ + func = RNA_def_function( + srna, "template_user_menu_item_properties", "uiTemplateUserMenuItemProperties"); + parm = RNA_def_pointer(func, "item", "um_item_op", "", ""); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); } #endif diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 699e08302e7..7d49cadbeed 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -88,6 +88,7 @@ const EnumPropertyItem rna_enum_preference_section_items[] = { {USER_SECTION_INPUT, "INPUT", 0, "Input", ""}, {USER_SECTION_NAVIGATION, "NAVIGATION", 0, "Navigation", ""}, {USER_SECTION_KEYMAP, "KEYMAP", 0, "Keymap", ""}, + {USER_SECTION_USER_MENUS, "USER_MENUS", 0, "User Menus", ""}, {0, "", 0, NULL, NULL}, {USER_SECTION_SYSTEM, "SYSTEM", 0, "System", ""}, {USER_SECTION_SAVE_LOAD, "SAVE_LOAD", 0, "Save & Load", ""}, @@ -177,6 +178,7 @@ static const EnumPropertyItem rna_enum_userdef_viewport_aa_items[] = { # include "DNA_screen_types.h" # include "BKE_blender.h" +# include "BKE_blender_user_menu.h" # include "BKE_global.h" # include "BKE_idprop.h" # include "BKE_image.h" @@ -186,6 +188,8 @@ static const EnumPropertyItem rna_enum_userdef_viewport_aa_items[] = { # include "BKE_pbvh.h" # include "BKE_screen.h" +# include "ED_screen.h" + # include "DEG_depsgraph.h" # include "GPU_extensions.h" @@ -531,6 +535,11 @@ static PointerRNA rna_UserDef_keymap_get(PointerRNA *ptr) return rna_pointer_inherit_refine(ptr, &RNA_PreferencesKeymap, ptr->data); } +static PointerRNA rna_UserDef_user_menus_get(PointerRNA *ptr) +{ + return rna_pointer_inherit_refine(ptr, &RNA_PreferencesUserMenus, ptr->data); +} + static PointerRNA rna_UserDef_filepaths_get(PointerRNA *ptr) { return rna_pointer_inherit_refine(ptr, &RNA_PreferencesFilePaths, ptr->data); @@ -1068,6 +1077,549 @@ static void rna_UserDef_studiolight_light_ambient_get(PointerRNA *ptr, float *va copy_v3_v3(values, sl->light_ambient); } +/* User Menus Functions */ + +static bUserMenu *rna_UserDef_usermenus_get_current(UserDef *userdef, bool ensure) +{ + const char **contexts_list = CTX_data_list_mode_string(); +# if 0 /* UNUSED */ + ListBase *umg_list = &userdef->user_menus_group; +# endif + + bUserMenusGroup *umg = userdef->runtime.umg_select; + bUserMenu *bum = NULL; + + if (!umg) + return NULL; + if (userdef->runtime.um_space_select > 0) { + if (umg->type) { + bum = BKE_blender_user_menu_find(&umg->menus, + userdef->runtime.um_space_select, + contexts_list[userdef->runtime.um_context_select]); + if (!bum) { + bum = BKE_blender_user_menu_ensure(&umg->menus, + userdef->runtime.um_space_select, + contexts_list[userdef->runtime.um_context_select]); + for (int i = 0; i < 8; i++) { + BKE_blender_user_menu_item_add(&bum->items, USER_MENU_TYPE_SEP); + } + } + } + else { + if (ensure) { + bum = BKE_blender_user_menu_ensure(&umg->menus, + userdef->runtime.um_space_select, + contexts_list[userdef->runtime.um_context_select]); + } + else { + bum = BKE_blender_user_menu_find(&umg->menus, + userdef->runtime.um_space_select, + contexts_list[userdef->runtime.um_context_select]); + } + } + } + return bum; +} + +/*static bUserMenuItem *rna_UserDef_usermenus_get_current_item(UserDef *userdef) +{ + int id = userdef->runtime.um_item_select - 1; + if (id < 0) return NULL; + + bUserMenu *bum = rna_UserDef_usermenus_get_current(userdef, false); + if (bum) { + + ListBase *lb = &bum->items; + int i = 0; + for (bUserMenuItem *umi = lb->first; umi; umi = umi->next, i++) { + if (i == id) + return umi; + } + } + + return NULL; +}*/ + +static bUserMenusGroup *rna_UserDef_usermenus_get_group(UserDef *userdef, const char *idname) +{ + return BKE_blender_user_menus_group_find(&userdef->user_menus_group, idname); +} + +static void rna_UserDef_usermenus_set_group(UserDef *userdef, bUserMenusGroup *umg) +{ + userdef->runtime.umg_select = umg; +} + +static void rna_UserDef_usermenus_add_group(UserDef *userdef) +{ + bUserMenusGroup *umg = BKE_blender_user_menus_group_new("new menu"); + BLI_addtail(&userdef->user_menus_group, umg); + userdef->runtime.umg_select = umg; +} + +static void rna_UserDef_usermenus_remove_group(UserDef *userdef) +{ + bUserMenusGroup *umg = userdef->runtime.umg_select; + + userdef->runtime.umg_select = umg->prev; + if (!userdef->runtime.umg_select) { + userdef->runtime.umg_select = umg->next; + if (!userdef->runtime.umg_select) { + return; + } + } + + BLI_remlink(&userdef->user_menus_group, umg); + BKE_blender_user_menu_free_list(&umg->menus); + MEM_freeN(umg); +} + +static bool rna_UserDef_usermenus_has_item(UserDef *userdef) +{ + bUserMenu *bum = rna_UserDef_usermenus_get_current(userdef, false); + if (!bum) + return false; + + ListBase *lb = &bum->items; + bUserMenuItem *umi = lb->first; + if (!umi) + return false; + return true; +} + +static int rna_UserDef_usermenus_items_length(UserDef *userdef) +{ + bUserMenu *bum = rna_UserDef_usermenus_get_current(userdef, false); + if (!bum) + return 0; + + ListBase *lb = &bum->items; + int i = 0; + for (bUserMenuItem *umi = lb->first; umi; umi = umi->next, i++) + ; + return i; +} + +static int rna_UserDef_usermenus_spacetypes_get(PointerRNA *ptr) +{ + UserDef *userdef = (UserDef *)ptr->data; + int id = userdef->runtime.um_space_select; + id = (id <= 0) ? USER_SECTION_EDITING : id; + return id; +} + +static void rna_UserDef_usermenus_active_item_set(PointerRNA *ptr, bool value) +{ + bUserMenuItem *umi = (bUserMenuItem *)ptr->data; + + if (value) { + U.runtime.um_item_select = umi; + } + umi->is_selected = value; +} + +static const EnumPropertyItem *rna_UserDef_usermenus_spacetypes_itemf(bContext *UNUSED(C), + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + bool *r_free) +{ + int totitem = 0; + EnumPropertyItem *item = NULL; + int i; + const ListBase *spacetypes = BKE_spacetypes_list(); + + SpaceType *st = NULL; + for (i = 0, st = spacetypes->first; st; st = st->next, i++) { + int id = st->spaceid; + EnumPropertyItem new_item = {id, st->name, 0, st->name, st->name}; + RNA_enum_item_add(&item, &totitem, &new_item); + } + +# ifndef NDEBUG + if (i == 0) { + EnumPropertyItem new_item = {i, "NO_SPACETYPE", 0, "No spacetype available", ""}; + RNA_enum_item_add(&item, &totitem, &new_item); + } +# endif + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + +static const EnumPropertyItem *rna_UserDef_usermenus_contexts_itemf(bContext *UNUSED(C), + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + bool *r_free) +{ + static const char *contexts_list[] = { + "mesh edit", "curve edit", "surface edit", "text edit", + "armature edit", "mball edit", "lattice edit", "pose mode", + "sculpt mode", "weight paint", "vertex paint", "image paint", + "particle mode", "object mode", "greasepencil paint", "greasepencil edit", + "greasepencil sculpt", "greasepencil weight", "greasepencil vertex", NULL, + }; + + int totitem = 0; + EnumPropertyItem *item = NULL; + int i; + + for (i = 0; contexts_list[i]; i++) { + EnumPropertyItem new_item = {i, contexts_list[i], 0, contexts_list[i], contexts_list[i]}; + RNA_enum_item_add(&item, &totitem, &new_item); + } + +# ifndef NDEBUG + if (i == 0) { + EnumPropertyItem new_item = {i, "NO_CONTEXT", 0, "No context available", ""}; + RNA_enum_item_add(&item, &totitem, &new_item); + } +# endif + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + +static const EnumPropertyItem *rna_UserDef_icons_itemf(bContext *UNUSED(C), + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + bool *r_free) +{ + int totitem = 0; + const EnumPropertyItem *list = rna_enum_icon_items; + EnumPropertyItem *item = NULL; + + int i; + + for (i = 0; list[i].identifier; i++) { + EnumPropertyItem new_item = { + list[i].value, list[i].identifier, list[i].value, list[i].name, list[i].description}; + RNA_enum_item_add(&item, &totitem, &new_item); + } + +# ifndef NDEBUG + if (i == 0) { + EnumPropertyItem new_item = {i, "NONE", 0, "No icons", ""}; + RNA_enum_item_add(&item, &totitem, &new_item); + } +# endif + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + +static void rna_UserDef_usermenus_item_add(UserDef *userdef, int index) +{ + bUserMenu *um = rna_UserDef_usermenus_get_current(userdef, true); + if (!um) + return; + + ListBase *lb = &um->items; + bUserMenuItem_Op *umi = (bUserMenuItem_Op *)BKE_blender_user_menu_item_add( + NULL, USER_MENU_TYPE_OPERATOR); + + if (index < 0) + BLI_addtail(lb, umi); + else { + int i = 0; + for (bUserMenuItem *insert = lb->first; insert; insert = insert->next, i++) { + if (i == index) { + BLI_insertlinkbefore(lb, insert, umi); + return; + } + } + BLI_addtail(lb, umi); + } + + // basic operator setup + wmOperatorType *ot = WM_operatortype_find("OBJECT_OT_add", true); + STRNCPY(umi->item.ui_name, "new item"); + STRNCPY(umi->op_idname, ot->idname); + userdef->runtime.um_item_select = &umi->item; +} + +static void rna_UserDef_usermenus_item_remove(UserDef *userdef) +{ + bUserMenu *bum = rna_UserDef_usermenus_get_current(userdef, true); + bUserMenuItem *umi = userdef->runtime.um_item_select; + if (!bum || !umi) + return; + + userdef->runtime.um_item_select = umi->next; + if (!userdef->runtime.um_item_select) + userdef->runtime.um_item_select = umi->prev; + ListBase *lb = (umi->parent) ? &umi->parent->items : &bum->items; + ED_screen_user_menu_item_remove(lb, umi); +} + +static void rna_UserDef_usermenus_item_move(UserDef *userdef, bool up) +{ + bUserMenu *bum = rna_UserDef_usermenus_get_current(userdef, true); + bUserMenuItem *umi = userdef->runtime.um_item_select; + if (!bum || !umi) + return; + + ListBase *lb = (umi->parent) ? &umi->parent->items : &bum->items; + bUserMenuItem *umi_toward = (up) ? umi->prev : umi->next; + BLI_remlink(lb, umi); + + if (!umi_toward) { + + if (!umi->parent) + return; + if (!umi->parent->item.parent) { + if (up) + BLI_insertlinkbefore(&bum->items, umi->parent, umi); + else + BLI_insertlinkafter(&bum->items, umi->parent, umi); + umi->parent = NULL; + } + else { + if (up) + BLI_insertlinkbefore(&umi->parent->item.parent->items, umi->parent, umi); + else + BLI_insertlinkafter(&umi->parent->item.parent->items, umi->parent, umi); + umi->parent = umi->parent->item.parent; + } + } + else if (umi_toward->type == USER_MENU_TYPE_SUBMENU) { + bUserMenuItem_SubMenu *umi_sm = (bUserMenuItem_SubMenu *)umi_toward; + if (up) + BLI_addtail(&umi_sm->items, umi); + else + BLI_addhead(&umi_sm->items, umi); + umi->parent = umi_sm; + } + else { + + if (up) + BLI_insertlinkbefore(lb, umi->prev, umi); + else + BLI_insertlinkafter(lb, umi->next, umi); + } +} + +static void rna_UserDef_usermenus_pie_item_add(UserDef *userdef, int index) +{ + bUserMenu *um = rna_UserDef_usermenus_get_current(userdef, true); + if (!um) + return; + + bUserMenuItem_Op *umi = NULL; + ListBase *lb = &um->items; + bUserMenuItem *insert = lb->first; + int i = 0; + for (insert = lb->first; insert; insert = insert->next, i++) { + if (insert->type != USER_MENU_TYPE_SUBMENU) + continue; + bUserMenuItem_SubMenu *sm = (bUserMenuItem_SubMenu *)insert; + if (i == index) { + umi = (bUserMenuItem_Op *)BKE_blender_user_menu_item_add(&sm->items, + USER_MENU_TYPE_OPERATOR); + break; + } + } + + if (!umi) + return; + + // basic operator setup + wmOperatorType *ot = WM_operatortype_find("OBJECT_OT_add", true); + STRNCPY(umi->item.ui_name, "new item"); + STRNCPY(umi->op_idname, ot->idname); + umi->item.parent = (bUserMenuItem_SubMenu *)insert; + userdef->runtime.um_item_select = &umi->item; +} + +static void rna_UserDef_usermenus_item_type_set(PointerRNA *ptr, int value) +{ + bUserMenuItem *umi = (bUserMenuItem *)ptr->data; + bUserMenu *bum = rna_UserDef_usermenus_get_current(&U, true); + + if (!bum || !umi || umi->type == value) + return; + + ListBase *lb = (!umi->parent) ? &bum->items : &umi->parent->items; + BLI_remlink(lb, umi); + bUserMenuItem *new_umi = BKE_blender_user_menu_item_add(NULL, value); + new_umi->parent = umi->parent; + if (value == USER_MENU_TYPE_SUBMENU) { + bUserMenuItem_SubMenu *umi_sm = (bUserMenuItem_SubMenu *)new_umi; + BLI_listbase_clear(&umi_sm->items); + } + BLI_insertlinkafter(lb, umi->prev, new_umi); + STRNCPY(new_umi->ui_name, umi->ui_name); + BKE_blender_user_menu_item_free(umi); + U.runtime.um_item_select = new_umi; +} + +static int rna_UserDef_usermenus_item_type_get(PointerRNA *ptr) +{ + bUserMenuItem *data = (bUserMenuItem *)(ptr->data); + if (data) + return (int)(data->type); + return 0; +} + +static void rna_UserDef_usermenus_item_name_get(PointerRNA *ptr, char *value) +{ + bUserMenuItem *umi = (bUserMenuItem *)(ptr->data); + + if (!*umi->ui_name) { + const char *name = " "; + if (umi->type == USER_MENU_TYPE_MENU) { + bUserMenuItem_Menu *umi_mt = (bUserMenuItem_Menu *)umi; + MenuType *mt = WM_menutype_find(umi_mt->mt_idname, true); + name = (const char *)CTX_IFACE_(mt->translation_context, mt->label); + BLI_strncpy(umi->ui_name, name, FILE_MAX); + } + } + BLI_strncpy(value, umi->ui_name, FILE_MAX); +} + +static void rna_UserDef_usermenus_pie_set(PointerRNA *ptr, int value) +{ + bUserMenusGroup *umg = (bUserMenusGroup *)ptr->data; + + LISTBASE_FOREACH (bUserMenu *, um, &umg->menus) { + ListBase *lb = &um->items; + BKE_blender_user_menu_item_free_list(lb); + if (value) + for (int i = 0; i < 8; i++) { + BKE_blender_user_menu_item_add(lb, USER_MENU_TYPE_SEP); + } + } + umg->type = value; +} + +static PointerRNA rna_UserDef_usermenus_item_op_prop_get(PointerRNA *ptr) +{ + bUserMenuItem_Op *umi_op = ptr->data; + + if (umi_op->ptr) { + return *(umi_op->ptr); + } + return PointerRNA_NULL; +} + +static void rna_UserDef_usermenus_group_idname_set(Main *bmain, + Scene *UNUSED(scene), + PointerRNA *ptr) +{ + bUserMenusGroup *umg = (bUserMenusGroup *)ptr->data; +# if 0 + const char *name = umg->name; +# endif + char old[64] = {'\0'}; + + STRNCPY(old, umg->idname); + BKE_blender_user_menus_group_idname_update(umg); + BKE_blender_user_menus_group_idname_update_keymap(bmain->wm.first, old, umg->idname); +} + +static void rna_UserDef_usermenus_item_op_get(PointerRNA *ptr, char *value) +{ + bUserMenuItem_Op *umi_op = ptr->data; + WM_operator_py_idname(value, umi_op->op_idname); +} + +static int rna_UserDef_usermenus_item_op_length(PointerRNA *ptr) +{ + bUserMenuItem_Op *umi_op = ptr->data; + char pyname[OP_MAX_TYPENAME]; + + WM_operator_py_idname(pyname, umi_op->op_idname); + return strlen(pyname); +} + +static void rna_UserDef_usermenus_item_op_set(PointerRNA *ptr, const char *value) +{ + bUserMenuItem_Op *umi_op = (bUserMenuItem_Op *)ptr->data; + char idname_bl[OP_MAX_TYPENAME]; + + wmOperatorType *origin_ot = WM_operatortype_find(umi_op->op_idname, true); + wmOperatorType *ot = WM_operatortype_find(value, false); + if (origin_ot == ot || ot == NULL) + return; + + WM_operator_bl_idname(idname_bl, value); + WM_operator_py_idname(umi_op->op_idname, value); + + if (LIKELY(umi_op->ptr)) { + WM_operator_properties_free(umi_op->ptr); + MEM_freeN(umi_op->ptr); + + umi_op->ptr = NULL; + } + umi_op->prop = NULL; + + WM_operator_properties_alloc(&(umi_op->ptr), &(umi_op->prop), idname_bl); + WM_operator_properties_sanitize(umi_op->ptr, 1); +} + +static void rna_UserDef_usermenu_draw(UserDef *UNUSED(userdef), + bContext *C, + uiLayout *layout, + bUserMenusGroup *umg) +{ + screen_user_menu_draw_begin(C, layout, true, umg); +} + +/* UserMenu.menu_items */ + +static void rna_UserDef_usermenu_items_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ + bUserMenu *bum = (bUserMenu *)ptr->data; + rna_iterator_listbase_begin(iter, &bum->items, NULL); +} + +static void rna_UserDef_usermenu_submenu_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ + bUserMenuItem_SubMenu *umi_sm = (bUserMenuItem_SubMenu *)ptr->data; + rna_iterator_listbase_begin(iter, &umi_sm->items, NULL); +} + +static bUserMenuItem_Op *rna_UserDef_usermenus_item_operator_get(bUserMenuItem *umi) +{ + if (umi->type == USER_MENU_TYPE_OPERATOR) + return (bUserMenuItem_Op *)umi; + return NULL; +} + +static bUserMenuItem_Menu *rna_UserDef_usermenus_item_menu_get(bUserMenuItem *umi) +{ + if (umi->type == USER_MENU_TYPE_MENU) + return (bUserMenuItem_Menu *)umi; + return NULL; +} + +static bUserMenuItem_Prop *rna_UserDef_usermenus_item_property_get(bUserMenuItem *umi) +{ + if (umi->type == USER_MENU_TYPE_PROP) + return (bUserMenuItem_Prop *)umi; + return NULL; +} + +static bUserMenuItem_SubMenu *rna_UserDef_usermenus_item_submenu_get(bUserMenuItem *umi) +{ + if (umi->type == USER_MENU_TYPE_SUBMENU) + return (bUserMenuItem_SubMenu *)umi; + return NULL; +} + +/* UserMenus.menus */ + +static void rna_UserDef_usermenu_menus_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ + UserDef *userdef = (UserDef *)ptr->data; + rna_iterator_listbase_begin(iter, &userdef->user_menus_group, NULL); +} + int rna_show_statusbar_vram_editable(struct PointerRNA *UNUSED(ptr), const char **UNUSED(r_info)) { return GPU_mem_stats_supported() ? PROP_EDITABLE : 0; @@ -5910,6 +6462,384 @@ static void rna_def_userdef_keymap(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Key Config", "The name of the active key configuration"); } +static void rna_def_userdef_usermenus_items_subtypes(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + /* operator item */ + srna = RNA_def_struct(brna, "um_item_op", NULL); + RNA_def_struct_sdna(srna, "bUserMenuItem_Op"); + RNA_def_struct_ui_text( + srna, "User Menu operator item", "an item of the user menus that can store an operator"); + + prop = RNA_def_property(srna, "item", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "item"); + RNA_def_property_struct_type(prop, "UserMenuItem"); + RNA_def_property_ui_text(prop, "item", ""); + + prop = RNA_def_property(srna, "operator", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "op_idname"); + RNA_def_property_ui_text(prop, "operator", "the operator that will be executed"); + RNA_def_property_string_funcs(prop, + "rna_UserDef_usermenus_item_op_get", + "rna_UserDef_usermenus_item_op_length", + "rna_UserDef_usermenus_item_op_set"); + RNA_def_struct_name_property(srna, prop); + + prop = RNA_def_property(srna, "prop", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "prop"); + RNA_def_property_struct_type(prop, "OperatorProperties"); + RNA_def_property_ui_text(prop, "properties", "properties of the operator"); + RNA_def_property_pointer_funcs(prop, "rna_UserDef_usermenus_item_op_prop_get", NULL, NULL, NULL); + + /* menu item */ + srna = RNA_def_struct(brna, "um_item_menu", NULL); + RNA_def_struct_sdna(srna, "bUserMenuItem_Menu"); + RNA_def_struct_ui_text( + srna, "User Menu menu item", "an item of the user menus that can store a submenu"); + + prop = RNA_def_property(srna, "item", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "item"); + RNA_def_property_struct_type(prop, "UserMenuItem"); + RNA_def_property_ui_text(prop, "item", ""); + + prop = RNA_def_property(srna, "id_name", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "mt_idname"); + RNA_def_property_ui_text(prop, "id name", "the menu id name"); + RNA_def_struct_name_property(srna, prop); + + /* prop item */ + srna = RNA_def_struct(brna, "um_item_prop", NULL); + RNA_def_struct_sdna(srna, "bUserMenuItem_Prop"); + RNA_def_struct_ui_text( + srna, "User Menu property item", "an item of the user menus that can store a property"); + + prop = RNA_def_property(srna, "item", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "item"); + RNA_def_property_struct_type(prop, "UserMenuItem"); + RNA_def_property_ui_text(prop, "item", ""); + + prop = RNA_def_property(srna, "id_name", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "prop_id"); + RNA_def_property_ui_text(prop, "id name", "the property id name"); + RNA_def_struct_name_property(srna, prop); + + prop = RNA_def_property(srna, "context", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "context_data_path"); + RNA_def_property_ui_text(prop, "context", "the context data path of the property"); + RNA_def_struct_name_property(srna, prop); + + /* sub menu item */ + srna = RNA_def_struct(brna, "um_item_submenu", NULL); + RNA_def_struct_sdna(srna, "bUserMenuItem_SubMenu"); + RNA_def_struct_ui_text( + srna, "User Menu sub menu item", "an item of the user menus that can store a sub menu"); + + prop = RNA_def_property(srna, "item", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "item"); + RNA_def_property_struct_type(prop, "UserMenuItem"); + RNA_def_property_ui_text(prop, "item", ""); + + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "name"); + RNA_def_property_ui_text(prop, "name", "the sub menu name"); + RNA_def_struct_name_property(srna, prop); + + prop = RNA_def_property(srna, "items_list", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "UserMenuItem"); + RNA_def_property_ui_text(prop, "sub menu items", "list of the items of the sub menu"); + RNA_def_property_collection_funcs(prop, + "rna_UserDef_usermenu_submenu_begin", + "rna_iterator_listbase_next", + NULL, + "rna_iterator_listbase_get", + NULL, + NULL, + NULL, + NULL); +} + +static void rna_def_userdef_usermenu(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + FunctionRNA *func; + PropertyRNA *parm; + + /* user menu */ + srna = RNA_def_struct(brna, "UserMenu", NULL); + RNA_def_struct_sdna(srna, "bUserMenu"); + RNA_def_struct_ui_text(srna, "User Menu", "an user menu"); + + prop = RNA_def_property(srna, "spacetype", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "space_type"); + RNA_def_property_ui_text(prop, "Space Type", "The Space type the user menu will be used in"); + + prop = RNA_def_property(srna, "context", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "context"); + RNA_def_property_ui_text(prop, "Context", "The Context the user menu will be used in"); + RNA_def_struct_name_property(srna, prop); + + prop = RNA_def_property(srna, "menu_items", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "UserMenuItem"); + RNA_def_property_ui_text(prop, "Items", "list of the items of the menu"); + RNA_def_property_collection_funcs(prop, + "rna_UserDef_usermenu_items_begin", + "rna_iterator_listbase_next", + NULL, + "rna_iterator_listbase_get", + NULL, + NULL, + NULL, + NULL); + + /* user menu item */ + static const EnumPropertyItem um_item_type[] = { + {USER_MENU_TYPE_OPERATOR, "OPERATOR", 0, "Operator", "Operator"}, + {USER_MENU_TYPE_PROP, "PROPERTY", 0, "Property", "Property"}, + {USER_MENU_TYPE_SUBMENU, "SUBMENU", 0, "Menu", "Menu"}, + {USER_MENU_TYPE_MENU, "MENU", 0, "Packed Menu", "Packed Menu"}, + {USER_MENU_TYPE_SEP, "SEPARATOR", 0, "Separator", "Separator"}, + {0, NULL, 0, NULL, NULL}, + }; + + srna = RNA_def_struct(brna, "UserMenuItem", NULL); + RNA_def_struct_sdna(srna, "bUserMenuItem"); + RNA_def_struct_ui_text(srna, "User Menu Item", "User Menu item"); + + prop = RNA_def_property(srna, "next", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "next"); + RNA_def_property_struct_type(prop, "UserMenuItem"); + RNA_def_property_ui_text(prop, "item", ""); + + prop = RNA_def_property(srna, "prev", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "prev"); + RNA_def_property_struct_type(prop, "UserMenuItem"); + RNA_def_property_ui_text(prop, "item", ""); + + prop = RNA_def_property(srna, "parent", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "parent"); + RNA_def_property_struct_type(prop, "um_item_submenu"); + RNA_def_property_ui_text(prop, "item", ""); + + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "ui_name"); + RNA_def_property_string_funcs(prop, "rna_UserDef_usermenus_item_name_get", NULL, NULL); + RNA_def_property_ui_text(prop, "Name", "Name of the item"); + RNA_def_struct_name_property(srna, prop); + + prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "type"); + RNA_def_property_enum_items(prop, um_item_type); + RNA_def_property_enum_funcs( + prop, "rna_UserDef_usermenus_item_type_get", "rna_UserDef_usermenus_item_type_set", NULL); + RNA_def_property_ui_text(prop, "type", "the type of item"); + + prop = RNA_def_property(srna, "icon", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "icon"); + RNA_def_property_enum_items(prop, rna_enum_icon_items); + RNA_def_property_enum_default(prop, ICON_NONE); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_UserDef_icons_itemf"); + RNA_def_property_ui_text(prop, "Icon", "The item icon"); + + prop = RNA_def_property(srna, "is_selected", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "is_selected", 0); + RNA_def_property_ui_text(prop, "is selected", "is selected"); + RNA_def_property_boolean_funcs(prop, NULL, "rna_UserDef_usermenus_active_item_set"); + + func = RNA_def_function(srna, "get_operator", "rna_UserDef_usermenus_item_operator_get"); + RNA_def_function_ui_description(func, "return the operator item"); + parm = RNA_def_pointer(func, "item_op", "um_item_op", "", "the item operator"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "get_menu", "rna_UserDef_usermenus_item_menu_get"); + RNA_def_function_ui_description(func, "return the operator menu"); + parm = RNA_def_pointer(func, "item_menu", "um_item_menu", "", "the item menu"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "get_property", "rna_UserDef_usermenus_item_property_get"); + RNA_def_function_ui_description(func, "return the operator property"); + parm = RNA_def_pointer(func, "item_prop", "um_item_prop", "", "the item property"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "get_submenu", "rna_UserDef_usermenus_item_submenu_get"); + RNA_def_function_ui_description(func, "return the submenu"); + parm = RNA_def_pointer(func, "item_submenu", "um_item_submenu", "", "the item submenu"); + RNA_def_function_return(func, parm); + + rna_def_userdef_usermenus_items_subtypes(brna); +} + +static void rna_def_userdef_usermenusgroup(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + static const EnumPropertyItem um_menu_type[] = { + {0, "LIST", 0, "List", "List"}, + {1, "PIE", 0, "Pie", "Pie"}, + {0, NULL, 0, NULL, NULL}, + }; + + /* user menus group */ + srna = RNA_def_struct(brna, "UserMenusGroup", NULL); + RNA_def_struct_sdna(srna, "bUserMenusGroup"); + RNA_def_struct_ui_text(srna, "User Menus Group", "A whole displayble user menu"); + + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "name"); + RNA_def_property_ui_text(prop, "Name", "Name of the user menu group"); + RNA_def_struct_name_property(srna, prop); + RNA_def_property_update(prop, 0, "rna_UserDef_usermenus_group_idname_set"); + + prop = RNA_def_property(srna, "idname", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "idname"); + RNA_def_property_ui_text(prop, "ID Name", "ID Name of the user menu group"); + RNA_def_struct_name_property(srna, prop); + + prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "type"); + RNA_def_property_enum_items(prop, um_menu_type); + RNA_def_property_ui_text(prop, "menu type", "change menu type between list and pie"); + RNA_def_property_enum_funcs(prop, NULL, "rna_UserDef_usermenus_pie_set", NULL); + + prop = RNA_def_property(srna, "menus", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "UserMenu"); + RNA_def_property_ui_text(prop, "User Menu", "list of user sub menus contained in the group"); +} + +static void rna_def_userdef_usermenus_editor(BlenderRNA *brna) +{ + PropertyRNA *prop; + FunctionRNA *func; + PropertyRNA *parm; + + static const EnumPropertyItem um_space_default[] = { + {0, "NULL", 0, "None", "No spacetypes found"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem um_context_default[] = { + {0, "NULL", 0, "None", "No context found"}, + {0, NULL, 0, NULL, NULL}, + }; + + StructRNA *srna = RNA_def_struct(brna, "PreferencesUserMenus", NULL); + RNA_def_struct_sdna(srna, "UserDef"); + RNA_def_struct_nested(brna, srna, "Preferences"); + RNA_def_struct_clear_flag(srna, STRUCT_UNDO); + RNA_def_struct_ui_text(srna, "User Menus", "User Menus editor"); + + prop = RNA_def_property(srna, "space_selected", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "runtime.um_space_select"); + RNA_def_property_enum_items(prop, um_space_default); + RNA_def_property_enum_funcs(prop, + "rna_UserDef_usermenus_spacetypes_get", + NULL, + "rna_UserDef_usermenus_spacetypes_itemf"); + RNA_def_property_ui_text(prop, "space type selected", "the space type selected"); + RNA_def_property_update(prop, 0, "rna_userdef_update"); + + prop = RNA_def_property(srna, "context_selected", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "runtime.um_context_select"); + RNA_def_property_enum_items(prop, um_context_default); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_UserDef_usermenus_contexts_itemf"); + RNA_def_property_ui_text(prop, "context selected", "the context selected"); + RNA_def_property_update(prop, 0, "rna_userdef_update"); + + prop = RNA_def_property(srna, "active_item", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "runtime.um_item_select"); + RNA_def_property_struct_type(prop, "UserMenuItem"); + RNA_def_property_ui_text(prop, "item", ""); + + prop = RNA_def_property(srna, "active_group", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_pointer_sdna(prop, NULL, "runtime.umg_select"); + RNA_def_property_struct_type(prop, "UserMenusGroup"); + RNA_def_property_ui_text(prop, "active user menus group", "active user menus group"); + + prop = RNA_def_property(srna, "menus", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "UserMenusGroup"); + RNA_def_property_ui_text(prop, "Menus", "list of the menus"); + RNA_def_property_collection_funcs(prop, + "rna_UserDef_usermenu_menus_begin", + "rna_iterator_listbase_next", + "rna_iterator_listbase_end", + "rna_iterator_listbase_get", + NULL, + NULL, + NULL, + NULL); + + prop = RNA_def_property(srna, "expanded", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "runtime.um_expanded", 0); + RNA_def_property_ui_text(prop, "Items Expanded", "Expanded in the user interface"); + RNA_def_property_ui_icon(prop, ICON_DISCLOSURE_TRI_RIGHT, 1); + + rna_def_userdef_usermenu(brna); + rna_def_userdef_usermenusgroup(brna); + + // functions + func = RNA_def_function(srna, "get_current_menu", "rna_UserDef_usermenus_get_current"); + RNA_def_function_ui_description(func, "get active user menu"); + parm = RNA_def_boolean(func, "ensure", false, "ensure", "create the menu if don't exist"); + parm = RNA_def_pointer(func, "current_menu", "UserMenu", "", "the menu"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "get_group", "rna_UserDef_usermenus_get_group"); + RNA_def_function_ui_description(func, "get user menus group by idname"); + parm = RNA_def_string(func, "idname", NULL, 0, "ID name", "ID name of the group"); + parm = RNA_def_pointer(func, "menu", "UserMenusGroup", "", "the menu group"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "set_group", "rna_UserDef_usermenus_set_group"); + RNA_def_function_ui_description(func, "set the group menu to edit"); + parm = RNA_def_pointer(func, "new_group", "UserMenusGroup", "", "the group menu"); + + func = RNA_def_function(srna, "add_group", "rna_UserDef_usermenus_add_group"); + RNA_def_function_ui_description(func, "add a group"); + + func = RNA_def_function(srna, "remove_group", "rna_UserDef_usermenus_remove_group"); + RNA_def_function_ui_description(func, "remove a group"); + + func = RNA_def_function(srna, "items_len", "rna_UserDef_usermenus_items_length"); + RNA_def_function_ui_description(func, "Refresh custom menu editor"); + parm = RNA_def_int(func, "len", 0, 0, 1000, "", "the list len", 0, 1000); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "has_item", "rna_UserDef_usermenus_has_item"); + RNA_def_function_ui_description(func, "the current list has items"); + parm = RNA_def_boolean(func, "has_item", false, "", "is there items in the current list ?"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "item_add", "rna_UserDef_usermenus_item_add"); + RNA_def_function_ui_description(func, "add an item to a menu"); + parm = RNA_def_int(func, "index", 0, -1, 1000, "index", "index to insert the item", -1, 1000); + + func = RNA_def_function(srna, "item_remove", "rna_UserDef_usermenus_item_remove"); + RNA_def_function_ui_description(func, "remove an item from a user menu"); + + func = RNA_def_function(srna, "item_move", "rna_UserDef_usermenus_item_move"); + RNA_def_function_ui_description(func, "up an item"); + parm = RNA_def_boolean(func, "up", false, "", "go up ?"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + + func = RNA_def_function(srna, "pie_item_add", "rna_UserDef_usermenus_pie_item_add"); + RNA_def_function_ui_description(func, "add an item to a menu"); + parm = RNA_def_int(func, "index", 0, -1, 1000, "index", "index to insert the item", -1, 1000); + + func = RNA_def_function(srna, "draw_menu", "rna_UserDef_usermenu_draw"); + RNA_def_function_ui_description(func, "draw items of usermenu"); + parm = RNA_def_pointer(func, "context", "Context", "", "context"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_pointer(func, "layout", "UILayout", "", "layout"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_pointer(func, "menu", "UserMenusGroup", "", "menu"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); +} + static void rna_def_userdef_filepaths(BlenderRNA *brna) { PropertyRNA *prop; @@ -6248,6 +7178,12 @@ void RNA_def_userdef(BlenderRNA *brna) RNA_def_property_pointer_funcs(prop, "rna_UserDef_keymap_get", NULL, NULL, NULL); RNA_def_property_ui_text(prop, "Keymap", "Shortcut setup for keyboards and other input devices"); + prop = RNA_def_property(srna, "user_menus", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_struct_type(prop, "PreferencesUserMenus"); + RNA_def_property_pointer_funcs(prop, "rna_UserDef_user_menus_get", NULL, NULL, NULL); + RNA_def_property_ui_text(prop, "User Menus", "User Menus Editor"); + prop = RNA_def_property(srna, "filepaths", PROP_POINTER, PROP_NONE); RNA_def_property_flag(prop, PROP_NEVER_NULL); RNA_def_property_struct_type(prop, "PreferencesFilePaths"); @@ -6315,6 +7251,7 @@ void RNA_def_userdef(BlenderRNA *brna) rna_def_userdef_edit(brna); rna_def_userdef_input(brna); rna_def_userdef_keymap(brna); + rna_def_userdef_usermenus_editor(brna); rna_def_userdef_filepaths(brna); rna_def_userdef_system(brna); rna_def_userdef_addon(brna); diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index f53a3d6bf35..707eeef02a9 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -1841,6 +1841,8 @@ static void rna_struct_update_when_changed(bContext *C, PointerRNA *ptr_b) { CollectionPropertyIterator iter; + if (!ptr_a->data || !ptr_b->data) + return; PropertyRNA *iterprop = RNA_struct_iterator_property(ptr_a->type); BLI_assert(ptr_a->type == ptr_b->type); RNA_property_collection_begin(ptr_a, iterprop, &iter); diff --git a/source/tools b/source/tools -Subproject 896c5f78952adb2d091d28c65086d46992dabda +Subproject 5cf2fc3e5dc28025394b57d8743401295528f31 |