ui/common/action.c

Fri, 17 Apr 2026 13:21:11 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 17 Apr 2026 13:21:11 +0200
changeset 1092
0accf125a65f
parent 1066
afe02792303d
permissions
-rw-r--r--

add actions, implement action binding for menu items (GTK)

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2026 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "action.h"
#include "context.h"

#include <cx/string.h>

void uic_add_action(
        UiContext *ctx,
        const char *name,
        ui_callback callback,
        void *userdata,
        const char *accelerator,
        const char *accelerator_text)
{
    if(!name) {
        return;
    }
    
    UiAction action;
    action.name = ui_strdup(ctx, name);
    action.callback = callback;
    action.userdata = userdata;
    action.accelerator = accelerator ? ui_strdup(ctx, accelerator) : NULL;
    action.accelerator_text = accelerator_text ? ui_strdup(ctx, accelerator_text) : NULL;
    action.ctx = ctx;
    cxMapPut(ctx->actions, name, &action);
    cxMapRehash(ctx->actions);
}

void uic_bind_action(
        UiContext *ctx,
        const char *action,
        void *bind_obj,
        ui_enablefunc set_enabled)
{
    if(!action) {
        return;
    }
    
    UiActionBinding binding;
    binding.action = ui_strdup(ctx, action);
    binding.userdata = bind_obj;
    binding.set_enabled = set_enabled;
    cxListAdd(ctx->action_bindings, &binding);
}

UiAction* uic_resolve_action(UiContext *ctx, const char *action) {
    UiAction *a = NULL;
    if(ctx->actions) {
        a = cxMapGet(ctx->actions, action);
    }
    // check if any sub-document defines this action
    // sub-document actions have precedence, the most specific action will
    // be returned
    CxIterator i = cxListIterator(ctx->documents);
    cx_foreach(void *, doc, i) {
        UiContext *doc_ctx = ui_document_context(doc);
        UiAction *sub_action = uic_resolve_action(doc_ctx, action);
        if(sub_action) {
            a = sub_action;
            // if one sub-tree has an action, we don't care about other
            // subtrees
            break;
        }
    }
    
    if(!a && ctx->parent) {
        // check parents
        a = uic_resolve_action_from_parents(ctx, action);
    }
    
    return a;
}

UiAction* uic_resolve_action_from_parents(UiContext *ctx, const char *action) {
    UiContext *parent = ctx->parent;
    if(parent == NULL) {
        return NULL;
    }
    if(parent->actions) {
        UiAction *a = cxMapGet(parent->actions, action);
        if(a) {
            return a;
        }
    }
    return uic_resolve_action_from_parents(parent, action);
}



void ui_update_action_bindings(UiContext *ctx) {
    CxIterator i = cxListIterator(ctx->action_bindings);
    cx_foreach(UiActionBinding*, binding, i) {
        UiAction *action = uic_resolve_action(ctx, binding->action);
        if(binding->set_enabled) {
            binding->set_enabled(binding->userdata, action != NULL);
        }
    }
}

void uic_action_callback(UiEvent *event, const char *action_name) {
    UiContext *ctx = ui_global_context();
    if(event->obj) {
        ctx = event->obj->ctx;
    }
    
    UiAction *action = uic_resolve_action(ctx, action_name);
    if(action) {
        // override event document: for actions we know that the event is
        // for a specific document
        event->document = action->ctx->self_doc;
        if(action->callback) {
            action->callback(event, action->userdata);
        }
    }
}

mercurial