# ##### 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 ##### # __all__ = ( "draw_entry", "draw_km", "draw_kmi", "draw_filtered", "draw_hierarchy", "draw_keymaps", ) 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 draw_entry(display_keymaps, entry, col, level=0): idname, spaceid, regionid, children = entry for km, kc in display_keymaps: if km.name == idname and km.space_type == spaceid and km.region_type == regionid: draw_km(display_keymaps, kc, km, children, col, level) ''' km = kc.keymaps.find(idname, space_type=spaceid, region_type=regionid) if not km: kc = defkc km = kc.keymaps.find(idname, space_type=spaceid, region_type=regionid) if km: draw_km(kc, km, children, col, level) ''' def draw_km(display_keymaps, kc, km, children, layout, level): km = km.active() layout.context_pointer_set("keymap", km) col = _indented_layout(layout, level) row = col.row(align=True) row.prop(km, "show_expanded_children", text="", emboss=False) row.label(text=km.name, text_ctxt=i18n_contexts.id_windowmanager) if km.is_user_modified or km.is_modal: subrow = row.row() subrow.alignment = 'RIGHT' if km.is_user_modified: subrow.operator("wm.keymap_restore", text="Restore") if km.is_modal: subrow.label(text="", icon='LINKED') del subrow if km.show_expanded_children: if children: # Put the Parent key map's entries in a 'global' sub-category # equal in hierarchy to the other children categories subcol = _indented_layout(col, level + 1) subrow = subcol.row(align=True) subrow.prop(km, "show_expanded_items", text="", emboss=False) subrow.label(text=iface_("%s (Global)") % km.name, translate=False) else: km.show_expanded_items = True # Key Map items if km.show_expanded_items: kmi_level = level + 3 if children else level + 1 for kmi in km.keymap_items: draw_kmi(display_keymaps, kc, km, kmi, col, kmi_level) # "Add New" at end of keymap item list subcol = _indented_layout(col, kmi_level) subcol = subcol.split(factor=0.2).column() subcol.operator("wm.keyitem_add", text="Add New", text_ctxt=i18n_contexts.id_windowmanager, icon='ZOOMIN') col.separator() # Child key maps if children: for entry in children: draw_entry(display_keymaps, entry, col, level + 1) col.separator() def draw_kmi(display_keymaps, kc, km, kmi, layout, level): map_type = kmi.map_type col = _indented_layout(layout, level) if kmi.show_expanded: col = col.column(align=True) box = col.box() else: box = col.column() split = box.split() # header bar row = split.row(align=True) row.prop(kmi, "show_expanded", text="", emboss=False) row.prop(kmi, "active", text="", emboss=False) if km.is_modal: row.separator() row.prop(kmi, "propvalue", text="") else: row.label(text=kmi.name) row = split.row() row.prop(kmi, "map_type", text="") 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 (not kmi.is_user_defined) and kmi.is_user_modified: row.operator("wm.keyitem_restore", text="", icon='BACK').item_id = kmi.id else: row.operator("wm.keyitem_remove", text="", icon='X').item_id = kmi.id # Expanded, additional event settings if kmi.show_expanded: box = col.box() split = box.split(factor=0.4) sub = split.row() if km.is_modal: sub.prop(kmi, "propvalue", text="") else: # One day... # sub.prop_search(kmi, "idname", bpy.context.window_manager, "operators_all", text="") sub.prop(kmi, "idname", text="") if map_type not in {'TEXTINPUT', 'TIMER'}: sub = split.column() subrow = sub.row(align=True) if map_type == 'KEYBOARD': subrow.prop(kmi, "type", text="", event=True) subrow.prop(kmi, "value", text="") 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") subrow.prop(kmi, "shift") subrow.prop(kmi, "ctrl") subrow.prop(kmi, "alt") subrow.prop(kmi, "oskey", text="Cmd") subrow.prop(kmi, "key_modifier", text="", event=True) # Operator properties box.template_keymap_item_properties(kmi) # Modal key maps attached to this operator if not km.is_modal: kmm = kc.keymaps.find_modal(kmi.idname) if kmm: draw_km(display_keymaps, kc, kmm, None, layout, level + 1) layout.context_pointer_set("keymap", km) _EVENT_TYPES = set() _EVENT_TYPE_MAP = {} _EVENT_TYPE_MAP_EXTRA = {} def draw_filtered(display_keymaps, filter_type, filter_text, layout): if filter_type == 'NAME': def filter_func(kmi): return (filter_text in kmi.idname.lower() or filter_text in kmi.name.lower()) else: if not _EVENT_TYPES: enum = bpy.types.Event.bl_rna.properties["type"].enum_items _EVENT_TYPES.update(enum.keys()) _EVENT_TYPE_MAP.update({item.name.replace(" ", "_").upper(): key for key, item in enum.items()}) del enum _EVENT_TYPE_MAP_EXTRA.update({ "`": 'ACCENT_GRAVE', "*": 'NUMPAD_ASTERIX', "/": 'NUMPAD_SLASH', '+': 'NUMPAD_PLUS', "RMB": 'RIGHTMOUSE', "LMB": 'LEFTMOUSE', "MMB": 'MIDDLEMOUSE', }) _EVENT_TYPE_MAP_EXTRA.update({ "%d" % i: "NUMPAD_%d" % i for i in range(10) }) # done with once off init filter_text_split = filter_text.strip() filter_text_split = filter_text.split() # Modifier {kmi.attribute: name} mapping key_mod = { "ctrl": "ctrl", "alt": "alt", "shift": "shift", "cmd": "oskey", "oskey": "oskey", "any": "any", } # KeyMapItem like dict, use for comparing against # attr: {states, ...} kmi_test_dict = {} # Special handling of 'type' using a list if sets, # keymap items must match against all. kmi_test_type = [] # initialize? - so if a if a kmi has a MOD assigned it wont show up. # for kv in key_mod.values(): # kmi_test_dict[kv] = {False} # altname: attr for kk, kv in key_mod.items(): if kk in filter_text_split: filter_text_split.remove(kk) kmi_test_dict[kv] = {True} # whats left should be the event type def kmi_type_set_from_string(kmi_type): kmi_type = kmi_type.upper() kmi_type_set = set() if kmi_type in _EVENT_TYPES: kmi_type_set.add(kmi_type) if not kmi_type_set or len(kmi_type) > 1: # replacement table for event_type_map in (_EVENT_TYPE_MAP, _EVENT_TYPE_MAP_EXTRA): kmi_type_test = event_type_map.get(kmi_type) if kmi_type_test is not None: kmi_type_set.add(kmi_type_test) else: # print("Unknown Type:", kmi_type) # Partial match for k, v in event_type_map.items(): if (kmi_type in k) or (kmi_type in v): kmi_type_set.add(v) return kmi_type_set for i, kmi_type in enumerate(filter_text_split): kmi_type_set = kmi_type_set_from_string(kmi_type) if not kmi_type_set: return False kmi_test_type.append(kmi_type_set) # tiny optimization, sort sets so the smallest is first # improve chances of failing early kmi_test_type.sort(key=lambda kmi_type_set: len(kmi_type_set)) # main filter func, runs many times def filter_func(kmi): for kk, ki in kmi_test_dict.items(): val = getattr(kmi, kk) if val not in ki: return False # special handling of 'type' for ki in kmi_test_type: val = kmi.type if val == 'NONE' or val not in ki: # exception for 'type' # also inspect 'key_modifier' as a fallback val = kmi.key_modifier if not (val == 'NONE' or val not in ki): continue return False return True for km, kc in display_keymaps: km = km.active() layout.context_pointer_set("keymap", km) filtered_items = [kmi for kmi in km.keymap_items if filter_func(kmi)] if filtered_items: col = layout.column() row = col.row() row.label(text=km.name, icon='DOT') row.label() row.label() if km.is_user_modified: row.operator("wm.keymap_restore", text="Restore") else: row.label() for kmi in filtered_items: draw_kmi(display_keymaps, kc, km, kmi, col, 1) # "Add New" at end of keymap item list col = _indented_layout(layout, 1) subcol = col.split(factor=0.2).column() subcol.operator("wm.keyitem_add", text="Add New", icon='ZOOMIN') return True def draw_hierarchy(display_keymaps, layout): from bpy_extras import keyconfig_utils for entry in keyconfig_utils.km_hierarchy(): draw_entry(display_keymaps, entry, layout) def draw_keymaps(context, layout): from bpy_extras import keyconfig_utils wm = context.window_manager kc = wm.keyconfigs.user spref = context.space_data col = layout.column() sub = col.column() subsplit = sub.split() subcol = subsplit.column() row = subcol.row(align=True) # row.prop_search(wm.keyconfigs, "active", wm, "keyconfigs", text="Key Config") text = bpy.path.display_name(wm.keyconfigs.active.name) if not text: text = "Blender (default)" row.menu("USERPREF_MT_keyconfigs", text=text) row.operator("wm.keyconfig_preset_add", text="", icon='ZOOMIN') row.operator("wm.keyconfig_preset_add", text="", icon='ZOOMOUT').remove_active = True # layout.context_pointer_set("keyconfig", wm.keyconfigs.active) # row.operator("wm.keyconfig_remove", text="", icon='X') row.separator() rowsub = row.split(factor=0.33, align=True) # postpone drawing into rowsub, so we can set alert! col.separator() display_keymaps = keyconfig_utils.keyconfig_merge(kc, kc) filter_type = spref.filter_type filter_text = spref.filter_text.strip() if filter_text: filter_text = filter_text.lower() ok = draw_filtered(display_keymaps, filter_type, filter_text, col) else: draw_hierarchy(display_keymaps, col) ok = True # go back and fill in rowsub rowsub.prop(spref, "filter_type", text="") rowsubsub = rowsub.row(align=True) if not ok: rowsubsub.alert = True rowsubsub.prop(spref, "filter_text", text="", icon='VIEWZOOM')