This is MiscClipTFCell.m in view mode; [Download] [Up]
//
// MiscClipTextFieldCell.h -- a cell for displaying long string values
// Written and Copyright (c) 1995 by Balazs Pataki.
// Version 1.0. All rights reserved.
//
// This notice may not be removed from this source code.
//
// 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 <misckit/MiscString.h>
#import <objc/objc-runtime.h>
#import "MiscClipTextFieldCell.h"
#define CLASS_NAME "MiscClipTextFieldCell"
#define CLASS_VERSION 1
#define DEFAULTCLIPPER "..."
#define DELIMITERS [delimiters stringValue]
// To make texts displayed in cells looking good inside views, the maximum
// width of a cell is 4.0 points (o whatever) less than the width of the view
// the cell is displayed in. So, we need this little adjustment (am I right?
// - maybe not but at least it works)
#define CELL_ADJUSTMENT 4.0
#define BEZELED_ADJUSTMENT 5.0
#define BORDERED_ADJUSTMENT 4.0
#define FLUSH [[[control window] reenableFlushWindow] flushWindow];
@interface MiscClipTextFieldCell (Private)
- _clipStringValue:(const char *)aString;
- _copyClipperObj:obj fromZone:(NXZone *)zone;
- _copyDelimitersObj:obj fromZone:(NXZone *)zone;
- _copyFullStringObj:obj fromZone:(NXZone *)zone;
@end
/*
********************************
* *
* MiscClipTextFieldCell *
* *
********************************
*/
@implementation MiscClipTextFieldCell
+ initialize
// Set class version
{
if (self == objc_lookUpClass(CLASS_NAME)) {
[self setVersion:CLASS_VERSION];
}
return self;
}
- init;
// Initializes a newly allocated MiscClipTextFieldCell with default values,
// that is the clipper string to "...", uses no delimiters when clipping, and
// clipping happens on the right. The cell's other attributes are set for
// displaying text only (no scroll, selection allowed).
{
return [self initTextCell:""];
}
- initTextCell:(const char*)aString;
{
[super initTextCell:aString];
clipper = [[MiscString allocFromZone:[self zone]]
initString:DEFAULTCLIPPER];
fullString = [[MiscString allocFromZone:[self zone]] init];
delimiters = nil; /* As a deafult we don't have delimiters*/
clipOnRight = YES; /* and clipping happens on the right */
clipEnabled = YES;
[self setDelegate:self];
// MiscClipField is initialized for only displaying its string value
[self setBezeled:YES];
[self setEditable:NO];
[self setScrollable:NO];
[self setBackgroundGray:NX_LTGRAY];
return self;
}
- copyFromZone:(NXZone *)zone
{
id obj = [super copyFromZone:zone];
[obj _copyClipperObj:clipper fromZone:zone];
[obj _copyDelimitersObj:delimiters fromZone:zone];
[obj _copyFullStringObj:fullString fromZone:(NXZone *)zone];
return obj;
}
- free
{
[fullString free];
[clipper free];
if (delimiters)
[delimiters free];
return [super free];
}
- setDelegate:anObject
{
delegate = anObject;
return self;
}
- setClipOnRight:(BOOL) flag
// If flag is YES clipping happens on the right, otherwise on the left of the
// string in the cell
{
clipOnRight = flag;
return self;
}
- setClipperString:(const char*) aString
// Sets `aString' as the string that is displayed in place of the clipped part
// of the original string
{
if (clipper)
[clipper setStringValue:aString];
else
clipper = [MiscString newWithString:aString];
return self;
}
- setClipDelimiters:(const char*) delimChars
// Sets `delimChars' as delimiters by which the clipping has to happen
{
if (delimiters)
[delimiters setStringValue:delimChars];
else
delimiters = [MiscString newWithString:delimChars];
return self;
}
- setClipEnabled:(BOOL) flag
// Sets whether the next `setStringValue:' message should clip the text or not
{
clipEnabled = flag;
return self;
}
- setStringValue:(const char *)aString
{
if ([fullString cmp:aString] != 0)
[fullString setStringValue:aString];
if ( clipEnabled )
return [self _clipStringValue:aString];
return [super setStringValue:aString];
}
- takeStringValueFrom:sender
{
[self setStringValue:[sender stringValue]];
return self;
}
- resetStringValue:sender
{
if ( clipEnabled )
return [self _clipStringValue:[self fullStringValue]];
return [super setStringValue:[self fullStringValue]];
}
- (const char*) fullStringValue { return [fullString stringValue]; }
- (BOOL) isClipEnabled { return clipEnabled; }
- (BOOL) doesClipOnRight { return clipOnRight; }
- clipper { return clipper; }
- delimiters { return delimiters; }
- delegate { return delegate; }
- (BOOL) isWrapped
// Returns yes if the Cell wraps the text by word
{
return ( cFlags2.noWrap ? NO : YES );
}
- write:(NXTypedStream *)stream
{
[super write:stream];
NXWriteObject(stream, fullString);
NXWriteObject(stream, clipper);
NXWriteObject(stream, delimiters);
NXWriteObject(stream, delegate);
NXWriteType(stream, @encode(BOOL), &clipOnRight);
NXWriteType(stream, @encode(BOOL), &clipEnabled);
return self;
}
- read:(NXTypedStream *)stream
{
[super read:stream];
fullString = NXReadObject(stream);
clipper = NXReadObject(stream);
delimiters = NXReadObject(stream);
delegate = NXReadObject(stream);
NXReadType(stream, @encode(BOOL), &clipOnRight);
NXReadType(stream, @encode(BOOL), &clipEnabled);
return self;
}
- awake
// Does nothing but with speed :-)
{
return self;
}
@end
/*
********************************
* *
* MiscClipTextField(Private) *
* *
* -- Private Methods -- *
* *
********************************
*/
@implementation MiscClipTextFieldCell (Private)
- _clipStringValue:(const char *)aString
// A very long story...
//
{
id control = nil;
id temp = nil;
id mainFont = [self font];
id scrnFont = [mainFont screenFont];
id theFont = (scrnFont ? scrnFont : mainFont); // try to use screen font
float clipperWidth = 0;
float clippedWidth = 0;
float cellWidth = 0;
BOOL doClip = NO;
BOOL usingDelimiters = NO;
NXRect biggestRect;
NXSize size;
int i=0;
if (!aString)
return self;
/* More initializing */
if (![clipper stringValue])
[clipper setStringValue:DEFAULTCLIPPER];
clipperWidth = [theFont getWidthOf:[clipper stringValue]];
temp = [[MiscString alloc] initString:aString];
usingDelimiters = ( delimiters && ![delimiters emptyString] ? YES : NO );
// Figure out the initial cell width (the biggest possible cell width)
// [and do some necessary adjustment too]
control = [self controlView];
/* Matrix will respond ... */
if ( [control respondsTo:@selector(getCellSize:)] ) {
NXSize size;
[control getCellSize:&size];
cellWidth = size.width;
biggestRect.size.width = size.width;
biggestRect.size.height = size.height;
}
else {
[control getFrame:&biggestRect];
cellWidth = NX_WIDTH(&biggestRect);
}
cellWidth -= CELL_ADJUSTMENT;
if ([self isBezeled])
cellWidth -= BEZELED_ADJUSTMENT;
else if ([self isBordered])
cellWidth -= BORDERED_ADJUSTMENT;
// Configuring the cell
[self setSelectable:NO]; /* Disable selecting and scrolling */
[self setScrollable:NO];
[[control window] disableFlushWindow];
// Check whether we need to clip or not. If we do give delegate an
// opportunity to clip the string in his custom way. If `temp's string
// value is still long we do the real clipping. I know its not really
// sexy doing this with a for loop but ...
for (i=1; i<=2; i++) {
doClip = NO;
if ([theFont getWidthOf:[temp stringValue]] > cellWidth)
doClip = YES; /* aString is too wide so do the clip */
else { /* Try the "trick" */
/* Set the clipped string value */
[super setStringValue:[temp stringValue]];
/* Calc how much space it needed to be */
/* displayed */
[self calcCellSize:&size inRect:&biggestRect];
/* It didn't fit so its broken into many*/
/* lines (ie: the cell got higher) */
if ((size.height > NX_HEIGHT(&biggestRect)))
doClip = YES; /* Do the clip */
}
if (i==1 && doClip)
[delegate stringWillBeClipped:temp];
}
// Clip on the right if `aString' is too long
if ( doClip && clipOnRight ) {
BOOL cutMore=NO; // This becomes true if text doesn't fits into
// the cell in the first round
/* The loop cuts characters from the end and checks */
/* wheter it's short enough to be displayed */
while (YES) {
clippedWidth=[theFont getWidthOf:[temp stringValue]];
/* If the string is still too long */
if ( ((clippedWidth+clipperWidth) >= cellWidth) || cutMore) {
[temp removeFrom:[temp length]-1 length:1];
if ( usingDelimiters ) {
/* No delimiter found means we reached the */
/* last delimited string element and it */
/* can't be shortened anymore because there*/
/* is no "next" delimter backwards */
int from = [temp rspotOfChars:DELIMITERS];
if ( from == -1 )
[temp replace:[temp stringValue] with:""];
else
[temp removeFrom:from+1 length:[temp length]-1];
}
/* Last element reached and still long */
if ([temp emptyString]) {
[temp setStringValue:[clipper stringValue]];
FLUSH;
return [super setStringValue:[temp stringValueAndFree]];
}
cutMore = NO;
} //End if (still too long)
else {
[temp concatenate:clipper];
/* Set the clipped string value */
[super setStringValue:[temp stringValue]];
/* Calc how much space it needed to be */
/* displayed */
[self calcCellSize:&size inRect:&biggestRect];
/* It didn't fit so its broken into two */
/* lines (ie: the cell got higher) */
if ( size.height > NX_HEIGHT(&biggestRect) ){
cutMore = YES; /* Force cutting some more */
/* Remove clipper string from end */
[temp removeFrom:([temp length]-[clipper length])
length:[clipper length]];
}
else { /* Finished, string value can be displyed */
[temp free];
FLUSH;
return self;
}
} // End else (check if string value can really be displayed)
} // End of string shortening loop
}//End if (clipOnRight)
// Clip on the left if `aString' is too long
// (This code is nearly identical to the right clipping one, except that
// it scans the string from left to right. Can you see any way to merge
// these two in a meaningful way?)
if ( doClip && !clipOnRight ) {
BOOL cutMore=NO; // This becomes true if text doesn't fits into
// the cell in the first round
/* The loop cuts characters from the beg. of temp */
/* and checks wheter it's short enough to be */
/* displayed */
while ( YES ) {
// This sometimes lies, maybe because of the screenfont
// - printerfont differency
clippedWidth=[theFont getWidthOf:[temp stringValue]];
/* If string is still too long or we are */
/* forced to cut more ... */
if ( ((clippedWidth+clipperWidth) >= cellWidth) || cutMore) {
[temp removeFrom:0 length:1];
if ( usingDelimiters ) {
int to=[temp spotOfChars:DELIMITERS];
/* No delimiters found means we reached the */
/* last delimited string element and it */
/* can't be shortened anymore because there*/
/* is no "next" delimter forward */
if (to == -1)
[temp replace:[temp stringValue] with:""];
else
[temp removeFrom:0 to:to-1];
}
/* Last element reached and still long */
if ([temp emptyString]) {
[temp setStringValue:[clipper stringValue]];
FLUSH;
return [super setStringValue:[temp stringValueAndFree]];
}
cutMore = NO; /* Turn forcing cutting more off */
} //Enf if (still too long)
else {
/* Insert clipper */
[temp insertString:clipper];
/* Set the clipped string value */
[super setStringValue:[temp stringValue]];
/* Calc how much space it needed to be */
/* displayed */
[self calcCellSize:&size inRect:&biggestRect];
if ( size.height > NX_HEIGHT(&biggestRect) ){
/* It didn't fit so its broken into many*/
/* lines (ie: the cell got higher) */
cutMore = YES; /* Force cutting some more */
/* Remove clipper string from front */
[temp removeFrom:0 length:[clipper length]];
}
else { /* Finished, string value can be displyed*/
[temp free];
FLUSH;
return self;
}
} // End else (check if string value can really be displayed)
} // End of string shortening loop
}//End if (clip on left)
FLUSH;
return [super setStringValue:[temp stringValueAndFree]];
}
- _copyClipperObj:obj fromZone:(NXZone *)zone
{
id newClipper = [obj copyFromZone:zone];
clipper = newClipper;
return self;
}
- _copyDelimitersObj:obj fromZone:(NXZone *)zone
{
id newDelim = [obj copyFromZone:zone];
delimiters = newDelim;
return self;
}
- _copyFullStringObj:obj fromZone:(NXZone *)zone
{
id newString = [obj copyFromZone:zone];
fullString = newString;
return self;
}
@end
@implementation Object (MiscClipDelegate)
- stringWillBeClipped:theString { return self; }
@end
@implementation MiscClipTextFieldCell(IBStuff)
- (const char *)getInspectorClassName
// Return the class name of our inspector.
{
return "MiscClipTextFieldInspector";
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.