1/** <title>NSButtonCell</title>
2
3   <abstract>The button cell class</abstract>
4
5   Copyright (C) 1996-1999 Free Software Foundation, Inc.
6
7   Author:  Scott Christley <scottc@net-community.com>
8            Ovidiu Predescu <ovidiu@net-community.com>
9   Date: 1996
10   Author:  Felipe A. Rodriguez <far@ix.netcom.com>
11   Date: August 1998
12
13   Modified: Richard Frith-Macdonald <richard@brainstorm.co.uk>
14
15   This file is part of the GNUstep GUI Library.
16
17   This library is free software; you can redistribute it and/or
18   modify it under the terms of the GNU Lesser General Public
19   License as published by the Free Software Foundation; either
20   version 2 of the License, or (at your option) any later version.
21
22   This library is distributed in the hope that it will be useful,
23   but WITHOUT ANY WARRANTY; without even the implied warranty of
24   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
25   Lesser General Public License for more details.
26
27   You should have received a copy of the GNU Lesser General Public
28   License along with this library; see the file COPYING.LIB.
29   If not, see <http://www.gnu.org/licenses/> or write to the
30   Free Software Foundation, 51 Franklin Street, Fifth Floor,
31   Boston, MA 02110-1301, USA.
32*/
33
34#import "config.h"
35#import <Foundation/NSArray.h>
36#import <Foundation/NSAttributedString.h>
37#import <Foundation/NSDebug.h>
38#import <Foundation/NSException.h>
39#import <Foundation/NSLock.h>
40#import <Foundation/NSString.h>
41#import <Foundation/NSValue.h>
42
43#import "AppKit/AppKitExceptions.h"
44#import "AppKit/NSApplication.h"
45#import "AppKit/NSBezierPath.h"
46#import "AppKit/NSButtonCell.h"
47#import "AppKit/NSButton.h"
48#import "AppKit/NSColor.h"
49#import "AppKit/NSEvent.h"
50#import "AppKit/NSFont.h"
51#import "AppKit/NSGraphics.h"
52#import "AppKit/NSImage.h"
53#import "AppKit/NSSound.h"
54#import "AppKit/NSStringDrawing.h"
55#import "AppKit/NSWindow.h"
56#import "GNUstepGUI/GSTheme.h"
57#import "GNUstepGUI/GSNibLoading.h"
58#import "GSGuiPrivate.h"
59#import "GSCodingFlags.h"
60
61#include <math.h>
62
63@interface NSCell (Private)
64- (NSSize) _scaleImageWithSize: (NSSize)imageSize
65                   toFitInSize: (NSSize)canvasSize
66                   scalingType: (NSImageScaling)scalingType;
67@end
68
69/**<p> TODO Description</p>
70 */
71@implementation NSButtonCell
72
73/*
74 * Class methods
75 */
76+ (void) initialize
77{
78  if (self == [NSButtonCell class])
79    [self setVersion: 3];
80}
81
82/*
83 * Instance methods
84 */
85- (id) _init
86{
87  // Implicitly performed by allocation:
88  //
89  //_buttoncell_is_transparent = NO;
90
91  [self setAlignment: NSCenterTextAlignment];
92  _cell.is_bordered = YES;
93  [self setButtonType: NSMomentaryPushInButton];
94  _delayInterval = 0.4;
95  _repeatInterval = 0.075;
96  _keyEquivalentModifierMask = 0;
97  _keyEquivalent = @"";
98  _altContents = @"";
99  _gradient_type = NSGradientNone;
100  [self setImageScaling: NSImageScaleNone];
101
102  return self;
103}
104
105- (id) init
106{
107  self = [self initTextCell: @"Button"];
108
109  return self;
110}
111
112- (id) initImageCell: (NSImage*)anImage
113{
114  self = [super initImageCell: anImage];
115  if (!self)
116    return nil;
117
118  return [self _init];
119}
120
121- (id) initTextCell: (NSString*)aString
122{
123  self = [super initTextCell: aString];
124  if (!self)
125    return nil;
126
127  return [self _init];
128}
129
130- (void) dealloc
131{
132  RELEASE(_altContents);
133  RELEASE(_altImage);
134  RELEASE(_keyEquivalent);
135  RELEASE(_keyEquivalentFont);
136  RELEASE(_sound);
137  RELEASE(_backgroundColor);
138
139  [super dealloc];
140}
141
142/**
143 *<p>The GNUstep implementation does nothing here
144 *  (to match the Mac OS X behavior) because with
145 * NSButtonCell GNUstep implementation the cell type is bound to the image
146 * position. We implemented this behavior because it permits to have
147 * -setFont: -setTitle -setImage: methods which are symetrical by not altering
148 * directly the cell type and to validate the fact that the cell type is more
149 * characterized by the potential visibility of the image (which is under the
150 * control of the method -setImagePosition:) than by the value of the image
151 * ivar itself (related to -setImage: method).
152 * On Mac OS X, the NSButtonCell cell type is NSTextCellType by default or
153 * NSImageCellType if the initialization has been done with -initImageCell:,
154 * it should be noted that the cell type never changes later.</p>
155 */
156- (void) setType: (NSCellType)aType
157{
158}
159
160/** <p>Returns the NSButtonCell's title.</p>
161 */
162- (NSString*) title
163{
164  if (nil == _contents)
165    {
166      return @"";
167    }
168
169  if (_cell.contents_is_attributed_string == NO)
170    {
171      // If we have a formatter this is also the string of the _object_value
172      return (NSString *)_contents;
173    }
174  else
175    {
176      return [(NSAttributedString *)_contents string];
177    }
178}
179
180/** <p>Returns the NSButtonCell's alternate title ( used when highlighted ).
181    </p><p>See Also: -setAlternateTitle:</p>
182 */
183- (NSString*) alternateTitle
184{
185  if (_altContents != nil)
186    {
187      return _altContents;
188    }
189  else
190    {
191      return @"";
192    }
193}
194
195- (NSInteger) cellAttribute: (NSCellAttribute)aParameter
196{
197  NSInteger value = 0;
198
199  switch (aParameter)
200    {
201    case NSPushInCell:
202      if (_highlightsByMask & NSPushInCellMask)
203        value = 1;
204      break;
205    case NSChangeGrayCell:
206      if (_showAltStateMask & NSChangeGrayCellMask)
207        value = 1;
208      break;
209    case NSCellLightsByGray:
210      if (_highlightsByMask & NSChangeGrayCellMask)
211        value = 1;
212      break;
213    case NSChangeBackgroundCell:
214      if (_showAltStateMask & NSChangeBackgroundCellMask)
215        value = 1;
216      break;
217    case NSCellLightsByBackground:
218      if (_highlightsByMask & NSChangeBackgroundCellMask)
219        value = 1;
220      break;
221    case NSCellChangesContents:
222      if (_showAltStateMask & NSContentsCellMask)
223        value = 1;
224      break;
225    case NSCellLightsByContents:
226      if (_highlightsByMask & NSContentsCellMask)
227        value = 1;
228      break;
229    default:
230      value = [super cellAttribute: aParameter];
231      break;
232    }
233
234  return value;
235}
236
237- (void) setCellAttribute: (NSCellAttribute)aParameter to: (NSInteger)value
238{
239  switch (aParameter)
240    {
241    case NSPushInCell:
242      if (value)
243        _highlightsByMask |= NSPushInCellMask;
244      else
245        _highlightsByMask &= ~NSPushInCellMask;
246      break;
247    case NSChangeGrayCell:
248      if (value)
249        _showAltStateMask |= NSChangeGrayCellMask;
250      else
251        _showAltStateMask &= ~NSChangeGrayCellMask;
252      break;
253    case NSChangeBackgroundCell:
254      if (value)
255        _showAltStateMask |= NSChangeBackgroundCellMask;
256      else
257        _showAltStateMask &= ~NSChangeBackgroundCellMask;
258      break;
259    case NSCellChangesContents:
260      if (value)
261        _showAltStateMask |= NSContentsCellMask;
262      else
263        _showAltStateMask &= ~NSContentsCellMask;
264      break;
265    case NSCellLightsByGray:
266      if (value)
267        _highlightsByMask |= NSChangeGrayCellMask;
268      else
269        _highlightsByMask &= ~NSChangeGrayCellMask;
270      break;
271    case NSCellLightsByBackground:
272      if (value)
273        _highlightsByMask |= NSChangeBackgroundCellMask;
274      else
275        _highlightsByMask &= ~NSChangeBackgroundCellMask;
276      break;
277    case NSCellLightsByContents:
278      if (value)
279        _highlightsByMask |= NSContentsCellMask;
280      else
281        _highlightsByMask &= ~NSContentsCellMask;
282      break;
283    default:
284      [super setCellAttribute: aParameter to: value];
285    }
286}
287
288/** <p>Sets the NSButtonCell's font to <var>fontObject</var>.
289    The key equivalent font size is changed to match the <var>fontObject</var>
290    if needed.</p><p>See Also: [NSCell-font] -keyEquivalentFont
291    -setKeyEquivalentFont: -setKeyEquivalentFont:size:</p>
292 */
293- (void) setFont: (NSFont*)fontObject
294{
295  int size;
296
297  [super setFont: fontObject];
298
299  if ((_keyEquivalentFont != nil) && (fontObject != nil)
300    && ((size = [fontObject pointSize]) != [_keyEquivalentFont pointSize]))
301    {
302      [self setKeyEquivalentFont: [_keyEquivalentFont fontName]
303            size: size];
304    }
305}
306
307/**<p>Sets the NSButtonCell's title to <var>aString</var>.</p>
308 */
309- (void) setTitle: (NSString*)aString
310{
311  ASSIGNCOPY(_contents, aString);
312  _cell.contents_is_attributed_string = NO;
313
314  if (_control_view)
315    {
316      if ([_control_view isKindOfClass: [NSControl class]])
317        {
318          [(NSControl*)_control_view updateCell: self];
319        }
320    }
321}
322
323/**<p>Sets the NSButtonCell's alternate title ( used when highlighted )
324   to <var>aString</var> and update the cell if it contains
325   a NSControl view.</p><p>See Also: -alternateTitle</p>
326 */
327- (void) setAlternateTitle: (NSString*)aString
328{
329  ASSIGNCOPY(_altContents, aString);
330
331  if (_control_view)
332    {
333      if ([_control_view isKindOfClass: [NSControl class]])
334        {
335          [(NSControl*)_control_view updateCell: self];
336        }
337    }
338}
339
340- (NSAttributedString *)attributedAlternateTitle
341{
342  // TODO
343  NSDictionary *dict;
344  NSAttributedString *attrStr;
345
346  dict = [self _nonAutoreleasedTypingAttributes];
347  attrStr = [[NSAttributedString alloc] initWithString: [self alternateTitle]
348                                        attributes: dict];
349  RELEASE(dict);
350
351  return AUTORELEASE(attrStr);
352}
353
354- (void)setAttributedAlternateTitle:(NSAttributedString *)aString
355{
356  // TODO
357  [self setAlternateTitle: [aString string]];
358}
359
360- (NSAttributedString *)attributedTitle
361{
362  if (_cell.contents_is_attributed_string &&
363      nil != _contents)
364    {
365      return (NSAttributedString *)_contents;
366    }
367  else
368    {
369      NSDictionary *dict;
370      NSAttributedString *attrStr;
371
372      dict = [self _nonAutoreleasedTypingAttributes];
373      attrStr = [[NSAttributedString alloc] initWithString: [self title]
374                                            attributes: dict];
375      RELEASE(dict);
376      return AUTORELEASE(attrStr);
377    }
378}
379
380- (void)setAttributedTitle:(NSAttributedString *)aString
381{
382  ASSIGNCOPY(_contents, aString);
383  _cell.contents_is_attributed_string = YES;
384
385  if (_control_view)
386    {
387      if ([_control_view isKindOfClass: [NSControl class]])
388        {
389          [(NSControl*)_control_view updateCell: self];
390        }
391    }
392}
393
394- (void)setTitleWithMnemonic:(NSString *)aString
395{
396  // TODO
397  [super setTitleWithMnemonic: aString];
398}
399
400- (NSString *)alternateMnemonic
401{
402  // TODO
403  return @"";
404}
405
406- (NSUInteger)alternateMnemonicLocation
407{
408  // TODO
409  return NSNotFound;
410}
411
412- (void)setAlternateMnemonicLocation:(NSUInteger)location
413{
414  // TODO
415}
416
417- (void)setAlternateTitleWithMnemonic:(NSString *)aString
418{
419  NSUInteger location = [aString rangeOfString: @"&"].location;
420
421  [self setAlternateTitle: [aString stringByReplacingString: @"&"
422                                    withString: @""]];
423  // TODO: We should underline this character
424  [self setAlternateMnemonicLocation: location];
425}
426
427/**<p>Returns the NSButtonCell's alternate image.</p>
428   <p>See Also: -setAlternateImage:</p>
429 */
430- (NSImage*) alternateImage
431{
432  return _altImage;
433}
434
435/** <p>Returns the NSButtonCell's image position. See <ref type="type"
436    id="NSCellImagePosition">NSCellImagePosition</ref> for more information.
437    </p><p>See Also: -setImagePosition:</p>
438 */
439- (NSCellImagePosition) imagePosition
440{
441  return _cell.image_position;
442}
443
444- (NSImageScaling) imageScaling
445{
446  return _imageScaling;
447}
448
449- (void) setImageScaling: (NSImageScaling)scaling
450{
451  _imageScaling = scaling;
452}
453
454- (void) setImage: (NSImage *)anImage
455{
456  if (_cell.image_position == NSNoImage)
457    {
458      [self setImagePosition: NSImageOnly];
459    }
460
461  [super setImage: anImage];
462}
463
464/**<p>Sets the NSButtonCell's alternate image to <var>anImage</var>.</p>
465   <p>See Also: -alternateImage</p>
466 */
467- (void) setAlternateImage: (NSImage*)anImage
468{
469  ASSIGN(_altImage, anImage);
470
471  if (_control_view)
472    {
473      if ([_control_view isKindOfClass: [NSControl class]])
474        {
475          [(NSControl*)_control_view updateCell: self];
476        }
477    }
478}
479
480/**<p>Sets the image position. The GNUstep implementation depends only on
481   the image position. If the image position is set to <ref type="type"
482   id="NSCellImagePosition">NSNoImage</ref> then the type is set to
483   <ref type="type" id="NSCellImagePosition">NSTextCellType</ref>, to
484   <ref type="type" id="NSCellImagePosition">NSImageCellType</ref> otherwise
485   </p><p>See Also: -imagePosition</p>
486*/
487- (void) setImagePosition: (NSCellImagePosition)aPosition
488{
489  _cell.image_position = aPosition;
490
491  // In the GNUstep NSButtonCell implementation, the cell type depends only on
492  // the image position.
493  /* NOTE: We set the cell type attribute directly here instead of calling
494     NSCell's -setType: method because it may change the title or image of
495     the button cell. This is to make our implementation compatible with
496     the behavior of Mac OS X, which does not change the cell's type and
497     hence does not involve any of the side effects of -setType: either. */
498  if (_cell.image_position == NSNoImage)
499    {
500      _cell.type = NSTextCellType;
501    }
502  else
503    {
504      _cell.type = NSImageCellType;
505    }
506
507  if (_control_view)
508    {
509      if ([_control_view isKindOfClass: [NSControl class]])
510        {
511          [(NSControl*)_control_view updateCell: self];
512        }
513    }
514}
515
516/**<p>Gets the NSButtonCell's <var>delay</var> and the <var>interval</var>
517   parameters used when NSButton sends continouly action messages.
518   By default <var>delay</var> is 0.4 and <var>interval</var> is 0.075.</p>
519   <p>See Also: -setPeriodicDelay:interval:
520   [NSCell-trackMouse:inRect:ofView:untilMouseUp:]</p>
521 */
522- (void) getPeriodicDelay: (float*)delay interval: (float*)interval
523{
524  *delay = _delayInterval;
525  *interval = _repeatInterval;
526}
527
528/** <p>Sets the NSButtonCell's  <var>delay</var> and <var>interval</var>
529    parameters used when NSButton sends continouly action messages.
530    By default <var>delay</var> is 0.4 and <var>interval</var> is 0.075.</p>
531    <p>See Also: -getPeriodicDelay:interval:
532    [NSCell-trackMouse:inRect:ofView:untilMouseUp:]</p>
533 */
534- (void) setPeriodicDelay: (float)delay interval: (float)interval
535{
536  _delayInterval = delay;
537  _repeatInterval = interval;
538}
539
540/**<p>Returns the NSButtonCell's key equivalent. The key equivalent and its
541   modifier mask are used to simulate the click of the button in
542   [NSButton-performKeyEquivalent:]. Returns an empty string if no key
543   equivalent is defined. By default NSButtonCell hasn't key equivalent.</p>
544   <p>See Also: -setKeyEquivalent: [NSButton-performKeyEquivalent:]
545   -keyEquivalentModifierMask [NSButtonCell-keyEquivalent]</p>
546 */
547- (NSString*) keyEquivalent
548{
549  if (nil == _keyEquivalent)
550    {
551      return @"";
552    }
553  else
554    {
555      return _keyEquivalent;
556    }
557}
558
559/**<p>Returns the NSFont of the key equivalent.</p>
560 *<p>See Also: -setKeyEquivalentFont:</p>
561 */
562- (NSFont*) keyEquivalentFont
563{
564  return _keyEquivalentFont;
565}
566
567/** <p>Returns the modifier mask of the NSButtonCell's key equivalent.
568    The key equivalent and its modifier mask are used to simulate the click
569    of the button in  [NSButton-performKeyEquivalent:]. The default mask is
570    0.</p><p>See Also: -setKeyEquivalentModifierMask:
571    -keyEquivalent [NSButton-performKeyEquivalent:]</p>
572 */
573- (NSUInteger) keyEquivalentModifierMask
574{
575  return _keyEquivalentModifierMask;
576}
577
578/** <p>Sets the NSButtonCell's key equivalent to <var>key</var>. The key
579    equivalent and its modifier mask are used to simulate the click
580    of the button in  [NSButton-performKeyEquivalent:]. By default NSButton
581    hasn't   key equivalent.</p><p>See Also: -keyEquivalent
582    -setKeyEquivalentModifierMask: [NSButton-performKeyEquivalent:]</p>
583*/
584- (void) setKeyEquivalent: (NSString*)key
585{
586  [[GSTheme theme] setKeyEquivalent: key
587		      forButtonCell: self];
588  ASSIGNCOPY(_keyEquivalent, key);
589}
590
591/** <p>Sets the modifier mask of the NSButtonCell's key equivalent to
592    <var>mask</var>. The key equivalent and its modifier mask are used to
593    simulate the click of the button in [NSButton-performKeyEquivalent:].
594    By default the mask is 0.</p>
595    <p>See Also: -keyEquivalentModifierMask
596    -setKeyEquivalent: [NSButton-performKeyEquivalent:]</p>
597*/
598- (void) setKeyEquivalentModifierMask: (NSUInteger)mask
599{
600  _keyEquivalentModifierMask = mask;
601}
602
603/**<p>Sets the NSFont of the key equivalent to <var>fontObject</var>.</p>
604 *<p>See Also: -keyEquivalentFont -setFont:</p>
605 */
606- (void) setKeyEquivalentFont: (NSFont*)fontObj
607{
608  ASSIGN(_keyEquivalentFont, fontObj);
609}
610
611/**<p>Sets the NSFont with size <var>fontSize</var> of the key equivalent
612   to <var>fontName</var>.</p>
613   <p>See Also: -keyEquivalentFont -setKeyEquivalentFont: -setFont:</p>
614 */
615- (void) setKeyEquivalentFont: (NSString*)fontName size: (float)fontSize
616{
617  ASSIGN(_keyEquivalentFont, [NSFont fontWithName: fontName size: fontSize]);
618}
619
620/**<p>Returns whether the button cell is transparent.</p>
621   <p>See Also: -setTransparent:</p>
622 */
623- (BOOL) isTransparent
624{
625  return _buttoncell_is_transparent;
626}
627
628/**<p>Sets whether the button cell is transparent.</p>
629   <p>See Also: -isTransparent </p>
630 */
631- (void) setTransparent: (BOOL)flag
632{
633  _buttoncell_is_transparent = flag;
634}
635
636/**<p>Returns whether the NSButtonCell is opaque. Currently always
637   returns NO</p>*/
638- (BOOL) isOpaque
639{
640  // May not be opaque, due to themes
641  return NO;
642
643  //  return !_buttoncell_is_transparent && _cell.is_bordered &&
644  //    _bezel_style == 0;
645}
646
647- (NSBezelStyle) bezelStyle
648{
649  return _bezel_style;
650}
651
652- (void) setBezelStyle: (NSBezelStyle)bezelStyle
653{
654  _bezel_style = bezelStyle;
655}
656
657- (BOOL) showsBorderOnlyWhileMouseInside
658{
659  return _shows_border_only_while_mouse_inside;
660}
661
662- (void) setShowsBorderOnlyWhileMouseInside: (BOOL)show
663{
664  if (_shows_border_only_while_mouse_inside == show)
665    {
666      return;
667    }
668
669  _shows_border_only_while_mouse_inside = show;
670  // FIXME Switch mouse tracking on
671}
672
673- (NSGradientType) gradientType
674{
675  return _gradient_type;
676}
677
678- (void) setGradientType: (NSGradientType)gradientType
679{
680  _gradient_type = gradientType;
681}
682
683- (BOOL) imageDimsWhenDisabled
684{
685  return _image_dims_when_disabled;
686}
687
688- (void) setImageDimsWhenDisabled:(BOOL)flag
689{
690  _image_dims_when_disabled = flag;
691}
692
693/**<p>Returns a mask describing how the button cell is highlighted : </p>
694   <p> NSNoCellMask, NSContentsCellMask,NSPushInCellMask,NSChangeGrayCellMask,
695   NSChangeBackgroundCellMask</p>
696   <p>See Also : -setHighlightsBy:</p>
697 */
698- (NSInteger) highlightsBy
699{
700  return _highlightsByMask;
701}
702
703/**<p>Sets a mask describing how the button cell is highlighted :  </p>
704   <p> NSNoCellMask, NSContentsCellMask,NSPushInCellMask,NSChangeGrayCellMask,
705   NSChangeBackgroundCellMask</p>
706   <p>See Also : -highlightsBy</p>
707 */
708- (void) setHighlightsBy: (NSInteger)mask
709{
710  _highlightsByMask = mask;
711}
712
713- (void) setShowsStateBy: (NSInteger)mask
714{
715  _showAltStateMask = mask;
716}
717
718- (void) setButtonType: (NSButtonType)buttonType
719{
720  // Don't store the button type anywhere
721
722  switch (buttonType)
723    {
724      case NSMomentaryLightButton:
725        [self setHighlightsBy: NSChangeBackgroundCellMask];
726        [self setShowsStateBy: NSNoCellMask];
727        [self setImageDimsWhenDisabled: YES];
728        break;
729      case NSMomentaryPushInButton:
730        [self setHighlightsBy: NSPushInCellMask | NSChangeGrayCellMask];
731        [self setShowsStateBy: NSNoCellMask];
732        [self setImageDimsWhenDisabled: YES];
733        break;
734      case NSMomentaryChangeButton:
735        [self setHighlightsBy: NSContentsCellMask];
736        [self setShowsStateBy: NSNoCellMask];
737        [self setImageDimsWhenDisabled: YES];
738        break;
739      case NSPushOnPushOffButton:
740        [self setHighlightsBy: NSPushInCellMask | NSChangeGrayCellMask];
741        [self setShowsStateBy: NSChangeBackgroundCellMask];
742        [self setImageDimsWhenDisabled: YES];
743        break;
744      case NSOnOffButton:
745        [self setHighlightsBy: NSChangeBackgroundCellMask];
746        [self setShowsStateBy: NSChangeBackgroundCellMask];
747        [self setImageDimsWhenDisabled: YES];
748        break;
749      case NSToggleButton:
750        [self setHighlightsBy: NSPushInCellMask | NSContentsCellMask];
751        [self setShowsStateBy: NSContentsCellMask];
752        [self setImageDimsWhenDisabled: YES];
753        break;
754      case NSSwitchButton:
755        [self setHighlightsBy: NSContentsCellMask];
756        [self setShowsStateBy: NSContentsCellMask];
757        [self setImage: [NSImage imageNamed: @"NSSwitch"]];
758        [self setAlternateImage: [NSImage imageNamed: @"NSHighlightedSwitch"]];
759        [self setImagePosition: NSImageLeft];
760        [self setAlignment: NSLeftTextAlignment];
761        [self setBordered: NO];
762        [self setBezeled: NO];
763        [self setImageDimsWhenDisabled: NO];
764        break;
765      case NSRadioButton:
766        [self setHighlightsBy: NSContentsCellMask];
767        [self setShowsStateBy: NSContentsCellMask];
768        [self setImage: [NSImage imageNamed: @"NSRadioButton"]];
769        [self setAlternateImage: [NSImage imageNamed: @"NSHighlightedRadioButton"]];
770        [self setImagePosition: NSImageLeft];
771        [self setAlignment: NSLeftTextAlignment];
772        [self setBordered: NO];
773        [self setBezeled: NO];
774        [self setImageDimsWhenDisabled: NO];
775        break;
776      default:
777        NSLog(@"Using unsupported button type %d", buttonType);
778        break;
779    }
780}
781
782- (NSInteger) showsStateBy
783{
784  return _showAltStateMask;
785}
786
787- (void) setIntValue: (int)anInt
788{
789  [self setState: (anInt != 0)];
790}
791
792- (void) setFloatValue: (float)aFloat
793{
794  [self setState: (aFloat != 0)];
795}
796
797- (void) setDoubleValue: (double)aDouble
798{
799  [self setState: (aDouble != 0)];
800}
801
802- (int) intValue
803{
804  return _cell.state;
805}
806
807- (float) floatValue
808{
809  return _cell.state;
810}
811
812- (double) doubleValue
813{
814  return _cell.state;
815}
816
817- (void) setObjectValue: (id)object
818{
819  if (object == nil)
820    {
821      [self setState: NSOffState];
822    }
823  else if ([object respondsToSelector: @selector(intValue)])
824    {
825      [self setState: [object intValue]];
826    }
827  else
828    {
829      [self setState: NSOnState];
830    }
831}
832
833- (id) objectValue
834{
835  if (_cell.state == NSOffState)
836    {
837      return [NSNumber numberWithBool: NO];
838    }
839  else if (_cell.state == NSOnState)
840    {
841      return [NSNumber numberWithBool: YES];
842    }
843  else // NSMixedState
844    {
845      return [NSNumber numberWithInt: -1];
846   }
847}
848
849- (void) setStringValue: (NSString *)aString
850{
851  [self setState: ([aString length] != 0)];
852}
853
854- (NSString *) stringValue
855{
856  return _cell.state ? @"1" : @"";
857}
858
859- (void) setAttributedStringValue: (NSAttributedString *)attrString
860{
861  [self setState: ([attrString length] != 0)];
862}
863
864- (NSAttributedString *) attributedStringValue
865{
866  return AUTORELEASE([[NSAttributedString alloc]
867                         initWithString: [self stringValue]]);
868}
869
870/*
871 * Displaying
872 */
873- (NSColor*) textColor
874{
875  if (_cell.is_disabled == YES)
876    return [NSColor disabledControlTextColor];
877  if ((_cell.state && (_showAltStateMask & NSChangeGrayCellMask))
878      || (_cell.is_highlighted && (_highlightsByMask & NSChangeGrayCellMask)))
879    return [NSColor selectedControlTextColor];
880  return [NSColor controlTextColor];
881}
882
883- (NSColor *) backgroundColor
884{
885  return _backgroundColor;
886}
887
888- (void) setBackgroundColor: (NSColor *)color
889{
890  ASSIGN(_backgroundColor, color);
891
892  if (_control_view)
893    {
894      if ([_control_view isKindOfClass: [NSControl class]])
895        {
896          [(NSControl*)_control_view updateCell: self];
897        }
898    }
899}
900
901- (GSThemeControlState) themeControlState
902{
903  unsigned mask;
904  GSThemeControlState buttonState = GSThemeNormalState;
905
906  // set the mask
907  if (_cell.is_highlighted)
908    {
909      mask = _highlightsByMask;
910      if (_cell.state)
911        {
912          mask &= ~_showAltStateMask;
913        }
914    }
915  else if (_cell.state)
916    mask = _showAltStateMask;
917  else
918    mask = NSNoCellMask;
919
920  /* Determine the background color.
921     We draw when there is a border or when highlightsByMask
922     is NSChangeBackgroundCellMask or NSChangeGrayCellMask,
923     as required by our nextstep-like look and feel.  */
924  if (mask & (NSChangeGrayCellMask | NSChangeBackgroundCellMask))
925    {
926      buttonState = GSThemeHighlightedState;
927    }
928
929  /* Pushed in buttons contents are displaced to the bottom right 1px.  */
930  if (mask & NSPushInCellMask)
931    {
932      buttonState = GSThemeSelectedState;
933    }
934
935  if (_cell.is_disabled && buttonState != GSThemeHighlightedState)
936    {
937      buttonState = GSThemeDisabledState;
938    }
939
940  /* If we are first responder, change to the corresponding
941     first responder state. Note that GSThemeDisabledState
942     doesn't have a first responder variant, currently. */
943  if (_cell.shows_first_responder
944      && [[[self controlView] window] firstResponder] == [self controlView]
945      && [self controlView] != nil)
946    {
947      if (buttonState == GSThemeSelectedState)
948	buttonState = GSThemeSelectedFirstResponderState;
949      else if (buttonState == GSThemeHighlightedState)
950	buttonState = GSThemeHighlightedFirstResponderState;
951      else if (buttonState == GSThemeNormalState)
952	buttonState = GSThemeFirstResponderState;
953    }
954
955  return buttonState;
956}
957
958- (void) drawBezelWithFrame: (NSRect)cellFrame inView: (NSView *)controlView
959{
960  GSThemeControlState buttonState = [self themeControlState];
961
962  [[GSTheme theme] drawButton: cellFrame
963                   in: self
964                   view: controlView
965                   style: _bezel_style
966                   state: buttonState];
967}
968
969- (void) drawImage: (NSImage*)imageToDisplay
970         withFrame: (NSRect)cellFrame
971            inView: (NSView*)controlView
972{
973  // Draw image
974  if (imageToDisplay != nil)
975    {
976      NSPoint offset;
977      NSRect rect;
978      CGFloat fraction;
979      NSSize size = [self _scaleImageWithSize: [imageToDisplay size]
980				  toFitInSize: cellFrame.size
981				  scalingType: _imageScaling];
982
983      /* Calculate an offset from the cellFrame origin */
984      offset = NSMakePoint((NSWidth(cellFrame) - size.width) / 2.0,
985                           (NSHeight(cellFrame) - size.height) / 2.0);
986
987      rect = NSMakeRect(cellFrame.origin.x + offset.x,
988   		        cellFrame.origin.y + offset.y,
989			size.width,
990			size.height);
991
992      /* Pixel-align */
993      if (nil != controlView)
994        {
995          rect = [controlView centerScanRect: rect];
996        }
997
998      /* Draw the image */
999
1000      fraction = (![self isEnabled] &&
1001		  [self imageDimsWhenDisabled]) ? 0.5 : 1.0;
1002
1003      [imageToDisplay drawInRect: rect
1004			fromRect: NSZeroRect
1005		       operation: NSCompositeSourceOver
1006			fraction: fraction
1007		  respectFlipped: YES
1008			   hints: nil];
1009    }
1010}
1011
1012- (NSRect) drawTitle: (NSAttributedString*)titleToDisplay
1013	   withFrame: (NSRect)cellFrame
1014	      inView: (NSView*)controlView
1015{
1016  [self _drawAttributedText: titleToDisplay
1017		    inFrame: cellFrame];
1018
1019  return [titleToDisplay
1020	   boundingRectWithSize: cellFrame.size
1021			options: NSStringDrawingUsesLineFragmentOrigin];
1022}
1023
1024// Private helper method overridden in subclasses
1025- (void) _drawBorderAndBackgroundWithFrame: (NSRect)cellFrame
1026                                    inView: (NSView*)controlView
1027{
1028  /* The background color is used for borderless cells (the MacOS-X
1029   * documentation of the NSButtonCell -backgroundColor method says
1030   * it's only used for borderless cells).
1031   */
1032  if (!_cell.is_bordered)
1033    {
1034      NSColor	*c = [self backgroundColor];
1035
1036      if (c != nil)
1037	{
1038	  [c set];
1039	  NSRectFill(cellFrame);
1040	}
1041    }
1042
1043  // Draw gradient
1044  if (!_cell.is_highlighted)
1045    {
1046      [[GSTheme theme] drawGradientBorder: _gradient_type
1047                       inRect: cellFrame
1048                       withClip: NSZeroRect];
1049    }
1050
1051  // The inside check could also be done via a track rect, but then this would
1052  // only work with specially prepared controls. Therefore we dont use
1053  // _mouse_inside here.
1054  if ((_cell.is_bordered)
1055      && (!_shows_border_only_while_mouse_inside
1056          || [controlView mouse: [[controlView window] mouseLocationOutsideOfEventStream]
1057                          inRect: cellFrame]))
1058    {
1059      [self drawBezelWithFrame: cellFrame inView: controlView];
1060    }
1061}
1062
1063- (void) drawInteriorWithFrame: (NSRect)cellFrame inView: (NSView*)controlView
1064{
1065  unsigned mask;
1066  NSImage *imageToDisplay;
1067  NSRect imageRect;
1068  NSAttributedString *titleToDisplay;
1069  NSRect titleRect;
1070  NSSize imageSize = {0, 0};
1071  NSSize titleSize = {0, 0};
1072  BOOL flippedView = [controlView isFlipped];
1073  NSCellImagePosition ipos = _cell.image_position;
1074
1075  // transparent buttons never draw
1076  if (_buttoncell_is_transparent)
1077    return;
1078
1079  _control_view = controlView;
1080
1081  cellFrame = [self drawingRectForBounds: cellFrame];
1082
1083  if (_cell.is_highlighted)
1084    {
1085      mask = _highlightsByMask;
1086
1087      if (_cell.state)
1088        mask &= ~_showAltStateMask;
1089    }
1090  else if (_cell.state)
1091    mask = _showAltStateMask;
1092  else
1093    mask = NSNoCellMask;
1094
1095  /*
1096   * Determine the image and the title that will be
1097   * displayed. If the NSContentsCellMask is set the
1098   * image and title are swapped only if state is 1 or
1099   * if highlighting is set (when a button is pushed it's
1100   * content is changed to the face of reversed state).
1101   */
1102  if (mask & NSContentsCellMask)
1103    {
1104      imageToDisplay = _altImage;
1105      if (!imageToDisplay)
1106        {
1107          imageToDisplay = _cell_image;
1108        }
1109      titleToDisplay = [self attributedAlternateTitle];
1110      if (titleToDisplay == nil || [titleToDisplay length] == 0)
1111        {
1112          titleToDisplay = [self attributedTitle];
1113        }
1114    }
1115  else
1116    {
1117      imageToDisplay = _cell_image;
1118      titleToDisplay = [self attributedTitle];
1119    }
1120
1121  if (imageToDisplay && ipos != NSNoImage)
1122    {
1123      imageSize = [imageToDisplay size];
1124    }
1125  else
1126    {
1127      // When there is no image to display, ignore it in the calculations
1128      imageToDisplay = nil;
1129      ipos = NSNoImage;
1130    }
1131
1132  if (titleToDisplay && ipos != NSImageOnly)
1133    {
1134      titleSize = [titleToDisplay size];
1135    }
1136  else
1137    {
1138      // When there is no text to display, ignore it in the calculations
1139      titleToDisplay = nil;
1140      ipos = NSImageOnly;
1141    }
1142
1143  if (flippedView == YES)
1144    {
1145      if (ipos == NSImageAbove)
1146        {
1147          ipos = NSImageBelow;
1148        }
1149      else if (ipos == NSImageBelow)
1150        {
1151          ipos = NSImageAbove;
1152        }
1153    }
1154
1155  /*
1156  The size calculations here should be changed very carefully, and _must_ be
1157  kept in sync with -cellSize. Changing the calculations to require more
1158  space isn't OK; this breaks interfaces designed using the old sizes by
1159  clipping away parts of the title.
1160
1161  The current size calculations ensure that for bordered or bezeled cells,
1162  there's always at least a three point margin between the size returned by
1163  -cellSize and the minimum size required not to clip text. (In other words,
1164  the text can become three points wider (due to eg. font mismatches) before
1165  you lose the last character.)
1166  */
1167  switch (ipos)
1168    {
1169      default:
1170      case NSNoImage:
1171        imageToDisplay = nil;
1172        titleRect = cellFrame;
1173         imageRect = NSZeroRect;
1174        if (titleSize.width + 6 <= titleRect.size.width)
1175          {
1176            titleRect.origin.x += 3;
1177            titleRect.size.width -= 6;
1178          }
1179        break;
1180
1181      case NSImageOnly:
1182        titleToDisplay = nil;
1183        imageRect = cellFrame;
1184        titleRect = NSZeroRect;
1185        break;
1186
1187      case NSImageLeft:
1188        imageRect.origin = cellFrame.origin;
1189        imageRect.size.width = imageSize.width;
1190        imageRect.size.height = cellFrame.size.height;
1191        if (_cell.is_bordered || _cell.is_bezeled)
1192          {
1193            imageRect.origin.x += 3;
1194          }
1195        titleRect = imageRect;
1196        titleRect.origin.x += imageSize.width + GSCellTextImageXDist;
1197        titleRect.size.width = NSMaxX(cellFrame) - titleRect.origin.x;
1198        if (titleSize.width + 3 <= titleRect.size.width)
1199          {
1200            titleRect.size.width -= 3;
1201          }
1202        break;
1203
1204      case NSImageRight:
1205        imageRect.origin.x = NSMaxX(cellFrame) - imageSize.width;
1206        imageRect.origin.y = cellFrame.origin.y;
1207        imageRect.size.width = imageSize.width;
1208        imageRect.size.height = cellFrame.size.height;
1209        if (_cell.is_bordered || _cell.is_bezeled)
1210          {
1211            imageRect.origin.x -= 3;
1212          }
1213        titleRect.origin = cellFrame.origin;
1214        titleRect.size.width = imageRect.origin.x - titleRect.origin.x
1215                               - GSCellTextImageXDist;
1216        titleRect.size.height = cellFrame.size.height;
1217        if (titleSize.width + 3 <= titleRect.size.width)
1218          {
1219            titleRect.origin.x += 3;
1220            titleRect.size.width -= 3;
1221          }
1222        break;
1223
1224      case NSImageAbove:
1225        /*
1226         * In this case, imageRect is all the space we can allocate
1227         * above the text.
1228         * The drawing code below will then center the image in imageRect.
1229         */
1230        titleRect.origin = cellFrame.origin;
1231        titleRect.size.width = cellFrame.size.width;
1232        titleRect.size.height = titleSize.height;
1233        if (_cell.is_bordered || _cell.is_bezeled)
1234          {
1235            titleRect.origin.y += 3;
1236          }
1237
1238        imageRect.origin.x = cellFrame.origin.x;
1239        imageRect.origin.y = NSMaxY(titleRect) + GSCellTextImageYDist;
1240        imageRect.size.width = cellFrame.size.width;
1241        imageRect.size.height = NSMaxY(cellFrame) - imageRect.origin.y;
1242
1243        if (_cell.is_bordered || _cell.is_bezeled)
1244          {
1245            imageRect.size.height -= 3;
1246          }
1247        if (titleSize.width + 6 <= titleRect.size.width)
1248          {
1249            titleRect.origin.x += 3;
1250            titleRect.size.width -= 6;
1251          }
1252        break;
1253
1254      case NSImageBelow:
1255        /*
1256         * In this case, imageRect is all the space we can allocate
1257         * below the text.
1258         * The drawing code below will then center the image in imageRect.
1259         */
1260        titleRect.origin.x = cellFrame.origin.x;
1261        titleRect.origin.y = NSMaxY(cellFrame) - titleSize.height;
1262        titleRect.size.width = cellFrame.size.width;
1263        titleRect.size.height = titleSize.height;
1264        if (_cell.is_bordered || _cell.is_bezeled)
1265          {
1266            titleRect.origin.y -= 3;
1267          }
1268
1269        imageRect.origin.x = cellFrame.origin.x;
1270        imageRect.origin.y = cellFrame.origin.y;
1271        imageRect.size.width = cellFrame.size.width;
1272        imageRect.size.height
1273          = titleRect.origin.y - GSCellTextImageYDist - imageRect.origin.y;
1274
1275        if (_cell.is_bordered || _cell.is_bezeled)
1276          {
1277            imageRect.origin.y += 3;
1278            imageRect.size.height -= 3;
1279          }
1280        if (titleSize.width + 6 <= titleRect.size.width)
1281          {
1282            titleRect.origin.x += 3;
1283            titleRect.size.width -= 6;
1284          }
1285        break;
1286
1287      case NSImageOverlaps:
1288        imageRect = cellFrame;
1289        titleRect = cellFrame;
1290        if (titleSize.width + 6 <= titleRect.size.width)
1291          {
1292            titleRect.origin.x += 3;
1293            titleRect.size.width -= 6;
1294          }
1295        break;
1296    }
1297
1298  // Draw image
1299  if (imageToDisplay != nil)
1300    {
1301      [self drawImage: imageToDisplay
1302            withFrame: imageRect
1303               inView: controlView];
1304    }
1305
1306  // Draw title
1307  if (titleToDisplay != nil)
1308    {
1309      [self drawTitle: titleToDisplay withFrame: titleRect inView: controlView];
1310    }
1311}
1312
1313- (NSSize) cellSize
1314{
1315  NSSize s;
1316  GSThemeMargins border;
1317  unsigned mask;
1318  NSImage *imageToDisplay;
1319  NSAttributedString *titleToDisplay;
1320  NSSize imageSize = NSZeroSize;
1321  NSSize titleSize = NSZeroSize;
1322
1323  /* The size calculations here must be kept in sync with
1324  -drawInteriorWithFrame. */
1325
1326  if (_cell.is_highlighted)
1327    {
1328      mask = _highlightsByMask;
1329
1330      if (_cell.state)
1331        mask &= ~_showAltStateMask;
1332    }
1333  else if (_cell.state)
1334    mask = _showAltStateMask;
1335  else
1336    mask = NSNoCellMask;
1337
1338  if (mask & NSContentsCellMask)
1339    {
1340      imageToDisplay = _altImage;
1341      if (!imageToDisplay)
1342        {
1343          imageToDisplay = _cell_image;
1344        }
1345      titleToDisplay = [self attributedAlternateTitle];
1346      if (titleToDisplay == nil || [titleToDisplay length] == 0)
1347        {
1348          titleToDisplay = [self attributedTitle];
1349        }
1350    }
1351  else
1352    {
1353      imageToDisplay = _cell_image;
1354      titleToDisplay = [self attributedTitle];
1355    }
1356
1357  if (imageToDisplay)
1358    {
1359      imageSize = [imageToDisplay size];
1360    }
1361
1362  if (titleToDisplay != nil)
1363    {
1364      titleSize = [titleToDisplay size];
1365    }
1366
1367  switch (_cell.image_position)
1368    {
1369      default:
1370      case NSNoImage:
1371        s = titleSize;
1372        break;
1373
1374      case NSImageOnly:
1375        s = imageSize;
1376        break;
1377
1378      case NSImageLeft:
1379      case NSImageRight:
1380        s.width = imageSize.width + titleSize.width + GSCellTextImageXDist;
1381        s.height = MAX(imageSize.height, titleSize.height);
1382        break;
1383
1384      case NSImageBelow:
1385      case NSImageAbove:
1386        s.width = MAX(imageSize.width, titleSize.width);
1387        s.height = imageSize.height + titleSize.height + GSCellTextImageYDist;
1388        break;
1389
1390      case NSImageOverlaps:
1391        s.width = MAX(imageSize.width, titleSize.width);
1392        s.height = MAX(imageSize.height, titleSize.height);
1393        break;
1394    }
1395
1396  // Get border size
1397  if (_cell.is_bordered)
1398    {
1399      GSThemeControlState        buttonState = GSThemeNormalState;
1400
1401      /* Determine the background color.
1402         We draw when there is a border or when highlightsByMask
1403         is NSChangeBackgroundCellMask or NSChangeGrayCellMask,
1404         as required by our nextstep-like look and feel.  */
1405      if (mask & (NSChangeGrayCellMask | NSChangeBackgroundCellMask))
1406        {
1407          buttonState = GSThemeHighlightedState;
1408        }
1409
1410      /* Pushed in buttons contents are displaced to the bottom right 1px.  */
1411      if (mask & NSPushInCellMask)
1412        {
1413          buttonState = GSThemeSelectedState;
1414        }
1415
1416      border = [[GSTheme theme] buttonMarginsForCell: self
1417					       style: _bezel_style
1418					       state: buttonState];
1419    }
1420  else
1421    {
1422      border.left = 0;
1423      border.top = 0;
1424      border.right = 0;
1425      border.bottom = 0;
1426    }
1427
1428  /* Add an additional 6 pixels horizontally so that the text is not
1429   * too near the boundaries of the button.  Without them, autosized
1430   * buttons look too tiny and crammed.  This might be made
1431   * configurable by the theme, but most likely only because themes
1432   * might want to have even more space here (to make buttons more
1433   * clear and readable) rather than less!  Eg. Apple by default has
1434   * huge amounts of empty space between the text and the borders of
1435   * their push buttons.
1436   */
1437  if ((_cell.is_bordered && (_cell.image_position != NSImageOnly))
1438      || _cell.is_bezeled)
1439    {
1440      border.left += 6;
1441      border.right += 6;
1442    }
1443
1444  // Add border size
1445  s.width += border.left + border.right;
1446  s.height += border.top + border.bottom;
1447
1448  return s;
1449}
1450
1451- (NSRect) drawingRectForBounds: (NSRect)theRect
1452{
1453  if (_cell.is_bordered)
1454    {
1455      GSThemeMargins border;
1456      unsigned mask;
1457      GSThemeControlState buttonState = GSThemeNormalState;
1458      NSRect interiorFrame;
1459
1460      if (_cell.is_highlighted)
1461        {
1462          mask = _highlightsByMask;
1463
1464          if (_cell.state)
1465            mask &= ~_showAltStateMask;
1466        }
1467      else if (_cell.state)
1468        mask = _showAltStateMask;
1469      else
1470        mask = NSNoCellMask;
1471
1472      /* Determine the background color.
1473         We draw when there is a border or when highlightsByMask
1474         is NSChangeBackgroundCellMask or NSChangeGrayCellMask,
1475         as required by our nextstep-like look and feel.  */
1476      if (mask & (NSChangeGrayCellMask | NSChangeBackgroundCellMask))
1477        {
1478          buttonState = GSThemeHighlightedState;
1479        }
1480
1481      if (mask & NSPushInCellMask)
1482        {
1483          buttonState = GSThemeSelectedState;
1484        }
1485
1486      border = [[GSTheme theme] buttonMarginsForCell: self
1487					       style: _bezel_style
1488					       state: buttonState];
1489
1490      interiorFrame = theRect;
1491      interiorFrame.origin.x += border.left;
1492      interiorFrame.size.width -= border.left + border.right;
1493      interiorFrame.origin.y += ([_control_view isFlipped] ?
1494				 border.top : border.bottom);
1495      interiorFrame.size.height -= border.bottom + border.top;
1496
1497      /* Pushed in buttons contents are displaced to the bottom right 1px.  */
1498      if (mask & NSPushInCellMask)
1499        {
1500          interiorFrame = NSOffsetRect(interiorFrame, 1.0,
1501	    [_control_view isFlipped] ? 1.0 : -1.0);
1502        }
1503      return interiorFrame;
1504    }
1505  else
1506    {
1507      return theRect;
1508    }
1509}
1510
1511- (void) setSound: (NSSound *)aSound
1512{
1513  ASSIGN(_sound, aSound);
1514}
1515
1516- (NSSound *) sound
1517{
1518  return _sound;
1519}
1520
1521- (void) mouseEntered: (NSEvent *)event
1522{
1523  _mouse_inside = YES;
1524  [(NSView *)[event userData] setNeedsDisplay: YES];
1525}
1526
1527- (void) mouseExited: (NSEvent *)event
1528{
1529  _mouse_inside = NO;
1530  [(NSView *)[event userData] setNeedsDisplay: YES];
1531}
1532
1533/**Simulates a single mouse click on the button cell. This method overrides the
1534  cell method performClickWithFrame:inView: to add the possibility to
1535  play a sound associated with the click.
1536 */
1537- (void) performClickWithFrame: (NSRect)cellFrame inView: (NSView *)controlView
1538{
1539  if (_sound != nil)
1540    {
1541      [_sound play];
1542    }
1543
1544  [super performClickWithFrame: cellFrame inView: controlView];
1545}
1546
1547/*
1548 * Comparing to Another NSButtonCell
1549 */
1550- (NSComparisonResult) compare: (id)otherCell
1551{
1552  if ([otherCell isKindOfClass: [NSButtonCell class]] == NO)
1553    {
1554      [NSException raise: NSBadComparisonException
1555                   format: @"NSButtonCell comparison with non-NSButtonCell"];
1556    }
1557  return [super compare: otherCell];
1558}
1559
1560/*
1561 * NSCopying protocol
1562 */
1563- (id) copyWithZone: (NSZone*)zone
1564{
1565  NSButtonCell *c = [super copyWithZone: zone];
1566
1567  c->_altContents = [_altContents copyWithZone: zone];
1568  _altImage = TEST_RETAIN(_altImage);
1569  _keyEquivalent = TEST_RETAIN(_keyEquivalent);
1570  _keyEquivalentFont = TEST_RETAIN(_keyEquivalentFont);
1571  _sound = TEST_RETAIN(_sound);
1572  _backgroundColor = TEST_RETAIN(_backgroundColor);
1573
1574  return c;
1575}
1576
1577/*
1578 * NSCoding protocol
1579 */
1580- (void) encodeWithCoder: (NSCoder*)aCoder
1581{
1582  BOOL tmp;
1583
1584  [super encodeWithCoder: aCoder];
1585  if ([aCoder allowsKeyedCoding])
1586    {
1587      GSButtonCellFlags buttonCellFlags;
1588      unsigned int bFlags = 0;
1589      unsigned int bFlags2 = 0;
1590      NSImage *image = [self image];
1591      NSButtonImageSource *bi = nil;
1592
1593      if ([[self keyEquivalent] length] > 0)
1594        {
1595          [aCoder encodeObject: [self keyEquivalent] forKey: @"NSKeyEquivalent"];
1596        }
1597      if ([self image] != nil)
1598        {
1599          [aCoder encodeObject: [self image] forKey: @"NSNormalImage"];
1600        }
1601      if ([[self alternateTitle] length] > 0)
1602        {
1603          [aCoder encodeObject: [self alternateTitle] forKey: @"NSAlternateContents"];
1604        }
1605
1606      buttonCellFlags.useButtonImageSource = (([NSImage imageNamed: @"NSSwitch"] == image) ||
1607                                              ([NSImage imageNamed: @"NSRadioButton"] == image));
1608      buttonCellFlags.isTransparent = [self isTransparent];
1609      buttonCellFlags.isBordered = [self isBordered];
1610      buttonCellFlags.imageDoesOverlap =
1611          (_cell.image_position == NSImageOverlaps)
1612          || (_cell.image_position == NSImageOnly);
1613      buttonCellFlags.isHorizontal = (_cell.image_position == NSImageLeft)
1614          || (_cell.image_position == NSImageRight);
1615      buttonCellFlags.isBottomOrLeft = (_cell.image_position == NSImageLeft)
1616          || (_cell.image_position == NSImageBelow);
1617      buttonCellFlags.isImageAndText = (image != nil)
1618          && (_cell.image_position != NSImageOnly);
1619      buttonCellFlags.hasKeyEquiv = ([self keyEquivalent] != nil);
1620
1621      // cell attributes...
1622      buttonCellFlags.isPushin = [self cellAttribute: NSPushInCell];
1623      buttonCellFlags.highlightByBackground = [self cellAttribute: NSCellLightsByBackground];
1624      buttonCellFlags.highlightByContents = [self cellAttribute: NSCellLightsByContents];
1625      buttonCellFlags.highlightByGray = [self cellAttribute: NSCellLightsByGray];
1626      buttonCellFlags.changeBackground = [self cellAttribute: NSChangeBackgroundCell];
1627      buttonCellFlags.changeContents = [self cellAttribute: NSCellChangesContents];
1628      buttonCellFlags.changeGray = [self cellAttribute: NSChangeGrayCell];
1629
1630      // set these to zero...
1631      buttonCellFlags.inset = 0;
1632      buttonCellFlags.doesNotDimImage = 0;
1633      buttonCellFlags.gradient = 0;
1634      buttonCellFlags.unused2 = 0;
1635      buttonCellFlags.lastState = 0;
1636      buttonCellFlags.isImageSizeDiff = 0;
1637      buttonCellFlags.drawing = 0;
1638
1639      memcpy((void *)&bFlags, (void *)&buttonCellFlags,sizeof(unsigned int));
1640      [aCoder encodeInt: bFlags forKey: @"NSButtonFlags"];
1641
1642      // style and border.
1643      bFlags2 |= [self showsBorderOnlyWhileMouseInside] ? 0x8 : 0;
1644      bFlags2 |= (([self bezelStyle] & 0x7) | (([self bezelStyle] & 0x18) << 2));
1645      bFlags2 |= [self keyEquivalentModifierMask] << 8;
1646
1647      switch ([self imageScaling])
1648	{
1649	case NSImageScaleProportionallyDown:
1650	  bFlags2 |= (2 << 6);
1651	  break;
1652	case NSImageScaleAxesIndependently:
1653	  bFlags2 |= (3 << 6);
1654	  break;
1655	case NSImageScaleNone:
1656	default:
1657	  break;
1658	case NSImageScaleProportionallyUpOrDown:
1659	  bFlags2 |= (1 << 6);
1660	  break;
1661	}
1662
1663      [aCoder encodeInt: bFlags2 forKey: @"NSButtonFlags2"];
1664
1665      // alternate image encoding...
1666      if (image != nil)
1667        {
1668          if ([image isKindOfClass: [NSImage class]] && buttonCellFlags.useButtonImageSource)
1669            {
1670              if ([NSImage imageNamed: @"NSSwitch"] == image)
1671                {
1672                  bi = [[NSButtonImageSource alloc] initWithImageNamed: @"NSHighlightedSwitch"];
1673                }
1674              else if ([NSImage imageNamed: @"NSRadioButton"] == image)
1675                {
1676                  bi = [[NSButtonImageSource alloc] initWithImageNamed: @"NSHighlightedRadioButton"];
1677                }
1678            }
1679        }
1680
1681      // encode button image source, if it exists...
1682      if (bi != nil)
1683        {
1684          [aCoder encodeObject: bi forKey: @"NSAlternateImage"];
1685          RELEASE(bi);
1686        }
1687      else if (_altImage != nil)
1688        {
1689          [aCoder encodeObject: _altImage forKey: @"NSAlternateImage"];
1690        }
1691
1692      // repeat and delay
1693      [aCoder encodeInt: (int)_delayInterval forKey: @"NSPeriodicDelay"];
1694      [aCoder encodeInt: (int)_repeatInterval forKey: @"NSPeriodicInterval"];
1695    }
1696  else
1697    {
1698      [aCoder encodeObject: _keyEquivalent];
1699      [aCoder encodeObject: _keyEquivalentFont];
1700      [aCoder encodeObject: _altContents];
1701      [aCoder encodeObject: _altImage];
1702      tmp = _buttoncell_is_transparent;
1703      [aCoder encodeValueOfObjCType: @encode(BOOL)
1704              at: &tmp];
1705
1706      if([NSButtonCell version] <= 2)
1707	{
1708	  unsigned int ke = _keyEquivalentModifierMask << 16;
1709	  [aCoder encodeValueOfObjCType: @encode(unsigned int)
1710		  at: &ke];
1711	}
1712      else
1713	{
1714	  [aCoder encodeValueOfObjCType: @encode(unsigned int)
1715		  at: &_keyEquivalentModifierMask];
1716	}
1717
1718      [aCoder encodeValueOfObjCType: @encode(unsigned int)
1719              at: &_highlightsByMask];
1720      [aCoder encodeValueOfObjCType: @encode(unsigned int)
1721              at: &_showAltStateMask];
1722
1723      if([NSButtonCell version] >= 2)
1724	{
1725	  [aCoder encodeObject: _sound];
1726	  [aCoder encodeObject: _backgroundColor];
1727	  [aCoder encodeValueOfObjCType: @encode(float)
1728		  at: &_delayInterval];
1729	  [aCoder encodeValueOfObjCType: @encode(float)
1730		  at: &_repeatInterval];
1731	  [aCoder encodeValueOfObjCType: @encode(unsigned int)
1732		  at: &_bezel_style];
1733	  [aCoder encodeValueOfObjCType: @encode(unsigned int)
1734		  at: &_gradient_type];
1735	  tmp = _image_dims_when_disabled;
1736	  [aCoder encodeValueOfObjCType: @encode(BOOL)
1737		  at: &tmp];
1738	  tmp = _shows_border_only_while_mouse_inside;
1739	  [aCoder encodeValueOfObjCType: @encode(BOOL)
1740		  at: &tmp];
1741	}
1742    }
1743}
1744
1745- (id) initWithCoder: (NSCoder*)aDecoder
1746{
1747  self = [super initWithCoder: aDecoder];
1748  if (!self)
1749    return nil;
1750
1751  if ([aDecoder allowsKeyedCoding])
1752    {
1753      int delay = 0;
1754      int interval = 0;
1755      // NSControl *control = [aDecoder decodeObjectForKey: @"NSControlView"];
1756
1757      if ([aDecoder containsValueForKey: @"NSKeyEquivalent"])
1758        {
1759          [self setKeyEquivalent: [aDecoder decodeObjectForKey: @"NSKeyEquivalent"]];
1760        }
1761      if ([aDecoder containsValueForKey: @"NSNormalImage"])
1762        {
1763          [self setImage: [aDecoder decodeObjectForKey: @"NSNormalImage"]];
1764        }
1765      if ([aDecoder containsValueForKey: @"NSAlternateContents"])
1766        {
1767          [self setAlternateTitle: [aDecoder decodeObjectForKey: @"NSAlternateContents"]];
1768        }
1769      if ([aDecoder containsValueForKey: @"NSButtonFlags"])
1770        {
1771          unsigned int bFlags = [aDecoder decodeIntForKey: @"NSButtonFlags"];
1772          GSButtonCellFlags buttonCellFlags;
1773          memcpy((void *)&buttonCellFlags,(void *)&bFlags,sizeof(struct _GSButtonCellFlags));
1774
1775          [self setTransparent: buttonCellFlags.isTransparent];
1776          [self setBordered: buttonCellFlags.isBordered];
1777
1778          [self setCellAttribute: NSPushInCell
1779                to: buttonCellFlags.isPushin];
1780          [self setCellAttribute: NSCellLightsByBackground
1781                to: buttonCellFlags.highlightByBackground];
1782          [self setCellAttribute: NSCellLightsByContents
1783                to: buttonCellFlags.highlightByContents];
1784          [self setCellAttribute: NSCellLightsByGray
1785                to: buttonCellFlags.highlightByGray];
1786          [self setCellAttribute: NSChangeBackgroundCell
1787                to: buttonCellFlags.changeBackground];
1788          [self setCellAttribute: NSCellChangesContents
1789                to: buttonCellFlags.changeContents];
1790          [self setCellAttribute: NSChangeGrayCell
1791                to: buttonCellFlags.changeGray];
1792
1793          if (buttonCellFlags.imageDoesOverlap)
1794            if (buttonCellFlags.isImageAndText)
1795              [self setImagePosition: NSImageOverlaps];
1796            else
1797              [self setImagePosition: NSImageOnly];
1798          else if (buttonCellFlags.isImageAndText)
1799            if (buttonCellFlags.isHorizontal)
1800              if (buttonCellFlags.isBottomOrLeft)
1801                [self setImagePosition: NSImageLeft];
1802              else
1803                [self setImagePosition: NSImageRight];
1804            else
1805              if (buttonCellFlags.isBottomOrLeft)
1806                [self setImagePosition: NSImageBelow];
1807              else
1808                [self setImagePosition: NSImageAbove];
1809          else
1810            [self setImagePosition: NSNoImage];
1811        }
1812      if ([aDecoder containsValueForKey: @"NSButtonFlags2"])
1813        {
1814	  NSUInteger imageScale;
1815          int bFlags2;
1816
1817          bFlags2 = [aDecoder decodeIntForKey: @"NSButtonFlags2"];
1818          [self setShowsBorderOnlyWhileMouseInside: (bFlags2 & 0x8)];
1819          [self setBezelStyle: (bFlags2 & 0x7) | ((bFlags2 & 0x20) >> 2)];
1820          [self setKeyEquivalentModifierMask: ((bFlags2 >> 8) &
1821                                               NSDeviceIndependentModifierFlagsMask)];
1822
1823	  switch ((bFlags2 >> 6) & 3)
1824	    {
1825	    case 2:
1826	      imageScale = NSImageScaleProportionallyDown;
1827	      break;
1828	    case 3:
1829	      imageScale = NSImageScaleAxesIndependently;
1830	      break;
1831	    case 0:
1832	    default:
1833	      imageScale = NSImageScaleNone;
1834	      break;
1835	    case 1:
1836	      imageScale = NSImageScaleProportionallyUpOrDown;
1837	      break;
1838	    }
1839	  [self setImageScaling: imageScale];
1840        }
1841      if ([aDecoder containsValueForKey: @"NSAlternateImage"])
1842        {
1843          id image;
1844
1845          //
1846          // NOTE: Okay... this is a humongous kludge.   It seems as though
1847          // Cocoa is doing something very odd here.  It doesn't seem to
1848          // encode system images for buttons normally, if it is using
1849          // images at all. Until I figure out what, this will stay.
1850          // Danger, Will Robinson! :)
1851          //
1852          image = [aDecoder decodeObjectForKey: @"NSAlternateImage"];
1853          if ([image isKindOfClass: [NSImage class]])
1854            {
1855              if ([NSImage imageNamed: @"NSSwitch"] == image)
1856                {
1857                  image = [NSImage imageNamed: @"NSHighlightedSwitch"];
1858                  if ([self image] == nil)
1859                    {
1860                      [self setImage: [NSImage imageNamed: @"NSSwitch"]];
1861                    }
1862                }
1863              else if ([NSImage imageNamed: @"NSRadioButton"] == image)
1864                {
1865                  image = [NSImage imageNamed: @"NSHighlightedRadioButton"];
1866                  if ([self image] == nil)
1867                    {
1868                      [self setImage: [NSImage imageNamed: @"NSRadioButton"]];
1869                    }
1870                }
1871
1872              [self setAlternateImage: image];
1873            }
1874        }
1875      if ([aDecoder containsValueForKey: @"NSPeriodicDelay"])
1876        {
1877          delay = [aDecoder decodeIntForKey: @"NSPeriodicDelay"];
1878        }
1879      if ([aDecoder containsValueForKey: @"NSPeriodicInterval"])
1880        {
1881          interval = [aDecoder decodeIntForKey: @"NSPeriodicInterval"];
1882        }
1883      [self setPeriodicDelay: delay interval: interval];
1884    }
1885  else
1886    {
1887      BOOL tmp;
1888      int version = [aDecoder versionForClassName: @"NSButtonCell"];
1889      NSString *key = nil;
1890
1891      [aDecoder decodeValueOfObjCType: @encode(id) at: &key];
1892      [self setKeyEquivalent: key]; // Set the key equivalent...
1893
1894      [aDecoder decodeValueOfObjCType: @encode(id) at: &_keyEquivalentFont];
1895      [aDecoder decodeValueOfObjCType: @encode(id) at: &_altContents];
1896      [aDecoder decodeValueOfObjCType: @encode(id) at: &_altImage];
1897      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &tmp];
1898      _buttoncell_is_transparent = tmp;
1899      [aDecoder decodeValueOfObjCType: @encode(unsigned int)
1900                                   at: &_keyEquivalentModifierMask];
1901      if (version <= 2)
1902        {
1903          _keyEquivalentModifierMask = _keyEquivalentModifierMask << 16;
1904        }
1905      [aDecoder decodeValueOfObjCType: @encode(unsigned int)
1906                                   at: &_highlightsByMask];
1907      [aDecoder decodeValueOfObjCType: @encode(unsigned int)
1908                                   at: &_showAltStateMask];
1909
1910      if (version >= 2)
1911        {
1912          [aDecoder decodeValueOfObjCType: @encode(id) at: &_sound];
1913          [aDecoder decodeValueOfObjCType: @encode(id) at: &_backgroundColor];
1914          [aDecoder decodeValueOfObjCType: @encode(float) at: &_delayInterval];
1915          [aDecoder decodeValueOfObjCType: @encode(float) at: &_repeatInterval];
1916          [aDecoder decodeValueOfObjCType: @encode(unsigned int)
1917                                       at: &_bezel_style];
1918          [aDecoder decodeValueOfObjCType: @encode(unsigned int)
1919                                       at: &_gradient_type];
1920          [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &tmp];
1921          _image_dims_when_disabled = tmp;
1922          [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &tmp];
1923          _shows_border_only_while_mouse_inside = tmp;
1924        }
1925      // Not encoded in non-keyed archive
1926      _imageScaling = NSImageScaleNone;
1927    }
1928
1929  // Hack to correct a Gorm problem, there "\n" is used instead of "\r".
1930  if ([_keyEquivalent isEqualToString: @"\n" ])
1931    {
1932      [self setKeyEquivalent: @"\r"];
1933    }
1934
1935  return self;
1936}
1937
1938@end
1939