Custom UI to change a Keymap in addon's preferences : "preferences.keyitem_restore" is grayed out

Hello,

I’m trying to write a template for specific keymaps to let the user change the shortcuts similarly to Blender’s way, but in the addon’s preferences UI.
Here I list the specific keymaps by their idnames. When the shortcut is not changed, there is a box that appears (placeholder to prevent them from deleting the keymap from the addon’s preferences. But if they change it, it becomes a Restore button. And my problem is that this line row.operator("preferences.keyitem_restore", text="", icon = "BACK").item_id = kmi.id is grayed out, and I don’t understand what’s missing?

class Addon_Keymaps_Items(Operator):
    bl_idname = "addon_keymaps_items.keymap_template"
    bl_label = "Keymap Item Test"
    bl_options = {'REGISTER', 'UNDO'}
    
    def draw(self, context):
        layout = self.layout
        layout.ui_units_x = 30.0
        layout.label(text = "TEST PANEL", icon = "MENU_PANEL")

        wm = context.window_manager
        kc = wm.keyconfigs.user
        
        if kc:
            for km in kc.keymaps:
                for kmi in km.keymap_items:
                    if (kmi.idname == 'wm.call_menu' and kmi.properties.name == 'TOPBAR_MT_file_new' or
                        kmi.idname == 'wm.open_mainfile' or
                        kmi.idname == 'wm.save_mainfile'):

                        col = layout.column(align=True)
                        box = col.box()

                        split = box.split()
                        row = split.row(align=True)
                        row.prop(kmi, "show_expanded", text="", emboss=False)
                        row.prop(kmi, "active", text="", emboss=False)
                        row.label(text=kmi.name)

                        row = split.row()
                        row.prop(kmi, "map_type", text="")
                        row.prop(kmi, "type", text="", full_event=True)
                        if (not kmi.is_user_defined) and kmi.is_user_modified:
                            row.operator("preferences.keyitem_restore", text="", icon = "BACK").item_id = kmi.id
                        else:
                            box = row.box()
                            box.scale_x = 0.75
                            box.scale_y = 0.55
                            box.label(text = "", icon = "BLANK1")

Upon further investigation, I think it might be grayed out because it’s failing a poll().

So I looked at Blender’s implementation of the preferences.keyitem_restore:

class PREFERENCES_OT_keyitem_restore(Operator):
    """Restore key map item"""
    bl_idname = "preferences.keyitem_restore"
    bl_label = "Restore Key Map Item"

    item_id: IntProperty(
        name="Item Identifier",
        description="Identifier of the item to restore",
    )

    @classmethod
    def poll(cls, context):
        keymap = getattr(context, "keymap", None)
        return keymap

    def execute(self, context):
        km = context.keymap
        kmi = km.keymap_items.from_id(self.item_id)

        if (not kmi.is_user_defined) and kmi.is_user_modified:
            km.restore_item_to_default(kmi)

        return {'FINISHED'}

Does anyone have an idea of what exactly it is failing?

in the context you’re in, there is no ‘keymap’ attribute. either override the context or find the keymap manually in the window manager object.

Just before your row.operator(...) try something like row.context_pointer_set("keymap", km) ?

I solved the issue like this, by implementing another class and having this km.restore_item_to_default(kmi) being responsible for restoring the item. But it would have been better to be able to spare a class just to use that.

def filter_keymap_items(kmi):
    return (
	kmi.idname == 'wm.call_menu' and kmi.properties.name == 'TOPBAR_MT_file_new' or
        kmi.idname == 'wm.open_mainfile' or
        kmi.idname == 'wm.save_mainfile'
    )

And the execute method of the class:

    def execute(self, context):
        wm = context.window_manager
        kc = wm.keyconfigs.user

        if kc:
            for km in kc.keymaps:
                for kmi in filter(filter_keymap_items, km.keymap_items):
                    km.restore_item_to_default(kmi)
        
        return {'FINISHED'}

@Gorgious
I couldn’t make it work with what you proposed.