This is MiscMatrix.m in view mode; [Download] [Up]
//
// MiscMatrix.m -- a class to implement variable-sized matrices
// Written by Mike Ferris Copyright (c) 1994 by Mike Ferris.
// Modified from original MOKit "MOMatrix" class by Don Yacktman.
// 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.
//
// MiscMatrix is a subclass of Matrix that allows independantly sizable
// rows and columns. Each row can have a different height and each column
// can have a different width.
/*
* Revision History:
* July 1994: Steve Quirk steveq@telerate.com
* - implemented sizeToFit method
* - fixed bugs in addRow, addCol methods
* - fixed bugs in initFrame:&etc&etc methods
* - improved performance when changing row/column size
* Aug 1994: Steve Quirk
* - improved drawing performance
* - improved getRow:andCol:forPoint: performance
* Sep 1994: Scott Violet
* - improved drawing performance; Scott's note:
* In the code for drawSelf:: Mike chose to call drawCellAt::
* which is very expensive, this will result in needless
* lockFocus: and unlockFocus: calls, and this will make it
* so that when the matrix is sent printPSCode: it won't work
* (it tries to print it flipped).
*/
#import <misckit/MiscMatrix.h>
#import <objc/List.h>
#import <objc/objc-runtime.h>
#define CLASS_VERSION 0
#define CLASS_NAME "MiscMatrix"
// These are the private methods we use in MiscMatrix
@interface MiscMatrix(private)
- _Misc_copyRowSizes:rs andColSizes:cs zone:(NXZone *)zone;
- _Misc_moveColumnsRightBy:(NXCoord)difference startingAt:(int)col;
- _Misc_moveRowsDownBy:(NXCoord)difference startingAt:(int)row;
@end
@implementation MiscMatrix
+ initialize
// Set the class version
{
if (self == objc_lookUpClass(CLASS_NAME)) {
[self setVersion:CLASS_VERSION];
}
return self;
}
- setupStorage:(int)rowsHigh :(int)colsWide
// Set up our storage objects
{
int i;
MiscColumnSize newCSize;
MiscRowSize newRSize;
columnSizes = [[Storage allocFromZone:[self zone]] initCount:0
elementSize:sizeof(MiscColumnSize)
description:MISC_COLUMNSIZE_DESC];
[columnSizes setAvailableCapacity:(colsWide > 2) ? colsWide : 2];
rowSizes = [[Storage allocFromZone:[self zone]] initCount:0
elementSize:sizeof(MiscRowSize)
description:MISC_ROWSIZE_DESC];
[rowSizes setAvailableCapacity:(rowsHigh > 2) ? rowsHigh : 2];
newCSize.width = cellSize.width;
newRSize.height = cellSize.height;
for (i=0; i<colsWide; i++) {
newCSize.x = bounds.origin.x + i * (cellSize.width + intercell.width);
[columnSizes addElement:&newCSize];
}
for (i=0; i<rowsHigh; i++) {
newRSize.y = bounds.origin.y + i * (cellSize.height + intercell.height);
[rowSizes addElement:&newRSize];
}
return self;
}
- initFrame:(const NXRect *)frm mode:(int)aMode prototype:cellId
numRows:(int)rowsHigh numCols:(int)colsWide
// Designated initializer override from Matrix. Sets up our storage stuff.
{
[super initFrame:frm mode:aMode prototype:cellId numRows:rowsHigh
numCols:colsWide];
[self setupStorage:rowsHigh :colsWide];
return self;
}
- initFrame:(const NXRect *)frm mode:(int)aMode cellClass:factoryId
numRows:(int)rowsHigh numCols:(int)colsWide
// Designated initializer override from Matrix. Sets up our storage stuff.
{
[super initFrame:frm mode:aMode cellClass:factoryId numRows:rowsHigh
numCols:colsWide];
[self setupStorage:rowsHigh :colsWide];
return self;
}
- _Misc_copyRowSizes:rs andColSizes:cs zone:(NXZone *)zone
{
rowSizes = [rs copyFromZone:zone];
columnSizes = [cs copyFromZone:zone];
return self;
}
- copyFromZone:(NXZone *)zone
{
id obj = [super copyFromZone:zone];
[obj _Misc_copyRowSizes:rowSizes andColSizes:columnSizes zone:zone];
return obj;
}
- free
// free the storage
{
[columnSizes free];
[rowSizes free];
return [super free];
}
- _Misc_moveColumnsRightBy:(NXCoord)difference startingAt:(int)col
// A private method used by the methods which cause sizing stuff to change
{
MiscColumnSize *cSize;
int i;
if ((col < 0) || (col >= numCols)) {
return nil;
}
cSize = (MiscColumnSize *)[columnSizes elementAt:col];
for (i=col; i<numCols; i++, cSize++)
cSize->x += difference;
return self;
}
- _Misc_moveRowsDownBy:(NXCoord)difference startingAt:(int)row
// A private method used by the methods which cause sizing stuff to change
{
MiscRowSize *rSize;
int i;
if ((row < 0) || (row >= numRows)) {
return nil;
}
rSize = (MiscRowSize *)[rowSizes elementAt:row];
for (i=row; i<numRows; i++, rSize++)
rSize->y += difference;
return self;
}
- setWidth:(NXCoord)newWidth ofCol:(int)col
// This method allows the setting of column widths
{
NXCoord diff;
MiscColumnSize *cSize;
if ((col < 0) || (col >= numCols)) {
return nil;
}
cSize = (MiscColumnSize *)[columnSizes elementAt:col];
diff = newWidth - cSize->width;
cSize->width = newWidth;
return [self _Misc_moveColumnsRightBy:diff startingAt:col+1];
}
- setHeight:(NXCoord)newHeight ofRow:(int)row
// This method allows the setting of row heights
{
NXCoord diff;
MiscRowSize *rSize;
if ((row < 0) || (row >= numRows)) {
return nil;
}
rSize = (MiscRowSize *)[rowSizes elementAt:row];
diff = newHeight - rSize->height;
rSize->height = newHeight;
return [self _Misc_moveRowsDownBy:diff startingAt:row+1];
}
- sizeToCells
// Resize the matrix to the proper size to fit all our cells.
{
NXRect rect;
MiscColumnSize *cSize;
MiscRowSize *rSize;
[self getFrame:&rect];
if (numCols == 0) {
rect.size.width = 0.0;
} else {
cSize = (MiscColumnSize *)[columnSizes lastElement];
rect.size.width = cSize->x + cSize->width - bounds.origin.x;
}
if (numRows == 0) {
rect.size.height = 0.0;
} else {
rSize = (MiscRowSize *)[rowSizes lastElement];
rect.size.height = rSize->y + rSize->height - bounds.origin.y;
}
/* Or do we want to just hit the instance variable directly--see the
* version at the end of -sizeRowsToFitCells where you _cannot_ use
* the -setFrame: method. If you have troubles with this implementation,
* let the maintainer of the class know to change this so it will
* work right for everybody.
*/
[self setFrame:&rect]; // I think we want self here, not super... -DAY
return self;
}
- sizeToFit
/*
* resize row heights and column widths to accommodate the largest cell in each
* then resize self to fit the cells.
*/
{
int row,col;
NXSize thisCellSize;
MiscRowSize *rSize;
MiscColumnSize *cSize;
NXCoord *maxWidth, *mW, maxHeight;
NXZone *myZone;
id *cellAt;
/*
* This method is a pig. Try to improve preformance by removing method calls
* by using the method implementation ptr...
*/
id (*cellCalcCellSize)(id, SEL, NXSize*);
myZone = [super zone];
// space to record the largest size of the columns
maxWidth = NXZoneCalloc(myZone, numCols, sizeof(NXCoord));
/*
* this monkey business is to speed up execution of this pig
*/
cellCalcCellSize = (id(*)(id, SEL, NXSize*))
[protoCell methodFor:@selector(calcCellSize:)];
cellAt = ((List *)(cellList))->dataPtr; // peek at Lists' data
rSize = (MiscRowSize *)[rowSizes elementAt:0];
for (row = 0; row < numRows; row++,rSize++) {
maxHeight = rSize->height; /* init max = height of cell 0 */
for (col = 0, mW = maxWidth; col < numCols; col++, mW++) {
// [[super cellAt:row:col] calcCellSize:&thisCellSize];
// replace above with....
cellCalcCellSize(*cellAt,@selector(calcCellSize:),&thisCellSize);
if (thisCellSize.height > maxHeight)
maxHeight = thisCellSize.height;
if (thisCellSize.width > *mW)
*mW = thisCellSize.width;
cellAt++; /* next column... */
}
/*
* change the height of this row if one of the columns needs more headroom
*/
if (maxHeight > rSize->height)
[self setHeight:maxHeight ofRow:row];
}
/*
* now adjust the column widths if needed...
*/
cSize = (MiscColumnSize *)[columnSizes elementAt:0];
for (col = 0, mW = maxWidth; col < numCols; col++,cSize++, mW++)
if (*mW != cSize->width) {
NXCoord diff;
diff = *mW - cSize->width;
cSize->width = *mW;
[self _Misc_moveColumnsRightBy:diff startingAt:col+1];
}
if (maxWidth)
NXZoneFree(myZone,maxWidth);
return [self sizeToCells];
}
- renewRows:(int)newRows cols:(int)newCols
// Makes sure to keep our storage objects in synch with everything else.
{
MiscColumnSize newCSize, *cSize;
MiscRowSize newRSize, *rSize;
int i;
// Remove any storage elements past the new number of cols
for (i=numCols-1; i>=newCols; i--) {
[columnSizes removeLastElement];
}
// Add any needed new storage elements to get up to the new number of cols
for (i=numCols; i<newCols; i++) {
if (i==0) {
newCSize.x = bounds.origin.x;
} else {
cSize = (MiscColumnSize *)[columnSizes lastElement];
newCSize.x = cSize->x + cSize->width + intercell.width;
}
newCSize.width = cellSize.width;
[columnSizes addElement:&newCSize];
}
// Remove any storage elements past the new number of rows
for (i=numRows-1; i>=newRows; i--) { // Was ++ but -- makes more sense
[rowSizes removeLastElement];
}
// Add any needed new storage elements to get up to the new number of rows
for (i=numRows; i<newRows; i++) {
if (i==0) {
newRSize.y = bounds.origin.y;
} else {
rSize = (MiscRowSize *)[rowSizes lastElement];
newRSize.y = rSize->y + rSize->height + intercell.height;
}
newRSize.height = cellSize.height;
[rowSizes addElement:&newRSize];
}
[super renewRows:newRows cols:newCols];
return self;
}
- addCol
/*
* addCol is implemented by the Matrix superclass as [self insertColAt:numCols]
* so doing all this work here is redundant (and wrong). Changed this to simply
* call super.
*/
{
int n = [columnSizes count]; /* copy just to prove a point... */
[super addCol];
// to prove my point, look how everything still checks out...
if (([columnSizes count] != (n+1)) || ((n+1) != numCols))
[self error:"Assertion failed in [MiscMatrix addCol]. Re-implement!"];
return self;
}
- addRow
/*
* addRow is implemented by the Matrix superclass as [self insertRowAt:numRows]
* so doing all this work here is redundant (and wrong). Changed this to simply
* call super.
*/
{
int n = [rowSizes count]; /* copy just to prove a point... */
[super addRow];
// to prove my point, look how everything still checks out...
if (([rowSizes count] != (n+1)) || ((n+1) != numRows))
[self error:"Assertion failed in [MiscMatrix addRow]. Re-implement!"];
return self;
}
- insertColAt:(int)col
// Keep the storage in synch
{
MiscColumnSize newCSize, *cSize;
if ((col < 0) || (col > numCols))
return nil;
newCSize.width = cellSize.width;
if (col == 0) /* adding first cell ? */
newCSize.x = bounds.origin.x;
else
if (col == numCols) { /* adding new last element? */
cSize = (MiscColumnSize *)[columnSizes lastElement];
newCSize.x = cSize->x + cSize->width + intercell.width;
} else { /* must be in the middle somewhere... */
cSize = (MiscColumnSize *)[columnSizes elementAt:col];
newCSize.x = cSize->x;
}
if (col == numCols)
[columnSizes addElement:&newCSize];
else {
[columnSizes insertElement:&newCSize at:col];
[self _Misc_moveColumnsRightBy:newCSize.width + intercell.width
startingAt:col+1];
}
[super insertColAt:col];
return self;
}
- insertRowAt:(int)row
// Keep the storage in synch
{
MiscRowSize newRSize, *rSize;
if ((row < 0) || (row > numRows))
return nil;
newRSize.height = cellSize.height;
if (row == 0) /* adding first row? */
newRSize.y = bounds.origin.y;
else
if (row == numRows) { /* adding new last row? */
rSize = (MiscRowSize *)[rowSizes lastElement];
newRSize.y = rSize->y + rSize->height + intercell.height;
} else { /* must be in the middle somewhere... */
rSize = (MiscRowSize *)[rowSizes elementAt:row];
newRSize.y = rSize->y;
}
if (row == numRows)
[rowSizes addElement:&newRSize];
else {
[rowSizes insertElement:&newRSize at:row]; // changed from row+1 (sq)
[self _Misc_moveRowsDownBy:newRSize.height + intercell.height
startingAt:row+1];
}
[super insertRowAt:row];
return self;
}
- removeColAt:(int)col andFree:(BOOL)flag
// Keep the storage in synch
{
MiscColumnSize *cSize;
NXCoord diff;
if ((col >= numCols) || (col < 0)) {
return nil;
}
[super removeColAt:col andFree:flag];
cSize = (MiscColumnSize *)[columnSizes elementAt:col];
diff = cSize->width;
[columnSizes removeElementAt:col];
[self _Misc_moveColumnsRightBy:0.0 - diff - intercell.width startingAt:col];
return self;
}
- removeRowAt:(int)row andFree:(BOOL)flag
// Keep the storage in synch
{
MiscRowSize *rSize;
NXCoord diff;
if ((row >= numRows) || (row < 0)) {
return nil;
}
[super removeRowAt:row andFree:flag];
rSize = (MiscRowSize *)[rowSizes elementAt:row];
diff = rSize->height;
[rowSizes removeElementAt:row];
[self _Misc_moveRowsDownBy:0.0 - diff - intercell.height startingAt:row];
return self;
}
- drawSelf:(const NXRect *)rects:(int)rectCount
// We do our own drawing because we need to draw our cells in diverse
// rectangles
{
int row, col;
int rMin,cMin,rMax,cMax;
NXPoint lowerRt;
NXRect cFrm;
MiscColumnSize *cSize;
MiscRowSize *rSize;
// Scott Violet removed the window flushing stuff; his change in the
// cell rendering should make these unnecessary, so removing them will
// remove flicker when the MiscMatrix is in a SplitView. I simply
// commented it out for now; --DAY
// [window disableFlushWindow];
// the background (if any)
if (backgroundGray != -1.0) {
PSsetgray(backgroundGray);
if (rectCount==1) {
NXRectFill(&(rects[0]));
} else {
NXRectFill(&(rects[1]));
NXRectFill(&(rects[2]));
}
}
/*
* calculate the cells that need to be redrawn & iterate through them only...
*/
[self getRow:&rMin andCol:&cMin forPoint:&(rects->origin)];
if (rMin == -1) rMin = 0;
if (cMin == -1) cMin = 0;
lowerRt.x = rects->origin.x + rects->size.width;
lowerRt.y = rects->origin.y + rects->size.height;
[self getRow:&rMax andCol:&cMax forPoint:&lowerRt];
if (rMax == -1) rMax = numRows-1;
if (cMax == -1) cMax = numCols-1;
rSize = (MiscRowSize *)[rowSizes elementAt:rMin];
for (row=rMin; row<=rMax; row++,rSize++) {
cSize = (MiscColumnSize *)[columnSizes elementAt:cMin];
for (col=cMin; col<=cMax; col++,cSize++) {
cFrm.origin.x = cSize->x;
cFrm.origin.y = rSize->y;
cFrm.size.width = cSize->width;
cFrm.size.height = rSize->height;
if (rectCount == 1) {
if (NXIntersectsRect(&(rects[0]), &cFrm))
// old way: [self drawCellAt:row:col];
[self getCellFrame:&cFrm at:row:col];
[[self cellAt:row:col] drawInside:&cFrm inView:self];
} else {
if ((NXIntersectsRect(&(rects[1]), &cFrm)) ||
(NXIntersectsRect(&(rects[2]), &cFrm)))
// old way: [self drawCellAt:row:col];
[self getCellFrame:&cFrm at:row:col];
[[self cellAt:row:col] drawInside:&cFrm inView:self];
}
}
}
// [window reenableFlushWindow];
// [window flushWindow];
return self;
}
- getCellFrame:(NXRect *)theRect at:(int)row:(int)col
// Calculate and return the rect used to display the cell at the given
// row and column
{
MiscColumnSize *cSize;
MiscRowSize *rSize;
if (col < numCols) {
cSize = (MiscColumnSize *)[columnSizes elementAt:col];
theRect->origin.x = cSize->x;
theRect->size.width = cSize->width;
} else {
int num = col - numCols;
cSize = (MiscColumnSize *)[columnSizes lastElement];
theRect->origin.x = cSize->x +
(num * (cellSize.width + intercell.width));
theRect->size.width = cellSize.width;
}
if (row < numRows) {
rSize = (MiscRowSize *)[rowSizes elementAt:row];
theRect->origin.y = rSize->y;
theRect->size.height = rSize->height;
} else {
int num = row - numRows;
rSize = (MiscRowSize *)[rowSizes lastElement];
theRect->origin.y = rSize->y +
(num * (cellSize.height + intercell.height));
theRect->size.height = cellSize.height;
}
return self;
}
- getRow:(int *)row andCol:(int *)col forPoint:(const NXPoint *)aPoint
// Calculate the row and column of the cell which contains the given point
// changed to use a bsearch instead of iterative... - sq
{
MiscColumnSize *cSize;
MiscRowSize *rSize;
int index, upper, lower;
*row = -1;
*col = -1;
if ((aPoint->x < bounds.origin.x) ||
(aPoint->x > bounds.origin.x + bounds.size.width) ||
(aPoint->y < bounds.origin.y) ||
(aPoint->y > bounds.origin.y + bounds.size.height)) {
return nil;
}
cSize = (MiscColumnSize *)[columnSizes elementAt:0];
lower = 0;
upper = [columnSizes count];
while (lower < upper) {
index = (lower + upper) / 2;
if (aPoint->x < cSize[index].x)
upper = index; // lower, look to the left...
else if (aPoint->x > (cSize[index].x + cSize[index].width))
lower = index + 1; // higher, look to the right...
else {
*col = index; // hit it...
break;
}
}
rSize = (MiscRowSize *)[rowSizes elementAt:0];
lower = 0;
upper = [rowSizes count];
while (lower < upper) {
index = (lower + upper) / 2;
if (aPoint->y < rSize[index].y)
upper = index; // lower, look to the left...
else if (aPoint->y > (rSize[index].y + rSize[index].height))
lower = index + 1; // higher, look to the right...
else {
*row = index; // hit it...
break;
}
}
return ((*row == -1) || (*col == -1)) ? nil : self;
}
- setIntercell:(const NXSize *)aSize
// Keep the storage in synch
{
NXCoord xDiff = aSize->width - intercell.width;
NXCoord yDiff = aSize->height - intercell.height;
MiscRowSize *rSize;
MiscColumnSize *cSize;
int i;
for (i=1; i<numRows; i++) {
rSize = (MiscRowSize *)[rowSizes elementAt:i];
rSize->y += (yDiff * i);
}
for (i=1; i<numCols; i++) {
cSize = (MiscColumnSize *)[columnSizes elementAt:i];
cSize->x += (xDiff * i);
}
return [super setIntercell:aSize];
}
- write:(NXTypedStream *)typedStream
// Write our ivars
{
[super write:typedStream];
NXWriteObject(typedStream, columnSizes);
NXWriteObject(typedStream, rowSizes);
return self;
}
- read:(NXTypedStream *)typedStream
// Read our ivars
{
int classVersion;
[super read:typedStream];
classVersion = NXTypedStreamClassVersion(typedStream, CLASS_NAME);
switch (classVersion) {
case 0: // First version.
columnSizes = NXReadObject(typedStream);
rowSizes = NXReadObject(typedStream);
break;
default:
NXLogError("[%s read:] class version %d cannot read "
"instances archived with version %d",
CLASS_NAME, CLASS_VERSION, classVersion);
[self setupStorage:numRows :numCols];
break;
}
return self;
}
// ********************Overridden private methods***********************
// *****************that I'm going to hell for using********************
// These methods are used by Matrix's mouseDown:. Doing the whole
// mouseDown: method over would have been a royal pain in the butt,
// so I cheated.
- (BOOL)_mouseHit:(const NXPoint *)forpoint row:(int *)row col:(int *)col
{
NXPoint point;
id ret;
point = *forpoint;
[self convertPoint:&point fromView:nil];
ret = [self getRow:row andCol:col forPoint:&point];
if (ret == nil)
return NO;
return YES;
}
- (BOOL)_loopHit:(const NXPoint *)forpoint row:(int *)row col:(int *)col
{
NXPoint point;
id ret;
point = *forpoint;
[self convertPoint:&point fromView:nil];
ret = [self getRow:row andCol:col forPoint:&point];
if (ret == nil)
return NO;
return YES;
}
- (BOOL)_radioHit:(const NXPoint *)forpoint row:(int *)row col:(int *)col
{
NXPoint point;
id ret;
point = *forpoint;
[self convertPoint:&point fromView:nil];
ret = [self getRow:row andCol:col forPoint:&point];
if (ret == nil)
return NO;
return YES;
}
- sizeRowsToFitCells
{
NXRect veryBig;
NXSize size;
NXRect myFrame;
NXCoord maxHeight;
NXCoord offset = 0.0;
MiscRowSize *rSize;
int i,j;
veryBig.size.width = cellSize.width;
veryBig.size.height = MAXFLOAT;
[self getFrame:&myFrame];
for (i = 0; i < numRows; i++)
{
maxHeight = 0.0;
rSize = (MiscRowSize *)[rowSizes elementAt:i];
rSize->y = offset;
for (j = 0; j < numCols; j++)
{
[[self cellAt:i :j] calcCellSize:&size inRect:&veryBig];
if (size.height > maxHeight)
maxHeight = size.height;
}
rSize->height = maxHeight;
offset += maxHeight + intercell.height;
}
myFrame.size.height = offset;
// This can't be used: [self setFrame:&myFrame];
/*
* We can't call setFrame: here because it will call sizeTo::
* which will try to modify cellFrame and the column/row sizes.
* So we just copy into the instance variable.
*
* And we'll probably be sent to the deepest corners of hell for doing it.
*/
// frame = myFrame;
// This has been suggested as a perhaps safer way to do the same thing,
// so we'll use it instead of the above line, but I'm keeping the above
// until we are sure that it is doing the right thing... -DAY
/*
* We use super here to avoid re-adjusting the row
* and column sizes.
*/
[super sizeTo:myFrame.size.width :myFrame.size.height];
return self;
}
- sizeTo:(NXCoord)width :(NXCoord)height
{
NXRect oldFrame;
NXCoord dx, dy;
float sx, sy;
[self getFrame:&oldFrame];
[super sizeTo:width :height];
dx = width - oldFrame.size.width;
dy = height - oldFrame.size.height;
if ([self doesAutosizeCells] && (numRows > 0) && (numCols > 0))
{
NXCoord offset;
int i;
MiscRowSize *rSize;
MiscColumnSize *cSize;
sx = 1 + (dx/(oldFrame.size.width - (numRows - 1)*intercell.width));
sy = 1 + (dy/(oldFrame.size.height - (numCols - 1)*intercell.height));
for (i=0, offset = 0.0; i < numRows; i++)
{
rSize = (MiscRowSize *)[rowSizes elementAt:i];
rSize->y = offset;
rSize->height *= floor(sy+0.5);
offset += rSize->height + intercell.height;
}
for (i=0, offset = 0.0; i < numCols; i++)
{
cSize = (MiscColumnSize *)[columnSizes elementAt:i];
cSize->x = offset;
cSize->width *= sx;
offset += cSize->width + intercell.width;
}
}
return self;
}
@end
@implementation Storage(MiscLastElementCategory)
- (void *)lastElement
// A little shortcut
{
void *theLastOne;
theLastOne = (numElements) ? (char *)dataPtr + (elementSize*(numElements-1)) : NULL;
return theLastOne;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.