ui/motif/button.c

changeset 100
d2bd73d28ff1
parent 0
2483f517c562
--- a/ui/motif/button.c	Fri Nov 29 22:21:36 2024 +0100
+++ b/ui/motif/button.c	Thu Dec 12 20:01:43 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;
+        }
     }
 }

mercurial