ui/cocoa/list.m

changeset 112
c3f2f16fa4b8
parent 109
c3dfcb8f0be7
child 113
dde28a806552
equal deleted inserted replaced
111:81c4f73236a4 112:c3f2f16fa4b8
28 28
29 #import "list.h" 29 #import "list.h"
30 #import "ListDelegate.h" 30 #import "ListDelegate.h"
31 #import <objc/runtime.h> 31 #import <objc/runtime.h>
32 32
33 #import <inttypes.h>
34 #import <limits.h>
35
36 #import <cx/array_list.h>
37
33 static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { 38 static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
34 ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata; 39 ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata;
35 return getvalue(elm, col); 40 return getvalue(elm, col);
36 } 41 }
37 42
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

mercurial