UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2025 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "button.h" 30 #include "widget.h" 31 32 #include <stdio.h> 33 #include <stdlib.h> 34 35 #include <cx/array_list.h> 36 37 #include <commctrl.h> 38 39 static W32WidgetClass button_widget_class = { 40 .eventproc = ui_button_eventproc, 41 .enable = w32_widget_default_enable, 42 .show = w32_widget_default_show, 43 .get_preferred_size = ui_button_get_preferred_size, 44 .destroy = w32_widget_default_destroy 45 }; 46 47 static W32WidgetClass togglebutton_widget_class = { 48 .eventproc = ui_togglebutton_eventproc, 49 .enable = w32_widget_default_enable, 50 .show = w32_widget_default_show, 51 .get_preferred_size = ui_button_get_preferred_size, 52 .destroy = w32_widget_default_destroy 53 }; 54 55 static W32WidgetClass radiobutton_widget_class = { 56 .eventproc = ui_radiobutton_eventproc, 57 .enable = w32_widget_default_enable, 58 .show = w32_widget_default_show, 59 .get_preferred_size = ui_button_get_preferred_size, 60 .destroy = w32_widget_default_destroy 61 }; 62 63 UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args) { 64 HINSTANCE hInstance = GetModuleHandle(NULL); 65 UiContainerPrivate *container = ui_obj_container(obj); 66 HWND parent = ui_container_get_parent(container); 67 UiLayout layout = UI_ARGS2LAYOUT(args); 68 69 HWND hwnd = CreateWindow( 70 "BUTTON", 71 args->label, 72 WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, 73 0, 0, 100, 30, 74 parent, 75 (HMENU)1, 76 hInstance, 77 NULL); 78 ui_win32_set_ui_font(hwnd); 79 80 W32Widget *widget = w32_widget_create(&button_widget_class, hwnd, sizeof(UiWidget)); 81 ui_container_add(container, widget, &layout); 82 83 UiWidget *btn = (UiWidget*)widget; 84 btn->obj = obj; 85 btn->callback = args->onclick; 86 btn->callbackdata = args->onclickdata; 87 88 return widget; 89 } 90 91 W32Size ui_button_get_preferred_size(W32Widget *widget) { 92 W32Size size; 93 size.width = 100; 94 size.height = 30; 95 SIZE sz; 96 if (Button_GetIdealSize(widget->hwnd, &sz)) { 97 size.width = sz.cx; 98 size.height = sz.cy; 99 } 100 return size; 101 } 102 103 int ui_button_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { 104 UiWidget *w = (UiWidget*)widget; 105 106 if (uMsg != WM_COMMAND) { 107 return 0; 108 } 109 110 UiEvent e; 111 e.obj = w->obj; 112 e.document = e.obj->ctx->document; 113 e.window = e.obj->window; 114 e.eventdata = NULL; 115 e.eventdatatype = 0; 116 e.intval = 0; 117 e.set = ui_get_setop(); 118 119 if (w->callback) { 120 w->callback(&e, w->callbackdata); 121 } 122 123 return 0; 124 } 125 126 static UIWIDGET create_togglebutton(UiObject *obj, UiToggleArgs *args, unsigned long type) { 127 HINSTANCE hInstance = GetModuleHandle(NULL); 128 UiContainerPrivate *container = ui_obj_container(obj); 129 HWND parent = ui_container_get_parent(container); 130 UiLayout layout = UI_ARGS2LAYOUT(args); 131 132 HWND hwnd = CreateWindow( 133 "BUTTON", 134 args->label, 135 WS_VISIBLE | WS_CHILD | type, 136 0, 0, 100, 30, 137 parent, 138 (HMENU)1, 139 hInstance, 140 NULL); 141 ui_win32_set_ui_font(hwnd); 142 143 W32Widget *widget = w32_widget_create(&togglebutton_widget_class, hwnd, sizeof(UiWidget)); 144 ui_container_add(container, widget, &layout); 145 146 UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); 147 if (var) { 148 UiInteger *i = var->value; 149 i->obj = widget; 150 i->get = ui_togglebutton_get; 151 i->set = ui_togglebutton_set; 152 if (i->value != 0) { 153 SendMessage(hwnd, BM_SETCHECK, BST_CHECKED, 0); 154 } 155 } 156 157 UiWidget *btn = (UiWidget*)widget; 158 btn->obj = obj; 159 btn->var = var; 160 btn->callback = args->onchange; 161 btn->callbackdata = args->onchangedata; 162 163 return widget; 164 } 165 166 UIWIDGET ui_togglebutton_create(UiObject *obj, UiToggleArgs *args) { 167 return create_togglebutton(obj, args, BS_AUTOCHECKBOX | BS_PUSHLIKE); 168 } 169 170 UIWIDGET ui_checkbox_create(UiObject *obj, UiToggleArgs *args) { 171 return create_togglebutton(obj, args, BS_AUTOCHECKBOX); 172 } 173 174 UIWIDGET ui_switch_create(UiObject *obj, UiToggleArgs *args) { 175 return create_togglebutton(obj, args, BS_AUTOCHECKBOX); 176 } 177 178 int64_t ui_togglebutton_get(UiInteger *i) { 179 UiWidget *btn = (UiWidget*)i->obj; 180 LRESULT state = SendMessage(btn->widget.hwnd, BM_GETCHECK, 0, 0); 181 i->value = state; 182 return state; 183 } 184 185 void ui_togglebutton_set(UiInteger *i, int64_t v) { 186 UiWidget *btn = (UiWidget*)i->obj; 187 WPARAM state = v ? BST_CHECKED : BST_UNCHECKED; 188 SendMessage(btn->widget.hwnd, BM_SETCHECK, state, 0); 189 i->value = v; 190 } 191 192 int ui_togglebutton_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { 193 if (uMsg != WM_COMMAND) { 194 return 0; 195 } 196 UiWidget *w = (UiWidget*)widget; 197 198 UiEvent e; 199 e.obj = w->obj; 200 e.document = e.obj->ctx->document; 201 e.window = e.obj->window; 202 e.eventdata = NULL; 203 e.eventdatatype = 0; 204 e.intval = SendMessage(w->widget.hwnd, BM_GETCHECK, 0, 0); 205 e.set = ui_get_setop(); 206 207 if (w->callback) { 208 w->callback(&e, w->callbackdata); 209 } 210 211 return 0; 212 } 213 214 215 UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args) { 216 HINSTANCE hInstance = GetModuleHandle(NULL); 217 UiContainerPrivate *container = ui_obj_container(obj); 218 HWND parent = ui_container_get_parent(container); 219 UiLayout layout = UI_ARGS2LAYOUT(args); 220 221 HWND hwnd = CreateWindow( 222 "BUTTON", 223 args->label, 224 WS_VISIBLE | WS_CHILD | BS_AUTORADIOBUTTON | WS_GROUP , 225 0, 0, 100, 30, 226 parent, 227 (HMENU)1, 228 hInstance, 229 NULL); 230 ui_win32_set_ui_font(hwnd); 231 232 W32Widget *widget = w32_widget_create(&radiobutton_widget_class, hwnd, sizeof(UiWidget)); 233 ui_container_add(container, widget, &layout); 234 UiWidget *btn = (UiWidget*)widget; 235 236 UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); 237 if (var) { 238 UiInteger *i = var->value; 239 // Use a CxList as binding object (i->obj) and add all radiobuttons to it 240 // The first radiobutton, which binds to this var, creates the CxList 241 CxList *group = NULL; 242 if (i->obj) { 243 group = i->obj; 244 } else { 245 group = cxArrayListCreate(obj->ctx->allocator, NULL, CX_STORE_POINTERS, 8); 246 i->obj = group; 247 } 248 249 cxListAdd(group, btn); 250 if (i->value == cxListSize(group)) { 251 // TODO: select 252 } 253 } 254 255 btn->obj = obj; 256 btn->var = var; 257 btn->callback = args->onchange; 258 btn->callbackdata = args->onchangedata; 259 260 return widget; 261 } 262 263 int ui_radiobutton_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { 264 if (uMsg != WM_COMMAND) { 265 return 0; 266 } 267 UiWidget *w = (UiWidget*)widget; 268 269 int checked = SendMessage(w->widget.hwnd, BM_GETCHECK, 0, 0); 270 if (!checked) { 271 return 0; // ignore uncheck events 272 } 273 274 int b = 0; 275 if (w->var) { 276 UiInteger *i = w->var->value; 277 CxList *group = i->obj; 278 // Find selected index and uncheck all radiobuttons in this group 279 // that are not this event widget 280 CxIterator iter = cxListIterator(group); 281 cx_foreach(UiWidget *, radiobutton, iter) { 282 if (radiobutton == w) { 283 i->value = iter.index+1; 284 b = i->value; 285 } else { 286 SendMessage(radiobutton->widget.hwnd, BM_SETCHECK, BST_UNCHECKED, 0); 287 } 288 } 289 } 290 291 UiEvent e; 292 e.obj = w->obj; 293 e.document = e.obj->ctx->document; 294 e.window = e.obj->window; 295 e.eventdata = NULL; 296 e.eventdatatype = 0; 297 e.intval = b; 298 e.set = ui_get_setop(); 299 300 if (w->callback) { 301 w->callback(&e, w->callbackdata); 302 } 303 304 return 0; 305 } 306 307 int64_t ui_radiobutton_get(UiInteger *i) { 308 return i->value; 309 } 310 311 void ui_radiobutton_set(UiInteger *i, int64_t v) { 312 CxList *group = i->obj; 313 CxIterator iter = cxListIterator(group); 314 cx_foreach(UiWidget *, radiobutton, iter) { 315 SendMessage(radiobutton->widget.hwnd, BM_SETCHECK, iter.index+1 == v ? BST_CHECKED : BST_UNCHECKED, 0); 316 } 317 i->value = v; 318 } 319