
/*
   MiscArrowButtonCell.m

   This object is included in the MiscKit by permission from the author
   and its use is governed by the MiscKit license, found in the file
   "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
   for a list of all applicable permissions and restrictions.
*/

#import <AppKit/AppKit.h>
#import "wraps.h"

#import "MiscArrowButtonCell.h"

// These #defines are used for versioning instances of this class, so that
// you will always be able to read any previously archived version. If you
// make changes to the class's ivars, bump the version number up one and 
// make the appropriate changes to the initWithCoder: method. I left the
// version number the same as it's pre-OpenStep days because no ivars
// changed. It's been tested and it will read nibs with pre-OpenStep 
// MiscArrowButton instances in them.

#define MISC_ABC_VERSION 1
#define MISC_ABC_CLASSNAME "MiscArrowButtonCell"

// Hack to stop the compiler from complaining. I used to import
// <InterfaceBuilder/InterfaceBuilder.h> but that's not good either.
// This is used is drawWithFrame:inView:.
@interface NSApplication (StopComplainingCompiler)
- (BOOL) isTestingInterface;
@end


@implementation MiscArrowButtonCell

/*"
   With the help of MiscArrowButton, this is a recreation of the
   button (well, it was a View there) from Websters.app (in the
   Preferences panel). It's basically made to be used off of
   it's palette but you could always use it programmatically
   if you so choose.

   There's just a few differences from using an NSButtonCell:
   I created a new designated initializer 
   #initTextCell:alternateTitle:, since it's convenient 
   to be able to set both sides of the button at once. Also, since
   stringValue does not return anything useful (the string "0"
   or "1" depending upon it's current state), I made it return the
   the selected title instead.
"*/


+ (void) initialize
/*"
   Sets our class version for archiving purposes.
"*/
{
    if (self == [MiscArrowButtonCell class]) {
		[self setVersion:MISC_ABC_VERSION];
    }
}


- (id) init
/*"
   Calls our designated initializer (#initTextCell:alternateTitle:) with default
   titles ("Left" and "Right").
"*/   
{
	return [self initTextCell:@"Left" alternateTitle:@"Right"];
}


- (id) initTextCell:(NSString*)aString
/*"
   Calls our designated initializer. aString will become our left title. The
   right title will be set to "Right".
"*/
{
	return [self initTextCell:aString alternateTitle:@"Right"];
}


- (id) initTextCell:(NSString*)aString alternateTitle:(NSString*)altString
/*"
   Our designated initializer. Sets both our title and alternate title.
"*/
{
    BOOL error = ([super initTextCell:aString] == nil);
	
    if (!error) {
        [self setAlternateTitle:altString];
        [self setState:0];
        [self setBordered:NO];
        [self setShowsStateBy:NSNoCellMask];
		// We don't want to highlight ourself.
        [self setHighlightsBy:NSNoCellMask];
        [self setArrowAlignment:MiscArrowAlignmentAbsolute];
		// We should probably override type to always be toggle.
        [self setType:NSToggleButton];
    }
    
	return error ? nil : self;
}


- (NSString*) stringValue
/*"
   Since #stringValue in Button does not do much but return a string with
   a "0" or "1" depending upon our state, I overrode it to return the 
   currently selected title.
"*/
{
    return ([self state] == 0) ? [self title] : [self alternateTitle];
}


- (MiscArrowAlignment) arrowAlignment
/*"
   Returns MiscArrowAlignmentAbsolute or MiscArrowAlignmentRelative depending
   upon how alignment is currently being handled. See #setArrowAlignment: for
   more details.
"*/
{
    return arrowAlignment;
}


- (void) setArrowAlignment:(MiscArrowAlignment)alignment
/*"
   Sets the alignment of the arrow. MiscArrowAlignmentAbsolute aligns
   the arrow in the center of the cellFrame, where MiscArrowAlignmentRelative
   centers the arrow between the left and right titles.
"*/
{
    arrowAlignment = alignment;
}


