Sun, 07 Dec 2025 15:45:30 +0100
rename combobox to dropdown
/* * 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(obj->ctx, 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; list->setselection = ui_listview_setselection; ui_listview_update(list, -1); } return (W32Widget*)listview; } static UiListSelection listview_get_selection(UiListView *listview) { UiListSelection sel = { 0, NULL }; HWND hwnd = listview->widget.hwnd; 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; } // 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(UiList *list) { UiListView *listview = (UiListView*)list->obj; return listview_get_selection(listview); } void ui_listview_setselection(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); } /* ------------------------------------ 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; list->setselection = ui_dropdown_setselection; ui_dropdown_update(list, -1); } 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(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(UiList *list, UiListSelection selection) { UiListView *listview = (UiListView*)list->obj; SendMessage(listview->widget.hwnd, CB_SETCURSEL, 0, 0); }