client/uiclient.c

Wed, 10 Dec 2025 22:22:55 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 10 Dec 2025 22:22:55 +0100
changeset 984
2cf5e6d55013
parent 983
1d7d24147961
permissions
-rw-r--r--

implement all window types in the client + small fixes

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2025 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 "uiclient.h"
#include "args.h"

#include <stdio.h>
#include <stdlib.h>
#include <cx/hash_map.h>

#include "../ui/common/args.h"

#ifndef _WIN32

static UiMessageHandler *io;

static CxMap *msg_types;
static CxMap *objects;

void client_init(UiMessageHandler *handler) {
    io = handler;
    
    msg_types = cxHashMapCreateSimple(CX_STORE_POINTERS);
    cxMapPut(msg_types, "window", msg_window);
    cxMapPut(msg_types, "sidebar_window", msg_window);
    cxMapPut(msg_types, "splitview_window", msg_window);
    cxMapPut(msg_types, "simple_window", msg_window);
    cxMapPut(msg_types, "show", msg_show);
    
    cxMapPut(msg_types, "vbox", msg_vbox);
    cxMapPut(msg_types, "hbox", msg_vbox);
    cxMapPut(msg_types, "grid", msg_vbox);
    cxMapPut(msg_types, "end", msg_end);
    cxMapPut(msg_types, "button", msg_button);
    cxMapPut(msg_types, "toggle", msg_togglebutton);
    
    objects = cxHashMapCreateSimple(CX_STORE_POINTERS);
}

static cxmutstr jsonobj_getstring(const CxJsonValue *obj, const char *name) {
    CxJsonValue *value = cxJsonObjGet(obj, name);
    if(value->type == CX_JSON_STRING) {
        return value->value.string;
    } else {
        return (cxmutstr){ NULL, 0 };
    }
}

static UiBool jsonobj_getbool(const CxJsonValue *obj, const char *name, int *error) {
    CxJsonValue *value = cxJsonObjGet(obj, name);
    if(value->type == CX_JSON_LITERAL) {
        if(*error) {
            *error = 0;
        }
        return value->value.literal == CX_JSON_TRUE ? 1 : 0;
    } else {
        if(error) {
            *error = 1;
        }
        return FALSE;
    }
}

/*
 * UI thread callback
 */
static int msg_received(void *data) {
    CxJsonValue *value = data;
    if(client_handle_json(NULL, value)) {
        fprintf(stderr, "Error: invalid json message\n");
    }
    cxJsonValueFree(value);
    return 0;
}

/*
 * This message callback is executed in the message handler input thread
 */
void client_msg_received(cxstring msg) {
    // parse message
    CxJson json;
    cxJsonInit(&json, NULL);
    
    cxJsonFilln(&json, msg.ptr, msg.length);
    CxJsonValue *value;
    if(cxJsonNext(&json, &value) == CX_JSON_NO_ERROR && value) {
        // handle json message in the UI thread
        ui_call_mainthread(msg_received, value);
    } else {
        fprintf(stderr, "Error: invalid json message\n");
    }
    cxJsonDestroy(&json);
}

int client_handle_json(UiObject *obj, const CxJsonValue *value) {
    if(value->type != CX_JSON_OBJECT) {
        return 1;
    }
    
    CxJsonValue *type = cxJsonObjGet(value, "type");
    if(!type || type->type != CX_JSON_STRING) {
        return 1;
    }
    
    json_msg_handler handler = cxMapGet(msg_types, type->value.string);
    if(!handler) {
        return 1;
    }
    
    return handler(obj, value, type->value.string);
}

int client_handle_children(UiObject *parent, const CxJsonValue *value) {
    CxJsonValue *children = cxJsonObjGet(value, "children");
    if(children && children->type == CX_JSON_ARRAY) {
        for(int i=0;i<children->value.array.array_size;i++) {
            CxJsonValue *child = children->value.array.array[i];
            if(client_handle_json(parent, child)) {
                fprintf(stderr, "Error: invalid child\n");
                return 1;
            }
        }
    }
    return 0;
}

void client_add_obj_mapping(UiObject *obj, cxmutstr id) {
    cxMapPut(objects, id, obj);
    
    CxAllocator *a = ui_allocator(obj->ctx);
    
    WindowData *wdata = cxMalloc(a, sizeof(WindowData));
    wdata->widgets = cxHashMapCreate(a, CX_STORE_POINTERS, 128);
    obj->window = wdata;
}

UiObject* client_get_mapped_obj(cxmutstr id) {
    return cxMapGet(objects, id);
}

void client_reg_widget(UiObject *obj, cxmutstr id, UIWIDGET w) {
    WindowData *wdata = obj->window;
    if(!wdata) {
        fprintf(stderr, "Error: missing obj window data\n");
        return;
    }
    cxMapPut(wdata->widgets, id, w);
}

UIWIDGET client_get_widget(UiObject *obj, cxmutstr id) {
    WindowData *wdata = obj->window;
    if(!wdata) {
        fprintf(stderr, "Error: missing obj window data\n");
        return NULL;
    }
    return cxMapGet(wdata->widgets, id);
}

