This is MiscArrowButtonCell.m in view mode; [Download] [Up]
/*
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.
*******************************************************************/
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.