/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2025 Olaf Wintermann. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#import "list.h"
#import "ListDelegate.h"
#import <objc/runtime.h>
#import <inttypes.h>
#import <limits.h>
#import <cx/array_list.h>
static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata;
return getvalue(elm, col);
}
static void* str_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
return elm;
}
/* --------------------------- ListView --------------------------- */
/*
* adds a NSTableViewDelegate that handles all events and calls
* callbacks specified in the UiListArgs
*/
static void add_listdelegate(UiObject *obj, NSTableView *tableview, UiListArgs *args) {
ListDelegate *delegate = [[ListDelegate alloc] init:tableview obj:obj];
delegate.onactivate = args->onactivate;
delegate.onactivatedata = args->onactivatedata;
delegate.onselection = args->onselection;
delegate.onselectiondata = args->onselectiondata;
tableview.delegate = delegate;
objc_setAssociatedObject(tableview, "ui_listdelegate", delegate, OBJC_ASSOCIATION_RETAIN);
tableview.doubleAction = @selector(activateEvent:);
tableview.target = delegate;
}
static void bind_list_to_tableview(UiList *list, NSTableView *tableview) {
list->obj = (__bridge void*)tableview;
list->update = ui_tableview_update;
list->getselection = ui_tableview_getselection;
list->setselection = ui_tableview_setselection;
}
UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) {
NSScrollView *scrollview = [[NSScrollView alloc] init];
NSTableView *tableview = [[NSTableView alloc] init];
tableview.autoresizingMask = NSViewWidthSizable;
tableview.headerView = nil;
if(args->multiselection) {
tableview.allowsMultipleSelection = YES;
}
scrollview.documentView = tableview;
UiLayout layout = UI_INIT_LAYOUT(args);
ui_container_add(obj, scrollview, &layout);
add_listdelegate(obj, tableview, args);
char **static_elements = args->static_elements;
size_t static_nelm = args->static_nelm;
UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
if(var) {
UiList *list = var->value;
bind_list_to_tableview(list, tableview);
ui_getvaluefunc2 getvalue = args->getvalue2;
void *getvaluedata = args->getvalue2data;
if(!getvalue) {
if(args->getvalue) {
getvalue = getvalue_wrapper;
getvaluedata = (void*)args->getvalue;
} else {
getvalue = str_getvalue; // by default list values are interpreted as strings
}
}
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"column"];
[tableview addTableColumn:column];
ListDataSource *dataSource = [[ListDataSource alloc] init:tableview.tableColumns var:var getvalue:getvalue getvaluedata:getvaluedata];
tableview.dataSource = dataSource;
[tableview reloadData];
objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN);
} else if(static_elements && static_nelm) {
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"column"];
[tableview addTableColumn:column];
ArrayDataSource *dataSource = [[ArrayDataSource alloc]init:static_elements size:static_nelm];
tableview.dataSource = dataSource;
[tableview reloadData];
objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN);
}
return (__bridge void*)scrollview;
}
/* --------------------------- TableView --------------------------- */
UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) {
NSScrollView *scrollview = [[NSScrollView alloc] init];
NSTableView *tableview = [[NSTableView alloc] init];
tableview.autoresizingMask = NSViewWidthSizable;
tableview.columnAutoresizingStyle = NSTableViewSequentialColumnAutoresizingStyle;
if(args->multiselection) {
tableview.allowsMultipleSelection = YES;
}
UiLayout layout = UI_INIT_LAYOUT(args);
ui_container_add(obj, scrollview, &layout);
add_listdelegate(obj, tableview, args);
// convert model
NSMutableArray<NSTableColumn*> *cols = [[NSMutableArray alloc] init];
UiModel *model = args->model;
if(model) {
for(int i=0;i<model->columns;i++) {
char *title = model->titles[i];
UiModelType type = model->types[i];
int width = model->columnsize[i];
NSString *identifier = [[NSString alloc] initWithUTF8String:title];
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:identifier];
column.title = identifier;
column.resizingMask = NSTableColumnUserResizingMask;
if(width > 0) {
column.width = width;
} else if(width < 0) {
column.resizingMask = NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask;
}
if(type >= UI_ICON) {
// TODO
}
[tableview addTableColumn:column];
[cols addObject:column];
}
}
UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
if(var) {
UiList *list = var->value;
bind_list_to_tableview(list, tableview);
ui_getvaluefunc2 getvalue = args->getvalue2;
void *getvaluedata = args->getvalue2data;
if(!getvalue) {
if(args->getvalue) {
getvalue = getvalue_wrapper;
getvaluedata = (void*)args->getvalue;
} else {
fprintf(stderr, "Error: tableview requires getvalue or getvalue2 func\n");
return (__bridge void*)scrollview;
}
}
ListDataSource *dataSource = [[ListDataSource alloc] init:cols var:var getvalue:getvalue getvaluedata:getvaluedata];
if(model) {
dataSource.model = model;
}
tableview.dataSource = dataSource;
[tableview reloadData];
objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN);
}
scrollview.documentView = tableview;
return (__bridge void*)scrollview;
}
/* ------ common functions ------ */
void ui_tableview_update(UiList *list, int i) {
NSTableView *tableview = (__bridge NSTableView*)list->obj;
if(i < 0) {
[tableview reloadData];
} else {
[tableview reloadData]; // TODO: optimize
}
}
UiListSelection ui_tableview_getselection(UiList *list) {
NSTableView *tableview = (__bridge NSTableView*)list->obj;
return ui_tableview_selection(tableview);
}
void ui_tableview_setselection(UiList *list, UiListSelection selection) {
NSTableView *tableview = (__bridge NSTableView*)list->obj;
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
for(int i=0;i<selection.count;i++) {
[indexSet addIndex:selection.rows[i]];
}
[tableview selectRowIndexes:indexSet byExtendingSelection:NO];
}
/* --------------------------- DropDown --------------------------- */
@implementation UiDropDown
- (id)init:(UiObject*)obj {
_obj = obj;
return self;
}
- (void) comboBoxSelectionDidChange:(NSNotification *) notification {
int index = (int)_combobox.indexOfSelectedItem;
void *eventdata = NULL;
if(_var) {
UiList *list = _var->value;
if(index >= 0) {
eventdata = list->get(list, index);
}
} else {
NSString *str = _combobox.objectValueOfSelectedItem;
if(str) {
eventdata = (void*)str.UTF8String;
}
}
UiEvent event;
event.obj = _obj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = eventdata;
event.eventdatatype = UI_EVENT_DATA_LIST_ELM;
event.intval = index;
if(_onselection) {
_onselection(&event, _onselectiondata);
}
if(_onactivate) {
_onactivate(&event, _onactivatedata);
}
}
@end
UIWIDGET ui_dropdown_create(UiObject* obj, UiListArgs *args) {
NSComboBox *dropdown = [[NSComboBox alloc] init];
dropdown.editable = NO;
UiDropDown *uidropdown = [[UiDropDown alloc] init:obj];
objc_setAssociatedObject(dropdown, "ui_dropdown", uidropdown, OBJC_ASSOCIATION_RETAIN);
uidropdown.onactivate = args->onactivate;
uidropdown.onactivatedata = args->onactivatedata;
uidropdown.onselection = args->onselection;
uidropdown.onselectiondata = args->onselectiondata;
uidropdown.combobox = dropdown;
if(!args->getvalue2) {
if(args->getvalue) {
args->getvalue2 = getvalue_wrapper;
args->getvalue2data = (void*)args->getvalue;
} else {
args->getvalue2 = str_getvalue;
}
}
uidropdown.getvalue = args->getvalue2;
uidropdown.getvaluedata = args->getvalue2data;
UiLayout layout = UI_INIT_LAYOUT(args);
ui_container_add(obj, dropdown, &layout);
UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
if(var) {
UiList *list = var->value;
list->obj = (__bridge void*)dropdown;
list->update = ui_dropdown_update;
list->getselection = ui_dropdown_getselection;
list->setselection = ui_dropdown_setselection;
ui_dropdown_update(list, -1);
} else {
for(int i=0;i<args->static_nelm;i++) {
char *str = args->static_elements[i];
NSString *item = [[NSString alloc] initWithUTF8String:str];
[dropdown addItemWithObjectValue:item];
}
}
uidropdown.var = var;
return (__bridge void*)dropdown;
}
void ui_dropdown_update(UiList *list, int i) {
NSComboBox *combobox = (__bridge NSComboBox*)list->obj;
UiDropDown *dropdown = objc_getAssociatedObject(combobox, "ui_dropdown");
if(dropdown) {
[combobox removeAllItems];
ui_getvaluefunc2 getvalue = dropdown.getvalue;
void *getvaluedata = dropdown.getvaluedata;
int index = 0;
void *elm = list->first(list);
while(elm) {
UiBool freeResult = FALSE;
char *str = getvalue(list, elm, index, 0, getvaluedata, &freeResult);
if(str) {
NSString *item = [[NSString alloc] initWithUTF8String:str];
[combobox addItemWithObjectValue:item];
}
if(freeResult) {
free(str);
}
elm = list->next(list);
index++;
}
} else {
fprintf(stderr, "Error: obj is not a dropdown\n");
}
}
UiListSelection ui_dropdown_getselection(UiList *list) {
UiListSelection sel = { 0, NULL };
NSComboBox *combobox = (__bridge NSComboBox*)list->obj;
NSInteger index = combobox.indexOfSelectedItem;
if(index >= 0) {
sel.rows = malloc(sizeof(int));
sel.count = 1;
sel.rows[0] = (int)index;
}
return sel;
}
void ui_dropdown_setselection(UiList *list, UiListSelection selection) {
NSComboBox *combobox = (__bridge NSComboBox*)list->obj;
if(selection.count > 0) {
[combobox selectItemAtIndex:selection.rows[0]];
} else {
[combobox selectItemAtIndex: -1];
}
}
/* --------------------------- SourceList --------------------------- */
static ui_sourcelist_update_func sclist_update_callback = NULL;
void ui_sourcelist_set_update_callback(ui_sourcelist_update_func cb) {
sclist_update_callback = cb;
}
void ui_sourcelist_updated(void) {
if(sclist_update_callback) {
sclist_update_callback();
}
}
static void sublist_free(const CxAllocator *a, UiSubList *sl) {
cxFree(a, (char*)sl->varname);
cxFree(a, (char*)sl->header);
}
static UiSubList copy_sublist(const CxAllocator *a, UiSubList *sl) {
UiSubList new_sl;
new_sl.value = sl->value;
new_sl.varname = sl->varname ? cx_strdup_a(a, cx_str(sl->varname)).ptr : NULL;
new_sl.header = sl->header ? cx_strdup_a(a, cx_str(sl->header)).ptr : NULL;
new_sl.separator = sl->separator;
new_sl.userdata = sl->userdata;
return new_sl;
}
static CxList* copy_sublists(const CxAllocator *a, UiSourceListArgs *args) {
if(args->sublists) {
size_t max = args->numsublists;
if(max == 0) {
max = INT_MAX;
}
CxList *sublists = cxArrayListCreate(a, NULL, sizeof(UiSubList), args->numsublists);
sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_free;
for(int i=0;i<max;i++) {
UiSubList *sl = &args->sublists[i];
if(sl->value == NULL && sl->varname == NULL) {
break;
}
UiSubList new_sl = copy_sublist(a, sl);
cxListAdd(sublists, &new_sl);
}
return sublists;
}
return NULL;
}
UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) {
// create views
NSScrollView *scrollview = [[NSScrollView alloc] init];
scrollview.autoresizingMask = NSViewWidthSizable;
scrollview.hasVerticalScroller = YES;
scrollview.hasHorizontalScroller = NO;
scrollview.autohidesScrollers = YES;
NSOutlineView *outline = [[NSOutlineView alloc]init];
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"x"];
[outline addTableColumn:column];
outline.outlineTableColumn = column;
outline.headerView = NULL;
outline.rowSizeStyle = NSTableViewRowSizeStyleDefault;
outline.usesAutomaticRowHeights = YES;
outline.indentationPerLevel = 0;
outline.style = NSTableViewStyleSourceList;
// Make background transparent so vibrancy shows through
scrollview.drawsBackground = NO;
scrollview.documentView = outline;
UiLayout layout = UI_ARGS2LAYOUT(args);
ui_container_add(obj, scrollview, &layout);
// datasource and delegate
UiSourceList *data = [[UiSourceList alloc] init:obj outline:outline];
data.sublists = copy_sublists(obj->ctx->allocator, args);
UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->dynamic_sublist, args->varname, UI_VAR_LIST);
if(var) {
UiList *list = var->value;
list->obj = (__bridge void*)data;
list->update = ui_sourcelist_update;
}
data.dynamic_sublists = var;
data.getvalue = args->getvalue;
data.getvaluedata = args->getvaluedata;
data.onactivate = args->onactivate;
data.onactivatedata = args->onactivatedata;
data.onbuttonclick = args->onbuttonclick;
data.onactivatedata = args->onbuttonclickdata;
[data update:-1];
outline.dataSource = data;
outline.delegate = data;
[data update:-1];
objc_setAssociatedObject(outline, "ui_datasource", data, OBJC_ASSOCIATION_RETAIN);
return (__bridge void*)scrollview;
}
void ui_sourcelist_update(UiList *list, int row) {
UiSourceList *sourcelist = (__bridge UiSourceList*)list->obj;
[sourcelist update:row];
}
/*
* Data Source and Delegate for the sourcelist NSOutlineView
*/
@implementation UiSourceList
- (id)init:(UiObject*)obj outline:(NSOutlineView*)view {
_obj = obj;
_outlineView = view;
_sections = [[NSMutableArray alloc] initWithCapacity:16];
return self;
}
- (void)dealloc {
cxListFree(_sublists);
}
- (void)update:(int)row {
// TODO: check row
[_sections removeAllObjects];
CxIterator i = cxListIterator(_sublists);
int index = 0;
int rownum = 0;
cx_foreach(UiSubList *, sl, i) {
UiSourceListItem *section = [[UiSourceListItem alloc] init:self sublist:sl];
section.sublistIndex = index;
section.rownum = rownum;
section.sublistStartRow = rownum;
[section update:-1];
[_sections addObject:section];
index++;
rownum += 1 + section.items.count;
}
[_outlineView reloadData];
[_outlineView expandItem:nil expandChildren:YES];
}
// NSOutlineViewDataSource implementation
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
if(item == nil) {
return _sections.count;
} else {
UiSourceListItem *i = item;
return i.items.count;
}
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
UiSourceListItem *i = item;
return [i isSection] ? YES : NO;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
UiSourceListItem *i = item;
if(i) {
return [i.items objectAtIndex:index];
}
return [_sections objectAtIndex:index];
}
- (void)outlineView:(NSOutlineView *)outlineView
setObjectValue:(id)object
forTableColumn:(NSTableColumn *)tableColumn
byItem:(id)item
{
}
// NSOutlineViewDelegate implementation
- (NSView *)outlineView:(NSOutlineView *)outlineView
viewForTableColumn:(NSTableColumn *)tableColumn
item:(id)item
{
UiSourceListItem *i = item;
NSTableCellView *cell = [[NSTableCellView alloc] init];
cell.identifier = @"cell";
// Icon
NSImageView *iconView = [[NSImageView alloc] initWithFrame:NSZeroRect];
iconView.translatesAutoresizingMaskIntoConstraints = NO;
[cell addSubview:iconView];
cell.imageView = iconView;
// Label
//NSTextField *textField = [NSTextField labelWithString:@""];
NSTextField *textField = [[NSTextField alloc] initWithFrame:NSZeroRect];
textField.translatesAutoresizingMaskIntoConstraints = NO;
textField.bezeled = NO;
textField.editable = NO;
textField.drawsBackground = NO;
textField.selectable = NO;
textField.lineBreakMode = NSLineBreakByTruncatingTail;
[cell addSubview:textField];
cell.textField = textField;
if([i isSection]) {
NSFont *font = [NSFont boldSystemFontOfSize:[NSFont systemFontSize]*0.85];
//NSFont *font = [NSFont preferredFontForTextStyle:NSFontTextStyleCaption1 options:@{}];
NSDictionary *attrs = @{
NSFontAttributeName: font,
NSForegroundColorAttributeName: [NSColor tertiaryLabelColor]
};
textField.attributedStringValue = [[NSAttributedString alloc] initWithString:i.label attributes:attrs];
// Layout constraints
[NSLayoutConstraint activateConstraints:@[
[iconView.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0],
[iconView.bottomAnchor constraintEqualToAnchor:cell.bottomAnchor constant:-1],
[textField.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0],
[textField.bottomAnchor constraintEqualToAnchor:cell.bottomAnchor constant:-1],
[textField.trailingAnchor constraintEqualToAnchor:cell.trailingAnchor constant:0],
]];
} else {
textField.stringValue = i.label;
// Layout constraints
[NSLayoutConstraint activateConstraints:@[
[iconView.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0],
[iconView.centerYAnchor constraintEqualToAnchor:cell.centerYAnchor],
[textField.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0],
[textField.centerYAnchor constraintEqualToAnchor:cell.centerYAnchor],
[textField.trailingAnchor constraintEqualToAnchor:cell.trailingAnchor constant:0],
]];
}
return cell;
}
- (NSTableRowView *) outlineView:(NSOutlineView *) outlineView
rowViewForItem:(id)item {
UiSourceListItem *it = item;
UiSourceListRow *row = [[UiSourceListRow alloc]init];
if([it isSection] && it.sublist->header) {
row.showDisclosureButton = YES;
}
return row;
}
- (BOOL) outlineView:(NSOutlineView *) outlineView
shouldSelectItem:(id)item
{
UiSourceListItem *i = item;
return [i isSection] ? NO : YES;
}
- (CGFloat) outlineView:(NSOutlineView *) outlineView
heightOfRowByItem:(id) item
{
UiSourceListItem *i = item;
CGFloat rowHeight = outlineView.rowHeight;
if([i isSection]) {
if(i.sublist->header) {
rowHeight += i.sublistIndex == 0 ? -12 : 4;
} else {
rowHeight = i.sublistIndex == 0 ? 0.1 : 12;
}
}
return rowHeight;
}
- (void) outlineViewSelectionDidChange:(NSNotification *) notification {
UiEvent event;
event.obj = _obj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = NULL;
event.eventdatatype = 0;
event.intval = 0;
event.set = ui_get_setop();
UiSubListEventData sublistEvent;
NSInteger selectedRow = _outlineView.selectedRow;
if(selectedRow >= 0) {
UiSourceListItem *item = [_outlineView itemAtRow:selectedRow];
UiSourceListItem *parent = item.parent;
UiSubList *sublist = parent != nil ? parent.sublist : item.sublist;
UiVar *var = parent != nil ? parent.var : item.var;
if(item && var) {
sublistEvent.list = var->value;
sublistEvent.sublist_index = parent ? parent.sublistIndex : item.sublistIndex;
sublistEvent.row_index = (int)selectedRow - item.sublistStartRow - 1;
sublistEvent.sublist_userdata = sublist ? sublist->userdata : NULL;
sublistEvent.event_data = item.eventdata;
sublistEvent.row_data = sublistEvent.list->get(sublistEvent.list, sublistEvent.row_index);
event.eventdata = &sublistEvent;
event.eventdatatype = UI_EVENT_DATA_SUBLIST;
}
}
if(_onactivate) {
_onactivate(&event, _onactivatedata);
}
}
@end
/*
* Outline datasource item
* Is used for sections (sublists) and individual items
*/
@implementation UiSourceListItem
- (id)init:(UiSourceList*)sourcelist sublist:(UiSubList*)sublist {
_sourcelist = sourcelist;
_sublist = sublist;
_items = [[NSMutableArray alloc]initWithCapacity:16];
if(sublist->header) {
_label = [[NSString alloc]initWithUTF8String:sublist->header];
} else {
_label = @"";
}
UiVar *var = uic_widget_var(sourcelist.obj->ctx,
sourcelist.obj->ctx,
sublist->value,
sublist->varname,
UI_VAR_LIST);
_var = var;
return self;
}
- (id)init:(UiSubListItem*)item parent:(UiSourceListItem*)parent {
_parent = parent;
if(item->label) {
_label = [[NSString alloc]initWithUTF8String:item->label];
} else {
_label = @"";
}
_eventdata = item->eventdata;
return self;
}
- (BOOL)isSection {
return _sublist != NULL;
}
- (void)update:(int)row {
// TODO: check row
[_items removeAllObjects];
if(_var == NULL) {
return;
}
UiList *list = _var->value;
void *elm = list->first(list);
int index = 0;
while(elm) {
UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL };
if(_sourcelist.getvalue) {
_sourcelist.getvalue(list, _sublist->userdata, elm, index, &item, _sourcelist.getvaluedata);
} else {
item.label = strdup(elm);
}
UiSourceListItem *it = [[UiSourceListItem alloc] init:&item parent:self];
it.sublistIndex = index;
it.rownum = self.rownum + index;
it.sublistStartRow = _parent ? _parent.sublistStartRow : _sublistStartRow;
[_items addObject:it];
elm = list->next(list);
index++;
}
}
@end
/*
* Custom NSTableRowView implementation
* Moves the disclosure button to the right side
* Handles mouse hover events (for hiding the disclosure button)
*/
@implementation UiSourceListRow
- (void)layout {
[super layout];
for (NSView *subview in self.subviews) {
if ([subview.identifier isEqualToString:NSOutlineViewDisclosureButtonKey] ||
[subview.identifier isEqualToString:NSOutlineViewShowHideButtonKey])
{
NSRect frame = subview.frame;
frame.origin.x = self.bounds.size.width - frame.size.width - 16.0;
subview.frame = frame;
if(!_hover) {
subview.hidden = YES;
}
if(subview != _disclosureButton) {
// init disclosure button
_disclosureButton = (NSButton*)subview;
if ([subview isKindOfClass:[NSButton class]]) {
NSButton *button = (NSButton*)subview;
button.contentTintColor = [NSColor tertiaryLabelColor];
}
}
} else if ([subview.identifier isEqualToString:@"cell"]) {
NSRect frame = subview.frame;
frame.origin.x = 16;
subview.frame = frame;
}
}
}
- (void)updateTrackingAreas {
[super updateTrackingAreas];
if(_trackingArea != nil) {
[self removeTrackingArea:_trackingArea];
}
_trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds
options:NSTrackingMouseEnteredAndExited |
NSTrackingActiveInActiveApp |
NSTrackingInVisibleRect
owner:self
userInfo:nil];
[self addTrackingArea:_trackingArea];
}
- (void)mouseEntered:(NSEvent *)event {
_hover = YES;
_disclosureButton.hidden = _showDisclosureButton ? NO : YES;
}
- (void)mouseExited:(NSEvent *)event {
_hover = NO;
_disclosureButton.hidden = YES;
}
@end