- (NSSize) cellSizeForBounds:(NSRect)theRect
/*"
   I believe this method is supposed to calculate the minimum size needed to
   fit the currently displayed contents  (contents, altContents, and the arrow)
   in the cellframe.
"*/
{  
    NSSize theSize;
    float contentsWidth = [[self font] widthOfString:[self title]];
    float altContentsWidth = [[self font] widthOfString:[self alternateTitle]];
  
	// first calculate the width needed to draw all text and the arrow
	  
  	theSize.width = 0.0;
	if ([self arrowAlignment] == MiscArrowAlignmentRelative) {
		theSize.width += contentsWidth;
		theSize.width += altContentsWidth;
    }
    else {	// absolute alignment
        if (contentsWidth > altContentsWidth) {
			theSize.width += contentsWidth * 2.0;
        }
        else {
			theSize.width += altContentsWidth * 2.0;
        }
    }
	 
	theSize.width += cellHeight + 10.0;					
	
	// now the height (which will usually be cellHeight)
	
	theSize.height = cellHeight;
	
    if ([ [self font] pointSize] > cellHeight) {
		theSize.height = [ [self font] pointSize];
    }
	 		
	return theSize;
}


- (NSRect) titleRectForBounds:(NSRect)theRect
/*"
   As far as I can tell (which may not be all that far) is that this
   method is only used by the NSButton's IBEditor to tell how large
   the editor should be.
"*/
{
    float size = [ [self font] pointSize];
    float theY;
    float maxWidth = theRect.size.width/2.0;
				
    // this is a hack so you can double click on the altTitle in IB

    if ([self state] == 1) {
		return [self alternateTitleRectForBounds:theRect];
    }

	[self setAlignment:NSLeftTextAlignment];	// used for the IBEditor
		
	theY = theRect.origin.y + theRect.size.height/2.0 - size/2.0;
	
    if ([self arrowAlignment] == MiscArrowAlignmentRelative) {
	  	maxWidth = [[self font] widthOfString:[self title]] + 10.0;  	
    }
		
	theRect = NSMakeRect(theRect.origin.x, theY-2.0, maxWidth, size+2.0);		   	

	return theRect;
}


- (NSRect) alternateTitleRectForBounds:(NSRect)theRect
/*"
   If the state is 1 (altContents selected), then this method will be
   called. It returns the location for editor to appear when editing
   the altContents. NOTE: This stopped working with OpenStep for Mach
   Prerelease 2. The button's IBEditors have changed.
"*/
{
    float size = [[self font] pointSize];
    float newX;
    float newY;
    float newWidth;
	
	[self setAlignment:NSRightTextAlignment];	// used for the IBEditor

	newX = theRect.origin.x + theRect.size.width/2.0; 			 
	newY = theRect.origin.y + theRect.size.height/2.0 - size/2.0;
	newWidth = theRect.size.width/2.0;
	
	return NSMakeRect(newX, newY-2.0, newWidth, size+2.0);		   	
}


- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView
/*"
   This method is used to draw only the parts of the cell that
   do change when the state changes. Therefore, only the black arrow
   and the text are drawn here.
"*/
{
    float  gray;
    float  size = [ [self font] pointSize];
		    
	// draw the arrow

    if ([self arrowAlignment] == MiscArrowAlignmentRelative) {
        NSRect  relativeFrame;
        float  contentsWidth = [[self font] widthOfString:[self title]];
        float  altContentsWidth = [[self font] widthOfString:[self alternateTitle]];

		// since it the arrow is drawn relative to the text, a little
		// calculation is needed
		
		relativeFrame.origin.x = cellFrame.origin.x + contentsWidth;
		relativeFrame.size.width = cellFrame.size.width - 
				(contentsWidth + altContentsWidth);

		PSABdrawarrow (relativeFrame.origin.x, cellFrame.origin.y + 3.0, 
				relativeFrame.size.width, cellFrame.size.height - 6.0, 
				(int)[self state], [self isEnabled]);
    }

    // draw the arrow in the center of the cellFrame
    else {
		PSABdrawarrow (cellFrame.origin.x, cellFrame.origin.y + 3.0, 
				cellFrame.size.width, cellFrame.size.height - 6.0, 
				(int)[self state], [self isEnabled]);
    }

    // draw the left and right hand text
	
	[[self font] set];
			
    if ([self title] != nil) {
        float  theY;

        gray = ([self state] || ![self isEnabled]) ? NSDarkGray : 0.0;

		// calculate the placement of the contents and print it
		theY = cellFrame.origin.y + cellFrame.size.height/2.0 + size/3.0;			
		PSABshowstring (cellFrame.origin.x + 3.0, theY, gray, [[self title] cString]);
    }
		 
	if ([self alternateTitle] != nil) {	
        float  theX, theY;
        float  strWidth = [[self font] widthOfString:[self alternateTitle]];
	  			
        gray = ([self state] && [self isEnabled]) ? 0.0 : NSDarkGray;
		
		// calculate the placement of the altContents and print it		
		theX = cellFrame.origin.x+(cellFrame.size.width - strWidth - 3.0);
		theY = cellFrame.origin.y + cellFrame.size.height/2.0 + size/3.0;
					
		PSABshowstring (theX, theY, gray, [[self alternateTitle] cString]);		 	
    }
}


