ui/win32/list.c

changeset 115
e57ca2747782
--- /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);
+}

mercurial