UNIXworkcode

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