PyAPI: Support KeyMap.keymap_items.find_match(...)

There was no convenient way for an add-on to show it's "user"
key-map items in the preferences, "user" meaning the key-map
item which is edited in the key-map editor, not the original
key-map item created in `wm.keyconfigs.addon`.

Add a method to key-maps to lookup a user key-map item from an
add-on key-map & key-map-item.

Ref !134830
This commit is contained in:
Campbell Barton
2025-02-23 21:05:19 +11:00
parent 28126b83a5
commit 4a6d687d53
3 changed files with 160 additions and 2 deletions

View File

@@ -446,6 +446,16 @@ static void rna_KeyMap_item_remove(wmKeyMap *km, ReportList *reports, PointerRNA
kmi_ptr->invalidate();
}
static PointerRNA rna_KeyMap_item_find_match(
ID *id, wmKeyMap *km_user, ReportList *reports, wmKeyMap *km_addon, wmKeyMapItem *kmi_addon)
{
wmKeyMapItem *kmi_match = WM_keymap_item_find_match(km_user, km_addon, kmi_addon, reports);
if (kmi_match) {
return RNA_pointer_create_discrete(id, &RNA_KeyMapItem, kmi_match);
}
return PointerRNA_NULL;
}
static PointerRNA rna_KeyMap_item_find_from_operator(ID *id,
wmKeyMap *km,
const char *idname,
@@ -1354,6 +1364,21 @@ void RNA_api_keymapitems(StructRNA *srna)
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_RNAPTR);
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "find_match", "rna_KeyMap_item_find_match");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_REPORTS);
parm = RNA_def_pointer(func, "keymap", "KeyMap", "", "The matching keymap");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
parm = RNA_def_pointer(func, "item", "KeyMapItem", "", "The matching keymap item");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
parm = RNA_def_pointer(func,
"result",
"KeyMapItem",
"",
"The keymap item from this keymap which matches the keymap item from the "
"arguments passed in");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_RNAPTR);
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "match_event", "rna_KeyMap_item_match_event");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_CONTEXT);
parm = RNA_def_pointer(func, "event", "Event", "", "");

View File

@@ -92,6 +92,16 @@ bool WM_keymap_poll(bContext *C, wmKeyMap *keymap);
wmKeyMapItem *WM_keymap_item_find_id(wmKeyMap *keymap, int id);
bool WM_keymap_item_compare(const wmKeyMapItem *k1, const wmKeyMapItem *k2);
/**
* Return the user key-map item from `km_base` based on `km_match` & `kmi_match`,
* currently the supported use case is looking up "User" key-map items from "Add-on" key-maps.
* Other lookups may be supported.
*/
wmKeyMapItem *WM_keymap_item_find_match(wmKeyMap *km_base,
wmKeyMap *km_match,
wmKeyMapItem *kmi_match,
ReportList *reports);
/* `wm_keymap_utils.cc`. */
/* Wrappers for #WM_keymap_add_item. */

View File

