--- a/ui/motif/button.c Wed Dec 04 08:57:35 2024 +0100 +++ b/ui/motif/button.c Wed Dec 04 18:31:22 2024 +0100 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2014 Olaf Wintermann. All rights reserved. + * Copyright 2024 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: @@ -38,69 +38,54 @@ #include <cx/array_list.h> #include <cx/compare.h> +#include <Xm/XmAll.h> -UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) { - UiContainer *ct = uic_get_current_container(obj); - XmString str = XmStringCreateLocalized(label); + +UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) { + Arg xargs[16]; + int n = 0; - int n = 0; - Arg args[16]; + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); - XtSetArg(args[n], XmNlabelString, str); - n++; + Widget parent = ctn->prepare(ctn, xargs, &n); - Widget parent = ct->prepare(ct, args, &n, FALSE); - Widget button = XmCreatePushButton(parent, "button", args, n); - ct->add(ct, button); + XmString label = NULL; + if(args.label) { + label = XmStringCreateLocalized((char*)args.label); + XtSetArg(xargs[n], XmNlabelString, label); n++; + } - if(f) { - UiEventData *event = cxMalloc( - obj->ctx->allocator, - sizeof(UiEventData)); - event->obj = obj; - event->userdata = data; - event->callback = f; - event->value = 0; + char *name = args.name ? (char*)args.name : "button"; + Widget button = XmCreatePushButton(parent, name, xargs, n); + XtManageChild(button); + ctn->add(ctn, button); + + ui_set_widget_groups(obj->ctx, button, args.groups); + + if(args.onclick) { + UiEventData *eventdata = malloc(sizeof(UiEventData)); + eventdata->callback = args.onclick; + eventdata->userdata = args.onclickdata; + eventdata->obj = obj; + eventdata->value = 0; XtAddCallback( button, XmNactivateCallback, (XtCallbackProc)ui_push_button_callback, - event); + eventdata); + XtAddCallback( + button, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + eventdata); } - XtManageChild(button); + XmStringFree(label); return button; } -// wrapper -int64_t ui_toggle_button_get(UiInteger *i) { - int state = 0; - XtVaGetValues(i->obj, XmNset, &state, NULL); - i->value = state; - return state; -} - -void ui_toggle_button_set(UiInteger *i, int64_t value) { - Arg arg; - XtSetArg(arg, XmNset, value); - XtSetValues(i->obj, &arg, 1); - i->value = value; -} - -void ui_toggle_button_callback( - Widget widget, - UiEventData *event, - XmToggleButtonCallbackStruct *tb) -{ - UiEvent e; - e.obj = event->obj; - e.window = event->obj->window; - // TODO: e.document - e.intval = tb->set; - event->callback(&e, event->userdata); -} - void ui_push_button_callback(Widget widget, UiEventData *event, XtPointer d) { UiEvent e; e.obj = event->obj; @@ -110,105 +95,295 @@ event->callback(&e, event->userdata); } +UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + Widget parent = ctn->prepare(ctn, xargs, &n); + XtSetArg(xargs[n], XmNfillOnSelect, True); n++; + XtSetArg(xargs[n], XmNindicatorOn, False); n++; + + XmString label = NULL; + if(args.label) { + label = XmStringCreateLocalized((char*)args.label); + XtSetArg(xargs[n], XmNlabelString, label); n++; + } + + char *name = args.name ? (char*)args.name : "togglebutton"; + Widget button = XmCreateToggleButton(parent, name, xargs, n); + XtManageChild(button); + ctn->add(ctn, button); + + ui_set_widget_groups(obj->ctx, button, args.groups); + + ui_bind_togglebutton(obj, button, args.varname, args.value, args.onchange, args.onchangedata, args.enable_group); + + XmStringFree(label); + return button; +} -static void radio_callback( - Widget widget, - RadioEventData *event, - XmToggleButtonCallbackStruct *tb) -{ - if(tb->set) { - RadioButtonGroup *group = event->group; - if(group->current) { - Arg arg; - XtSetArg(arg, XmNset, FALSE); - XtSetValues(group->current, &arg, 1); +UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + Widget parent = ctn->prepare(ctn, xargs, &n); + + XmString label = NULL; + if(args.label) { + label = XmStringCreateLocalized((char*)args.label); + XtSetArg(xargs[n], XmNlabelString, label); n++; + } + + char *name = args.name ? (char*)args.name : "button"; + Widget button = XmCreateToggleButton(parent, name, xargs, n); + XtManageChild(button); + ctn->add(ctn, button); + + ui_set_widget_groups(obj->ctx, button, args.groups); + + ui_bind_togglebutton(obj, button, args.varname, args.value, args.onchange, args.onchangedata, args.enable_group); + + XmStringFree(label); + return button; +} + +UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) { + return ui_checkbox_create(obj, args); +} + +static void togglebutton_changed(Widget w, UiVarEventData *event, XmToggleButtonCallbackStruct *tb) { + if(event->value > 0) { + // button in configured to enable/disable states + if(tb->set) { + ui_set_group(event->obj->ctx, event->value); + } else { + ui_unset_group(event->obj->ctx, event->value); } - group->current = widget; + } + + UiEvent e; + e.obj = event->obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = NULL; + e.intval = XmToggleButtonGetState(w); + + if(event->callback) { + event->callback(&e, event->userdata); + } + + if(event->var && event->var->value) { + UiInteger *v = event->var->value; + v->value = e.intval; + ui_notify_evt(v->observers, &e); } } -UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) { - UiContainer *ct = uic_get_current_container(obj); - XmString str = XmStringCreateLocalized(label); - - int n = 0; - Arg args[16]; - - XtSetArg(args[n], XmNlabelString, str); - n++; - XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY_ROUND); - n++; - - Widget parent = ct->prepare(ct, args, &n, FALSE); - Widget button = XmCreateToggleButton(parent, "radiobutton", args, n); - ct->add(ct, button); - - if(rgroup) { - RadioButtonGroup *group; - if(rgroup->obj) { - group = rgroup->obj; - if(!group->buttons) { - group->buttons = cxArrayListCreate(cxDefaultAllocator, cx_cmp_uintptr, CX_STORE_POINTERS, 8); - } - cxListAdd(group->buttons, button); - group->ref++; - } else { - group = malloc(sizeof(RadioButtonGroup)); - group->buttons = cxArrayListCreate(cxDefaultAllocator, cx_cmp_uintptr, CX_STORE_POINTERS, 8); - cxListAdd(group->buttons, button); - group->current = button; - // this is the first button in the radiobutton group - // so we should enable it - Arg arg; - XtSetArg(arg, XmNset, TRUE); - XtSetValues(button, &arg, 1); - rgroup->obj = group; - - group->current = button; +void ui_bind_togglebutton( + UiObject *obj, + Widget widget, + const char *varname, + UiInteger *value, + ui_callback onchange, + void *onchangedata, + int enable_state) +{ + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, value, varname, UI_VAR_INTEGER); + if(var) { + value = (UiInteger*)var->value; + value->obj = widget; + value->get = ui_togglebutton_get; + value->set = ui_togglebutton_set; + + if(value->value) { + XmToggleButtonSetState(widget, True, False); } - - RadioEventData *event = malloc(sizeof(RadioEventData)); - event->obj = obj; - event->callback = NULL; - event->userdata = NULL; - event->group = group; - XtAddCallback( - button, - XmNvalueChangedCallback, - (XtCallbackProc)radio_callback, - event); - - rgroup->get = ui_radiobutton_get; - rgroup->set = ui_radiobutton_set; } - XtManageChild(button); - return button; + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->callback = onchange; + event->userdata = onchangedata; + event->var = var; + event->observers = NULL; + event->value = enable_state; + XtAddCallback( + widget, + XmNvalueChangedCallback, + (XtCallbackProc)togglebutton_changed, + event); + XtAddCallback( + widget, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + event); +} + +int64_t ui_togglebutton_get(UiInteger *i) { + Widget togglebutton = i->obj; + Boolean state = XmToggleButtonGetState(togglebutton); + i->value = state; + return state; +} + +void ui_togglebutton_set(UiInteger *i, int64_t value) { + Widget togglebutton = i->obj; + i->value = value; + XmToggleButtonSetState(togglebutton, (Boolean)value, False); +} + +static void destroy_list(Widget w, CxList *list, XtPointer d) { + cxListDestroy(list); } -int64_t ui_radiobutton_get(UiInteger *value) { - RadioButtonGroup *group = value->obj; +static void radiobutton_changed(Widget w, UiVarEventData *event, XmToggleButtonCallbackStruct *tb) { + if(event->value > 0) { + // button in configured to enable/disable states + if(tb->set) { + ui_set_group(event->obj->ctx, event->value); + } else { + ui_unset_group(event->obj->ctx, event->value); + } + } + + if(!tb->set) { + return; // only handle set-events + } - int i = cxListFind(group->buttons, group->current); - if (i >= 0) { - value->value = i; - return i; - } else { - return 0; + UiInteger *value = NULL; + int64_t v = 0; + if(event->var) { + value = event->var->value; + // find widget index and update all radiobuttons + // the UiInteger value must always be up-to-date + CxList *list = value->obj; + CxIterator i = cxListIterator(list); + cx_foreach(Widget, button, i) { + Boolean state = False; + if(button == w) { + value->value = i.index+1; // update value + state = True; + } + XmToggleButtonSetState(button, state, False); + } + v = value->value; + } + + UiEvent e; + e.obj = event->obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = value; + e.intval = v; + + if(event->callback) { + event->callback(&e, event->userdata); + } + + if(value) { + ui_notify_evt(value->observers, &e); } } -void ui_radiobutton_set(UiInteger *value, int64_t i) { - RadioButtonGroup *group = value->obj; - Arg arg; +UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + Widget parent = ctn->prepare(ctn, xargs, &n); + XtSetArg(xargs[n], XmNindicatorType, XmONE_OF_MANY_ROUND); n++; + XmString label = NULL; + if(args.label) { + label = XmStringCreateLocalized((char*)args.label); + XtSetArg(xargs[n], XmNlabelString, label); n++; + } + + char *name = args.name ? (char*)args.name : "button"; + Widget button = XmCreateToggleButton(parent, name, xargs, n); + XtManageChild(button); + ctn->add(ctn, button); + + ui_set_widget_groups(obj->ctx, button, args.groups); - XtSetArg(arg, XmNset, FALSE); - XtSetValues(group->current, &arg, 1); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER); + if(var) { + UiInteger *value = var->value; + CxList *rb = value->obj; + if(!rb) { + // first button in the radiobutton group + // create a list for all buttons and use the list as value obj + rb = cxArrayListCreateSimple(CX_STORE_POINTERS, 4); + value->obj = rb; + value->get = ui_radiobutton_get; + value->set = ui_radiobutton_set; + + // the first radio button is also responsible for cleanup + XtAddCallback( + button, + XmNdestroyCallback, + (XtCallbackProc)destroy_list, + rb); + } + cxListAdd(rb, button); + + // set the radiobutton state, if the value is already set + if(cxListSize(rb) == value->value) { + XmToggleButtonSetState(button, True, False); + } + } - Widget button = cxListAt(group->buttons, i); - if(button) { - XtSetArg(arg, XmNset, TRUE); - XtSetValues(button, &arg, 1); - group->current = button; + // the radio button needs to handle change events to update all + // other buttons in the radio button group + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->callback = args.onchange; + event->userdata = args.onchangedata; + event->observers = NULL; + event->var = var; + event->value = args.enable_group; + XtAddCallback( + button, + XmNvalueChangedCallback, + (XtCallbackProc)radiobutton_changed, + event); + XtAddCallback( + button, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + event); + + XmStringFree(label); + return button; + + +} + +int64_t ui_radiobutton_get(UiInteger *i) { + // the UiInteger should be updated automatically by change events + return i->value; +} + +void ui_radiobutton_set(UiInteger *i, int64_t value) { + CxList *list = i->obj; + if(i->value > 0) { + Widget current = cxListAt(list, i->value-1); + if(current) { + XmToggleButtonSetState(current, False, False); + } + } + if(value > 0 && value <= cxListSize(list)) { + Widget button = cxListAt(list, value-1); + if(button) { + XmToggleButtonSetState(button, True, False); + i->value = value; + } } }