- (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
/*"
   This part of the drawing displays only the parts that don't change
   often, which includes the diamond that encloses the arrow, and the
   border when I get around to adding one.
"*/
{		  
	// if transparent draw the background white (when in IB) and 
	// same as the background when in an app (or testing interface)
	
    if ([self isTransparent]) {

        if ([NSApp respondsToSelector:@selector(isTestingInterface)]) {
            if ([NSApp isTestingInterface]) {
				PSsetgray (NSLightGray);
            }
            else {
				PSsetgray (1.0);
            }
        }
        else {
            PSsetgray (NSLightGray);
        }
		
		NSRectFill(cellFrame);
		return;
	 }
	
	// set cellHeight since some of the calculations that are in other
	// methods have to know the height of the cell (this is probably the
	// wrong way to go about this)
	
	cellHeight = cellFrame.size.height;
	
	// draw the border or bezel, then call drawInside
	
    if ([self isBordered]) {
		NSDrawButton(cellFrame , cellFrame);
    }
		
	PSgsave();

	// have to flip drawing if we are drawing into a flipped view
    if ([controlView isFlipped]) {
		PSABflipme (cellFrame.size.height + (cellFrame.origin.y * 2.0));
    }
	
	if ([self arrowAlignment] == MiscArrowAlignmentRelative) {
        NSRect  relativeFrame;
        float  contentsWidth = [[self font] widthOfString:[self title]];
        float  altContentsWidth = [[self font] widthOfString:[self alternateTitle]];

        relativeFrame.origin.x = cellFrame.origin.x + contentsWidth;
		relativeFrame.size.width = cellFrame.size.width - 
				(contentsWidth + altContentsWidth);
		
		PSABdrawdiamond (relativeFrame.origin.x, cellFrame.origin.y + 3.0,
				relativeFrame.size.width, cellFrame.size.height - 6.0);
    }
    else {
		PSABdrawdiamond (cellFrame.origin.x, cellFrame.origin.y + 3.0, 
			cellFrame.size.width, cellFrame.size.height - 6.0);
    }
    
	PSgrestore();				

	// draw the rest of the cell
	[self drawInteriorWithFrame:cellFrame inView:controlView];
}


// Archiving methods

- (id) initWithCoder:(NSCoder *)aDecoder
/*"
   With luck this will still unarchive pre-OpenStep palettized code.
"*/
{
	int  version;
	
	[super initWithCoder:aDecoder];
	version = [aDecoder versionForClassName:[NSString stringWithCString:MISC_ABC_CLASSNAME]];
	
	switch (version) {
        case 0:
            // First version had no ivars, but set any new ivars to a useable
            // value.
            arrowAlignment = MiscArrowAlignmentAbsolute;	
            break;

        case 1:
            // Current version.
            [aDecoder decodeValueOfObjCType:@encode(unsigned int) at:&arrowAlignment];
            break;

        default:
            break;
	 }

	return self;
}


- (void) encodeWithCoder:(NSCoder *)aCoder
/*"
	Encodes our instance variables.
"*/
{
	[super encodeWithCoder:aCoder];
	
	// You can also keep versioning here just in case you would like 
	// to back to an older version, but I don't.
	[aCoder encodeValueOfObjCType:@encode(unsigned int) at:&arrowAlignment];
}

@end


/******************************************************************
  CHANGES:
   1. Now use setArrowAlignment/arrowAlignment to set/get whether the
 	 the arrow is relative or absolutely aligned. Added arrowAlignment
     instance var.

   October 1, 1994:
   2. Added archive versioning (+initialize).

   May 16, 1996
   3. Converted to OpenStep and cleaned up somewhat.

   August 4, 1996
   4. Cleaned up a little more and touched up the autodoc comments.
   
 *******************************************************************/
 
