--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/win32/list.c Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,501 @@ +/* +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 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: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <cx/array_list.h> + +#include "list.h" +#include "container.h" + + + +static W32WidgetClass listview_widget_class = { + .eventproc = ui_listview_eventproc, + .enable = w32_widget_default_enable, + .show = w32_widget_default_show, + .get_preferred_size = ui_listview_get_preferred_size, + .destroy = w32_widget_default_destroy +}; + +static void* strmodel_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + return col == 0 ? elm : NULL; +} + +static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata; + return getvalue(elm, col); +} + +static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + return NULL; +} + +/* + * Creates an UiListView widget object and initializes it from the UiListArgs + */ +static UiListView* create_listview_widget(UiObject *obj, W32WidgetClass *widget_class, HWND hwnd, UiListArgs *args, UiBool table) { + UiListView *listview = w32_widget_create(widget_class, hwnd, sizeof(UiListView)); + listview->widget.hwnd = hwnd; + listview->obj = obj; + listview->preferred_width = args->width ? args->width : 300; // 300: default width/height + listview->preferred_height = args->height ? args->height : 300; + listview->onactivate = args->onactivate; + listview->onactivatedata = args->onactivatedata; + listview->onselection = args->onselection; + listview->onselectiondata = args->onselectiondata; + listview->ondragstart = args->ondragstart; + listview->ondragstartdata = args->ondragstartdata; + listview->ondragcomplete = args->ondragcomplete; + listview->ondragcompletedata = args->ondragcompletedata; + listview->ondrop = args->ondrop; + listview->ondropdata = args->ondropdata; + listview->istable = table; + + // convert ui_getvaluefunc into ui_getvaluefunc2 if necessary + ui_getvaluefunc2 getvalue = args->getvalue2; + void *getvaluedata = args->getvalue2data; + if(!getvalue) { + if(args->getvalue) { + getvalue = getvalue_wrapper; + getvaluedata = (void*)args->getvalue; + } else { + getvalue = table ? null_getvalue : strmodel_getvalue; + } + } + listview->getvalue = getvalue; + listview->getvaluedata = getvaluedata; + + listview->var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + return listview; +} + +static UIWIDGET listview_create(UiObject *obj, UiListArgs *args, UiBool table) { + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + HWND hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, + WC_LISTVIEW, + "", + WS_CHILD | WS_VISIBLE | LVS_REPORT, + 0, 0, 100, 100, + parent, + (HMENU)1337, + hInstance, + NULL); + ui_win32_set_ui_font(hwnd); + ListView_SetExtendedListViewStyle( + hwnd, + LVS_EX_FULLROWSELECT //| LVS_EX_GRIDLINES + ); + + UiListView *listview = create_listview_widget(obj, &listview_widget_class, hwnd, args, table); + ui_container_add(container, (W32Widget*)listview, &layout); + + // init list model + // always initialize listview->model + int numcolumns = 0; + if (table) { + if (args->model) { + listview->model = ui_model_copy(obj->ctx, args->model); + numcolumns = listview->model->columns; + } else { + listview->model = ui_model_new(obj->ctx); + } + } else { + UiModel *model = ui_model_new(obj->ctx); + ui_model_add_column(model, UI_STRING, "Test", -1); + listview->model = model; + numcolumns = 1; + } + + // create columns + UiModel *model = listview->model; + for (int i=0;i<numcolumns;i++) { + LVCOLUMN col; + UiModelType type = model->types[i]; + char *title = model->titles[i]; + size_t titlelen = title ? strlen(title) : 0; + int size = model->columnsize[i]; + switch (type) { + default: { + col.mask = LVCF_TEXT | LVCF_WIDTH; + col.pszText = title; + col.cx = size > 0 ? size : titlelen*10+5; + break; + } + case UI_ICON: { + break; // TODO + } + } + ListView_InsertColumn(hwnd, i, &col); + } + + // bind the listview to the provided UiList + if (listview->var) { + UiList *list = listview->var->value; + list->obj = listview; + list->update = ui_listview_update; + list->getselection = ui_listview_getselection_impl; + list->setselection = ui_listview_setselection_impl; + + ui_listview_update(list, -1); + } else if (!table && args->static_elements && args->static_nelm > 0) { + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; + LVITEM item; + item.mask = LVIF_TEXT; + item.iSubItem = 0; + for (int i=0;i<static_nelm;i++) { + item.iItem = i; + item.pszText = static_elements[i]; + ListView_InsertItem(hwnd, &item); + } + listview->getvalue = strmodel_getvalue; + listview->getvaluedata = NULL; + } + + return (W32Widget*)listview; +} + +static UiListSelection listview_get_selection2(HWND hwnd) { + UiListSelection sel = { 0, NULL }; + + CX_ARRAY_DECLARE(int, indices); + cx_array_initialize(indices, 8); + + int index = -1; + while ((index = ListView_GetNextItem(hwnd, index, LVNI_SELECTED)) != -1) { + cx_array_simple_add(indices, index); + } + + if (indices_size > 0) { + sel.rows = indices; + sel.count = indices_size; + } + + return sel; +} + +static UiListSelection listview_get_selection(UiListView *listview) { + HWND hwnd = listview->widget.hwnd; + return listview_get_selection2(hwnd); +} + +// listview class event proc +int ui_listview_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + UiListView *listview = (UiListView*)widget; + switch (uMsg) { + case WM_NOTIFY: { + LPNMHDR hdr = (LPNMHDR)lParam; + switch (hdr->code) { + case LVN_ITEMCHANGED: { + LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam; + int row = lv->iItem; + if ((lv->uChanged & LVIF_STATE) && (lv->uNewState & LVIS_SELECTED) && listview->onselection) { + UiListSelection sel = listview_get_selection(listview); + + UiEvent event; + event.obj = listview->obj; + event.window = listview->obj->window; + event.document = listview->obj->ctx->document; + event.eventdata = &sel; + event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; + event.intval = row; + event.set = ui_get_setop(); + listview->onselection(&event, listview->onselectiondata); + + ui_listselection_free(sel); + } + break; + } + case LVN_ITEMACTIVATE: { + LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam; + int row = lv->iItem; + if (listview->onactivate) { + UiEvent event; + event.obj = listview->obj; + event.window = listview->obj->window; + event.document = listview->obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = UI_EVENT_DATA_LIST_ELM; + event.intval = row; + event.set = ui_get_setop(); + + if (listview->var) { + UiList *list = listview->var->value; + event.eventdata = list->get(list, row); + event.eventdatatype = UI_EVENT_DATA_LIST_ELM; + } + + listview->onactivate(&event, listview->onactivatedata); + } + break; + } + } + break; + } + } + + return 0; +} + +W32Size ui_listview_get_preferred_size(W32Widget *widget) { + UiListView *listview = (UiListView*)widget; + W32Size size; + size.width = listview->preferred_width; + size.height = listview->preferred_height; + return size; +} + +/* + * Creates and inserts an LVITEM + * + * list: An UiList bound to an UiListView + * row: row index + * elm: list element (same as list->get(list, row)) + */ +static void insert_item(UiList *list, int row, void *elm) { + UiListView *listview = (UiListView*)list->obj; + HWND hwnd = listview->widget.hwnd; + UiModel *model = listview->model; + + LVITEM item; + item.mask = LVIF_TEXT; + item.iItem = row; + item.iSubItem = 0; + int idx = -1; + for (int col=0;col<model->columns;col++) { + UiBool freeResult = FALSE; + // convert the list element to a value, that can be displayed in the list view + // TODO: handle all model types + char *str = listview->getvalue(list, elm, row, col, listview->getvaluedata, &freeResult); + if (col == 0) { + item.pszText = str; + idx = ListView_InsertItem(hwnd, &item); + } else { + ListView_SetItemText(hwnd, idx, col, str); + } + + if (freeResult) { + free(str); + } + } +} + +/* + * UiList->update function + * + * Updates one or all rows + * row: list index or -1 for updating all rows + */ +void ui_listview_update(UiList *list, int row) { + UiListView *listview = (UiListView*)list->obj; + HWND hwnd = listview->widget.hwnd; + UiModel *model = listview->model; + if (row < 0) { + ListView_DeleteAllItems(hwnd); + void *elm = list->first(list); + int row = 0; + while (elm) { + insert_item(list, row, elm); + elm = list->next(list); + row++; + } + } else { + ListView_DeleteItem(hwnd, row); + void *elm = list->get(list, row); + insert_item(list, row, elm); + } + + // re-adjust all columns + for (int i=0;i<model->columns;i++) { + ListView_SetColumnWidth(hwnd, i, LVSCW_AUTOSIZE); + } +} + +UiListSelection ui_listview_getselection_impl(UiList *list) { + UiListView *listview = (UiListView*)list->obj; + return listview_get_selection(listview); +} + +void ui_listview_setselection_impl(UiList *list, UiListSelection selection) { + +} + +// public API +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { + return listview_create(obj, args, FALSE); +} + +// public API +UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { + return listview_create(obj, args, TRUE); +} + +void ui_listview_select(UIWIDGET listview, int index) { + +} + +int ui_listview_selection(UIWIDGET listview) { + W32Widget *w = (W32Widget*)listview; + UiListSelection sel = listview_get_selection2(w->hwnd); + int index = -1; + if (sel.count > 0) { + index = sel.rows[0]; + } + free(sel.rows); + return index; +} + +/* ------------------------------------ DropDown ------------------------------------*/ + +static W32WidgetClass dropdown_widget_class = { + .eventproc = ui_dropdown_eventproc, + .enable = w32_widget_default_enable, + .show = w32_widget_default_show, + .get_preferred_size = ui_dropdown_get_preferred_size, + .destroy = w32_widget_default_destroy +}; + +UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + HWND hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, + WC_COMBOBOX, + "", + WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST, + 0, 0, 100, 100, + parent, + (HMENU)1337, + hInstance, + NULL); + ui_win32_set_ui_font(hwnd); + + UiListView *dropdown = create_listview_widget(obj, &dropdown_widget_class, hwnd, args, FALSE); + ui_container_add(container, (W32Widget*)dropdown, &layout); + + // bind the dropdown to the provided UiList + if (dropdown->var) { + UiList *list = dropdown->var->value; + list->obj = dropdown; + list->update = ui_dropdown_update; + list->getselection = ui_dropdown_getselection_impl; + list->setselection = ui_dropdown_setselection_impl; + + ui_dropdown_update(list, -1); + } else if (args->static_elements && args->static_nelm > 0) { + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; + for (int i=0;i<static_nelm;i++) { + SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)static_elements[i]); + } + dropdown->getvalue = strmodel_getvalue; + dropdown->getvaluedata = NULL; + } + + return (W32Widget*)dropdown; +} + +int ui_dropdown_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + return 0; +} + +W32Size ui_dropdown_get_preferred_size(W32Widget *widget) { + W32Size size; + size.width = 200; + size.height = 30; + return size; +} + +static void dropdown_insert_item(UiList *list, int row, void *elm) { + UiListView *listview = (UiListView*)list->obj; + HWND hwnd = listview->widget.hwnd; + + UiBool freeResult = FALSE; + char *str = listview->getvalue(list, elm, row, 0, listview->getvaluedata, &freeResult); + SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)str); + + if (freeResult) { + free(str); + } +} + +void ui_dropdown_update(UiList *list, int row) { + UiListView *listview = (UiListView*)list->obj; + HWND hwnd = listview->widget.hwnd; + if (row < 0) { + SendMessage(hwnd, CB_RESETCONTENT, 0, 0); + + void *elm = list->first(list); + int row = 0; + while (elm) { + dropdown_insert_item(list, row, elm); + elm = list->next(list); + row++; + } + } else { + SendMessage(hwnd, CB_DELETESTRING, row, 0); + void *elm = list->get(list, row); + dropdown_insert_item(list, row, elm); + } +} + +UiListSelection ui_dropdown_getselection_impl(UiList *list) { + UiListSelection sel = { 0, NULL }; + UiListView *listview = (UiListView*)list->obj; + int index = (int)SendMessage(listview->widget.hwnd, CB_GETCURSEL, 0, 0); + if (index >= 0) { + sel.rows = malloc(sizeof(int)); + sel.rows[0] = index; + sel.count = 1; + } + return sel; +} + +void ui_dropdown_setselection_impl(UiList *list, UiListSelection selection) { + UiListView *listview = (UiListView*)list->obj; + SendMessage(listview->widget.hwnd, CB_SETCURSEL, 0, 0); +} + +void ui_dropdown_select(UIWIDGET dropdown, int index) { + SendMessage(dropdown->hwnd, CB_SETCURSEL, 0, 0); +} + +int ui_dropdown_selection(UIWIDGET dropdown) { + return SendMessage(dropdown->hwnd, CB_GETCURSEL, 0, 0); +}