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 <stdio.h> 30 #include <stdlib.h> 31 32 #include <cx/array_list.h> 33 34 #include "list.h" 35 #include "container.h" 36 37 38 39 static W32WidgetClass listview_widget_class = { 40 .eventproc = ui_listview_eventproc, 41 .enable = w32_widget_default_enable, 42 .show = w32_widget_default_show, 43 .get_preferred_size = ui_listview_get_preferred_size, 44 .destroy = w32_widget_default_destroy 45 }; 46 47 static void* strmodel_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { 48 return col == 0 ? elm : NULL; 49 } 50 51 static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { 52 ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata; 53 return getvalue(elm, col); 54 } 55 56 static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { 57 return NULL; 58 } 59 60 /* 61 * Creates an UiListView widget object and initializes it from the UiListArgs 62 */ 63 static UiListView* create_listview_widget(UiObject *obj, W32WidgetClass *widget_class, HWND hwnd, UiListArgs *args, UiBool table) { 64 UiListView *listview = w32_widget_create(widget_class, hwnd, sizeof(UiListView)); 65 listview->widget.hwnd = hwnd; 66 listview->obj = obj; 67 listview->preferred_width = args->width ? args->width : 300; // 300: default width/height 68 listview->preferred_height = args->height ? args->height : 300; 69 listview->onactivate = args->onactivate; 70 listview->onactivatedata = args->onactivatedata; 71 listview->onselection = args->onselection; 72 listview->onselectiondata = args->onselectiondata; 73 listview->ondragstart = args->ondragstart; 74 listview->ondragstartdata = args->ondragstartdata; 75 listview->ondragcomplete = args->ondragcomplete; 76 listview->ondragcompletedata = args->ondragcompletedata; 77 listview->ondrop = args->ondrop; 78 listview->ondropdata = args->ondropdata; 79 listview->istable = table; 80 81 // convert ui_getvaluefunc into ui_getvaluefunc2 if necessary 82 ui_getvaluefunc2 getvalue = args->getvalue2; 83 void *getvaluedata = args->getvalue2data; 84 if(!getvalue) { 85 if(args->getvalue) { 86 getvalue = getvalue_wrapper; 87 getvaluedata = (void*)args->getvalue; 88 } else { 89 getvalue = table ? null_getvalue : strmodel_getvalue; 90 } 91 } 92 listview->getvalue = getvalue; 93 listview->getvaluedata = getvaluedata; 94 95 listview->var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); 96 97 return listview; 98 } 99 100 static UIWIDGET listview_create(UiObject *obj, UiListArgs *args, UiBool table) { 101 HINSTANCE hInstance = GetModuleHandle(NULL); 102 UiContainerPrivate *container = ui_obj_container(obj); 103 HWND parent = ui_container_get_parent(container); 104 UiLayout layout = UI_ARGS2LAYOUT(args); 105 106 HWND hwnd = CreateWindowEx( 107 WS_EX_CLIENTEDGE, 108 WC_LISTVIEW, 109 "", 110 WS_CHILD | WS_VISIBLE | LVS_REPORT, 111 0, 0, 100, 100, 112 parent, 113 (HMENU)1337, 114 hInstance, 115 NULL); 116 ui_win32_set_ui_font(hwnd); 117 ListView_SetExtendedListViewStyle( 118 hwnd, 119 LVS_EX_FULLROWSELECT //| LVS_EX_GRIDLINES 120 ); 121 122 UiListView *listview = create_listview_widget(obj, &listview_widget_class, hwnd, args, table); 123 ui_container_add(container, (W32Widget*)listview, &layout); 124 125 // init list model 126 // always initialize listview->model 127 int numcolumns = 0; 128 if (table) { 129 if (args->model) { 130 listview->model = ui_model_copy(obj->ctx, args->model); 131 numcolumns = listview->model->columns; 132 } else { 133 listview->model = ui_model_new(obj->ctx); 134 } 135 } else { 136 UiModel *model = ui_model_new(obj->ctx); 137 ui_model_add_column(model, UI_STRING, "Test", -1); 138 listview->model = model; 139 numcolumns = 1; 140 } 141 142 // create columns 143 UiModel *model = listview->model; 144 for (int i=0;i<numcolumns;i++) { 145 LVCOLUMN col; 146 UiModelType type = model->types[i]; 147 char *title = model->titles[i]; 148 size_t titlelen = title ? strlen(title) : 0; 149 int size = model->columnsize[i]; 150 switch (type) { 151 default: { 152 col.mask = LVCF_TEXT | LVCF_WIDTH; 153 col.pszText = title; 154 col.cx = size > 0 ? size : titlelen*10+5; 155 break; 156 } 157 case UI_ICON: { 158 break; // TODO 159 } 160 } 161 ListView_InsertColumn(hwnd, i, &col); 162 } 163 164 // bind the listview to the provided UiList 165 if (listview->var) { 166 UiList *list = listview->var->value; 167 list->obj = listview; 168 list->update = ui_listview_update; 169 list->getselection = ui_listview_getselection_impl; 170 list->setselection = ui_listview_setselection_impl; 171 172 ui_listview_update(list, -1); 173 } else if (!table && args->static_elements && args->static_nelm > 0) { 174 char **static_elements = args->static_elements; 175 size_t static_nelm = args->static_nelm; 176 LVITEM item; 177 item.mask = LVIF_TEXT; 178 item.iSubItem = 0; 179 for (int i=0;i<static_nelm;i++) { 180 item.iItem = i; 181 item.pszText = static_elements[i]; 182 ListView_InsertItem(hwnd, &item); 183 } 184 listview->getvalue = strmodel_getvalue; 185 listview->getvaluedata = NULL; 186 } 187 188 return (W32Widget*)listview; 189 } 190 191 static UiListSelection listview_get_selection2(HWND hwnd) { 192 UiListSelection sel = { 0, NULL }; 193 194 CX_ARRAY_DECLARE(int, indices); 195 cx_array_initialize(indices, 8); 196 197 int index = -1; 198 while ((index = ListView_GetNextItem(hwnd, index, LVNI_SELECTED)) != -1) { 199 cx_array_simple_add(indices, index); 200 } 201 202 if (indices_size > 0) { 203 sel.rows = indices; 204 sel.count = indices_size; 205 } 206 207 return sel; 208 } 209 210 static UiListSelection listview_get_selection(UiListView *listview) { 211 HWND hwnd = listview->widget.hwnd; 212 return listview_get_selection2(hwnd); 213 } 214 215 // listview class event proc 216 int ui_listview_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { 217 UiListView *listview = (UiListView*)widget; 218 switch (uMsg) { 219 case WM_NOTIFY: { 220 LPNMHDR hdr = (LPNMHDR)lParam; 221 switch (hdr->code) { 222 case LVN_ITEMCHANGED: { 223 LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam; 224 int row = lv->iItem; 225 if ((lv->uChanged & LVIF_STATE) && (lv->uNewState & LVIS_SELECTED) && listview->onselection) { 226 UiListSelection sel = listview_get_selection(listview); 227 228 UiEvent event; 229 event.obj = listview->obj; 230 event.window = listview->obj->window; 231 event.document = listview->obj->ctx->document; 232 event.eventdata = &sel; 233 event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; 234 event.intval = row; 235 event.set = ui_get_setop(); 236 listview->onselection(&event, listview->onselectiondata); 237 238 ui_listselection_free(sel); 239 } 240 break; 241 } 242 case LVN_ITEMACTIVATE: { 243 LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam; 244 int row = lv->iItem; 245 if (listview->onactivate) { 246 UiEvent event; 247 event.obj = listview->obj; 248 event.window = listview->obj->window; 249 event.document = listview->obj->ctx->document; 250 event.eventdata = NULL; 251 event.eventdatatype = UI_EVENT_DATA_LIST_ELM; 252 event.intval = row; 253 event.set = ui_get_setop(); 254 255 if (listview->var) { 256 UiList *list = listview->var->value; 257 event.eventdata = list->get(list, row); 258 event.eventdatatype = UI_EVENT_DATA_LIST_ELM; 259 } 260 261 listview->onactivate(&event, listview->onactivatedata); 262 } 263 break; 264 } 265 } 266 break; 267 } 268 } 269 270 return 0; 271 } 272 273 W32Size ui_listview_get_preferred_size(W32Widget *widget) { 274 UiListView *listview = (UiListView*)widget; 275 W32Size size; 276 size.width = listview->preferred_width; 277 size.height = listview->preferred_height; 278 return size; 279 } 280 281 /* 282 * Creates and inserts an LVITEM 283 * 284 * list: An UiList bound to an UiListView 285 * row: row index 286 * elm: list element (same as list->get(list, row)) 287 */ 288 static void insert_item(UiList *list, int row, void *elm) { 289 UiListView *listview = (UiListView*)list->obj; 290 HWND hwnd = listview->widget.hwnd; 291 UiModel *model = listview->model; 292 293 LVITEM item; 294 item.mask = LVIF_TEXT; 295 item.iItem = row; 296 item.iSubItem = 0; 297 int idx = -1; 298 for (int col=0;col<model->columns;col++) { 299 UiBool freeResult = FALSE; 300 // convert the list element to a value, that can be displayed in the list view 301 // TODO: handle all model types 302 char *str = listview->getvalue(list, elm, row, col, listview->getvaluedata, &freeResult); 303 if (col == 0) { 304 item.pszText = str; 305 idx = ListView_InsertItem(hwnd, &item); 306 } else { 307 ListView_SetItemText(hwnd, idx, col, str); 308 } 309 310 if (freeResult) { 311 free(str); 312 } 313 } 314 } 315 316 /* 317 * UiList->update function 318 * 319 * Updates one or all rows 320 * row: list index or -1 for updating all rows 321 */ 322 void ui_listview_update(UiList *list, int row) { 323 UiListView *listview = (UiListView*)list->obj; 324 HWND hwnd = listview->widget.hwnd; 325 UiModel *model = listview->model; 326 if (row < 0) { 327 ListView_DeleteAllItems(hwnd); 328 void *elm = list->first(list); 329 int row = 0; 330 while (elm) { 331 insert_item(list, row, elm); 332 elm = list->next(list); 333 row++; 334 } 335 } else { 336 ListView_DeleteItem(hwnd, row); 337 void *elm = list->get(list, row); 338 insert_item(list, row, elm); 339 } 340 341 // re-adjust all columns 342 for (int i=0;i<model->columns;i++) { 343 ListView_SetColumnWidth(hwnd, i, LVSCW_AUTOSIZE); 344 } 345 } 346 347 UiListSelection ui_listview_getselection_impl(UiList *list) { 348 UiListView *listview = (UiListView*)list->obj; 349 return listview_get_selection(listview); 350 } 351 352 void ui_listview_setselection_impl(UiList *list, UiListSelection selection) { 353 354 } 355 356 // public API 357 UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { 358 return listview_create(obj, args, FALSE); 359 } 360 361 // public API 362 UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { 363 return listview_create(obj, args, TRUE); 364 } 365 366 void ui_listview_select(UIWIDGET listview, int index) { 367 368 } 369 370 int ui_listview_selection(UIWIDGET listview) { 371 W32Widget *w = (W32Widget*)listview; 372 UiListSelection sel = listview_get_selection2(w->hwnd); 373 int index = -1; 374 if (sel.count > 0) { 375 index = sel.rows[0]; 376 } 377 free(sel.rows); 378 return index; 379 } 380 381 /* ------------------------------------ DropDown ------------------------------------*/ 382 383 static W32WidgetClass dropdown_widget_class = { 384 .eventproc = ui_dropdown_eventproc, 385 .enable = w32_widget_default_enable, 386 .show = w32_widget_default_show, 387 .get_preferred_size = ui_dropdown_get_preferred_size, 388 .destroy = w32_widget_default_destroy 389 }; 390 391 UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { 392 HINSTANCE hInstance = GetModuleHandle(NULL); 393 UiContainerPrivate *container = ui_obj_container(obj); 394 HWND parent = ui_container_get_parent(container); 395 UiLayout layout = UI_ARGS2LAYOUT(args); 396 397 HWND hwnd = CreateWindowEx( 398 WS_EX_CLIENTEDGE, 399 WC_COMBOBOX, 400 "", 401 WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST, 402 0, 0, 100, 100, 403 parent, 404 (HMENU)1337, 405 hInstance, 406 NULL); 407 ui_win32_set_ui_font(hwnd); 408 409 UiListView *dropdown = create_listview_widget(obj, &dropdown_widget_class, hwnd, args, FALSE); 410 ui_container_add(container, (W32Widget*)dropdown, &layout); 411 412 // bind the dropdown to the provided UiList 413 if (dropdown->var) { 414 UiList *list = dropdown->var->value; 415 list->obj = dropdown; 416 list->update = ui_dropdown_update; 417 list->getselection = ui_dropdown_getselection_impl; 418 list->setselection = ui_dropdown_setselection_impl; 419 420 ui_dropdown_update(list, -1); 421 } else if (args->static_elements && args->static_nelm > 0) { 422 char **static_elements = args->static_elements; 423 size_t static_nelm = args->static_nelm; 424 for (int i=0;i<static_nelm;i++) { 425 SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)static_elements[i]); 426 } 427 dropdown->getvalue = strmodel_getvalue; 428 dropdown->getvaluedata = NULL; 429 } 430 431 return (W32Widget*)dropdown; 432 } 433 434 int ui_dropdown_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { 435 return 0; 436 } 437 438 W32Size ui_dropdown_get_preferred_size(W32Widget *widget) { 439 W32Size size; 440 size.width = 200; 441 size.height = 30; 442 return size; 443 } 444 445 static void dropdown_insert_item(UiList *list, int row, void *elm) { 446 UiListView *listview = (UiListView*)list->obj; 447 HWND hwnd = listview->widget.hwnd; 448 449 UiBool freeResult = FALSE; 450 char *str = listview->getvalue(list, elm, row, 0, listview->getvaluedata, &freeResult); 451 SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)str); 452 453 if (freeResult) { 454 free(str); 455 } 456 } 457 458 void ui_dropdown_update(UiList *list, int row) { 459 UiListView *listview = (UiListView*)list->obj; 460 HWND hwnd = listview->widget.hwnd; 461 if (row < 0) { 462 SendMessage(hwnd, CB_RESETCONTENT, 0, 0); 463 464 void *elm = list->first(list); 465 int row = 0; 466 while (elm) { 467 dropdown_insert_item(list, row, elm); 468 elm = list->next(list); 469 row++; 470 } 471 } else { 472 SendMessage(hwnd, CB_DELETESTRING, row, 0); 473 void *elm = list->get(list, row); 474 dropdown_insert_item(list, row, elm); 475 } 476 } 477 478 UiListSelection ui_dropdown_getselection_impl(UiList *list) { 479 UiListSelection sel = { 0, NULL }; 480 UiListView *listview = (UiListView*)list->obj; 481 int index = (int)SendMessage(listview->widget.hwnd, CB_GETCURSEL, 0, 0); 482 if (index >= 0) { 483 sel.rows = malloc(sizeof(int)); 484 sel.rows[0] = index; 485 sel.count = 1; 486 } 487 return sel; 488 } 489 490 void ui_dropdown_setselection_impl(UiList *list, UiListSelection selection) { 491 UiListView *listview = (UiListView*)list->obj; 492 SendMessage(listview->widget.hwnd, CB_SETCURSEL, 0, 0); 493 } 494 495 void ui_dropdown_select(UIWIDGET dropdown, int index) { 496 SendMessage(dropdown->hwnd, CB_SETCURSEL, 0, 0); 497 } 498 499 int ui_dropdown_selection(UIWIDGET dropdown) { 500 return SendMessage(dropdown->hwnd, CB_GETCURSEL, 0, 0); 501 } 502