ui/win32/list.c

Sun, 07 Dec 2025 15:45:30 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 07 Dec 2025 15:45:30 +0100
changeset 969
7385c26d998d
parent 938
be4c88ded783
child 980
39cb60b6a81b
permissions
-rw-r--r--

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);
}

mercurial