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.