ui/cocoa/list.m

changeset 109
c3dfcb8f0be7
child 112
c3f2f16fa4b8
--- /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];
+    }
+}

mercurial