static UiObject* get_msg_obj(UiObject *obj, const CxJsonValue *value) {
    if(obj) {
        return obj;
    }
    CxJsonValue *obj_id = cxJsonObjGet(value, "obj");
    if(!obj_id || obj_id->type != CX_JSON_STRING) {
        return NULL;
    }
    return client_get_mapped_obj(obj_id->value.string);
}

int msg_window(UiObject *parent, const CxJsonValue *value, cxmutstr type) {
    cxmutstr obj_id = jsonobj_getstring(value, "obj");
    cxmutstr id = jsonobj_getstring(value, "id");
    cxmutstr title = jsonobj_getstring(value, "title");
    
    if(!obj_id.ptr) {
        return 1;
    }
    if(!id.ptr) {
        return 1;
    }
    
    UiObject *obj;
    if(!cx_strcmp(type, "window")) {
        obj = ui_window(title.ptr, NULL);
    } else if(!cx_strcmp(type, "sidebar_window")) {
        obj = ui_sidebar_window(title.ptr, NULL);
    } else if(!cx_strcmp(type, "splitview_window")) {
        int err;
        bool sidebar = jsonobj_getbool(value, "sidebar", &err);
        if(err) {
            return 1;
        }
        obj = ui_splitview_window(title.ptr, sidebar);
    } else if(!cx_strcmp(type, "simple_window")) {
        obj = ui_simple_window(title.ptr, NULL);
    }
    
    client_add_obj_mapping(obj, obj_id);
    
    if(obj->widget) {
        client_reg_widget(obj, id, obj->widget);
    }
    
    return client_handle_children(obj, value);
}

int msg_show(UiObject *parent, const CxJsonValue *value, cxmutstr type) {
    UiObject *obj = client_get_mapped_obj(jsonobj_getstring(value, "obj"));
    if(!obj) {
        return 1;
    }
    ui_show(obj);
    return 0;
}

typedef UIWIDGET(*ctcreate_func)(UiObject *obj, UiContainerArgs *args);

static int msg_container(UiObject *parent, const CxJsonValue *value, ctcreate_func create) {
    CxJsonValue *args_value = cxJsonObjGet(value, "args");
    cxmutstr id = jsonobj_getstring(value, "id");
    if(!id.ptr) {
        return 1;
    }
    UiObject *obj = get_msg_obj(parent, value);
    if(!obj) {
        return 1;
    }
    
    UiContainerArgs *args = json2container_args(args_value);
    UIWIDGET w = create(obj, args);
    ui_container_args_free(args);
    client_reg_widget(obj, id, w);
    
    return 0;
}

int msg_vbox(UiObject *parent, const CxJsonValue *value, cxmutstr type) {
    return msg_container(parent, value, ui_vbox_create);
}

int msg_hbox(UiObject *parent, const CxJsonValue *value, cxmutstr type) {
    return msg_container(parent, value, ui_hbox_create);
}

int msg_grid(UiObject *parent, const CxJsonValue *value, cxmutstr type) {
    return msg_container(parent, value, ui_grid_create);
}

int msg_end(UiObject *parent, const CxJsonValue *value, cxmutstr type) {
    UiObject *obj = get_msg_obj(parent, value);
    if(!obj) {
        return 1;
    }
    ui_end_new(obj);
    return 0;
}

int msg_button(UiObject *parent, const CxJsonValue *value, cxmutstr type) {
    CxJsonValue *args_value = cxJsonObjGet(value, "args");
    cxmutstr id = jsonobj_getstring(value, "id");
    if(!id.ptr) {
        return 1;
    }
    UiObject *obj = get_msg_obj(parent, value);
    if(!obj) {
        return 1;
    }
    
    UiButtonArgs *args = json2button_args(args_value);
    UIWIDGET w = ui_button_create(obj, args);
    ui_button_args_free(args);
    client_reg_widget(obj, id, w);
    
    return 0;
}

int msg_togglebutton(UiObject *parent, const CxJsonValue *value, cxmutstr type) {
    CxJsonValue *args_value = cxJsonObjGet(value, "args");
    cxmutstr id = jsonobj_getstring(value, "id");
    if(!id.ptr) {
        return 1;
    }
    UiObject *obj = get_msg_obj(parent, value);
    if(!obj) {
        return 1;
    }
    
    CxJsonValue *button_type = cxJsonObjGet(value, "button_type");
    if(!button_type || button_type->type != CX_JSON_INTEGER) {
        return 1;
    }
    
    CxJsonValue *val = cxJsonObjGet(value, "value");
    UiInteger *i = NULL;
    if(val && val->type == CX_JSON_STRING) {
        i = ui_get_int_var(obj->ctx, val->value.string.ptr);
        if(!i) {
            i = ui_int_new(obj->ctx, val->value.string.ptr);
        }
    }
    
    UiToggleArgs *args = json2toggle_args(args_value);
    UIWIDGET w;
    switch(button_type->value.integer) {
        default: {
            w = ui_togglebutton_create(obj, args);
            break;
        }
        case 1: {
            w = ui_checkbox_create(obj, args);
            break;
        }
        case 2: {
            w = ui_switch_create(obj, args);
            break;
        }
        case 3: {
            w = ui_radiobutton_create(obj, args);
            break;
        }
    }
    ui_toggle_args_free(args);
    client_reg_widget(obj, id, w);
    
    return 0;
}

#endif

mercurial