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 #import <inttypes.h>
34 #import <limits.h>
35
36 #import <cx/array_list.h>
37
38 static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
39 ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata;
40 return getvalue(elm, col);
41 }
42
43 static void* str_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
44 return elm;
45 }
46
47 /* --------------------------- ListView --------------------------- */
48
49 /*
50 * adds a NSTableViewDelegate that handles all events and calls
51 * callbacks specified in the UiListArgs
52 */
53 static void add_listdelegate(UiObject *obj, NSTableView *tableview, UiListArgs *args) {
54 ListDelegate *delegate = [[ListDelegate alloc] init:tableview obj:obj];
55 delegate.onactivate = args->onactivate;
56 delegate.onactivatedata = args->onactivatedata;
57 delegate.onselection = args->onselection;
58 delegate.onselectiondata = args->onselectiondata;
59 tableview.delegate = delegate;
60 objc_setAssociatedObject(tableview, "ui_listdelegate", delegate, OBJC_ASSOCIATION_RETAIN);
61 tableview.doubleAction = @selector(activateEvent:);
62 tableview.target = delegate;
63 }
64
65 static void bind_list_to_tableview(UiList *list, NSTableView *tableview) {
66 list->obj = (__bridge void*)tableview;
67 list->update = ui_tableview_update;
68 list->getselection = ui_tableview_getselection;
69 list->setselection = ui_tableview_setselection;
70 }
71
72 UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) {
73 NSScrollView *scrollview = [[NSScrollView alloc] init];
74
75 NSTableView *tableview = [[NSTableView alloc] init];
76 tableview.autoresizingMask = NSViewWidthSizable;
77 tableview.headerView = nil;
78
79 if(args->multiselection) {
80 tableview.allowsMultipleSelection = YES;
81 }
82
83 scrollview.documentView = tableview;
84
85 UiLayout layout = UI_INIT_LAYOUT(args);
86 ui_container_add(obj, scrollview, &layout);
87
88 add_listdelegate(obj, tableview, args);
89
90 UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
91 if(var) {
92 UiList *list = var->value;
93 bind_list_to_tableview(list, tableview);
94
95 ui_getvaluefunc2 getvalue = args->getvalue2;
96 void *getvaluedata = args->getvalue2data;
97 if(!getvalue) {
98 if(args->getvalue) {
99 getvalue = getvalue_wrapper;
100 getvaluedata = (void*)args->getvalue;
101 } else {
102 getvalue = str_getvalue; // by default list values are interpreted as strings
103 }
104 }
105
106 NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"column"];
107 [tableview addTableColumn:column];
108
109 ListDataSource *dataSource = [[ListDataSource alloc] init:tableview.tableColumns var:var getvalue:getvalue getvaluedata:getvaluedata];
110
111 tableview.dataSource = dataSource;
112 [tableview reloadData];
113
114 objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN);
115 }
116
117 return (__bridge void*)scrollview;
118 }
119
120 /* --------------------------- TableView --------------------------- */
121
122 UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) {
123 NSScrollView *scrollview = [[NSScrollView alloc] init];
124
125 NSTableView *tableview = [[NSTableView alloc] init];
126 tableview.autoresizingMask = NSViewWidthSizable;
127 tableview.columnAutoresizingStyle = NSTableViewSequentialColumnAutoresizingStyle;
128
129 if(args->multiselection) {
130 tableview.allowsMultipleSelection = YES;
131 }
132
133 UiLayout layout = UI_INIT_LAYOUT(args);
134 ui_container_add(obj, scrollview, &layout);
135
136 add_listdelegate(obj, tableview, args);
137
138 // convert model
139 NSMutableArray<NSTableColumn*> *cols = [[NSMutableArray alloc] init];
140 UiModel *model = args->model;
141 if(model) {
142 for(int i=0;i<model->columns;i++) {
143 char *title = model->titles[i];
144 UiModelType type = model->types[i];
145 int width = model->columnsize[i];
146 NSString *identifier = [[NSString alloc] initWithUTF8String:title];
147 NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:identifier];
148 column.title = identifier;
149 column.resizingMask = NSTableColumnUserResizingMask;
150 if(width > 0) {
151 column.width = width;
152 } else if(width < 0) {
153 column.resizingMask = NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask;
154 }
155 if(type >= UI_ICON) {
156 // TODO
157 }
158 [tableview addTableColumn:column];
159 [cols addObject:column];
160 }
161 }
162
163 UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
164 if(var) {
165 UiList *list = var->value;
166 bind_list_to_tableview(list, tableview);
167
168 ui_getvaluefunc2 getvalue = args->getvalue2;
169 void *getvaluedata = args->getvalue2data;
170 if(!getvalue) {
171 if(args->getvalue) {
172 getvalue = getvalue_wrapper;
173 getvaluedata = (void*)args->getvalue;
174 } else {
175 fprintf(stderr, "Error: tableview requires getvalue or getvalue2 func\n");
176 return (__bridge void*)scrollview;
177 }
178 }
179
180 ListDataSource *dataSource = [[ListDataSource alloc] init:cols var:var getvalue:getvalue getvaluedata:getvaluedata];
181 if(model) {
182 dataSource.model = model;
183 }
184
185 tableview.dataSource = dataSource;
186 [tableview reloadData];
187
188 objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN);
189 }
190
191 scrollview.documentView = tableview;
192
193 return (__bridge void*)scrollview;
194 }
195
196 /* ------ common functions ------ */
197
198 void ui_tableview_update(UiList *list, int i) {
199 NSTableView *tableview = (__bridge NSTableView*)list->obj;
200 if(i < 0) {
201 [tableview reloadData];
202 } else {
203 [tableview reloadData]; // TODO: optimize
204 }
205 }
206
207 UiListSelection ui_tableview_getselection(UiList *list) {
208 NSTableView *tableview = (__bridge NSTableView*)list->obj;
209 return ui_tableview_selection(tableview);
210 }
211
212 void ui_tableview_setselection(UiList *list, UiListSelection selection) {
213 NSTableView *tableview = (__bridge NSTableView*)list->obj;
214 NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
215 for(int i=0;i<selection.count;i++) {
216 [indexSet addIndex:selection.rows[i]];
217 }
218 [tableview selectRowIndexes:indexSet byExtendingSelection:NO];
219 }
220
221
222 /* --------------------------- DropDown --------------------------- */
223
224 @implementation UiDropDown
225
226 - (id)init:(UiObject*)obj {
227 _obj = obj;
228 return self;
229 }
230
231 - (void) comboBoxSelectionDidChange:(NSNotification *) notification {
232 int index = (int)_combobox.indexOfSelectedItem;
233
234 void *eventdata = NULL;
235 if(_var) {
236 UiList *list = _var->value;
237 if(index >= 0) {
238 eventdata = list->get(list, index);
239 }
240 } else {
241 NSString *str = _combobox.objectValueOfSelectedItem;
242 if(str) {
243 eventdata = (void*)str.UTF8String;
244 }
245 }
246
247 UiEvent event;
248 event.obj = _obj;
249 event.window = event.obj->window;
250 event.document = event.obj->ctx->document;
251 event.eventdata = eventdata;
252 event.eventdatatype = UI_EVENT_DATA_LIST_ELM;
253 event.intval = index;
254
255 if(_onselection) {
256 _onselection(&event, _onselectiondata);
257 }
258
259 if(_onactivate) {
260 _onactivate(&event, _onactivatedata);
261 }
262 }
263
264 @end
265
266 UIWIDGET ui_dropdown_create(UiObject* obj, UiListArgs *args) {
267 NSComboBox *dropdown = [[NSComboBox alloc] init];
268 dropdown.editable = NO;
269
270 UiDropDown *uidropdown = [[UiDropDown alloc] init:obj];
271 objc_setAssociatedObject(dropdown, "ui_dropdown", uidropdown, OBJC_ASSOCIATION_RETAIN);
272 uidropdown.onactivate = args->onactivate;
273 uidropdown.onactivatedata = args->onactivatedata;
274 uidropdown.onselection = args->onselection;
275 uidropdown.onselectiondata = args->onselectiondata;
276 uidropdown.combobox = dropdown;
277
278 if(!args->getvalue2) {
279 if(args->getvalue) {
280 args->getvalue2 = getvalue_wrapper;
281 args->getvalue2data = (void*)args->getvalue;
282 } else {
283 args->getvalue2 = str_getvalue;
284 }
285 }
286 uidropdown.getvalue = args->getvalue2;
287 uidropdown.getvaluedata = args->getvalue2data;
288
289 UiLayout layout = UI_INIT_LAYOUT(args);
290 ui_container_add(obj, dropdown, &layout);
291
292 UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
293 if(var) {
294 UiList *list = var->value;
295 list->obj = (__bridge void*)dropdown;
296 list->update = ui_dropdown_update;
297 list->getselection = ui_dropdown_getselection;
298 list->setselection = ui_dropdown_setselection;
299 ui_dropdown_update(list, -1);
300 } else {
301 for(int i=0;i<args->static_nelm;i++) {
302 char *str = args->static_elements[i];
303 NSString *item = [[NSString alloc] initWithUTF8String:str];
304 [dropdown addItemWithObjectValue:item];
305 }
306 }
307
308 uidropdown.var = var;
309
310 return (__bridge void*)dropdown;
311 }
312
313 void ui_dropdown_update(UiList *list, int i) {
314 NSComboBox *combobox = (__bridge NSComboBox*)list->obj;
315 UiDropDown *dropdown = objc_getAssociatedObject(combobox, "ui_dropdown");
316 if(dropdown) {
317 [combobox removeAllItems];
318
319 ui_getvaluefunc2 getvalue = dropdown.getvalue;
320 void *getvaluedata = dropdown.getvaluedata;
321
322 int index = 0;
323 void *elm = list->first(list);
324 while(elm) {
325 UiBool freeResult = FALSE;
326 char *str = getvalue(list, elm, index, 0, getvaluedata, &freeResult);
327 if(str) {
328 NSString *item = [[NSString alloc] initWithUTF8String:str];
329 [combobox addItemWithObjectValue:item];
330 }
331 if(freeResult) {
332 free(str);
333 }
334 elm = list->next(list);
335 index++;
336 }
337 } else {
338 fprintf(stderr, "Error: obj is not a dropdown\n");
339 }
340 }
341
342 UiListSelection ui_dropdown_getselection(UiList *list) {
343 UiListSelection sel = { 0, NULL };
344 NSComboBox *combobox = (__bridge NSComboBox*)list->obj;
345 NSInteger index = combobox.indexOfSelectedItem;
346 if(index >= 0) {
347 sel.rows = malloc(sizeof(int));
348 sel.count = 1;
349 sel.rows[0] = (int)index;
350 }
351 return sel;
352 }
353
354 void ui_dropdown_setselection(UiList *list, UiListSelection selection) {
355 NSComboBox *combobox = (__bridge NSComboBox*)list->obj;
356 if(selection.count > 0) {
357 [combobox selectItemAtIndex:selection.rows[0]];
358 } else {
359 [combobox selectItemAtIndex: -1];
360 }
361 }
362
363
364 /* --------------------------- SourceList --------------------------- */
365
366 static ui_sourcelist_update_func sclist_update_callback = NULL;
367
368 void ui_sourcelist_set_update_callback(ui_sourcelist_update_func cb) {
369 sclist_update_callback = cb;
370 }
371
372 void ui_sourcelist_updated(void) {
373 if(sclist_update_callback) {
374 sclist_update_callback();
375 }
376 }
377
378 static void sublist_free(const CxAllocator *a, UiSubList *sl) {
379 cxFree(a, (char*)sl->varname);
380 cxFree(a, (char*)sl->header);
381 }
382
383 static UiSubList copy_sublist(const CxAllocator *a, UiSubList *sl) {
384 UiSubList new_sl;
385 new_sl.value = sl->value;
386 new_sl.varname = sl->varname ? cx_strdup_a(a, cx_str(sl->varname)).ptr : NULL;
387 new_sl.header = sl->header ? cx_strdup_a(a, cx_str(sl->header)).ptr : NULL;
388 new_sl.separator = sl->separator;
389 new_sl.userdata = sl->userdata;
390 return new_sl;
391 }
392
393 static CxList* copy_sublists(const CxAllocator *a, UiSourceListArgs *args) {
394 if(args->sublists) {
395 size_t max = args->numsublists;
396 if(max == 0) {
397 max = INT_MAX;
398 }
399
400 CxList *sublists = cxArrayListCreate(a, NULL, sizeof(UiSubList), args->numsublists);
401 sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_free;
402
403 for(int i=0;i<max;i++) {
404 UiSubList *sl = &args->sublists[i];
405 if(sl->value == NULL && sl->varname == NULL) {
406 break;
407 }
408
409 UiSubList new_sl = copy_sublist(a, sl);
410 cxListAdd(sublists, &new_sl);
411 }
412
413 return sublists;
414 }
415 return NULL;
416 }
417
418 UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) {
419 // create views
420 NSScrollView *scrollview = [[NSScrollView alloc] init];
421 scrollview.autoresizingMask = NSViewWidthSizable;
422 scrollview.hasVerticalScroller = YES;
423 scrollview.hasHorizontalScroller = NO;
424 scrollview.autohidesScrollers = YES;
425
426 NSOutlineView *outline = [[NSOutlineView alloc]init];
427 NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"x"];
428 [outline addTableColumn:column];
429 outline.outlineTableColumn = column;
430 outline.headerView = NULL;
431 outline.rowSizeStyle = NSTableViewRowSizeStyleDefault;
432 outline.usesAutomaticRowHeights = YES;
433 outline.indentationPerLevel = 0;
434
435 outline.style = NSTableViewStyleSourceList;
436
437 // Make background transparent so vibrancy shows through
438 scrollview.drawsBackground = NO;
439
440 scrollview.documentView = outline;
441
442 UiLayout layout = UI_ARGS2LAYOUT(args);
443 ui_container_add(obj, scrollview, &layout);
444
445 // datasource and delegate
446 UiSourceList *data = [[UiSourceList alloc] init:obj outline:outline];
447 data.sublists = copy_sublists(obj->ctx->allocator, args);
448 UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->dynamic_sublist, args->varname, UI_VAR_LIST);
449 if(var) {
450 UiList *list = var->value;
451 list->obj = (__bridge void*)data;
452 list->update = ui_sourcelist_update;
453 }
454 data.dynamic_sublists = var;
455 data.getvalue = args->getvalue;
456 data.getvaluedata = args->getvaluedata;
457 data.onactivate = args->onactivate;
458 data.onactivatedata = args->onactivatedata;
459 data.onbuttonclick = args->onbuttonclick;
460 data.onactivatedata = args->onbuttonclickdata;
461 [data update:-1];
462
463 outline.dataSource = data;
464 outline.delegate = data;
465
466 [data update:-1];
467
468 objc_setAssociatedObject(outline, "ui_datasource", data, OBJC_ASSOCIATION_RETAIN);
469
470 return (__bridge void*)scrollview;
471 }
472
473 void ui_sourcelist_update(UiList *list, int row) {
474 UiSourceList *sourcelist = (__bridge UiSourceList*)list->obj;
475 [sourcelist update:row];
476 }
477
478
479 /*
480 * Data Source and Delegate for the sourcelist NSOutlineView
481 */
482 @implementation UiSourceList
483
484 - (id)init:(UiObject*)obj outline:(NSOutlineView*)view {
485 _obj = obj;
486 _outlineView = view;
487 _sections = [[NSMutableArray alloc] initWithCapacity:16];
488 return self;
489 }
490
491 - (void)dealloc {
492 cxListFree(_sublists);
493 }
494
495 - (void)update:(int)row {
496 // TODO: check row
497
498 [_sections removeAllObjects];
499
500 CxIterator i = cxListIterator(_sublists);
501 int index = 0;
502 int rownum = 0;
503 cx_foreach(UiSubList *, sl, i) {
504 UiSourceListItem *section = [[UiSourceListItem alloc] init:self sublist:sl];
505 section.sublistIndex = index;
506 section.rownum = rownum;
507 section.sublistStartRow = rownum;
508 [section update:-1];
509 [_sections addObject:section];
510 index++;
511 rownum += 1 + section.items.count;
512 }
513
514 [_outlineView reloadData];
515 [_outlineView expandItem:nil expandChildren:YES];
516 }
517
518 // NSOutlineViewDataSource implementation
519
520 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
521 if(item == nil) {
522 return _sections.count;
523 } else {
524 UiSourceListItem *i = item;
525 return i.items.count;
526 }
527 }
528
529 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
530 UiSourceListItem *i = item;
531 return [i isSection] ? YES : NO;
532 }
533
534 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
535 UiSourceListItem *i = item;
536 if(i) {
537 return [i.items objectAtIndex:index];
538 }
539 return [_sections objectAtIndex:index];
540 }
541
542 - (void)outlineView:(NSOutlineView *)outlineView
543 setObjectValue:(id)object
544 forTableColumn:(NSTableColumn *)tableColumn
545 byItem:(id)item
546 {
547
548 }
549
550 // NSOutlineViewDelegate implementation
551
552 - (NSView *)outlineView:(NSOutlineView *)outlineView
553 viewForTableColumn:(NSTableColumn *)tableColumn
554 item:(id)item
555 {
556 UiSourceListItem *i = item;
557
558 NSTableCellView *cell = [[NSTableCellView alloc] init];
559 cell.identifier = @"cell";
560 // Icon
561 NSImageView *iconView = [[NSImageView alloc] initWithFrame:NSZeroRect];
562 iconView.translatesAutoresizingMaskIntoConstraints = NO;
563 [cell addSubview:iconView];
564 cell.imageView = iconView;
565
566 // Label
567 //NSTextField *textField = [NSTextField labelWithString:@""];
568 NSTextField *textField = [[NSTextField alloc] initWithFrame:NSZeroRect];
569 textField.translatesAutoresizingMaskIntoConstraints = NO;
570 textField.bezeled = NO;
571 textField.editable = NO;
572 textField.drawsBackground = NO;
573 textField.selectable = NO;
574 textField.lineBreakMode = NSLineBreakByTruncatingTail;
575
576
577 [cell addSubview:textField];
578 cell.textField = textField;
579
580 if([i isSection]) {
581 NSFont *font = [NSFont boldSystemFontOfSize:[NSFont systemFontSize]*0.85];
582 //NSFont *font = [NSFont preferredFontForTextStyle:NSFontTextStyleCaption1 options:@{}];
583 NSDictionary *attrs = @{
584 NSFontAttributeName: font,
585 NSForegroundColorAttributeName: [NSColor tertiaryLabelColor]
586 };
587 textField.attributedStringValue = [[NSAttributedString alloc] initWithString:i.label attributes:attrs];
588
589 // Layout constraints
590 [NSLayoutConstraint activateConstraints:@[
591 [iconView.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0],
592 [iconView.bottomAnchor constraintEqualToAnchor:cell.bottomAnchor constant:-1],
593
594 [textField.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0],
595 [textField.bottomAnchor constraintEqualToAnchor:cell.bottomAnchor constant:-1],
596 [textField.trailingAnchor constraintEqualToAnchor:cell.trailingAnchor constant:0],
597 ]];
598 } else {
599 textField.stringValue = i.label;
600
601 // Layout constraints
602 [NSLayoutConstraint activateConstraints:@[
603 [iconView.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0],
604 [iconView.centerYAnchor constraintEqualToAnchor:cell.centerYAnchor],
605
606 [textField.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0],
607 [textField.centerYAnchor constraintEqualToAnchor:cell.centerYAnchor],
608 [textField.trailingAnchor constraintEqualToAnchor:cell.trailingAnchor constant:0],
609 ]];
610 }
611
612 return cell;
613 }
614
615 - (NSTableRowView *) outlineView:(NSOutlineView *) outlineView
616 rowViewForItem:(id)item {
617 UiSourceListItem *it = item;
618 UiSourceListRow *row = [[UiSourceListRow alloc]init];
619 if([it isSection] && it.sublist->header) {
620 row.showDisclosureButton = YES;
621 }
622 return row;
623 }
624
625 - (BOOL) outlineView:(NSOutlineView *) outlineView
626 shouldSelectItem:(id)item
627 {
628 UiSourceListItem *i = item;
629 return [i isSection] ? NO : YES;
630 }
631
632 - (CGFloat) outlineView:(NSOutlineView *) outlineView
633 heightOfRowByItem:(id) item
634 {
635 UiSourceListItem *i = item;
636 CGFloat rowHeight = outlineView.rowHeight;
637 if([i isSection]) {
638 if(i.sublist->header) {
639 rowHeight += i.sublistIndex == 0 ? -12 : 4;
640 } else {
641 rowHeight = i.sublistIndex == 0 ? 0.1 : 12;
642 }
643 }
644 return rowHeight;
645 }
646
647 - (void) outlineViewSelectionDidChange:(NSNotification *) notification {
648 UiEvent event;
649 event.obj = _obj;
650 event.window = event.obj->window;
651 event.document = event.obj->ctx->document;
652 event.eventdata = NULL;
653 event.eventdatatype = 0;
654 event.intval = 0;
655 event.set = ui_get_setop();
656
657 UiSubListEventData sublistEvent;
658
659 NSInteger selectedRow = _outlineView.selectedRow;
660 if(selectedRow >= 0) {
661 UiSourceListItem *item = [_outlineView itemAtRow:selectedRow];
662 UiSourceListItem *parent = item.parent;
663 UiSubList *sublist = parent != nil ? parent.sublist : item.sublist;
664 UiVar *var = parent != nil ? parent.var : item.var;
665 if(item && var) {
666 sublistEvent.list = var->value;
667 sublistEvent.sublist_index = parent ? parent.sublistIndex : item.sublistIndex;
668 sublistEvent.row_index = (int)selectedRow - item.sublistStartRow - 1;
669 sublistEvent.sublist_userdata = sublist ? sublist->userdata : NULL;
670 sublistEvent.event_data = item.eventdata;
671 sublistEvent.row_data = sublistEvent.list->get(sublistEvent.list, sublistEvent.row_index);
672
673 event.eventdata = &sublistEvent;
674 event.eventdatatype = UI_EVENT_DATA_SUBLIST;
675 }
676 }
677
678 if(_onactivate) {
679 _onactivate(&event, _onactivatedata);
680 }
681 }
682
683 @end
684
685 /*
686 * Outline datasource item
687 * Is used for sections (sublists) and individual items
688 */
689 @implementation UiSourceListItem
690
691 - (id)init:(UiSourceList*)sourcelist sublist:(UiSubList*)sublist {
692 _sourcelist = sourcelist;
693 _sublist = sublist;
694 _items = [[NSMutableArray alloc]initWithCapacity:16];
695 if(sublist->header) {
696 _label = [[NSString alloc]initWithUTF8String:sublist->header];
697 } else {
698 _label = @"";
699 }
700 UiVar *var = uic_widget_var(sourcelist.obj->ctx,
701 sourcelist.obj->ctx,
702 sublist->value,
703 sublist->varname,
704 UI_VAR_LIST);
705 _var = var;
706 return self;
707 }
708
709 - (id)init:(UiSubListItem*)item parent:(UiSourceListItem*)parent {
710 _parent = parent;
711 if(item->label) {
712 _label = [[NSString alloc]initWithUTF8String:item->label];
713 } else {
714 _label = @"";
715 }
716 _eventdata = item->eventdata;
717 return self;
718 }
719
720 - (BOOL)isSection {
721 return _sublist != NULL;
722 }
723
724 - (void)update:(int)row {
725 // TODO: check row
726
727 [_items removeAllObjects];
728 if(_var == NULL) {
729 return;
730 }
731 UiList *list = _var->value;
732 void *elm = list->first(list);
733 int index = 0;
734 while(elm) {
735 UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL };
736 if(_sourcelist.getvalue) {
737 _sourcelist.getvalue(list, _sublist->userdata, elm, index, &item, _sourcelist.getvaluedata);
738 } else {
739 item.label = strdup(elm);
740 }
741
742 UiSourceListItem *it = [[UiSourceListItem alloc] init:&item parent:self];
743 it.sublistIndex = index;
744 it.rownum = self.rownum + index;
745 it.sublistStartRow = _parent ? _parent.sublistStartRow : _sublistStartRow;
746 [_items addObject:it];
747
748 elm = list->next(list);
749 index++;
750 }
751 }
752
753 @end
754
755 /*
756 * Custom NSTableRowView implementation
757 * Moves the disclosure button to the right side
758 * Handles mouse hover events (for hiding the disclosure button)
759 */
760 @implementation UiSourceListRow
761
762 - (void)layout {
763 [super layout];
764
765 for (NSView *subview in self.subviews) {
766 if ([subview.identifier isEqualToString:NSOutlineViewDisclosureButtonKey] ||
767 [subview.identifier isEqualToString:NSOutlineViewShowHideButtonKey])
768 {
769 NSRect frame = subview.frame;
770 frame.origin.x = self.bounds.size.width - frame.size.width - 16.0;
771 subview.frame = frame;
772
773 if(!_hover) {
774 subview.hidden = YES;
775 }
776
777 if(subview != _disclosureButton) {
778 // init disclosure button
779 _disclosureButton = (NSButton*)subview;
780 if ([subview isKindOfClass:[NSButton class]]) {
781 NSButton *button = (NSButton*)subview;
782 button.contentTintColor = [NSColor tertiaryLabelColor];
783 }
784 }
785
786
787 } else if ([subview.identifier isEqualToString:@"cell"]) {
788 NSRect frame = subview.frame;
789 frame.origin.x = 16;
790 subview.frame = frame;
791 }
792 }
793 }
794
795 - (void)updateTrackingAreas {
796 [super updateTrackingAreas];
797 if(_trackingArea != nil) {
798 [self removeTrackingArea:_trackingArea];
799 }
800 _trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds
801 options:NSTrackingMouseEnteredAndExited |
802 NSTrackingActiveInActiveApp |
803 NSTrackingInVisibleRect
804 owner:self
805 userInfo:nil];
806 [self addTrackingArea:_trackingArea];
807 }
808
809 - (void)mouseEntered:(NSEvent *)event {
810 _hover = YES;
811 _disclosureButton.hidden = _showDisclosureButton ? NO : YES;
812 }
813
814 - (void)mouseExited:(NSEvent *)event {
815 _hover = NO;
816 _disclosureButton.hidden = YES;
817 }
818
819 @end
820