@@ -33,6 +33,7 @@
#include "BKE_global.hh"
#include "BKE_idprop.hh"
#include "BKE_main.hh"
#include "BKE_report.hh"
#include "BKE_screen.hh"
#include "BKE_workspace.hh"
@@ -501,6 +502,12 @@ bool WM_keymap_poll(bContext *C, wmKeyMap *keymap)
return true;
}
static bool wm_keymap_is_match(const wmKeyMap *km_a, const wmKeyMap *km_b)
{
return ((km_a->spaceid == km_b->spaceid) && (km_a->regionid == km_b->regionid) &&
STREQ(km_a->idname, km_b->idname));
}
static void keymap_event_set(wmKeyMapItem *kmi, const KeyMapItem_Params *params)
{
kmi->type = params->type;
@@ -2105,6 +2112,17 @@ void WM_keymap_restore_to_default(wmKeyMap *keymap, wmWindowManager *wm)
}
}
const char *WM_bool_as_string(bool test)
{
return test ? IFACE_("ON") : IFACE_("OFF");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Keymap Queries
* \{ */
wmKeyMapItem *WM_keymap_item_find_id(wmKeyMap *keymap, int id)
{
LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
@@ -2116,9 +2134,114 @@ wmKeyMapItem *WM_keymap_item_find_id(wmKeyMap *keymap, int id)
return nullptr;
}
const char *WM_bool_as_string(bool test)
wmKeyMapItem *WM_keymap_item_find_match(wmKeyMap *km_base,
wmKeyMap *km_match,
wmKeyMapItem *kmi_match,
ReportList *reports)
{
return test ? IFACE_("ON") : IFACE_("OFF");
/* NOTE: this is called by RNA, some of the reports in this function
* would be asserts when called from C++. */
if (wm_keymap_update_flag != 0) {
/* NOTE: this could be limited to the key-maps marked for updating.
* However #KEYMAP_UPDATE is only cleared for `wm->userconf`
* so only check the global flag for now.
*
* Use a warning not an error because scripts cannot prevent other scripts from manipulating
* key-map items, so we won't want scripts to create exceptions in unrelated scripts.
* Ideally we could detect which key-maps have been modified. */
BKE_reportf(reports,
RPT_WARNING,
"KeyMap item result may be incorrect since an update is pending, call "
"`context.window_manager.keyconfigs.update()` to ensure matches can be found.");
}
if (km_base == km_match) {
/* We could also return `kmi_match` (it's technically correct)
* however this is almost certainly API misuse (as it's a no-op). */
BKE_report(reports, RPT_ERROR, "KeyMaps are equal");
return nullptr;
}
const char *idname = km_base->idname;
const short spaceid = km_base->spaceid;
const short regionid = km_base->regionid;
if (!wm_keymap_is_match(km_base, km_match)) {
BKE_reportf(
reports, RPT_ERROR, "KeyMap \"%s\" doesn't match \"%s\"", idname, km_match->idname);
return nullptr;
}
wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
wmKeyConfig *kc_active = WM_keyconfig_active(wm);
/* NOTE: the key-maps could store this, it would simplify checks here. */
enum {
KM_TYPE_UNKNOWN = 0,
KM_TYPE_USER,
KM_TYPE_ADDON,
KM_TYPE_ACTIVE,
/* No support yet for preferences. */
} base_type = KM_TYPE_UNKNOWN,
match_type = KM_TYPE_UNKNOWN;
if (km_base->flag & KEYMAP_USER) {
if (km_base == WM_keymap_list_find(&wm->userconf->keymaps, idname, spaceid, regionid)) {
base_type = KM_TYPE_USER;
}
}
if ((km_match->flag & KEYMAP_USER) == 0) {
if (km_match == WM_keymap_list_find(&wm->addonconf->keymaps, idname, spaceid, regionid)) {
match_type = KM_TYPE_ADDON;
}
else if (km_match == WM_keymap_list_find(&kc_active->keymaps, idname, spaceid, regionid)) {
match_type = KM_TYPE_ACTIVE;
}
}
if (base_type == KM_TYPE_UNKNOWN) {
BKE_reportf(reports, RPT_ERROR, "KeyMap \"%s\" (base) must be a user keymap", idname);
return nullptr;
}
if (match_type == KM_TYPE_UNKNOWN) {
BKE_reportf(
reports, RPT_ERROR, "KeyMap \"%s\" (other) must be an add-on or active keymap", idname);
return nullptr;
}
const int kmi_index = BLI_findindex(&km_match->items, kmi_match);
if (kmi_index == -1) {
BKE_reportf(reports, RPT_ERROR, "KeyMap \"%s\" item not part of the keymap", idname);
return nullptr;
}
int kmi_id;
if (match_type == KM_TYPE_ADDON) {
/* Perform the following lookup that calculates the ID that *would* be used
* if the user key-map was re-created, see: #WM_keymap_item_restore_to_default.
*
* Find the index of the key-map item and add this to the `defaultmap`'s key-map index
* since this is how the "user" key-map ID's are generated.
*
* This is needed so add-ons can show the user key-map items in preferences. */
wmKeyMap *defaultmap = wm_keymap_preset(wm, kc_active, km_base);
if (defaultmap == nullptr) {
/* This should practically never fail, it could be caused by failure
* to refresh the user key-map after manipulating the add-on key-map. */
return nullptr;
}
kmi_id = defaultmap->kmi_id + kmi_index + 1;
}
else {
kmi_id = kmi_match->id;
}
/* Returning null here isn't an error because it's possible there is no match. */
return WM_keymap_item_find_id(km_base, kmi_id);
}
/** \} */