Sun, 24 Aug 2025 15:24:16 +0200
update toolkit, remove getvalue func from model to table/listview args
--- a/application/Makefile Sun Jul 20 22:04:39 2025 +0200 +++ b/application/Makefile Sun Aug 24 15:24:16 2025 +0200 @@ -51,7 +51,7 @@ all: ../build/bin/idav$(APP_EXT) ../build/bin/idav$(APP_EXT): $(OBJ) $(LIB_UCX) $(LIB_UITK) - $(CC) -o ../build/bin/idav$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -lidav $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(LIB_UCX) $(LIB_UITK) + $(CC) -o ../build/bin/idav$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib $(LIB_UITK) $(LIB_UCX) $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) -lidav ../build/application/%.$(OBJ_EXT): %.c $(CC) $(CFLAGS) $(TK_CFLAGS) $(DAV_CFLAGS) -o $@ -c $<
--- a/application/application.c Sun Jul 20 22:04:39 2025 +0200 +++ b/application/application.c Sun Aug 24 15:24:16 2025 +0200 @@ -191,7 +191,7 @@ ui_list_append(app->repos, repo); } - ui_notify(app->repos->observers, NULL); + ui_list_update(app->repos); }
--- a/application/settings.c Sun Jul 20 22:04:39 2025 +0200 +++ b/application/settings.c Sun Aug 24 15:24:16 2025 +0200 @@ -656,9 +656,9 @@ ui_newline(obj); UiModel* model = ui_model(obj->ctx, UI_STRING, "Name", UI_STRING, "URL", UI_STRING, "User", UI_STRING, "Encrypted", -1); - model->getvalue = (ui_getvaluefunc) settings_repolist_getvalue; ui_table(obj, .model = model, + .getvalue = (ui_getvaluefunc) settings_repolist_getvalue, .list = wdata->repos, .multiselection = FALSE, .onactivate = repolist_activate,
--- a/application/window.c Sun Jul 20 22:04:39 2025 +0200 +++ b/application/window.c Sun Aug 24 15:24:16 2025 +0200 @@ -95,10 +95,10 @@ UiModel* model = ui_model(obj->ctx, UI_ICON_TEXT, "Name", UI_STRING_FREE, "Flags", UI_STRING, "Type", UI_STRING_FREE, "Last Modified", UI_STRING_FREE, "Size", -1); model->columnsize[0] = -1; model->columnsize[2] = 150; - model->getvalue = (ui_getvaluefunc) window_resource_table_getvalue; ui_table(obj, .fill = TRUE, .model = model, + .getvalue = (ui_getvaluefunc) window_resource_table_getvalue, .onselection = action_list_selection, .onactivate = action_list_activate, .ondragstart = action_dnd_start, @@ -255,8 +255,13 @@ ui_tab(win, "Properties") { UiModel* model = ui_model(win->ctx, UI_STRING, "Namespace", UI_STRING, "Name", UI_STRING, "Value", -1); - model->getvalue = (ui_getvaluefunc) resourceviewer_proplist_getvalue; - ui_table(win, .fill = TRUE, .model = model, .varname = "properties", .onselection = action_resourceviewer_property_select, .onactivate = action_resourceviewer_property_activate); + ui_table(win, + .fill = TRUE, + .model = model, + .getvalue = (ui_getvaluefunc) resourceviewer_proplist_getvalue, + .varname = "properties", + .onselection = action_resourceviewer_property_select, + .onactivate = action_resourceviewer_property_activate); ui_hbox(win, .margin = 4, .spacing = 4) { ui_button(win, .label = "Add", .onclick = action_resourceviewer_property_add); ui_button(win, .label = "Edit", .onclick = action_resourceviewer_property_edit, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_SELECTED));
--- a/ucx/cx/string.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ucx/cx/string.h Sun Aug 24 15:24:16 2025 +0200 @@ -263,6 +263,10 @@ static inline cxstring cx_strcast(cxstring str) { return str; } +cx_attr_nodiscard +static inline cxstring cx_strcast(const char *str) { + return cx_str(str); +} extern "C" { #else /** @@ -287,6 +291,17 @@ } /** + * Internal function, do not use. + * @param str + * @return + * @see cx_strcast() + */ +cx_attr_nodiscard +static inline cxstring cx_strcast_z(const char *str) { + return cx_str(str); +} + +/** * Casts a mutable string to an immutable string. * * Does nothing for already immutable strings. @@ -300,8 +315,9 @@ */ #define cx_strcast(str) _Generic((str), \ cxmutstr: cx_strcast_m, \ - cxstring: cx_strcast_c) \ - (str) + cxstring: cx_strcast_c, \ + const char*: cx_strcast_z, \ + char *: cx_strcast_z) (str) #endif /**
--- a/ucx/mempool.c Sun Jul 20 22:04:39 2025 +0200 +++ b/ucx/mempool.c Sun Aug 24 15:24:16 2025 +0200 @@ -638,7 +638,7 @@ // transfer all registered memory memcpy(&dest->registered[dest->registered_size], source->registered, - sizeof(struct cx_mempool_foreign_memory_s) * source->registered_size); + sizeof(struct cx_mempool_foreign_memory_s) * source->size); dest->registered_size += source->registered_size; // register the old allocator with the new pool
--- a/ui/cocoa/GridLayout.m Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/cocoa/GridLayout.m Sun Aug 24 15:24:16 2025 +0200 @@ -293,7 +293,7 @@ return self.preferredSize; } -- (void) addView:(NSView*)view fill:(BOOL)fill { +- (void) addView:(NSView*)view { if(_newline) { _y++; _x = 0;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/ListDataSource.h Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,42 @@ +/* + * 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 "toolkit.h" +#import "../ui/tree.h" + +@interface ListDataSource : NSObject <NSTableViewDataSource> + +@property NSArray<NSTableColumn*> *columns; +@property UiVar *var; +@property ui_getvaluefunc2 getvalue; +@property void *getvaluedata; +@property UiModel *model; + +- (id) init:(NSArray<NSTableColumn*>*) columns var:(UiVar*)var getvalue:(ui_getvaluefunc2) getvaluefunc getvaluedata:(void*)userdata; + +@end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/ListDataSource.m Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,110 @@ +/* + * 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 "ListDataSource.h" + +@implementation ListDataSource + +- (id) init:(NSArray<NSTableColumn*>*) columns var:(UiVar*)var getvalue:(ui_getvaluefunc2) getvaluefunc getvaluedata:(void*)userdata { + _columns = columns; + _var = var; + _getvalue = getvaluefunc; + _getvaluedata = userdata; + return self; +} + +- (NSInteger) numberOfRowsInTableView:(NSTableView *) tableView { + if(_var) { + UiList *list = _var->value; + if(list->count) { + return list->count(list); + } + } + return 0; +} + +- (id) tableView:(NSTableView *) tableView +objectValueForTableColumn:(NSTableColumn *) tableColumn + row:(NSInteger) row +{ + id ret = nil; + UiList *list = _var->value; + void *elm = list->get(list, (int)row); + if(elm) { + // get column index + NSUInteger colIndex = [_columns indexOfObject:tableColumn]; + if(colIndex == NSNotFound) { + return nil; + } + + // get UI model type for this column + UiModelType type = UI_STRING; + UiModel *model = _model; + if(model) { + if(colIndex >= model->columns) { + return nil; + } + type = model->types[colIndex]; + } + + // convert the list element + UiBool freeResult = FALSE; + void *data = _getvalue(list, elm, (int)row, (int)colIndex, _getvaluedata, &freeResult); + + switch(type) { + case UI_STRING: { + ret = [[NSString alloc] initWithUTF8String:data]; + break; + } + case UI_STRING_FREE: { + ret = [[NSString alloc] initWithUTF8String:data]; + freeResult = TRUE; + break; + } + case UI_INTEGER: { + break; + } + case UI_ICON: { + break; + } + case UI_ICON_TEXT: { + break; + } + case UI_ICON_TEXT_FREE: { + break; + } + } + + if(freeResult) { + free(data); + } + } + return ret; +} + +@end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/ListDelegate.h Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,46 @@ +/* + * 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 "toolkit.h" + +@interface ListDelegate : NSObject <NSTableViewDelegate> + +@property (weak) NSTableView *tableview; +@property UiObject *obj; +@property ui_callback onselection; +@property void *onselectiondata; +@property ui_callback onactivate; +@property void *onactivatedata; + +- (id)init:(NSTableView*) tableview obj:(UiObject*)obj; + +- (void)activateEvent:(id)sender; + +@end + +UiListSelection ui_tableview_selection(NSTableView *tableview);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/ListDelegate.m Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,99 @@ +/* + * 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 "ListDelegate.h" + +@implementation ListDelegate + +- (id)init:(NSTableView*) tableview obj:(UiObject*)obj { + _tableview = tableview; + _obj = obj; + return self; +} + +- (void)activateEvent:(id)sender { + NSTableView *table = sender; + if(_onactivate) { + int row = (int)table.clickedRow; + + UiListSelection sel; + sel.count = 1; + sel.rows = &row; + + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &sel; + event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; + event.intval = row; + event.set = ui_get_setop(); + + _onactivate(&event, _onactivatedata); + } +} + +- (void) tableViewSelectionDidChange:(NSNotification *) notification { + if(_onselection) { + UiListSelection sel = ui_tableview_selection(_tableview); + + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &sel; + event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; + event.intval = 0; + event.set = ui_get_setop(); + + _onselection(&event, _onselectiondata); + } +} + +@end + +UiListSelection ui_tableview_selection(NSTableView *tableview) { + NSIndexSet *indexSet = tableview.selectedRowIndexes; + NSUInteger count = [indexSet count]; + + if(count == 0) { + return (UiListSelection){0, NULL}; + } + + int *rows = calloc(count, sizeof(int)); + + __block NSUInteger i = 0; + [indexSet enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { + rows[i++] = (int)index; + }]; + + UiListSelection sel; + sel.count = (int)count; + sel.rows = rows; + return sel; +}
--- a/ui/cocoa/MainWindow.m Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/cocoa/MainWindow.m Sun Aug 24 15:24:16 2025 +0200 @@ -34,6 +34,7 @@ #import "EventData.h" #import "menu.h" +#import "Toolbar.h" @implementation MainWindow @@ -48,6 +49,12 @@ backing:NSBackingStoreBuffered defer:false]; + if(uic_toolbar_isenabled()) { + UiToolbar *toolbar = [[UiToolbar alloc]initWithObject:obj]; + [self setToolbar:toolbar]; + } + + // create a vertical stackview as default container BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; //GridLayout *vbox = [[GridLayout alloc] init]; @@ -134,8 +141,13 @@ - (void)menuItemAction:(id)sender { EventData *event = objc_getAssociatedObject(sender, "eventdata"); if(event) { - event.obj = self.uiobj; // temporary set the event object - [event handleEvent:sender]; + if(event.obj) { + [event handleEvent:sender]; + } else { + event.obj = self.uiobj; + [event handleEvent:sender]; + event.obj = NULL; + } } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/Toolbar.h Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,77 @@ +/* + * 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 "toolkit.h" +#import "../common/toolbar.h" + +/* + * UiToolbarDelegate + */ +@interface UiToolbarDelegate : NSObject <NSToolbarDelegate> { + NSMutableArray<NSString*> *allowedItems; + NSMutableArray<NSString*> *defaultItems; +} + +- (UiToolbarDelegate*) init; + +@end + +/* + * UiToolbar + */ +@interface UiToolbar : NSToolbar <NSToolbarDelegate> { + NSMutableArray<NSString*> *allowedItems; + NSMutableArray<NSString*> *defaultItems; +} + +@property UiObject *obj; + +- (UiToolbar*) initWithObject:(UiObject*)object; + +@end + + +@interface UiToolbarToggleEventHandler : NSObject +@property UiObject *obj; +@property UiVar *var; +@property ui_callback callback; +@property void *userdata; + +- (UiToolbarToggleEventHandler*)init; +- (void)handleEvent:(id)sender; + +@end + +void ui_toolbar_init(void); + +NSToolbarItem* ui_nstoolbaritem_create_item(UiObject *obj, UiToolbarItem *item, NSString *identifier); +NSToolbarItem* ui_nstoolbaritem_create_toggle(UiObject *obj, UiToolbarToggleItem *item, NSString *identifier); +NSToolbarItem* ui_nstoolbaritem_create_menu(UiObject *obj, UiToolbarMenuItem *item, NSString *identifier); + +int64_t ui_toolbar_seg_toggleitem_get(UiInteger *i); +void ui_toolbar_seg_toggleitem_set(UiInteger *i, int64_t value);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/Toolbar.m Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,245 @@ +/* + * 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 "Toolbar.h" +#import "EventData.h" +#import "image.h" +#import "menu.h" +#import <objc/runtime.h> + +#include "../common/toolbar.h" + + +void ui_toolbar_init(void) { + +} + + + +/* --------------------- UiToolbar --------------------- */ + +@implementation UiToolbar + +- (UiToolbar*) initWithObject:(UiObject*)object { + self = [super initWithIdentifier:@"UiToolbar"]; + _obj = object; + + allowedItems = [[NSMutableArray alloc]initWithCapacity:16]; + defaultItems = [[NSMutableArray alloc]initWithCapacity:16]; + + CxMap *toolbarItems = uic_get_toolbar_items(); + CxMapIterator i = cxMapIteratorKeys(toolbarItems); + cx_foreach(CxHashKey *, key, i) { + NSString *s = [[NSString alloc]initWithBytes:key->data length:key->len encoding:NSUTF8StringEncoding]; + [allowedItems addObject:s]; + } + [allowedItems addObject: NSToolbarFlexibleSpaceItemIdentifier]; + [allowedItems addObject: NSToolbarSpaceItemIdentifier]; + + CxList *tbitems[3]; + tbitems[0] = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT); + tbitems[1] = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER); + tbitems[2] = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT); + for(int t=0;t<3;t++) { + CxIterator iter = cxListIterator(tbitems[t]); + cx_foreach(char *, name, iter) { + NSString *s = [[NSString alloc] initWithUTF8String:name]; + [defaultItems addObject:s]; + } + } + + [self setDelegate:self]; + [self setAllowsUserCustomization:YES]; + return self; +} + +// implementation of NSToolbarDelegate methods + +- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar { + return allowedItems; +} + +- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar { + return defaultItems; +} + +- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar + itemForItemIdentifier:(NSString *)itemIdentifier + willBeInsertedIntoToolbar:(BOOL)flag { + CxMap *items = uic_get_toolbar_items(); + UiToolbarItemI *item = cxMapGet(items, itemIdentifier.UTF8String); + if(!item) { + return nil; + } + + switch(item->type) { + default: return nil; + case UI_TOOLBAR_ITEM: { + return ui_nstoolbaritem_create_item(_obj, (UiToolbarItem*)item, itemIdentifier); + } + case UI_TOOLBAR_TOGGLEITEM: { + return ui_nstoolbaritem_create_toggle(_obj, (UiToolbarToggleItem*)item, itemIdentifier); + } + case UI_TOOLBAR_MENU: { + return ui_nstoolbaritem_create_menu(_obj, (UiToolbarMenuItem*)item, itemIdentifier); + } + } + + return nil; +} + +@end + +@implementation UiToolbarToggleEventHandler + +- (UiToolbarToggleEventHandler*)init { + return self; +} + +- (void)handleEvent:(id)sender { + if(_callback == nil) { + return; + } + + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = 0; + event.set = ui_get_setop(); + + if([sender isKindOfClass:[NSSegmentedControl class]]) { + NSSegmentedControl *seg = (NSSegmentedControl*)sender; + event.intval = [seg isSelectedForSegment:0]; + } + + _callback(&event, _userdata); +} + +@end + +NSToolbarItem* ui_nstoolbaritem_create_item(UiObject *obj, UiToolbarItem *item, NSString *identifier) { + NSToolbarItem *button = [[NSToolbarItem alloc] initWithItemIdentifier: identifier]; + button.bordered = YES; + + if(item->args.label) { + NSString *label = [[NSString alloc] initWithUTF8String:item->args.label]; + button.paletteLabel = label; + button.label = label; + } + if(item->args.icon) { + button.image = ui_cocoa_named_icon(item->args.icon); + } + + if(item->args.onclick) { + EventData *event = [[EventData alloc] init:item->args.onclick userdata:item->args.onclickdata]; + event.obj = obj; + button.target = event; + button.action = @selector(handleEvent:); + objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + return button; +} + +NSToolbarItem* ui_nstoolbaritem_create_toggle(UiObject *obj, UiToolbarToggleItem *item, NSString *identifier) { + UiToolbarToggleEventHandler *event = [[UiToolbarToggleEventHandler alloc]init]; + event.obj = obj; + event.callback = item->args.onchange; + event.userdata = item->args.onchangedata; + + NSToolbarItem *button = [[NSToolbarItem alloc] initWithItemIdentifier:identifier]; + if(item->args.label) { + NSString *label = [[NSString alloc] initWithUTF8String:item->args.label]; + button.paletteLabel = label; + button.label = label; + } + objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + + NSSegmentedControl *seg; + if(!item->args.icon) { + NSArray *labels = @[[[NSString alloc] initWithUTF8String:item->args.label]]; + seg = [NSSegmentedControl segmentedControlWithLabels:labels trackingMode:NSSegmentSwitchTrackingSelectAny target:event action:@selector(handleEvent:)]; + button.view = seg; + } else { + NSArray *images = @[ui_cocoa_named_icon(item->args.icon)]; + seg = [NSSegmentedControl segmentedControlWithImages:images trackingMode:NSSegmentSwitchTrackingSelectAny target:event action:@selector(handleEvent:)]; + } + button.view = seg; + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, item->args.varname, UI_VAR_INTEGER); + if(var) { + event.var = var; + UiInteger *i = var->value; + if(i->get) { + if(ui_get(i) != 0) { + [seg setEnabled:YES forSegment:0]; + } + + } + i->obj = (__bridge void*)seg; + i->get = ui_toolbar_seg_toggleitem_get; + i->set = ui_toolbar_seg_toggleitem_set; + } + + return button; +} + +int64_t ui_toolbar_seg_toggleitem_get(UiInteger *i) { + NSSegmentedControl *seg = (__bridge NSSegmentedControl*)i->obj; + i->value = [seg isSelectedForSegment:0]; + return i->value; +} + +void ui_toolbar_seg_toggleitem_set(UiInteger *i, int64_t value) { + NSSegmentedControl *seg = (__bridge NSSegmentedControl*)i->obj; + i->value = value; + [seg setSelected:value != 0 forSegment:0]; +} + +NSToolbarItem* ui_nstoolbaritem_create_menu(UiObject *obj, UiToolbarMenuItem *item, NSString *identifier) { + NSMenuToolbarItem *button = [[NSMenuToolbarItem alloc] initWithItemIdentifier: identifier]; + button.bordered = YES; + + if(item->args.label) { + NSString *label = [[NSString alloc] initWithUTF8String:item->args.label]; + button.paletteLabel = label; + button.label = label; + } + if(item->args.icon) { + button.image = ui_cocoa_named_icon(item->args.icon); + } + + NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; + ui_add_menu_items(obj, menu, 0, &item->menu); + + button.menu = menu; + + return button; +}
--- a/ui/cocoa/UiJob.h Sun Jul 20 22:04:39 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2024 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 "toolkit.h" -
--- a/ui/cocoa/UiJob.m Sun Jul 20 22:04:39 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2024 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 "UiJob.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/UiThread.h Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,45 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 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 "toolkit.h" + +@interface UiThread : NSObject + +@property UiObject *obj; +@property ui_threadfunc job; +@property void *jobdata; +@property ui_callback finish_callback; +@property void *finish_userdata; + +- (id)init:(UiObject*)obj jobfunc:(ui_threadfunc)job jobdata:(void*)jobdata; + +- (void)start; +- (void) runJob:(id)n; +- (void) finish:(id)n; + +@end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/UiThread.m Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 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 "UiThread.h" + + +@implementation UiThread + +- (id) init:(UiObject*)obj jobfunc:(ui_threadfunc)job jobdata:(void*)jobdata { + _obj = obj; + _job = job; + _jobdata = jobdata; + return self; +} + +- (void) start { + [NSThread detachNewThreadSelector:@selector(runJob:) + toTarget:self + withObject:nil]; +} + + +- (void) runJob:(id)n { + int result = _job(_jobdata); + if(!result) { + [self performSelectorOnMainThread:@selector(finish:) + withObject:nil + waitUntilDone:NO]; + } +} + +- (void) finish:(id)n { + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.intval = 0; + _finish_callback(&event, _finish_userdata); +} + +@end +
--- a/ui/cocoa/button.m Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/cocoa/button.m Sun Aug 24 15:24:16 2025 +0200 @@ -47,7 +47,7 @@ } UiLayout layout = UI_INIT_LAYOUT(args); - ui_container_add(obj, button, &layout, FALSE); + ui_container_add(obj, button, &layout); return (__bridge void*)button; } @@ -89,7 +89,7 @@ } UiLayout layout = UI_INIT_LAYOUT(args); - ui_container_add(obj, button, &layout, FALSE); + ui_container_add(obj, button, &layout); return (__bridge void*)button; } @@ -150,7 +150,7 @@ } UiLayout layout = UI_INIT_LAYOUT(args); - ui_container_add(obj, button, &layout, FALSE); + ui_container_add(obj, button, &layout); return (__bridge void*)button; } @@ -242,7 +242,7 @@ } UiLayout layout = UI_INIT_LAYOUT(args); - ui_container_add(obj, button, &layout, FALSE); + ui_container_add(obj, button, &layout); return (__bridge void*)button; }
--- a/ui/cocoa/container.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/cocoa/container.h Sun Aug 24 15:24:16 2025 +0200 @@ -71,7 +71,7 @@ @property const char *label; @property UiBool newline; -- (void) addView:(NSView*)view fill:(BOOL)fill; +- (void) addView:(NSView*)view; @end @@ -87,4 +87,4 @@ UiContainerX* ui_create_container(UiObject *obj, id<Container> container); -void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout, UiBool fill); +void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout);
--- a/ui/cocoa/container.m Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/cocoa/container.m Sun Aug 24 15:24:16 2025 +0200 @@ -57,10 +57,8 @@ return self; } -- (void) addView:(NSView*)view fill:(BOOL)fill { - if(_uilayout.fill != UI_LAYOUT_UNDEFINED) { - fill = ui_lb2bool(_uilayout.fill); - } +- (void) addView:(NSView*)view { + UiBool fill = _uilayout.fill; [self addArrangedSubview:view]; @@ -101,7 +99,7 @@ // add box to the parent UiLayout layout = UI_INIT_LAYOUT(args); - ui_container_add(obj, box, &layout, TRUE); + ui_container_add(obj, box, &layout); // add new box to the obj container chain uic_object_push_container(obj, ui_create_container(obj, box)); @@ -123,7 +121,7 @@ // add box to the parent UiLayout layout = UI_INIT_LAYOUT(args); - ui_container_add(obj, grid, &layout, TRUE); + ui_container_add(obj, grid, &layout); // add new box to the obj container chain uic_object_push_container(obj, ui_create_container(obj, grid)); @@ -157,11 +155,11 @@ return ctn; } -void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout, UiBool fill) { +void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout) { UiContainerX *ctn = obj->container_end; id<Container> container = (__bridge id<Container>)ctn->container; container.uilayout = *layout; - [container addView:view fill:fill]; + [container addView:view]; } /* ---------------------- public layout functions ----------------------- */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/image.h Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,35 @@ +/* + * 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 "../ui/image.h" + +#include "Container.h" + +void ui_icon_init(void); + +NSImage* ui_cocoa_named_icon(const char *name);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/image.m Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,67 @@ +/* + * 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 "image.h" + +static NSDictionary *standardIconNames; + +void ui_icon_init(void) { + standardIconNames = @{ + @"NSImageNameActionTemplate": NSImageNameActionTemplate, + @"NSImageNameAddTemplate": NSImageNameAddTemplate, + @"NSImageNameAdvanced": NSImageNameAdvanced, + @"NSImageNameApplicationIcon": NSImageNameApplicationIcon, + @"NSImageNameBluetoothTemplate": NSImageNameBluetoothTemplate, + @"NSImageNameBonjour": NSImageNameBonjour, + @"NSImageNameBookmarksTemplate": NSImageNameBookmarksTemplate, + @"NSImageNameCaution": NSImageNameCaution, + // TODO + @"NSImageNameRefreshTemplate": NSImageNameRefreshTemplate, + @"NSImageNameFolder": NSImageNameFolder, + @"NSImageNameGoForwardTemplate": NSImageNameGoForwardTemplate, + @"NSImageNameGoBackTemplate": NSImageNameGoBackTemplate + }; +} + +NSImage* ui_cocoa_named_icon(const char *name) { + NSString *imageName = [[NSString alloc] initWithUTF8String:name]; + NSString *imgName = [standardIconNames objectForKey:imageName]; + if(imgName) { + imageName = imgName; + } + return [NSImage imageNamed:imageName]; +} + + +void ui_image_ref(UIIMAGE img) { + // TODO +} + +void ui_image_unref(UIIMAGE img) { + // TODO +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/label.h Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,33 @@ +/* + * 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 "toolkit.h" +#import "../ui/display.h" + +char* ui_label_get(UiString *s); +void ui_label_set(UiString *s, const char *str);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/label.m Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,104 @@ +/* + * 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 "label.h" +#import "container.h" + +#import <string.h> + +static UIWIDGET create_label(UiObject *obj, UiLabelArgs *args) { + NSTextField *label = [[NSTextField alloc] init]; + label.editable = NO; + label.bezeled = NO; + label.drawsBackground = NO; + label.selectable = NO; + + NSTextAlignment alignment; + switch(args->align) { + case UI_ALIGN_LEFT: alignment = NSTextAlignmentLeft; break; + case UI_ALIGN_RIGHT: alignment = NSTextAlignmentRight; break; + default: alignment = NSTextAlignmentCenter; + } + label.alignment = alignment; + + if(args->label) { + NSString *str = [[NSString alloc] initWithUTF8String:args->label]; + label.stringValue = str; + } + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, label, &layout); + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); + if(var) { + UiString *s = var->value; + s->obj = (__bridge void*)label; + s->get = ui_label_get; + s->set = ui_label_set; + + if(s->value.ptr) { + label.stringValue = [[NSString alloc]initWithUTF8String:s->value.ptr]; + } + } + + return (__bridge void*)label; +} + + +UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs *args) { + return create_label(obj, args); +} + +UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs *args) { + args->align = UI_ALIGN_LEFT; + return create_label(obj, args); +} + +UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs *args) { + args->align = UI_ALIGN_RIGHT; + return create_label(obj, args); +} + + +char* ui_label_get(UiString *s) { + NSTextField *label = (__bridge NSTextField*)s->obj; + if(s->value.free) { + s->value.free(s->value.ptr); + } + s->value.ptr = strdup(label.stringValue.UTF8String); + return s->value.ptr; +} + +void ui_label_set(UiString *s, const char *str) { + NSTextField *label = (__bridge NSTextField*)s->obj; + if(s->value.free) { + s->value.free(s->value.ptr); + } + s->value.ptr = NULL; + label.stringValue = [[NSString alloc] initWithUTF8String:str]; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/list.h Sun Aug 24 15:24:16 2025 +0200 @@ -0,0 +1,57 @@ +/* + * 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 "toolkit.h" +#import "Container.h" +#import "../ui/tree.h" + +#import "ListDataSource.h" + +@interface UiDropDown : NSObject<NSComboBoxDelegate> + +@property UiObject *obj; +@property ui_callback onactivate; +@property void *onactivatedata; +@property ui_callback onselection; +@property void *onselectiondata; +@property ui_getvaluefunc2 getvalue; +@property void *getvaluedata; +@property UiVar *var; +@property (weak) NSComboBox *combobox; + +- (id)init:(UiObject*)obj; + +@end + +void ui_tableview_update(UiList *list, int i); +UiListSelection ui_tableview_getselection(UiList *list); +void ui_tableview_setselection(UiList *list, UiListSelection selection); + +void ui_dropdown_update(UiList *list, int i); +UiListSelection ui_dropdown_getselection(UiList *list); +void ui_dropdown_setselection(UiList *list, UiListSelection selection);
--- /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]; + } +}
--- a/ui/cocoa/menu.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/cocoa/menu.h Sun Aug 24 15:24:16 2025 +0200 @@ -33,26 +33,38 @@ @interface MenuItem : NSObject +@property (weak) NSMenuItem *menuItem; @property (strong) NSString *itemId; -@property UiMenuCheckItem *checkItem; -@property UiMenuRadioItem *radioItem; -@property ui_callback callback; -@property void *userdata; +@property UiMenuCheckItem *checkItem; +@property UiMenuRadioItem *radioItem; +@property ui_callback callback; +@property void *userdata; +@property (strong) NSString *varname; +@property UiObject *obj; +@property UiVar *var; +@property BOOL state; - (MenuItem*)init:(int)itId; +- (void)handleToggleEvent:(id)sender; + @end void ui_menu_init(void); -typedef void(*ui_menu_add_f)(NSMenu*, int, UiMenuItemI*); +typedef void(*ui_menu_add_f)(UiObject*, NSMenu*, int, UiMenuItemI*); -void add_menu_widget(NSMenu *parent, int i, UiMenuItemI *item); -void add_menuitem_widget(NSMenu *parent, int i, UiMenuItemI *item); -void add_menuseparator_widget(NSMenu *parent, int i, UiMenuItemI *item); -void add_checkitem_widget(NSMenu *parent, int i, UiMenuItemI *item); -void add_radioitem_widget(NSMenu *parent, int index, UiMenuItemI *item); -void add_checkitemnv_widget(NSMenu *parent, int i, UiMenuItemI *item); -void add_menuitem_list_widget(NSMenu *parent, int i, UiMenuItemI *item); +void add_menu_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item); +void add_menuitem_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item); +void add_menuseparator_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item); +void add_checkitem_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item); +void add_radioitem_widget(UiObject *obj, NSMenu *parent, int index, UiMenuItemI *item); +void add_checkitemnv_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item); +void add_menuitem_list_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item); + +void ui_add_menu_items(UiObject *obj, NSMenu *parent, int i, UiMenu *menu); NSArray* ui_get_binding_items(void); + +int64_t ui_menu_toggleitem_get(UiInteger *i); +void ui_menu_toggleitem_set(UiInteger *i, int64_t value);
--- a/ui/cocoa/menu.m Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/cocoa/menu.m Sun Aug 24 15:24:16 2025 +0200 @@ -50,6 +50,28 @@ return self; } +- (void)handleToggleEvent:(id)sender { + NSMenuItem *item = (NSMenuItem*)sender; + if(!_state) { + item.state = NSControlStateValueOn; + } else { + item.state = NSControlStateValueOff; + } + _state = !_state; + + if(_callback) { + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = _state; + event.set = ui_get_setop(); + _callback(&event, _userdata); + } +} + @end static ui_menu_add_f createMenuItem[] = { @@ -63,61 +85,84 @@ /* UI_MENU_SEPARATOR */ add_menuseparator_widget }; -static void add_menu_items(NSMenu *parent, int i, UiMenu *menu) { +void ui_add_menu_items(UiObject *obj, NSMenu *parent, int i, UiMenu *menu) { UiMenuItemI *it = menu->items_begin; int index = 0; while(it) { - createMenuItem[it->type](parent, index, it); + createMenuItem[it->type](obj, parent, index, it); it = it->next; index++; } } -void add_menu_widget(NSMenu *parent, int i, UiMenuItemI *item) { +void add_menu_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) { UiMenu *it = (UiMenu*)item; NSString *str = [[NSString alloc] initWithUTF8String:it->label]; NSMenu *menu = [[NSMenu alloc] initWithTitle: str]; NSMenuItem *menuItem = [parent addItemWithTitle:str action:nil keyEquivalent:@""]; [parent setSubmenu:menu forItem:menuItem]; - add_menu_items(menu, i, it); + ui_add_menu_items(obj, menu, i, it); } -void add_menuitem_widget(NSMenu *parent, int i, UiMenuItemI *item) { +void add_menuitem_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) { UiMenuItem *it = (UiMenuItem*)item; NSString *str = [[NSString alloc] initWithUTF8String:it->label]; - NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuItemAction:) keyEquivalent:@""]; + NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuItemAction) keyEquivalent:@""]; if(it->callback) { EventData *event = [[EventData alloc] init:it->callback userdata:it->userdata]; + if(obj) { + event.obj = obj; + menuItem.target = event; + menuItem.action = @selector(handleEvent:); + } objc_setAssociatedObject(menuItem, "eventdata", event, OBJC_ASSOCIATION_RETAIN); } } -void add_menuseparator_widget(NSMenu *parent, int i, UiMenuItemI *item) { +void add_menuseparator_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) { NSMenuItem *menuItem = [NSMenuItem separatorItem]; [parent addItem:menuItem]; } static int nItem = 0; -void add_checkitem_widget(NSMenu *parent, int i, UiMenuItemI *item) { +void add_checkitem_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) { UiMenuCheckItem *it = (UiMenuCheckItem*)item; NSString *str = [[NSString alloc] initWithUTF8String:it->label]; NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuCheckItemAction:) keyEquivalent:@""]; MenuItem *mItem = [[MenuItem alloc] init:nItem++]; + mItem.menuItem = menuItem; + mItem.obj = obj; mItem.callback = it->callback; mItem.userdata = it->userdata; mItem.checkItem = it; + if(it->varname) { + mItem.varname = [[NSString alloc] initWithUTF8String:it->varname]; + } objc_setAssociatedObject(menuItem, "menuitem", mItem, OBJC_ASSOCIATION_RETAIN); - [bindingItems addObject:mItem]; + + if(!obj) { + [bindingItems addObject:mItem]; + } else { + mItem.var = uic_widget_var(obj->ctx, obj->ctx, NULL, it->varname, UI_VAR_INTEGER); + if(mItem.var) { + UiInteger *i = mItem.var->value; + i->obj = (__bridge void*)mItem; + i->get = ui_menu_toggleitem_get; + i->set = ui_menu_toggleitem_set; + } + menuItem.target = mItem; + menuItem.action = @selector(handleToggleEvent:); + } } -void add_radioitem_widget(NSMenu *parent, int index, UiMenuItemI *item) { +void add_radioitem_widget(UiObject *obj, NSMenu *parent, int index, UiMenuItemI *item) { UiMenuRadioItem *it = (UiMenuRadioItem*)item; NSString *str = [[NSString alloc] initWithUTF8String:it->label]; @@ -132,11 +177,11 @@ [bindingItems addObject:mItem]; } -void add_checkitemnv_widget(NSMenu *parent, int i, UiMenuItemI *item) { +void add_checkitemnv_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) { } -void add_menuitem_list_widget(NSMenu *parent, int i, UiMenuItemI *item) { +void add_menuitem_list_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) { } @@ -154,7 +199,7 @@ NSMenuItem *menuItem = [[NSApp mainMenu] insertItemWithTitle:str action:nil keyEquivalent:@"" atIndex:index]; [[NSApp mainMenu] setSubmenu:menu forItem:menuItem]; - add_menu_items(menu, 0, ls); + ui_add_menu_items(NULL, menu, 0, ls); } ls = (UiMenu*)ls->item.next; index++; @@ -164,3 +209,20 @@ NSArray* ui_get_binding_items(void) { return bindingItems; } + + +int64_t ui_menu_toggleitem_get(UiInteger *i) { + MenuItem *item = (__bridge MenuItem*)i->obj; + i->value = item.state; + return i->value; +} + +void ui_menu_toggleitem_set(UiInteger *i, int64_t value) { + MenuItem *item = (__bridge MenuItem*)i->obj; + i->value = value; + if(value != 0) { + item.menuItem.state = NSControlStateValueOn; + } else { + item.menuItem.state = NSControlStateValueOff; + } +}
--- a/ui/cocoa/objs.mk Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/cocoa/objs.mk Sun Aug 24 15:24:16 2025 +0200 @@ -41,6 +41,7 @@ COCOAOBJ += button.o COCOAOBJ += text.o COCOAOBJ += menu.o +COCOAOBJ += Toolbar.o TOOLKITOBJS += $(COCOAOBJ:%=$(COCOA_OBJPRE)%) TOOLKITSOURCE += $(COCOAOBJ:%.o=cocoa/%.m)
--- a/ui/cocoa/text.m Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/cocoa/text.m Sun Aug 24 15:24:16 2025 +0200 @@ -42,7 +42,7 @@ scrollview.documentView = textview; UiLayout layout = UI_INIT_LAYOUT(args); - ui_container_add(obj, scrollview, &layout, TRUE); + ui_container_add(obj, scrollview, &layout); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_TEXT); @@ -85,7 +85,6 @@ NSTextStorage *textStorage; if(text->data1) { textStorage = (__bridge NSTextStorage*)text->data1; - } else { textStorage = [[NSTextStorage alloc] init]; } @@ -94,43 +93,83 @@ } void ui_textarea_set(UiText *text, const char *str) { - + NSTextView *textview = (__bridge NSTextView*)text->obj; + if(text->value.free) { + text->value.free(text->value.ptr); + } + text->value.ptr = strdup(str); + text->value.free = free; + textview.string = [[NSString alloc] initWithUTF8String:str]; } char* ui_textarea_get(UiText *text) { - return NULL; + NSTextView *textview = (__bridge NSTextView*)text->obj; + if(text->value.free) { + text->value.free(text->value.ptr); + } + text->value.ptr = strdup(textview.string.UTF8String); + text->value.free = free; + return text->value.ptr; } char* ui_textarea_getsubstr(UiText *text, int begin, int end) { - return NULL; + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSString *str = textview.string; + NSRange range = NSMakeRange(begin, end-begin); + NSString *sub = [str substringWithRange:range]; + return strdup(sub.UTF8String); } void ui_textarea_insert(UiText *text, int pos, char *str) { - + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSString *s = [[NSString alloc] initWithUTF8String:str]; + NSAttributedString *attributedStr = [[NSAttributedString alloc] initWithString:s]; + [textview.textStorage insertAttributedString:attributedStr atIndex:pos]; } void ui_textarea_setposition(UiText *text, int pos) { - + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSRange range = NSMakeRange(pos, 0); + [textview setSelectedRange:range]; } int ui_textarea_position(UiText *text) { - return 0; + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSRange range = textview.selectedRange; + return (int)range.location; } void ui_textarea_setselection(UiText *text, int begin, int end) { - + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSRange range = NSMakeRange(begin, end-begin); + [textview setSelectedRange:range]; } void ui_textarea_selection(UiText *text, int *begin, int *end) { - + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSRange range = textview.selectedRange; + if(begin) { + *begin = (int)range.location; + } + if(end) { + *end = (int)(range.location+range.length); + } } int ui_textarea_length(UiText *text) { - return 0; + NSTextView *textview = (__bridge NSTextView*)text->obj; + return (int)textview.string.length; } void ui_textarea_remove(UiText *text, int begin, int end) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + if (begin < 0 || end < begin || end > textview.string.length) { + return; + } + + NSRange range = NSMakeRange(begin, end - begin); + [[textview textStorage] deleteCharactersInRange:range]; } @@ -150,7 +189,7 @@ } UiLayout layout = UI_INIT_LAYOUT(args); - ui_container_add(obj, textfield, &layout, FALSE); + ui_container_add(obj, textfield, &layout); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); if(var) {
--- a/ui/cocoa/toolkit.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/cocoa/toolkit.h Sun Aug 24 15:24:16 2025 +0200 @@ -31,6 +31,19 @@ #include "../common/context.h" #include "../common/object.h" +@interface UiAppCallback : NSObject { + ui_threadfunc callback; + void *userdata; +} + +- (id) initWithCallback:(ui_threadfunc)func userdata:(void*)userdata; + +- (void) callMainThread; + +- (void) mainThread:(id)n; + +@end + void ui_cocoa_onstartup(void); void ui_cocoa_onopen(const char *file); void ui_cocoa_onexit(void);
--- a/ui/cocoa/toolkit.m Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/cocoa/toolkit.m Sun Aug 24 15:24:16 2025 +0200 @@ -34,7 +34,10 @@ #include "../common/toolbar.h" #include "../common/threadpool.h" +#import "image.h" #import "menu.h" +#import "Toolbar.h" +#import "UiThread.h" #import "AppDelegate.h" @@ -70,6 +73,10 @@ [NSApplication sharedApplication]; //[NSBundle loadNibNamed:@"MainMenu" owner:NSApp ]; //[[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:NSApp topLevelObjects:&topLevelObjects]; + + ui_icon_init(); + ui_toolbar_init(); + } const char* ui_appname() { @@ -154,9 +161,33 @@ /* ------------------- Job Control / Threadpool functions ------------------- */ void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) { + UiThread *thread = [[UiThread alloc]init:obj jobfunc:tf jobdata:td]; + thread.finish_callback = f; + thread.finish_userdata = fd; + [thread start]; +} +@implementation UiAppCallback + +- (id) initWithCallback:(ui_threadfunc)func userdata:(void*)userdata { + self->callback = func; + self->userdata = userdata; + return self; } -void ui_call_mainthread(ui_threadfunc tf, void* td) { +- (void) callMainThread { + [self performSelectorOnMainThread:@selector(mainThread:) + withObject:nil + waitUntilDone:NO]; +} +- (void) mainThread:(id)n { + callback(userdata); } + +@end + +void ui_call_mainthread(ui_threadfunc tf, void* td) { + UiAppCallback *cb = [[UiAppCallback alloc]initWithCallback:tf userdata:td]; + [cb callMainThread]; +}
--- a/ui/common/args.c Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/common/args.c Sun Aug 24 15:24:16 2025 +0200 @@ -758,6 +758,71 @@ } +/* ------------------------- UiWidgetArgs ----------------------------*/ + +UiWidgetArgs* ui_widget_args_new(void) { + UiWidgetArgs *args = malloc(sizeof(UiWidgetArgs)); + memset(args, 0, sizeof(UiWidgetArgs)); + return args; +} + + +void ui_widget_args_set_fill(UiWidgetArgs *args, UiBool fill) { + args->fill = fill ? UI_ON : UI_OFF; +} + + +void ui_widget_args_set_hexpand(UiWidgetArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_widget_args_set_vexpand(UiWidgetArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_widget_args_set_hfill(UiWidgetArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_widget_args_set_vfill(UiWidgetArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_widget_args_set_override_defaults(UiWidgetArgs *args, UiBool value) { + args->override_defaults = value; +} + + +void ui_widget_args_set_colspan(UiWidgetArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_widget_args_set_rowspan(UiWidgetArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_widget_args_set_name(UiWidgetArgs *args, const char *name) { + args->name = strdup(name); +} + + +void ui_widget_args_set_style_class(UiWidgetArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_widget_args_free(UiWidgetArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free(args); +} + + /* ------------------------- UiLabelArgs ----------------------------*/ @@ -1209,6 +1274,111 @@ free(args); } +/* ------------------------- UiLinkButtonArgs ----------------------------*/ + + +UiLinkButtonArgs* ui_linkbutton_args_new(void) { + UiLinkButtonArgs *args = malloc(sizeof(UiLinkButtonArgs)); + memset(args, 0, sizeof(UiLinkButtonArgs)); + return args; +} + + +void ui_linkbutton_args_set_fill(UiLinkButtonArgs *args, UiBool fill) { + args->fill = fill ? UI_ON : UI_OFF; +} + + +void ui_linkbutton_args_set_hexpand(UiLinkButtonArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_linkbutton_args_set_vexpand(UiLinkButtonArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_linkbutton_args_set_hfill(UiLinkButtonArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_linkbutton_args_set_vfill(UiLinkButtonArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_linkbutton_args_set_override_defaults(UiLinkButtonArgs *args, UiBool value) { + args->override_defaults = value; +} + + +void ui_linkbutton_args_set_colspan(UiLinkButtonArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_linkbutton_args_set_rowspan(UiLinkButtonArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_linkbutton_args_set_name(UiLinkButtonArgs *args, const char *name) { + args->name = strdup(name); +} + + +void ui_linkbutton_args_set_style_class(UiLinkButtonArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_linkbutton_args_set_label(UiLinkButtonArgs *args, const char *label){ + args->label = strdup(label); +} + +void ui_linkbutton_args_set_uri(UiLinkButtonArgs *args, const char *uri) { + args->uri = strdup(uri); +} + +void ui_linkbutton_args_set_onclick(UiLinkButtonArgs *args, ui_callback callback) { + args->onclick = callback; +} + +void ui_linkbutton_args_set_onclickdata(UiLinkButtonArgs *args, void *userdata) { + args->onclickdata = userdata; +} + +void ui_linkbutton_args_set_nofollow(UiLinkButtonArgs *args, UiBool value) { + args->nofollow = value; +} + +void ui_linkbutton_args_set_type(UiLinkButtonArgs *args, UiLinkType type) { + args->type = type; +} + +void ui_linkbutton_args_set_varname(UiLinkButtonArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_linkbutton_args_set_value(UiLinkButtonArgs *args, UiString *value) { + args->value = value; +} + +void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *groups){ + // TODO +} + +void ui_linkbutton_args_free(UiLinkButtonArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->label); + free((void*)args->uri); + free((void*)args->varname); + free((void*)args->groups); + free(args); +} + /* ------------------------- UiListArgs ----------------------------*/
--- a/ui/common/args.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/common/args.h Sun Aug 24 15:24:16 2025 +0200 @@ -38,6 +38,7 @@ #include "../ui/tree.h" #include "../ui/text.h" #include "../ui/webview.h" +#include "../ui/widget.h" #ifdef __cplusplus extern "C" { @@ -195,6 +196,19 @@ UIEXPORT void ui_splitpane_args_set_max_panes(UiSplitPaneArgs *args, int max); UIEXPORT void ui_splitpane_args_free(UiSplitPaneArgs *args); +UIEXPORT UiWidgetArgs* ui_widget_args_new(void); +UIEXPORT void ui_widget_args_set_fill(UiWidgetArgs *args, UiBool fill); +UIEXPORT void ui_widget_args_set_hexpand(UiWidgetArgs *args, UiBool value); +UIEXPORT void ui_widget_args_set_vexpand(UiWidgetArgs *args, UiBool value); +UIEXPORT void ui_widget_args_set_hfill(UiWidgetArgs *args, UiBool value); +UIEXPORT void ui_widget_args_set_vfill(UiWidgetArgs *args, UiBool value); +UIEXPORT void ui_widget_args_set_override_defaults(UiWidgetArgs *args, UiBool value); +UIEXPORT void ui_widget_args_set_colspan(UiWidgetArgs *args, int colspan); +UIEXPORT void ui_widget_args_set_rowspan(UiWidgetArgs *args, int rowspan); +UIEXPORT void ui_widget_args_set_name(UiWidgetArgs *args, const char *name); +UIEXPORT void ui_widget_args_set_style_class(UiWidgetArgs *args, const char *classname); +UIEXPORT void ui_widget_args_free(UiWidgetArgs *args); + UIEXPORT UiLabelArgs* ui_label_args_new(void); UIEXPORT void ui_label_args_set_fill(UiLabelArgs *args, UiBool fill); UIEXPORT void ui_label_args_set_hexpand(UiLabelArgs *args, UiBool value); @@ -288,6 +302,28 @@ UIEXPORT void ui_toggle_args_set_groups(UiToggleArgs *args, int *groups); UIEXPORT void ui_toggle_args_free(UiToggleArgs *args); +UIEXPORT UiLinkButtonArgs* ui_linkbutton_args_new(void); +UIEXPORT void ui_linkbutton_args_set_fill(UiLinkButtonArgs *args, UiBool fill); +UIEXPORT void ui_linkbutton_args_set_hexpand(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_vexpand(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_hfill(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_vfill(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_override_defaults(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_colspan(UiLinkButtonArgs *args, int colspan); +UIEXPORT void ui_linkbutton_args_set_rowspan(UiLinkButtonArgs *args, int rowspan); +UIEXPORT void ui_linkbutton_args_set_name(UiLinkButtonArgs *args, const char *name); +UIEXPORT void ui_linkbutton_args_set_style_class(UiLinkButtonArgs *args, const char *classname); +UIEXPORT void ui_linkbutton_args_set_varname(UiLinkButtonArgs *args, const char *varname); +UIEXPORT void ui_linkbutton_args_set_value(UiLinkButtonArgs *args, UiString *value); +UIEXPORT void ui_linkbutton_args_set_label(UiLinkButtonArgs *args, const char *label); +UIEXPORT void ui_linkbutton_args_set_uri(UiLinkButtonArgs *args, const char *uri); +UIEXPORT void ui_linkbutton_args_set_onclick(UiLinkButtonArgs *args, ui_callback callback); +UIEXPORT void ui_linkbutton_args_set_onclickdata(UiLinkButtonArgs *args, void *userdata); +UIEXPORT void ui_linkbutton_args_set_nofollow(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_type(UiLinkButtonArgs *args, UiLinkType type); +UIEXPORT void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *groups); +UIEXPORT void ui_linkbutton_args_free(UiLinkButtonArgs *args); + UIEXPORT UiListArgs* ui_list_args_new(void); UIEXPORT void ui_list_args_set_fill(UiListArgs *args, UiBool fill); UIEXPORT void ui_list_args_set_hexpand(UiListArgs *args, UiBool value);
--- a/ui/common/types.c Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/common/types.c Sun Aug 24 15:24:16 2025 +0200 @@ -165,12 +165,26 @@ cxListClear(list->data); } -UIEXPORT void ui_list_update(UiList *list) { +void ui_list_update(UiList *list) { if(list->update) { list->update(list, -1); } } +void ui_list_update_row(UiList *list, int row) { + if(list->update) { + list->update(list, row); + } +} + +UiListSelection ui_list_get_selection(UiList *list) { + if(list->getselection) { + return list->getselection(list); + } else { + return (UiListSelection){0, NULL}; + } +} + void ui_list_addobsv(UiList *list, ui_callback f, void *data) { list->observers = ui_add_observer(list->observers, f, data); }
--- a/ui/common/wrapper.c Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/common/wrapper.c Sun Aug 24 15:24:16 2025 +0200 @@ -218,3 +218,38 @@ void ui_sublist_item_set_eventdata(UiSubListItem *item, void *eventdata) { item->eventdata = NULL; } + +/* ---------------------------- UiListSelection ---------------------------- */ + +UiListSelection* ui_list_get_selection_allocated(UiList *list) { + UiListSelection *sel = malloc(sizeof(UiListSelection)); + *sel = ui_list_get_selection(list); + return sel; + +} + +int ui_list_selection_get_count(UiListSelection *sel) { + return sel->count; +} + +int* ui_list_selection_get_rows(UiListSelection *sel) { + return sel->rows; +} + +void ui_list_selection_free(UiListSelection *sel) { + ui_listselection_free(*sel); + free(sel); +} + +/* ---------------------------- UiFileList ---------------------------- */ + +int ui_filelist_count(UiFileList *flist) { + return flist->nfiles; +} + +char* ui_filelist_get(UiFileList *flist, int index) { + if(index >= 0 && index < flist->nfiles) { + return flist->files[index]; + } + return NULL; +}
--- a/ui/common/wrapper.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/common/wrapper.h Sun Aug 24 15:24:16 2025 +0200 @@ -74,6 +74,13 @@ UIEXPORT int ui_event_get_int(UiEvent *event); UIEXPORT int ui_event_get_set(UiEvent *event); +UIEXPORT UiListSelection* ui_list_get_selection_allocated(UiList *list); +UIEXPORT int ui_list_selection_get_count(UiListSelection *sel); +UIEXPORT int* ui_list_selection_get_rows(UiListSelection *sel); +UIEXPORT void ui_list_selection_free(UiListSelection *sel); + +UIEXPORT int ui_filelist_count(UiFileList *flist); +UIEXPORT char* ui_filelist_get(UiFileList *flist, int index); #ifdef __cplusplus
--- a/ui/gtk/button.c Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/gtk/button.c Sun Aug 24 15:24:16 2025 +0200 @@ -32,6 +32,8 @@ #include "button.h" #include "container.h" #include <cx/allocator.h> +#include <cx/buffer.h> +#include <cx/json.h> #include "../common/context.h" #include "../common/object.h" @@ -120,6 +122,14 @@ event->callback(&e, event->userdata); } +void ui_button_set_label(UIWIDGET button, const char *label) { + gtk_button_set_label(GTK_BUTTON(button), label); +} + +void ui_button_set_icon(UIWIDGET button, const char *icon) { + ui_button_set_icon_name(button, icon); +} + int64_t ui_toggle_button_get(UiInteger *integer) { GtkToggleButton *button = integer->obj; integer->value = (int)gtk_toggle_button_get_active(button); @@ -594,3 +604,280 @@ value->value = i; } #endif + + +static void ui_destroy_linkbutton(GtkWidget *widget, UiLinkButton *data) { + free(data->link); + free(data); +} + +static const char* linkbutton_get_uri(UiLinkButton *link) { + if(link->type == UI_LINK_BUTTON) { + return link->link; + } else { + return gtk_link_button_get_uri(GTK_LINK_BUTTON(link->widget)); + } +} + +static void linkbutton_set_uri(UiLinkButton *link, const char *uri) { + if(link->type == UI_LINK_BUTTON) { + free(link->link); + link->link = uri ? strdup(uri) : NULL; + } else { + gtk_link_button_set_uri(GTK_LINK_BUTTON(link->widget), uri); + } +} + +static gboolean linkbutton_get_visited(UiLinkButton *link) { + if(link->type == UI_LINK_BUTTON) { + return FALSE; + } else { + gtk_link_button_get_visited(GTK_LINK_BUTTON(link->widget)); + } +} + +static void linkbutton_set_visited(UiLinkButton *link, gboolean visited) { + if(link->type != UI_LINK_BUTTON) { + gtk_link_button_set_visited(GTK_LINK_BUTTON(link->widget), visited); + } +} + +/* + * Apply linkbutton settings from json. Expects jsonvalue to be a valid + * json object. + * + * { + * "label": "label text", + * "uri": "http://example.com", + * "visited": true + * } + * + */ +static void linkbutton_apply_value(UiLinkButton *link, const char *jsonvalue) { + CxJson json; + cxJsonInit(&json, NULL); + cxJsonFill(&json, jsonvalue); + + CxJsonValue *value; + if(cxJsonNext(&json, &value) == CX_JSON_NO_ERROR) { + if(cxJsonIsObject(value)) { + CxJsonValue *label = cxJsonObjGet(value, "label"); + CxJsonValue *uri = cxJsonObjGet(value, "uri"); + CxJsonValue *visited = cxJsonObjGet(value, "visited"); + if(label) { + gtk_button_set_label(GTK_BUTTON(link->widget), cxJsonIsString(label) ? cxJsonAsString(label) : NULL); + } + if(uri) { + linkbutton_set_uri(link, cxJsonIsString(uri) ? cxJsonAsString(uri) : NULL); + + } + if(visited) { + linkbutton_set_visited(link, cxJsonIsBool(visited) ? cxJsonAsBool(visited) : FALSE); + } + } + cxJsonValueFree(value); + } + cxJsonDestroy(&json); +} + +static char* create_linkbutton_jsonvalue(const char *label, const char *uri, gboolean include_null, gboolean visited, gboolean set_visited) { + CxJsonValue *obj = cxJsonCreateObj(NULL); + if(label) { + cxJsonObjPutString(obj, CX_STR("label"), label); + } else if(include_null) { + cxJsonObjPutLiteral(obj, CX_STR("label"), CX_JSON_NULL); + } + + if(uri) { + cxJsonObjPutString(obj, CX_STR("uri"), uri); + } else if(include_null) { + cxJsonObjPutLiteral(obj, CX_STR("uri"), CX_JSON_NULL); + } + + if(set_visited) { + cxJsonObjPutLiteral(obj, CX_STR("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE); + } + + CxJsonWriter writer = cxJsonWriterCompact(); + CxBuffer buf; + cxBufferInit(&buf, NULL, 128, NULL, CX_BUFFER_AUTO_EXTEND); + cxJsonWrite(&buf, obj, (cx_write_func)cxBufferWrite, &writer); + cxJsonValueFree(obj); + cxBufferTerminate(&buf); + + return buf.space; +} + +static char* linkbutton_get_value(UiLinkButton *link) { + const char *label = gtk_button_get_label(GTK_BUTTON(link->widget)); + const char *uri = linkbutton_get_uri(link); + gboolean visited = linkbutton_get_visited(link); + return create_linkbutton_jsonvalue(label, uri, TRUE, visited, TRUE); +} + +static void linkbutton_clicked(GtkWidget *widget, UiLinkButton *data) { + if(data->onclick) { + UiEvent e; + e.obj = data->obj; + e.document = e.obj->ctx->document; + e.window = e.obj->window; + e.eventdata = (char*)linkbutton_get_uri(data); + e.eventdatatype = UI_EVENT_DATA_STRING; + e.intval = 0; + e.set = ui_get_setop(); + data->onclick(&e, data->onclickdata); + } +} + +static gboolean linkbutton_activate_link(GtkLinkButton *self, UiLinkButton *data) { + linkbutton_clicked(data->widget, data); + return data->nofollow; +} + +UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args) { + UiLinkButton *data = malloc(sizeof(UiLinkButton)); + memset(data, 0, sizeof(UiLinkButton)); + data->obj = obj; + data->type = args->type; + data->nofollow = args->nofollow; + data->onclick = args->onclick; + data->onclickdata = args->onclickdata; + + GtkWidget *button; + if(args->type == UI_LINK_BUTTON) { + button = gtk_button_new(); + g_signal_connect( + button, + "clicked", + G_CALLBACK(linkbutton_clicked), + data); + } else { + button = gtk_link_button_new("file:///"); + g_signal_connect( + button, + "activate-link", + G_CALLBACK(linkbutton_activate_link), + data); + } + gtk_button_set_label(GTK_BUTTON(button), args->label); + g_object_set_data(G_OBJECT(button), "ui_linkbutton", data); + g_signal_connect( + button, + "destroy", + G_CALLBACK(ui_destroy_linkbutton), + data); + + data->widget = button; + + UiObject* current = uic_current_obj(obj); + UiVar *var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_STRING); + if(var) { + UiString *str = var->value; + char *current_value = ui_get(str); + if(current_value) { + linkbutton_apply_value(data, current_value); + } + + str->obj = data; + str->get = ui_linkbutton_get; + str->set = ui_linkbutton_set; + } + + ui_set_name_and_style(button, args->name, args->style_class); + ui_set_widget_groups(obj->ctx, button, args->groups); + UI_APPLY_LAYOUT2(current, args); + current->container->add(current->container, button); + + return button; +} + +char* ui_linkbutton_get(UiString *s) { + UiLinkButton *link = s->obj; + if(s->value.free) { + s->value.free(s->value.ptr); + } + s->value.ptr = linkbutton_get_value(link); + s->value.free = free; + return s->value.ptr; +} + +void ui_linkbutton_set(UiString *s, const char *str) { + linkbutton_apply_value(s->obj, str); + if(s->value.free) { + s->value.free(s->value.ptr); + } +} + + +void ui_linkbutton_value_set(UiString *str, const char *label, const char *uri) { + char *value = create_linkbutton_jsonvalue(label, uri, TRUE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_label(UiString *str, const char *label) { + char *value = create_linkbutton_jsonvalue(label, NULL, FALSE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_uri(UiString *str, const char *uri) { + char *value = create_linkbutton_jsonvalue(NULL, uri, FALSE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_visited(UiString *str, UiBool visited) { + char *value = create_linkbutton_jsonvalue(NULL, NULL, FALSE, visited, TRUE); + ui_set(str, value); + free(value); +} + + +void ui_linkbutton_set_label(UIWIDGET button, const char *label) { + gtk_button_set_label(GTK_BUTTON(button), label); +} + +void ui_linkbutton_set_uri(UIWIDGET button, const char *label) { + UiLinkButton *link = g_object_get_data(G_OBJECT(button), "ui_linkbutton"); + if(link) { + linkbutton_set_uri(link, label); + } else { + fprintf(stderr, "Error: ui_linkbutton_set_label: widget is not a linkbutton\n"); + } +} + +void ui_linkbutton_set_visited(UIWIDGET button, UiBool visited) { + UiLinkButton *link = g_object_get_data(G_OBJECT(button), "ui_linkbutton"); + if(link) { + linkbutton_set_visited(link, visited); + } else { + fprintf(stderr, "Error: ui_linkbutton_set_label: widget is not a linkbutton\n"); + } +} + +char* ui_linkbutton_get_label(UIWIDGET button) { + const char *label = gtk_button_get_label(GTK_BUTTON(button)); + return label ? strdup(label) : NULL; +} + +char* ui_linkbutton_get_uri(UIWIDGET button) { + UiLinkButton *link = g_object_get_data(G_OBJECT(button), "ui_linkbutton"); + if(link) { + const char *uri = linkbutton_get_uri(link); + return uri ? strdup(uri) : NULL; + } else { + fprintf(stderr, "Error: ui_linkbutton_set_label: widget is not a linkbutton\n"); + } + return NULL; +} + +UiBool ui_linkbutton_get_visited(UIWIDGET button) { + UiLinkButton *link = g_object_get_data(G_OBJECT(button), "ui_linkbutton"); + if(link) { + return linkbutton_get_visited(link); + } else { + fprintf(stderr, "Error: ui_linkbutton_set_label: widget is not a linkbutton\n"); + } + return FALSE; +}
--- a/ui/gtk/button.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/gtk/button.h Sun Aug 24 15:24:16 2025 +0200 @@ -37,6 +37,16 @@ extern "C" { #endif +typedef struct UiLinkButton { + UiObject *obj; + GtkWidget *widget; + UiLinkType type; + UiBool nofollow; + char *link; + ui_callback onclick; + void *onclickdata; +} UiLinkButton; + void ui_button_set_icon_name(GtkWidget *button, const char *icon_name); typedef void (*ui_toggled_func)(void*, void*); @@ -89,6 +99,9 @@ int64_t ui_radiobutton_get(UiInteger *value); void ui_radiobutton_set(UiInteger *value, int64_t i); +char* ui_linkbutton_get(UiString *s); +void ui_linkbutton_set(UiString *s, const char *str); + #ifdef __cplusplus } #endif
--- a/ui/gtk/display.c Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/gtk/display.c Sun Aug 24 15:24:16 2025 +0200 @@ -101,7 +101,7 @@ case UI_ALIGN_DEFAULT: break; case UI_ALIGN_LEFT: set_alignment(widget, 0, .5); break; case UI_ALIGN_RIGHT: set_alignment(widget, 1, .5); break; - case UI_ALIGN_CENTER: break; // TODO + case UI_ALIGN_CENTER: set_alignment(widget, .5, .5); break; }
--- a/ui/gtk/headerbar.c Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/gtk/headerbar.c Sun Aug 24 15:24:16 2025 +0200 @@ -162,9 +162,10 @@ gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(menubutton), G_MENU_MODEL(menu)); #else GtkWidget *menubutton = gtk_menu_button_new(); - - // TODO - + GtkWidget *menu = gtk_menu_new(); + ui_add_menu_items(menu, 0, &item->menu, obj); + gtk_widget_show_all(menu); + gtk_menu_button_set_popup(GTK_MENU_BUTTON(menubutton), menu); #endif
--- a/ui/gtk/list.c Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/gtk/list.c Sun Aug 24 15:24:16 2025 +0200 @@ -39,21 +39,22 @@ #include <cx/linked_list.h> #include "list.h" +#include "button.h" #include "icon.h" #include "menu.h" #include "dnd.h" -void* ui_strmodel_getvalue(void *elm, int column) { - return column == 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* model_getvalue(UiModel *model, UiList *list, void *elm, int row, int col, UiBool *freeResult) { - if(model->getvalue2) { - return model->getvalue2(list, elm, row, col, model->getvalue2data, freeResult); - } else if(model->getvalue) { - return model->getvalue(elm, col); - } +static void* str_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + return elm; +} + +static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { return NULL; } @@ -74,6 +75,37 @@ } } +static UiListView* create_listview(UiObject *obj, UiListArgs *args) { + UiListView *tableview = malloc(sizeof(UiListView)); + memset(tableview, 0, sizeof(UiListView)); + tableview->obj = obj; + tableview->model = args->model; + tableview->onactivate = args->onactivate; + tableview->onactivatedata = args->onactivatedata; + tableview->onselection = args->onselection; + tableview->onselectiondata = args->onselectiondata; + tableview->ondragstart = args->ondragstart; + tableview->ondragstartdata = args->ondragstartdata; + tableview->ondragcomplete = args->ondragcomplete; + tableview->ondragcompletedata = args->ondragcompletedata; + tableview->ondrop = args->ondrop; + tableview->ondropdata = args->ondropdata; + tableview->selection.count = 0; + tableview->selection.rows = NULL; + + if(args->getvalue2) { + tableview->getvalue = args->getvalue2; + tableview->getvaluedata = args->getvalue2data; + } else if(args->getvalue) { + tableview->getvalue = getvalue_wrapper; + tableview->getvaluedata = (void*)args->getvalue; + } else { + tableview->getvalue = null_getvalue; + } + + return tableview; +} + #if GTK_CHECK_VERSION(4, 10, 0) @@ -131,16 +163,17 @@ } } -static void column_factory_bind( GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { +static void column_factory_bind(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { UiColData *col = userdata; UiList *list = col->listview->var ? col->listview->var->value : NULL; + UiListView *listview = col->listview; ObjWrapper *obj = gtk_list_item_get_item(item); UiModel *model = col->listview->model; UiModelType type = model->types[col->model_column]; UiBool freevalue = FALSE; - void *data = model_getvalue(model, list, obj->data, obj->i, col->data_column, &freevalue); + void *data = listview->getvalue(list, obj->data, obj->i, col->data_column, listview->getvaluedata, &freevalue); GtkWidget *child = gtk_list_item_get_child(item); switch(type) { @@ -172,7 +205,7 @@ } case UI_ICON_TEXT_FREE: { - void *data2 = model_getvalue(model, list, obj->data, obj->i, col->data_column+1, &freevalue); + void *data2 = listview->getvalue(list, obj->data, obj->i, col->data_column+1, listview->getvaluedata, &freevalue); if(type == UI_ICON_TEXT_FREE) { freevalue = TRUE; } @@ -199,55 +232,32 @@ selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(liststore))); } else { selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(liststore))); + gtk_single_selection_set_can_unselect(GTK_SINGLE_SELECTION(selection_model), TRUE); + gtk_single_selection_set_autoselect(GTK_SINGLE_SELECTION(selection_model), FALSE); } g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), listview); return selection_model; } -static UiListView* create_listview(UiObject *obj, UiListArgs *args) { - UiListView *tableview = malloc(sizeof(UiListView)); - memset(tableview, 0, sizeof(UiListView)); - tableview->obj = obj; - tableview->model = args->model; - tableview->onactivate = args->onactivate; - tableview->onactivatedata = args->onactivatedata; - tableview->onselection = args->onselection; - tableview->onselectiondata = args->onselectiondata; - tableview->ondragstart = args->ondragstart; - tableview->ondragstartdata = args->ondragstartdata; - tableview->ondragcomplete = args->ondragcomplete; - tableview->ondragcompletedata = args->ondragcompletedata; - tableview->ondrop = args->ondrop; - tableview->ondropdata = args->ondropdata; - tableview->selection.count = 0; - tableview->selection.rows = NULL; - return tableview; -} - UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { UiObject* current = uic_current_obj(obj); // to simplify things and share code with ui_table_create, we also // use a UiModel for the listview UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); - if(args->getvalue2) { - model->getvalue2 = args->getvalue2; - model->getvalue2data = args->getvalue2data; - } else if(args->getvalue) { - model->getvalue = args->getvalue; - } else { - model->getvalue = ui_strmodel_getvalue; - } args->model = model; GListStore *ls = g_list_store_new(G_TYPE_OBJECT); UiListView *listview = create_listview(obj, args); + if(!args->getvalue && !args->getvalue2) { + listview->getvalue = str_getvalue; + } listview->columns = malloc(sizeof(UiColData)); listview->columns->listview = listview; listview->columns->data_column = 0; listview->columns->model_column = 0; - + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); @@ -280,7 +290,7 @@ ui_update_liststore(ls, list); } else if (args->static_elements && args->static_nelm > 0) { listview_copy_static_elements(listview, args->static_elements, args->static_nelm); - listview->model->getvalue = ui_strmodel_getvalue; // force strmodel + listview->getvalue = str_getvalue; // force string values ui_update_liststore_static(ls, listview->elements, listview->nelm); } @@ -320,19 +330,15 @@ // to simplify things and share code with ui_tableview_create, we also // use a UiModel for the listview UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); - if(args->getvalue2) { - model->getvalue2 = args->getvalue2; - model->getvalue2data = args->getvalue2data; - } else if(args->getvalue) { - model->getvalue = args->getvalue; - } else { - model->getvalue = ui_strmodel_getvalue; - } args->model = model; GListStore *ls = g_list_store_new(G_TYPE_OBJECT); UiListView *listview = create_listview(obj, args); + if(!args->getvalue && !args->getvalue2) { + listview->getvalue = str_getvalue; + } + listview->columns = malloc(sizeof(UiColData)); listview->columns->listview = listview; listview->columns->data_column = 0; @@ -370,7 +376,7 @@ ui_update_liststore(ls, list); } else if (args->static_elements && args->static_nelm > 0) { listview_copy_static_elements(listview, args->static_elements, args->static_nelm); - listview->model->getvalue = ui_strmodel_getvalue; // force strmodel + listview->getvalue = str_getvalue; // force string values ui_update_liststore_static(ls, listview->elements, listview->nelm); } @@ -626,14 +632,20 @@ void *value = list->get(list, i); if(value) { ObjWrapper *obj = obj_wrapper_new(value, i); + UiListSelection sel = list->getselection(list); // TODO: if index i is selected, the selection is lost // is it possible to update the item without removing it? + // workaround: save selection and reapply it int count = g_list_model_get_n_items(G_LIST_MODEL(view->liststore)); if(count <= i) { g_list_store_splice(view->liststore, i, 0, (void **)&obj, 1); } else { g_list_store_splice(view->liststore, i, 1, (void **)&obj, 1); } + if(sel.count > 0) { + list->setselection(list, sel); + } + ui_listselection_free(sel); } } } @@ -695,12 +707,13 @@ #else -static void update_list_row(GtkListStore *store, GtkTreeIter *iter, UiModel *model, UiList *list, void *elm, int row) { +static void update_list_row(UiListView *listview, GtkListStore *store, GtkTreeIter *iter, UiList *list, void *elm, int row) { + UiModel *model = listview->model; // set column values int c = 0; for(int i=0;i<model->columns;i++,c++) { UiBool freevalue = FALSE; - void *data = model_getvalue(model, list, elm, row, c, &freevalue); + void *data = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue); GValue value = G_VALUE_INIT; switch(model->types[i]) { @@ -765,7 +778,7 @@ c++; freevalue = FALSE; - char *str = model_getvalue(model, list, elm, row, c, &freevalue); + char *str = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue); g_value_init(&value, G_TYPE_STRING); g_value_set_string(&value, str); if(model->types[i] == UI_ICON_TEXT_FREE || freevalue) { @@ -779,7 +792,8 @@ } } -static GtkListStore* create_list_store(UiList *list, UiModel *model) { +static GtkListStore* create_list_store(UiListView *listview, UiList *list) { + UiModel *model = listview->model; int columns = model->columns; GType types[2*columns]; int c = 0; @@ -807,7 +821,7 @@ GtkTreeIter iter; gtk_list_store_insert (store, &iter, -1); - update_list_row(store, &iter, model, list, elm, i++); + update_list_row(listview, store, &iter, list, elm, i++); // next row elm = list->next(list); @@ -841,36 +855,29 @@ #endif UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); - if(args->getvalue2) { - model->getvalue2 = args->getvalue2; - model->getvalue2data = args->getvalue2data; - } else if(args->getvalue) { - model->getvalue = args->getvalue; - } else { - model->getvalue = ui_strmodel_getvalue; + + UiListView *listview = create_listview(obj, args); + if(!args->getvalue && !args->getvalue2) { + listview->getvalue = str_getvalue; } - - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST); - - UiList *list = var ? var->value : NULL; - GtkListStore *listmodel = create_list_store(list, model); - gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); - g_object_unref(listmodel); - - UiListView *listview = malloc(sizeof(UiListView)); - memset(listview, 0, sizeof(UiListView)); - listview->obj = obj; - listview->widget = view; - listview->var = var; listview->model = model; - listview->selection.count = 0; - listview->selection.rows = NULL; g_signal_connect( view, "destroy", G_CALLBACK(ui_listview_destroy), listview); + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST); + + // init listview + listview->widget = view; + listview->var = var; + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(listview, list); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); + g_object_unref(listmodel); + // bind var list->update = ui_listview_update; list->getselection = ui_listview_getselection; @@ -921,7 +928,7 @@ SCROLLEDWINDOW_SET_CHILD(scroll_area, view); UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, scroll_area, FALSE); + current->container->add(current->container, scroll_area); // ct->current should point to view, not scroll_area, to make it possible // to add a context menu @@ -1006,36 +1013,23 @@ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST); - UiList *list = var ? var->value : NULL; - GtkListStore *listmodel = create_list_store(list, model); - gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); - g_object_unref(listmodel); - //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL); //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL); // add TreeView as observer to the UiList to update the TreeView if the // data changes - UiListView *tableview = malloc(sizeof(UiListView)); - memset(tableview, 0, sizeof(UiListView)); - tableview->obj = obj; - tableview->widget = view; - tableview->var = var; - tableview->model = model; - tableview->ondragstart = args->ondragstart; - tableview->ondragstartdata = args->ondragstartdata; - tableview->ondragcomplete = args->ondragcomplete; - tableview->ondragcompletedata = args->ondragcompletedata; - tableview->ondrop = args->ondrop; - tableview->ondropdata = args->ondropdata; - tableview->selection.count = 0; - tableview->selection.rows = NULL; + UiListView *tableview = create_listview(obj, args); g_signal_connect( view, "destroy", G_CALLBACK(ui_listview_destroy), tableview); + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(tableview, list); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); + g_object_unref(listmodel); + // bind var list->update = ui_listview_update; list->getselection = ui_listview_getselection; @@ -1098,7 +1092,7 @@ } UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, scroll_area, FALSE); + current->container->add(current->container, scroll_area); // ct->current should point to view, not scroll_area, to make it possible // to add a context menu @@ -1112,7 +1106,7 @@ void ui_listview_update(UiList *list, int i) { UiListView *view = list->obj; if(i < 0) { - GtkListStore *store = create_list_store(list, view->model); + GtkListStore *store = create_list_store(view, list); gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); g_object_unref(G_OBJECT(store)); } else { @@ -1120,7 +1114,7 @@ GtkTreeModel *store = gtk_tree_view_get_model(GTK_TREE_VIEW(view->widget)); GtkTreeIter iter; if(gtk_tree_model_iter_nth_child(store, &iter, NULL, i)) { - update_list_row(GTK_LIST_STORE(store), &iter, view->model, list, elm, i); + update_list_row(view, GTK_LIST_STORE(store), &iter, list, elm, i); } } } @@ -1150,46 +1144,41 @@ UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { UiObject* current = uic_current_obj(obj); - UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); - if(args->getvalue2) { - model->getvalue2 = args->getvalue2; - model->getvalue2data = args->getvalue2data; - } else if(args->getvalue) { - model->getvalue = args->getvalue; - } else { - model->getvalue = ui_strmodel_getvalue; - } + GtkWidget *combobox = gtk_combo_box_new(); - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST); - - GtkWidget *combobox = ui_create_combobox(obj, model, var, args->static_elements, args->static_nelm, args->onactivate, args->onactivatedata); ui_set_name_and_style(combobox, args->name, args->style_class); ui_set_widget_groups(obj->ctx, combobox, args->groups); UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, combobox, FALSE); + current->container->add(current->container, combobox); current->container->current = combobox; - return combobox; -} - -GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, char **elm, size_t nelm, ui_callback f, void *udata) { - GtkWidget *combobox = gtk_combo_box_new(); - - UiListView *uicbox = malloc(sizeof(UiListView)); - memset(uicbox, 0, sizeof(UiListView)); - uicbox->obj = obj; - uicbox->widget = combobox; + + UiListView *listview = create_listview(obj, args); + listview->widget = combobox; + listview->model = ui_model(obj->ctx, UI_STRING, "", -1); + g_signal_connect( + combobox, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST); UiList *list = var ? var->value : NULL; - GtkListStore *listmodel = create_list_store(list, model); - - if(!list && elm && nelm > 0) { - listview_copy_static_elements(uicbox, elm, nelm); - for(int i=0;i<nelm;i++) { + GtkListStore *listmodel = create_list_store(listview, list); + if(var) { + listview->var = var; + list->update = ui_combobox_modelupdate; + list->getselection = ui_combobox_getselection; + list->setselection = ui_combobox_setselection; + list->obj = listview; + list->update(list, -1); + } else if(args->static_nelm > 0) { + listview_copy_static_elements(listview, args->static_elements, args->static_nelm); + for(int i=0;i<args->static_nelm;i++) { GtkTreeIter iter; GValue value = G_VALUE_INIT; gtk_list_store_insert(listmodel, &iter, -1); g_value_init(&value, G_TYPE_STRING); - g_value_set_string(&value, uicbox->elements[i]); + g_value_set_string(&value, listview->elements[i]); gtk_list_store_set_value(listmodel, &iter, 0, &value); } } @@ -1199,23 +1188,6 @@ g_object_unref(listmodel); } - uicbox->var = var; - uicbox->model = model; - - g_signal_connect( - combobox, - "destroy", - G_CALLBACK(ui_combobox_destroy), - uicbox); - - // bind var - if(list) { - list->update = ui_combobox_modelupdate; - list->getselection = ui_combobox_getselection; - list->setselection = ui_combobox_setselection; - list->obj = uicbox; - } - GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); gtk_cell_layout_set_attributes( @@ -1227,13 +1199,13 @@ gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); // add callback - if(f) { + if(args->onactivate) { UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); event->obj = obj; - event->userdata = udata; - event->callback = f; + event->userdata = args->onactivatedata; + event->callback = args->onactivate; event->value = 0; - event->customdata = uicbox; + event->customdata = listview; g_signal_connect( combobox, @@ -1268,7 +1240,7 @@ void ui_combobox_modelupdate(UiList *list, int i) { UiListView *view = list->obj; - GtkListStore *store = create_list_store(view->var->value, view->model); + GtkListStore *store = create_list_store(view, list); gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); g_object_unref(store); } @@ -1709,19 +1681,6 @@ free(v); } -void ui_combobox_destroy(GtkWidget *w, UiListView *v) { - if(v->var) { - ui_destroy_boundvar(v->obj->ctx, v->var); - } - if(v->elements) { - for(int i=0;i<v->nelm;i++) { - free(v->elements[i]); - } - free(v->elements); - } - free(v); -} - /* ------------------------------ Source List ------------------------------ */ @@ -1801,16 +1760,16 @@ uisublist.listbox = uilistbox; uisublist.userdata = sublist->userdata; uisublist.index = cxListSize(sublists); - + uisublist.startpos = 0; + cxListAdd(sublists, &uisublist); + // bind UiList UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(sublists)-1); - UiList *list = uisublist.var->value; - if(list) { + if(uisublist.var && uisublist.var->value) { + UiList *list = uisublist.var->value; list->obj = sublist_ptr; list->update = ui_listbox_list_update; } - - cxListAdd(sublists, &uisublist); } UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) { @@ -1883,6 +1842,11 @@ g_object_set_data(G_OBJECT(scroll_area), "ui_listbox", uilistbox); g_object_set_data(G_OBJECT(listbox), "ui_listbox", uilistbox); + if(args->contextmenu) { + UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, listbox); + ui_widget_set_contextmenu(listbox, menu); + } + // signals g_signal_connect( listbox, @@ -1952,11 +1916,37 @@ } // reload sublist + sublist->startpos = pos; ui_listbox_update_sublist(listbox, sublist, pos); pos += sublist->numitems; } } +static void listbox_button_clicked(GtkWidget *widget, UiEventDataExt *data) { + UiListBoxSubList *sublist = data->customdata0; + + UiSubListEventData eventdata; + eventdata.list = sublist->var->value; + eventdata.sublist_index = sublist->index; + eventdata.row_index = data->value0; + eventdata.sublist_userdata = sublist->userdata; + eventdata.row_data = eventdata.list->get(eventdata.list, eventdata.row_index); + eventdata.event_data = data->customdata2; + + UiEvent event; + event.obj = data->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &eventdata; + event.eventdatatype = UI_EVENT_DATA_SUBLIST; + event.intval = data->value0; + event.set = ui_get_setop(); + + if(data->callback2) { + data->callback2(&event, data->userdata2); + } +} + static GtkWidget* create_listbox_row(UiListBox *listbox, UiListBoxSubList *sublist, UiSubListItem *item, int index) { GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); if(item->icon) { @@ -1966,7 +1956,9 @@ GtkWidget *label = gtk_label_new(item->label); gtk_widget_set_halign(label, GTK_ALIGN_START); BOX_ADD_EXPAND(hbox, label); - // TODO: badge, button + if(item->badge) { + + } GtkWidget *row = gtk_list_box_row_new(); LISTBOX_ROW_SET_CHILD(row, hbox); @@ -1991,6 +1983,34 @@ g_object_set_data(G_OBJECT(row), "ui-listbox-row-eventdata", event); + // badge + if(item->badge) { + GtkWidget *badge = gtk_label_new(item->badge); + WIDGET_ADD_CSS_CLASS(badge, "ui-badge"); +#if GTK_CHECK_VERSION(4, 0, 0) + gtk_widget_set_valign(badge, GTK_ALIGN_CENTER); + BOX_ADD(hbox, badge); +#else + GtkWidget *align = gtk_alignment_new(0.5, 0.5, 0, 0); + gtk_container_add(GTK_CONTAINER(align), badge); + BOX_ADD(hbox, align); +#endif + } + // button + if(item->button_icon || item->button_label) { + GtkWidget *button = gtk_button_new(); + gtk_button_set_label(GTK_BUTTON(button), item->button_label); + ui_button_set_icon_name(button, item->button_icon); + WIDGET_ADD_CSS_CLASS(button, "flat"); + BOX_ADD(hbox, button); + g_signal_connect( + button, + "clicked", + G_CALLBACK(listbox_button_clicked), + event + ); + } + return row; } @@ -2005,6 +2025,9 @@ sublist->numitems = 0; // create items for each UiList element + if(!sublist->var) { + return; + } UiList *list = sublist->var->value; if(!list) { return; @@ -2012,6 +2035,20 @@ size_t index = 0; void *elm = list->first(list); + + if(!elm && sublist->header) { + // empty row for header + GtkWidget *row = gtk_list_box_row_new(); + cxListAdd(sublist->widgets, row); + g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); + g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); + intptr_t rowindex = listbox_insert_index + index; + g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); + gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index); + sublist->numitems = 1; + return; + } + while(elm) { UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; if(listbox->getvalue) { @@ -2055,6 +2092,14 @@ void ui_listbox_list_update(UiList *list, int i) { UiListBoxSubList *sublist = list->obj; + ui_listbox_update_sublist(sublist->listbox, sublist, sublist->startpos); + size_t pos = 0; + CxIterator it = cxListIterator(sublist->listbox->sublists); + cx_foreach(UiListBoxSubList *, ls, it) { + ls->startpos = pos; + pos += sublist->numitems; + } + } void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) {
--- a/ui/gtk/list.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/gtk/list.h Sun Aug 24 15:24:16 2025 +0200 @@ -45,6 +45,8 @@ GtkWidget *widget; UiVar *var; UiModel *model; + ui_getvaluefunc2 getvalue; + void *getvaluedata; char **elements; size_t nelm; #if GTK_CHECK_VERSION(4, 10, 0) @@ -91,6 +93,7 @@ UiListBox *listbox; void *userdata; size_t index; + size_t startpos; } UiListBoxSubList; struct UiListBox {
--- a/ui/gtk/text.c Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/gtk/text.c Sun Aug 24 15:24:16 2025 +0200 @@ -1118,7 +1118,7 @@ pathtf); UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, eventbox, FALSE); + current->container->add(current->container, eventbox); // hbox as parent for the GtkEntry and GtkButtonBox GtkWidget *hbox = ui_gtk_hbox_new(0);
--- a/ui/gtk/toolkit.c Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/gtk/toolkit.c Sun Aug 24 15:24:16 2025 +0200 @@ -395,6 +395,15 @@ " margin-top: 4px;\n" " margin-bottom: 10px;\n" "}\n" +".ui-badge {\n" +" background-color: #e53935;\n" +" color: white;\n" +" border-radius: 9999px;\n" +" padding: 0px 10px 0px 10px;\n" +" font-weight: bold;\n" +" margin-left: 4px;" +" margin-right: 4px;" +"}\n" ; #elif GTK_MAJOR_VERSION == 3 @@ -428,6 +437,15 @@ " margin-top: 4px;\n" " margin-bottom: 10px;\n" "}\n" +".ui-badge {\n" +" background-color: #e53935;\n" +" color: white;\n" +" border-radius: 9999px;\n" +" padding: 0px 10px 0px 10px;\n" +" font-weight: bold;\n" +" margin-left: 4px;" +" margin-right: 4px;" +"}\n" ; #endif
--- a/ui/gtk/webview.c Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/gtk/webview.c Sun Aug 24 15:24:16 2025 +0200 @@ -55,7 +55,7 @@ value->set = ui_webview_set; value->obj = data; if(value->value && value->type && !strcmp(value->type, UI_WEBVIEW_OBJECT_TYPE)) { - // TODO + ui_webview_set(value, value->value, UI_WEBVIEW_OBJECT_TYPE); } }
--- a/ui/gtk/window.c Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/gtk/window.c Sun Aug 24 15:24:16 2025 +0200 @@ -313,7 +313,6 @@ evt.window = evt.obj->window; evt.eventdata = NULL; evt.eventdatatype = 0; - evt.eventdatatype = 0; evt.intval = 0; if(!strcmp(response, "btn1")) { @@ -410,7 +409,7 @@ WINDOW_DESTROY(GTK_WIDGET(self)); } -void ui_dialog_create(UiObject *parent, UiDialogArgs args) { +void ui_dialog_create(UiObject *parent, UiDialogArgs *args) { GtkDialog *dialog = GTK_DIALOG(gtk_dialog_new()); gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget)); gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
--- a/ui/qt/button.cpp Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/qt/button.cpp Sun Aug 24 15:24:16 2025 +0200 @@ -48,6 +48,16 @@ return button; } +void ui_button_set_label(UIWIDGET button, const char *label) { + QString str = QString::fromUtf8(label); + QAbstractButton *b = (QAbstractButton*)button; + b->setText(str); +} + +void ui_button_set_icon(UIWIDGET button, const char *icon) { + // TODO +} + static void togglebutton_event(UiEvent *event, UiEventWrapper *wrapper) { QPushButton *button = (QPushButton*)wrapper->customdata1; event->intval = button->isChecked();
--- a/ui/qt/container.cpp Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/qt/container.cpp Sun Aug 24 15:24:16 2025 +0200 @@ -35,6 +35,7 @@ #include <QSpacerItem> #include <QStackedWidget> #include <QLabel> +#include <QDockWidget> static void delete_container(UiContainerPrivate *ct) { delete ct; @@ -228,7 +229,7 @@ } } -UIEXPORT UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { +UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { UiContainerPrivate *ctn = (UiContainerPrivate*)ui_obj_container(obj); UI_APPLY_LAYOUT(ctn->layout, args); @@ -251,6 +252,25 @@ } +/* ---------------------------- UiSidebar ---------------------------- */ + +UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) { + QVariant v = obj->widget->property("ui_sidebar"); + QDockWidget *dock = (QDockWidget*)v.value<void*>(); + if(!dock) { + fprintf(stderr, "Error: window is not configured for sidebar\n"); + return nullptr; + } + + QWidget *widget = new QWidget(); + QBoxLayout *box = new QBoxLayout(QBoxLayout::TopToBottom); + widget->setLayout(box); + dock->setWidget(widget); + + ui_container_add(obj, new UiBoxContainer(box)); + + return dock; +} /* -------------------- Container Helper Functions -------------------- */
--- a/ui/qt/list.cpp Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/qt/list.cpp Sun Aug 24 15:24:16 2025 +0200 @@ -42,6 +42,10 @@ return getvalue(elm, col); } +static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + return NULL; +} + UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); UI_APPLY_LAYOUT(ctn->layout, args); @@ -102,7 +106,17 @@ UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); - TableModel *model = new TableModel(obj, view, var, args->model); + ui_getvaluefunc2 getvalue = args->getvalue2; + void *getvaluedata = args->getvalue2data; + if(!getvalue) { + if(args->getvalue) { + getvalue = getvalue_wrapper; + getvaluedata = (void*)args->getvalue; + } else { + getvalue = null_getvalue; + } + } + TableModel *model = new TableModel(obj, view, var, args->model, getvalue, getvaluedata); view->setModel(model); if(var) {
--- a/ui/qt/model.cpp Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/qt/model.cpp Sun Aug 24 15:24:16 2025 +0200 @@ -28,14 +28,6 @@ #include "model.h" -static void* model_getvalue(UiModel *model, UiList *list, void *elm, int row, int col, UiBool *freeResult) { - if(model->getvalue2) { - return model->getvalue2(list, elm, row, col, model->getvalue2data, freeResult); - } else if(model->getvalue) { - return model->getvalue(elm, col); - } - return NULL; -} ListModel::ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata){ this->obj = obj; @@ -116,11 +108,13 @@ -TableModel::TableModel(UiObject *obj, QTreeView *view, UiVar *var, UiModel *model){ +TableModel::TableModel(UiObject *obj, QTreeView *view, UiVar *var, UiModel *model, ui_getvaluefunc2 getvalue, void *getvaluedata){ this->obj = obj; this->view = view; this->var = var; this->model = model; + this->getvalue = getvalue; + this->getvaluedata = getvaluedata; this->onactivate = nullptr; this->onactivatedata = nullptr; this->onselection = nullptr; @@ -162,7 +156,7 @@ if(rowData) { int col = index.column(); UiBool freeResult = false; - void *value = model_getvalue(model, ls, rowData, index.row(), col, &freeResult); + void *value = getvalue(ls, rowData, index.row(), col, getvaluedata, &freeResult); if(value) { UiModelType type = model->types[col]; switch(type) {
--- a/ui/qt/model.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/qt/model.h Sun Aug 24 15:24:16 2025 +0200 @@ -45,7 +45,7 @@ Q_OBJECT ui_getvaluefunc2 getvalue; - void *getvaluedata; + void *getvaluedata; ui_callback onactivate; void *onactivatedata; ui_callback onselection; @@ -75,18 +75,20 @@ class TableModel : public QAbstractListModel { Q_OBJECT - UiModel *model; - ui_callback onactivate; - void *onactivatedata; - ui_callback onselection; - void *onselectiondata; + UiModel *model; + ui_getvaluefunc2 getvalue; + void *getvaluedata; + ui_callback onactivate; + void *onactivatedata; + ui_callback onselection; + void *onselectiondata; public: - UiObject *obj; - UiVar *var; - QTreeView *view; + UiObject *obj; + UiVar *var; + QTreeView *view; - TableModel(UiObject *obj, QTreeView *view, UiVar *var, UiModel *model); + TableModel(UiObject *obj, QTreeView *view, UiVar *var, UiModel *model, ui_getvaluefunc2 getvalue, void *getvaluedata); void setActivationCallback(ui_callback f, void *userdata); void setSelectionCallback(ui_callback f, void *userdata);
--- a/ui/qt/window.cpp Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/qt/window.cpp Sun Aug 24 15:24:16 2025 +0200 @@ -38,8 +38,10 @@ #include <QVBoxLayout> #include <QFileDialog> #include <QPushButton> +#include <QDockWidget> +#include <QMessageBox> -static UiObject* create_window(const char *title, void *window_data, bool simple) { +static UiObject* create_window(const char *title, void *window_data, bool simple, bool sidebar = false) { UiObject *obj = uic_object_new_toplevel(); obj->window = window_data; obj->next = NULL; @@ -61,19 +63,70 @@ boxWidget->setLayout(box); window->setCentralWidget(boxWidget); ui_container_add(obj, new UiBoxContainer(box)); + if(sidebar) { + QDockWidget *dock = new QDockWidget(); + window->addDockWidget(Qt::LeftDockWidgetArea, dock); + window->setProperty("ui_sidebar", QVariant::fromValue((void*)dock)); + } obj->widget = window; return obj; } UiObject* ui_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE); + return create_window(title, window_data, false); } UiObject* ui_simplewindow(char *title, void *window_data) { - return create_window(title, window_data, TRUE); + return create_window(title, window_data, true); +} + +UiObject *ui_sidebar_window(const char *title, void *window_data) { + return create_window(title, window_data, false, true); } +void ui_dialog_create(UiObject *parent, UiDialogArgs *args) { + if(args->input || args->password) { + // TODO: QInputDialog + } else { + QMessageBox msgBox; + if(args->title) { + msgBox.setWindowTitle(args->title); + } + if(args->content) { + msgBox.setText(args->content); + } + QPushButton *btn1; + QPushButton *btn2; + if(args->button1_label) { + btn1 = msgBox.addButton(args->button1_label, QMessageBox::ActionRole); + } + if(args->button2_label) { + btn2 = msgBox.addButton(args->button2_label, QMessageBox::ActionRole); + } + if(args->closebutton_label) { + msgBox.addButton(args->closebutton_label, QMessageBox::DestructiveRole); + } + + msgBox.exec(); + + UiEvent evt; + evt.obj = parent; + evt.document = evt.obj->ctx->document; + evt.window = evt.obj->window; + evt.eventdata = NULL; + evt.eventdatatype = 0; + evt.intval = 0; + if(msgBox.clickedButton() == btn1) { + evt.intval = 1; + } else if(msgBox.clickedButton() == btn2) { + evt.intval = 2; + } + if(args->result) { + args->result(&evt, args->resultdata); + } + } +} char* ui_openfiledialog(UiObject *obj) { QString fileName = QFileDialog::getOpenFileName(obj->widget);
--- a/ui/ui/button.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/ui/button.h Sun Aug 24 15:24:16 2025 +0200 @@ -35,6 +35,12 @@ extern "C" { #endif +enum UiLinkType { + UI_LINK_TEXT = 0, + UI_LINK_BUTTON +}; +typedef enum UiLinkType UiLinkType; + typedef struct UiButtonArgs { UiBool fill; UiBool hexpand; @@ -81,21 +87,59 @@ const int* groups; } UiToggleArgs; + +typedef struct UiLinkButtonArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + const char *label; + const char *uri; + UiString *value; + const char *varname; + ui_callback onclick; + void *onclickdata; + UiBool nofollow; + UiLinkType type; + + const int* groups; +} UiLinkButtonArgs; #define ui_button(obj, ...) ui_button_create(obj, &(UiButtonArgs){ __VA_ARGS__ } ) #define ui_togglebutton(obj, ...) ui_togglebutton_create(obj, &(UiToggleArgs){ __VA_ARGS__ } ) #define ui_checkbox(obj, ...) ui_checkbox_create(obj, &(UiToggleArgs){ __VA_ARGS__ } ) #define ui_switch(obj, ...) ui_switch_create(obj, &(UiToggleArgs){ __VA_ARGS__ } ) #define ui_radiobutton(obj, ...) ui_radiobutton_create(obj, &(UiToggleArgs){ __VA_ARGS__ } ) +#define ui_linkbutton(obj, ...) ui_linkbutton_create(obj, &(UiLinkButtonArgs){ __VA_ARGS__ }) -UIEXPORT UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs *args); -UIEXPORT UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs *args); -UIEXPORT UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args); -UIEXPORT UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args); -UIEXPORT UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs *args); +UIEXPORT UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args); +UIEXPORT UIWIDGET ui_togglebutton_create(UiObject *obj, UiToggleArgs *args); +UIEXPORT UIWIDGET ui_checkbox_create(UiObject *obj, UiToggleArgs *args); +UIEXPORT UIWIDGET ui_switch_create(UiObject *obj, UiToggleArgs *args); +UIEXPORT UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args); +UIEXPORT UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args); + +UIEXPORT void ui_button_set_label(UIWIDGET button, const char *label); +UIEXPORT void ui_button_set_icon(UIWIDGET button, const char *icon); +UIEXPORT void ui_linkbutton_value_set(UiString *str, const char *label, const char *uri); +UIEXPORT void ui_linkbutton_value_set_label(UiString *str, const char *label); +UIEXPORT void ui_linkbutton_value_set_uri(UiString *str, const char *uri); +UIEXPORT void ui_linkbutton_value_set_visited(UiString *str, UiBool visited); - +UIEXPORT void ui_linkbutton_set_label(UIWIDGET button, const char *label); +UIEXPORT void ui_linkbutton_set_uri(UIWIDGET button, const char *label); +UIEXPORT void ui_linkbutton_set_visited(UIWIDGET button, UiBool visited); +UIEXPORT char* ui_linkbutton_get_label(UIWIDGET button); +UIEXPORT char* ui_linkbutton_get_uri(UIWIDGET button); +UIEXPORT UiBool ui_linkbutton_get_visited(UIWIDGET button); #ifdef __cplusplus }
--- a/ui/ui/display.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/ui/display.h Sun Aug 24 15:24:16 2025 +0200 @@ -38,15 +38,7 @@ #ifdef __cplusplus extern "C" { #endif - -enum UiAlignment { - UI_ALIGN_DEFAULT = 0, - UI_ALIGN_LEFT, - UI_ALIGN_RIGHT, - UI_ALIGN_CENTER -}; - -typedef enum UiAlignment UiAlignment; + enum UiLabelStyle { UI_LABEL_STYLE_DEFAULT = 0,
--- a/ui/ui/toolkit.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/ui/toolkit.h Sun Aug 24 15:24:16 2025 +0200 @@ -237,7 +237,16 @@ UI_DND_ACTION_LINK, UI_DND_ACTION_CUSTOM } UiDnDAction; - + +enum UiAlignment { + UI_ALIGN_DEFAULT = 0, + UI_ALIGN_LEFT, + UI_ALIGN_RIGHT, + UI_ALIGN_CENTER +}; + +typedef enum UiAlignment UiAlignment; + typedef void(*ui_callback)(UiEvent*, void*); /* event, user data */ typedef void*(*ui_getvaluefunc)(void *elm, int col); @@ -611,6 +620,8 @@ UIEXPORT void ui_list_remove(UiList *list, int i); UIEXPORT void ui_list_clear(UiList *list); UIEXPORT void ui_list_update(UiList *list); +UIEXPORT void ui_list_update_row(UiList *list, int row); +UIEXPORT UiListSelection ui_list_get_selection(UiList *list); UIEXPORT void ui_list_addobsv(UiList *list, ui_callback f, void *data); UIEXPORT void ui_list_notify(UiList *list);
--- a/ui/ui/tree.h Sun Jul 20 22:04:39 2025 +0200 +++ b/ui/ui/tree.h Sun Aug 24 15:24:16 2025 +0200 @@ -76,25 +76,6 @@ * array of column size hints */ int *columnsize; - - /* - * void*(*ui_getvaluefunc)(void *elm, int col); - * - * function for translating model data to view data - * first argument is the pointer returned by UiList first|next|get - * second argument is the column index - * TODO: return - */ - ui_getvaluefunc getvalue; - - /* - * void*(*ui_getvaluefunc2)(UiList *list, void *elm, int row, int col, void *userdata) - * - * alternative for getvalue - */ - ui_getvaluefunc2 getvalue2; - - void *getvalue2data; }; struct UiListCallbacks { @@ -248,12 +229,23 @@ */ ui_callback onbuttonclick; void *onbuttonclickdata; + + UiMenuBuilder *contextmenu; }; #define UI_SUBLIST(...) (UiSubList){ __VA_ARGS__ } #define UI_SUBLISTS(...) (UiSubList[]){ __VA_ARGS__, (UiSubList){NULL,NULL,NULL,0} } +/* + * Creates an UiModel, that specifies columns for a table widget. + * + * For each column a column type (UiModelType) and a title string + * (char*) must be specified. The column list must be terminated + * with -1. + * + * UiModel *model = ui_model(ctx, UI_STRING, "Column 1", UI_STRING, "Column 2", -1); + */ UIEXPORT UiModel* ui_model(UiContext *ctx, ...); UIEXPORT UiModel* ui_model_copy(UiContext *ctx, UiModel* model); UIEXPORT void ui_model_free(UiContext *ctx, UiModel *mi);