| 352 [combobox selectItemAtIndex:selection.rows[0]]; |
357 [combobox selectItemAtIndex:selection.rows[0]]; |
| 353 } else { |
358 } else { |
| 354 [combobox selectItemAtIndex: -1]; |
359 [combobox selectItemAtIndex: -1]; |
| 355 } |
360 } |
| 356 } |
361 } |
| |
362 |
| |
363 |
| |
364 /* --------------------------- SourceList --------------------------- */ |
| |
365 |
| |
366 static void sublist_free(const CxAllocator *a, UiSubList *sl) { |
| |
367 cxFree(a, (char*)sl->varname); |
| |
368 cxFree(a, (char*)sl->header); |
| |
369 } |
| |
370 |
| |
371 static UiSubList copy_sublist(const CxAllocator *a, UiSubList *sl) { |
| |
372 UiSubList new_sl; |
| |
373 new_sl.value = sl->value; |
| |
374 new_sl.varname = sl->varname ? cx_strdup_a(a, cx_str(sl->varname)).ptr : NULL; |
| |
375 new_sl.header = sl->header ? cx_strdup_a(a, cx_str(sl->header)).ptr : NULL; |
| |
376 new_sl.separator = sl->separator; |
| |
377 new_sl.userdata = sl->userdata; |
| |
378 return new_sl; |
| |
379 } |
| |
380 |
| |
381 static CxList* copy_sublists(const CxAllocator *a, UiSourceListArgs *args) { |
| |
382 if(args->sublists) { |
| |
383 size_t max = args->numsublists; |
| |
384 if(max == 0) { |
| |
385 max = INT_MAX; |
| |
386 } |
| |
387 |
| |
388 CxList *sublists = cxArrayListCreate(a, NULL, sizeof(UiSubList), args->numsublists); |
| |
389 sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_free; |
| |
390 |
| |
391 for(int i=0;i<max;i++) { |
| |
392 UiSubList *sl = &args->sublists[i]; |
| |
393 if(sl->value == NULL && sl->varname == NULL) { |
| |
394 break; |
| |
395 } |
| |
396 |
| |
397 UiSubList new_sl = copy_sublist(a, sl); |
| |
398 cxListAdd(sublists, &new_sl); |
| |
399 } |
| |
400 |
| |
401 return sublists; |
| |
402 } |
| |
403 return NULL; |
| |
404 } |
| |
405 |
| |
406 UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) { |
| |
407 // create views |
| |
408 NSScrollView *scrollview = [[NSScrollView alloc] init]; |
| |
409 scrollview.autoresizingMask = NSViewWidthSizable; |
| |
410 scrollview.hasVerticalScroller = YES; |
| |
411 scrollview.hasHorizontalScroller = NO; |
| |
412 scrollview.autohidesScrollers = YES; |
| |
413 |
| |
414 NSOutlineView *outline = [[NSOutlineView alloc]init]; |
| |
415 NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"x"]; |
| |
416 [outline addTableColumn:column]; |
| |
417 outline.outlineTableColumn = column; |
| |
418 outline.headerView = NULL; |
| |
419 outline.rowSizeStyle = NSTableViewRowSizeStyleDefault; |
| |
420 outline.usesAutomaticRowHeights = YES; |
| |
421 outline.indentationPerLevel = 0; |
| |
422 |
| |
423 outline.style = NSTableViewStyleSourceList; |
| |
424 |
| |
425 // Make background transparent so vibrancy shows through |
| |
426 scrollview.drawsBackground = NO; |
| |
427 |
| |
428 scrollview.documentView = outline; |
| |
429 |
| |
430 UiLayout layout = UI_ARGS2LAYOUT(args); |
| |
431 ui_container_add(obj, scrollview, &layout); |
| |
432 |
| |
433 // datasource and delegate |
| |
434 UiSourceList *data = [[UiSourceList alloc] init:obj outline:outline]; |
| |
435 data.sublists = copy_sublists(obj->ctx->allocator, args); |
| |
436 UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->dynamic_sublist, args->varname, UI_VAR_LIST); |
| |
437 if(var) { |
| |
438 UiList *list = var->value; |
| |
439 list->obj = (__bridge void*)data; |
| |
440 list->update = ui_sourcelist_update; |
| |
441 } |
| |
442 data.dynamic_sublists = var; |
| |
443 data.getvalue = args->getvalue; |
| |
444 data.getvaluedata = args->getvaluedata; |
| |
445 data.onactivate = args->onactivate; |
| |
446 data.onactivatedata = args->onactivatedata; |
| |
447 data.onbuttonclick = args->onbuttonclick; |
| |
448 data.onactivatedata = args->onbuttonclickdata; |
| |
449 [data update:-1]; |
| |
450 |
| |
451 outline.dataSource = data; |
| |
452 outline.delegate = data; |
| |
453 |
| |
454 [data update:-1]; |
| |
455 |
| |
456 objc_setAssociatedObject(outline, "ui_datasource", data, OBJC_ASSOCIATION_RETAIN); |
| |
457 |
| |
458 return (__bridge void*)scrollview; |
| |
459 } |
| |
460 |
| |
461 void ui_sourcelist_update(UiList *list, int row) { |
| |
462 UiSourceList *sourcelist = (__bridge UiSourceList*)list->obj; |
| |
463 [sourcelist update:row]; |
| |
464 } |
| |
465 |
| |
466 |
| |
467 /* |
| |
468 * Data Source and Delegate for the sourcelist NSOutlineView |
| |
469 */ |
| |
470 @implementation UiSourceList |
| |
471 |
| |
472 - (id)init:(UiObject*)obj outline:(NSOutlineView*)view { |
| |
473 _obj = obj; |
| |
474 _outlineView = view; |
| |
475 _sections = [[NSMutableArray alloc] initWithCapacity:16]; |
| |
476 return self; |
| |
477 } |
| |
478 |
| |
479 - (void)dealloc { |
| |
480 cxListFree(_sublists); |
| |
481 } |
| |
482 |
| |
483 - (void)update:(int)row { |
| |
484 // TODO: check row |
| |
485 |
| |
486 [_sections removeAllObjects]; |
| |
487 |
| |
488 CxIterator i = cxListIterator(_sublists); |
| |
489 int index = 0; |
| |
490 int rownum = 0; |
| |
491 cx_foreach(UiSubList *, sl, i) { |
| |
492 UiSourceListItem *section = [[UiSourceListItem alloc] init:self sublist:sl]; |
| |
493 section.sublistIndex = index; |
| |
494 section.rownum = rownum; |
| |
495 section.sublistStartRow = rownum; |
| |
496 [section update:-1]; |
| |
497 [_sections addObject:section]; |
| |
498 index++; |
| |
499 rownum += 1 + section.items.count; |
| |
500 } |
| |
501 |
| |
502 [_outlineView reloadData]; |
| |
503 [_outlineView expandItem:nil expandChildren:YES]; |
| |
504 } |
| |
505 |
| |
506 // NSOutlineViewDataSource implementation |
| |
507 |
| |
508 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { |
| |
509 if(item == nil) { |
| |
510 return _sections.count; |
| |
511 } else { |
| |
512 UiSourceListItem *i = item; |
| |
513 return i.items.count; |
| |
514 } |
| |
515 } |
| |
516 |
| |
517 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { |
| |
518 UiSourceListItem *i = item; |
| |
519 return [i isSection] ? YES : NO; |
| |
520 } |
| |
521 |
| |
522 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { |
| |
523 UiSourceListItem *i = item; |
| |
524 if(i) { |
| |
525 return [i.items objectAtIndex:index]; |
| |
526 } |
| |
527 return [_sections objectAtIndex:index]; |
| |
528 } |
| |
529 |
| |
530 - (void)outlineView:(NSOutlineView *)outlineView |
| |
531 setObjectValue:(id)object |
| |
532 forTableColumn:(NSTableColumn *)tableColumn |
| |
533 byItem:(id)item |
| |
534 { |
| |
535 |
| |
536 } |
| |
537 |
| |
538 // NSOutlineViewDelegate implementation |
| |
539 |
| |
540 - (NSView *)outlineView:(NSOutlineView *)outlineView |
| |
541 viewForTableColumn:(NSTableColumn *)tableColumn |
| |
542 item:(id)item |
| |
543 { |
| |
544 UiSourceListItem *i = item; |
| |
545 |
| |
546 NSTableCellView *cell = [[NSTableCellView alloc] init]; |
| |
547 cell.identifier = @"cell"; |
| |
548 // Icon |
| |
549 NSImageView *iconView = [[NSImageView alloc] initWithFrame:NSZeroRect]; |
| |
550 iconView.translatesAutoresizingMaskIntoConstraints = NO; |
| |
551 [cell addSubview:iconView]; |
| |
552 cell.imageView = iconView; |
| |
553 |
| |
554 // Label |
| |
555 //NSTextField *textField = [NSTextField labelWithString:@""]; |
| |
556 NSTextField *textField = [[NSTextField alloc] initWithFrame:NSZeroRect]; |
| |
557 textField.translatesAutoresizingMaskIntoConstraints = NO; |
| |
558 textField.bezeled = NO; |
| |
559 textField.editable = NO; |
| |
560 textField.drawsBackground = NO; |
| |
561 textField.selectable = NO; |
| |
562 textField.lineBreakMode = NSLineBreakByTruncatingTail; |
| |
563 |
| |
564 |
| |
565 [cell addSubview:textField]; |
| |
566 cell.textField = textField; |
| |
567 |
| |
568 if([i isSection]) { |
| |
569 NSFont *font = [NSFont boldSystemFontOfSize:[NSFont systemFontSize]*0.85]; |
| |
570 //NSFont *font = [NSFont preferredFontForTextStyle:NSFontTextStyleCaption1 options:@{}]; |
| |
571 NSDictionary *attrs = @{ |
| |
572 NSFontAttributeName: font, |
| |
573 NSForegroundColorAttributeName: [NSColor tertiaryLabelColor] |
| |
574 }; |
| |
575 textField.attributedStringValue = [[NSAttributedString alloc] initWithString:i.label attributes:attrs]; |
| |
576 |
| |
577 // Layout constraints |
| |
578 [NSLayoutConstraint activateConstraints:@[ |
| |
579 [iconView.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0], |
| |
580 [iconView.bottomAnchor constraintEqualToAnchor:cell.bottomAnchor constant:-1], |
| |
581 |
| |
582 [textField.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0], |
| |
583 [textField.bottomAnchor constraintEqualToAnchor:cell.bottomAnchor constant:-1], |
| |
584 [textField.trailingAnchor constraintEqualToAnchor:cell.trailingAnchor constant:0], |
| |
585 ]]; |
| |
586 } else { |
| |
587 textField.stringValue = i.label; |
| |
588 |
| |
589 // Layout constraints |
| |
590 [NSLayoutConstraint activateConstraints:@[ |
| |
591 [iconView.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0], |
| |
592 [iconView.centerYAnchor constraintEqualToAnchor:cell.centerYAnchor], |
| |
593 |
| |
594 [textField.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0], |
| |
595 [textField.centerYAnchor constraintEqualToAnchor:cell.centerYAnchor], |
| |
596 [textField.trailingAnchor constraintEqualToAnchor:cell.trailingAnchor constant:0], |
| |
597 ]]; |
| |
598 } |
| |
599 |
| |
600 return cell; |
| |
601 } |
| |
602 |
| |
603 - (NSTableRowView *) outlineView:(NSOutlineView *) outlineView |
| |
604 rowViewForItem:(id)item { |
| |
605 UiSourceListItem *it = item; |
| |
606 UiSourceListRow *row = [[UiSourceListRow alloc]init]; |
| |
607 if([it isSection] && it.sublist->header) { |
| |
608 row.showDisclosureButton = YES; |
| |
609 } |
| |
610 return row; |
| |
611 } |
| |
612 |
| |
613 - (BOOL) outlineView:(NSOutlineView *) outlineView |
| |
614 shouldSelectItem:(id)item |
| |
615 { |
| |
616 UiSourceListItem *i = item; |
| |
617 return [i isSection] ? NO : YES; |
| |
618 } |
| |
619 |
| |
620 - (CGFloat) outlineView:(NSOutlineView *) outlineView |
| |
621 heightOfRowByItem:(id) item |
| |
622 { |
| |
623 UiSourceListItem *i = item; |
| |
624 CGFloat rowHeight = outlineView.rowHeight; |
| |
625 if([i isSection]) { |
| |
626 if(i.sublist->header) { |
| |
627 rowHeight += i.sublistIndex == 0 ? -12 : 4; |
| |
628 } else { |
| |
629 rowHeight = i.sublistIndex == 0 ? 0.1 : 12; |
| |
630 } |
| |
631 } |
| |
632 return rowHeight; |
| |
633 } |
| |
634 |
| |
635 - (void) outlineViewSelectionDidChange:(NSNotification *) notification { |
| |
636 UiEvent event; |
| |
637 event.obj = _obj; |
| |
638 event.window = event.obj->window; |
| |
639 event.document = event.obj->ctx->document; |
| |
640 event.eventdata = NULL; |
| |
641 event.eventdatatype = 0; |
| |
642 event.intval = 0; |
| |
643 event.set = ui_get_setop(); |
| |
644 |
| |
645 UiSubListEventData sublistEvent; |
| |
646 |
| |
647 NSInteger selectedRow = _outlineView.selectedRow; |
| |
648 if(selectedRow >= 0) { |
| |
649 UiSourceListItem *item = [_outlineView itemAtRow:selectedRow]; |
| |
650 UiSourceListItem *parent = item.parent; |
| |
651 UiSubList *sublist = parent != nil ? parent.sublist : item.sublist; |
| |
652 UiVar *var = parent != nil ? parent.var : item.var; |
| |
653 if(item && var) { |
| |
654 sublistEvent.list = var->value; |
| |
655 sublistEvent.sublist_index = parent ? parent.sublistIndex : item.sublistIndex; |
| |
656 sublistEvent.row_index = (int)selectedRow - item.sublistStartRow - 1; |
| |
657 sublistEvent.sublist_userdata = sublist ? sublist->userdata : NULL; |
| |
658 sublistEvent.event_data = item.eventdata; |
| |
659 sublistEvent.row_data = sublistEvent.list->get(sublistEvent.list, sublistEvent.row_index); |
| |
660 |
| |
661 event.eventdata = &sublistEvent; |
| |
662 event.eventdatatype = UI_EVENT_DATA_SUBLIST; |
| |
663 } |
| |
664 } |
| |
665 |
| |
666 if(_onactivate) { |
| |
667 _onactivate(&event, _onactivatedata); |
| |
668 } |
| |
669 } |
| |
670 |
| |
671 @end |
| |
672 |
| |
673 /* |
| |
674 * Outline datasource item |
| |
675 * Is used for sections (sublists) and individual items |
| |
676 */ |
| |
677 @implementation UiSourceListItem |
| |
678 |
| |
679 - (id)init:(UiSourceList*)sourcelist sublist:(UiSubList*)sublist { |
| |
680 _sourcelist = sourcelist; |
| |
681 _sublist = sublist; |
| |
682 _items = [[NSMutableArray alloc]initWithCapacity:16]; |
| |
683 if(sublist->header) { |
| |
684 _label = [[NSString alloc]initWithUTF8String:sublist->header]; |
| |
685 } else { |
| |
686 _label = @""; |
| |
687 } |
| |
688 UiVar *var = uic_widget_var(sourcelist.obj->ctx, |
| |
689 sourcelist.obj->ctx, |
| |
690 sublist->value, |
| |
691 sublist->varname, |
| |
692 UI_VAR_LIST); |
| |
693 _var = var; |
| |
694 return self; |
| |
695 } |
| |
696 |
| |
697 - (id)init:(UiSubListItem*)item parent:(UiSourceListItem*)parent { |
| |
698 _parent = parent; |
| |
699 if(item->label) { |
| |
700 _label = [[NSString alloc]initWithUTF8String:item->label]; |
| |
701 } else { |
| |
702 _label = @""; |
| |
703 } |
| |
704 _eventdata = item->eventdata; |
| |
705 return self; |
| |
706 } |
| |
707 |
| |
708 - (BOOL)isSection { |
| |
709 return _sublist != NULL; |
| |
710 } |
| |
711 |
| |
712 - (void)update:(int)row { |
| |
713 // TODO: check row |
| |
714 |
| |
715 [_items removeAllObjects]; |
| |
716 if(_var == NULL) { |
| |
717 return; |
| |
718 } |
| |
719 UiList *list = _var->value; |
| |
720 void *elm = list->first(list); |
| |
721 int index = 0; |
| |
722 while(elm) { |
| |
723 UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; |
| |
724 if(_sourcelist.getvalue) { |
| |
725 _sourcelist.getvalue(list, _sublist->userdata, elm, index, &item, _sourcelist.getvaluedata); |
| |
726 } else { |
| |
727 item.label = strdup(elm); |
| |
728 } |
| |
729 |
| |
730 UiSourceListItem *it = [[UiSourceListItem alloc] init:&item parent:self]; |
| |
731 it.sublistIndex = index; |
| |
732 it.rownum = self.rownum + index; |
| |
733 it.sublistStartRow = _parent ? _parent.sublistStartRow : _sublistStartRow; |
| |
734 [_items addObject:it]; |
| |
735 |
| |
736 elm = list->next(list); |
| |
737 index++; |
| |
738 } |
| |
739 } |
| |
740 |
| |
741 @end |
| |
742 |
| |
743 /* |
| |
744 * Custom NSTableRowView implementation |
| |
745 * Moves the disclosure button to the right side |
| |
746 * Handles mouse hover events (for hiding the disclosure button) |
| |
747 */ |
| |
748 @implementation UiSourceListRow |
| |
749 |
| |
750 - (void)layout { |
| |
751 [super layout]; |
| |
752 |
| |
753 for (NSView *subview in self.subviews) { |
| |
754 if ([subview.identifier isEqualToString:NSOutlineViewDisclosureButtonKey] || |
| |
755 [subview.identifier isEqualToString:NSOutlineViewShowHideButtonKey]) |
| |
756 { |
| |
757 NSRect frame = subview.frame; |
| |
758 frame.origin.x = self.bounds.size.width - frame.size.width - 16.0; |
| |
759 subview.frame = frame; |
| |
760 |
| |
761 if(!_hover) { |
| |
762 subview.hidden = YES; |
| |
763 } |
| |
764 |
| |
765 if(subview != _disclosureButton) { |
| |
766 // init disclosure button |
| |
767 _disclosureButton = (NSButton*)subview; |
| |
768 if ([subview isKindOfClass:[NSButton class]]) { |
| |
769 NSButton *button = (NSButton*)subview; |
| |
770 button.contentTintColor = [NSColor tertiaryLabelColor]; |
| |
771 } |
| |
772 } |
| |
773 |
| |
774 |
| |
775 } else if ([subview.identifier isEqualToString:@"cell"]) { |
| |
776 NSRect frame = subview.frame; |
| |
777 frame.origin.x = 16; |
| |
778 subview.frame = frame; |
| |
779 } |
| |
780 } |
| |
781 } |
| |
782 |
| |
783 - (void)updateTrackingAreas { |
| |
784 [super updateTrackingAreas]; |
| |
785 if(_trackingArea != nil) { |
| |
786 [self removeTrackingArea:_trackingArea]; |
| |
787 } |
| |
788 _trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds |
| |
789 options:NSTrackingMouseEnteredAndExited | |
| |
790 NSTrackingActiveInActiveApp | |
| |
791 NSTrackingInVisibleRect |
| |
792 owner:self |
| |
793 userInfo:nil]; |
| |
794 [self addTrackingArea:_trackingArea]; |
| |
795 } |
| |
796 |
| |
797 - (void)mouseEntered:(NSEvent *)event { |
| |
798 _hover = YES; |
| |
799 _disclosureButton.hidden = _showDisclosureButton ? NO : YES; |
| |
800 } |
| |
801 |
| |
802 - (void)mouseExited:(NSEvent *)event { |
| |
803 _hover = NO; |
| |
804 _disclosureButton.hidden = YES; |
| |
805 } |
| |
806 |
| |
807 @end |