ui/cocoa/list.m

Sun, 24 Aug 2025 15:24:16 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 24 Aug 2025 15:24:16 +0200
changeset 109
c3dfcb8f0be7
child 112
c3f2f16fa4b8
permissions
-rw-r--r--

update toolkit, remove getvalue func from model to table/listview args

/*
 * 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.
 */

#import "list.h"
#import "ListDelegate.h"
#import <objc/runtime.h>

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* str_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
    return elm;
}

/* --------------------------- ListView --------------------------- */

/*
 * adds a NSTableViewDelegate that handles all events and calls
 * callbacks specified in the UiListArgs
 */
static void add_listdelegate(UiObject *obj, NSTableView *tableview, UiListArgs *args) {
    ListDelegate *delegate = [[ListDelegate alloc] init:tableview obj:obj];
    delegate.onactivate = args->onactivate;
    delegate.onactivatedata = args->onactivatedata;
    delegate.onselection = args->onselection;
    delegate.onselectiondata = args->onselectiondata;
    tableview.delegate = delegate;
    objc_setAssociatedObject(tableview, "ui_listdelegate", delegate, OBJC_ASSOCIATION_RETAIN);
    tableview.doubleAction = @selector(activateEvent:);
    tableview.target = delegate;
}

static void bind_list_to_tableview(UiList *list, NSTableView *tableview) {
    list->obj = (__bridge void*)tableview;
    list->update = ui_tableview_update;
    list->getselection = ui_tableview_getselection;
    list->setselection = ui_tableview_setselection;
}

UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) {
    NSScrollView *scrollview = [[NSScrollView alloc] init];
    
    NSTableView *tableview = [[NSTableView alloc] init];
    tableview.autoresizingMask = NSViewWidthSizable;
    tableview.headerView = nil;
    
    if(args->multiselection) {
        tableview.allowsMultipleSelection = YES;
    }
    
    scrollview.documentView = tableview;
    
    UiLayout layout = UI_INIT_LAYOUT(args);
    ui_container_add(obj, scrollview, &layout);
    
    add_listdelegate(obj, tableview, args);
    
    UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
    if(var) {
        UiList *list = var->value;
        bind_list_to_tableview(list, tableview);
        
        ui_getvaluefunc2 getvalue = args->getvalue2;
        void *getvaluedata = args->getvalue2data;
        if(!getvalue) {
            if(args->getvalue) {
                getvalue = getvalue_wrapper;
                getvaluedata = (void*)args->getvalue;
            } else {
                getvalue = str_getvalue; // by default list values are interpreted as strings
            }
        }
        
        NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"column"];
        [tableview addTableColumn:column];
        
        ListDataSource *dataSource = [[ListDataSource alloc] init:tableview.tableColumns var:var getvalue:getvalue getvaluedata:getvaluedata];
        
        tableview.dataSource = dataSource;
        [tableview reloadData];
        
        objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN);
    }

    return (__bridge void*)scrollview;
}

/* --------------------------- TableView --------------------------- */

UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) {
    NSScrollView *scrollview = [[NSScrollView alloc] init];
    
    NSTableView *tableview = [[NSTableView alloc] init];
    tableview.autoresizingMask = NSViewWidthSizable;
    tableview.columnAutoresizingStyle = NSTableViewSequentialColumnAutoresizingStyle;
    
    if(args->multiselection) {
        tableview.allowsMultipleSelection = YES;
    }
    
    UiLayout layout = UI_INIT_LAYOUT(args);
    ui_container_add(obj, scrollview, &layout);
    
    add_listdelegate(obj, tableview, args);
    
    // convert model
    NSMutableArray<NSTableColumn*> *cols = [[NSMutableArray alloc] init];
    UiModel *model = args->model;
    if(model) {
        for(int i=0;i<model->columns;i++) {
            char *title = model->titles[i];
            UiModelType type = model->types[i];
            int width = model->columnsize[i];
            NSString *identifier = [[NSString alloc] initWithUTF8String:title];
            NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:identifier];
            column.title = identifier;
            column.resizingMask = NSTableColumnUserResizingMask;
            if(width > 0) {
                column.width = width;
            } else if(width < 0) {
                column.resizingMask = NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask;
            }
            if(type >= UI_ICON) {
                // TODO
            }
            [tableview addTableColumn:column];
            [cols addObject:column];
        }
    }
    
    UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
    if(var) {
        UiList *list = var->value;
        bind_list_to_tableview(list, tableview);
        
        ui_getvaluefunc2 getvalue = args->getvalue2;
        void *getvaluedata = args->getvalue2data;
        if(!getvalue) {
            if(args->getvalue) {
                getvalue = getvalue_wrapper;
                getvaluedata = (void*)args->getvalue;
            } else {
                fprintf(stderr, "Error: tableview requires getvalue or getvalue2 func\n");
                return (__bridge void*)scrollview;
            }
        }
        
        ListDataSource *dataSource = [[ListDataSource alloc] init:cols var:var getvalue:getvalue getvaluedata:getvaluedata];
        if(model) {
            dataSource.model = ui_model_copy(obj->ctx, model);
        }
        
        tableview.dataSource = dataSource;
        [tableview reloadData];
        
        objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN);
    }
    
    scrollview.documentView = tableview;

    return (__bridge void*)scrollview;
}

