darling-cocotron/AppKit/NSButtonCell.m
2020-05-12 17:04:16 -04:00

1507 lines
50 KiB
Objective-C

/* Copyright (c) 2006-2007 Christopher J. W. Lloyd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#import <AppKit/NSApplication.h>
#import <AppKit/NSAttributedString.h>
#import <AppKit/NSButtonCell.h>
#import <AppKit/NSButtonImageSource.h>
#import <AppKit/NSColor.h>
#import <AppKit/NSComboBoxCell.h>
#import <AppKit/NSControl.h>
#import <AppKit/NSFont.h>
#import <AppKit/NSGraphics.h>
#import <AppKit/NSGraphicsContext.h>
#import <AppKit/NSGraphicsStyle.h>
#import <AppKit/NSImage.h>
#import <AppKit/NSMatrix.h>
#import <AppKit/NSParagraphStyle.h>
#import <AppKit/NSPopUpButtonCell.h>
#import <AppKit/NSRaise.h>
#import <AppKit/NSSound.h>
#import <AppKit/NSStringDrawer.h>
#import <AppKit/NSStringDrawing.h>
#import <AppKit/NSWindow.h>
#import <Foundation/NSKeyedArchiver.h>
@implementation NSButtonCell
// Margin between an image and the Button interior borders
static const CGFloat kImageMargin = 2.;
- (void) encodeWithCoder: (NSCoder *) coder {
[super encodeWithCoder: coder];
[coder encodeObject: _titleOrAttributedTitle forKey: @"NSButtonCell title"];
[coder encodeObject: _alternateTitle
forKey: @"NSButtonCell alternateTitle"];
[coder encodeInt: _imagePosition forKey: @"NSButtonCell imagePosition"];
[coder encodeInt: _highlightsBy forKey: @"NSButtonCell highlightsBy"];
[coder encodeInt: _showsStateBy forKey: @"NSButtonCell showsStateBy"];
[coder encodeBool: _isTransparent forKey: @"NSButtonCell transparent"];
[coder encodeBool: _imageDimsWhenDisabled
forKey: @"NSButtonCell imageDimsWhenDisabled"];
[coder encodeObject: _alternateImage
forKey: @"NSButtonCell alternateImage"];
[coder encodeObject: _keyEquivalent forKey: @"NSButtonCell keyEquivalent"];
[coder encodeInt: _keyEquivalentModifierMask
forKey: @"NSButtonCell keyEquivalentModifierMask"];
}
- _applyButtonCellAppleFlags: (unsigned) flags flags2: (unsigned) flags2 {
_imagePosition = NSNoImage;
if ((flags & 0x00480000) == 0x00400000)
_imagePosition = NSImageOnly;
else if ((flags & 0x00480000) == 0x00480000)
_imagePosition = NSImageOverlaps;
else if ((flags & 0x00380000) == 0x00380000)
_imagePosition = NSImageLeft;
else if ((flags & 0x00380000) == 0x00280000)
_imagePosition = NSImageRight;
else if ((flags & 0x00380000) == 0x00180000)
_imagePosition = NSImageBelow;
else if ((flags & 0x00380000) == 0x00080000)
_imagePosition = NSImageAbove;
// bits 6 and 7 in flags2, but not in order
switch ((flags2 >> 6) & 0x3) {
case 0:
_imageScaling = NSImageScaleNone;
break;
case 1:
_imageScaling = NSImageScaleProportionallyUpOrDown;
break;
case 2:
_imageScaling = NSImageScaleProportionallyDown;
break;
case 3:
_imageScaling = NSImageScaleAxesIndependently;
break;
}
_highlightsBy = 0;
_showsStateBy = 0;
if (flags & 0x80000000)
_highlightsBy |= NSPushInCellMask;
if (flags & 0x40000000)
_showsStateBy |= NSContentsCellMask;
if (flags & 0x20000000)
_showsStateBy |= NSChangeBackgroundCellMask;
if (flags & 0x10000000)
_showsStateBy |= NSChangeGrayCellMask;
if (flags & 0x08000000)
_highlightsBy |= NSContentsCellMask;
if (flags & 0x04000000)
_highlightsBy |= NSChangeBackgroundCellMask;
if (flags & 0x02000000)
_highlightsBy |= NSChangeGrayCellMask;
_isBordered =
(flags & 0x00800000) ? YES : NO; // err, this flag is in NSCell too
_bezelStyle = (flags2 & 0x7) | (flags2 & 0x20 >> 2);
if (_bezelStyle == 0) // this is how textured buttons are encoded by IB
_bezelStyle = NSTexturedSquareBezelStyle;
if (_bezelStyle == 3)
_bezelStyle = NSTexturedRoundedBezelStyle;
if (_bezelStyle == 4)
_bezelStyle = NSRoundRectBezelStyle;
_isTransparent = (flags & 0x00008000) ? YES : NO;
_imageDimsWhenDisabled = (flags & 0x00002000) ? NO : YES;
_showsBorderOnlyWhileMouseInside = (flags2 & 0x8) ? YES : NO;
if ((flags & 0x7e800000) != 0x48000000)
[self setButtonType: NSMomentaryPushInButton];
else if (flags & 0x100) {
id altImageSource = nil;
if (flags & 0x100)
altImageSource = self->_alternateImage;
if ([[altImageSource name] isEqualToString: @"NSSwitch"])
[self setButtonType: NSSwitchButton];
if ([[altImageSource name] isEqualToString: @"NSRadioButton"])
[self setButtonType: NSPushOnPushOffButton];
} else {
if (([[[self image] name] isEqualToString: @"NSRadioButton"] &&
[[[self alternateImage] name]
isEqualToString: @"NSHighlightedRadioButton"]) &&
(flags & 0x20000) == 0) {
[self setButtonType: NSRadioButton];
} else if (([[[self image] name] isEqualToString: @"NSSwitch"] &&
[[[self alternateImage] name]
isEqualToString: @"NSHighlightedSwitch"]) &&
(flags & 0x20000) == 0) {
[self setButtonType: NSMomentaryPushInButton];
}
}
}
- initWithCoder: (NSCoder *) coder {
[super initWithCoder: coder];
unsigned flags = 0, flags2 = 0;
if ([coder allowsKeyedCoding]) {
NSKeyedUnarchiver *keyed = (NSKeyedUnarchiver *) coder;
flags = [keyed decodeIntForKey: @"NSButtonFlags"];
flags2 = [keyed decodeIntForKey: @"NSButtonFlags2"];
id check;
_titleOrAttributedTitle =
[[keyed decodeObjectForKey: @"NSContents"] retain];
_alternateTitle =
[[keyed decodeObjectForKey: @"NSAlternateContents"] retain];
[self _applyButtonCellAppleFlags: flags flags2: flags2];
check = [keyed decodeObjectForKey: @"NSAlternateImage"];
if ([check isKindOfClass: [NSImage class]])
_alternateImage = [check retain];
else if ([check isKindOfClass: [NSButtonImageSource class]]) {
[_image release];
_image = [[check normalImage] retain];
_alternateImage = [[check alternateImage] retain];
} else
_alternateImage = nil;
/* _normalImage is a private ivar in Apple's AppKit. Third party library
BGHUDAppKit uses it to figure out what kind of standard button is
being drawn. We emulate it for BGHUDAppKit. */
_normalImage = [_image retain];
_keyEquivalent =
[[keyed decodeObjectForKey: @"NSKeyEquivalent"] retain];
_keyEquivalentModifierMask = flags2 >> 8;
[self setIntValue: _state]; // make the int value of NSButtonCell to be
// in synch with the bare _state of NSCell
} else {
NSInteger version = [coder versionForClassName: @"NSButtonCell"];
NSLog(@"NSButtonCell version is %d\n", version);
unichar keyEquivModMask;
if (version <= 16) {
if ([(NSUnarchiver *) coder systemVersion] > 82) {
short periodicDelay, periodicInterval;
[coder decodeValuesOfObjCTypes: "ss", &periodicDelay,
&periodicInterval];
[self setPeriodicDelay: periodicDelay / 1000.0f
interval: periodicInterval / 1000.0f];
}
char *altContents;
unsigned short inFlags1, inFlags2;
[coder decodeValuesOfObjCTypes: "*@ss", &altContents, &_sound,
&inFlags1, &inFlags2];
flags = (((unsigned) inFlags1) << 16) & 0xFEF80000;
flags |= (((unsigned) inFlags1) & 0x3) << 17;
flags |= (((unsigned) inFlags2) << 8) & 0x8000;
flags |= (((unsigned) inFlags2) & 0x6) << 12;
unichar keyEquivalent = inFlags2 >> 8;
if (keyEquivalent != 0) {
_keyEquivalent =
[[NSString alloc] initWithCharacters: &keyEquivalent
length: 1];
}
if (altContents) {
_alternateTitle = [[NSString alloc]
initWithBytesNoCopy: altContents
length: strlen(altContents)
encoding: NSUTF8StringEncoding
freeWhenDone: TRUE];
}
if (flags & 0x20000) {
[coder decodeValuesOfObjCTypes: "@@", &_normalImage,
&_alternateImage];
} else {
float dummy;
[coder decodeValuesOfObjCTypes: "@f", &_alternateImage, &dummy];
}
if (version >= 2)
[coder decodeValuesOfObjCTypes: "I", &keyEquivModMask];
else
keyEquivModMask = 0;
} else if (version <= 40) {
short periodicDelay, periodicInterval;
NSUnarchiver *una = (NSUnarchiver *) coder;
[coder decodeValuesOfObjCTypes: "ss", &periodicDelay,
&periodicInterval];
[self setPeriodicDelay: periodicDelay / 1000.0f
interval: periodicInterval / 1000.0f];
[coder decodeValuesOfObjCTypes: "I", &keyEquivModMask];
unsigned int inFlags = [una decodeByte];
flags = inFlags << 31;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 30;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 29;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 28;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 27;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 26;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 25;
[una decodeByte];
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 23;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 22;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 21;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 20;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 19;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 18;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 17;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 15;
inFlags = [una decodeByte];
flags |= (inFlags & 3) << 13;
inFlags = [una decodeByte];
flags |= (inFlags & 1) << 12;
id altContents = [coder decodeObject];
// TODO: _setAltContents
[self setKeyEquivalent: [coder decodeObject]];
[self setKeyEquivalentFont: [coder decodeObject]];
[self setImage: [coder decodeObject]];
[self setAlternateImage: [coder decodeObject]];
[self setSoundImage: [coder decodeObject]];
unsigned int var50;
if (version >= 19)
[coder decodeValuesOfObjCTypes: "I", &var50];
if (version > 20)
[coder decodeValuesOfObjCTypes: "I", &var50];
if (version > 26)
[una decodeByte];
if (version >= 26)
[una decodeByte];
} else if (version <= 57) {
short periodicDelay, periodicInterval;
[coder decodeValuesOfObjCTypes: "ssIi@@@@@", &periodicDelay,
&periodicInterval, &flags,
&keyEquivModMask, &_alternateTitle,
&_keyEquivalent, &_normalImage,
&_alternateImage, &_sound];
[self setPeriodicDelay: periodicDelay / 1000.0f
interval: periodicInterval / 1000.0f];
flags &= 0xfeffffff;
flags2 &= 0xFFFFFFD8;
} else {
short periodicDelay, periodicInterval;
[coder decodeValuesOfObjCTypes: "ssii@@@@@", &periodicDelay,
&periodicInterval, &flags2, &flags,
&_alternateTitle, &_keyEquivalent,
&_normalImage, &_alternateImage,
&_sound];
[self setPeriodicDelay: periodicDelay / 1000.0f
interval: periodicInterval / 1000.0f];
flags &= 0xfeffffff;
}
if (version <= 36) {
const unsigned int bits = (flags >> 13 & 3);
if (bits == 2)
flags &= 0x0FFFF9FFF;
else if (bits == 0) {
flags &= 0xFFFF9FFF;
flags |= 0x4000;
}
}
if (version <= 40) {
if (([[self image]
isEqual: [NSImage imageNamed: @"NSRadioButton"]] &&
[[self alternateImage]
isEqual: [NSImage imageNamed: @"NSHighlightedRadioButt"
@"on"]]) ||
([[self image] isEqual: [NSImage imageNamed: @"NSSwitch"]] &&
[[self alternateImage]
isEqual: [NSImage imageNamed:
@"NSHighlightedSwitch"]])) {
flags |= 0x1000;
}
}
if ([[self keyEquivalent] isEqualToString: @"\n"])
[self setKeyEquivalent: @"\r"];
if (version <= 53) {
if ([[self keyEquivalent] isEqualToString: @"\r"]) {
if ([[[self image] name] isEqualToString: @"NSReturnSign"] ||
[[[self alternateImage] name]
isEqualToString: @"NXreturnSignH"]) {
[self setImage: nil];
[self setAlternateImage: nil];
flags &= 0xB70000;
}
}
[self _applyButtonCellAppleFlags: flags flags2: flags2];
[self setKeyEquivalentModifierMask: keyEquivModMask];
} else {
[self _applyButtonCellAppleFlags: flags flags2: flags2];
}
if (version < 58) {
[self setKeyEquivalentModifierMask: keyEquivModMask];
}
// TODO: set alternate image based on buttonType
}
return self;
}
- initTextCell: (NSString *) string {
[super initTextCell: string];
_titleOrAttributedTitle = [string copy];
_alternateTitle = @"";
_imagePosition = NSNoImage;
_highlightsBy = NSPushInCellMask;
_showsStateBy = 0;
_isTransparent = NO;
_imageDimsWhenDisabled = NO;
_alternateImage = nil;
_keyEquivalent = @"";
_keyEquivalentModifierMask = 0;
_showsBorderOnlyWhileMouseInside = NO;
[self setBordered: YES];
[self setBezeled: YES];
[self setAlignment: NSCenterTextAlignment];
[self setObjectValue: [NSNumber numberWithBool: NO]];
return self;
}
- initImageCell: (NSImage *) image {
[super initImageCell: image];
_titleOrAttributedTitle = @""; // empty string, not nil
_imagePosition = NSImageOnly;
[self setObjectValue: [NSNumber numberWithBool: NO]];
return self;
}
- init {
return [self initTextCell: @"Button"];
}
- (void) dealloc {
[_titleOrAttributedTitle release];
[_normalImage release];
[_alternateTitle release];
[_alternateImage release];
[_keyEquivalent release];
[_sound release];
[_keyEquivalentFont release];
[_backgroundColor release];
[super dealloc];
}
- copyWithZone: (NSZone *) zone {
NSButtonCell *result = [super copyWithZone: zone];
result->_titleOrAttributedTitle = [_titleOrAttributedTitle copy];
result->_alternateTitle = [_alternateTitle copy];
result->_alternateImage = [_alternateImage retain];
result->_keyEquivalent = [_keyEquivalent copy];
result->_sound = [_sound retain];
result->_keyEquivalentFont = [_keyEquivalentFont retain];
result->_backgroundColor = [_backgroundColor retain];
return result;
}
- (BOOL) isTransparent {
return _isTransparent;
}
- (NSString *) keyEquivalent {
return _keyEquivalent;
}
- (NSCellImagePosition) imagePosition {
return _imagePosition;
}
- (NSString *) title {
if ([_titleOrAttributedTitle isKindOfClass: [NSAttributedString class]])
return [_titleOrAttributedTitle string];
else
return _titleOrAttributedTitle;
}
- (NSString *) alternateTitle {
return _alternateTitle;
}
- (NSImage *) alternateImage {
return _alternateImage;
}
- (NSAttributedString *) attributedTitle {
if (_titleOrAttributedTitle == nil) {
// attributedTitle is explicitly documented to return an empty
// attributed string when title is nil
return [[NSAttributedString alloc] initWithString: @""];
} else if ([_titleOrAttributedTitle
isKindOfClass: [NSAttributedString class]])
return _titleOrAttributedTitle;
else {
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
NSMutableParagraphStyle *paraStyle =
[[[NSParagraphStyle defaultParagraphStyle] mutableCopy]
autorelease];
NSFont *font = [self font];
if (font != nil)
[attributes setObject: font forKey: NSFontAttributeName];
[paraStyle setLineBreakMode: _lineBreakMode];
[paraStyle setAlignment: _textAlignment];
[attributes setObject: paraStyle forKey: NSParagraphStyleAttributeName];
if ([self isEnabled])
[attributes setObject: [NSColor controlTextColor]
forKey: NSForegroundColorAttributeName];
else
[attributes setObject: [NSColor disabledControlTextColor]
forKey: NSForegroundColorAttributeName];
return [[[NSAttributedString alloc] initWithString: [self title]
attributes: attributes]
autorelease];
}
}
- (NSAttributedString *) attributedAlternateTitle {
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
NSMutableParagraphStyle *paraStyle =
[[[NSParagraphStyle defaultParagraphStyle] mutableCopy]
autorelease];
NSFont *font = [self font];
if (font != nil)
[attributes setObject: font forKey: NSFontAttributeName];
[paraStyle setLineBreakMode: _lineBreakMode];
[paraStyle setAlignment: _textAlignment];
[attributes setObject: paraStyle forKey: NSParagraphStyleAttributeName];
if ([self isEnabled])
[attributes setObject: [NSColor controlTextColor]
forKey: NSForegroundColorAttributeName];
else
[attributes setObject: [NSColor disabledControlTextColor]
forKey: NSForegroundColorAttributeName];
return [[[NSAttributedString alloc] initWithString: [self alternateTitle]
attributes: attributes]
autorelease];
}
- (int) highlightsBy {
return _highlightsBy;
}
- (int) showsStateBy {
return _showsStateBy;
}
- (BOOL) imageDimsWhenDisabled {
return _imageDimsWhenDisabled;
}
- (unsigned) keyEquivalentModifierMask {
return _keyEquivalentModifierMask;
}
- (NSBezelStyle) bezelStyle {
return _bezelStyle;
}
- (BOOL) showsBorderOnlyWhileMouseInside {
return _showsBorderOnlyWhileMouseInside;
}
- (NSSound *) sound {
return _sound;
}
- (NSGradientType) gradientType {
return _gradientType;
}
- (NSImageScaling) imageScaling {
return _imageScaling;
}
- (BOOL) isOpaque {
if (_bezelStyle == NSDisclosureBezelStyle)
return NO;
if (_bezelStyle == NSTexturedSquareBezelStyle)
return NO;
if (_bezelStyle == NSTexturedRoundedBezelStyle)
return NO;
if (_bezelStyle == NSShadowlessSquareBezelStyle)
return NO;
if (_bezelStyle == NSRecessedBezelStyle)
return NO;
return ![self isTransparent] && [self isBordered];
}
- (NSFont *) keyEquivalentFont {
return _keyEquivalentFont;
}
- (NSColor *) backgroundColor {
return _backgroundColor;
}
- (void) getPeriodicDelay: (float *) delay interval: (float *) interval {
*delay = _periodicDelay;
*interval = _periodicInterval;
}
- (NSControlStateValue) state {
return [self intValue];
}
- (void) setTransparent: (BOOL) flag {
_isTransparent = flag;
}
- (void) setKeyEquivalent: (NSString *) keyEquivalent {
keyEquivalent = [keyEquivalent copy];
[_keyEquivalent release];
_keyEquivalent = keyEquivalent;
}
- (void) setImagePosition: (NSCellImagePosition) position {
_imagePosition = position;
}
- (void) setTitle: (NSString *) title {
title = [title copy];
[_titleOrAttributedTitle release];
_titleOrAttributedTitle = title;
}
- (void) setAlternateTitle: (NSString *) title {
title = [title copy];
[_alternateTitle release];
_alternateTitle = title;
}
- (void) setAlternateImage: (NSImage *) image {
image = [image retain];
[_alternateImage release];
_alternateImage = image;
}
- (void) setAttributedTitle: (NSAttributedString *) title {
title = [title copy];
[_titleOrAttributedTitle release];
_titleOrAttributedTitle = title;
}
- (void) setAttributedAlternateTitle: (NSAttributedString *) title {
NSUnimplementedMethod();
}
- (void) setHighlightsBy: (int) type {
_highlightsBy = type;
}
- (void) setShowsStateBy: (int) type {
_showsStateBy = type;
}
- (void) setImageDimsWhenDisabled: (BOOL) flag {
_imageDimsWhenDisabled = flag;
}
- (void) setKeyEquivalentModifierMask: (unsigned) mask {
_keyEquivalentModifierMask = mask;
}
- (void) setState: (NSControlStateValue) value {
[self setIntValue: value];
}
- (void) setNextState {
[self setIntValue: [self nextState]];
}
- (void) setObjectValue: (id<NSCopying>) value {
if ([(id) value respondsToSelector: @selector(intValue)])
[super setState: [(NSNumber *) value intValue]];
else
[super setState: 0];
[[self controlView] willChangeValueForKey: @"objectValue"];
[_objectValue release];
_objectValue = [[NSNumber numberWithInt: [super state]] retain];
[[self controlView] didChangeValueForKey: @"objectValue"];
if ([[self controlView] respondsToSelector: @selector(updateCell:)])
[(NSControl *) [self controlView] updateCell: self];
}
- (void) setBezelStyle: (NSBezelStyle) bezelStyle {
_bezelStyle = bezelStyle;
}
- (void) setButtonType: (NSButtonType) buttonType {
switch (buttonType) {
case NSMomentaryLightButton:
_highlightsBy = NSChangeBackgroundCellMask;
_showsStateBy = NSNoCellMask;
_imageDimsWhenDisabled = YES;
break;
case NSMomentaryPushInButton:
_highlightsBy = NSPushInCellMask | NSChangeGrayCellMask;
_showsStateBy = NSNoCellMask;
_imageDimsWhenDisabled = YES;
break;
case NSMomentaryChangeButton:
_highlightsBy = NSContentsCellMask;
_showsStateBy = NSNoCellMask;
_imageDimsWhenDisabled = YES;
break;
case NSPushOnPushOffButton:
_highlightsBy = NSPushInCellMask | NSChangeGrayCellMask;
_showsStateBy = NSChangeBackgroundCellMask;
_imageDimsWhenDisabled = YES;
break;
case NSOnOffButton:
_highlightsBy = NSChangeBackgroundCellMask | NSChangeGrayCellMask;
_showsStateBy = NSChangeBackgroundCellMask | NSChangeGrayCellMask;
_imageDimsWhenDisabled = YES;
break;
case NSToggleButton:
_highlightsBy = NSPushInCellMask | NSContentsCellMask;
_showsStateBy = NSContentsCellMask;
_imageDimsWhenDisabled = YES;
break;
case NSSwitchButton:
_highlightsBy = NSContentsCellMask;
_showsStateBy = NSContentsCellMask;
_imagePosition = NSImageLeft;
_imageDimsWhenDisabled = NO;
[self setImage: [NSImage imageNamed: @"NSSwitch"]];
[self setAlternateImage: [NSImage imageNamed: @"NSHighlightedSwitch"]];
[self setAlignment: NSLeftTextAlignment];
[self setBordered: NO];
[self setBezeled: NO];
break;
case NSRadioButton:
_highlightsBy = NSContentsCellMask;
_showsStateBy = NSContentsCellMask;
_imagePosition = NSImageLeft;
_imageDimsWhenDisabled = NO;
[self setImage: [NSImage imageNamed: @"NSRadioButton"]];
[self setAlternateImage:
[NSImage imageNamed: @"NSHighlightedRadioButton"]];
[self setAlignment: NSLeftTextAlignment];
[self setBordered: NO];
[self setBezeled: NO];
break;
}
[(NSControl *) [self controlView] updateCell: self];
}
- (void) setShowsBorderOnlyWhileMouseInside: (BOOL) show {
_showsBorderOnlyWhileMouseInside = show;
}
- (void) setSound: (NSSound *) sound {
sound = [sound retain];
[_sound release];
_sound = sound;
}
- (void) setGradientType: (NSGradientType) value {
_gradientType = value;
}
- (void) setBackgroundColor: (NSColor *) value {
value = [value copy];
[_backgroundColor release];
_backgroundColor = value;
}
- (void) setImageScaling: (NSImageScaling) value {
_imageScaling = value;
}
- (void) setKeyEquivalentFont: (NSFont *) value {
value = [value retain];
[_keyEquivalentFont release];
_keyEquivalentFont = value;
}
- (void) setKeyEquivalentFont: (NSString *) value size: (CGFloat) size {
NSFont *font = [NSFont fontWithName: value size: size];
[self setKeyEquivalentFont: font];
}
- (void) setPeriodicDelay: (float) delay interval: (float) interval {
_periodicDelay = delay;
_periodicInterval = interval;
}
- (NSAttributedString *) titleForHighlight {
if ((([self highlightsBy] & NSContentsCellMask) && [self isHighlighted]) ||
(([self showsStateBy] & NSContentsCellMask) && [self state])) {
NSAttributedString *result = [self attributedAlternateTitle];
if ([result length] > 0)
return result;
}
return [self attributedTitle];
}
- (NSImage *) imageForHighlight {
if (_bezelStyle == NSDisclosureBezelStyle) {
if ((([self highlightsBy] & NSContentsCellMask) &&
[self isHighlighted]))
return [NSImage imageNamed: @"NSButtonCell_disclosure_highlighted"];
else if ([self state])
return [NSImage imageNamed: @"NSButtonCell_disclosure_selected"];
else
return [NSImage imageNamed: @"NSButtonCell_disclosure_normal"];
return nil;
} else {
if ((([self highlightsBy] & NSContentsCellMask) &&
[self isHighlighted]) ||
(([self showsStateBy] & NSContentsCellMask) && [self state]))
return [self alternateImage];
return [self image];
}
}
- (NSRect) imageRectForBounds: (NSRect) rect {
// Make sure we use the same image as will be drawn!
NSImage *image = [self imageForHighlight];
NSSize imageSize = NSMakeSize(0, 0);
if (image != nil) {
BOOL enabled = [self isEnabled] ? YES : ![self imageDimsWhenDisabled];
BOOL mixed = [self state] == NSMixedState;
imageSize =
[[[self controlView] graphicsStyle] sizeOfButtonImage: image
enabled: enabled
mixed: mixed];
}
return NSMakeRect(rect.origin.x, rect.origin.y, imageSize.width,
imageSize.height);
}
- (BOOL) isVisuallyHighlighted {
return ((([self highlightsBy] & NSChangeGrayCellMask) &&
[self isHighlighted]) ||
(([self showsStateBy] & NSChangeGrayCellMask) && [self state]));
}
- (NSRect) getControlSizeAdjustment: (BOOL) flipped {
/*
Aqua Push Buttons actually have a frame much larger than told by IB to make
room for shadows and whatnot So we have to compensate for this when drawing
simpler buttons. There is probably a way to streamline this, make
NSPopUpButtonCell draw itself for starters NSGraphicsStyle should probably
do this adjustment too
*/
NSRect frame = {{0, 0}, {0, 0}};
if ([self isKindOfClass: [NSComboBoxCell class]]) {
switch (_controlSize) {
case NSRegularControlSize:
frame.size.width = 2;
frame.size.height = 1;
frame.origin.x = 1;
break;
case NSSmallControlSize:
frame.size.width = 4;
frame.size.height = 8;
frame.origin.x = 2;
frame.origin.y = 6;
break;
case NSMiniControlSize:
frame.size.width = 6;
frame.size.height = 4;
frame.origin.x = 3;
frame.origin.y = 4;
break;
}
} else if ([self isKindOfClass: [NSPopUpButtonCell class]]) {
switch (_controlSize) {
case NSRegularControlSize:
frame.size.width = 2;
frame.size.height = 1;
frame.origin.x = 1;
break;
case NSSmallControlSize:
frame.size.width = 4;
frame.size.height = 3;
frame.origin.x = 2;
frame.origin.y = 3;
break;
case NSMiniControlSize:
// Mini controls don't need adjusting they're small enough already.
break;
}
} else if ((_bezelStyle == NSRoundedBezelStyle) &&
(_highlightsBy & NSPushInCellMask) &&
(_highlightsBy & NSChangeGrayCellMask) &&
(_showsStateBy == NSNoCellMask)) {
switch (_controlSize) {
default:
frame.size.width = 10 - _controlSize * 2;
frame.size.height = 10 - _controlSize * 2;
frame.origin.x = 5 - _controlSize;
frame.origin.y =
flipped ? _controlSize * 2 - 3 : 7 - _controlSize * 2;
break;
case NSMiniControlSize:
break;
}
} else if (_bezelStyle == NSRegularSquareBezelStyle) {
}
return frame;
}
- (void) _drawTexturedBezelWithFrame: (NSRect) frame {
BOOL highlighted = [self isHighlighted];
BOOL pressed =
[self state] && ([self showsStateBy] & NSChangeBackgroundCellMask);
BOOL renderDarkenBg = NO, renderOutlineShadow = NO;
// CGFloat topGray=0.76, bottomGray=0.98, strokeGray=0.4;
CGFloat topGray = 0.98, bottomGray = 0.76, strokeGray = 0.4;
if (pressed) {
topGray = 0.4;
bottomGray = 0.30;
}
renderDarkenBg = highlighted;
renderOutlineShadow = highlighted || pressed;
CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(ctx);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextSetFillColorSpace(ctx, colorSpace);
CGContextSetStrokeColorSpace(ctx, colorSpace);
CGColorSpaceRelease(colorSpace);
frame = NSInsetRect(frame, 1.5, 1.5);
const CGFloat rounding = 1.5;
const CGFloat baseY = floor(frame.origin.y);
const CGFloat maxY = baseY + frame.size.height - 1.0;
CGRect r = CGRectMake(floor(frame.origin.x), baseY, ceil(frame.size.width),
1.0);
while (r.origin.y <= maxY) {
CGFloat g =
bottomGray + (topGray - bottomGray) *
((r.origin.y - baseY) / (maxY - baseY));
CGFloat components[4] = {g, g, g, 1.0};
CGContextSetFillColor(ctx, components);
if (r.origin.y < baseY + 1.0f || r.origin.y > maxY - 1.0f) {
CGContextFillRect(ctx,
CGRectMake(r.origin.x + 1.0f, r.origin.y,
r.size.width - 2.0f, r.size.height));
} else {
CGContextFillRect(ctx, r);
}
r.origin.y += 1.0f;
}
const CGFloat lx = floor(frame.origin.x) + 0.5f;
const CGFloat rx = floor(frame.origin.x + frame.size.width) + 0.5f;
const CGFloat ty = floor(frame.origin.y) + 0.5f;
const CGFloat by = floor(frame.origin.y + frame.size.height) + 0.5f;
const CGFloat rlx = lx + rounding;
const CGFloat rrx = rx - rounding;
const CGFloat rty = ty + rounding;
const CGFloat rby = by - rounding;
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, rlx, ty);
CGContextAddLineToPoint(ctx, rrx, ty);
CGContextAddLineToPoint(ctx, rx, rty);
CGContextAddLineToPoint(ctx, rx, rby);
CGContextAddLineToPoint(ctx, rrx, by);
CGContextAddLineToPoint(ctx, rlx, by);
CGContextAddLineToPoint(ctx, lx, rby);
CGContextAddLineToPoint(ctx, lx, rty);
CGContextClosePath(ctx);
CGFloat components[4] = {strokeGray, strokeGray, strokeGray, 1.0};
CGContextSetStrokeColor(ctx, components);
CGContextSetLineWidth(ctx, 1.0);
if (renderDarkenBg) {
components[0] = components[1] = components[2] = 0.0;
components[3] = 0.15;
CGContextSetFillColor(ctx, components);
CGContextDrawPath(ctx, kCGPathFillStroke);
} else {
CGContextStrokePath(ctx);
}
if (renderOutlineShadow) { // a small interior shadow within the button's
// outline
const CGFloat ins = 0.4f;
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, lx + ins, ty + ins);
CGContextAddLineToPoint(ctx, rx - ins, ty + ins);
CGContextAddLineToPoint(ctx, rx - ins, by - ins);
CGContextAddLineToPoint(ctx, lx + ins, by - ins);
CGContextClosePath(ctx);
components[0] = components[1] = components[2] = 0.0;
components[3] = 0.3;
CGContextSetStrokeColor(ctx, components);
CGContextSetLineWidth(ctx, 0.9);
CGContextStrokePath(ctx);
}
CGContextRestoreGState(ctx);
}
static void drawRoundedBezel(CGContextRef context, CGRect frame) {
CGFloat radius = frame.size.height / 2;
CGContextBeginPath(context);
CGContextAddArc(context, CGRectGetMaxX(frame) - radius,
CGRectGetMinY(frame) + radius, radius, M_PI_2, M_PI_2 * 3,
YES);
CGContextAddArc(context, CGRectGetMinX(frame) + radius,
CGRectGetMinY(frame) + radius, radius, M_PI_2 * 3, M_PI_2,
YES);
CGContextClosePath(context);
CGContextFillPath(context);
}
- (void) drawBezelWithFrame: (NSRect) frame inView: (NSView *) controlView {
BOOL defaulted = ([[controlView window] defaultButtonCell] == self);
NSRect adjustment =
[self getControlSizeAdjustment: [controlView isFlipped]];
frame.size.width -= adjustment.size.width;
frame.size.height -= adjustment.size.height;
frame.origin.x += adjustment.origin.x;
frame.origin.y += adjustment.origin.y;
switch (_bezelStyle) {
case NSDisclosureBezelStyle:
break;
case NSRegularSquareBezelStyle:
if ([self isBordered]) {
BOOL highlighted = (([self highlightsBy] & NSPushInCellMask) &&
[self isHighlighted]);
CGFloat topGray = highlighted ? 0.8 : 0.9;
CGFloat bottomGray = highlighted ? 0.7 : 0.8;
NSRect top = frame, bottom = frame;
top.size.height = floor(frame.size.height / 2);
bottom.size.height = ceil(frame.size.height / 2);
if ([controlView isFlipped])
bottom.origin.y += top.size.height;
else
top.origin.y += bottom.size.height;
[[NSColor colorWithCalibratedWhite: topGray alpha: 1] set];
NSRectFill(top);
[[NSColor colorWithCalibratedWhite: bottomGray alpha: 1] set];
NSRectFill(bottom);
[[NSColor lightGrayColor] set];
NSFrameRectWithWidth(frame, 1);
}
break;
case NSTexturedSquareBezelStyle:
case NSTexturedRoundedBezelStyle:
case NSShadowlessSquareBezelStyle:
if ([self isBordered]) {
[self _drawTexturedBezelWithFrame: frame];
}
break;
case NSRecessedBezelStyle:;
if ([self isBordered] && [self isVisuallyHighlighted]) {
CGContextRef context =
[[NSGraphicsContext currentContext] graphicsPort];
frame.size.height--;
frame.origin.y += [controlView isFlipped] ? 1 : 0;
[[NSColor lightGrayColor] setFill];
drawRoundedBezel(context, frame);
frame.origin.y += [controlView isFlipped] ? -1 : 1;
[[NSColor darkGrayColor] setFill];
drawRoundedBezel(context, frame);
frame.origin.y += [controlView isFlipped] ? 0 : -1;
frame.size.height++;
frame = CGRectInset(frame, 1, 1.5);
[[NSColor grayColor] setFill];
drawRoundedBezel(context, frame);
}
break;
default:
if (![self isBordered]) {
[[_controlView graphicsStyle]
drawUnborderedButtonInRect: frame
defaulted: defaulted];
} else {
if (([self highlightsBy] & NSPushInCellMask) &&
[self isHighlighted])
[[_controlView graphicsStyle]
drawPushButtonPressedInRect: frame];
else if ([self isVisuallyHighlighted])
[[_controlView graphicsStyle]
drawPushButtonHighlightedInRect: frame];
else
[[_controlView graphicsStyle]
drawPushButtonNormalInRect: frame
defaulted: defaulted];
}
break;
}
}
- (void) drawImage: (NSImage *) image
withFrame: (NSRect) rect
inView: (NSView *) controlView
{
BOOL enabled = [self isEnabled] ? YES : ![self imageDimsWhenDisabled];
BOOL mixed = ([self state] == NSMixedState) ? YES : NO;
CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, rect.origin.x, rect.origin.y);
if ([controlView isFlipped]) {
CGContextTranslateCTM(ctx, 0, rect.size.height);
CGContextScaleCTM(ctx, 1, -1);
}
[[controlView graphicsStyle]
drawButtonImage: image
inRect: NSMakeRect(0, 0, rect.size.width, rect.size.height)
enabled: enabled
mixed: mixed];
CGContextRestoreGState(ctx);
}
- (NSRect) drawTitle: (NSAttributedString *) title
withFrame: (NSRect) titleRect
inView: (NSView *) controlView
{
[title _clipAndDrawInRect: titleRect];
BOOL drawDottedRect = NO;
if ([[controlView window] firstResponder] == controlView) {
if ([controlView isKindOfClass: [NSMatrix class]]) {
NSMatrix *matrix = (NSMatrix *) controlView;
drawDottedRect = ([matrix keyCell] == self) ? YES : NO;
} else if ([controlView isKindOfClass: [NSControl class]]) {
NSControl *control = (NSControl *) controlView;
drawDottedRect = ([control selectedCell] == self) ? YES : NO;
}
}
if (drawDottedRect)
NSDottedFrameRect(NSInsetRect(titleRect, 1, 1));
return titleRect; // FIXME: wrong value
}
// This function is duplicated in NSImageCell, consolidate
static NSSize scaledImageSizeInFrameSize(NSSize imageSize, NSSize frameSize,
NSImageScaling scaling)
{
switch (scaling) {
case NSImageScaleProportionallyDown: {
CGFloat xscale = frameSize.width / imageSize.width;
CGFloat yscale = frameSize.height / imageSize.height;
CGFloat scale = MIN(1.0, MIN(xscale, yscale));
imageSize.width *= scale;
imageSize.height *= scale;
return imageSize;
}
case NSImageScaleAxesIndependently:
return frameSize;
case NSImageScaleProportionallyUpOrDown: {
CGFloat xscale = frameSize.width / imageSize.width;
CGFloat yscale = frameSize.height / imageSize.height;
CGFloat scale = MIN(xscale, yscale);
imageSize.width *= scale;
imageSize.height *= scale;
return imageSize;
}
default:
case NSImageScaleNone:
return imageSize;
}
}
- (void) drawInteriorWithFrame: (NSRect) frame inView: (NSView *) controlView {
/* This method gets the original button frame. We have to compensate for
borders. There is some duplication of rect calculation which can be split
out
*/
BOOL defaulted = ([[controlView window] defaultButtonCell] == self);
NSRect adjustment =
[self getControlSizeAdjustment: [controlView isFlipped]];
frame.size.width -= adjustment.size.width;
frame.size.height -= adjustment.size.height;
frame.origin.x += adjustment.origin.x;
frame.origin.y += adjustment.origin.y;
if (_bezelStyle == NSDisclosureBezelStyle)
;
else if (![self isBordered]) {
if (defaulted)
frame = NSInsetRect(frame, 1, 1);
} else {
frame = NSInsetRect(frame, 2, 2);
}
NSAttributedString *title = [self titleForHighlight];
NSImage *image = [self imageForHighlight];
BOOL enabled = [self isEnabled] ? YES : ![self imageDimsWhenDisabled];
BOOL mixed = ([self state] == NSMixedState) ? YES : NO;
NSRect imageRect = [self imageRectForBounds: frame];
NSSize imageSize = imageRect.size;
NSPoint imageOrigin = imageRect.origin;
NSSize titleSize = [title size];
NSRect titleRect = [self titleRectForBounds: frame];
BOOL drawImage = YES, drawTitle = YES;
NSCellImagePosition imagePosition = [self imagePosition];
if ([self isTransparent])
return;
// it doesnt actually change the image pos in the button but it draws like
// this
if ([self bezelStyle] == NSDisclosureBezelStyle)
imagePosition = NSImageOnly;
imageSize = scaledImageSizeInFrameSize(imageSize, frame.size,
[self imageScaling]);
imageOrigin.x += floor((frame.size.width - imageSize.width) / 2);
imageOrigin.y += floor((frame.size.height - imageSize.height) / 2);
titleRect.origin.y += floor((titleRect.size.height - titleSize.height) / 2);
titleRect.size.height = titleSize.height;
switch (imagePosition) {
case NSNoImage:
drawImage = NO;
break;
case NSImageOnly:
drawTitle = NO;
break;
case NSImageLeft:
imageOrigin.x = frame.origin.x + kImageMargin;
titleRect.origin.x += imageSize.width + 4;
titleRect.size.width -= imageSize.width + 4;
break;
case NSImageRight:
imageOrigin.x = frame.origin.x + (frame.size.width - imageSize.width) -
kImageMargin;
titleRect.size.width -= (imageSize.width + 4);
break;
case NSImageBelow:
imageOrigin.y = frame.origin.y;
titleRect.origin.y += imageSize.height;
imageOrigin.y = MAX(frame.origin.y, imageOrigin.y);
titleRect.origin.y =
MIN(frame.origin.y + frame.size.height - titleRect.size.height,
titleRect.origin.y);
break;
case NSImageAbove:
imageOrigin.y = frame.origin.y + (frame.size.height - imageSize.height);
titleRect.origin.y -= imageSize.height;
imageOrigin.y =
MIN(frame.origin.y + frame.size.height - imageSize.height,
imageOrigin.y);
titleRect.origin.y = MAX(frame.origin.y, titleRect.origin.y);
break;
case NSImageOverlaps:
break;
}
if (![self isBordered]) {
if ([self isVisuallyHighlighted]) {
[[NSColor whiteColor] setFill];
NSRectFill(frame);
}
}
const BOOL isTextured = (_bezelStyle == NSTexturedSquareBezelStyle ||
_bezelStyle == NSTexturedRoundedBezelStyle);
if ([self isBordered] && !isTextured) {
if (([self highlightsBy] & NSPushInCellMask) && [self isHighlighted]) {
imageOrigin.x += 1;
imageOrigin.y += [controlView isFlipped] ? 1 : -1;
titleRect.origin.x += 1;
titleRect.origin.y += [controlView isFlipped] ? 1 : -1;
}
}
if (drawImage) {
NSRect rect = NSMakeRect(imageOrigin.x, imageOrigin.y, imageSize.width,
imageSize.height);
[self drawImage: image withFrame: rect inView: controlView];
}
if (drawTitle) {
if (isTextured && [title length]) {
// FIXME: use shadow in attributed string and implement shadow text
// drawing
const BOOL pressed = [self state] && ([self showsStateBy] &
NSChangeBackgroundCellMask);
const CGFloat fgGray = (pressed) ? 0.98 : 0.0;
const CGFloat fgGrayDisabled = 0.5;
const CGFloat shadowGray = (pressed) ? 0.07 : 0.93;
const CGFloat shadowAlpha = ([self isHighlighted]) ? 0.15 : 0.25;
NSString *baseTitle = [NSString stringWithString: [title string]];
NSMutableDictionary *shadowAttrs =
[[[title attributesAtIndex: 0
effectiveRange: NULL] mutableCopy] autorelease];
if (titleRect.origin.y >
frame.origin.y + 1) { // only draw the shadow if it doesn't come
// too close to the edge
[shadowAttrs
setObject: [NSColor colorWithDeviceRed: shadowGray
green: shadowGray
blue: shadowGray
alpha: shadowAlpha]
forKey: NSForegroundColorAttributeName];
NSAttributedString *shadowTitle = [[[NSAttributedString alloc]
initWithString: baseTitle
attributes: shadowAttrs] autorelease];
NSRect shadowRect = NSOffsetRect(titleRect, 0, 1);
[shadowTitle _clipAndDrawInRect: shadowRect];
}
NSMutableDictionary *fgAttrs =
[[shadowAttrs mutableCopy] autorelease];
if ([self isEnabled])
[fgAttrs setObject: [NSColor colorWithDeviceRed: fgGray
green: fgGray
blue: fgGray
alpha: 1.0]
forKey: NSForegroundColorAttributeName];
else
[fgAttrs setObject: [NSColor colorWithDeviceRed: fgGrayDisabled
green: fgGrayDisabled
blue: fgGrayDisabled
alpha: 1.0]
forKey: NSForegroundColorAttributeName];
title = [[[NSAttributedString alloc] initWithString: baseTitle
attributes: fgAttrs]
autorelease];
}
[self drawTitle: title withFrame: titleRect inView: controlView];
}
}
- (NSSize) cellSize {
NSSize result = NSMakeSize(0, 0);
NSAttributedString *title = [self attributedTitle];
NSImage *image = [self image];
BOOL enabled = [self isEnabled] ? YES : ![self imageDimsWhenDisabled];
BOOL mixed = ([self state] == NSMixedState) ? YES : NO;
NSSize imageSize, titleSize;
if (image == nil)
imageSize = NSMakeSize(0, 0);
else if (_controlView)
imageSize = [[_controlView graphicsStyle] sizeOfButtonImage: image
enabled: enabled
mixed: mixed];
else
imageSize = [image size];
if (title == nil) {
titleSize = NSMakeSize(0, 0);
} else {
titleSize = [title size];
}
switch ([self imagePosition]) {
case NSNoImage:
result = titleSize;
break;
case NSImageOnly:
result = imageSize;
break;
case NSImageLeft:
case NSImageRight:
result.width = imageSize.width + 4 + titleSize.width;
result.height = MAX(imageSize.height, titleSize.height);
break;
case NSImageBelow:
case NSImageAbove:
result.width = MAX(imageSize.width, titleSize.width);
result.height = imageSize.height + 4 + titleSize.height;
break;
case NSImageOverlaps:
result.width = MAX(imageSize.width, titleSize.width);
result.height = MAX(imageSize.height, titleSize.height);
break;
}
// Add some margin
result.width += 4;
if ([self isBordered] || [self isBezeled]) {
result.width += 4;
result.height += 4;
}
NSRect adjustment = [self getControlSizeAdjustment: NO];
result.width += adjustment.size.width;
result.height += adjustment.size.height;
return result;
}
- (void) drawWithFrame: (NSRect) frame inView: (NSView *) control {
_controlView = control;
if ([self isTransparent])
return;
[self drawBezelWithFrame: frame inView: control];
[self drawInteriorWithFrame: frame inView: control];
}
- (void) performClick: sender {
if ([_controlView respondsToSelector: @selector(performClick:)])
[_controlView performSelector: @selector(performClick:)
withObject: sender];
}
- (void) mouseEntered: (NSEvent *) event {
NSUnimplementedMethod();
}
- (void) mouseExited: (NSEvent *) event {
NSUnimplementedMethod();
}
@end