// 3 june 2018 #import "uipriv_darwin.h" #import "table.h" // values from interface builder #define textColumnLeading 2 #define textColumnTrailing 2 #define imageColumnLeading 3 #define imageTextColumnLeading 7 #define checkboxTextColumnLeading 0 // these aren't provided by IB; let's just choose one #define checkboxColumnLeading imageColumnLeading #define progressBarColumnLeading imageColumnLeading #define progressBarColumnTrailing progressBarColumnLeading #define buttonColumnLeading imageColumnLeading #define buttonColumnTrailing buttonColumnLeading @implementation uiprivTableCellView - (void)uiprivUpdate:(NSInteger)row { [self doesNotRecognizeSelector:_cmd]; } @end @implementation uiprivTableColumn - (uiprivTableCellView *)uiprivMakeCellView { [self doesNotRecognizeSelector:_cmd]; return nil; // appease compiler } @end struct textColumnCreateParams { uiTable *t; uiTableModel *m; BOOL makeTextField; int textModelColumn; int textEditableModelColumn; uiTableTextColumnOptionalParams textParams; BOOL makeImageView; int imageModelColumn; BOOL makeCheckbox; int checkboxModelColumn; int checkboxEditableModelColumn; }; @interface uiprivTextImageCheckboxTableCellView : uiprivTableCellView { uiTable *t; uiTableModel *m; NSTextField *tf; int textModelColumn; int textEditableModelColumn; uiTableTextColumnOptionalParams textParams; NSImageView *iv; int imageModelColumn; NSButton *cb; int checkboxModelColumn; int checkboxEditableModelColumn; } - (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p; - (IBAction)uiprivOnTextFieldAction:(id)sender; - (IBAction)uiprivOnCheckboxAction:(id)sender; @end @implementation uiprivTextImageCheckboxTableCellView - (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p { self = [super initWithFrame:r]; if (self) { NSMutableArray *constraints; self->t = p->t; self->m = p->m; constraints = [NSMutableArray new]; self->tf = nil; if (p->makeTextField) { self->textModelColumn = p->textModelColumn; self->textEditableModelColumn = p->textEditableModelColumn; self->textParams = p->textParams; self->tf = uiprivNewLabel(@""); // TODO set wrap and ellipsize modes? [self->tf setTarget:self]; [self->tf setAction:@selector(uiprivOnTextFieldAction:)]; [self->tf setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addSubview:self->tf]; // TODO for all three controls: set hugging and compression resistance properly [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading, NSLayoutRelationEqual, self->tf, NSLayoutAttributeLeading, 1, -textColumnLeading, @"uiTable cell text leading constraint")]; [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop, NSLayoutRelationEqual, self->tf, NSLayoutAttributeTop, 1, 0, @"uiTable cell text top constraint")]; [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTrailing, NSLayoutRelationEqual, self->tf, NSLayoutAttributeTrailing, 1, textColumnTrailing, @"uiTable cell text trailing constraint")]; [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom, NSLayoutRelationEqual, self->tf, NSLayoutAttributeBottom, 1, 0, @"uiTable cell text bottom constraint")]; } self->iv = nil; if (p->makeImageView) { self->imageModelColumn = p->imageModelColumn; self->iv = [[NSImageView alloc] initWithFrame:NSZeroRect]; [self->iv setImageFrameStyle:NSImageFrameNone]; [self->iv setImageAlignment:NSImageAlignCenter]; [self->iv setImageScaling:NSImageScaleProportionallyDown]; [self->iv setAnimates:NO]; [self->iv setEditable:NO]; [self->iv setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addSubview:self->iv]; [constraints addObject:uiprivMkConstraint(self->iv, NSLayoutAttributeWidth, NSLayoutRelationEqual, self->iv, NSLayoutAttributeHeight, 1, 0, @"uiTable image squareness constraint")]; if (self->tf != nil) { [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading, NSLayoutRelationEqual, self->iv, NSLayoutAttributeLeading, 1, -imageColumnLeading, @"uiTable cell image leading constraint")]; [constraints replaceObjectAtIndex:0 withObject:uiprivMkConstraint(self->iv, NSLayoutAttributeTrailing, NSLayoutRelationEqual, self->tf, NSLayoutAttributeLeading, 1, -imageTextColumnLeading, @"uiTable cell image-text spacing constraint")]; } else [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeCenterX, NSLayoutRelationEqual, self->iv, NSLayoutAttributeCenterX, 1, 0, @"uiTable cell image centering constraint")]; [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop, NSLayoutRelationEqual, self->iv, NSLayoutAttributeTop, 1, 0, @"uiTable cell image top constraint")]; [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom, NSLayoutRelationEqual, self->iv, NSLayoutAttributeBottom, 1, 0, @"uiTable cell image bottom constraint")]; } self->cb = nil; if (p->makeCheckbox) { self->checkboxModelColumn = p->checkboxModelColumn; self->checkboxEditableModelColumn = p->checkboxEditableModelColumn; self->cb = [[NSButton alloc] initWithFrame:NSZeroRect]; [self->cb setTitle:@""]; [self->cb setButtonType:NSSwitchButton]; // doesn't seem to have an associated bezel style [self->cb setBordered:NO]; [self->cb setTransparent:NO]; uiDarwinSetControlFont(self->cb, NSRegularControlSize); [self->cb setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addSubview:self->cb]; if (self->tf != nil) { [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading, NSLayoutRelationEqual, self->cb, NSLayoutAttributeLeading, 1, -imageColumnLeading, @"uiTable cell checkbox leading constraint")]; [constraints replaceObjectAtIndex:0 withObject:uiprivMkConstraint(self->cb, NSLayoutAttributeTrailing, NSLayoutRelationEqual, self->tf, NSLayoutAttributeLeading, 1, -imageTextColumnLeading, @"uiTable cell checkbox-text spacing constraint")]; } else [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeCenterX, NSLayoutRelationEqual, self->cb, NSLayoutAttributeCenterX, 1, 0, @"uiTable cell checkbox centering constraint")]; [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop, NSLayoutRelationEqual, self->cb, NSLayoutAttributeTop, 1, 0, @"uiTable cell checkbox top constraint")]; [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom, NSLayoutRelationEqual, self->cb, NSLayoutAttributeBottom, 1, 0, @"uiTable cell checkbox bottom constraint")]; } [self addConstraints:constraints]; // take advantage of NSTableCellView-provided accessibility features if (self->tf != nil) [self setTextField:self->tf]; if (self->iv != nil) [self setImageView:self->iv]; } return self; } - (void)dealloc { if (self->cb != nil) { [self->cb release]; self->cb = nil; } if (self->iv != nil) { [self->iv release]; self->iv = nil; } if (self->tf != nil) { [self->tf release]; self->tf = nil; } [super dealloc]; } - (void)uiprivUpdate:(NSInteger)row { uiTableValue *value; if (self->tf != nil) { NSString *str; NSColor *color; double r, g, b, a; value = uiprivTableModelCellValue(self->m, row, self->textModelColumn); str = uiprivToNSString(uiTableValueString(value)); uiFreeTableValue(value); [self->tf setStringValue:str]; [self->tf setEditable:uiprivTableModelCellEditable(self->m, row, self->textEditableModelColumn)]; color = [NSColor controlTextColor]; if (uiprivTableModelColorIfProvided(self->m, row, self->textParams.ColorModelColumn, &r, &g, &b, &a)) color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a]; [self->tf setTextColor:color]; // we don't own color in ether case; don't release } if (self->iv != nil) { uiImage *img; value = uiprivTableModelCellValue(self->m, row, self->imageModelColumn); img = uiTableValueImage(value); uiFreeTableValue(value); [self->iv setImage:uiprivImageNSImage(img)]; } if (self->cb != nil) { value = uiprivTableModelCellValue(self->m, row, self->checkboxModelColumn); if (uiTableValueInt(value) != 0) [self->cb setState:NSOnState]; else [self->cb setState:NSOffState]; uiFreeTableValue(value); [self->cb setEnabled:uiprivTableModelCellEditable(self->m, row, self->checkboxEditableModelColumn)]; } } - (IBAction)uiprivOnTextFieldAction:(id)sender { NSInteger row; uiTableValue *value; row = [self->t->tv rowForView:self->tf]; value = uiNewTableValueString([[self->tf stringValue] UTF8String]); uiprivTableModelSetCellValue(self->m, row, self->textModelColumn, value); uiFreeTableValue(value); // always refresh the value in case the model rejected it // TODO document that we do this, but not for the whole row (or decide to do both, or do neither...) [self uiprivUpdate:row]; } - (IBAction)uiprivOnCheckboxAction:(id)sender { NSInteger row; uiTableValue *value; row = [self->t->tv rowForView:self->cb]; value = uiNewTableValueInt([self->cb state] != NSOffState); uiprivTableModelSetCellValue(self->m, row, self->checkboxModelColumn, value); uiFreeTableValue(value); // always refresh the value in case the model rejected it [self uiprivUpdate:row]; } @end @interface uiprivTextImageCheckboxTableColumn : uiprivTableColumn { struct textColumnCreateParams params; } - (id)initWithIdentifier:(NSString *)ident params:(struct textColumnCreateParams *)p; @end @implementation uiprivTextImageCheckboxTableColumn - (id)initWithIdentifier:(NSString *)ident params:(struct textColumnCreateParams *)p { self = [super initWithIdentifier:ident]; if (self) self->params = *p; return self; } - (uiprivTableCellView *)uiprivMakeCellView { uiprivTableCellView *cv; cv = [[uiprivTextImageCheckboxTableCellView alloc] initWithFrame:NSZeroRect params:&(self->params)]; [cv setIdentifier:[self identifier]]; return cv; } @end @interface uiprivProgressBarTableCellView : uiprivTableCellView { uiTable *t; uiTableModel *m; NSProgressIndicator *p; int modelColumn; } - (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc; @end @implementation uiprivProgressBarTableCellView - (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc { self = [super initWithFrame:r]; if (self) { self->t = table; self->m = model; self->modelColumn = mc; self->p = [[NSProgressIndicator alloc] initWithFrame:NSZeroRect]; [self->p setControlSize:NSRegularControlSize]; [self->p setBezeled:YES]; [self->p setStyle:NSProgressIndicatorBarStyle]; [self->p setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addSubview:self->p]; // TODO set hugging and compression resistance properly [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeLeading, NSLayoutRelationEqual, self->p, NSLayoutAttributeLeading, 1, -progressBarColumnLeading, @"uiTable cell progressbar leading constraint")]; [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTop, NSLayoutRelationEqual, self->p, NSLayoutAttributeTop, 1, 0, @"uiTable cell progressbar top constraint")]; [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTrailing, NSLayoutRelationEqual, self->p, NSLayoutAttributeTrailing, 1, progressBarColumnTrailing, @"uiTable cell progressbar trailing constraint")]; [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeBottom, NSLayoutRelationEqual, self->p, NSLayoutAttributeBottom, 1, 0, @"uiTable cell progressbar bottom constraint")]; } return self; } - (void)dealloc { [self->p release]; self->p = nil; [super dealloc]; } - (void)uiprivUpdate:(NSInteger)row { uiTableValue *value; int progress; value = uiprivTableModelCellValue(self->m, row, self->modelColumn); progress = uiTableValueInt(value); uiFreeTableValue(value); if (progress == -1) { [self->p setIndeterminate:YES]; [self->p startAnimation:self->p]; } else if (progress == 100) { [self->p setIndeterminate:NO]; [self->p setMaxValue:101]; [self->p setDoubleValue:101]; [self->p setDoubleValue:100]; [self->p setMaxValue:100]; } else { [self->p setIndeterminate:NO]; [self->p setDoubleValue:(progress + 1)]; [self->p setDoubleValue:progress]; } } @end @interface uiprivProgressBarTableColumn : uiprivTableColumn { uiTable *t; // TODO remove the need for this given t (or make t not require m, one of the two) uiTableModel *m; int modelColumn; } - (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc; @end @implementation uiprivProgressBarTableColumn - (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc { self = [super initWithIdentifier:ident]; if (self) { self->t = table; self->m = model; self->modelColumn = mc; } return self; } - (uiprivTableCellView *)uiprivMakeCellView { uiprivTableCellView *cv; cv = [[uiprivProgressBarTableCellView alloc] initWithFrame:NSZeroRect table:self->t model:self->m modelColumn:self->modelColumn]; [cv setIdentifier:[self identifier]]; return cv; } @end @interface uiprivButtonTableCellView : uiprivTableCellView { uiTable *t; uiTableModel *m; NSButton *b; int modelColumn; int editableColumn; } - (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec; - (IBAction)uiprivOnClicked:(id)sender; @end @implementation uiprivButtonTableCellView - (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec { self = [super initWithFrame:r]; if (self) { self->t = table; self->m = model; self->modelColumn = mc; self->editableColumn = ec; self->b = [[NSButton alloc] initWithFrame:NSZeroRect]; [self->b setButtonType:NSMomentaryPushInButton]; [self->b setBordered:YES]; [self->b setBezelStyle:NSRoundRectBezelStyle]; uiDarwinSetControlFont(self->b, NSRegularControlSize); [self->b setTarget:self]; [self->b setAction:@selector(uiprivOnClicked:)]; [self->b setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addSubview:self->b]; // TODO set hugging and compression resistance properly [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeLeading, NSLayoutRelationEqual, self->b, NSLayoutAttributeLeading, 1, -buttonColumnLeading, @"uiTable cell button leading constraint")]; [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTop, NSLayoutRelationEqual, self->b, NSLayoutAttributeTop, 1, 0, @"uiTable cell button top constraint")]; [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTrailing, NSLayoutRelationEqual, self->b, NSLayoutAttributeTrailing, 1, buttonColumnTrailing, @"uiTable cell button trailing constraint")]; [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeBottom, NSLayoutRelationEqual, self->b, NSLayoutAttributeBottom, 1, 0, @"uiTable cell button bottom constraint")]; } return self; } - (void)dealloc { [self->b release]; self->b = nil; [super dealloc]; } - (void)uiprivUpdate:(NSInteger)row { uiTableValue *value; NSString *str; value = uiprivTableModelCellValue(self->m, row, self->modelColumn); str = uiprivToNSString(uiTableValueString(value)); uiFreeTableValue(value); [self->b setTitle:str]; [self->b setEnabled:uiprivTableModelCellEditable(self->m, row, self->editableColumn)]; } - (IBAction)uiprivOnClicked:(id)sender { NSInteger row; row = [self->t->tv rowForView:self->b]; uiprivTableModelSetCellValue(self->m, row, self->modelColumn, NULL); // TODO document we DON'T update the cell after doing this // TODO or decide what to do instead } @end @interface uiprivButtonTableColumn : uiprivTableColumn { uiTable *t; uiTableModel *m; int modelColumn; int editableColumn; } - (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec; @end @implementation uiprivButtonTableColumn - (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec { self = [super initWithIdentifier:ident]; if (self) { self->t = table; self->m = model; self->modelColumn = mc; self->editableColumn = ec; } return self; } - (uiprivTableCellView *)uiprivMakeCellView { uiprivTableCellView *cv; cv = [[uiprivButtonTableCellView alloc] initWithFrame:NSZeroRect table:self->t model:self->m modelColumn:self->modelColumn editableColumn:self->editableColumn]; [cv setIdentifier:[self identifier]]; return cv; } @end void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) { struct textColumnCreateParams p; uiprivTableColumn *col; NSString *str; memset(&p, 0, sizeof (struct textColumnCreateParams)); p.t = t; p.m = t->m; p.makeTextField = YES; p.textModelColumn = textModelColumn; p.textEditableModelColumn = textEditableModelColumn; if (textParams != NULL) p.textParams = *textParams; else p.textParams = uiprivDefaultTextColumnOptionalParams; str = [NSString stringWithUTF8String:name]; col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; [col setTitle:str]; [t->tv addTableColumn:col]; } void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) { struct textColumnCreateParams p; uiprivTableColumn *col; NSString *str; memset(&p, 0, sizeof (struct textColumnCreateParams)); p.t = t; p.m = t->m; p.makeImageView = YES; p.imageModelColumn = imageModelColumn; str = [NSString stringWithUTF8String:name]; col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; [col setTitle:str]; [t->tv addTableColumn:col]; } void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) { struct textColumnCreateParams p; uiprivTableColumn *col; NSString *str; memset(&p, 0, sizeof (struct textColumnCreateParams)); p.t = t; p.m = t->m; p.makeTextField = YES; p.textModelColumn = textModelColumn; p.textEditableModelColumn = textEditableModelColumn; if (textParams != NULL) p.textParams = *textParams; else p.textParams = uiprivDefaultTextColumnOptionalParams; p.makeImageView = YES; p.imageModelColumn = imageModelColumn; str = [NSString stringWithUTF8String:name]; col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; [col setTitle:str]; [t->tv addTableColumn:col]; } void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) { struct textColumnCreateParams p; uiprivTableColumn *col; NSString *str; memset(&p, 0, sizeof (struct textColumnCreateParams)); p.t = t; p.m = t->m; p.makeCheckbox = YES; p.checkboxModelColumn = checkboxModelColumn; p.checkboxEditableModelColumn = checkboxEditableModelColumn; str = [NSString stringWithUTF8String:name]; col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; [col setTitle:str]; [t->tv addTableColumn:col]; } void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) { struct textColumnCreateParams p; uiprivTableColumn *col; NSString *str; memset(&p, 0, sizeof (struct textColumnCreateParams)); p.t = t; p.m = t->m; p.makeTextField = YES; p.textModelColumn = textModelColumn; p.textEditableModelColumn = textEditableModelColumn; if (textParams != NULL) p.textParams = *textParams; else p.textParams = uiprivDefaultTextColumnOptionalParams; p.makeCheckbox = YES; p.checkboxModelColumn = checkboxModelColumn; p.checkboxEditableModelColumn = checkboxEditableModelColumn; str = [NSString stringWithUTF8String:name]; col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; [col setTitle:str]; [t->tv addTableColumn:col]; } void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) { uiprivTableColumn *col; NSString *str; str = [NSString stringWithUTF8String:name]; col = [[uiprivProgressBarTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:progressModelColumn]; [col setTitle:str]; [t->tv addTableColumn:col]; } void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn) { uiprivTableColumn *col; NSString *str; str = [NSString stringWithUTF8String:name]; col = [[uiprivButtonTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:buttonModelColumn editableColumn:buttonClickableModelColumn]; [col setTitle:str]; [t->tv addTableColumn:col]; }