| |
1 /* |
| |
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
| |
3 * |
| |
4 * Copyright 2025 Olaf Wintermann. All rights reserved. |
| |
5 * |
| |
6 * Redistribution and use in source and binary forms, with or without |
| |
7 * modification, are permitted provided that the following conditions are met: |
| |
8 * |
| |
9 * 1. Redistributions of source code must retain the above copyright |
| |
10 * notice, this list of conditions and the following disclaimer. |
| |
11 * |
| |
12 * 2. Redistributions in binary form must reproduce the above copyright |
| |
13 * notice, this list of conditions and the following disclaimer in the |
| |
14 * documentation and/or other materials provided with the distribution. |
| |
15 * |
| |
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| |
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| |
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| |
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| |
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| |
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| |
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| |
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| |
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| |
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| |
26 * POSSIBILITY OF SUCH DAMAGE. |
| |
27 */ |
| |
28 |
| |
29 #import "list.h" |
| |
30 #import "ListDelegate.h" |
| |
31 #import <objc/runtime.h> |
| |
32 |
| |
33 static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { |
| |
34 ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata; |
| |
35 return getvalue(elm, col); |
| |
36 } |
| |
37 |
| |
38 static void* str_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { |
| |
39 return elm; |
| |
40 } |
| |
41 |
| |
42 /* --------------------------- ListView --------------------------- */ |
| |
43 |
| |
44 /* |
| |
45 * adds a NSTableViewDelegate that handles all events and calls |
| |
46 * callbacks specified in the UiListArgs |
| |
47 */ |
| |
48 static void add_listdelegate(UiObject *obj, NSTableView *tableview, UiListArgs *args) { |
| |
49 ListDelegate *delegate = [[ListDelegate alloc] init:tableview obj:obj]; |
| |
50 delegate.onactivate = args->onactivate; |
| |
51 delegate.onactivatedata = args->onactivatedata; |
| |
52 delegate.onselection = args->onselection; |
| |
53 delegate.onselectiondata = args->onselectiondata; |
| |
54 tableview.delegate = delegate; |
| |
55 objc_setAssociatedObject(tableview, "ui_listdelegate", delegate, OBJC_ASSOCIATION_RETAIN); |
| |
56 tableview.doubleAction = @selector(activateEvent:); |
| |
57 tableview.target = delegate; |
| |
58 } |
| |
59 |
| |
60 static void bind_list_to_tableview(UiList *list, NSTableView *tableview) { |
| |
61 list->obj = (__bridge void*)tableview; |
| |
62 list->update = ui_tableview_update; |
| |
63 list->getselection = ui_tableview_getselection; |
| |
64 list->setselection = ui_tableview_setselection; |
| |
65 } |
| |
66 |
| |
67 UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { |
| |
68 NSScrollView *scrollview = [[NSScrollView alloc] init]; |
| |
69 |
| |
70 NSTableView *tableview = [[NSTableView alloc] init]; |
| |
71 tableview.autoresizingMask = NSViewWidthSizable; |
| |
72 tableview.headerView = nil; |
| |
73 |
| |
74 if(args->multiselection) { |
| |
75 tableview.allowsMultipleSelection = YES; |
| |
76 } |
| |
77 |
| |
78 scrollview.documentView = tableview; |
| |
79 |
| |
80 UiLayout layout = UI_INIT_LAYOUT(args); |
| |
81 ui_container_add(obj, scrollview, &layout); |
| |
82 |
| |
83 add_listdelegate(obj, tableview, args); |
| |
84 |
| |
85 UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); |
| |
86 if(var) { |
| |
87 UiList *list = var->value; |
| |
88 bind_list_to_tableview(list, tableview); |
| |
89 |
| |
90 ui_getvaluefunc2 getvalue = args->getvalue2; |
| |
91 void *getvaluedata = args->getvalue2data; |
| |
92 if(!getvalue) { |
| |
93 if(args->getvalue) { |
| |
94 getvalue = getvalue_wrapper; |
| |
95 getvaluedata = (void*)args->getvalue; |
| |
96 } else { |
| |
97 getvalue = str_getvalue; // by default list values are interpreted as strings |
| |
98 } |
| |
99 } |
| |
100 |
| |
101 NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"column"]; |
| |
102 [tableview addTableColumn:column]; |
| |
103 |
| |
104 ListDataSource *dataSource = [[ListDataSource alloc] init:tableview.tableColumns var:var getvalue:getvalue getvaluedata:getvaluedata]; |
| |
105 |
| |
106 tableview.dataSource = dataSource; |
| |
107 [tableview reloadData]; |
| |
108 |
| |
109 objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN); |
| |
110 } |
| |
111 |
| |
112 return (__bridge void*)scrollview; |
| |
113 } |
| |
114 |
| |
115 /* --------------------------- TableView --------------------------- */ |
| |
116 |
| |
117 UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) { |
| |
118 NSScrollView *scrollview = [[NSScrollView alloc] init]; |
| |
119 |
| |
120 NSTableView *tableview = [[NSTableView alloc] init]; |
| |
121 tableview.autoresizingMask = NSViewWidthSizable; |
| |
122 tableview.columnAutoresizingStyle = NSTableViewSequentialColumnAutoresizingStyle; |
| |
123 |
| |
124 if(args->multiselection) { |
| |
125 tableview.allowsMultipleSelection = YES; |
| |
126 } |
| |
127 |
| |
128 UiLayout layout = UI_INIT_LAYOUT(args); |
| |
129 ui_container_add(obj, scrollview, &layout); |
| |
130 |
| |
131 add_listdelegate(obj, tableview, args); |
| |
132 |
| |
133 // convert model |
| |
134 NSMutableArray<NSTableColumn*> *cols = [[NSMutableArray alloc] init]; |
| |
135 UiModel *model = args->model; |
| |
136 if(model) { |
| |
137 for(int i=0;i<model->columns;i++) { |
| |
138 char *title = model->titles[i]; |
| |
139 UiModelType type = model->types[i]; |
| |
140 int width = model->columnsize[i]; |
| |
141 NSString *identifier = [[NSString alloc] initWithUTF8String:title]; |
| |
142 NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:identifier]; |
| |
143 column.title = identifier; |
| |
144 column.resizingMask = NSTableColumnUserResizingMask; |
| |
145 if(width > 0) { |
| |
146 column.width = width; |
| |
147 } else if(width < 0) { |
| |
148 column.resizingMask = NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask; |
| |
149 } |
| |
150 if(type >= UI_ICON) { |
| |
151 // TODO |
| |
152 } |
| |
153 [tableview addTableColumn:column]; |
| |
154 [cols addObject:column]; |
| |
155 } |
| |
156 } |
| |
157 |
| |
158 UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); |
| |
159 if(var) { |
| |
160 UiList *list = var->value; |
| |
161 bind_list_to_tableview(list, tableview); |
| |
162 |
| |
163 ui_getvaluefunc2 getvalue = args->getvalue2; |
| |
164 void *getvaluedata = args->getvalue2data; |
| |
165 if(!getvalue) { |
| |
166 if(args->getvalue) { |
| |
167 getvalue = getvalue_wrapper; |
| |
168 getvaluedata = (void*)args->getvalue; |
| |
169 } else { |
| |
170 fprintf(stderr, "Error: tableview requires getvalue or getvalue2 func\n"); |
| |
171 return (__bridge void*)scrollview; |
| |
172 } |
| |
173 } |
| |
174 |
| |
175 ListDataSource *dataSource = [[ListDataSource alloc] init:cols var:var getvalue:getvalue getvaluedata:getvaluedata]; |
| |
176 if(model) { |
| |
177 dataSource.model = ui_model_copy(obj->ctx, model); |
| |
178 } |
| |
179 |
| |
180 tableview.dataSource = dataSource; |
| |
181 [tableview reloadData]; |
| |
182 |
| |
183 objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN); |
| |
184 } |
| |
185 |
| |
186 scrollview.documentView = tableview; |
| |
187 |
| |
188 return (__bridge void*)scrollview; |
| |
189 } |
| |
190 |
| |
191 /* ------ common functions ------ */ |
| |
192 |
| |
193 void ui_tableview_update(UiList *list, int i) { |
| |
194 NSTableView *tableview = (__bridge NSTableView*)list->obj; |
| |
195 if(i < 0) { |
| |
196 [tableview reloadData]; |
| |
197 } else { |
| |
198 [tableview reloadData]; // TODO: optimize |
| |
199 } |
| |
200 } |
| |
201 |
| |
202 UiListSelection ui_tableview_getselection(UiList *list) { |
| |
203 NSTableView *tableview = (__bridge NSTableView*)list->obj; |
| |
204 return ui_tableview_selection(tableview); |
| |
205 } |
| |
206 |
| |
207 void ui_tableview_setselection(UiList *list, UiListSelection selection) { |
| |
208 NSTableView *tableview = (__bridge NSTableView*)list->obj; |
| |
209 NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet]; |
| |
210 for(int i=0;i<selection.count;i++) { |
| |
211 [indexSet addIndex:selection.rows[i]]; |
| |
212 } |
| |
213 [tableview selectRowIndexes:indexSet byExtendingSelection:NO]; |
| |
214 } |
| |
215 |
| |
216 |
| |
217 /* --------------------------- DropDown --------------------------- */ |
| |
218 |
| |
219 @implementation UiDropDown |
| |
220 |
| |
221 - (id)init:(UiObject*)obj { |
| |
222 _obj = obj; |
| |
223 return self; |
| |
224 } |
| |
225 |
| |
226 - (void) comboBoxSelectionDidChange:(NSNotification *) notification { |
| |
227 int index = (int)_combobox.indexOfSelectedItem; |
| |
228 |
| |
229 void *eventdata = NULL; |
| |
230 if(_var) { |
| |
231 UiList *list = _var->value; |
| |
232 if(index >= 0) { |
| |
233 eventdata = list->get(list, index); |
| |
234 } |
| |
235 } else { |
| |
236 NSString *str = _combobox.objectValueOfSelectedItem; |
| |
237 if(str) { |
| |
238 eventdata = (void*)str.UTF8String; |
| |
239 } |
| |
240 } |
| |
241 |
| |
242 UiEvent event; |
| |
243 event.obj = _obj; |
| |
244 event.window = event.obj->window; |
| |
245 event.document = event.obj->ctx->document; |
| |
246 event.eventdata = eventdata; |
| |
247 event.eventdatatype = UI_EVENT_DATA_LIST_ELM; |
| |
248 event.intval = index; |
| |
249 |
| |
250 if(_onselection) { |
| |
251 _onselection(&event, _onselectiondata); |
| |
252 } |
| |
253 |
| |
254 if(_onactivate) { |
| |
255 _onactivate(&event, _onactivatedata); |
| |
256 } |
| |
257 } |
| |
258 |
| |
259 @end |
| |
260 |
| |
261 UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { |
| |
262 NSComboBox *dropdown = [[NSComboBox alloc] init]; |
| |
263 dropdown.editable = NO; |
| |
264 |
| |
265 UiDropDown *uidropdown = [[UiDropDown alloc] init:obj]; |
| |
266 objc_setAssociatedObject(dropdown, "ui_dropdown", uidropdown, OBJC_ASSOCIATION_RETAIN); |
| |
267 uidropdown.onactivate = args->onactivate; |
| |
268 uidropdown.onactivatedata = args->onactivatedata; |
| |
269 uidropdown.onselection = args->onselection; |
| |
270 uidropdown.onselectiondata = args->onselectiondata; |
| |
271 uidropdown.combobox = dropdown; |
| |
272 |
| |
273 if(!args->getvalue2) { |
| |
274 if(args->getvalue) { |
| |
275 args->getvalue2 = getvalue_wrapper; |
| |
276 args->getvalue2data = (void*)args->getvalue; |
| |
277 } else { |
| |
278 args->getvalue2 = str_getvalue; |
| |
279 } |
| |
280 } |
| |
281 uidropdown.getvalue = args->getvalue2; |
| |
282 uidropdown.getvaluedata = args->getvalue2data; |
| |
283 |
| |
284 UiLayout layout = UI_INIT_LAYOUT(args); |
| |
285 ui_container_add(obj, dropdown, &layout); |
| |
286 |
| |
287 UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); |
| |
288 if(var) { |
| |
289 UiList *list = var->value; |
| |
290 list->obj = (__bridge void*)dropdown; |
| |
291 list->update = ui_dropdown_update; |
| |
292 list->getselection = ui_dropdown_getselection; |
| |
293 list->setselection = ui_dropdown_setselection; |
| |
294 ui_dropdown_update(list, -1); |
| |
295 } else { |
| |
296 for(int i=0;i<args->static_nelm;i++) { |
| |
297 char *str = args->static_elements[i]; |
| |
298 NSString *item = [[NSString alloc] initWithUTF8String:str]; |
| |
299 [dropdown addItemWithObjectValue:item]; |
| |
300 } |
| |
301 } |
| |
302 |
| |
303 uidropdown.var = var; |
| |
304 |
| |
305 return (__bridge void*)dropdown; |
| |
306 } |
| |
307 |
| |
308 void ui_dropdown_update(UiList *list, int i) { |
| |
309 NSComboBox *combobox = (__bridge NSComboBox*)list->obj; |
| |
310 UiDropDown *dropdown = objc_getAssociatedObject(combobox, "ui_dropdown"); |
| |
311 if(dropdown) { |
| |
312 [combobox removeAllItems]; |
| |
313 |
| |
314 ui_getvaluefunc2 getvalue = dropdown.getvalue; |
| |
315 void *getvaluedata = dropdown.getvaluedata; |
| |
316 |
| |
317 int index = 0; |
| |
318 void *elm = list->first(list); |
| |
319 while(elm) { |
| |
320 UiBool freeResult = FALSE; |
| |
321 char *str = getvalue(list, elm, index, 0, getvaluedata, &freeResult); |
| |
322 if(str) { |
| |
323 NSString *item = [[NSString alloc] initWithUTF8String:str]; |
| |
324 [combobox addItemWithObjectValue:item]; |
| |
325 } |
| |
326 if(freeResult) { |
| |
327 free(str); |
| |
328 } |
| |
329 elm = list->next(list); |
| |
330 index++; |
| |
331 } |
| |
332 } else { |
| |
333 fprintf(stderr, "Error: obj is not a dropdown\n"); |
| |
334 } |
| |
335 } |
| |
336 |
| |
337 UiListSelection ui_dropdown_getselection(UiList *list) { |
| |
338 UiListSelection sel = { 0, NULL }; |
| |
339 NSComboBox *combobox = (__bridge NSComboBox*)list->obj; |
| |
340 NSInteger index = combobox.indexOfSelectedItem; |
| |
341 if(index >= 0) { |
| |
342 sel.rows = malloc(sizeof(int)); |
| |
343 sel.count = 1; |
| |
344 sel.rows[0] = (int)index; |
| |
345 } |
| |
346 return sel; |
| |
347 } |
| |
348 |
| |
349 void ui_dropdown_setselection(UiList *list, UiListSelection selection) { |
| |
350 NSComboBox *combobox = (__bridge NSComboBox*)list->obj; |
| |
351 if(selection.count > 0) { |
| |
352 [combobox selectItemAtIndex:selection.rows[0]]; |
| |
353 } else { |
| |
354 [combobox selectItemAtIndex: -1]; |
| |
355 } |
| |
356 } |