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