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; 170 list->setselection = ui_listview_setselection; 171 172 ui_listview_update(list, -1); 173 } 174 175 return (W32Widget*)listview; 176 } 177 178 static UiListSelection listview_get_selection(UiListView *listview) { 179 UiListSelection sel = { 0, NULL }; 180 HWND hwnd = listview->widget.hwnd; 181 182 CX_ARRAY_DECLARE(int, indices); 183 cx_array_initialize(indices, 8); 184 185 int index = -1; 186 while ((index = ListView_GetNextItem(hwnd, index, LVNI_SELECTED)) != -1) { 187 cx_array_simple_add(indices, index); 188 } 189 190 if (indices_size > 0) { 191 sel.rows = indices; 192 sel.count = indices_size; 193 } 194 195 return sel; 196 } 197 198 // listview class event proc 199 int ui_listview_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { 200 UiListView *listview = (UiListView*)widget; 201 switch (uMsg) { 202 case WM_NOTIFY: { 203 LPNMHDR hdr = (LPNMHDR)lParam; 204 switch (hdr->code) { 205 case LVN_ITEMCHANGED: { 206 LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam; 207 int row = lv->iItem; 208 if ((lv->uChanged & LVIF_STATE) && (lv->uNewState & LVIS_SELECTED) && listview->onselection) { 209 UiListSelection sel = listview_get_selection(listview); 210 211 UiEvent event; 212 event.obj = listview->obj; 213 event.window = listview->obj->window; 214 event.document = listview->obj->ctx->document; 215 event.eventdata = &sel; 216 event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; 217 event.intval = row; 218 event.set = ui_get_setop(); 219 listview->onselection(&event, listview->onselectiondata); 220 221 ui_listselection_free(sel); 222 } 223 break; 224 } 225 case LVN_ITEMACTIVATE: { 226 LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam; 227 int row = lv->iItem; 228 if (listview->onactivate) { 229 UiEvent event; 230 event.obj = listview->obj; 231 event.window = listview->obj->window; 232 event.document = listview->obj->ctx->document; 233 event.eventdata = NULL; 234 event.eventdatatype = UI_EVENT_DATA_LIST_ELM; 235 event.intval = row; 236 event.set = ui_get_setop(); 237 238 if (listview->var) { 239 UiList *list = listview->var->value; 240 event.eventdata = list->get(list, row); 241 event.eventdatatype = UI_EVENT_DATA_LIST_ELM; 242 } 243 244 listview->onactivate(&event, listview->onactivatedata); 245 } 246 break; 247 } 248 } 249 break; 250 } 251 } 252 253 return 0; 254 } 255 256 W32Size ui_listview_get_preferred_size(W32Widget *widget) { 257 UiListView *listview = (UiListView*)widget; 258 W32Size size; 259 size.width = listview->preferred_width; 260 size.height = listview->preferred_height; 261 return size; 262 } 263 264 /* 265 * Creates and inserts an LVITEM 266 * 267 * list: An UiList bound to an UiListView 268 * row: row index 269 * elm: list element (same as list->get(list, row)) 270 */ 271 static void insert_item(UiList *list, int row, void *elm) { 272 UiListView *listview = (UiListView*)list->obj; 273 HWND hwnd = listview->widget.hwnd; 274 UiModel *model = listview->model; 275 276 LVITEM item; 277 item.mask = LVIF_TEXT; 278 item.iItem = row; 279 item.iSubItem = 0; 280 int idx = -1; 281 for (int col=0;col<model->columns;col++) { 282 UiBool freeResult = FALSE; 283 // convert the list element to a value, that can be displayed in the list view 284 // TODO: handle all model types 285 char *str = listview->getvalue(list, elm, row, col, listview->getvaluedata, &freeResult); 286 if (col == 0) { 287 item.pszText = str; 288 idx = ListView_InsertItem(hwnd, &item); 289 } else { 290 ListView_SetItemText(hwnd, idx, col, str); 291 } 292 293 if (freeResult) { 294 free(str); 295 } 296 } 297 } 298 299 /* 300 * UiList->update function 301 * 302 * Updates one or all rows 303 * row: list index or -1 for updating all rows 304 */ 305 void ui_listview_update(UiList *list, int row) { 306 UiListView *listview = (UiListView*)list->obj; 307 HWND hwnd = listview->widget.hwnd; 308 UiModel *model = listview->model; 309 if (row < 0) { 310 ListView_DeleteAllItems(hwnd); 311 void *elm = list->first(list); 312 int row = 0; 313 while (elm) { 314 insert_item(list, row, elm); 315 elm = list->next(list); 316 row++; 317 } 318 } else { 319 ListView_DeleteItem(hwnd, row); 320 void *elm = list->get(list, row); 321 insert_item(list, row, elm); 322 } 323 324 // re-adjust all columns 325 for (int i=0;i<model->columns;i++) { 326 ListView_SetColumnWidth(hwnd, i, LVSCW_AUTOSIZE); 327 } 328 } 329 330 UiListSelection ui_listview_getselection(UiList *list) { 331 UiListView *listview = (UiListView*)list->obj; 332 return listview_get_selection(listview); 333 } 334 335 void ui_listview_setselection(UiList *list, UiListSelection selection) { 336 337 } 338 339 // public API 340 UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { 341 return listview_create(obj, args, FALSE); 342 } 343 344 // public API 345 UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { 346 return listview_create(obj, args, TRUE); 347 } 348 349 350 /* ------------------------------------ DropDown ------------------------------------*/ 351 352 static W32WidgetClass dropdown_widget_class = { 353 .eventproc = ui_dropdown_eventproc, 354 .enable = w32_widget_default_enable, 355 .show = w32_widget_default_show, 356 .get_preferred_size = ui_dropdown_get_preferred_size, 357 .destroy = w32_widget_default_destroy 358 }; 359 360 UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { 361 HINSTANCE hInstance = GetModuleHandle(NULL); 362 UiContainerPrivate *container = ui_obj_container(obj); 363 HWND parent = ui_container_get_parent(container); 364 UiLayout layout = UI_ARGS2LAYOUT(args); 365 366 HWND hwnd = CreateWindowEx( 367 WS_EX_CLIENTEDGE, 368 WC_COMBOBOX, 369 "", 370 WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST, 371 0, 0, 100, 100, 372 parent, 373 (HMENU)1337, 374 hInstance, 375 NULL); 376 ui_win32_set_ui_font(hwnd); 377 378 UiListView *dropdown = create_listview_widget(obj, &dropdown_widget_class, hwnd, args, FALSE); 379 ui_container_add(container, (W32Widget*)dropdown, &layout); 380 381 // bind the dropdown to the provided UiList 382 if (dropdown->var) { 383 UiList *list = dropdown->var->value; 384 list->obj = dropdown; 385 list->update = ui_dropdown_update; 386 list->getselection = ui_dropdown_getselection; 387 list->setselection = ui_dropdown_setselection; 388 389 ui_dropdown_update(list, -1); 390 } 391 392 393 return (W32Widget*)dropdown; 394 } 395 396 int ui_dropdown_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { 397 return 0; 398 } 399 400 W32Size ui_dropdown_get_preferred_size(W32Widget *widget) { 401 W32Size size; 402 size.width = 200; 403 size.height = 30; 404 return size; 405 } 406 407 static void dropdown_insert_item(UiList *list, int row, void *elm) { 408 UiListView *listview = (UiListView*)list->obj; 409 HWND hwnd = listview->widget.hwnd; 410 411 UiBool freeResult = FALSE; 412 char *str = listview->getvalue(list, elm, row, 0, listview->getvaluedata, &freeResult); 413 SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)str); 414 415 if (freeResult) { 416 free(str); 417 } 418 } 419 420 void ui_dropdown_update(UiList *list, int row) { 421 UiListView *listview = (UiListView*)list->obj; 422 HWND hwnd = listview->widget.hwnd; 423 if (row < 0) { 424 SendMessage(hwnd, CB_RESETCONTENT, 0, 0); 425 426 void *elm = list->first(list); 427 int row = 0; 428 while (elm) { 429 dropdown_insert_item(list, row, elm); 430 elm = list->next(list); 431 row++; 432 } 433 } else { 434 SendMessage(hwnd, CB_DELETESTRING, row, 0); 435 void *elm = list->get(list, row); 436 dropdown_insert_item(list, row, elm); 437 } 438 } 439 440 UiListSelection ui_dropdown_getselection(UiList *list) { 441 UiListSelection sel = { 0, NULL }; 442 UiListView *listview = (UiListView*)list->obj; 443 int index = (int)SendMessage(listview->widget.hwnd, CB_GETCURSEL, 0, 0); 444 if (index >= 0) { 445 sel.rows = malloc(sizeof(int)); 446 sel.rows[0] = index; 447 sel.count = 1; 448 } 449 return sel; 450 } 451 452 void ui_dropdown_setselection(UiList *list, UiListSelection selection) { 453 UiListView *listview = (UiListView*)list->obj; 454 SendMessage(listview->widget.hwnd, CB_SETCURSEL, 0, 0); 455 } 456