--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/list.m Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,356 @@ +/* + * 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]; + } +}