/* ------ common functions ------ */

void ui_tableview_update(UiList *list, int i) {
    NSTableView *tableview = (__bridge NSTableView*)list->obj;
    if(i < 0) {
        [tableview reloadData];
    } else {
        [tableview reloadData]; // TODO: optimize
    }
}

UiListSelection ui_tableview_getselection(UiList *list) {
    NSTableView *tableview = (__bridge NSTableView*)list->obj;
    return ui_tableview_selection(tableview);
}

void ui_tableview_setselection(UiList *list, UiListSelection selection) {
    NSTableView *tableview = (__bridge NSTableView*)list->obj;
    NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
    for(int i=0;i<selection.count;i++) {
        [indexSet addIndex:selection.rows[i]];
    }
    [tableview selectRowIndexes:indexSet byExtendingSelection:NO];
}


/* --------------------------- DropDown --------------------------- */

@implementation UiDropDown

- (id)init:(UiObject*)obj {
    _obj = obj;
    return self;
}

- (void) comboBoxSelectionDidChange:(NSNotification *) notification {
    int index = (int)_combobox.indexOfSelectedItem;
    
    void *eventdata = NULL;
    if(_var) {
        UiList *list = _var->value;
        if(index >= 0) {
            eventdata = list->get(list, index);
        }
    } else {
        NSString *str = _combobox.objectValueOfSelectedItem;
        if(str) {
            eventdata = (void*)str.UTF8String;
        }
    }
    
    UiEvent event;
    event.obj = _obj;
    event.window = event.obj->window;
    event.document = event.obj->ctx->document;
    event.eventdata = eventdata;
    event.eventdatatype = UI_EVENT_DATA_LIST_ELM;
    event.intval = index;
    
    if(_onselection) {
        _onselection(&event, _onselectiondata);
    }
    
    if(_onactivate) {
        _onactivate(&event, _onactivatedata);
    }
}

@end

UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) {
    NSComboBox *dropdown = [[NSComboBox alloc] init];
    dropdown.editable = NO;
    
    UiDropDown *uidropdown = [[UiDropDown alloc] init:obj];
    objc_setAssociatedObject(dropdown, "ui_dropdown", uidropdown, OBJC_ASSOCIATION_RETAIN);
    uidropdown.onactivate = args->onactivate;
    uidropdown.onactivatedata = args->onactivatedata;
    uidropdown.onselection = args->onselection;
    uidropdown.onselectiondata = args->onselectiondata;
    uidropdown.combobox = dropdown;
    
    if(!args->getvalue2) {
        if(args->getvalue) {
            args->getvalue2 = getvalue_wrapper;
            args->getvalue2data = (void*)args->getvalue;
        } else {
            args->getvalue2 = str_getvalue;
        }
    }
    uidropdown.getvalue = args->getvalue2;
    uidropdown.getvaluedata = args->getvalue2data;
    
    UiLayout layout = UI_INIT_LAYOUT(args);
    ui_container_add(obj, dropdown, &layout);
    
    UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
    if(var) {
        UiList *list = var->value;
        list->obj = (__bridge void*)dropdown;
        list->update = ui_dropdown_update;
        list->getselection = ui_dropdown_getselection;
        list->setselection = ui_dropdown_setselection;
        ui_dropdown_update(list, -1);
    } else {
        for(int i=0;i<args->static_nelm;i++) {
            char *str = args->static_elements[i];
            NSString *item = [[NSString alloc] initWithUTF8String:str];
            [dropdown addItemWithObjectValue:item];
        }
    }
    
    uidropdown.var = var;
    
    return (__bridge void*)dropdown;
}

void ui_dropdown_update(UiList *list, int i) {
    NSComboBox *combobox = (__bridge NSComboBox*)list->obj;
    UiDropDown *dropdown = objc_getAssociatedObject(combobox, "ui_dropdown");
    if(dropdown) {
        [combobox removeAllItems];
        
        ui_getvaluefunc2 getvalue = dropdown.getvalue;
        void *getvaluedata = dropdown.getvaluedata;
        
        int index = 0;
        void *elm = list->first(list);
        while(elm) {
            UiBool freeResult = FALSE;
            char *str = getvalue(list, elm, index, 0, getvaluedata, &freeResult);
            if(str) {
                NSString *item = [[NSString alloc] initWithUTF8String:str];
                [combobox addItemWithObjectValue:item];
            }
            if(freeResult) {
                free(str);
            }
            elm = list->next(list);
            index++;
        }
    } else {
        fprintf(stderr, "Error: obj is not a dropdown\n");
    }
}

UiListSelection ui_dropdown_getselection(UiList *list) {
    UiListSelection sel = { 0, NULL };
    NSComboBox *combobox = (__bridge NSComboBox*)list->obj;
    NSInteger index = combobox.indexOfSelectedItem;
    if(index >= 0) {
        sel.rows = malloc(sizeof(int));
        sel.count = 1;
        sel.rows[0] = (int)index;
    }
    return sel;
}

void ui_dropdown_setselection(UiList *list, UiListSelection selection) {
    NSComboBox *combobox = (__bridge NSComboBox*)list->obj;
    if(selection.count > 0) {
        [combobox selectItemAtIndex:selection.rows[0]];
    } else {
        [combobox selectItemAtIndex: -1];
    }
}

mercurial