/* NSButton.m Button control and associated button cell class Copyright (C) 2000 Free Software Foundation, Inc. Author: Felipe A. Rodriguez Date: June 2000 This file is part of the mySTEP Library and is provided under the terms of the GNU Library General Public License. */ #import #import #import #import #import #import #import #import #import #import #import #import #import "NSAppKitPrivate.h" // class variables id __buttonCellClass = nil; //***************************************************************************** // // NSButtonCell // //***************************************************************************** @implementation NSButtonCell - (id) init { return [self initTextCell:@"Button"]; } // override - (id) initTextCell:(NSString *)aString { #if 0 NSLog(@"%@ (NSButtonCell)initTextCell:%@", self, aString); #endif if((self=[super initTextCell:@"0"])) // aString is not printed but used as the state! { [self setBordered:YES]; // draw bezel [self setShowsFirstResponder:YES]; //default [self setTitle:aString]; // replace title default (@"Button") _bezelStyle=NSRoundedBezelStyle; // default style [self setButtonType:NSMomentaryPushInButton]; _backgroundColor=[[NSColor controlColor] retain]; // default color _c.drawsBackground=NO; // but don't draw background (CHECKME) _periodicDelay = 0.4; _periodicInterval = 0.075; _d.imageScaling=NSScaleNone; // (historical) default is no scaling } #if 0 NSLog(@"%@ initTextCell done", self); #endif return self; } - (id) initImageCell:(NSImage *)anImage { if((self=[self initTextCell:nil])) { // no title _c.imagePosition = NSImageOnly; _normalImage = [anImage retain]; _mixedImage = [[NSImage imageNamed:@"NSMultiStateSwitch"] retain]; } return self; } - (void) dealloc { [_alternateTitle release]; [_alternateImage release]; [_mixedImage release]; [_normalImage release]; [_keyEquivalent release]; [_keyEquivalentFont release]; [super dealloc]; } - (id) copyWithZone:(NSZone *) zone; { NSButtonCell *c = [super copyWithZone:zone]; c->_title = [_title retain]; c->_alternateTitle = [_alternateTitle copyWithZone:zone]; if(_alternateImage) c->_alternateImage = [_alternateImage retain]; if(_normalImage) c->_normalImage = [_normalImage retain]; if(_mixedImage) c->_mixedImage = [_mixedImage retain]; if(_keyEquivalent) { c->_keyEquivalent = [_keyEquivalent copyWithZone:zone]; if(_keyEquivalentFont) c->_keyEquivalentFont = [_keyEquivalentFont retain]; c->_keyEquivalentModifierMask = _keyEquivalentModifierMask; } c->_highlightMask = _highlightMask; c->_stateMask = _stateMask; c->_periodicDelay = _periodicDelay; c->_periodicInterval = _periodicInterval; c->_buttonType = _buttonType; c->_bezelStyle = _bezelStyle; c->_dimsWhenDisabled = _dimsWhenDisabled; c->_transparent = _transparent; return c; } - (NSString *) description; { return [NSString stringWithFormat:@"%@\n" @"title=%@\n" @"stateMask=%02x\n" @"highlightMask=%02x\n" @"buttonType=%d\n" @"bezelStyle=%d\n" @"transparent=%d\n" @"dimsWhenDisabled=%d\n" @"bgcolor=%@\n", [super description], _title, _stateMask, _highlightMask, _buttonType, _bezelStyle, _transparent, _dimsWhenDisabled, _backgroundColor ]; } - (NSColor *) backgroundColor { return _backgroundColor; } - (void) setBackgroundColor:(NSColor *)col { ASSIGN(_backgroundColor,col); } - (NSString *) alternateTitle { return _alternateTitle; } - (void) setAlternateTitle:(NSString *)aStr { ASSIGN(_alternateTitle,aStr); } - (void) setFont:(NSFont *)fontObject { [super setFont:fontObject]; } - (NSCellImagePosition) imagePosition { return _c.imagePosition; } - (NSImageScaling) imageScaling { return _d.imageScaling; } - (NSImage *) alternateImage { return _alternateImage; } - (NSImage *) image { return _normalImage; } - (NSImage *) _mixedImage { return _mixedImage; } - (void) setAlternateImage:(NSImage*)aImage { ASSIGN(_alternateImage,aImage); } - (void) setImage:(NSImage *)anImage { ASSIGN(_normalImage, anImage); } - (void) _setMixedImage:(NSImage *)anImage { ASSIGN(_mixedImage, anImage); } - (NSAttributedString *) attributedTitle { return [_title isKindOfClass:[NSAttributedString class]]?(NSAttributedString *) _title:[[[NSAttributedString alloc] initWithString:_title] autorelease]; } - (NSString *) title { return [_title isKindOfClass:[NSAttributedString class]]?[(NSAttributedString *) _title string]:_title; } - (void) setTitle:(NSString *)title { ASSIGN(_title, title); } - (void) setTitleWithMnemonic:(NSString *)aString; { [self setTitle:aString]; } - (void) setAttributedTitle:(NSAttributedString *)aStr { ASSIGN(_title, aStr); } - (void) setImagePosition:(NSCellImagePosition)aPosition { _c.imagePosition = aPosition; } - (void) setImageScaling:(NSImageScaling)scaling { #if 0 NSLog(@"setImageScaling"); #endif _d.imageScaling = scaling; // [_normalImage setScalesWhenResized: (_d.imageScaling != NSImageScaleNone)]; // [_alternateImage setScalesWhenResized: (_d.imageScaling != NSImageScaleNone)]; // [_mixedImage setScalesWhenResized: (_d.imageScaling != NSImageScaleNone)]; #if 0 NSLog(@"setImageScaling done"); #endif } - (NSSize) cellSize { // FIXME - base on width of title and alternateTitle! Not on contents NSSize m; if(_c.imagePosition != NSImageOnly) { // get title width id savedContents=_contents; // may be some retained object _contents=_title; m=[super cellSize]; // get title size _contents=savedContents; if(_c.bordered) // undo frame calculation m=(NSSize){m.width-10, m.height-10}; else if(_c.bezeled) m=(NSSize){m.width-12, m.height-12}; else m=(NSSize){m.width-8, m.height-8}; // neither } else m=NSZeroSize; #if 0 NSLog(@"super size %@", NSStringFromSize(m)); NSLog(@"control size %d", _d.controlSize); #endif if(_normalImage != nil) { NSSize isz; switch(_d.controlSize) { default: case NSRegularControlSize: isz=NSMakeSize(24.0, 24.0); break; case NSSmallControlSize: isz=NSMakeSize(16.0, 16.0); break; case NSMiniControlSize: isz=NSMakeSize(14.0, 14.0); break; } #if 0 NSLog(@"isz %@", NSStringFromSize(isz)); NSLog(@"image pos %d", _c.imagePosition); #endif switch (_c.imagePosition) { default: case NSImageOnly: m = isz; break; case NSNoImage: break; case NSImageLeft: case NSImageRight: m.width += isz.width + 8; m.height = MAX(isz.height + 4, m.height); break; case NSImageBelow: case NSImageAbove: m.height += isz.height + 4; m.width = MAX(isz.width + 4, m.width); break; case NSImageOverlaps: { m.width = MAX(isz.width + 4, m.width); m.height = MAX(isz.height + 4, m.height); break; } } } // add border if(_c.bordered) m=(NSSize){m.width+4, m.height+4}; else if(_c.bezeled) { // make depend on bezel style m=(NSSize){m.width+12, m.height+12}; } else m=(NSSize){m.width+2, m.height+2}; // neither #if 0 NSLog(@"cell size %@", NSStringFromSize(m)); #endif return m; } - (void) getPeriodicDelay:(float *)delay interval:(float *)interval { if(delay) *delay = _periodicDelay; if(interval) *interval = _periodicInterval; } - (void) setPeriodicDelay:(float)delay interval:(float)interval { if(delay > 60.0) delay=60.0; if(interval > 60.0) interval=60.0; _periodicDelay = delay; _periodicInterval = interval; // Set Repeat Interval } - (void) performClick: (id)sender { [super performClick:sender]; } // Key Equivalent - (NSFont*) keyEquivalentFont { return _keyEquivalentFont; } - (unsigned int) keyEquivalentModifierMask { return _keyEquivalentModifierMask; } // override default implementation of NSCell - (NSString*) keyEquivalent { return _keyEquivalent; } - (void) setKeyEquivalent:(NSString*)key { if (_keyEquivalent != key) ASSIGN(_keyEquivalent, [[key copy] autorelease]); } - (void) setKeyEquivalentModifierMask:(unsigned int)mask { _keyEquivalentModifierMask = mask; } - (void) setKeyEquivalentFont:(NSFont*)fontObj { ASSIGN(_keyEquivalentFont, fontObj); } - (void) setKeyEquivalentFont:(NSString*)fontName size: (float)fontSize { ASSIGN(_keyEquivalentFont, [NSFont fontWithName:fontName size:fontSize]); } - (void) setButtonType:(NSButtonType)buttonType // Graphic Attributes { [self setImageDimsWhenDisabled:YES]; // default switch(_buttonType=buttonType) { case NSMomentaryLightButton: [self setHighlightsBy:NSChangeGrayCellMask | NSChangeBackgroundCellMask]; [self setShowsStateBy:NSNoCellMask]; break; case NSMomentaryPushInButton: [self setHighlightsBy:NSPushInCellMask | NSChangeGrayCellMask | NSChangeBackgroundCellMask]; [self setShowsStateBy:NSNoCellMask]; break; case NSMomentaryChangeButton: [self setHighlightsBy:NSContentsCellMask]; [self setShowsStateBy:NSNoCellMask]; break; case NSPushOnPushOffButton: [self setHighlightsBy:NSPushInCellMask | NSChangeGrayCellMask | NSChangeBackgroundCellMask]; [self setShowsStateBy:NSChangeGrayCellMask | NSChangeBackgroundCellMask]; break; case NSOnOffButton: [self setHighlightsBy:NSChangeGrayCellMask | NSChangeBackgroundCellMask]; [self setShowsStateBy:NSChangeGrayCellMask | NSChangeBackgroundCellMask]; break; case NSToggleButton: [self setHighlightsBy:NSPushInCellMask | NSContentsCellMask]; [self setShowsStateBy:NSContentsCellMask]; break; case NSSwitchButton: [self setHighlightsBy:NSContentsCellMask]; [self setShowsStateBy:NSContentsCellMask]; [self setImage:(NSImage *) [[NSButtonImageSource alloc] initWithName:@"NSSwitch"]]; [self setImagePosition:NSImageLeft]; [self setBordered:NO]; [self setImageDimsWhenDisabled:NO]; [self setImageScaling:NSImageScaleProportionallyDown]; break; case NSRadioButton: [self setHighlightsBy:NSContentsCellMask]; [self setShowsStateBy:NSContentsCellMask]; [self setImage:(NSImage *) [[NSButtonImageSource alloc] initWithName:@"NSRadioButton"]]; [self setImagePosition:NSImageLeft]; [self setBordered:NO]; [self setImageDimsWhenDisabled:NO]; [self setImageScaling:NSImageScaleProportionallyDown]; break; } [self setState:[self state]]; // update our state (to a valid value) } #define stateOrHighlight(mask) ((_c.state != 0 && (_stateMask & (mask))) || (_c.highlighted && (_highlightMask & (mask)))) - (BOOL) isTransparent { return ![self isOpaque]; } - (BOOL) isOpaque { return _backgroundColor && ( !_transparent || _c.bordered || _c.drawsBackground || (_stateMask & NSChangeBackgroundCellMask) || stateOrHighlight(NSChangeGrayCellMask | NSChangeBackgroundCellMask)); } - (int) highlightsBy { return _highlightMask; } - (int) showsStateBy { return _stateMask; } - (NSBezelStyle) bezelStyle; { return _bezelStyle; } - (BOOL) imageDimsWhenDisabled; { return _dimsWhenDisabled; } - (void) setTransparent:(BOOL)flag { _transparent = flag; } - (void) setImageDimsWhenDisabled:(BOOL)flag; { _dimsWhenDisabled = flag; } - (void) setHighlightsBy:(int)mask { _highlightMask = mask; } - (void) setShowsStateBy:(int)mask { _stateMask = mask; } - (void) setBezelStyle:(NSBezelStyle) bezelStyle; { _bezelStyle=bezelStyle; } - (void) setIntValue:(int)anInt { [self setState:(anInt != 0)]; } - (void) setFloatValue:(float)aFloat { [self setState:(aFloat != 0)]; } - (void) setDoubleValue:(double)aDouble { [self setState:(aDouble != 0)]; } - (void) setObjectValue:(id )anObject { if([(id ) anObject respondsToSelector:@selector(intValue)]) [self setState:[(id) anObject intValue]]; else [self setState:(anObject != nil)]; } - (void) stopTracking:(NSPoint) lastPoint at:(NSPoint)stopPoint inView:(NSView*)controlView mouseIsUp:(BOOL)flag; { #if 0 NSLog(@"clicked on %@ bezelStyle=%d", _title, _bezelStyle); #endif if(flag) { if(_buttonType == NSRadioButton) [self setState:NSOnState]; // don't cycle through states else [self setNextState]; // cycle } } // does never use _c.state to allow for subclasses to override -state - (int) intValue { return [self state]; } - (float) floatValue { return [self state]; } - (double) doubleValue { return [self state]; } - (id) objectValue { return [NSNumber numberWithInt:[self state]]; } // FIXME: // visual appearance (colors, shapes, icons) depends on: // _bezelStyle // stateOrHighlight(NSChangeGrayCellMask | NSChangeBackgroundCellMask) // _c.highlighted (should dim the button a little) // _c.bordered // _state (shows icon, toggles background etc.) - (NSRect) drawingRectForBounds:(NSRect) cellFrame { if(!_c.bordered) return cellFrame; // borderless switch(_bezelStyle & 15) // only lower 4 bits are relevant { case _NSTraditionalBezelStyle: break; case NSRoundedBezelStyle: cellFrame=NSInsetRect(cellFrame, 4, floor(cellFrame.size.height*0.1875)); // make smaller than enclosing frame break; case NSRegularSquareBezelStyle: // Square 2 pixels border case NSThickSquareBezelStyle: // 3 px case NSThickerSquareBezelStyle: // 4 px break; case NSDisclosureBezelStyle: cellFrame.origin.x=(cellFrame.size.width-cellFrame.size.height)/2.0; cellFrame.size.width=cellFrame.size.height; // make square in the middle break; case NSShadowlessSquareBezelStyle: break; case NSCircularBezelStyle: cellFrame.origin.x=(cellFrame.size.width-cellFrame.size.height)/2.0; cellFrame.size.width=cellFrame.size.height; // make square in the middle break; case NSTexturedSquareBezelStyle: break; case NSHelpButtonBezelStyle: cellFrame.origin.x=(cellFrame.size.width-cellFrame.size.height)/2.0; cellFrame.size.width=cellFrame.size.height; // make square in the middle break; case NSSmallSquareBezelStyle: break; case NSTexturedRoundBezelStyle: break; case NSRoundRectBezelStyle: cellFrame=NSInsetRect(cellFrame, 4, floor(cellFrame.size.height*0.1875)); // make smaller than enclosing frame break; case NSRecessedBezelStyle: cellFrame=NSInsetRect(cellFrame, 4, floor(cellFrame.size.height*0.1875)); // make smaller than enclosing frame break; case NSRoundedDisclosureBezelStyle: break; case 15: break; } return cellFrame; } - (NSRect) imageRectForBounds:(NSRect)theRect { theRect=[self drawingRectForBounds:theRect]; // handle image position return theRect; } - (NSRect) titleRectForBounds:(NSRect)theRect { theRect=[self drawingRectForBounds:theRect]; // handle text position return theRect; } - (void) drawBezelWithFrame:(NSRect) cellFrame inView:(NSView *) controlView; { NSColor *backgroundColor; NSGraphicsContext *ctxt; NSBezierPath *bezel; #if 0 NSLog(@"drawBezelWithFrame %@", self); #endif #if 0 if([_title isEqualToString:@"Round Textured"]) NSLog(@"drawing %@", _title); #endif #if 0 if([_title isEqualToString:@"Toolbar"]) NSLog(@"drawing %@", _title); #endif if(stateOrHighlight(NSChangeGrayCellMask | NSChangeBackgroundCellMask)) backgroundColor = [NSColor whiteColor]; // FIXME: make background white or light grey dependent on highlight else if(_c.drawsBackground) // not bordered - e.g. Radio Button and Checkbox backgroundColor=_backgroundColor; else backgroundColor = nil; // default: transparent if(!_c.bordered) { // special case for non-bordered buttons if(!backgroundColor && (_stateMask & NSChangeBackgroundCellMask)) backgroundColor = [NSColor windowBackgroundColor]; // show control background even if not bordered (On/Off and Push On/Push Off buttons) if(backgroundColor) { [backgroundColor setFill]; NSRectFill(cellFrame); } return; } cellFrame=[self drawingRectForBounds:cellFrame]; #if 0 NSLog(@"bgcolor=%@", backgroundColor); #endif switch(_bezelStyle & 15) // only lower 4 bits are relevant { case _NSTraditionalBezelStyle: { // traditional Bezel if(stateOrHighlight(NSChangeGrayCellMask | NSChangeBackgroundCellMask)) NSDrawWhiteBezel(cellFrame, cellFrame); else NSDrawGrayBezel(cellFrame, cellFrame); break; } case NSRoundedBezelStyle: { // standard Push Button (half-circle at both ends) ctxt=[NSGraphicsContext currentContext]; if(_c.highlighted != ([_keyEquivalent isEqualToString:@"\r"] || [_keyEquivalent isEqualToString:@"\n"]) && [[controlView window] isKeyWindow]) backgroundColor=[NSColor selectedControlColor]; // selected or default button else if(!backgroundColor) backgroundColor=[NSColor controlColor]; // never transparent bezel=[NSBezierPath _bezierPathWithRoundedBezelInRect:cellFrame vertical:NO]; // box with halfcircular rounded ends [ctxt saveGraphicsState]; [bezel addClip]; // clip to contour [backgroundColor setFill]; [bezel fill]; // fill with background color [ctxt restoreGraphicsState]; [[NSColor blackColor] setStroke]; [bezel stroke]; // and stroke rounded button break; } case NSRegularSquareBezelStyle: // Square 2 pixels border case NSThickSquareBezelStyle: // 3 px case NSThickerSquareBezelStyle: // 4 px { [NSBezierPath _drawRoundedBezel:3 inFrame:cellFrame enabled:YES selected:NO highlighted:stateOrHighlight(NSChangeGrayCellMask | NSChangeBackgroundCellMask) radius:5.0]; // draw segment #if 0 float radius=3.0; ctxt=[NSGraphicsContext currentContext]; bezel=[NSBezierPath new]; [bezel appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(cellFrame)+radius, NSMinY(cellFrame)+radius) radius:radius startAngle:270.0 endAngle:180.0 clockwise:YES]; [bezel appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(cellFrame)+radius, NSMaxY(cellFrame)-radius) radius:radius startAngle:180.0 endAngle:90.0 clockwise:YES]; [bezel appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(cellFrame)-radius, NSMaxY(cellFrame)-radius) radius:radius startAngle:90.0 endAngle:0.0 clockwise:YES]; [bezel appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(cellFrame)-radius, NSMinY(cellFrame)+radius) radius:radius startAngle:0.0 endAngle:270.0 clockwise:YES]; [bezel closePath]; [ctxt saveGraphicsState]; [bezel addClip]; // clip to contour if(stateOrHighlight(NSChangeGrayCellMask | NSChangeBackgroundCellMask)) backgroundColor = [NSColor whiteColor]; // make background white dependent on state/highlight if(!backgroundColor) [[NSColor controlHighlightColor] setFill]; // bordered cell never has transparent background else [backgroundColor setFill]; [bezel fill]; // fill with background color [[NSColor controlShadowColor] setStroke]; // border shadow [ctxt restoreGraphicsState]; [[NSColor blackColor] setStroke]; [bezel stroke]; // and stroke rounded button [bezel release]; #endif break; } case NSDisclosureBezelStyle: { NSImage *img=_image; // img=[[NSButtonImageSource alloc] initWithName:@"NSDisclose"]; // assign as default image source [img drawInRect:cellFrame fromRect:NSZeroRect operation:0 fraction:1.0]; break; } case NSShadowlessSquareBezelStyle: { if(stateOrHighlight(NSChangeGrayCellMask | NSChangeBackgroundCellMask)) [[NSColor controlHighlightColor] setFill]; else if(!backgroundColor) [[NSColor controlColor] setFill]; else [backgroundColor setFill]; NSRectFill(cellFrame); [[NSColor controlDarkShadowColor] setFill]; // border NSFrameRectWithWidth(cellFrame, 1); break; } case NSCircularBezelStyle: { // Round // FIXME: we should get an NSImage ctxt=[NSGraphicsContext currentContext]; bezel=[NSBezierPath bezierPathWithOvalInRect:NSInsetRect(cellFrame, 1, 1)]; // make smaller so that it does not touch frame if(!backgroundColor) backgroundColor=[NSColor controlColor]; [ctxt saveGraphicsState]; [bezel addClip]; [backgroundColor set]; [bezel fill]; // fill oval with background color [ctxt restoreGraphicsState]; [_c.highlighted?[NSColor selectedControlColor]:[NSColor blackColor] set]; // make ring color dependent on highlight state? [bezel stroke]; break; } case NSTexturedSquareBezelStyle: { if(stateOrHighlight(NSChangeGrayCellMask | NSChangeBackgroundCellMask)) [[NSColor controlShadowColor] setFill]; else if(!backgroundColor) [[NSColor controlHighlightColor] setFill]; // bordered cell never has transparent background else [backgroundColor setFill]; NSRectFill(cellFrame); [[NSColor controlDarkShadowColor] setFill]; // border NSFrameRectWithWidth(cellFrame, 1); break; } case NSHelpButtonBezelStyle: { NSSize size; // FIXME: we should draw a HelpButton NSImage ctxt=[NSGraphicsContext currentContext]; bezel=[NSBezierPath bezierPathWithOvalInRect:NSInsetRect(cellFrame, 2, 2)]; // make smaller so that it does not touch frame if(!backgroundColor) backgroundColor=[NSColor controlColor]; [ctxt saveGraphicsState]; [bezel addClip]; [_c.highlighted?[NSColor selectedControlColor]:backgroundColor set]; // fill interior [bezel fill]; // fill oval with background color [ctxt restoreGraphicsState]; [[NSColor blackColor] set]; // black ring [bezel stroke]; size=[@"?" sizeWithAttributes:nil]; cellFrame.origin.x+=(cellFrame.size.width-size.width)/2.0; cellFrame.origin.y+=(cellFrame.size.height-size.height)/2.0; [@"?" drawAtPoint:cellFrame.origin withAttributes:nil]; // default attribs break; } case NSSmallSquareBezelStyle: { if(stateOrHighlight(NSChangeGrayCellMask | NSChangeBackgroundCellMask)) [[NSColor controlHighlightColor] setFill]; else if(!backgroundColor) [[NSColor controlColor] setFill]; else [backgroundColor setFill]; NSRectFill(cellFrame); [[NSColor controlDarkShadowColor] setFill]; // border NSFrameRectWithWidth(cellFrame, 1); break; } case NSTexturedRoundBezelStyle: { if(stateOrHighlight(NSChangeGrayCellMask | NSChangeBackgroundCellMask)) [[NSColor controlShadowColor] setFill]; else if(!backgroundColor) [[NSColor controlHighlightColor] setFill]; // bordered cell never has transparent background else [backgroundColor set]; NSRectFill(cellFrame); [[NSColor controlDarkShadowColor] setFill]; // border NSFrameRectWithWidth(cellFrame, 1); break; } case NSRoundRectBezelStyle: { ctxt=[NSGraphicsContext currentContext]; if(!backgroundColor) backgroundColor=[NSColor controlColor]; bezel=[NSBezierPath _bezierPathWithRoundedBezelInRect:cellFrame vertical:NO]; // box with halfcircular rounded ends [ctxt saveGraphicsState]; [bezel addClip]; // clip to contour [backgroundColor setFill]; [bezel fill]; // fill with background color [ctxt restoreGraphicsState]; [[NSColor lightGrayColor] setStroke]; [bezel stroke]; // and stroke rounded button break; } case NSRecessedBezelStyle: { // has no border ctxt=[NSGraphicsContext currentContext]; if(!backgroundColor) backgroundColor=[NSColor controlColor]; bezel=[NSBezierPath _bezierPathWithRoundedBezelInRect:cellFrame vertical:NO]; // box with halfcircular rounded ends [ctxt saveGraphicsState]; [bezel addClip]; // clip to contour [backgroundColor setFill]; [bezel fill]; // fill with background color [ctxt restoreGraphicsState]; break; } case NSRoundedDisclosureBezelStyle: { // blue regular square with small centered down-pointing arrow [NSBezierPath _drawRoundedBezel:3 inFrame:cellFrame enabled:YES selected:YES highlighted:stateOrHighlight(NSChangeGrayCellMask | NSChangeBackgroundCellMask) radius:5.0]; // draw segment // draw centered [NSImage imageNamed:@"GSArrowDown"] } case 15: NSLog(@"NSButtonCell (%@) unknown bezelStyle:%d", _title, _bezelStyle); break; } } - (void) drawImage:(NSImage *) img withFrame:(NSRect) cellFrame inView:(NSView *) controlView; { NSSize imageSize; NSCompositingOperation op; if(!img || _transparent || _c.imagePosition == NSNoImage) return; // don't draw any image if(stateOrHighlight(NSChangeBackgroundCellMask)) op = NSCompositeSourceOver; // NSCompositeHighlight; // change background else op = NSCompositeSourceOver; // default composition if([img isKindOfClass:[NSButtonImageSource class]]) img=[(NSButtonImageSource *) img buttonImageForCell:self]; // substitute // all this should be moved to -imageRectForBounds imageSize = [img size]; if(_d.imageScaling != NSImageScaleNone) { NSSize isz; switch(_d.controlSize) { default: case NSRegularControlSize: isz=NSMakeSize(18.0, 18.0); break; case NSSmallControlSize: isz=NSMakeSize(13.0, 13.0); break; case NSMiniControlSize: isz=NSMakeSize(10.0, 10.0); break; } if(_d.imageScaling == NSImageScaleAxesIndependently) imageSize=isz; else { // proportionally float factor=MIN(isz.width/imageSize.width, isz.height/imageSize.height); if(_d.imageScaling != NSImageScaleProportionallyDown || factor < 1.0) { // scale image imageSize.width*=factor; imageSize.height*=factor; } } // [img setScalesWhenResized:YES]; // [img setSize:imageSize]; // rescale } switch(_c.imagePosition) { case NSImageOnly: // draw image only - centered case NSImageOverlaps: // draw title over the centered image cellFrame.origin.x += (NSWidth(cellFrame) - imageSize.width)/2; cellFrame.origin.y += (NSHeight(cellFrame) - imageSize.height)/2; break; case NSImageLeft: // draw image to the left of title cellFrame.origin.x += 4; cellFrame.origin.y += (NSHeight(cellFrame) - imageSize.height)/2; break; case NSImageRight: // draw image to the right of the title cellFrame.origin.x += (NSWidth(cellFrame) - imageSize.width) - 4; cellFrame.origin.y += (NSHeight(cellFrame) - imageSize.height)/2; break; case NSImageAbove: // draw image above the title if(![controlView isFlipped]) { cellFrame.origin.x += (NSWidth(cellFrame) - imageSize.width)/2; cellFrame.origin.y += (NSHeight(cellFrame) - imageSize.height) - 4; } else { cellFrame.origin.x += (NSWidth(cellFrame) - imageSize.width)/2; cellFrame.origin.y += 4; } break; case NSImageBelow: // draw image below the title if(![controlView isFlipped]) { cellFrame.origin.x += (NSWidth(cellFrame) - imageSize.width)/2; cellFrame.origin.y += 4; } else { cellFrame.origin.x += (NSWidth(cellFrame) - imageSize.width)/2; cellFrame.origin.y += (NSHeight(cellFrame) - imageSize.height) - 4; } break; } if(stateOrHighlight(NSPushInCellMask)) { // makes button appear pushed in by moving the image by one pixel cellFrame.origin.x += 1; cellFrame.origin.y += 1; } // if([controlView isFlipped]) // cellFrame.origin.y += imageSize.height; #if 0 NSLog(@"drawImage: %@ at %@", img, NSStringFromPoint(cellFrame.origin)); #endif [img drawInRect:(NSRect){ cellFrame.origin, imageSize } fromRect:NSZeroRect operation:op fraction:(_c.highlighted?0.6:(!_dimsWhenDisabled || _c.enabled?1.0:0.5))]; // [img compositeToPoint:cellFrame.origin operation:op fraction:(_c.highlighted?0.6:(!_dimsWhenDisabled || _c.enabled?1.0:0.5))]; } - (void) drawTitle:(NSAttributedString *) title withFrame:(NSRect) cellFrame inView:(NSView *) controlView; { // this is an inofficial method! And, the title can also be an NSString NSColor *titleColor = [NSColor controlTextColor]; // default NSRect textFrame; id savedContents; #if 0 if([title isEqualToString:@"mini"]) NSLog(@"mini"); #endif if(!title || _c.imagePosition == NSImageOnly) return; // don't draw title if(stateOrHighlight(NSChangeGrayCellMask)) { // change text color when highlighting titleColor=[NSColor selectedControlTextColor]; } if(stateOrHighlight(NSPushInCellMask)) { // make button appear pushed in (move text down one pixel?) // might have to depend on bezel style } cellFrame=[self titleRectForBounds:cellFrame]; _d.verticallyCentered=YES; // default is vertically centered within its box textFrame=cellFrame; if(_image && !(_c.imagePosition == NSNoImage || _c.imagePosition == NSImageOverlaps)) { // adjust text field position for image // FIXME: determine text size and position independently of image size! // and draw title either at bottom or top or left or right border // needed for properly drawing Toolbar buttons NSSize imageSize; if([_image isKindOfClass:[NSButtonImageSource class]]) _image=[(NSButtonImageSource *) _image buttonImageForCell:self]; // substitute imageSize=[_image size]; switch(_c.imagePosition) { case NSImageLeft: // draw title to the right of image textFrame.origin.x+=imageSize.width+8; textFrame.size.width-=imageSize.width+8; break; case NSImageRight: // draw title to the left of the image textFrame.origin.x+=4; textFrame.size.width-=imageSize.width+8; break; case NSImageAbove: // draw title below the image [self setAlignment:NSCenterTextAlignment]; _d.verticallyCentered=NO; if(![controlView isFlipped]) { textFrame.origin.y += 4; textFrame.size.height-=imageSize.height+8; } else { textFrame.origin.y += imageSize.height+4; textFrame.size.height-=imageSize.height+8; } break; case NSImageBelow: // draw title above the image [self setAlignment:NSCenterTextAlignment]; _d.verticallyCentered=NO; if(![controlView isFlipped]) { textFrame.origin.y += imageSize.height+4; textFrame.size.height-=imageSize.height+8; } else { textFrame.origin.y += 4; textFrame.size.height-=imageSize.height+8; } break; default: break; } } savedContents=_contents; // FIXME: do we really need to save? We don't use it otherwise [_attribs setObject:titleColor forKey:NSForegroundColorAttributeName]; // change color as needed /* NOTE: this code will also work if title is a NSString or an NSAttributedString or if a NSFormatter is attached i.e. we can easily implement attributedTitle, attributedAlternateTitle etc. */ _contents=title; // draw title by superclass #if 0 // test to find out why NSEnabled is not working properly if(!_c.enabled) NSLog(@"button not enabled: %@", self); #endif #if 0 NSLog(@"ButtonCell draw: %@ textFrame: %@", _contents, NSStringFromRect(textFrame)); #endif [super drawInteriorWithFrame:textFrame inView:controlView]; _contents=savedContents; } - (void) drawWithFrame:(NSRect)cellFrame // Draw cell's frame inView:(NSView*)controlView { NSDebugLog(@"NSButtonCell drawWithFrame \n"); if(_transparent) return; // don't draw if(_d.focusRingType != NSFocusRingTypeNone && [self showsFirstResponder]) { // button is first responder cell - draw focus ring BOOL isFlipped = [controlView isFlipped]; NSColor *y = [NSColor selectedControlColor]; NSColor *c[] = {y, y, y, y}; NSRect cellRing=NSInsetRect(cellFrame, -1, -1); NSDrawColorTiledRects(cellRing, cellRing, isFlipped ? BEZEL_EDGES_FLIPPED : BEZEL_EDGES_NORMAL, c, 4); } [self drawBezelWithFrame:cellFrame inView:controlView]; [self drawInteriorWithFrame:cellFrame inView:controlView]; } - (void) drawInteriorWithFrame:(NSRect) cellFrame inView:(NSView*) controlView { NSAttributedString *title; #if 0 NSLog(@"normimage=%@", _normalImage); NSLog(@"altimage=%@", _alternateImage); #endif if(_c.state == NSMixedState && _mixedImage) _image=_mixedImage; else if(_alternateImage && stateOrHighlight(NSContentsCellMask)) // alternate content _image=_alternateImage; else _image=_normalImage; // default image #if 0 NSLog(@"draw image %@", _image); #endif [self drawImage:_image withFrame:cellFrame inView:controlView]; if(_c.imagePosition == NSImageOnly) return; title=(NSAttributedString *) _title; if([title length] == 0 || stateOrHighlight(NSContentsCellMask)) // standard content { // change to alternate text/image (if defined) if([_alternateTitle length] != 0) title = (NSAttributedString *) _alternateTitle; } #if 0 NSLog(@"draw title %@", title); #endif [self drawTitle:title withFrame:cellFrame inView:controlView]; } - (void) encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; [aCoder encodeObject: _alternateTitle]; [aCoder encodeObject: _alternateImage]; [aCoder encodeObject: _normalImage]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_transparent]; } - (id) initWithCoder:(NSCoder *) aDecoder { self=[super initWithCoder:aDecoder]; // NSCell if([aDecoder allowsKeyedCoding]) { unsigned int buttonflags=[aDecoder decodeIntForKey:@"NSButtonFlags"]; unsigned int buttonflags2=[aDecoder decodeIntForKey:@"NSButtonFlags2"]; _buttonType=-1; // we don't know #if 0 NSLog(@"%@ controlSize=%d", self, [self controlSize]); #endif // the encoding is quite weird // stateby mapping // bit 0 <-> bit 30 // bit 1 <- always 0 // bit 2,3 <-> bit 28,29 #define STATEBY (((buttonflags&(1<<30))>>(30-0))+((buttonflags&(3<<28))>>(28-2))) _stateMask=STATEBY; // highlightsby mapping // bit 0 <-> bit 27 // bit 1 <-> bit 31 // bit 2,3 <-> bit 25,26 #define HIGHLIGHTSBY (((buttonflags&(1<<27))>>(27-0))+((buttonflags&(1<<31))>>(31-1))+((buttonflags&(3<<25))>>(25-2))) _highlightMask=HIGHLIGHTSBY; #define DRAWING ((buttonflags&(1<<24))!=0) // ignored #define BORDERED ((buttonflags&(1<<23))!=0) _c.bordered |= BORDERED; // combine with cell border (meaning isBezeled...) #define OVERLAPS ((buttonflags&0x00400000)!=0) #define IMAGEPOSITION (NSImageAbove-((buttonflags&0x00300000)>>20)) #define IMAGEANDTEXT ((buttonflags&0x00080000)!=0) #define IMAGESIZEDIFF ((buttonflags&0x00040000)!=0) #define KEYEQUIVNOIMAGE ((buttonflags&0x00020000)!=0) if(OVERLAPS) _c.imagePosition=IMAGEANDTEXT?NSImageOverlaps:NSImageOnly; else if(IMAGEANDTEXT) _c.imagePosition=IMAGEPOSITION; else _c.imagePosition=NSNoImage; #define TRANSPARENT ((buttonflags&0x00008000)!=0) _transparent=TRANSPARENT; #define INSET ((buttonflags&0x00006000)>>13) #define DIMSWHENDISABLED ((buttonflags&0x00001000)==0) _dimsWhenDisabled=DIMSWHENDISABLED; #define GRADIENTTYPE ((buttonflags&(7<<9))>>9) #define ALTERNATEMNEMONICLOC ((buttonflags&0x000000ff)>>0) // 0xff=none #define KEYEQUIVALENTMASK ((buttonflags2>>8)&0x00ff) _keyEquivalentModifierMask = KEYEQUIVALENTMASK; // if encoded by flags #define BORDERWHILEMOUSEINSIDE ((buttonflags2&0x00000010)!=0) #define BEZELSTYLE (((buttonflags2&(7<<0))>>0)+((buttonflags2&(8<<2))>>2)) _bezelStyle=BEZELSTYLE; ASSIGN(_alternateTitle, [aDecoder decodeObjectForKey:@"NSAlternateContents"]); ASSIGN(_normalImage, [aDecoder decodeObjectForKey:@"NSNormalImage"]); ASSIGN(_alternateImage, [aDecoder decodeObjectForKey:@"NSAlternateImage"]); if([_alternateImage isKindOfClass:[NSFont class]]) { // bug (or feature?) in IB archiver [self setFont:(NSFont *)_alternateImage]; #if 0 NSLog(@"strange NSAlternateImage %@", _alternateImage); #endif [_alternateImage release], _alternateImage=nil; } if([_normalImage isKindOfClass:[NSFont class]]) { [self setFont:(NSFont *)_alternateImage]; #if 0 NSLog(@"strange NSNormalImage %@", _normalImage); #endif [_normalImage release], _normalImage=nil; } if([_alternateImage isKindOfClass:[NSButtonImageSource class]] || (!_normalImage && _alternateImage)) { // no (relevant) normal image but alternate #if 0 NSLog(@"no NSNormalImage %@ substituting alternate %@", _normalImage, _alternateImage); #endif ASSIGN(_normalImage, _alternateImage), [_alternateImage release], _alternateImage=nil; } if(_normalImage) { // try to deduce the button type from the image name NSString *name; NSLog(@"normalImage=%@", _normalImage); name=[_normalImage name]; if([name isEqualToString:@"NSRadioButton"]) _buttonType=NSRadioButton, _d.imageScaling=NSImageScaleProportionallyDown; else if([name isEqualToString:@"NSSwitch"]) _buttonType=NSSwitchButton, _d.imageScaling=NSImageScaleProportionallyDown; } ASSIGN(_keyEquivalent, [aDecoder decodeObjectForKey:@"NSKeyEquivalent"]); if([aDecoder containsValueForKey:@"NSKeyEquiv"]) ASSIGN(_keyEquivalent, [aDecoder decodeObjectForKey:@"NSKeyEquiv"]); if([aDecoder containsValueForKey:@"NSKeyEquivModMask"]) _keyEquivalentModifierMask = [aDecoder decodeIntForKey:@"NSKeyEquivModMask"]; if([aDecoder containsValueForKey:@"NSAttributedTitle"]) [self setAttributedTitle:[aDecoder decodeObjectForKey:@"NSAttributedTitle"]]; // overwrite _periodicDelay = 0.001*[aDecoder decodeIntForKey:@"NSPeriodicDelay"]; _periodicInterval = 0.001*[aDecoder decodeIntForKey:@"NSPeriodicInterval"]; #if 0 NSLog(@"initWithCoder final: %@", self); NSLog(@" title=%@", _title); NSLog(@" normalImage=%@", _normalImage); NSLog(@" alternateImage=%@", _alternateImage); NSLog(@" NSMnemonicLoc=%d", [aDecoder decodeIntForKey:@"NSMnemonicLoc"]); NSLog(@" NSButtonFlags=%08x", [aDecoder decodeIntForKey:@"NSButtonFlags"]); // encodes the button type&style NSLog(@" NSButtonFlags2=%08x", [aDecoder decodeIntForKey:@"NSButtonFlags2"]); NSLog(@" bezelstyle=%d", _bezelStyle); NSLog(@" buttontype=%d", _buttonType); NSLog(@" transparent=%d", _transparent); NSLog(@" stateMask=%d", _stateMask); NSLog(@" highlightMask=%d", _highlightMask); NSLog(@" dimsWhenDisabled=%d", _dimsWhenDisabled); #endif return self; } _alternateTitle = [[aDecoder decodeObject] retain]; _alternateImage = [[aDecoder decodeObject] retain]; _normalImage = [[aDecoder decodeObject] retain]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_transparent]; return self; } @end //***************************************************************************** // // NSButton // //***************************************************************************** @implementation NSButton + (void) initialize { if (self == [NSButton class]) __buttonCellClass = [NSButtonCell class]; } + (Class) cellClass { return __buttonCellClass; } + (void) setCellClass:(Class)aClass { __buttonCellClass = aClass; } - (id) initWithFrame:(NSRect)frameRect { if((self=[super initWithFrame:frameRect])) // installs a default cell { _v.autoresizingMask = (NSViewMaxXMargin | NSViewMaxYMargin); } return self; } - (void) getPeriodicDelay:(float*)delay interval:(float*)interval { [_cell getPeriodicDelay:delay interval:interval]; } - (void) setPeriodicDelay:(float)delay interval:(float)interval { [_cell setPeriodicDelay:delay interval:interval]; } - (void) setButtonType:(NSButtonType)aType { [_cell setButtonType:aType]; [self setNeedsDisplay:YES]; } - (void) setState:(int)value { [_cell setState:value]; [self setNeedsDisplay:YES]; } - (void) setNextState { [_cell setNextState]; [self setNeedsDisplay:YES]; } - (void) setIntValue:(int)anInt { [_cell setIntValue:anInt]; } - (void) setFloatValue:(float)aFloat { [_cell setFloatValue:aFloat]; } - (void) setDoubleValue:(double)aDouble { [_cell setDoubleValue:aDouble]; } - (void) setObjectValue:(id )val { [_cell setObjectValue:val]; } - (int) state { return [_cell state]; } - (NSString*) alternateTitle { return [_cell alternateTitle]; } - (NSAttributedString*) attributedAlternateTitle { return [_cell attributedAlternateTitle]; } - (NSAttributedString*) attributedTitle { return [_cell attributedTitle]; } - (NSString*) title { return [_cell title]; } - (NSString*) keyEquivalent { return [_cell keyEquivalent]; } - (NSImage*) image { return [_cell image]; } - (NSImage*) alternateImage { return [_cell alternateImage]; } - (NSCellImagePosition) imagePosition { return [_cell imagePosition]; } - (BOOL) allowsMixedState { return [_cell allowsMixedState]; } - (BOOL) isBordered { return [_cell isBordered]; } - (BOOL) isTransparent { return [_cell isTransparent]; } - (BOOL) isOpaque { return [_cell isOpaque]; } - (BOOL) isFlipped { return YES; } - (BOOL) showsBorderOnlyWhileMouseInside { return [_cell showsBorderOnlyWhileMouseInside]; } - (NSBezelStyle) bezelStyle; { return [_cell bezelStyle]; } - (void) setAlternateTitle:(NSString *)aString { [_cell setAlternateTitle:aString]; [self setNeedsDisplay:YES]; } - (void) setAttributedAlternateTitle:(NSAttributedString *)aString { [_cell setAttributedAlternateTitle:aString]; [self setNeedsDisplay:YES]; } - (void) setAttributedTitle:(NSAttributedString *)aString { [_cell setAttributedTitle:aString]; [self setNeedsDisplay:YES]; } - (void) setTitle:(NSString *)aString { [_cell setTitle:aString]; [self setNeedsDisplay:YES]; } - (void) setTitleWithMnemonic:(NSString *)aString { [_cell setTitleWithMnemonic:aString]; [self setNeedsDisplay:YES]; } - (void) setAlternateImage:(NSImage *)anImage { [_cell setAlternateImage:anImage]; [self setNeedsDisplay:YES]; } - (void) setImage:(NSImage *)anImage { [_cell setImage:anImage]; [self setNeedsDisplay:YES]; } - (void) setImagePosition:(NSCellImagePosition)aPosition { [_cell setImagePosition:aPosition]; [self setNeedsDisplay:YES]; } - (void) setShowsBorderOnlyWhileMouseInside:(BOOL)flag { [_cell setShowsBorderOnlyWhileMouseInside:flag]; } - (void) setAllowsMixedState:(BOOL)flag { [_cell setAllowsMixedState:flag]; [self setNeedsDisplay:YES]; } - (void) setBezelStyle:(NSBezelStyle) style; { [_cell setBezelStyle:style]; [self setNeedsDisplay:YES]; } - (void) setBordered:(BOOL)flag { [_cell setBordered:flag]; // has a different interpretation in button cells than in NSCell [self setNeedsDisplay:YES]; } - (void) setTransparent:(BOOL)flag { [_cell setTransparent:flag]; [self setNeedsDisplay:YES]; } - (void) setSound:(NSSound *) sound { [_cell setSound:sound]; } - (NSSound *) sound; { return [_cell sound]; } - (void) highlight:(BOOL)flag { [_cell highlight:flag withFrame:_bounds inView:self]; } - (void) setKeyEquivalent:(NSString*)aKeyEquivalent // Key Equivalent { [_cell setKeyEquivalent: aKeyEquivalent]; } - (unsigned int) keyEquivalentModifierMask { return [_cell keyEquivalentModifierMask]; } - (void) setKeyEquivalentModifierMask:(unsigned int)mask { [_cell setKeyEquivalentModifierMask: mask]; } - (BOOL) acceptsFirstResponder { return [super acceptsFirstResponder] || ([self keyEquivalent] != nil); } - (void) keyDown:(NSEvent*)event { // CHECKME - is this reasonable behaviour? if(([self isEnabled] && [event keyCode] == ' ')) // Space [self performClick: self]; else [super keyDown: event]; } // NOTE: already defined in NSControl... - (void) performClick:(id)sender // Handle Events { #if 0 NSLog(@"performClick %@", self); #endif [_cell performClick: sender]; } - (BOOL) performKeyEquivalent:(NSEvent *)anEvent { if([self isEnabled]) { NSString *key = [self keyEquivalent]; unsigned int modifiers=[anEvent modifierFlags] & (NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask | NSShiftKeyMask); if([anEvent modifierFlags] == modifiers && [key isEqualToString: [anEvent charactersIgnoringModifiers]]) { #if 0 NSLog(@"%@ performKeyEquivalent -> YES", self); #endif [self performClick:self]; return YES; } } #if 0 NSLog(@"%@ performKeyEquivalent -> NO", self); #endif return NO; } - (void) encodeWithCoder:(NSCoder *) aCoder { [super encodeWithCoder:aCoder]; } - (id) initWithCoder:(NSCoder *) aDecdr { return [super initWithCoder:aDecdr]; } @end /* NSButton */