1/** <title>NSCell</title>
2
3   <abstract>The abstract cell class</abstract>
4
5   Copyright (C) 1996-2012,2019 Free Software Foundation, Inc.
6
7   Author:  Scott Christley <scottc@net-community.com>
8   Date: 1996
9   Modifications:  Felipe A. Rodriguez <far@ix.netcom.com>
10   Date: August 1998
11   Rewrite:  Multiple authors
12   Date: 1999
13   Editing, formatters: Nicola Pero <nicola@brainstorm.co.uk>
14   Date: 2000
15
16   This file is part of the GNUstep GUI Library.
17
18   This library is free software; you can redistribute it and/or
19   modify it under the terms of the GNU Lesser General Public
20   License as published by the Free Software Foundation; either
21   version 2 of the License, or (at your option) any later version.
22
23   This library is distributed in the hope that it will be useful,
24   but WITHOUT ANY WARRANTY; without even the implied warranty of
25   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26   Lesser General Public License for more details.
27
28   You should have received a copy of the GNU Lesser General Public
29   License along with this library; see the file COPYING.LIB.
30   If not, see <http://www.gnu.org/licenses/> or write to the
31   Free Software Foundation, 51 Franklin Street, Fifth Floor,
32   Boston, MA 02110-1301, USA.
33*/
34
35#include <math.h>
36
37#import "config.h"
38#import <Foundation/NSCoder.h>
39#import <Foundation/NSDebug.h>
40#import <Foundation/NSException.h>
41#import <Foundation/NSFormatter.h>
42#import <Foundation/NSValue.h>
43#import <Foundation/NSNotification.h>
44#import <Foundation/NSNumberFormatter.h>
45#import <Foundation/NSRunLoop.h>
46#import <Foundation/NSString.h>
47#import <Foundation/NSGeometry.h>
48
49#import "AppKit/AppKitExceptions.h"
50#import "AppKit/NSAttributedString.h"
51#import "AppKit/NSApplication.h"
52#import "AppKit/NSControl.h"
53#import "AppKit/NSCell.h"
54#import "AppKit/NSClipView.h"
55#import "AppKit/NSColor.h"
56#import "AppKit/NSCursor.h"
57#import "AppKit/NSEvent.h"
58#import "AppKit/NSFont.h"
59#import "AppKit/NSGraphics.h"
60#import "AppKit/NSImage.h"
61#import "AppKit/NSMenu.h"
62#import "AppKit/NSParagraphStyle.h"
63#import "AppKit/NSStringDrawing.h"
64#import "AppKit/NSTextView.h"
65#import "AppKit/NSTextContainer.h"
66#import "AppKit/NSView.h"
67#import "AppKit/NSWindow.h"
68#import "AppKit/NSKeyValueBinding.h"
69#import "GSBindingHelpers.h"
70#import "GNUstepGUI/GSTheme.h"
71#import "GSGuiPrivate.h"
72
73static Class colorClass;
74static Class cellClass;
75static Class fontClass;
76static Class imageClass;
77
78static NSColor *txtCol;
79static NSColor *dtxtCol;
80
81@interface NSCell (PrivateColor)
82+ (void) _systemColorsChanged: (NSNotification*)n;
83@end
84
85
86@implementation NSCell (PrivateColor)
87+ (void) _systemColorsChanged: (NSNotification*)n
88{
89  ASSIGN (txtCol, [colorClass controlTextColor]);
90  ASSIGN (dtxtCol, [colorClass disabledControlTextColor]);
91}
92@end
93
94
95/**
96 *<p> TODO Desctiption</p>
97 */
98
99@implementation NSCell
100
101/*
102 * Class methods
103 */
104+ (void) initialize
105{
106  if (self == [NSCell class])
107    {
108      [self setVersion: 4];
109      colorClass = [NSColor class];
110      cellClass = [NSCell class];
111      fontClass = [NSFont class];
112      imageClass = [NSImage class];
113      /*
114       * Watch for changes to system colors, and simulate an initial change
115       * in order to set up our defaults.
116       */
117      [[NSNotificationCenter defaultCenter]
118        addObserver: self
119           selector: @selector(_systemColorsChanged:)
120               name: NSSystemColorsDidChangeNotification
121             object: nil];
122      [self _systemColorsChanged: nil];
123#if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST)
124      [self exposeBinding: NSTitleBinding];
125#endif
126    }
127}
128
129+ (NSMenu*) defaultMenu
130{
131  return nil;
132}
133
134+ (NSFocusRingType) defaultFocusRingType
135{
136  return NSFocusRingTypeDefault;
137}
138
139/**<p>This class method returns NO. This method should be overrided by
140   subclasses.</p>
141 */
142+ (BOOL) prefersTrackingUntilMouseUp
143{
144  return NO;
145}
146
147/*
148 * Instance methods
149 */
150- (id) init
151{
152  return [self initTextCell: @""];
153}
154
155/**<p>Initializes and returns a new NSCell with a NSImage <var>anImage</var>.
156   This method sets the image position to <ref type="type"
157   id="NSCellImagePosition">NSImageOnly</ref> and the cell's type to
158   <ref type="type" id="NSCellType">NSImageCellType</ref>.</p>
159   <p>See Also: -initTextCell: </p>
160 */
161- (id) initImageCell: (NSImage*)anImage
162{
163  _cell.type = NSImageCellType;
164  _cell_image = RETAIN (anImage);
165  _cell.image_position = NSImageOnly;
166  _font = RETAIN ([fontClass systemFontOfSize: 0]);
167
168  // Implicitly set by allocation:
169  //
170  //_font = nil;
171  //_cell.contents_is_attributed_string = NO;
172  //_cell.is_highlighted = NO;
173  //_cell.is_disabled = NO;
174  //_cell.is_editable = NO;
175  //_cell.is_rich_text = NO;
176  //_cell.imports_graphics = NO;
177  //_cell.shows_first_responder = NO;
178  //_cell.refuses_first_responder = NO;
179  //_cell.sends_action_on_end_editing = NO;
180  //_cell.is_bordered = NO;
181  //_cell.is_bezeled = NO;
182  //_cell.is_scrollable = NO;
183  //_cell.is_selectable = NO;
184  //_cell.state = 0;
185  //_cell.line_break_mode = NSLineBreakByWordWrapping;
186  _action_mask = NSLeftMouseUpMask;
187  _menu = [object_getClass(self) defaultMenu];
188  [self setFocusRingType: [object_getClass(self) defaultFocusRingType]];
189
190  return self;
191}
192/**<p>Initializes and returns a new NSCell with a NSString aString.
193   This method sets the cell's type to <ref type="type" id="NSCellType">
194   NSTextCellType</ref>.</p>
195   <p>See Also: -initImageCell: </p>
196 */
197- (id) initTextCell: (NSString*)aString
198{
199  _cell.type = NSTextCellType;
200  _contents = RETAIN (aString);
201  _font = RETAIN ([fontClass systemFontOfSize: 0]);
202
203  // Implicitly set by allocation:
204  //
205  //_cell.contents_is_attributed_string = NO;
206  //_cell_image = nil;
207  //_cell.image_position = NSNoImage;
208  //_cell.is_disabled = NO;
209  //_cell.state = 0;
210  //_cell.is_highlighted = NO;
211  //_cell.is_editable = NO;
212  //_cell.is_bordered = NO;
213  //_cell.is_bezeled = NO;
214  //_cell.is_scrollable = NO;
215  //_cell.is_selectable = NO;
216  //_cell.line_break_mode = NSLineBreakByWordWrapping;
217  _action_mask = NSLeftMouseUpMask;
218  _menu = [object_getClass(self) defaultMenu];
219  [self setFocusRingType: [object_getClass(self) defaultFocusRingType]];
220
221  return self;
222}
223
224- (void) dealloc
225{
226  // Remove all key value bindings for this object.
227  [GSKeyValueBinding unbindAllForObject: self];
228  TEST_RELEASE (_contents);
229  TEST_RELEASE (_cell_image);
230  TEST_RELEASE (_font);
231  TEST_RELEASE (_represented_object);
232  TEST_RELEASE (_object_value);
233  TEST_RELEASE (_formatter);
234  TEST_RELEASE (_menu);
235
236  [super dealloc];
237}
238
239/*
240 * Setting the NSCell's Value
241 */
242- (id) objectValue
243{
244  if (_cell.has_valid_object_value)
245    {
246      return _object_value;
247    }
248  else
249    {
250      return nil;
251    }
252}
253
254- (BOOL) hasValidObjectValue
255{
256  return _cell.has_valid_object_value;
257}
258
259/**<p>Returns the NSCell's value as a double. </p>
260 *<p>See Also: -setDoubleValue: </p>
261 */
262- (double) doubleValue
263{
264  if ((_cell.has_valid_object_value == YES) &&
265      ([_object_value respondsToSelector: @selector(doubleValue)]))
266    {
267      return [_object_value doubleValue];
268    }
269  else
270    {
271      return [[self stringValue] doubleValue];
272    }
273}
274
275/**<p>Returns the cell's value as a float. </p>
276 *<p>See Also: -setFloatValue: </p>
277 */
278- (float) floatValue
279{
280  if ((_cell.has_valid_object_value == YES) &&
281      ([_object_value respondsToSelector: @selector(floatValue)]))
282    {
283      return [_object_value floatValue];
284    }
285  else
286    {
287      return [[self stringValue] floatValue];
288    }
289}
290
291/**<p>Returns the cell's value as an int. </p>
292 *<p>See Also: -setIntValue:</p>
293 */
294- (int) intValue
295{
296  if ((_cell.has_valid_object_value == YES) &&
297      ([_object_value respondsToSelector: @selector(intValue)]))
298    {
299      return [_object_value intValue];
300    }
301  else
302    {
303      return [[self stringValue] intValue];
304    }
305}
306
307/**<p>Returns the cell's value as an NSInteger. </p>
308 *<p>See Also: -setIntegerValue:</p>
309 */
310- (NSInteger) integerValue
311{
312  if ((_cell.has_valid_object_value == YES) &&
313      ([_object_value respondsToSelector: @selector(integerValue)]))
314    {
315      return [_object_value integerValue];
316    }
317  else
318    {
319      return [[self stringValue] integerValue];
320    }
321}
322
323/**<p>Returns the cell's value as a NSString.</p>
324 *<p>See Also: -setStringValue: </p>
325 */
326- (NSString*) stringValue
327{
328  if (nil == _contents)
329    {
330      return @"";
331    }
332
333  if (_cell.contents_is_attributed_string == NO)
334    {
335      // If we have a formatter this is also the string of the _object_value
336      return (NSString *)_contents;
337    }
338  else
339    {
340      return [(NSAttributedString *)_contents string];
341    }
342}
343
344- (void) setObjectValue: (id)object
345{
346  id newContents;
347
348  ASSIGN (_object_value, object);
349  if (_formatter == nil)
350    {
351      if (object == nil || [object isKindOfClass: [NSString class]] == YES)
352        {
353          newContents = object;
354          _cell.contents_is_attributed_string = NO;
355          _cell.has_valid_object_value = YES;
356
357	  // If we are in single line mode, trim the new line characters
358	  if(_cell.uses_single_line_mode == YES)
359	    {
360	      newContents = [object stringByTrimmingCharactersInSet:
361				      [NSCharacterSet newlineCharacterSet]];
362	    }
363        }
364      else if ([object isKindOfClass: [NSAttributedString class]] == YES)
365        {
366          newContents = object;
367          _cell.contents_is_attributed_string = YES;
368          _cell.has_valid_object_value = YES;
369
370	  // If we are in single line mode, trim the new line characters
371	  if(_cell.uses_single_line_mode == YES)
372	    {
373	      newContents = [object stringByTrimmingCharactersInSet:
374				      [NSCharacterSet newlineCharacterSet]];
375	    }
376	}
377      else if ([_object_value respondsToSelector: @selector(attributedStringValue)])
378        {
379          newContents = [_object_value attributedStringValue];
380          _cell.contents_is_attributed_string = YES;
381          _cell.has_valid_object_value = YES;
382        }
383      else if ([_object_value respondsToSelector: @selector(stringValue)])
384        {
385          // If the thing that was assigned is not a string, but
386          // responds to stringValue then get that.
387          newContents = [_object_value stringValue];
388          _cell.contents_is_attributed_string = NO;
389          _cell.has_valid_object_value = YES;
390        }
391      else
392        {
393          newContents = [_object_value description];
394          _cell.contents_is_attributed_string = NO;
395          _cell.has_valid_object_value = YES;
396        }
397    }
398  else
399    {
400      newContents = [_formatter stringForObjectValue: _object_value];
401      _cell.contents_is_attributed_string = NO;
402      if (newContents != nil)
403        {
404          _cell.has_valid_object_value = YES;
405        }
406      else
407        {
408          _cell.has_valid_object_value = NO;
409        }
410    }
411
412  ASSIGNCOPY(_contents, newContents);
413}
414
415
416/**<p>Sets the NSCell's value to aDouble.</p>
417 *<p>See Also: -doubleValue</p>
418 */
419- (void) setDoubleValue: (double)aDouble
420{
421  NSNumber *number;
422
423  // NB: GNUstep can set a double value for an image cell
424
425  number = [NSNumber numberWithDouble: aDouble];
426  [self setObjectValue: number];
427}
428
429/**
430 *<p>Sets the NSCell's value to a aFloat. This used for example in
431 NSSliderCell</p>
432 *<p>See Also: -floatValue</p>
433 */
434- (void) setFloatValue: (float)aFloat
435{
436  NSNumber *number;
437
438  // NB: GNUstep can set a float value for an image cell.
439  // NSSliderCell is an example of it!
440
441  number = [NSNumber numberWithFloat: aFloat];
442  [self setObjectValue: number];
443}
444
445
446/**
447 *<p>Sets the NSCell's value to anInt.</p>
448 *<p>See Also: -intValue</p>
449 */
450- (void) setIntValue: (int)anInt
451{
452  NSNumber *number;
453
454  // NB: GNUstep can set an int value for an image cell.
455
456  number = [NSNumber numberWithInt: anInt];
457  [self setObjectValue: number];
458}
459
460/**
461 *<p>Sets the NSCell's value to anInt.</p>
462 *<p>See Also: -integerValue</p>
463 */
464- (void) setIntegerValue: (NSInteger)anInt
465{
466  NSNumber *number;
467
468  // NB: GNUstep can set an int value for an image cell.
469
470  number = [NSNumber numberWithInteger: anInt];
471  [self setObjectValue: number];
472}
473
474/**<p>Sets the cell's value to a NSString.
475   The NSCell's type is set to NSTextCellType if needed</p>
476   <p>See Also: -stringValue</p>
477 */
478- (void) setStringValue: (NSString*)aString
479{
480  /* We warn about nil for compatibiliy with MacOS X, which refuses
481     nil.  */
482  if (aString == nil)
483    {
484      NSDebugMLLog (@"MacOSXCompatibility",
485                    @"Attempt to use nil as string value");
486    }
487
488  if (_cell.type != NSTextCellType)
489    {
490      [self setType: NSTextCellType];
491    }
492
493  if (_formatter == nil)
494    {
495      [self setObjectValue: aString];
496    }
497  else
498    {
499      id newObjectValue;
500
501      if ([_formatter getObjectValue: &newObjectValue
502                      forString: aString
503                      errorDescription: NULL])
504        {
505          [self setObjectValue: newObjectValue];
506        }
507      else
508        {
509          ASSIGNCOPY(_contents, aString);
510          _cell.contents_is_attributed_string = NO;
511          _cell.has_valid_object_value = NO;
512        }
513    }
514}
515
516/**<p>Returns some NSCell's attributes for the specified <ref type="type"
517   id="NSCellAttribute">NSCellAttribute</ref></p>
518   <p>See Also: -setCellAttribute:to:</p>
519 */
520- (NSInteger) cellAttribute: (NSCellAttribute)aParameter
521{
522  switch (aParameter)
523    {
524    case NSCellDisabled: return _cell.is_disabled;
525    case NSCellState: return _cell.state;
526    case NSCellEditable: return _cell.is_editable;
527    case NSCellHighlighted: return _cell.is_highlighted;
528    case NSCellIsBordered: return _cell.is_bordered;
529    case NSCellAllowsMixedState: return _cell.allows_mixed_state;
530
531      /*
532        case NSPushInCell: return 0;
533        case NSChangeGrayCell: return 0;
534        case NSCellLightsByContents: return 0;
535        case NSCellLightsByGray: return 0;
536        case NSChangeBackgroundCell: return 0;
537        case NSCellLightsByBackground: return 0;
538        case NSCellChangesContents: return 0;
539        case NSCellIsInsetButton: return 0;
540      */
541    case NSCellHasOverlappingImage:
542      {
543        return _cell.image_position == NSImageOverlaps;
544      }
545    case NSCellHasImageHorizontal:
546      {
547        return (_cell.image_position == NSImageRight)
548          || (_cell.image_position == NSImageLeft);
549      }
550    case NSCellHasImageOnLeftOrBottom:
551      {
552        return (_cell.image_position == NSImageBelow)
553          || (_cell.image_position == NSImageLeft);
554      }
555    default:
556      {
557        NSWarnLog (@"cell attribute %d not supported", (int)aParameter);
558        break;
559      }
560    }
561
562  return 0;
563}
564
565
566/**<p>TODO</p>
567 *<p>See Also: -cellAttribute:</p>
568 */
569- (void) setCellAttribute: (NSCellAttribute)aParameter to: (NSInteger)value
570{
571  switch (aParameter)
572    {
573    case NSCellDisabled:
574      {
575        _cell.is_disabled = value;
576        break;
577      }
578    case NSCellState:
579      {
580        _cell.state = value;
581        break;
582      }
583    case NSCellEditable:
584      {
585        _cell.is_editable = value;
586        break;
587      }
588    case NSCellHighlighted:
589      {
590        _cell.is_highlighted = value;
591        break;
592      }
593    case NSCellHasOverlappingImage:
594      {
595        if (value)
596          {
597            _cell.image_position = NSImageOverlaps;
598          }
599        else
600          {
601            if (_cell.image_position == NSImageOverlaps)
602              {
603                _cell.image_position = NSImageLeft;
604              }
605          }
606        break;
607      }
608    case NSCellHasImageHorizontal:
609      {
610        if (value)
611          {
612            if (_cell.image_position != NSImageLeft
613                && _cell.image_position != NSImageRight)
614              {
615                _cell.image_position = NSImageLeft;
616              }
617          }
618        else
619          {
620            if (_cell.image_position == NSImageLeft)
621              {
622                _cell.image_position = NSImageAbove;
623              }
624            else if (_cell.image_position == NSImageRight)
625              {
626                _cell.image_position = NSImageBelow;
627              }
628          }
629        break;
630      }
631    case NSCellHasImageOnLeftOrBottom:
632      {
633        if (value)
634          {
635            if (_cell.image_position == NSImageAbove)
636              {
637                _cell.image_position = NSImageBelow;
638              }
639            else
640              {
641                _cell.image_position = NSImageLeft;
642              }
643          }
644        else
645          {
646            if (_cell.image_position == NSImageBelow)
647              {
648                _cell.image_position = NSImageAbove;
649              }
650            else
651              {
652                _cell.image_position = NSImageRight;
653              }
654          }
655        break;
656      }
657      /*
658    case NSCellChangesContents:
659      _cell. = value;
660      break;
661    case NSCellIsInsetButton:
662      _cell. = value;
663      break;
664*/
665    case NSCellIsBordered:
666      {
667        _cell.is_bordered = value;
668        break;
669      }
670    case NSCellAllowsMixedState:
671      {
672        _cell.allows_mixed_state = value;
673        break;
674      }
675    default:
676      {
677        NSWarnLog (@"cell attribute %d not supported", (int)aParameter);
678        break;
679      }
680    }
681}
682
683/**<p>Sets the NSCell's type. See <ref type="type" id="NSCellType">NSCellType
684   </ref>.If the cell is set to NSTextCellType, the cell is given
685   a default title and is reset to the default system font.</p>
686   <p>See Also: -type</p>
687*/
688- (void) setType: (NSCellType)aType
689{
690  if (_cell.type == aType)
691    {
692      return;
693    }
694
695  _cell.type = aType;
696  switch (_cell.type)
697    {
698      case NSTextCellType:
699        {
700          ASSIGN (_contents, @"title");
701          _cell.contents_is_attributed_string = NO;
702          /* Doc says we have to reset the font too. */
703          ASSIGN (_font, [fontClass systemFontOfSize: 0]);
704          break;
705        }
706      case NSImageCellType:
707        {
708          TEST_RELEASE (_cell_image);
709          _cell_image = nil;
710          break;
711        }
712    }
713}
714
715/**<p>Returns the cell's type. Returns NSNullCellType if the
716  cell's type flag is set to NSImageCellType and if the cell's image
717  is nil. See <ref type="type" id="NSCellType">NSCellType</ref> for more
718  information.</p><p>See Also -setType:</p>
719 */
720- (NSCellType) type
721{
722  if (_cell.type == NSImageCellType && _cell_image == nil)
723    return NSNullCellType;
724
725  return _cell.type;
726}
727
728
729/**<p>Returns whether the NSCell can respond to mouse events.</p>
730 *<p>See Also: -setEnabled:</p>
731 */
732- (BOOL) isEnabled
733{
734  return !_cell.is_disabled;
735}
736
737/**<p>Sets whether the NSCell can respond to mouse events</p>
738 <p>See Also: -isEnabled</p>
739 */
740- (void) setEnabled: (BOOL)flag
741{
742  _cell.is_disabled = !flag;
743}
744
745/**<p>Returns whether the NSCell has a bezeled border. By default a NSCell
746   has no bezeled border</p><p>See Also: -setBezeled:</p>
747 */
748- (BOOL) isBezeled
749{
750  return _cell.is_bezeled;
751}
752
753/**<p>Returns whether the NSCell has a border. By default a NSCell has
754   border</p><p>See Also: -setBordered: -setBezeled: -isBezeled</p>
755 */
756- (BOOL) isBordered
757{
758  return _cell.is_bordered;
759}
760
761/**<p>Returns whether the cell is opaque. Return NO by default</p>
762 */
763- (BOOL) isOpaque
764{
765  return NO;
766}
767
768/**<p>Sets whether the cell has a bezeled border.
769 If this method is called, the bordered flag is turn off.
770 By default a NSCell has no bezeled border</p>
771 <p>See Also: -isBezeled -setBordered: -isBordered</p>
772 */
773- (void) setBezeled: (BOOL)flag
774{
775  _cell.is_bezeled = flag;
776  _cell.is_bordered = NO;
777}
778
779/**<p>Sets whether the cell has a border.  If this method is called,
780 the bezeled flag is turn off. By default a NSCell has no border</p>
781 <p>See Also: -isBordered -setBezeled: -isBezeled</p>
782 */
783- (void) setBordered: (BOOL)flag
784{
785  _cell.is_bordered = flag;
786  _cell.is_bezeled = NO;
787}
788
789- (NSFocusRingType) focusRingType
790{
791  return _cell.focus_ring_type;
792}
793
794- (void) setFocusRingType: (NSFocusRingType)type
795{
796  _cell.focus_ring_type = type;
797}
798
799/**<p>Sets the NSCell's state.  Please use always symbolic constants when
800   calling this method. The integer values could be changed in the this
801   implementation. (Currently they match the Cocoa values but they are
802   quite strange)</p> <p>See Also: -state</p>
803 */
804- (void) setState: (NSInteger)value
805{
806  /* We do exactly as in macosx when value is not NSOnState,
807   * NSOffState, NSMixedState, even if their behaviour (value < 0 ==>
808   * NSMixedState) is a bit strange.  We could decide to do
809   * differently in the future, so please use always symbolic
810   * constants when calling this method, this way your code won't be
811   * broken by changes. */
812  if (value > 0 || (value < 0 && _cell.allows_mixed_state == NO))
813    {
814      _cell.state = NSOnState;
815    }
816  else if (value == 0)
817    {
818      _cell.state = NSOffState;
819    }
820  else
821    {
822      _cell.state = NSMixedState;
823    }
824}
825
826/**<p>Returns the NSCell's state</p>
827 <p>See Also: -setState: </p>
828*/
829- (NSInteger) state
830{
831  return _cell.state;
832}
833
834- (BOOL) allowsMixedState
835{
836  return _cell.allows_mixed_state;
837}
838
839- (void) setAllowsMixedState: (BOOL)flag
840{
841  _cell.allows_mixed_state = flag;
842  if (!flag && _cell.state == NSMixedState)
843    {
844      [self setNextState];
845    }
846}
847
848- (NSInteger) nextState
849{
850  switch (_cell.state)
851    {
852      case NSOnState:
853        {
854          return NSOffState;
855        }
856      case NSOffState:
857        {
858          if (_cell.allows_mixed_state)
859            {
860              return NSMixedState;
861            }
862          else
863            {
864              return NSOnState;
865            }
866        }
867      case NSMixedState:
868      default:
869        {
870          return NSOnState;
871        }
872    }
873}
874
875- (void) setNextState
876{
877  [self setState: [self nextState]];
878}
879
880/**<p>Returns the alignment of the text used in the NSCell. See
881   <ref type="type" id="NSTextAlignment">NSTextAlignment</ref> for more
882   informations. By default the text alignment is <ref type="type"
883   id="NSTextAlignment">NSJustifiedTextAlignment</ref></p>
884   <p>See Also: -setAlignment:</p>
885 */
886- (NSTextAlignment) alignment
887{
888  return _cell.text_align;
889}
890
891/** <p>Returns the font of the text used in the NSCell</p>
892    <p>See Also: -setFont:</p>
893 */
894- (NSFont*) font
895{
896  return _font;
897}
898
899/**<p>Returns whether the cell is editable.By default a NSCell is not editable.
900   </p><p>See Also: -setEditable:</p>
901 */
902- (BOOL) isEditable
903{
904  return _cell.is_editable;
905}
906
907/**<p>Returns whether the cell is selectable. This method returns YES if
908   the cell is selectable or editable. NO otherwise</p>
909   <p>See Also: -setSelectable: -isEditable -setEditable: </p>
910 */
911- (BOOL) isSelectable
912{
913  return _cell.is_selectable || _cell.is_editable;
914}
915
916/**<p>Returns whether the NSCell is scrollable. By default a NSCell is not
917   scrollable</p><p>See Also: -setScrollable:</p>
918 */
919- (BOOL) isScrollable
920{
921  return _cell.is_scrollable;
922}
923
924/**<p>Sets the alignment of the text. See <ref type="type"
925   id="NSTextAlignment">NSTextAlignment</ref>.</p><p>See Also: -alignment </p>
926 */
927- (void) setAlignment: (NSTextAlignment)mode
928{
929  // This does not have any influence on attributed strings
930  _cell.text_align = mode;
931}
932
933/**<p>Sets whether the NSCell's text is editable.</p>
934   <p>See Also: -isEditable -setSelectable: -isSelectable</p>
935*/
936- (void) setEditable: (BOOL)flag
937{
938  /*
939   * The cell_editable flag is also checked to see if the cell is
940   * selectable so turning edit on also turns selectability on (until
941   * edit is turned off again).
942   */
943  _cell.is_editable = flag;
944}
945
946/**<p>Sets the text font. The NSCell's type is set to NSTextCellType if needed
947   </p><p>See Also: -font -setType: -type</p>
948 */
949- (void) setFont: (NSFont*)fontObject
950{
951  if (_cell.type != NSTextCellType)
952    {
953      [self setType: NSTextCellType];
954    }
955
956  // This does not have any influence on attributed strings
957  ASSIGN (_font, fontObject);
958}
959
960/**<p>Sets whether the cell selectable. Making a cell unselectable also
961 * makes it uneditable until a -setEditable: re-enables it.</p>
962 *<p>See Also: -isSelectable -setEditable: -isEditable</p>
963 */
964- (void) setSelectable: (BOOL)flag
965{
966  _cell.is_selectable = flag;
967
968  if (!flag)
969    _cell.is_editable = NO;
970}
971
972/**<p>Sets whether the NCell is scrollable. By default a NSCell is not
973   scrollable</p><p>See Also: -isSelectable</p>
974 */
975- (void) setScrollable: (BOOL)flag
976{
977  _cell.is_scrollable = flag;
978  if (flag)
979    {
980      [self setWraps: NO];
981    }
982}
983
984- (void) setWraps: (BOOL)flag
985{
986  if (flag)
987    {
988      if (![self wraps])
989	[self setLineBreakMode: NSLineBreakByWordWrapping];
990    }
991  else
992    {
993      if ([self wraps])
994	[self setLineBreakMode: NSLineBreakByClipping];
995    }
996}
997
998- (BOOL) wraps
999{
1000  return _cell.line_break_mode == NSLineBreakByWordWrapping
1001      || _cell.line_break_mode == NSLineBreakByCharWrapping;
1002}
1003
1004- (void) setAttributedStringValue: (NSAttributedString*)attribStr
1005{
1006  /* Hmm.  FIXME.  Not sure what to do here. */
1007  if (_formatter != nil)
1008    {
1009      id newObjectValue;
1010
1011      if ([_formatter getObjectValue: &newObjectValue
1012                      forString: [attribStr string]
1013                      errorDescription: NULL] == YES)
1014        {
1015          [self setObjectValue: newObjectValue];
1016          /* What about the attributed string ?  We are loosing it. */
1017          return;
1018        }
1019      _cell.has_valid_object_value = NO;
1020    }
1021  else
1022    {
1023      _cell.has_valid_object_value = YES;
1024      ASSIGN (_object_value, attribStr);
1025    }
1026
1027  ASSIGN (_contents, attribStr);
1028  _cell.contents_is_attributed_string = YES;
1029}
1030
1031- (NSAttributedString*) attributedStringValue
1032{
1033  if (_formatter != nil)
1034    {
1035      NSDictionary *attributes;
1036      NSAttributedString *attrStr;
1037
1038      attributes = [self _nonAutoreleasedTypingAttributes];
1039      attrStr = [_formatter attributedStringForObjectValue: _object_value
1040                            withDefaultAttributes: attributes];
1041      RELEASE(attributes);
1042      if (attrStr != nil)
1043        {
1044          return attrStr;
1045        }
1046    }
1047
1048  /* In all other cases */
1049  if (_cell.contents_is_attributed_string &&
1050      nil != _contents)
1051    {
1052      return (NSAttributedString *)_contents;
1053    }
1054  else
1055    {
1056      NSDictionary *dict;
1057      NSAttributedString *attrStr;
1058
1059      dict = [self _nonAutoreleasedTypingAttributes];
1060      attrStr = [[NSAttributedString alloc] initWithString: [self stringValue]
1061                                            attributes: dict];
1062      RELEASE(dict);
1063      return AUTORELEASE(attrStr);
1064    }
1065}
1066
1067- (void) setAllowsEditingTextAttributes: (BOOL)flag
1068{
1069  _cell.is_rich_text = flag;
1070  if (!flag)
1071    _cell.imports_graphics = NO;
1072}
1073
1074- (BOOL) allowsEditingTextAttributes
1075{
1076  return _cell.is_rich_text;
1077}
1078
1079- (void) setImportsGraphics: (BOOL)flag
1080{
1081  _cell.imports_graphics = flag;
1082  if (flag)
1083    _cell.is_rich_text = YES;
1084}
1085
1086- (BOOL) importsGraphics
1087{
1088  return _cell.imports_graphics;
1089}
1090
1091- (NSString*) title
1092{
1093  return [self stringValue];
1094}
1095
1096- (void) setTitle: (NSString*)aString
1097{
1098  [self setStringValue: aString];
1099}
1100
1101- (NSLineBreakMode) lineBreakMode
1102{
1103  return _cell.line_break_mode;
1104}
1105
1106- (void) setLineBreakMode: (NSLineBreakMode)mode
1107{
1108  if (mode == NSLineBreakByCharWrapping || mode == NSLineBreakByWordWrapping)
1109    {
1110      _cell.is_scrollable = NO;
1111    }
1112  _cell.line_break_mode = mode;
1113}
1114
1115- (NSWritingDirection) baseWritingDirection
1116{
1117  return _cell.base_writing_direction;
1118}
1119
1120- (void) setBaseWritingDirection: (NSWritingDirection)direction
1121{
1122  _cell.base_writing_direction = direction;
1123}
1124
1125/**<p>Implemented by subclasses to return the action method.
1126   The NSCell implementaiton returns NULL.</p>
1127 <p>See Also: -setAction: -setTarget: -target</p>
1128 */
1129- (SEL) action
1130{
1131  return NULL;
1132}
1133
1134/** <p>Implemented by subclasses to set the action method.
1135    The NSCell implementation raises a NSInternalInconsistencyException</p>
1136 <p>See Also: -action -setTarget: -target</p>
1137*/
1138- (void) setAction: (SEL)aSelector
1139{
1140  [NSException raise: NSInternalInconsistencyException
1141              format: @"attempt to set an action in an NSCell"];
1142}
1143
1144/**<p>Implemented by subclasses to set the target object.
1145   The NSCell implementation raises a NSInternalInconsistencyException</p>
1146   <p>See Also: -target -setAction: -action</p>
1147 */
1148- (void) setTarget: (id)anObject
1149{
1150  [NSException raise: NSInternalInconsistencyException
1151              format: @"attempt to set a target in an NSCell"];
1152}
1153
1154/**<p>Implemented by subclass to return the target object.
1155   The NSCell implementation returns nil</p>
1156   <p>See Also: -setTarget: -setAction: -action</p>
1157 */
1158- (id) target
1159{
1160  return nil;
1161}
1162
1163/**<p>Returns whether the cell can continuously send its action messages.</p>
1164   <p>See Also: -setContinuous:</p>
1165 */
1166- (BOOL) isContinuous
1167{
1168  // Some subclasses should redefine this with NSLeftMouseDraggedMask
1169  return (_action_mask & NSPeriodicMask) != 0;
1170}
1171
1172/**<p>Sets whether the cell can continuously send its action messages.</p>
1173 *<p>See Also: -isContinuous</p>
1174 */
1175- (void) setContinuous: (BOOL)flag
1176{
1177  // Some subclasses should redefine this with NSLeftMouseDraggedMask
1178  if (flag)
1179    {
1180      _action_mask |= NSPeriodicMask;
1181    }
1182  else
1183    {
1184      _action_mask &= ~NSPeriodicMask;
1185    }
1186}
1187
1188/**<p>TODO Explain</p>
1189 */
1190- (NSInteger) sendActionOn: (NSInteger)mask
1191{
1192  NSUInteger previousMask = _action_mask;
1193
1194  _action_mask = mask;
1195
1196  return previousMask;
1197}
1198
1199/**<p>Returns the NSCell's image if the NSCell's type is <ref type="type"
1200   id="NSCellType">NSImageCellType</ref>,
1201   returns nil otherwise.</p>
1202   <p>See Also: -setImage: -setType: -type</p>
1203 */
1204- (NSImage*) image
1205{
1206  if (_cell.type == NSImageCellType)
1207    {
1208      return _cell_image;
1209    }
1210  else
1211    return nil;
1212}
1213
1214/**<p>Sets the NSCell's image to anImage. This method sets the cell's type
1215   to NSImageCellType if needed. Raises an NSInvalidArgumentException if
1216   the anImage is not an NSImage (sub)class. The new image is retained and the
1217   old one is released</p><p>See Also: -image</p>
1218 */
1219- (void) setImage: (NSImage*)anImage
1220{
1221  if (anImage)
1222    {
1223      NSAssert ([anImage isKindOfClass: imageClass],
1224                NSInvalidArgumentException);
1225    }
1226
1227  if (_cell.type != NSImageCellType)
1228    {
1229      [self setType: NSImageCellType];
1230    }
1231
1232  ASSIGN (_cell_image, anImage);
1233}
1234
1235/**<p>Implemented by sublclasses to assigns the tag <var>anInt</var>.
1236    The NSCell implementation raises an NSInvalidArgumentException.</p>
1237    <p>See Also: -tag</p>
1238 */
1239- (void) setTag: (NSInteger)anInt
1240{
1241  [NSException raise: NSInternalInconsistencyException
1242              format: @"attempt to set a tag in an NSCell"];
1243}
1244
1245/**<p>Implemented by subclasses to Return the tag.
1246   The NSCell implementation returns -1 </p><p>See Also: -setTag:</p>
1247 */
1248- (NSInteger) tag
1249{
1250  return -1;
1251}
1252
1253/*
1254 * Formatting Data
1255 */
1256- (void) setFloatingPointFormat: (BOOL)autoRange
1257                           left: (NSUInteger)leftDigits
1258                          right: (NSUInteger)rightDigits
1259{
1260  NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
1261  NSMutableString *format = [[NSMutableString alloc] init];
1262
1263  if (autoRange)
1264    {
1265      NSUInteger fieldWidth = leftDigits + rightDigits + 1;
1266
1267      // FIXME: this does not fully match the documentation.
1268      while (fieldWidth--)
1269        {
1270          [format appendString: @"#"];
1271        }
1272    }
1273  else
1274    {
1275      while (leftDigits--)
1276        {
1277          [format appendString: @"#"];
1278        }
1279      [format appendString: @"."];
1280      while (rightDigits--)
1281        {
1282          [format appendString: @"0"];
1283        }
1284    }
1285
1286  [formatter setFormat: format];
1287  RELEASE(format);
1288  [self setFormatter: formatter];
1289  RELEASE(formatter);
1290}
1291
1292- (void) setFormatter: (NSFormatter*)newFormatter
1293{
1294  ASSIGN(_formatter, newFormatter);
1295}
1296
1297- (id) formatter
1298{
1299  return _formatter;
1300}
1301
1302/**<p> TODO</p>
1303 */
1304- (NSInteger) entryType
1305{
1306  return _cell.entry_type;
1307}
1308
1309/** <p>TODO</p>
1310 */
1311- (void) setEntryType: (NSInteger)aType
1312{
1313  [self setType: NSTextCellType];
1314  // TODO: This should select a suitable formatter
1315  _cell.entry_type = aType;
1316}
1317
1318- (BOOL) isEntryAcceptable: (NSString*)aString
1319{
1320  if ((_formatter != nil) && ![aString isEqualToString: @""])
1321    {
1322      id newObjectValue;
1323
1324      return [_formatter getObjectValue: &newObjectValue
1325                         forString: aString
1326                         errorDescription: NULL];
1327    }
1328  else
1329    {
1330      return YES;
1331    }
1332}
1333
1334/*
1335 * Menu
1336 */
1337- (void) setMenu: (NSMenu*)aMenu
1338{
1339  ASSIGN (_menu, aMenu);
1340}
1341
1342- (NSMenu*) menu
1343{
1344  return _menu;
1345}
1346
1347- (NSMenu*) menuForEvent: (NSEvent*)anEvent
1348                  inRect: (NSRect)cellFrame
1349                  ofView: (NSView*)aView
1350{
1351  return [self menu];
1352}
1353
1354/**
1355 * Compares the reciever to another to another NSCell.
1356 * The argument must be an NSCell sublclass and have
1357 * the NSCellType NSTextCellType.  Returns the result
1358 * of the comparison of each cell's stringValue.
1359 */
1360- (NSComparisonResult) compare: (id)otherCell
1361{
1362  if ([otherCell isKindOfClass: cellClass] == NO)
1363    {
1364      [NSException raise: NSBadComparisonException
1365                   format: @"NSCell comparison with non-NSCell"];
1366    }
1367  if (_cell.type != NSTextCellType
1368      || ((NSCell*)otherCell)->_cell.type != NSTextCellType)
1369    {
1370      [NSException raise: NSBadComparisonException
1371                   format: @"Comparison between non-text cells"];
1372    }
1373  /* We shouldn't access instance variables directly as subclasses
1374     may override stringValue to retrieve the value from somewhere else.  */
1375  return [[self stringValue] compare: [(NSCell*)otherCell stringValue]];
1376}
1377
1378/*
1379 * Should this cell respond to keyboard input?
1380 */
1381- (BOOL) acceptsFirstResponder
1382{
1383  return _cell.is_disabled == NO && _cell.refuses_first_responder == NO;
1384}
1385
1386- (void) setShowsFirstResponder: (BOOL)flag
1387{
1388  _cell.shows_first_responder = flag;
1389}
1390
1391- (BOOL) showsFirstResponder
1392{
1393  return _cell.shows_first_responder;
1394}
1395
1396- (void) setTitleWithMnemonic: (NSString*)aString
1397{
1398  NSRange r = [aString rangeOfString: @"&"];
1399
1400  if (r.length > 0)
1401    {
1402      NSUInteger location = r.location;
1403
1404      [self setTitle: [[aString substringToIndex: location]
1405                        stringByAppendingString:
1406                          [aString substringFromIndex: NSMaxRange(r)]]];
1407      // TODO: We should underline this character
1408      [self setMnemonicLocation: location];
1409    }
1410}
1411
1412- (NSString*) mnemonic
1413{
1414  NSUInteger location = [self mnemonicLocation];
1415  NSString *c = [self title];
1416
1417  if ((location == NSNotFound) || location >= [c length])
1418    return @"";
1419
1420  return [c substringWithRange: NSMakeRange (location, 1)];
1421}
1422
1423- (void) setMnemonicLocation: (NSUInteger)location
1424{
1425  _cell.mnemonic_location = location;
1426}
1427
1428- (NSUInteger) mnemonicLocation
1429{
1430  return _cell.mnemonic_location;
1431}
1432
1433- (BOOL) refusesFirstResponder
1434{
1435  return _cell.refuses_first_responder;
1436}
1437
1438- (void) setRefusesFirstResponder: (BOOL)flag
1439{
1440  _cell.refuses_first_responder = flag;
1441}
1442
1443/**
1444 * Simulates a single click in the cell (only works with controls which have
1445 * no more than one cell). This method is deprecated,
1446 * performClickWithFrame:inView: is the right method to use now.
1447 */
1448- (void) performClick: (id)sender
1449{
1450  NSView *cv = [self controlView];
1451
1452  if (cv != nil)
1453    [self performClickWithFrame: [cv bounds] inView: cv];
1454}
1455
1456/*
1457 * Helper method used to send actions. Sender normally is [self controlView].
1458 */
1459- (BOOL) _sendActionFrom: (id)sender
1460{
1461  SEL action = [self action];
1462
1463  if ([sender respondsToSelector: @selector(sendAction:to:)])
1464    {
1465      return [sender sendAction: action to: [self target]];
1466    }
1467  else
1468    {
1469      if (sender == nil)
1470        sender = self;
1471
1472      if (action)
1473        {
1474          return [NSApp sendAction: action to: [self target] from: sender];
1475        }
1476    }
1477
1478  return NO;
1479}
1480
1481/**
1482 * Simulates a single click in the cell.
1483 * The display of the cell with this event
1484 * occurs in the area delimited by <var>cellFrame</var> within
1485 * <var>controlView</var>.
1486 */
1487- (void) performClickWithFrame: (NSRect)cellFrame inView: (NSView *)controlView
1488{
1489  if (_cell.is_disabled == YES)
1490    {
1491      return;
1492    }
1493
1494  [self setNextState];
1495
1496  if ((controlView != nil) && [controlView canDraw])
1497    {
1498      NSWindow *cvWin = [controlView window];
1499      NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: 0.1];
1500
1501      [controlView lockFocus];
1502      [self highlight: YES withFrame: cellFrame inView: controlView];
1503      [cvWin flushWindow];
1504
1505      // Wait approx 1/10 seconds
1506      [[NSRunLoop currentRunLoop] runUntilDate: limit];
1507
1508      [self highlight: NO withFrame: cellFrame inView: controlView];
1509      [cvWin flushWindow];
1510      [controlView unlockFocus];
1511    }
1512
1513  [self _sendActionFrom: controlView];
1514}
1515
1516/*
1517 * Deriving values from other objects (not necessarily cells)
1518 */
1519- (void) takeObjectValueFrom: (id)sender
1520{
1521  [self setObjectValue: [sender objectValue]];
1522}
1523
1524/** <p>Sets the NSCell's double value to sender's double value</p>
1525    <p>See Also: -setDoubleValue:</p>
1526 */
1527- (void) takeDoubleValueFrom: (id)sender
1528{
1529  [self setDoubleValue: [sender doubleValue]];
1530}
1531
1532/** <p>Sets the NSCell's float value to sender's float value</p>
1533    <p>See Also: -setFloatValue:</p>
1534 */
1535- (void) takeFloatValueFrom: (id)sender
1536{
1537  [self setFloatValue: [sender floatValue]];
1538}
1539
1540/** <p>Sets the NSCell's int value to sender's int value</p>
1541    <p>See Also: -setIntValue:</p>
1542 */
1543- (void) takeIntValueFrom: (id)sender
1544{
1545  [self setIntValue: [sender intValue]];
1546}
1547
1548/** <p>Sets the NSCell's NSInteger value to sender's NSInteger value</p>
1549    <p>See Also: -setIntegerValue:</p>
1550 */
1551- (void) takeIntegerValueFrom: (id)sender
1552{
1553  [self setIntegerValue: [sender integerValue]];
1554}
1555
1556/** <p>Sets the NSCell's NSString value to sender's NSSting value</p>
1557    <p>See Also: -setStringValue:</p>
1558 */
1559- (void) takeStringValueFrom: (id)sender
1560{
1561  [self setStringValue: [sender stringValue]];
1562}
1563
1564/** <p>Returns the NSCell's represented object</p>
1565    <p>See Also: -setRepresentedObject:</p>
1566 */
1567- (id) representedObject
1568{
1569  return _represented_object;
1570}
1571
1572/** <p>Sets the NSCell's represented object to <var>anObject</var>.
1573    anObject will be retain.</p><p>See Also: -representedObject</p>
1574 */
1575- (void) setRepresentedObject: (id)anObject
1576{
1577  /* Ahm - not nice - the RETAIN here could cause retain cycles - anyway. */
1578  ASSIGN (_represented_object, anObject);
1579}
1580
1581- (NSBackgroundStyle)backgroundStyle
1582{
1583  return(_cell.background_style);
1584}
1585
1586- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle
1587{
1588  _cell.background_style = backgroundStyle;
1589}
1590
1591/**<p>Returns the mouse flags. This flags are usally sets in
1592   the -trackMouse:inRect:ofView:untilMouseUp: method</p>
1593 */
1594- (NSInteger) mouseDownFlags
1595{
1596  return _mouse_down_flags;
1597}
1598
1599/**<p>Gets the NSCell's <var>delay</var> and the <var>interval</var>
1600   parameters used when NSCell sends continouly action messages.
1601   The NSCell implementation sets <var>delay</var> to 0.2 and <var>interval</var>
1602   to 0.025.</p>
1603   <p>See Also: -trackMouse:inRect:ofView:untilMouseUp:</p>
1604 */
1605- (void) getPeriodicDelay: (float*)delay interval: (float*)interval
1606{
1607  *delay = 0.2;
1608  *interval = 0.025;
1609}
1610
1611/**<p>Returns whether tracking starts. The NSCell implementation
1612   returns YES when the <var>startPoint</var> is into the control view
1613   retangle, NO otherwise. This method is call at the early stage of
1614   -trackMouse:inRect:ofView:untilMouseUp:</p><p>See Also:
1615   [NSView-mouse:inRect:] -trackMouse:inRect:ofView:untilMouseUp:
1616   </p>
1617 */
1618- (BOOL) startTrackingAt: (NSPoint)startPoint inView: (NSView*)controlView
1619{
1620  if ([self isContinuous] || (_action_mask & NSLeftMouseDraggedMask))
1621    {
1622      return YES;
1623    }
1624  else
1625    {
1626      return NO;
1627    }
1628}
1629
1630/** <p>Returns whether the mouse dragging should continue for the cell.
1631    Subclasses should overrided this method if you want
1632    stop tracking the mouse. This method is call in the
1633    -trackMouse:inRect:ofView:untilMouseUp: main loop.</p>
1634    <p>See Also: -trackMouse:inRect:ofView:untilMouseUp:</p>
1635 */
1636- (BOOL) continueTracking: (NSPoint)lastPoint
1637                       at: (NSPoint)currentPoint
1638                   inView: (NSView*)controlView
1639{
1640  if ([self isContinuous] || (_action_mask & NSLeftMouseDraggedMask))
1641    {
1642      return YES;
1643    }
1644  else
1645    {
1646      return NO;
1647    }
1648}
1649
1650/**<p>Default implementation of this method in NSCell does nothing.</p>
1651 */
1652- (void) stopTracking: (NSPoint)lastPoint
1653                   at: (NSPoint)stopPoint
1654               inView: (NSView*)controlView
1655            mouseIsUp: (BOOL)flag
1656{
1657}
1658
1659- (BOOL) trackMouse: (NSEvent*)theEvent
1660             inRect: (NSRect)cellFrame
1661             ofView: (NSView*)controlView
1662       untilMouseUp: (BOOL)flag
1663{
1664  NSApplication *theApp = [NSApplication sharedApplication];
1665  NSUInteger event_mask = NSLeftMouseDownMask | NSLeftMouseUpMask
1666    | NSMouseMovedMask | NSLeftMouseDraggedMask | NSOtherMouseDraggedMask
1667    | NSRightMouseDraggedMask;
1668  NSPoint location = [theEvent locationInWindow];
1669  NSPoint point = [controlView convertPoint: location fromView: nil];
1670  NSPoint last_point = point;
1671  BOOL mouseWentUp = NO;
1672  BOOL tracking;
1673  unsigned periodCount = 0;
1674
1675  NSDebugLLog(@"NSCell", @"cell start tracking in rect %@ initial point %f %f",
1676             NSStringFromRect(cellFrame), point.x, point.y);
1677
1678  _mouse_down_flags = [theEvent modifierFlags];
1679
1680  if (![self isEnabled])
1681    {
1682      return NO;
1683    }
1684
1685  if (![controlView mouse: point inRect: cellFrame])
1686    {
1687      // point is not in cell
1688      return NO;
1689    }
1690
1691  tracking = [self startTrackingAt: point inView: controlView];
1692
1693  if (_action_mask & NSEventMaskFromType([theEvent type]))
1694    {
1695      [self _sendActionFrom: controlView];
1696    }
1697
1698  if ([self isContinuous])
1699    {
1700      float delay;
1701      float interval;
1702
1703      [self getPeriodicDelay: &delay interval: &interval];
1704      [NSEvent startPeriodicEventsAfterDelay: delay withPeriod: interval];
1705      event_mask |= NSPeriodicMask;
1706    }
1707
1708  NSDebugLLog(@"NSCell", @"cell get mouse events\n");
1709  if (theEvent != [NSApp currentEvent])
1710    {
1711      theEvent = [NSApp currentEvent];
1712    }
1713  else
1714    {
1715      theEvent = [theApp nextEventMatchingMask: event_mask
1716                                     untilDate: [NSDate distantFuture]
1717                                        inMode: NSEventTrackingRunLoopMode
1718                                       dequeue: YES];
1719    }
1720
1721  while (YES)
1722    {
1723      NSEventType eventType;
1724
1725      eventType = [theEvent type];
1726
1727      // Did the mouse go up?
1728      if (eventType == NSLeftMouseUp)
1729        {
1730          NSDebugLLog(@"NSCell", @"cell mouse went up\n");
1731          mouseWentUp = YES;
1732          break;
1733        }
1734      else if (eventType == NSPeriodic)
1735        {
1736          NSDebugLLog (@"NSCell", @"cell got a periodic event");
1737          if (periodCount == 4)
1738            {
1739              NSWindow *w = [controlView window];
1740
1741              /*
1742               * Too many periodic events in succession -
1743               * update the mouse location and reset the counter.
1744               */
1745              location = [w mouseLocationOutsideOfEventStream];
1746              last_point = point;
1747              point = [controlView convertPoint: location fromView: nil];
1748              periodCount = 0;
1749            }
1750          else
1751            {
1752              periodCount++;
1753            }
1754        }
1755      else
1756        {
1757          location = [theEvent locationInWindow];
1758          last_point = point;
1759          point = [controlView convertPoint: location fromView: nil];
1760        }
1761
1762      if (!flag && ![controlView mouse: point inRect: cellFrame])
1763        {
1764          NSDebugLLog(@"NSCell", @"point not in cell frame\n");
1765          break;
1766        }
1767
1768      if (tracking)
1769        {
1770          // should continue tracking?
1771          tracking = [self continueTracking: last_point
1772                                         at: point
1773                                     inView: controlView];
1774          NSDebugLLog(@"NSCell", @"cell continue tracking %d\n", tracking);
1775        }
1776
1777      if (_action_mask & NSEventMaskFromType([theEvent type]))
1778        {
1779          [self _sendActionFrom: controlView];
1780        }
1781
1782      theEvent = [theApp nextEventMatchingMask: event_mask
1783                                     untilDate: [NSDate distantFuture]
1784                                        inMode: NSEventTrackingRunLoopMode
1785                                       dequeue: YES];
1786    }
1787
1788  if (tracking)
1789    {
1790      // Hook called when stop tracking
1791      [self stopTracking: last_point
1792                      at: point
1793                  inView: controlView
1794               mouseIsUp: mouseWentUp];
1795    }
1796
1797  if ([self isContinuous])
1798    {
1799      [NSEvent stopPeriodicEvents];
1800    }
1801
1802  if (mouseWentUp)
1803    {
1804      [self setNextState];
1805      if (_action_mask & NSEventMaskFromType([theEvent type]))
1806        {
1807          [self _sendActionFrom: controlView];
1808        }
1809    }
1810
1811  // Return YES only if the mouse went up within the cell or flag was true
1812  if (mouseWentUp && (flag || [controlView mouse: point inRect: cellFrame]))
1813    {
1814      NSDebugLLog(@"NSCell", @"mouse went up in cell\n");
1815      return YES;
1816    }
1817  else
1818    {
1819      // Otherwise return NO
1820      NSDebugLLog(@"NSCell", @"mouse did not go up in cell\n");
1821      return NO;
1822    }
1823}
1824
1825- (NSUInteger) hitTestForEvent: (NSEvent*)event
1826                       inRect: (NSRect)cellFrame
1827                       ofView: (NSView*)controlView
1828{
1829  if (_cell.type == NSImageCellType)
1830    {
1831      if ((_cell_image != nil) &&
1832          NSMouseInRect([controlView convertPoint: [event locationInWindow]
1833                                         fromView: nil],
1834                        [self imageRectForBounds: [controlView bounds]],
1835                        [controlView isFlipped]))
1836        {
1837          return NSCellHitContentArea;
1838        }
1839      else
1840        {
1841          return NSCellHitNone;
1842        }
1843    }
1844  else if (_cell.type == NSTextCellType)
1845    {
1846      if (_contents == nil)
1847        {
1848          return NSCellHitNone;
1849        }
1850      else if (_cell.is_disabled == NO)
1851        {
1852          return NSCellHitContentArea | NSCellHitEditableTextArea;
1853        }
1854      else
1855        {
1856          return NSCellHitContentArea;
1857        }
1858    }
1859  else
1860    {
1861      if (_cell.is_disabled == NO)
1862        {
1863          return NSCellHitContentArea | NSCellHitTrackableArea;
1864        }
1865      else
1866        {
1867          return NSCellHitContentArea;
1868        }
1869    }
1870}
1871
1872/** <p>TODO</p>
1873 */
1874- (void) resetCursorRect: (NSRect)cellFrame inView: (NSView*)controlView
1875{
1876  if (_cell.type == NSTextCellType && _cell.is_disabled == NO
1877    && (_cell.is_selectable == YES || _cell.is_editable == YES))
1878    {
1879      static NSCursor        *cursor = nil;
1880      NSRect        rect;
1881
1882      if (cursor== nil)
1883        {
1884          cursor = RETAIN([NSCursor IBeamCursor]);
1885        }
1886      rect = NSIntersectionRect(cellFrame, [controlView visibleRect]);
1887      /*
1888       * Here we depend on an undocumented feature of NSCursor which may or
1889       * may not exist in OPENSTEP or MacOS-X ...
1890       * If we add a cursor rect to a view and don't set it to be set on
1891       * either entry to or exit from the view, we push it on entry and
1892       * pop it from the cursor stack on exit.
1893       */
1894      [controlView addCursorRect: rect cursor: cursor];
1895    }
1896}
1897
1898/**<p>Implemented by subclasses to returns the key equivalent.
1899   The NSCell implementation returns an empty NSString. </p>
1900 */
1901- (NSString*) keyEquivalent
1902{
1903  return @"";
1904}
1905
1906/**<p>Does nothing. This method is used by subclasses to recalculate sizes</p>
1907   <p>It is usally called from a NSControl object</p>
1908   <p>See Also: [NSControl-calcSize]</p>
1909 */
1910- (void) calcDrawInfo: (NSRect)aRect
1911{
1912}
1913
1914/**Returns the minimun size needed to display the NSCell.
1915   This size is calculate by adding :
1916   <list>
1917   <item> the borders (plain or bezeled) size</item>
1918   <item> the spacing between the border and inside the cell</item>
1919   <item> the TODO ... if the cell is type  of NSTextCellType
1920   or the image size if the cell has a NSImageCellType type.</item>
1921   </list>
1922  <p>This method  returns NSZeroSize if the cell has a NSNullCellType type
1923   (Cocoa returns a very big size instead).
1924   </p>
1925 */
1926- (NSSize) cellSize
1927{
1928  NSSize borderSize, s;
1929  NSBorderType aType;
1930
1931  // Get border size
1932  if (_cell.is_bordered)
1933    aType = NSLineBorder;
1934  else if (_cell.is_bezeled)
1935    aType = NSBezelBorder;
1936  else
1937    aType = NSNoBorder;
1938
1939  borderSize = [[GSTheme theme] sizeForBorderType: aType];
1940
1941  // Add spacing between border and inside
1942  if (_cell.is_bordered || _cell.is_bezeled)
1943    {
1944      borderSize.height += 1;
1945      borderSize.width  += 3;
1946    }
1947
1948  // Get Content Size
1949  switch (_cell.type)
1950    {
1951      case NSTextCellType:
1952        {
1953          NSAttributedString *attrStr;
1954
1955          attrStr = [self attributedStringValue];
1956          if ([attrStr length] != 0)
1957            {
1958              s = [attrStr size];
1959            }
1960          else
1961            {
1962              s = [self _sizeText: @"A"];
1963            }
1964        }
1965        break;
1966
1967      case NSImageCellType:
1968        if (_cell_image == nil)
1969          {
1970            s = NSZeroSize;
1971          }
1972        else
1973          {
1974            s = [_cell_image size];
1975          }
1976        break;
1977
1978      default:
1979      case NSNullCellType:
1980        //  macosx instead returns a 'very big size' here; we return NSZeroSize
1981        s = NSZeroSize;
1982        break;
1983    }
1984
1985  // Add in border size
1986  s.width += 2 * borderSize.width;
1987  s.height += 2 * borderSize.height;
1988
1989  return s;
1990}
1991
1992/**<p>TODO. Currently the GNUstep implementation returns -cellSize</p>
1993   <p>See Also: -cellSize</p>
1994 */
1995- (NSSize) cellSizeForBounds: (NSRect)aRect
1996{
1997  if (_cell.type == NSTextCellType)
1998    {
1999      // TODO: Resize the text to fit
2000    }
2001
2002  return [self cellSize];
2003}
2004
2005/**<p>TODO</p>
2006 */
2007- (NSRect) drawingRectForBounds: (NSRect)theRect
2008{
2009  NSSize borderSize;
2010  NSBorderType aType;
2011
2012  // Get border size
2013  if (_cell.is_bordered)
2014    aType = NSLineBorder;
2015  else if (_cell.is_bezeled)
2016    aType = NSBezelBorder;
2017  else
2018    aType = NSNoBorder;
2019
2020  borderSize = [[GSTheme theme] sizeForBorderType: aType];
2021  return NSInsetRect(theRect, borderSize.width, borderSize.height);
2022}
2023
2024/**<p>Frame the image gets drawn in</p>
2025 */
2026- (NSRect) imageRectForBounds: (NSRect)theRect
2027{
2028  if (_cell.type == NSImageCellType)
2029    {
2030      NSRect frame = [self drawingRectForBounds: theRect];
2031
2032      // Add spacing between border and inside
2033      if (_cell.is_bordered || _cell.is_bezeled)
2034        {
2035          frame.origin.x += 3;
2036          frame.size.width -= 6;
2037          frame.origin.y += 1;
2038          frame.size.height -= 2;
2039        }
2040      return frame;
2041    }
2042  else
2043    {
2044      return theRect;
2045    }
2046}
2047
2048/** <p>Frame the title gets drawn in</p>
2049 */
2050- (NSRect) titleRectForBounds: (NSRect)theRect
2051{
2052  if (_cell.type == NSTextCellType)
2053    {
2054      NSRect frame = [self drawingRectForBounds: theRect];
2055
2056      // Add spacing between border and inside
2057      if (_cell.is_bordered || _cell.is_bezeled)
2058        {
2059          frame.origin.x += 3;
2060          frame.size.width -= 6;
2061          frame.origin.y += 1;
2062          frame.size.height -= 2;
2063        }
2064      return frame;
2065    }
2066  else
2067    {
2068      return theRect;
2069    }
2070}
2071
2072- (void) setControlSize: (NSControlSize)controlSize
2073{
2074  _cell.control_size = controlSize;
2075}
2076
2077- (NSControlSize) controlSize
2078{
2079  return _cell.control_size;
2080}
2081
2082- (void) setControlTint: (NSControlTint)controlTint
2083{
2084  _cell.control_tint = controlTint;
2085}
2086
2087- (NSControlTint) controlTint
2088{
2089  return _cell.control_tint;
2090}
2091
2092/**<p>This method is used by subclasses to get the control view.
2093   This method returns nil.</p>
2094 */
2095- (NSView*) controlView
2096{
2097  return nil;
2098}
2099
2100/**<p>This method is used by subclasses to specify the control view.</p>
2101 */
2102- (void) setControlView: (NSView*)view
2103{
2104  // Do nothing
2105}
2106
2107/** <p>This drawing is minimal and with no background,
2108 * to make it easier for subclass to customize drawing. </p>
2109 */
2110- (void) drawInteriorWithFrame: (NSRect)cellFrame inView: (NSView*)controlView
2111{
2112  switch (_cell.type)
2113    {
2114      case NSTextCellType:
2115	if (_cell.in_editing)
2116	  [self _drawEditorWithFrame: cellFrame inView: controlView];
2117	else
2118	  [self _drawAttributedText: [self _drawAttributedString]
2119			    inFrame: [self titleRectForBounds: cellFrame]];
2120        break;
2121
2122      case NSImageCellType:
2123        if (_cell_image)
2124          {
2125            NSSize size;
2126            NSPoint position;
2127            NSRect drawingRect = [self imageRectForBounds: cellFrame];
2128            NSRect rect;
2129
2130            size = [_cell_image size];
2131            position.x = MAX(NSMidX(drawingRect) - (size.width/2.),0.);
2132            position.y = MAX(NSMidY(drawingRect) - (size.height/2.),0.);
2133            rect = NSMakeRect(position.x, position.y, size.width, size.height);
2134
2135            if (nil != controlView)
2136              {
2137                rect = [controlView centerScanRect: rect];
2138              }
2139
2140            [_cell_image drawInRect: rect
2141			   fromRect: NSZeroRect
2142			  operation: NSCompositeSourceOver
2143			   fraction: 1.0
2144		     respectFlipped: YES
2145			      hints: nil];
2146          }
2147        break;
2148
2149      case NSNullCellType:
2150        break;
2151    }
2152
2153  // NB: We don't do any highlighting to make it easier for subclasses
2154  // to reuse this code while doing their own custom highlighting and
2155  // prettyfying
2156}
2157
2158/**<p>Draws the cell in <var>controlView</var></p>
2159 */
2160- (void) drawWithFrame: (NSRect)cellFrame inView: (NSView*)controlView
2161{
2162  // do nothing if cell's frame rect is zero
2163  if (NSIsEmptyRect(cellFrame))
2164    return;
2165
2166  // draw the border if needed
2167  [self _drawBorderAndBackgroundWithFrame: cellFrame inView: controlView];
2168  // draw interior
2169  [self drawInteriorWithFrame: cellFrame inView: controlView];
2170  // Draw first responder
2171  [self _drawFocusRingWithFrame: cellFrame inView: controlView];
2172}
2173
2174/**<p>Sets whether the NSCell is highlighted.</p>
2175   <p>See Also: -isHighlighted</p>
2176 */
2177- (void) setHighlighted: (BOOL) flag
2178{
2179  _cell.is_highlighted = flag;
2180}
2181
2182/**<p>Returns whether the cell is highlighted. By default NO</p>
2183   <p>See Also: -setHighlighted:</p>
2184 */
2185- (BOOL) isHighlighted
2186{
2187  return _cell.is_highlighted;
2188}
2189
2190/**
2191 *<p>TODO explain</p>
2192 */
2193
2194- (void) highlight: (BOOL)lit
2195         withFrame: (NSRect)cellFrame
2196            inView: (NSView*)controlView
2197{
2198  if (_cell.is_highlighted != lit)
2199    {
2200      _cell.is_highlighted = lit;
2201
2202      // Disabling this because when combined with making
2203      // -[NSButtonCell isOpaque] return NO, it was causing scroller buttons
2204      // to stay stuck down. --Eric (2013-09-28)
2205#if 0
2206      /*
2207       * NB: This has a visible effect only if subclasses override
2208       * drawWithFrame:inView: to draw something special when the
2209       * cell is highlighted.
2210       * NSCell simply draws border+text/image and makes no highlighting,
2211       * for easier subclassing.
2212       */
2213      if ([self isOpaque] == NO)
2214        {
2215          /* FIXME - This looks like potentially generating an
2216           * infinite loop!  The control asking the cell to draw
2217           * itself in the rect, the cell asking the control to draw
2218           * the rect, the control asking the cell to draw itself in
2219           * the rect, the cell ...
2220           *
2221           * I think we should remove it.  The control is responsible
2222           * for using the cell to draw, not vice versa.
2223           */
2224          [controlView displayRect: cellFrame];
2225        }
2226#endif
2227      [self drawWithFrame: cellFrame inView: controlView];
2228    }
2229}
2230
2231- (NSColor*) highlightColorWithFrame: (NSRect)cellFrame
2232                              inView: (NSView *)controlView
2233{
2234  return [NSColor selectedControlColor];
2235}
2236
2237- (NSText*) setUpFieldEditorAttributes: (NSText*)textObject
2238{
2239  NSDictionary *attr;
2240
2241  // Reset the string to have a well defined state. The real string gets set later on.
2242  [textObject setString: @""];
2243
2244  [textObject setTextColor: [self textColor]];
2245  if ([self isBezeled])
2246    {
2247      [textObject setBackgroundColor: [NSColor textBackgroundColor]];
2248      [textObject setDrawsBackground: YES];
2249    }
2250  else
2251    {
2252      [textObject setDrawsBackground: NO];
2253    }
2254  [textObject setFont: [self font]];
2255  [textObject setAlignment: [self alignment]];
2256  // FIXME: Add base writing direction
2257
2258  [textObject setEditable: [self isEditable]];
2259  [textObject setSelectable: [self isSelectable]];
2260  [textObject setRichText: [self allowsEditingTextAttributes]];
2261  [textObject setImportsGraphics: [self importsGraphics]];
2262  [(NSTextView*)textObject setAllowsUndo: [self allowsUndo]];
2263  attr = [self _nonAutoreleasedTypingAttributes];
2264  [(NSTextView*)textObject setTypingAttributes: attr];
2265  RELEASE(attr);
2266
2267  return textObject;
2268}
2269
2270- (void) _setupTextWithFrame: (NSRect)aRect
2271                      inView: (NSView*)controlView
2272                      editor: (NSText*)textObject
2273                    delegate: (id)anObject
2274                       range: (NSRange)selection
2275{
2276  BOOL needsClipView;
2277  BOOL wraps = [self wraps];
2278  NSTextContainer *ct;
2279  NSSize maxSize;
2280  NSRect titleRect = [self titleRectForBounds: aRect];
2281
2282  /* We always add a clip view if the cell is editable so that the user
2283     can edit the whole contents even if the cell's contents normally is
2284     clipped. */
2285  needsClipView = [self isScrollable] || [self isEditable];
2286  if (needsClipView)
2287    {
2288      NSClipView *cv;
2289
2290      cv = [[NSClipView alloc] initWithFrame: titleRect];
2291      [cv setDocumentView: textObject];
2292      [controlView addSubview: cv];
2293      RELEASE(cv);
2294    }
2295  else
2296    [controlView addSubview: textObject];
2297
2298  /* Note: The order of statements matters here. We must set the text object's
2299     horizontallyResizable and verticallyResizable attributes before setting
2300     its frame size. Otherwise, the text object's width and/or height might
2301     incorrectly be reduced to zero (since the text object has no contents at
2302     this point) if the text object was resizable by its text container before.
2303     Of course we could also have set the text object's minimum size to the
2304     intended frame size, but then we must update the minimum size whenever
2305     the field editor's frame is changed in -_drawEditorWithFrame:inView:.
2306     Note that the minimum size is not relevant when a clip view is used. */
2307  [textObject setMinSize: NSZeroSize];
2308  [textObject setMaxSize: NSMakeSize(1e6, 1e6)];
2309  [textObject setHorizontallyResizable: needsClipView && !wraps];
2310  [textObject setVerticallyResizable: needsClipView];
2311  [textObject setFrame: titleRect];
2312  if (needsClipView)
2313    [textObject setAutoresizingMask: NSViewWidthSizable + NSViewHeightSizable];
2314  else
2315    [textObject setAutoresizingMask: NSViewNotSizable];
2316
2317  /* Note: Order of statements matters again. The heightTracksTextView
2318     and widthTracksTextView container attributes must be set after setting
2319     the horizontallyResizable and verticallyResizable text view attributes
2320     because NSTextView's setter methods include "safety code", which
2321     always updates the container attributes along with the text view
2322     attributes.
2323     FIXME Fix NSTextView to only reset the text container attributes, but
2324     never set them. However note that this may break some sloppily written
2325     code which forgets to set the text container attributes. */
2326  /* See comments in NSStringDrawing.m about the choice of maximum size. */
2327  ct = [(NSTextView*)textObject textContainer];
2328  if (wraps)
2329    maxSize = NSMakeSize(NSWidth(titleRect), 1e6);
2330  else
2331    maxSize = NSMakeSize(1e6, 1e6);
2332  [ct setContainerSize: maxSize];
2333  [ct setWidthTracksTextView: wraps];
2334  [ct setHeightTracksTextView: NO];
2335
2336  [self _updateFieldEditor: textObject];
2337
2338  [textObject setSelectedRange: selection];
2339  [textObject scrollRangeToVisible: selection];
2340
2341  [textObject setDelegate: anObject];
2342  [[controlView window] makeFirstResponder: textObject];
2343  _cell.in_editing = YES;
2344}
2345
2346/**<p>Ends any text editing. This method sets the text object's delegate
2347   to nil, and remove the NSClipView and the text object used for editing</p>
2348 <p>See Also:  -editWithFrame:inView:editor:delegate:event:</p>
2349 */
2350- (void) endEditing: (NSText*)textObject
2351{
2352  NSClipView *clipView;
2353
2354  _cell.in_editing = NO;
2355  [textObject setString: @""];
2356  [textObject setDelegate: nil];
2357
2358  clipView = (NSClipView*)[textObject superview];
2359  if ([clipView isKindOfClass: [NSClipView class]])
2360    {
2361      [clipView setDocumentView: nil];
2362      [clipView removeFromSuperview];
2363    }
2364  else
2365    [textObject removeFromSuperview];
2366}
2367
2368/*
2369 * Editing Text
2370 */
2371/** <p>.This method does nothing if a the <var>controlView</var> is nil,
2372    if text object does not exist or if the cell's type is not <ref type="type"
2373    id="NSCellType">NSTextCellType</ref></p>
2374 */
2375- (void) editWithFrame: (NSRect)aRect
2376                inView: (NSView*)controlView
2377                editor: (NSText*)textObject
2378              delegate: (id)anObject
2379                 event: (NSEvent*)theEvent
2380{
2381  if (!controlView || !textObject || (_cell.type != NSTextCellType))
2382    return;
2383
2384  [self _setupTextWithFrame: aRect
2385        inView: controlView
2386        editor: textObject
2387        delegate: anObject
2388        range: NSMakeRange(0, 0)];
2389
2390  if ([theEvent type] == NSLeftMouseDown)
2391    {
2392      [textObject mouseDown: theEvent];
2393    }
2394}
2395
2396/** <p> This method does nothing if the <var>controlView</var> is nil,
2397    if text object does not exist or if the cell's type is not <ref type="type"
2398    id="NSCellType">NSTextCellType</ref> </p>
2399 */
2400- (void) selectWithFrame: (NSRect)aRect
2401                  inView: (NSView*)controlView
2402                  editor: (NSText*)textObject
2403                delegate: (id)anObject
2404                   start: (NSInteger)selStart
2405                  length: (NSInteger)selLength
2406{
2407  if (!controlView || !textObject || (_cell.type != NSTextCellType))
2408    return;
2409
2410  [self _setupTextWithFrame: aRect
2411        inView: controlView
2412        editor: textObject
2413        delegate: anObject
2414        range: NSMakeRange(selStart, selLength)];
2415}
2416
2417- (BOOL) sendsActionOnEndEditing
2418{
2419  return _cell.sends_action_on_end_editing;
2420}
2421
2422- (void) setSendsActionOnEndEditing: (BOOL)flag
2423{
2424  _cell.sends_action_on_end_editing = flag;
2425}
2426
2427- (BOOL) allowsUndo
2428{
2429  return _cell.allows_undo;
2430}
2431
2432- (void) setAllowsUndo: (BOOL)flag
2433{
2434  _cell.allows_undo = flag;
2435}
2436
2437/*
2438 * Copying
2439 */
2440- (id) copyWithZone: (NSZone*)zone
2441{
2442  NSCell *c = (NSCell*)NSCopyObject (self, 0, zone);
2443
2444  /* Hmmm. */
2445  c->_contents = [_contents copyWithZone: zone];
2446  /* Because of performance issues (and because so the doc says) only
2447     pointers to the objects are copied.  We need to RETAIN them all
2448     though. */
2449  _font = TEST_RETAIN (_font);
2450  _object_value = TEST_RETAIN (_object_value);
2451  _menu = TEST_RETAIN (_menu);
2452  _cell_image = TEST_RETAIN (_cell_image);
2453  _formatter = TEST_RETAIN (_formatter);
2454  _represented_object = TEST_RETAIN (_represented_object);
2455
2456  return c;
2457}
2458
2459/*
2460 * NSCoding protocol
2461 */
2462- (void) encodeWithCoder: (NSCoder*)aCoder
2463{
2464  if ([aCoder allowsKeyedCoding])
2465    {
2466      unsigned long cFlags = 0;
2467      unsigned int cFlags2 = 0;
2468      id contents = _contents;
2469
2470      // encode contents
2471      [aCoder encodeObject: contents forKey: @"NSContents"];
2472
2473      // flags
2474      cFlags |= [self focusRingType];
2475      cFlags |= [self showsFirstResponder] ?  0x4 : 0;
2476      cFlags |= (_action_mask & NSLeftMouseUpMask) ?  0 : 0x20;
2477      cFlags |= [self wraps] ?  0x40 : 0;
2478      cFlags |= (_action_mask & NSLeftMouseDraggedMask) ?  0x100 : 0;
2479      cFlags |= (_action_mask & NSLeftMouseDownMask) ?  0x40000 : 0;
2480      cFlags |= [self isContinuous] ? 0x80000 : 0;
2481      cFlags |= [self isScrollable] ? 0x100000 : 0;
2482      cFlags |= [self isSelectable] ? 0x200000 : 0;
2483      cFlags |= [self isBezeled] ? 0x400000 : 0;
2484      cFlags |= [self isBordered] ? 0x800000 : 0;
2485      cFlags |= ([self type] << 26);
2486      cFlags |= [self isEditable] ? 0x10000000 : 0;
2487      cFlags |= ([self isEnabled] == NO) ? 0x20000000 : 0;
2488      cFlags |= [self isHighlighted] ? 0x40000000 : 0;
2489      cFlags |= ([self state] == NSOnState) ? 0x80000000 : 0;
2490      [aCoder encodeInt: cFlags forKey: @"NSCellFlags"];
2491
2492      // flags part 2
2493      cFlags2 |= ([self usesSingleLineMode] ? 0x40 : 0);
2494      cFlags2 |= (([self allowsUndo] == NO) ? 0x1000 : 0);
2495      cFlags2 |= ([self controlTint] << 5);
2496      cFlags2 |= ([self lineBreakMode] << 9);
2497      cFlags2 |= ([self controlSize] << 17);
2498      cFlags2 |= [self sendsActionOnEndEditing] ? 0x400000 : 0;
2499      cFlags2 |= [self allowsMixedState] ? 0x1000000 : 0;
2500      cFlags2 |= [self refusesFirstResponder] ? 0x2000000 : 0;
2501      cFlags2 |= ([self alignment] << 26);
2502      cFlags2 |= [self importsGraphics] ? 0x20000000 : 0;
2503      cFlags2 |= [self allowsEditingTextAttributes] ? 0x40000000 : 0;
2504      [aCoder encodeInt: cFlags2 forKey: @"NSCellFlags2"];
2505
2506      if (_cell.type == NSTextCellType)
2507        {
2508          // font and formatter.
2509          if ([self font])
2510            {
2511              [aCoder encodeObject: [self font] forKey: @"NSSupport"];
2512            }
2513
2514          if ([self formatter])
2515            {
2516              [aCoder encodeObject: [self formatter] forKey: @"NSFormatter"];
2517            }
2518        }
2519      else if ([self image])
2520        {
2521            [aCoder encodeObject: [self image] forKey: @"NSSupport"];
2522        }
2523    }
2524  else
2525    {
2526      BOOL flag;
2527      unsigned int tmp_int;
2528
2529      [aCoder encodeObject: _contents];
2530      [aCoder encodeObject: _cell_image];
2531      [aCoder encodeObject: _font];
2532      [aCoder encodeObject: _object_value];
2533      flag = _cell.contents_is_attributed_string;
2534      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2535      flag = _cell.is_highlighted;
2536      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2537      flag = _cell.is_disabled;
2538      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2539      flag = _cell.is_editable;
2540      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2541      flag = _cell.is_rich_text;
2542      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2543      flag = _cell.imports_graphics;
2544      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2545      flag = _cell.shows_first_responder;
2546      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2547      flag = _cell.refuses_first_responder;
2548      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2549      flag = _cell.sends_action_on_end_editing;
2550      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2551      flag = _cell.is_bordered;
2552      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2553      flag = _cell.is_bezeled;
2554      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2555      flag = _cell.is_scrollable;
2556      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2557      flag = _cell.is_selectable;
2558      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2559      // This used to be is_continuous, which has been replaced.
2560      /* Ayers 20.03.2003: But we must continue to encode it for backward
2561         compatibility or current releases will have undefined behavior when
2562         decoding archives (i.e. .gorm files) encoded by this version. */
2563      flag = [self isContinuous];
2564      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2565      flag = _cell.allows_mixed_state;
2566      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2567      flag = [self wraps];
2568      [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
2569      tmp_int = _cell.text_align;
2570      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2571      tmp_int = _cell.type;
2572      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2573      tmp_int = _cell.image_position;
2574      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2575      tmp_int = _cell.entry_type;
2576      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2577      // FIXME: State may be -1, why do we encode it as unsigned?
2578      tmp_int = _cell.state;
2579      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2580      tmp_int = _cell.mnemonic_location;
2581      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2582      [aCoder encodeValueOfObjCType: @encode(NSUInteger) at: &_mouse_down_flags];
2583      [aCoder encodeValueOfObjCType: @encode(NSUInteger) at: &_action_mask];
2584      [aCoder encodeValueOfObjCType: @encode(id) at: &_formatter];
2585      [aCoder encodeValueOfObjCType: @encode(id) at: &_menu];
2586      [aCoder encodeValueOfObjCType: @encode(id) at: &_represented_object];
2587
2588      tmp_int = _cell.allows_undo;
2589      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2590      tmp_int = _cell.line_break_mode;
2591      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2592      tmp_int = _cell.control_tint;
2593      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2594      tmp_int = _cell.control_size;
2595      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2596      tmp_int = _cell.focus_ring_type;
2597      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2598      tmp_int = _cell.base_writing_direction;
2599      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2600      tmp_int = _cell.uses_single_line_mode;
2601      [aCoder encodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2602    }
2603}
2604
2605- (id) initWithCoder: (NSCoder*)aDecoder
2606{
2607  if ([aDecoder allowsKeyedCoding])
2608    {
2609      id contents = [aDecoder decodeObjectForKey: @"NSContents"];
2610
2611      // initialize based on content...
2612      if ([contents isKindOfClass: [NSString class]])
2613        {
2614          self = [self initTextCell: contents];
2615        }
2616      else if ([contents isKindOfClass: [NSImage class]])
2617        {
2618          self = [self initImageCell: contents];
2619        }
2620      else
2621        {
2622          self = [self init];
2623          [self setObjectValue: contents];
2624        }
2625
2626      if ([aDecoder containsValueForKey: @"NSCellFlags"])
2627        {
2628          unsigned long cFlags;
2629          NSUInteger mask = 0;
2630          cFlags = [aDecoder decodeIntForKey: @"NSCellFlags"];
2631
2632          [self setFocusRingType: (cFlags & 0x3)];
2633          [self setShowsFirstResponder: ((cFlags & 0x4) == 0x4)];
2634          // This bit flag is the other way around!
2635          if ((cFlags & 0x20) != 0x20)
2636              mask |= NSLeftMouseUpMask;
2637          // This bit flag is the other way around!
2638          [self setWraps: ((cFlags & 0x40) != 0x40)];
2639          if ((cFlags & 0x100) == 0x100)
2640            mask |= NSLeftMouseDraggedMask;
2641          if ((cFlags & 0x40000) == 0x40000)
2642            mask |= NSLeftMouseDownMask;
2643          if ((cFlags & 0x80000) == 0x80000)
2644            mask |= NSPeriodicMask;
2645          [self sendActionOn: mask];
2646          [self setScrollable: ((cFlags & 0x100000) == 0x100000)];
2647          [self setSelectable: ((cFlags & 0x200000) == 0x200000)];
2648          [self setBezeled: ((cFlags & 0x400000) == 0x400000)];
2649          [self setBordered: ((cFlags & 0x800000) == 0x800000)];
2650          [self setType: ((cFlags & 0xC000000) >> 26)];
2651          [self setEditable: ((cFlags & 0x10000000) == 0x10000000)];
2652          // This bit flag is the other way around!
2653          [self setEnabled: ((cFlags & 0x20000000) != 0x20000000)];
2654          [self setHighlighted: ((cFlags & 0x40000000) == 0x40000000)];
2655          [self setState: ((cFlags & 0x80000000) == 0x80000000) ? NSOnState : NSOffState];
2656        }
2657      if ([aDecoder containsValueForKey: @"NSCellFlags2"])
2658        {
2659          int cFlags2;
2660
2661          cFlags2 = [aDecoder decodeIntForKey: @"NSCellFlags2"];
2662	  [self setUsesSingleLineMode: (cFlags2 & 0x40)];
2663          [self setControlTint: ((cFlags2 & 0xE0) >> 5)];
2664	  [self setLineBreakMode: ((cFlags2 & 0xE00) >> 9)];
2665          [self setControlSize: ((cFlags2 & 0xE0000) >> 17)];
2666          [self setSendsActionOnEndEditing: ((cFlags2 & 0x400000) == 0x400000)];
2667          [self setAllowsMixedState: ((cFlags2 & 0x1000000) == 0x1000000)];
2668          [self setRefusesFirstResponder: ((cFlags2 & 0x2000000) == 0x2000000)];
2669          [self setAlignment: ((cFlags2 & 0x1C000000) >> 26)];
2670          [self setImportsGraphics: ((cFlags2 & 0x20000000) == 0x20000000)];
2671          [self setAllowsEditingTextAttributes: ((cFlags2 & 0x40000000) == 0x40000000)];
2672        }
2673      if ([aDecoder containsValueForKey: @"NSSupport"])
2674        {
2675          id support = [aDecoder decodeObjectForKey: @"NSSupport"];
2676
2677          if ([support isKindOfClass: [NSFont class]])
2678            {
2679              [self setFont: support];
2680            }
2681          else if ([support isKindOfClass: [NSImage class]])
2682            {
2683              [self setImage: support];
2684            }
2685        }
2686      if ([aDecoder containsValueForKey: @"NSFormatter"])
2687        {
2688          NSFormatter *formatter = [aDecoder decodeObjectForKey: @"NSFormatter"];
2689
2690          [self setFormatter: formatter];
2691        }
2692    }
2693  else
2694    {
2695      BOOL flag, wraps;
2696      unsigned int tmp_int;
2697      id formatter, menu;
2698      int version = [aDecoder versionForClassName: @"NSCell"];
2699
2700      [aDecoder decodeValueOfObjCType: @encode(id) at: &_contents];
2701      [aDecoder decodeValueOfObjCType: @encode(id) at: &_cell_image];
2702      [aDecoder decodeValueOfObjCType: @encode(id) at: &_font];
2703      [aDecoder decodeValueOfObjCType: @encode(id) at: &_object_value];
2704      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2705      _cell.contents_is_attributed_string = flag;
2706      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2707      _cell.is_highlighted = flag;
2708      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2709      _cell.is_disabled = flag;
2710      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2711      _cell.is_editable = flag;
2712      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2713      _cell.is_rich_text = flag;
2714      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2715      _cell.imports_graphics = flag;
2716      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2717      _cell.shows_first_responder = flag;
2718      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2719      _cell.refuses_first_responder = flag;
2720      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2721      _cell.sends_action_on_end_editing = flag;
2722      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2723      _cell.is_bordered = flag;
2724      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2725      _cell.is_bezeled = flag;
2726      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2727      _cell.is_scrollable = flag;
2728      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2729      _cell.is_selectable = flag;
2730      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2731      // This used to be is_continuous, which has been replaced.
2732      //_cell.is_continuous = flag;
2733      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2734      _cell.allows_mixed_state = flag;
2735      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
2736      /* The wraps attribute has been superseded by lineBreakMode. However,
2737	 we may need it to set lineBreakMode when reading old archives. */
2738      wraps = flag;
2739      [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2740      _cell.text_align = tmp_int;
2741      [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2742      _cell.type = tmp_int;
2743      [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2744      _cell.image_position = tmp_int;
2745      [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2746      _cell.entry_type = tmp_int;
2747      [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2748      _cell.state = tmp_int;
2749      [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2750      _cell.mnemonic_location = tmp_int;
2751      [aDecoder decodeValueOfObjCType: @encode(NSUInteger)
2752                                   at: &_mouse_down_flags];
2753      [aDecoder decodeValueOfObjCType: @encode(NSUInteger) at: &_action_mask];
2754      if (version < 3)
2755        {
2756          unsigned int mask = 0;
2757
2758          // Convert old GNUstep mask value to Cocoa values
2759          if ((_action_mask & 0x1) == 0x1)
2760            {
2761              mask |= NSLeftMouseDownMask;
2762            }
2763          if ((_action_mask & 0x2) == 0x2)
2764            {
2765              mask |= NSLeftMouseUpMask;
2766            }
2767          if ((_action_mask & 0x4) == 0x4)
2768            {
2769              mask |= NSOtherMouseDownMask;
2770            }
2771          if ((_action_mask & 0x8) == 0x8)
2772            {
2773              mask |= NSOtherMouseUpMask;
2774            }
2775          if ((_action_mask & 0x10) == 0x10)
2776            {
2777              mask |= NSRightMouseDownMask;
2778            }
2779          if ((_action_mask & 0x20) == 0x20)
2780            {
2781              mask |= NSRightMouseUpMask;
2782            }
2783          if ((_action_mask & 0x40) == 0x40)
2784            {
2785              mask |= NSMouseMovedMask;
2786            }
2787          if ((_action_mask & 0x80) == 0x80)
2788            {
2789              mask |= NSLeftMouseDraggedMask;
2790            }
2791          if ((_action_mask & 0x100) == 0x100)
2792            {
2793              mask |= NSOtherMouseDraggedMask;
2794            }
2795          if ((_action_mask & 0x200) == 0x200)
2796            {
2797              mask |= NSRightMouseDraggedMask;
2798            }
2799          if ((_action_mask & 0x400) == 0x400)
2800            {
2801              mask |= NSMouseEnteredMask;
2802            }
2803          if ((_action_mask & 0x800) == 0x800)
2804            {
2805              mask |= NSMouseExitedMask;
2806            }
2807          if ((_action_mask & 0x1000) == 0x1000)
2808            {
2809              mask |= NSKeyDownMask;
2810            }
2811          if ((_action_mask & 0x2000) == 0x2000)
2812            {
2813              mask |= NSKeyUpMask;
2814            }
2815          if ((_action_mask & 0x4000) == 0x4000)
2816            {
2817              mask |= NSFlagsChangedMask;
2818            }
2819          if ((_action_mask & 0x8000) == 0x8000)
2820            {
2821              mask |= NSAppKitDefinedMask;
2822            }
2823          if ((_action_mask & 0x10000) == 0x10000)
2824            {
2825              mask |= NSSystemDefinedMask;
2826            }
2827          if ((_action_mask & 0x20000) == 0x20000)
2828            {
2829              mask |= NSApplicationDefinedMask;
2830            }
2831          if ((_action_mask & 0x40000) == 0x40000)
2832            {
2833              mask |= NSPeriodicMask;
2834            }
2835          if ((_action_mask & 0x80000) == 0x80000)
2836            {
2837              mask |= NSCursorUpdateMask;
2838            }
2839          if ((_action_mask & 0x100000) == 0x100000)
2840            {
2841              mask |= NSScrollWheelMask;
2842            }
2843          _action_mask = mask;
2844        }
2845      _action_mask |= NSLeftMouseUpMask;
2846      [aDecoder decodeValueOfObjCType: @encode(id) at: &formatter];
2847      [self setFormatter: formatter];
2848      [aDecoder decodeValueOfObjCType: @encode(id) at: &menu];
2849      [self setMenu: menu];
2850      [aDecoder decodeValueOfObjCType: @encode(id) at: &_represented_object];
2851
2852      if (_formatter != nil)
2853        {
2854          NSString *contents;
2855
2856          contents = [_formatter stringForObjectValue: _object_value];
2857          if (contents != nil)
2858            {
2859              _cell.has_valid_object_value = YES;
2860              ASSIGN (_contents, contents);
2861              _cell.contents_is_attributed_string = NO;
2862            }
2863        }
2864
2865      if (version >= 2)
2866        {
2867          [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2868          _cell.allows_undo = tmp_int;
2869          [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2870          _cell.line_break_mode = tmp_int;
2871          [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2872          _cell.control_tint = tmp_int;
2873          [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2874          _cell.control_size = tmp_int;
2875          [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2876          _cell.focus_ring_type = tmp_int;
2877          [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2878          _cell.base_writing_direction = tmp_int;
2879        }
2880      else
2881	{
2882	  /* Backward compatibility: Derive lineBreakMode from the superseded
2883	     wraps attribute. */
2884	  [self setWraps: wraps];
2885	}
2886
2887      if (version >= 4)
2888	{
2889	  [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &tmp_int];
2890	  _cell.uses_single_line_mode = tmp_int;
2891	}
2892
2893    }
2894  return self;
2895}
2896
2897- (NSUserInterfaceLayoutDirection) userInterfaceLayoutDirection
2898{
2899  // FIXME
2900  return NSUserInterfaceLayoutDirectionLeftToRight;
2901}
2902
2903- (void) setUserInterfaceLayoutDirection: (NSUserInterfaceLayoutDirection)dir
2904{
2905  // FIXME: implement this
2906  return;
2907}
2908
2909- (void) setUsesSingleLineMode: (BOOL)flag
2910{
2911  _cell.uses_single_line_mode = flag;
2912}
2913
2914- (BOOL) usesSingleLineMode
2915{
2916  return _cell.uses_single_line_mode;
2917}
2918
2919@end
2920
2921@implementation NSCell (PrivateMethods)
2922
2923- (NSColor*) textColor
2924{
2925  if (_cell.is_disabled)
2926    return dtxtCol;
2927  else
2928    return txtCol;
2929}
2930
2931/* This method is an exception and returns a non-autoreleased
2932   dictionary, so that calling methods can deallocate it immediately
2933   using release.  Otherwise if many cells are drawn/their size
2934   computed, we pile up hundreds or thousands of these objects before they
2935   are deallocated at the end of the run loop. */
2936- (NSDictionary*) _nonAutoreleasedTypingAttributes
2937{
2938  NSDictionary *attr;
2939  NSColor *color;
2940  NSMutableParagraphStyle *paragraphStyle;
2941
2942  color = [self textColor];
2943  /* Note: There are only a few possible paragraph styles for cells.
2944     TODO: Cache them and reuse them for the whole app lifetime. */
2945  paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
2946  [paragraphStyle setLineBreakMode: [self lineBreakMode]];
2947  [paragraphStyle setBaseWritingDirection: [self baseWritingDirection]];
2948  [paragraphStyle setAlignment: [self alignment]];
2949
2950  attr = [[NSDictionary alloc] initWithObjectsAndKeys:
2951                               _font, NSFontAttributeName,
2952                               color, NSForegroundColorAttributeName,
2953                               paragraphStyle, NSParagraphStyleAttributeName,
2954                               nil];
2955  RELEASE (paragraphStyle);
2956  return attr;
2957}
2958
2959- (NSSize) _sizeText: (NSString*)title
2960{
2961  NSSize size;
2962  NSDictionary *dict;
2963
2964  if (title == nil)
2965    {
2966      return NSMakeSize (0,0);
2967    }
2968
2969  dict = [self _nonAutoreleasedTypingAttributes];
2970  size = [title sizeWithAttributes: dict];
2971  RELEASE (dict);
2972  return size;
2973}
2974
2975/**
2976 * Private internal method, returns an attributed string to display.
2977 */
2978- (NSAttributedString*) _drawAttributedString
2979{
2980  if (!_cell.is_disabled)
2981    {
2982      return [self attributedStringValue];
2983    }
2984  else
2985    {
2986      NSAttributedString *attrStr = [self attributedStringValue];
2987      NSDictionary *attribs;
2988      NSMutableDictionary *newAttribs;
2989
2990      attribs = [attrStr attributesAtIndex: 0
2991                         effectiveRange: NULL];
2992      newAttribs = [NSMutableDictionary dictionaryWithDictionary: attribs];
2993      [newAttribs setObject: [NSColor disabledControlTextColor]
2994                  forKey: NSForegroundColorAttributeName];
2995
2996      return AUTORELEASE([[NSAttributedString alloc]
2997                             initWithString: [attrStr string]
2998                             attributes: newAttribs]);
2999    }
3000}
3001
3002
3003- (BOOL) _shouldShortenStringForRect: (NSRect)titleRect size: (NSSize)titleSize length: (NSUInteger)length
3004{
3005  NSLineBreakMode mode = [self lineBreakMode];
3006
3007  return ((titleSize.width > titleRect.size.width) && (length > 4) &&
3008          (mode == NSLineBreakByTruncatingHead ||
3009           mode == NSLineBreakByTruncatingTail ||
3010           mode == NSLineBreakByTruncatingMiddle));
3011}
3012
3013- (NSAttributedString*) _resizeAttributedString: (NSAttributedString*)attrstring forRect: (NSRect)titleRect
3014{
3015  // Redo string based on selected truncation mask...
3016  NSMutableAttributedString *mutableString = AUTORELEASE([attrstring mutableCopy]);
3017  NSString *ellipsis = @"...";
3018  NSLineBreakMode mode = [self lineBreakMode];
3019  // This code shortens the string one character at a time.
3020  // To speed it up we start off proportional:
3021  CGFloat width = [mutableString size].width;
3022  int cut = MAX(floor([mutableString length] * (width - titleRect.size.width) / width), 4);
3023
3024  do
3025    {
3026      NSRange replaceRange;
3027      if (mode == NSLineBreakByTruncatingHead)
3028        replaceRange = NSMakeRange(0, cut);
3029      else if (mode == NSLineBreakByTruncatingTail)
3030        replaceRange = NSMakeRange([mutableString length] - cut, cut);
3031      else
3032        replaceRange = NSMakeRange(([mutableString length] / 2) - (cut / 2), cut);
3033      [mutableString replaceCharactersInRange: replaceRange withString: ellipsis];
3034      cut = 4;
3035    } while ([mutableString length] > 4 && [mutableString size].width > titleRect.size.width);
3036
3037  // Return the modified attributed string...
3038  return mutableString;
3039}
3040
3041/**
3042 * Private internal method to display an attributed string.
3043 */
3044- (void) _drawAttributedText: (NSAttributedString*)aString
3045                     inFrame: (NSRect)aRect
3046{
3047  NSSize titleSize;
3048
3049  if (aString == nil)
3050    return;
3051
3052  titleSize = [aString size];
3053  if ([self _shouldShortenStringForRect: aRect size: titleSize length: [aString length]])
3054    {
3055      aString = [self _resizeAttributedString: aString forRect: aRect];
3056      titleSize = [aString size];
3057    }
3058
3059  /** Important: text should always be vertically centered without
3060   * considering descender [as if descender did not exist].
3061   * This is particularly important for single line texts.
3062   * Please make sure the output remains always correct.
3063   */
3064  aRect.origin.y = NSMidY (aRect) - titleSize.height/2;
3065  aRect.size.height = titleSize.height;
3066
3067  [aString drawInRect: aRect];
3068}
3069
3070- (void) _drawText: (NSString*)aString  inFrame: (NSRect)cellFrame
3071{
3072  NSSize titleSize;
3073  NSDictionary *attributes;
3074
3075  if (aString == nil)
3076    return;
3077
3078  attributes = [self _nonAutoreleasedTypingAttributes];
3079  titleSize = [aString sizeWithAttributes: attributes];
3080  if ([self _shouldShortenStringForRect: cellFrame size: titleSize length: [aString length]])
3081    {
3082      NSAttributedString *attrstring = AUTORELEASE([[NSAttributedString alloc] initWithString: aString
3083                                                                                   attributes: attributes]);
3084      return [self _drawAttributedText: attrstring inFrame: cellFrame];
3085    }
3086
3087  /** Important: text should always be vertically centered without
3088   * considering descender [as if descender did not exist].
3089   * This is particularly important for single line texts.
3090   * Please make sure the output remains always correct.
3091   */
3092  cellFrame.origin.y = NSMidY (cellFrame) - titleSize.height/2;
3093  cellFrame.size.height = titleSize.height;
3094
3095  [aString drawInRect: cellFrame  withAttributes: attributes];
3096  RELEASE (attributes);
3097}
3098
3099// Private helper method overridden in subclasses
3100- (void) _drawBorderAndBackgroundWithFrame: (NSRect)cellFrame
3101                                    inView: (NSView*)controlView
3102{
3103  NSBorderType aType;
3104
3105  // Get border size
3106  if (_cell.is_bordered)
3107    aType = NSLineBorder;
3108  else if (_cell.is_bezeled)
3109    aType = NSBezelBorder;
3110  else
3111    aType = NSNoBorder;
3112
3113  [[GSTheme theme] drawBorderType: aType frame: cellFrame view: controlView];
3114}
3115
3116// Private helper method
3117- (void) _drawFocusRingWithFrame: (NSRect)cellFrame inView: (NSView*)controlView
3118{
3119  if (_cell.shows_first_responder
3120    && [[controlView window] firstResponder] == controlView)
3121    {
3122      switch (_cell.focus_ring_type)
3123        {
3124          case NSFocusRingTypeDefault:
3125            [[GSTheme theme] drawFocusFrame: [self drawingRectForBounds:
3126                                                       cellFrame]
3127                             view: controlView];
3128            break;
3129          case NSFocusRingTypeExterior:
3130            [[GSTheme theme] drawFocusFrame: cellFrame
3131                             view: controlView];
3132            break;
3133          case NSFocusRingTypeNone:
3134          default:
3135            break;
3136        }
3137    }
3138}
3139
3140- (void) _drawEditorWithFrame: (NSRect)cellFrame
3141		       inView: (NSView *)controlView
3142{
3143  /* Look Ma', no drawing here... */
3144
3145  if ([controlView isKindOfClass: [NSControl class]])
3146    {
3147      /* Adjust the text editor's frame to match cell's frame (w/o border) */
3148      NSRect titleRect = [self titleRectForBounds: cellFrame];
3149      NSText *textObject = [(NSControl*)controlView currentEditor];
3150      NSView *clipView = [textObject superview];
3151
3152      if ([clipView isKindOfClass: [NSClipView class]])
3153	{
3154	  [clipView setFrame: titleRect];
3155	}
3156      else
3157	{
3158	  [textObject setFrame: titleRect];
3159	}
3160    }
3161}
3162
3163- (BOOL) _sendsActionOn:(NSUInteger)eventTypeMask
3164{
3165  return (_action_mask & eventTypeMask);
3166}
3167
3168- (void) _setInEditing: (BOOL)flag
3169{
3170  _cell.in_editing = flag;
3171}
3172
3173- (BOOL) _inEditing
3174{
3175  return _cell.in_editing;
3176}
3177
3178- (void) _updateFieldEditor: (NSText*)textObject
3179{
3180  if (_formatter != nil)
3181    {
3182      NSString *contents;
3183
3184      contents = [_formatter editingStringForObjectValue: _object_value];
3185      if (contents == nil)
3186        {
3187          // We cannot call the stringValue method as this will call
3188          // validateEditing in NSActionCell subclasses
3189          if (nil == _contents)
3190            {
3191              contents = @"";
3192            }
3193	  else
3194	    {
3195	      if (_cell.contents_is_attributed_string == NO)
3196		{
3197		  contents = (NSString *)_contents;
3198		}
3199	      else
3200		{
3201		  contents = [(NSAttributedString *)_contents string];
3202		}
3203	    }
3204        }
3205      if (![contents isEqualToString: [textObject string]])
3206	[textObject setText: contents];
3207    }
3208  else
3209    {
3210      NSString *contents;
3211
3212      if (nil == _contents)
3213        {
3214          contents = @"";
3215        }
3216      else
3217	{
3218	  if (_cell.contents_is_attributed_string == NO)
3219	    {
3220	      contents = (NSString *)_contents;
3221	    }
3222	  else
3223	    {
3224	      contents = [(NSAttributedString *)_contents string];
3225	    }
3226	}
3227      if (![contents isEqualToString: [textObject string]])
3228        {
3229          if (_cell.contents_is_attributed_string == NO)
3230            {
3231              [textObject setText: contents];
3232            }
3233          else
3234            {
3235              // FIXME what about attribute changes?
3236              NSRange range = NSMakeRange(0, [[textObject string] length]);
3237              [textObject replaceCharactersInRange: range
3238                              withAttributedString: (NSAttributedString *)_contents];
3239            }
3240        }
3241    }
3242}
3243
3244/**
3245 * Private method used by NSImageCell and NSButtonCell for calculating
3246 * scaled image size
3247 */
3248
3249static inline NSSize
3250scaleProportionally(NSSize imageSize, NSSize canvasSize, BOOL scaleUpOrDown)
3251{
3252  CGFloat ratio;
3253
3254  if (imageSize.width <= 0
3255      || imageSize.height <= 0)
3256    {
3257      return NSMakeSize(0, 0);
3258    }
3259
3260  /* Get the smaller ratio and scale the image size by it.  */
3261  ratio = MIN(canvasSize.width / imageSize.width,
3262	      canvasSize.height / imageSize.height);
3263
3264  /* Only scale down, unless scaleUpOrDown is YES */
3265  if (ratio < 1.0 || scaleUpOrDown)
3266    {
3267      imageSize.width *= ratio;
3268      imageSize.height *= ratio;
3269    }
3270
3271  return imageSize;
3272}
3273
3274- (NSSize) _scaleImageWithSize: (NSSize)imageSize
3275                   toFitInSize: (NSSize)canvasSize
3276		   scalingType: (NSImageScaling)scalingType
3277{
3278  NSSize result;
3279  switch (scalingType)
3280    {
3281    case NSImageScaleProportionallyDown: // == NSScaleProportionally
3282      {
3283	result = scaleProportionally (imageSize, canvasSize, NO);
3284	break;
3285      }
3286    case NSImageScaleAxesIndependently: // == NSScaleToFit
3287      {
3288	result = canvasSize;
3289	break;
3290      }
3291    default:
3292    case NSImageScaleNone: // == NSScaleNone
3293      {
3294	result = imageSize;
3295	break;
3296      }
3297    case NSImageScaleProportionallyUpOrDown:
3298      {
3299	result = scaleProportionally (imageSize, canvasSize, YES);
3300	break;
3301      }
3302    }
3303  return result;
3304}
3305
3306@end
3307