rna_keymap_ui.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. # ##### BEGIN GPL LICENSE BLOCK #####
  2. #
  3. # This program is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU General Public License
  5. # as published by the Free Software Foundation; either version 2
  6. # of the License, or (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software Foundation,
  15. # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. #
  17. # ##### END GPL LICENSE BLOCK #####
  18. # <pep8 compliant>
  19. __all__ = (
  20. "draw_entry",
  21. "draw_km",
  22. "draw_kmi",
  23. "draw_filtered",
  24. "draw_hierarchy",
  25. "draw_keymaps",
  26. )
  27. import bpy
  28. from bpy.app.translations import pgettext_iface as iface_
  29. from bpy.app.translations import contexts as i18n_contexts
  30. def _indented_layout(layout, level):
  31. indentpx = 16
  32. if level == 0:
  33. level = 0.0001 # Tweak so that a percentage of 0 won't split by half
  34. indent = level * indentpx / bpy.context.region.width
  35. split = layout.split(factor=indent)
  36. col = split.column()
  37. col = split.column()
  38. return col
  39. def draw_entry(display_keymaps, entry, col, level=0):
  40. idname, spaceid, regionid, children = entry
  41. for km, kc in display_keymaps:
  42. if km.name == idname and km.space_type == spaceid and km.region_type == regionid:
  43. draw_km(display_keymaps, kc, km, children, col, level)
  44. '''
  45. km = kc.keymaps.find(idname, space_type=spaceid, region_type=regionid)
  46. if not km:
  47. kc = defkc
  48. km = kc.keymaps.find(idname, space_type=spaceid, region_type=regionid)
  49. if km:
  50. draw_km(kc, km, children, col, level)
  51. '''
  52. def draw_km(display_keymaps, kc, km, children, layout, level):
  53. km = km.active()
  54. layout.context_pointer_set("keymap", km)
  55. col = _indented_layout(layout, level)
  56. row = col.row(align=True)
  57. row.prop(km, "show_expanded_children", text="", emboss=False)
  58. row.label(text=km.name, text_ctxt=i18n_contexts.id_windowmanager)
  59. if km.is_user_modified or km.is_modal:
  60. subrow = row.row()
  61. subrow.alignment = 'RIGHT'
  62. if km.is_user_modified:
  63. subrow.operator("preferences.keymap_restore", text="Restore")
  64. if km.is_modal:
  65. subrow.label(text="", icon='LINKED')
  66. del subrow
  67. if km.show_expanded_children:
  68. if children:
  69. # Put the Parent key map's entries in a 'global' sub-category
  70. # equal in hierarchy to the other children categories
  71. subcol = _indented_layout(col, level + 1)
  72. subrow = subcol.row(align=True)
  73. subrow.prop(km, "show_expanded_items", text="", emboss=False)
  74. subrow.label(text=iface_("%s (Global)") % km.name, translate=False)
  75. else:
  76. km.show_expanded_items = True
  77. # Key Map items
  78. if km.show_expanded_items:
  79. kmi_level = level + 3 if children else level + 1
  80. for kmi in km.keymap_items:
  81. draw_kmi(display_keymaps, kc, km, kmi, col, kmi_level)
  82. # "Add New" at end of keymap item list
  83. subcol = _indented_layout(col, kmi_level)
  84. subcol = subcol.split(factor=0.2).column()
  85. subcol.operator("preferences.keyitem_add", text="Add New", text_ctxt=i18n_contexts.id_windowmanager,
  86. icon='ADD')
  87. col.separator()
  88. # Child key maps
  89. if children:
  90. for entry in children:
  91. draw_entry(display_keymaps, entry, col, level + 1)
  92. col.separator()
  93. def draw_kmi(display_keymaps, kc, km, kmi, layout, level):
  94. map_type = kmi.map_type
  95. col = _indented_layout(layout, level)
  96. if kmi.show_expanded:
  97. col = col.column(align=True)
  98. box = col.box()
  99. else:
  100. box = col.column()
  101. split = box.split()
  102. # header bar
  103. row = split.row(align=True)
  104. row.prop(kmi, "show_expanded", text="", emboss=False)
  105. row.prop(kmi, "active", text="", emboss=False)
  106. if km.is_modal:
  107. row.separator()
  108. row.prop(kmi, "propvalue", text="")
  109. else:
  110. row.label(text=kmi.name)
  111. row = split.row()
  112. row.prop(kmi, "map_type", text="")
  113. if map_type == 'KEYBOARD':
  114. row.prop(kmi, "type", text="", full_event=True)
  115. elif map_type == 'MOUSE':
  116. row.prop(kmi, "type", text="", full_event=True)
  117. elif map_type == 'NDOF':
  118. row.prop(kmi, "type", text="", full_event=True)
  119. elif map_type == 'TWEAK':
  120. subrow = row.row()
  121. subrow.prop(kmi, "type", text="")
  122. subrow.prop(kmi, "value", text="")
  123. elif map_type == 'TIMER':
  124. row.prop(kmi, "type", text="")
  125. else:
  126. row.label()
  127. if (not kmi.is_user_defined) and kmi.is_user_modified:
  128. row.operator("preferences.keyitem_restore", text="", icon='BACK').item_id = kmi.id
  129. else:
  130. row.operator("preferences.keyitem_remove", text="", icon='X').item_id = kmi.id
  131. # Expanded, additional event settings
  132. if kmi.show_expanded:
  133. box = col.box()
  134. split = box.split(factor=0.4)
  135. sub = split.row()
  136. if km.is_modal:
  137. sub.prop(kmi, "propvalue", text="")
  138. else:
  139. # One day...
  140. # sub.prop_search(kmi, "idname", bpy.context.window_manager, "operators_all", text="")
  141. sub.prop(kmi, "idname", text="")
  142. if map_type not in {'TEXTINPUT', 'TIMER'}:
  143. sub = split.column()
  144. subrow = sub.row(align=True)
  145. if map_type == 'KEYBOARD':
  146. subrow.prop(kmi, "type", text="", event=True)
  147. subrow.prop(kmi, "value", text="")
  148. elif map_type in {'MOUSE', 'NDOF'}:
  149. subrow.prop(kmi, "type", text="")
  150. subrow.prop(kmi, "value", text="")
  151. subrow = sub.row()
  152. subrow.scale_x = 0.75
  153. subrow.prop(kmi, "any", toggle=True)
  154. subrow.prop(kmi, "shift", toggle=True)
  155. subrow.prop(kmi, "ctrl", toggle=True)
  156. subrow.prop(kmi, "alt", toggle=True)
  157. subrow.prop(kmi, "oskey", text="Cmd", toggle=True)
  158. subrow.prop(kmi, "key_modifier", text="", event=True)
  159. # Operator properties
  160. box.template_keymap_item_properties(kmi)
  161. # Modal key maps attached to this operator
  162. if not km.is_modal:
  163. kmm = kc.keymaps.find_modal(kmi.idname)
  164. if kmm:
  165. draw_km(display_keymaps, kc, kmm, None, layout, level + 1)
  166. layout.context_pointer_set("keymap", km)
  167. _EVENT_TYPES = set()
  168. _EVENT_TYPE_MAP = {}
  169. _EVENT_TYPE_MAP_EXTRA = {}
  170. def draw_filtered(display_keymaps, filter_type, filter_text, layout):
  171. if filter_type == 'NAME':
  172. def filter_func(kmi):
  173. return (filter_text in kmi.idname.lower() or
  174. filter_text in kmi.name.lower())
  175. else:
  176. if not _EVENT_TYPES:
  177. enum = bpy.types.Event.bl_rna.properties["type"].enum_items
  178. _EVENT_TYPES.update(enum.keys())
  179. _EVENT_TYPE_MAP.update({item.name.replace(" ", "_").upper(): key
  180. for key, item in enum.items()})
  181. del enum
  182. _EVENT_TYPE_MAP_EXTRA.update({
  183. "`": 'ACCENT_GRAVE',
  184. "*": 'NUMPAD_ASTERIX',
  185. "/": 'NUMPAD_SLASH',
  186. '+': 'NUMPAD_PLUS',
  187. "RMB": 'RIGHTMOUSE',
  188. "LMB": 'LEFTMOUSE',
  189. "MMB": 'MIDDLEMOUSE',
  190. })
  191. _EVENT_TYPE_MAP_EXTRA.update({
  192. "%d" % i: "NUMPAD_%d" % i for i in range(10)
  193. })
  194. # done with once off init
  195. filter_text_split = filter_text.strip()
  196. filter_text_split = filter_text.split()
  197. # Modifier {kmi.attribute: name} mapping
  198. key_mod = {
  199. "ctrl": "ctrl",
  200. "alt": "alt",
  201. "shift": "shift",
  202. "cmd": "oskey",
  203. "oskey": "oskey",
  204. "any": "any",
  205. }
  206. # KeyMapItem like dict, use for comparing against
  207. # attr: {states, ...}
  208. kmi_test_dict = {}
  209. # Special handling of 'type' using a list if sets,
  210. # keymap items must match against all.
  211. kmi_test_type = []
  212. # initialize? - so if a if a kmi has a MOD assigned it wont show up.
  213. # for kv in key_mod.values():
  214. # kmi_test_dict[kv] = {False}
  215. # altname: attr
  216. for kk, kv in key_mod.items():
  217. if kk in filter_text_split:
  218. filter_text_split.remove(kk)
  219. kmi_test_dict[kv] = {True}
  220. # whats left should be the event type
  221. def kmi_type_set_from_string(kmi_type):
  222. kmi_type = kmi_type.upper()
  223. kmi_type_set = set()
  224. if kmi_type in _EVENT_TYPES:
  225. kmi_type_set.add(kmi_type)
  226. if not kmi_type_set or len(kmi_type) > 1:
  227. # replacement table
  228. for event_type_map in (_EVENT_TYPE_MAP, _EVENT_TYPE_MAP_EXTRA):
  229. kmi_type_test = event_type_map.get(kmi_type)
  230. if kmi_type_test is not None:
  231. kmi_type_set.add(kmi_type_test)
  232. else:
  233. # print("Unknown Type:", kmi_type)
  234. # Partial match
  235. for k, v in event_type_map.items():
  236. if (kmi_type in k) or (kmi_type in v):
  237. kmi_type_set.add(v)
  238. return kmi_type_set
  239. for i, kmi_type in enumerate(filter_text_split):
  240. kmi_type_set = kmi_type_set_from_string(kmi_type)
  241. if not kmi_type_set:
  242. return False
  243. kmi_test_type.append(kmi_type_set)
  244. # tiny optimization, sort sets so the smallest is first
  245. # improve chances of failing early
  246. kmi_test_type.sort(key=lambda kmi_type_set: len(kmi_type_set))
  247. # main filter func, runs many times
  248. def filter_func(kmi):
  249. for kk, ki in kmi_test_dict.items():
  250. val = getattr(kmi, kk)
  251. if val not in ki:
  252. return False
  253. # special handling of 'type'
  254. for ki in kmi_test_type:
  255. val = kmi.type
  256. if val == 'NONE' or val not in ki:
  257. # exception for 'type'
  258. # also inspect 'key_modifier' as a fallback
  259. val = kmi.key_modifier
  260. if not (val == 'NONE' or val not in ki):
  261. continue
  262. return False
  263. return True
  264. for km, kc in display_keymaps:
  265. km = km.active()
  266. layout.context_pointer_set("keymap", km)
  267. filtered_items = [kmi for kmi in km.keymap_items if filter_func(kmi)]
  268. if filtered_items:
  269. col = layout.column()
  270. row = col.row()
  271. row.label(text=km.name, icon='DOT')
  272. row.label()
  273. row.label()
  274. if km.is_user_modified:
  275. row.operator("preferences.keymap_restore", text="Restore")
  276. else:
  277. row.label()
  278. for kmi in filtered_items:
  279. draw_kmi(display_keymaps, kc, km, kmi, col, 1)
  280. return True
  281. def draw_hierarchy(display_keymaps, layout):
  282. from bl_keymap_utils import keymap_hierarchy
  283. for entry in keymap_hierarchy.generate():
  284. draw_entry(display_keymaps, entry, layout)
  285. def draw_keymaps(context, layout):
  286. from bl_keymap_utils.io import keyconfig_merge
  287. wm = context.window_manager
  288. kc_user = wm.keyconfigs.user
  289. kc_active = wm.keyconfigs.active
  290. spref = context.space_data
  291. # row.prop_search(wm.keyconfigs, "active", wm, "keyconfigs", text="Key Config")
  292. text = bpy.path.display_name(kc_active.name, has_ext=False)
  293. if not text:
  294. text = "Blender (default)"
  295. split = layout.split(factor=0.6)
  296. row = split.row()
  297. rowsub = row.row(align=True)
  298. rowsub.menu("USERPREF_MT_keyconfigs", text=text)
  299. rowsub.operator("wm.keyconfig_preset_add", text="", icon='ADD')
  300. rowsub.operator("wm.keyconfig_preset_add", text="", icon='REMOVE').remove_active = True
  301. rowsub = split.row(align=True)
  302. rowsub.operator("preferences.keyconfig_import", text="Import...", icon='IMPORT')
  303. rowsub.operator("preferences.keyconfig_export", text="Export...", icon='EXPORT')
  304. row = layout.row()
  305. col = layout.column()
  306. # layout.context_pointer_set("keyconfig", wm.keyconfigs.active)
  307. # row.operator("preferences.keyconfig_remove", text="", icon='X')
  308. rowsub = row.split(factor=0.3, align=True)
  309. # postpone drawing into rowsub, so we can set alert!
  310. layout.separator()
  311. display_keymaps = keyconfig_merge(kc_user, kc_user)
  312. filter_type = spref.filter_type
  313. filter_text = spref.filter_text.strip()
  314. if filter_text:
  315. filter_text = filter_text.lower()
  316. ok = draw_filtered(display_keymaps, filter_type, filter_text, layout)
  317. else:
  318. draw_hierarchy(display_keymaps, layout)
  319. ok = True
  320. # go back and fill in rowsub
  321. rowsub.prop(spref, "filter_type", text="")
  322. rowsubsub = rowsub.row(align=True)
  323. if not ok:
  324. rowsubsub.alert = True
  325. rowsubsub.prop(spref, "filter_text", text="", icon='VIEWZOOM')
  326. if not filter_text:
  327. # When the keyconfig defines it's own preferences.
  328. kc_prefs = kc_active.preferences
  329. if kc_prefs is not None:
  330. box = col.box()
  331. row = box.row(align=True)
  332. pref = context.preferences
  333. keymappref = pref.keymap
  334. show_ui_keyconfig = keymappref.show_ui_keyconfig
  335. row.prop(
  336. keymappref,
  337. "show_ui_keyconfig",
  338. text="",
  339. icon='DISCLOSURE_TRI_DOWN' if show_ui_keyconfig else 'DISCLOSURE_TRI_RIGHT',
  340. emboss=False,
  341. )
  342. row.label(text="Preferences")
  343. if show_ui_keyconfig:
  344. # Defined by user preset, may contain mistakes out of our control.
  345. try:
  346. kc_prefs.draw(box)
  347. except Exception:
  348. import traceback
  349. traceback.print_exc()
  350. del box
  351. del kc_prefs