mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-25 06:30:55 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@4343 72102866-910b-0410-8b05-ffd578937521
2508 lines
60 KiB
Objective-C
2508 lines
60 KiB
Objective-C
/*
|
|
NSMatrix.m
|
|
|
|
Matrix class for grouping controls
|
|
|
|
Copyright (C) 1996 Free Software Foundation, Inc.
|
|
|
|
Author: Ovidiu Predescu <ovidiu@net-community.com>
|
|
Date: March 1997
|
|
A completely rewritten version of the original source by Pascal Forget and
|
|
Scott Christley.
|
|
Author: Felipe A. Rodriguez <far@ix.netcom.com>
|
|
Date: August 1998
|
|
|
|
This file is part of the GNUstep GUI Library.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public
|
|
License along with this library; see the file COPYING.LIB.
|
|
If not, write to the Free Software Foundation,
|
|
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include <gnustep/gui/config.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <Foundation/NSValue.h>
|
|
#include <Foundation/NSArray.h>
|
|
#include <Foundation/NSAutoreleasePool.h>
|
|
#include <Foundation/NSString.h>
|
|
|
|
#include <AppKit/NSColor.h>
|
|
#include <AppKit/NSCursor.h>
|
|
#include <AppKit/NSActionCell.h>
|
|
#include <AppKit/NSWindow.h>
|
|
#include <AppKit/NSApplication.h>
|
|
#include <AppKit/NSMatrix.h>
|
|
|
|
|
|
|
|
/* Define the following symbol when NSView will support flipped views */
|
|
#define HAS_FLIPPED_VIEWS 1
|
|
|
|
#ifdef MIN
|
|
# undef MIN
|
|
#endif
|
|
#define MIN(A,B) ({ typeof(A) __a = (A); \
|
|
typeof(B) __b = (B); \
|
|
__a < __b ? __a : __b; })
|
|
|
|
#ifdef MAX
|
|
# undef MAX
|
|
#endif
|
|
#define MAX(A,B) ({ typeof(A) __a = (A); \
|
|
typeof(B) __b = (B); \
|
|
__a < __b ? __b : __a; })
|
|
|
|
#ifdef ABS
|
|
# undef ABS
|
|
#endif
|
|
#define ABS(A) ({ typeof(A) __a = (A); __a < 0 ? -__a : __a; })
|
|
|
|
|
|
#define SIGN(x) \
|
|
({typeof(x) _SIGN_x = (x); \
|
|
_SIGN_x > 0 ? 1 : (_SIGN_x == 0 ? 0 : -1); })
|
|
|
|
#define FREE(p) do { if (p) free (p); } while (0)
|
|
|
|
#define POINT_FROM_INDEX(index) \
|
|
({MPoint point = { index % numCols, index / numCols }; point; })
|
|
|
|
#define INDEX_FROM_POINT(point) \
|
|
(point.y * numCols + point.x)
|
|
|
|
|
|
typedef struct _tMatrix {
|
|
int numRows, numCols;
|
|
int allocatedRows, allocatedCols;
|
|
BOOL** matrix;
|
|
} *tMatrix;
|
|
|
|
static tMatrix newMatrix (int numRows, int numCols)
|
|
{
|
|
int rows = (numRows ? numRows : 1);
|
|
int cols = (numCols ? numCols : 1);
|
|
tMatrix m = malloc (sizeof(struct _tMatrix));
|
|
int i;
|
|
|
|
m->matrix = malloc (rows * sizeof(BOOL*));
|
|
for (i = 0; i < rows; i++)
|
|
m->matrix[i] = calloc (cols, sizeof(BOOL));
|
|
|
|
m->allocatedRows = rows;
|
|
m->allocatedCols = cols;
|
|
m->numRows = numRows;
|
|
m->numCols = numCols;
|
|
|
|
return m;
|
|
}
|
|
|
|
static void freeMatrix (tMatrix m)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < m->allocatedRows; i++)
|
|
FREE (m->matrix[i]);
|
|
FREE (m->matrix);
|
|
FREE (m);
|
|
}
|
|
|
|
/* Grow the matrix to some arbitrary dimensions */
|
|
static void growMatrix (tMatrix m, int numRows, int numCols)
|
|
{
|
|
int i, j;
|
|
|
|
if (numCols > m->allocatedCols) {
|
|
/* Grow the existing rows to numCols */
|
|
for (i = 0; i < m->allocatedRows; i++) {
|
|
m->matrix[i] = realloc (m->matrix[i], numCols * sizeof(BOOL));
|
|
for (j = m->allocatedCols - 1; j < numCols; j++)
|
|
m->matrix[i][j] = NO;
|
|
}
|
|
m->allocatedCols = numCols;
|
|
}
|
|
|
|
if (numRows > m->allocatedRows) {
|
|
/* Grow the vector that keeps the rows */
|
|
m->matrix = realloc (m->matrix, numRows * sizeof(BOOL*));
|
|
|
|
/* Allocate the rows up to allocatedRows that are NULL */
|
|
for (i = 0; i < m->allocatedRows; i++)
|
|
if (!m->matrix[i])
|
|
m->matrix[i] = calloc (m->allocatedCols, sizeof(BOOL));
|
|
|
|
/* Add the necessary rows */
|
|
for (i = m->allocatedRows; i < numRows; i++)
|
|
m->matrix[i] = calloc (m->allocatedCols, sizeof(BOOL));
|
|
m->allocatedRows = numRows;
|
|
}
|
|
|
|
m->numRows = numRows;
|
|
m->numCols = numCols;
|
|
}
|
|
|
|
static void insertRow (tMatrix m, int rowPosition)
|
|
{
|
|
int rows = m->numRows + 1;
|
|
int i;
|
|
|
|
/* Create space for the new rows if necessary */
|
|
if (rows > m->allocatedRows)
|
|
{
|
|
m->matrix = realloc (m->matrix, rows * sizeof(BOOL*));
|
|
m->allocatedRows = rows;
|
|
}
|
|
|
|
/* Make room for the new row */
|
|
for (i = m->numRows - 1; i > rowPosition; i--)
|
|
m->matrix[i] = m->matrix[i - 1];
|
|
|
|
/* Allocate any NULL row that exists up to rowPosition */
|
|
for (i = 0; i < rowPosition; i++)
|
|
if (!m->matrix[i])
|
|
m->matrix[i] = calloc (m->allocatedCols, sizeof(BOOL));
|
|
|
|
/* Create the required row */
|
|
m->matrix[rowPosition] = calloc (m->allocatedCols, sizeof(BOOL));
|
|
|
|
m->numRows++;
|
|
}
|
|
|
|
static void insertColumn (tMatrix m, int colPosition)
|
|
{
|
|
int cols = m->numCols + 1;
|
|
int i, j;
|
|
|
|
/* First grow the rows to hold `cols' elements */
|
|
if (cols > m->allocatedCols)
|
|
{
|
|
for (i = 0; i < m->numRows; i++)
|
|
m->matrix[i] = realloc (m->matrix[i], cols * sizeof(BOOL));
|
|
m->allocatedCols = cols;
|
|
}
|
|
|
|
/* Now insert a new column between the rows, in the required position.
|
|
If it happens that a row is NULL create a new row with the maximum
|
|
number of columns for it. */
|
|
for (i = 0; i < m->numRows; i++)
|
|
{
|
|
BOOL* row = m->matrix[i];
|
|
|
|
if (!row)
|
|
m->matrix[i] = calloc (m->allocatedCols, sizeof(BOOL));
|
|
else
|
|
{
|
|
for (j = m->numCols - 1; j > colPosition; j--)
|
|
row[j] = row[j - 1];
|
|
row[colPosition] = NO;
|
|
}
|
|
}
|
|
m->numCols++;
|
|
}
|
|
|
|
static void removeRow (tMatrix m, int row)
|
|
{
|
|
int i;
|
|
|
|
/* Free the row and shrink the matrix by removing the row from it */
|
|
FREE (m->matrix[row]);
|
|
m->matrix[row] = NULL;
|
|
for (i = row; i < m->numRows - 1; i++)
|
|
m->matrix[i] = m->matrix[i + 1];
|
|
m->numRows--;
|
|
m->matrix[m->numRows] = NULL;
|
|
}
|
|
|
|
static void removeColumn (tMatrix m, int column)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < m->numRows; i++)
|
|
{
|
|
BOOL* row = m->matrix[i];
|
|
|
|
for (j = column; j < m->numCols - 1; j++)
|
|
row[j] = row[j + 1];
|
|
row[m->numCols] = NO;
|
|
}
|
|
m->numCols--;
|
|
}
|
|
|
|
/* Some stuff needed to compute the selection in the list mode. */
|
|
typedef struct {
|
|
int x;
|
|
int y;
|
|
} MPoint;
|
|
|
|
typedef struct {
|
|
int x;
|
|
int y;
|
|
int width;
|
|
int height;
|
|
} MRect;
|
|
|
|
static inline MPoint MakePoint (int x, int y)
|
|
{
|
|
MPoint point = { x, y };
|
|
return point;
|
|
}
|
|
|
|
@interface NSMatrix (PrivateMethods)
|
|
|
|
/* Returns by reference the row and column of cell that is above or below or
|
|
to the left or to the right of point. `point' is in the matrix coordinates.
|
|
Returns NO if the point is outside the bounds of matrix, YES otherwise.
|
|
*/
|
|
- (BOOL) _getRow: (int*)row
|
|
column: (int*)column
|
|
forPoint: (NSPoint)point
|
|
above: (BOOL)aboveRequired
|
|
right: (BOOL)rightRequired
|
|
isBetweenCells: (BOOL*)isBetweenCells;
|
|
- (void) _selectRectUsingAnchor: (MPoint)anchor
|
|
last: (MPoint)last
|
|
current: (MPoint)current;
|
|
- (void) _setState: (int)state inRect: (MRect)rect;
|
|
- (void) _selectContinuousUsingAnchor: (MPoint)anchor
|
|
last: (MPoint)last
|
|
current: (MPoint)current;
|
|
- (void) _setState: (int)state startIndex: (int)start endIndex: (int)end;
|
|
@end
|
|
|
|
enum {
|
|
DEFAULT_CELL_HEIGHT = 17,
|
|
DEFAULT_CELL_WIDTH = 100
|
|
};
|
|
|
|
@implementation NSMatrix
|
|
|
|
/* Class variables */
|
|
static Class defaultCellClass = nil;
|
|
static int mouseDownFlags = 0;
|
|
|
|
+ (void) initialize
|
|
{
|
|
if (self == [NSMatrix class])
|
|
{
|
|
/* Set the initial version */
|
|
[self setVersion: 1];
|
|
|
|
/* Set the default cell class */
|
|
defaultCellClass = [NSCell class];
|
|
}
|
|
}
|
|
|
|
+ (Class) cellClass
|
|
{
|
|
return defaultCellClass;
|
|
}
|
|
|
|
+ (void) setCellClass: (Class)classId
|
|
{
|
|
defaultCellClass = classId;
|
|
}
|
|
|
|
- init
|
|
{
|
|
return [self initWithFrame: NSZeroRect
|
|
mode: NSRadioModeMatrix
|
|
prototype: [[[isa cellClass] new] autorelease]
|
|
numberOfRows: 0
|
|
numberOfColumns: 1];
|
|
}
|
|
|
|
- (id) initWithFrame: (NSRect)frameRect
|
|
{
|
|
return [self initWithFrame: frameRect
|
|
mode: NSRadioModeMatrix
|
|
cellClass: [isa cellClass]
|
|
numberOfRows: 0
|
|
numberOfColumns: 0];
|
|
}
|
|
|
|
- (id) initWithFrame: (NSRect)frameRect
|
|
mode: (int)aMode
|
|
cellClass: (Class)class
|
|
numberOfRows: (int)rowsHigh
|
|
numberOfColumns: (int)colsWide
|
|
{
|
|
return [self initWithFrame: frameRect
|
|
mode: aMode
|
|
prototype: [[class new] autorelease]
|
|
numberOfRows: rowsHigh
|
|
numberOfColumns: colsWide];
|
|
}
|
|
|
|
- (id) initWithFrame: (NSRect)frameRect
|
|
mode: (int)aMode
|
|
prototype: (NSCell*)prototype
|
|
numberOfRows: (int)rows
|
|
numberOfColumns: (int)cols
|
|
{
|
|
int i, j;
|
|
|
|
[super initWithFrame: frameRect];
|
|
|
|
ASSIGN(cellPrototype, prototype);
|
|
|
|
cells = [[NSMutableArray alloc] initWithCapacity: rows];
|
|
selectedCells = newMatrix (rows, cols);
|
|
for (i = 0; i < rows; i++)
|
|
{
|
|
NSMutableArray* row = [NSMutableArray arrayWithCapacity: cols];
|
|
|
|
[cells addObject: row];
|
|
for (j = 0; j < cols; j++)
|
|
{
|
|
[row addObject: [[cellPrototype copy] autorelease]];
|
|
}
|
|
}
|
|
|
|
numRows = rows;
|
|
numCols = cols;
|
|
mode = aMode;
|
|
[self setFrame: frameRect];
|
|
|
|
cellSize = NSMakeSize(DEFAULT_CELL_WIDTH, DEFAULT_CELL_HEIGHT);
|
|
intercell = NSMakeSize(1, 1);
|
|
[self setBackgroundColor: [NSColor lightGrayColor]];
|
|
[self setDrawsBackground: YES];
|
|
[self setCellBackgroundColor: [NSColor lightGrayColor]];
|
|
[self setSelectionByRect: YES];
|
|
[self setAutosizesCells: YES];
|
|
if (mode == NSRadioModeMatrix && numRows && numCols)
|
|
{
|
|
[self selectCellAtRow: 0 column: 0];
|
|
}
|
|
else
|
|
selectedRow = selectedColumn = 0;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[cells release];
|
|
[cellPrototype release];
|
|
[backgroundColor release];
|
|
[cellBackgroundColor release];
|
|
freeMatrix (selectedCells);
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void) addColumn
|
|
{
|
|
[self insertColumn: numCols];
|
|
}
|
|
|
|
- (void) addColumnWithCells: (NSArray*)cellArray
|
|
{
|
|
[self insertColumn: numCols withCells: cellArray];
|
|
}
|
|
|
|
- (void) addRow
|
|
{
|
|
[self insertRow: numRows];
|
|
}
|
|
|
|
- (void) addRowWithCells: (NSArray*)cellArray
|
|
{
|
|
[self insertRow: numRows withCells: cellArray];
|
|
}
|
|
|
|
- (void) insertColumn: (int)column
|
|
{
|
|
int i;
|
|
|
|
/* Grow the matrix if necessary */
|
|
if (column >= numCols)
|
|
[self renewRows: (numRows == 0 ? 1 : numRows) columns: column];
|
|
if (numRows == 0)
|
|
{
|
|
numRows = 1;
|
|
[cells addObject: [NSMutableArray array]];
|
|
}
|
|
|
|
numCols++;
|
|
for (i = 0; i < numRows; i++)
|
|
[self makeCellAtRow: i column: column];
|
|
insertColumn (selectedCells, column);
|
|
|
|
if (mode == NSRadioModeMatrix && !allowsEmptySelection && !selectedCell)
|
|
[self selectCellAtRow: 0 column: 0];
|
|
}
|
|
|
|
- (void) insertColumn: (int)column withCells: (NSArray*)cellArray
|
|
{
|
|
int i;
|
|
|
|
/* Grow the matrix if necessary */
|
|
if (column >= numCols)
|
|
[self renewRows: (numRows == 0 ? 1 : numRows) columns: column];
|
|
if (numRows == 0)
|
|
{
|
|
numRows = 1;
|
|
[cells addObject: [NSMutableArray array]];
|
|
}
|
|
|
|
numCols++;
|
|
for (i = 0; i < numRows; i++)
|
|
[[cells objectAtIndex: i]
|
|
replaceObjectAtIndex: column withObject: [cellArray objectAtIndex: i]];
|
|
insertColumn (selectedCells, column);
|
|
|
|
if (mode == NSRadioModeMatrix && !allowsEmptySelection && !selectedCell)
|
|
[self selectCellAtRow: 0 column: 0];
|
|
}
|
|
|
|
- (void) insertRow: (int)row
|
|
{
|
|
int i;
|
|
|
|
/* Grow the matrix if necessary */
|
|
if (row >= numRows)
|
|
[self renewRows: row columns: (numCols == 0 ? 1 : numCols)];
|
|
if (numCols == 0)
|
|
numCols = 1;
|
|
|
|
[cells insertObject: [NSMutableArray arrayWithCapacity: numCols] atIndex: row];
|
|
|
|
numRows++;
|
|
for (i = 0; i < numCols; i++)
|
|
[self makeCellAtRow: row column: i];
|
|
|
|
insertRow (selectedCells, row);
|
|
|
|
if (mode == NSRadioModeMatrix && !allowsEmptySelection && !selectedCell)
|
|
[self selectCellAtRow: 0 column: 0];
|
|
}
|
|
|
|
- (void) insertRow: (int)row withCells: (NSArray*)cellArray
|
|
{
|
|
/* Grow the matrix if necessary */
|
|
if (row >= numRows)
|
|
[self renewRows: row columns: (numCols == 0 ? 1 : numCols)];
|
|
if (numCols == 0)
|
|
numCols = 1;
|
|
|
|
[cells insertObject: [cellArray subarrayWithRange: NSMakeRange(0, numCols)]
|
|
atIndex: row];
|
|
|
|
insertRow (selectedCells, row);
|
|
|
|
numRows++;
|
|
|
|
if (mode == NSRadioModeMatrix && !allowsEmptySelection && !selectedCell)
|
|
[self selectCellAtRow: 0 column: 0];
|
|
}
|
|
|
|
- (NSCell*) makeCellAtRow: (int)row
|
|
column: (int)column
|
|
{
|
|
NSCell* aCell;
|
|
|
|
if (cellPrototype)
|
|
aCell = [[cellPrototype copy] autorelease];
|
|
else if (cellClass)
|
|
aCell = [[cellClass new] autorelease];
|
|
else
|
|
aCell = [[NSActionCell new] autorelease];
|
|
|
|
[[cells objectAtIndex: row] insertObject: aCell atIndex: column];
|
|
return aCell;
|
|
}
|
|
|
|
- (NSRect) cellFrameAtRow: (int)row
|
|
column: (int)column
|
|
{
|
|
NSRect rect;
|
|
|
|
rect.origin.x = column * (cellSize.width + intercell.width);
|
|
#if HAS_FLIPPED_VIEWS
|
|
rect.origin.y = row * (cellSize.height + intercell.height);
|
|
#else
|
|
rect.origin.y = (numRows - row - 1) * (cellSize.height + intercell.height);
|
|
#endif
|
|
rect.size = cellSize;
|
|
return rect;
|
|
}
|
|
|
|
- (void) getNumberOfRows: (int*)rowCount
|
|
columns: (int*)columnCount
|
|
{
|
|
*rowCount = numRows;
|
|
*columnCount = numCols;
|
|
}
|
|
|
|
- (void) putCell: (NSCell*)newCell
|
|
atRow: (int)row
|
|
column: (int)column
|
|
{
|
|
[[cells objectAtIndex: row] replaceObjectAtIndex: column withObject: newCell];
|
|
[self setNeedsDisplayInRect: [self cellFrameAtRow: row column: column]];
|
|
}
|
|
|
|
- (void) removeColumn: (int)column
|
|
{
|
|
int i;
|
|
|
|
if (column >= numCols)
|
|
return;
|
|
|
|
for (i = 0; i < numRows; i++)
|
|
[[cells objectAtIndex: i] removeObjectAtIndex: column];
|
|
|
|
removeColumn (selectedCells, column);
|
|
numCols--;
|
|
|
|
if (column == selectedColumn)
|
|
{
|
|
selectedCell = nil;
|
|
[self selectCellAtRow: 0 column: 0];
|
|
}
|
|
}
|
|
|
|
- (void) removeRow: (int)row
|
|
{
|
|
if (row >= numRows)
|
|
return;
|
|
|
|
[cells removeObjectAtIndex: row];
|
|
removeRow (selectedCells, row);
|
|
numRows--;
|
|
|
|
if (row == selectedRow)
|
|
{
|
|
selectedCell = nil;
|
|
[self selectCellAtRow: 0 column: 0];
|
|
}
|
|
}
|
|
|
|
- (void) renewRows: (int)newRows
|
|
columns: (int)newColumns
|
|
{
|
|
int i, j;
|
|
|
|
if (newColumns > numCols)
|
|
{
|
|
/* First check to see if the rows really have fewer cells than
|
|
newColumns. This may happen because the row arrays are not shrink
|
|
when a lower number of cells is given. */
|
|
if (numRows && newColumns > [[cells objectAtIndex: 0] count])
|
|
{
|
|
/* Add columns to the existing rows. Call makeCellAtRow: column:
|
|
to be consistent. */
|
|
for (i = 0; i < numRows; i++)
|
|
{
|
|
for (j = numCols; j < newColumns; j++)
|
|
[self makeCellAtRow: i column: j];
|
|
}
|
|
}
|
|
}
|
|
numCols = newColumns;
|
|
|
|
if (newRows > numRows)
|
|
{
|
|
for (i = numRows; i < newRows; i++)
|
|
{
|
|
[cells addObject: [NSMutableArray arrayWithCapacity: numCols]];
|
|
for (j = 0; j < numCols; j++)
|
|
[self makeCellAtRow: i column: j];
|
|
}
|
|
}
|
|
numRows = newRows;
|
|
|
|
growMatrix (selectedCells, newRows, newColumns);
|
|
}
|
|
|
|
- (void) setCellSize: (NSSize)size
|
|
{
|
|
cellSize = size;
|
|
[self sizeToCells];
|
|
}
|
|
|
|
- (void) setIntercellSpacing: (NSSize)size
|
|
{
|
|
intercell = size;
|
|
[self sizeToCells];
|
|
}
|
|
|
|
- (void) sortUsingFunction: (int (*)(id element1, id element2,
|
|
void *userData))comparator
|
|
context: (void*)context
|
|
{
|
|
NSMutableArray* sorted = [NSMutableArray arrayWithCapacity: numRows*numCols];
|
|
NSMutableArray* row;
|
|
int i, j, index = 0;
|
|
|
|
for (i = 0; i < numRows; i++)
|
|
[sorted addObjectsFromArray: [[cells objectAtIndex: i]
|
|
subarrayWithRange: NSMakeRange(0, numCols)]];
|
|
|
|
[sorted sortUsingFunction: comparator context: context];
|
|
|
|
for (i = 0; i < numRows; i++)
|
|
{
|
|
row = [cells objectAtIndex: i];
|
|
for (j = 0; j < numCols; j++)
|
|
{
|
|
[row replaceObjectAtIndex: j
|
|
withObject: [sorted objectAtIndex: index]];
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) sortUsingSelector: (SEL)comparator
|
|
{
|
|
NSMutableArray* sorted = [NSMutableArray arrayWithCapacity: numRows*numCols];
|
|
NSMutableArray* row;
|
|
int i, j, index = 0;
|
|
|
|
for (i = 0; i < numRows; i++)
|
|
[sorted addObjectsFromArray: [[cells objectAtIndex: i]
|
|
subarrayWithRange: NSMakeRange(0, numCols)]];
|
|
|
|
[sorted sortUsingSelector: comparator];
|
|
|
|
for (i = 0; i < numRows; i++)
|
|
{
|
|
row = [cells objectAtIndex: i];
|
|
for (j = 0; j < numCols; j++)
|
|
{
|
|
[row replaceObjectAtIndex: j
|
|
withObject: [sorted objectAtIndex: index]];
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL) getRow: (int*)row
|
|
column: (int*)column
|
|
forPoint: (NSPoint)aPoint
|
|
{
|
|
BOOL isBetweenCells, insideBounds;
|
|
|
|
insideBounds = [self _getRow: row column: column
|
|
forPoint: aPoint
|
|
above: NO right: NO
|
|
isBetweenCells: &isBetweenCells];
|
|
|
|
if (!insideBounds || isBetweenCells)
|
|
return NO;
|
|
else
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL) getRow: (int*)row
|
|
column: (int*)column
|
|
ofCell: (NSCell*)aCell
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < numRows; i++)
|
|
{
|
|
NSMutableArray* rowArray = [cells objectAtIndex: i];
|
|
|
|
for (j = 0; j < numCols; j++)
|
|
if ([rowArray objectAtIndex: j] == aCell)
|
|
{
|
|
*row = i;
|
|
*column = j;
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (void) setState: (int)value
|
|
atRow: (int)row
|
|
column: (int)column
|
|
{
|
|
NSCell* aCell = [self cellAtRow: row column: column];
|
|
|
|
if (!aCell)
|
|
return;
|
|
|
|
if (mode == NSRadioModeMatrix)
|
|
{
|
|
if (value)
|
|
{
|
|
selectedCell = aCell;
|
|
selectedRow = row;
|
|
selectedColumn = column;
|
|
[selectedCell setState: 1];
|
|
}
|
|
else if (allowsEmptySelection)
|
|
[self deselectSelectedCell];
|
|
}
|
|
else
|
|
[aCell setState: value];
|
|
|
|
[self setNeedsDisplayInRect: [self cellFrameAtRow: row column: column]];
|
|
}
|
|
|
|
- (void) deselectAllCells
|
|
{
|
|
unsigned i, j;
|
|
NSArray *row;
|
|
NSCell *aCell;
|
|
|
|
for (i = 0; i < numRows; i++)
|
|
{
|
|
row = [cells objectAtIndex: i];
|
|
for (j = 0; j < numCols; j++)
|
|
if (((tMatrix)selectedCells)->matrix[i][j])
|
|
{
|
|
NSRect theFrame = [self cellFrameAtRow: i column: j];
|
|
|
|
aCell = [row objectAtIndex: j];
|
|
[aCell setState: 0];
|
|
[aCell highlight: NO withFrame: theFrame inView: self];
|
|
[self setNeedsDisplayInRect: theFrame];
|
|
((tMatrix)selectedCells)->matrix[i][j] = NO;
|
|
}
|
|
}
|
|
|
|
if (!allowsEmptySelection)
|
|
[self selectCellAtRow: 0 column: 0];
|
|
}
|
|
|
|
- (void) deselectSelectedCell
|
|
{
|
|
[selectedCell setState: 0];
|
|
selectedCell = nil;
|
|
selectedRow = 0;
|
|
selectedColumn = 0;
|
|
}
|
|
|
|
- (void) selectAll: (id)sender
|
|
{
|
|
unsigned i, j;
|
|
NSArray *row;
|
|
|
|
/* Make the selected cell the cell at (0, 0) */
|
|
selectedCell = [self cellAtRow: 0 column: 0]; // select current cell
|
|
selectedRow = 0;
|
|
selectedColumn = 0;
|
|
|
|
for (i = 0; i < numRows; i++)
|
|
{
|
|
row = [cells objectAtIndex: i];
|
|
|
|
for (j = 0; j < numCols; j++)
|
|
{
|
|
[[row objectAtIndex: j] setState: 1];
|
|
((tMatrix)selectedCells)->matrix[i][j] = YES;
|
|
}
|
|
}
|
|
|
|
[self display];
|
|
}
|
|
|
|
- (void) selectCellAtRow: (int)row column: (int)column
|
|
{
|
|
NSCell* aCell = [self cellAtRow: row column: column];
|
|
|
|
if (mode == NSRadioModeMatrix)
|
|
{
|
|
/* Don't allow loss of selection if in radio mode and empty selection
|
|
is not allowed. Otherwise deselect the selected cell. */
|
|
if (!aCell && !allowsEmptySelection)
|
|
return;
|
|
else if (selectedCell)
|
|
[selectedCell setState: 0];
|
|
}
|
|
else if (!aCell)
|
|
return;
|
|
|
|
selectedCell = aCell; // select current cell
|
|
selectedRow = row;
|
|
selectedColumn = column;
|
|
[selectedCell setState: 1];
|
|
|
|
[self setNeedsDisplayInRect: [self cellFrameAtRow: row column: column]];
|
|
}
|
|
|
|
- (BOOL) selectCellWithTag: (int)anInt
|
|
{
|
|
NSMutableArray* row;
|
|
id aCell;
|
|
int i, j;
|
|
|
|
for (i = numRows - 1; i >= 0; i--)
|
|
{
|
|
row = [cells objectAtIndex: i];
|
|
for (j = numCols - 1; j >= 0; j --)
|
|
{
|
|
aCell = [row objectAtIndex: j];
|
|
if ([aCell tag] == anInt)
|
|
{
|
|
[self selectCellAtRow: i column: j];
|
|
return YES;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (NSArray*) selectedCells
|
|
{
|
|
NSMutableArray* array = [NSMutableArray array];
|
|
int i, j;
|
|
|
|
for (i = 0; i < numRows; i++)
|
|
{
|
|
NSArray* row = [cells objectAtIndex: i];
|
|
|
|
for (j = 0; j < numCols; j++)
|
|
if (((tMatrix)selectedCells)->matrix[i][j])
|
|
[array addObject: [row objectAtIndex: j]];
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
- (void) setSelectionFrom: (int)startPos
|
|
to: (int)endPos
|
|
anchor: (int)anchorPos
|
|
highlight: (BOOL)flag
|
|
{
|
|
MPoint anchor = POINT_FROM_INDEX(anchorPos);
|
|
MPoint last = POINT_FROM_INDEX(startPos);
|
|
MPoint current = POINT_FROM_INDEX(endPos);
|
|
|
|
if (selectionByRect)
|
|
[self _selectRectUsingAnchor: anchor last: last current: current];
|
|
else
|
|
[self _selectContinuousUsingAnchor: anchor last: last current: current];
|
|
[self display];
|
|
}
|
|
|
|
- (id) cellAtRow: (int)row
|
|
column: (int)column
|
|
{
|
|
if (row < 0 || row >= numRows || column < 0 || column >= numCols)
|
|
return nil;
|
|
|
|
return [[cells objectAtIndex: row] objectAtIndex: column];
|
|
}
|
|
|
|
- (id) cellWithTag: (int)anInt
|
|
{
|
|
NSMutableArray* row;
|
|
id aCell;
|
|
int i, j;
|
|
|
|
for (i = numRows - 1; i >= 0; i--)
|
|
{
|
|
row = [cells objectAtIndex: i];
|
|
for (j = numCols - 1; j >= 0; j --)
|
|
{
|
|
aCell = [row objectAtIndex: j];
|
|
if ([aCell tag] == anInt)
|
|
return aCell;
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (NSArray*) cells
|
|
{
|
|
return cells;
|
|
}
|
|
|
|
- (void) selectText: (id)sender
|
|
{
|
|
fprintf(stderr, " NSMatrix: selectText --- ");
|
|
// TODO
|
|
}
|
|
|
|
- (id) selectTextAtRow: (int)row column: (int)column
|
|
{
|
|
// TODO
|
|
// NSCell* aCell = [self cellAtRow: row column: column];
|
|
|
|
fprintf(stderr, " NSMatrix: selectTextAtRow --- ");
|
|
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (id) nextText
|
|
{
|
|
// TODO
|
|
return nil;
|
|
}
|
|
|
|
- (id) previousText
|
|
{
|
|
// TODO
|
|
return nil;
|
|
}
|
|
|
|
- (void) textDidBeginEditing: (NSNotification*)notification
|
|
{
|
|
}
|
|
|
|
- (void) textDidChange: (NSNotification*)notification
|
|
{
|
|
}
|
|
|
|
- (void) textDidEndEditing: (NSNotification*)notification
|
|
{
|
|
}
|
|
|
|
- (BOOL) textShouldBeginEditing: (NSText*)textObject
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL) textShouldEndEditing: (NSText*)textObject
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (void) setNextText: (id)anObject
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
- (void) setPreviousText: (id)anObject
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
- (void) setValidateSize: (BOOL)flag
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
- (void) sizeToCells
|
|
{
|
|
NSSize newSize;
|
|
int nc = numCols;
|
|
int nr = numRows;
|
|
|
|
if (!nc)
|
|
nc = 1;
|
|
if (!nr)
|
|
nr = 1;
|
|
newSize.width = nc * (cellSize.width + intercell.width) - intercell.width;
|
|
newSize.height = nr * (cellSize.height + intercell.height) - intercell.height;
|
|
[self setFrameSize: newSize];
|
|
}
|
|
|
|
- (void) scrollCellToVisibleAtRow: (int)row
|
|
column: (int)column
|
|
{
|
|
[self scrollRectToVisible: [self cellFrameAtRow: row column: column]];
|
|
}
|
|
|
|
- (void) setAutoscroll: (BOOL)flag
|
|
{
|
|
autoscroll = flag;
|
|
}
|
|
|
|
- (void) setScrollable: (BOOL)flag
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < numRows; i++)
|
|
{
|
|
NSArray* row = [cells objectAtIndex: i];
|
|
|
|
for (j = 0; j < numCols; j++)
|
|
[[row objectAtIndex: j] setScrollable: flag];
|
|
}
|
|
[cellPrototype setScrollable: flag];
|
|
}
|
|
|
|
- (void) drawRect: (NSRect)rect
|
|
{
|
|
int i, j;
|
|
int row1, col1; // The cell at the upper left corner
|
|
int row2, col2; // The cell at the lower right corner
|
|
|
|
if (drawsBackground)
|
|
{
|
|
// draw the background
|
|
[backgroundColor set];
|
|
NSRectFill(rect);
|
|
}
|
|
|
|
[self _getRow: &row1 column: &col1 forPoint: rect.origin
|
|
above: NO right: NO isBetweenCells: NULL];
|
|
[self _getRow: &row2 column: &col2
|
|
forPoint: NSMakePoint(NSMaxX(rect), NSMaxY(rect))
|
|
above: NO right: NO isBetweenCells: NULL];
|
|
|
|
if (row1 < 0)
|
|
row1 = 0;
|
|
if (col1 < 0)
|
|
col1 = 0;
|
|
|
|
/* Draw the cells within the drawing rectangle. */
|
|
for (i = row1; i <= row2 && i < numRows; i++)
|
|
for (j = col1; j <= col2 && j < numCols; j++)
|
|
[self drawCellAtRow: i column: j];
|
|
}
|
|
|
|
- (void) drawCellAtRow: (int)row column: (int)column
|
|
{
|
|
NSCell *aCell = [self cellAtRow: row column: column];
|
|
|
|
if (aCell)
|
|
{
|
|
NSRect cellFrame = [self cellFrameAtRow: row column: column];
|
|
|
|
if (drawsCellBackground)
|
|
{
|
|
[cellBackgroundColor set];
|
|
NSRectFill(cellFrame);
|
|
}
|
|
[aCell drawWithFrame: cellFrame inView: self];
|
|
}
|
|
}
|
|
|
|
- (void) highlightCell: (BOOL)flag atRow: (int)row column: (int)column
|
|
{
|
|
NSCell *aCell = [self cellAtRow: row column: column];
|
|
|
|
if (aCell)
|
|
{
|
|
NSRect cellFrame = [self cellFrameAtRow: row column: column];
|
|
|
|
if (drawsCellBackground)
|
|
{
|
|
[cellBackgroundColor set];
|
|
NSRectFill(cellFrame);
|
|
}
|
|
[aCell highlight: flag
|
|
withFrame: cellFrame
|
|
inView: self];
|
|
}
|
|
}
|
|
|
|
|
|
- (BOOL) sendAction: (SEL)theAction
|
|
to: (id)theTarget
|
|
{
|
|
if (theAction)
|
|
{
|
|
if (theTarget)
|
|
return [super sendAction: theAction to: theTarget];
|
|
else
|
|
return [super sendAction: theAction to: [self target]];
|
|
}
|
|
else
|
|
return [super sendAction: [self action] to: [self target]];
|
|
}
|
|
|
|
|
|
- (BOOL) sendAction
|
|
{
|
|
if (![selectedCell isEnabled])
|
|
return NO;
|
|
|
|
return [self sendAction: [selectedCell action] to: [selectedCell target]];
|
|
}
|
|
|
|
- (void) sendAction: (SEL)aSelector
|
|
to: (id)anObject
|
|
forAllCells: (BOOL)flag
|
|
{
|
|
int i, j;
|
|
|
|
if (flag)
|
|
{
|
|
for (i = 0; i < numRows; i++)
|
|
{
|
|
NSMutableArray* row = [cells objectAtIndex: i];
|
|
|
|
for (j = 0; j < numCols; j++)
|
|
if (![anObject performSelector: aSelector
|
|
withObject: [row objectAtIndex: j]])
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < numRows; i++)
|
|
{
|
|
BOOL* row = ((tMatrix)selectedCell)->matrix[i];
|
|
NSMutableArray* cellRow = [cells objectAtIndex: i];
|
|
|
|
for (j = 0; j < numCols; j++)
|
|
if (row[i])
|
|
if (![anObject performSelector: aSelector
|
|
withObject: [cellRow objectAtIndex: j]])
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) sendDoubleAction
|
|
{
|
|
if (![selectedCell isEnabled])
|
|
return;
|
|
|
|
if (doubleAction)
|
|
[target performSelector: doubleAction withObject: self];
|
|
else
|
|
[self sendAction];
|
|
}
|
|
|
|
- (BOOL) acceptsFirstMouse: (NSEvent*)theEvent
|
|
{
|
|
return mode == NSListModeMatrix ? NO : YES;
|
|
}
|
|
|
|
- (void) _mouseDownNonListMode: (NSEvent *)theEvent
|
|
{
|
|
BOOL mouseUpInCell = NO;
|
|
NSCell *highlightedCell = nil;
|
|
int highlightedRow = 0;
|
|
int highlightedColumn = 0;
|
|
NSCell *mouseCell;
|
|
int mouseRow;
|
|
int mouseColumn;
|
|
NSPoint mouseLocation;
|
|
NSRect mouseCellFrame;
|
|
unsigned eventMask = NSLeftMouseUpMask | NSLeftMouseDownMask
|
|
| NSMouseMovedMask | NSLeftMouseDraggedMask;
|
|
|
|
[self lockFocus];
|
|
|
|
if ((mode == NSRadioModeMatrix) && selectedCell)
|
|
{
|
|
[selectedCell setState: NSOffState];
|
|
[self drawCellAtRow: selectedRow column: selectedColumn];
|
|
[window flushWindow];
|
|
((tMatrix)selectedCells)->matrix[selectedRow][selectedColumn] = NO;
|
|
selectedCell = nil;
|
|
selectedRow = selectedColumn = -1;
|
|
}
|
|
|
|
while (!mouseUpInCell && ([theEvent type] != NSLeftMouseUp))
|
|
{
|
|
mouseLocation = [self convertPoint: [theEvent locationInWindow]
|
|
fromView: nil];
|
|
[self getRow: &mouseRow column: &mouseColumn forPoint: mouseLocation];
|
|
mouseCellFrame = [self cellFrameAtRow: mouseRow column: mouseColumn];
|
|
|
|
if (((mode == NSRadioModeMatrix) && ![self allowsEmptySelection])
|
|
|| [self mouse: mouseLocation inRect: mouseCellFrame])
|
|
{
|
|
mouseCell = [self cellAtRow: mouseRow column: mouseColumn];
|
|
|
|
selectedCell = mouseCell;
|
|
selectedRow = mouseRow;
|
|
selectedColumn = mouseColumn;
|
|
((tMatrix)selectedCells)->matrix[selectedRow][selectedColumn] = YES;
|
|
|
|
if (((mode == NSRadioModeMatrix) || (mode == NSHighlightModeMatrix))
|
|
&& (highlightedCell != mouseCell))
|
|
{
|
|
if (highlightedCell)
|
|
[self highlightCell: NO
|
|
atRow: highlightedRow
|
|
column: highlightedColumn];
|
|
|
|
highlightedCell = mouseCell;
|
|
highlightedRow = mouseRow;
|
|
highlightedColumn = mouseColumn;
|
|
[self highlightCell: YES
|
|
atRow: highlightedRow
|
|
column: highlightedColumn];
|
|
[window flushWindow];
|
|
}
|
|
|
|
mouseUpInCell = [mouseCell trackMouse: theEvent
|
|
inRect: mouseCellFrame
|
|
ofView: self
|
|
untilMouseUp: YES];
|
|
|
|
if (mode == NSHighlightModeMatrix)
|
|
{
|
|
[self highlightCell: NO
|
|
atRow: highlightedRow
|
|
column: highlightedColumn];
|
|
highlightedCell = nil;
|
|
[window flushWindow];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// mouse is not over a Cell
|
|
if (highlightedCell)
|
|
{
|
|
[self highlightCell: NO
|
|
atRow: highlightedRow
|
|
column: highlightedColumn];
|
|
highlightedCell = nil;
|
|
[window flushWindow];
|
|
}
|
|
}
|
|
|
|
// if mouse didn't go up, take next event
|
|
if (!mouseUpInCell)
|
|
theEvent = [NSApp nextEventMatchingMask: eventMask
|
|
untilDate: [NSDate distantFuture]
|
|
inMode: NSEventTrackingRunLoopMode
|
|
dequeue: YES];
|
|
}
|
|
|
|
// the mouse went up.
|
|
// if it was inside a cell, the cell has already sent the action.
|
|
// if not, selectedCell is the last cell that had the mouse, and
|
|
// it's state is Off. It must be set into a consistent state.
|
|
// anyway, the action has to be sent
|
|
if (!mouseUpInCell)
|
|
{
|
|
if ((mode == NSRadioModeMatrix) && !allowsEmptySelection)
|
|
{
|
|
[selectedCell setState: NSOnState];
|
|
[window flushWindow];
|
|
}
|
|
else
|
|
{
|
|
if (selectedCell)
|
|
{
|
|
((tMatrix)selectedCells)->matrix[selectedRow][selectedColumn]
|
|
= NO;
|
|
selectedCell = nil;
|
|
selectedRow = selectedColumn = -1;
|
|
}
|
|
}
|
|
[self sendAction];
|
|
}
|
|
|
|
if (highlightedCell)
|
|
{
|
|
[self highlightCell: NO
|
|
atRow: highlightedRow
|
|
column: highlightedColumn];
|
|
[window flushWindow];
|
|
}
|
|
|
|
[self unlockFocus];
|
|
}
|
|
|
|
|
|
- (void) mouseDown: (NSEvent*)theEvent
|
|
{
|
|
BOOL isBetweenCells, insideBounds;
|
|
int row, column;
|
|
unsigned eventMask = NSLeftMouseUpMask | NSLeftMouseDownMask
|
|
| NSMouseMovedMask | NSLeftMouseDraggedMask
|
|
| NSPeriodicMask;
|
|
NSPoint lastLocation = [theEvent locationInWindow];
|
|
NSEvent* lastEvent = nil;
|
|
BOOL done = NO;
|
|
NSRect rect;
|
|
id aCell, previousCell = nil, selectedCellTarget;
|
|
NSRect previousCellRect;
|
|
NSApplication *app = [NSApplication sharedApplication];
|
|
static MPoint anchor = {0, 0};
|
|
|
|
mouseDownFlags = [theEvent modifierFlags];
|
|
|
|
if (mode != NSListModeMatrix)
|
|
{
|
|
[self _mouseDownNonListMode: theEvent];
|
|
return;
|
|
}
|
|
|
|
//FIXME list mode is not working well. there is a flipping coordinates bug
|
|
// when selecting by rect. the code here should be cleaned to eliminate
|
|
// references to other modes.
|
|
|
|
lastLocation = [self convertPoint: lastLocation fromView: nil];
|
|
if ((mode != NSTrackModeMatrix) && (mode != NSHighlightModeMatrix))
|
|
[NSEvent startPeriodicEventsAfterDelay: 0.05 withPeriod: 0.05];
|
|
ASSIGN(lastEvent, theEvent);
|
|
|
|
[window _captureMouse: self]; // grab the mouse
|
|
[self lockFocus];
|
|
// selection involves two steps, first
|
|
// a loop that continues until the left mouse goes up; then a series of
|
|
// steps which send actions and display the cell as it should appear after
|
|
// the selection process is complete
|
|
while (!done)
|
|
{
|
|
BOOL shouldProceedEvent = NO;
|
|
|
|
insideBounds = [self _getRow: &row
|
|
column: &column
|
|
forPoint: lastLocation
|
|
above: NO right: NO
|
|
isBetweenCells: &isBetweenCells];
|
|
if (insideBounds && !isBetweenCells)
|
|
{
|
|
aCell = [self cellAtRow: row column: column];
|
|
rect = [self cellFrameAtRow: row column: column];
|
|
|
|
switch (mode)
|
|
{
|
|
case NSTrackModeMatrix:
|
|
// in Track mode the cell should track the mouse
|
|
// until the cursor either leaves the cellframe or
|
|
// NSLeftMouseUp occurs
|
|
selectedCell = aCell;
|
|
selectedRow = row;
|
|
selectedColumn = column;
|
|
if ([aCell trackMouse: lastEvent
|
|
inRect: rect
|
|
ofView: self
|
|
untilMouseUp: YES])
|
|
done = YES;
|
|
break;
|
|
|
|
case NSHighlightModeMatrix:
|
|
// Highlight mode is like Track mode except that
|
|
// the cell is lit before begins tracking and
|
|
// unlit afterwards
|
|
[aCell setState: 1];
|
|
selectedCell = aCell;
|
|
selectedRow = row;
|
|
selectedColumn = column;
|
|
[aCell highlight: YES withFrame: rect inView: self];
|
|
[window flushWindow];
|
|
|
|
if ([aCell trackMouse: lastEvent
|
|
inRect: rect
|
|
ofView: self
|
|
untilMouseUp: YES])
|
|
done = YES;
|
|
|
|
[aCell setState: 0];
|
|
[aCell highlight: NO withFrame: rect inView: self];
|
|
[window flushWindow];
|
|
break;
|
|
|
|
case NSRadioModeMatrix:
|
|
// Radio mode allows no more than one cell to be selected
|
|
if (previousCell == aCell)
|
|
break;
|
|
|
|
// deselect previously selected cell
|
|
if (selectedCell)
|
|
{
|
|
[selectedCell setState: 0];
|
|
if (!previousCell)
|
|
previousCellRect = [self cellFrameAtRow: selectedRow
|
|
column: selectedColumn];
|
|
[selectedCell highlight: NO
|
|
withFrame: previousCellRect
|
|
inView: self];
|
|
((tMatrix)selectedCells)->matrix[selectedRow][selectedColumn] = NO;
|
|
}
|
|
// select current cell
|
|
selectedCell = aCell;
|
|
selectedRow = row;
|
|
selectedColumn = column;
|
|
[aCell setState: 1];
|
|
[aCell highlight: YES withFrame: rect inView: self];
|
|
((tMatrix)selectedCells)->matrix[row][column] = YES;
|
|
[window flushWindow];
|
|
break;
|
|
|
|
case NSListModeMatrix:
|
|
// List mode allows multiple cells to be selected
|
|
{
|
|
unsigned modifiers = [lastEvent modifierFlags];
|
|
|
|
if (previousCell == aCell)
|
|
break;
|
|
// When the user first clicks on a cell
|
|
// we clear the existing selection
|
|
// unless the Alternate or Shift keys have been pressed.
|
|
if (!previousCell)
|
|
{
|
|
if (!(modifiers & NSShiftKeyMask) &&
|
|
!(modifiers & NSAlternateKeyMask))
|
|
{
|
|
[self deselectAllCells];
|
|
anchor = MakePoint (column, row);
|
|
}
|
|
// Consider the selected cell as the
|
|
// anchor from which to extend the
|
|
// selection to the current cell
|
|
if (!(modifiers & NSAlternateKeyMask))
|
|
{
|
|
selectedCell = aCell; // select current cell
|
|
selectedRow = row;
|
|
selectedColumn = column;
|
|
|
|
[selectedCell setState: 1];
|
|
[selectedCell highlight: YES
|
|
withFrame: rect
|
|
inView: self];
|
|
((tMatrix)selectedCells)->matrix[row][column] =YES;
|
|
[window flushWindow];
|
|
break;
|
|
}
|
|
}
|
|
if (selectionByRect)
|
|
[self _selectRectUsingAnchor: anchor
|
|
last: MakePoint (selectedColumn, selectedRow)
|
|
current: MakePoint (column, row)];
|
|
else
|
|
[self _selectContinuousUsingAnchor: anchor
|
|
last: MakePoint (selectedColumn, selectedRow)
|
|
current: MakePoint (column, row)];
|
|
|
|
[window flushWindow];
|
|
selectedCell = aCell;
|
|
selectedRow = row;
|
|
selectedColumn = column;
|
|
break;
|
|
}
|
|
}
|
|
previousCell = aCell;
|
|
previousCellRect = rect;
|
|
[self scrollRectToVisible: rect];
|
|
}
|
|
|
|
// if done break out of selection loop
|
|
if (done)
|
|
break;
|
|
|
|
while (!shouldProceedEvent)
|
|
{
|
|
theEvent = [app nextEventMatchingMask: eventMask
|
|
untilDate: [NSDate distantFuture]
|
|
inMode: NSEventTrackingRunLoopMode
|
|
dequeue: YES];
|
|
switch ([theEvent type])
|
|
{
|
|
case NSPeriodic:
|
|
NSDebugLog(@"NSMatrix: got NSPeriodic event\n");
|
|
shouldProceedEvent = YES;
|
|
break;
|
|
|
|
// Track and Highlight modes do not use
|
|
// periodic events so we must break out
|
|
// and check if the mouse is in a cell
|
|
case NSLeftMouseUp:
|
|
done = YES;
|
|
case NSLeftMouseDown:
|
|
default:
|
|
if ((mode == NSTrackModeMatrix) ||
|
|
(mode == NSHighlightModeMatrix))
|
|
shouldProceedEvent = YES;
|
|
NSDebugLog(@"NSMatrix: got event of type: %d\n",
|
|
[theEvent type]);
|
|
ASSIGN(lastEvent, theEvent);
|
|
continue;
|
|
}
|
|
}
|
|
lastLocation = [lastEvent locationInWindow];
|
|
lastLocation = [self convertPoint: lastLocation fromView: nil];
|
|
}
|
|
|
|
[window _releaseMouse: self];
|
|
|
|
switch (mode)
|
|
{
|
|
case NSRadioModeMatrix:
|
|
if (selectedCell)
|
|
[selectedCell highlight: NO withFrame: rect inView: self];
|
|
case NSListModeMatrix:
|
|
[self setNeedsDisplayInRect: rect];
|
|
[window flushWindow];
|
|
case NSHighlightModeMatrix:
|
|
case NSTrackModeMatrix:
|
|
break;
|
|
}
|
|
|
|
if (selectedCell)
|
|
{
|
|
// send single click action
|
|
if (!(selectedCellTarget = [selectedCell target]))
|
|
{
|
|
// selected cell has no target so send single
|
|
// click action to matrix's (self's) target
|
|
if (target)
|
|
[target performSelector: action withObject: self];
|
|
}
|
|
else
|
|
{
|
|
// in Track and Highlight modes the single
|
|
// click action has already been sent by the
|
|
// cell to it's target (if it has one)
|
|
if ((mode != NSTrackModeMatrix) && (mode != NSHighlightModeMatrix))
|
|
[selectedCellTarget performSelector: [selectedCell action]
|
|
withObject: self];
|
|
}
|
|
}
|
|
// click count > 1 indicates a double click
|
|
if (target && doubleAction && ([lastEvent clickCount] > 1))
|
|
[target performSelector: doubleAction withObject: self];
|
|
|
|
[self unlockFocus];
|
|
|
|
if ((mode != NSTrackModeMatrix) && (mode != NSHighlightModeMatrix))
|
|
[NSEvent stopPeriodicEvents];
|
|
|
|
[lastEvent release];
|
|
}
|
|
|
|
- (void) updateCell: (NSCell*)aCell
|
|
{
|
|
int row, col;
|
|
NSRect rect;
|
|
|
|
if ([self getRow: &row column: &col ofCell: aCell] == NO)
|
|
return; // Not a cell in this matrix - we can't update it.
|
|
|
|
rect = [self cellFrameAtRow: row column: col];
|
|
[self setNeedsDisplayInRect: rect];
|
|
}
|
|
|
|
- (BOOL) performKeyEquivalent: (NSEvent*)theEvent
|
|
{
|
|
int i, j;
|
|
NSMutableArray* row;
|
|
NSString* key = [theEvent charactersIgnoringModifiers];
|
|
|
|
for (i = 0; i < numRows; i++)
|
|
{
|
|
row = [cells objectAtIndex: i];
|
|
for (j = 0; j < numCols; j++)
|
|
{
|
|
NSCell* aCell = [row objectAtIndex: j];
|
|
|
|
if ([aCell isEnabled]
|
|
&& [[aCell keyEquivalent] isEqualToString: key])
|
|
{
|
|
NSCell* oldSelectedCell = selectedCell;
|
|
|
|
selectedCell = aCell;
|
|
[self highlightCell: YES atRow: i column: j];
|
|
[aCell setState: ![aCell state]];
|
|
[self sendAction];
|
|
[self highlightCell: NO atRow: i column: j];
|
|
selectedCell = oldSelectedCell;
|
|
|
|
return YES;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (void) resetCursorRects
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < numRows; i++)
|
|
{
|
|
NSArray* row = [cells objectAtIndex: i];
|
|
|
|
for (j = 0; j < numCols; j++)
|
|
{
|
|
NSCell* aCell = [row objectAtIndex: j];
|
|
[aCell resetCursorRect: [self cellFrameAtRow: i column: j]
|
|
inView: self];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) encodeWithCoder: (NSCoder*)aCoder
|
|
{
|
|
[super encodeWithCoder: aCoder];
|
|
}
|
|
|
|
- (id) initWithCoder: (NSCoder*)aDecoder
|
|
{
|
|
[super initWithCoder: aDecoder];
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void) setMode: (NSMatrixMode)aMode
|
|
{
|
|
mode = aMode;
|
|
}
|
|
- (NSMatrixMode) mode
|
|
{
|
|
return mode;
|
|
}
|
|
- (void) setCellClass: (Class)class
|
|
{
|
|
cellClass = class;
|
|
}
|
|
- (Class) cellClass
|
|
{
|
|
return cellClass;
|
|
}
|
|
- (void) setPrototype: (NSCell*)aCell
|
|
{
|
|
ASSIGN(cellPrototype, aCell);
|
|
}
|
|
- (id) prototype
|
|
{
|
|
return cellPrototype;
|
|
}
|
|
- (NSSize) cellSize
|
|
{
|
|
return cellSize;
|
|
}
|
|
- (NSSize) intercellSpacing
|
|
{
|
|
return intercell;
|
|
}
|
|
- (void) setBackgroundColor: (NSColor*)c
|
|
{
|
|
ASSIGN(backgroundColor, c);
|
|
}
|
|
- (NSColor*) backgroundColor
|
|
{
|
|
return backgroundColor;
|
|
}
|
|
- (void) setCellBackgroundColor: (NSColor*)c
|
|
{
|
|
ASSIGN(cellBackgroundColor, c);
|
|
}
|
|
- (NSColor*) cellBackgroundColor
|
|
{
|
|
return cellBackgroundColor;
|
|
}
|
|
- (void) setDelegate: (id)object
|
|
{
|
|
ASSIGN(delegate, object);
|
|
}
|
|
- (id) delegate
|
|
{
|
|
return delegate;
|
|
}
|
|
- (void) setTarget: anObject
|
|
{
|
|
ASSIGN(target, anObject);
|
|
}
|
|
- (id) target
|
|
{
|
|
return target;
|
|
}
|
|
- (void) setAction: (SEL)sel
|
|
{
|
|
action = sel;
|
|
}
|
|
- (SEL) action
|
|
{
|
|
return action;
|
|
}
|
|
- (void) setDoubleAction: (SEL)sel
|
|
{
|
|
doubleAction = sel;
|
|
}
|
|
- (SEL) doubleAction
|
|
{
|
|
return doubleAction;
|
|
}
|
|
- (void) setErrorAction: (SEL)sel
|
|
{
|
|
errorAction = sel;
|
|
}
|
|
- (SEL) errorAction
|
|
{
|
|
return errorAction;
|
|
}
|
|
- (void) setAllowsEmptySelection: (BOOL)f
|
|
{
|
|
allowsEmptySelection = f;
|
|
}
|
|
- (BOOL) allowsEmptySelection
|
|
{
|
|
return allowsEmptySelection;
|
|
}
|
|
- (void) setSelectionByRect: (BOOL)flag
|
|
{
|
|
selectionByRect = flag;
|
|
}
|
|
- (BOOL) isSelectionByRect
|
|
{
|
|
return selectionByRect;
|
|
}
|
|
- (void) setDrawsBackground: (BOOL)flag
|
|
{
|
|
drawsBackground = flag;
|
|
}
|
|
- (BOOL) drawsBackground
|
|
{
|
|
return drawsBackground;
|
|
}
|
|
- (void) setDrawsCellBackground: (BOOL)f
|
|
{
|
|
drawsCellBackground = f;
|
|
}
|
|
- (BOOL) drawsCellBackground
|
|
{
|
|
return drawsCellBackground;
|
|
}
|
|
- (void) setAutosizesCells: (BOOL)flag
|
|
{
|
|
autosizesCells = flag;
|
|
}
|
|
- (BOOL) autosizesCells
|
|
{
|
|
return autosizesCells;
|
|
}
|
|
- (BOOL) isAutoscroll
|
|
{
|
|
return autoscroll;
|
|
}
|
|
- (int) numberOfRows
|
|
{
|
|
return numRows;
|
|
}
|
|
- (int) numberOfColumns
|
|
{
|
|
return numCols;
|
|
}
|
|
- (id) selectedCell
|
|
{
|
|
return selectedCell;
|
|
}
|
|
- (int) selectedColumn
|
|
{
|
|
return selectedColumn;
|
|
}
|
|
- (int) selectedRow
|
|
{
|
|
return selectedRow;
|
|
}
|
|
- (int) mouseDownFlags
|
|
{
|
|
return mouseDownFlags;
|
|
}
|
|
|
|
#if HAS_FLIPPED_VIEWS
|
|
- (BOOL) isFlipped
|
|
{
|
|
return YES;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
//
|
|
// Methods that may not be needed FIX ME
|
|
//
|
|
|
|
/*
|
|
* Get characters until you encounter
|
|
* a carriage return, return number of characters.
|
|
* Deal with backspaces, etc. Deal with Expose events
|
|
* on all windows associated with this application.
|
|
* Deal with keyboard remapping.
|
|
*/
|
|
|
|
- (void) keyDown: (NSEvent *)theEvent;
|
|
{
|
|
unsigned int flags = [theEvent modifierFlags];
|
|
unsigned int key_code = [theEvent keyCode];
|
|
NSRect rect = [self cellFrameAtRow: selectedRow column: selectedColumn];
|
|
|
|
fprintf(stderr, " NSMatrix: keyDown --- ");
|
|
|
|
// If not editable then don't recognize the key down
|
|
if (![selectedCell isEditable]) return;
|
|
|
|
[self lockFocus];
|
|
|
|
// If RETURN key then make the next text the first responder
|
|
if (key_code == 0x0d)
|
|
{
|
|
[selectedCell endEditing: nil];
|
|
[selectedCell drawInteriorWithFrame: rect inView: self];
|
|
[self unlockFocus];
|
|
return;
|
|
}
|
|
|
|
// Hide the cursor during typing
|
|
[NSCursor hide];
|
|
|
|
[selectedCell _handleKeyEvent: theEvent];
|
|
[selectedCell drawInteriorWithFrame: rect inView: self];
|
|
[self unlockFocus];
|
|
}
|
|
|
|
- (BOOL) acceptsFirstResponder
|
|
{
|
|
if ([selectedCell isSelectable])
|
|
return YES;
|
|
else
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL) becomeFirstResponder
|
|
{
|
|
if ([selectedCell isSelectable])
|
|
{
|
|
// [selectedCell selectText: self];
|
|
return YES;
|
|
}
|
|
else
|
|
{
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation NSMatrix (PrivateMethods)
|
|
|
|
#define SET_POINTER_VALUE(pointer, value) \
|
|
do { if (pointer) *pointer = value; } while (0)
|
|
|
|
/* Returns by reference the row and column of cell that is above or below or
|
|
to the left or to the right of point. `point' is in the matrix coordinates.
|
|
Returns NO if the point is outside the bounds of matrix, YES otherwise.
|
|
|
|
Note that the cell numbering is flipped relative to the coordinate system.
|
|
*/
|
|
- (BOOL) _getRow: (int*)row
|
|
column: (int*)column
|
|
forPoint: (NSPoint)point
|
|
above: (BOOL)aboveRequired
|
|
right: (BOOL)rightRequired
|
|
isBetweenCells: (BOOL*)isBetweenCells
|
|
{
|
|
BOOL rowReady = NO, colReady = NO;
|
|
BOOL betweenRows = NO, betweenCols = NO;
|
|
NSRect theBounds = [self bounds];
|
|
|
|
SET_POINTER_VALUE(isBetweenCells, NO);
|
|
|
|
/* First check the limit cases */
|
|
if (point.x > theBounds.size.width)
|
|
{
|
|
SET_POINTER_VALUE(column, numCols - 1);
|
|
colReady = YES;
|
|
}
|
|
else if (point.x < 0)
|
|
{
|
|
SET_POINTER_VALUE(column, 0);
|
|
colReady = YES;
|
|
}
|
|
|
|
if (point.y > theBounds.size.height)
|
|
{
|
|
SET_POINTER_VALUE(row, numRows - 1);
|
|
rowReady = YES;
|
|
}
|
|
else if (point.y < 0)
|
|
{
|
|
SET_POINTER_VALUE(row, 0);
|
|
rowReady = YES;
|
|
}
|
|
|
|
if (rowReady && colReady)
|
|
return NO;
|
|
|
|
if (!rowReady)
|
|
{
|
|
int approxRow = point.y / (cellSize.height + intercell.height);
|
|
float approxRowsHeight = approxRow * (cellSize.height + intercell.height);
|
|
|
|
/* Determine if the point is inside the cell */
|
|
betweenRows = !(point.y > approxRowsHeight
|
|
&& point.y <= approxRowsHeight + cellSize.height);
|
|
|
|
/* If the point is between cells then adjust the computed row taking into
|
|
account the `aboveRequired' flag. */
|
|
if (aboveRequired && betweenRows)
|
|
approxRow++;
|
|
|
|
#if HAS_FLIPPED_VIEWS == 0
|
|
approxRow = numRows - approxRow - 1;
|
|
#endif
|
|
if (approxRow < 0)
|
|
{
|
|
approxRow = -1;
|
|
rowReady = YES;
|
|
}
|
|
else if (approxRow >= numRows)
|
|
{
|
|
approxRow = numRows - 1;
|
|
rowReady = YES;
|
|
}
|
|
SET_POINTER_VALUE(row, approxRow);
|
|
}
|
|
|
|
if (!colReady)
|
|
{
|
|
int approxCol = point.x / (cellSize.width + intercell.width);
|
|
float approxColsWidth = approxCol * (cellSize.width + intercell.width);
|
|
|
|
/* Determine if the point is inside the cell */
|
|
betweenCols = !(point.x > approxColsWidth
|
|
&& point.x <= approxColsWidth + cellSize.width);
|
|
|
|
/* If the point is between cells then adjust the computed column taking
|
|
into account the `rightRequired' flag. */
|
|
if (rightRequired && betweenCols)
|
|
approxCol++;
|
|
|
|
if (approxCol < 0)
|
|
{
|
|
approxCol = -1;
|
|
colReady = YES;
|
|
}
|
|
else if (approxCol >= numCols)
|
|
{
|
|
approxCol = numCols - 1;
|
|
colReady = YES;
|
|
}
|
|
SET_POINTER_VALUE(column, approxCol);
|
|
}
|
|
|
|
/* If the point is outside the matrix bounds return NO */
|
|
if (rowReady || colReady)
|
|
return NO;
|
|
|
|
if (betweenRows || betweenCols)
|
|
SET_POINTER_VALUE(isBetweenCells, YES);
|
|
return YES;
|
|
}
|
|
|
|
/* This method is used to select cells in the list mode with selection by rect
|
|
option enabled. `anchor' is the first point in the selection (the coordinates
|
|
of the cell first clicked). `last' is the last point up to which the anterior
|
|
selection has been made. `current' is the point to which we must extend the
|
|
selection. */
|
|
|
|
- (void) _selectRectUsingAnchor: (MPoint)anchor
|
|
last: (MPoint)last
|
|
current: (MPoint)current
|
|
{
|
|
/* We use an imaginar coordinate system whose center is the `anchor' point.
|
|
We should determine in which quadrants are located the `last' and the
|
|
`current' points. Based on this we extend the selection to the rectangle
|
|
determined by `anchor' and `current' points.
|
|
|
|
The algorithm uses two rectangles: one determined by `anchor' and
|
|
`current' that defines how the final selection rectangle will look, and
|
|
another one determined by `anchor' and `last' that defines the current
|
|
visible selection.
|
|
|
|
The three points above determine 9 distinct zones depending on the
|
|
position of `last' and `current' relative to `anchor'. Each of these
|
|
zones have a different way of extending the selection from `last' to
|
|
`current'.
|
|
|
|
Note the coordinate system is a flipped one not a usual geometric one
|
|
(the y coordinate increases downward).
|
|
*/
|
|
|
|
int dxca = current.x - anchor.x;
|
|
int dyca = current.y - anchor.y;
|
|
int dxla = last.x - anchor.x;
|
|
int dyla = last.y - anchor.y;
|
|
int dxca_dxla, dyca_dyla;
|
|
int selectRectsNo = 0;
|
|
MRect selectRect[2];
|
|
int unselectRectsNo = 0;
|
|
MRect unselectRect[2];
|
|
int tmpx, tmpy;
|
|
int i;
|
|
|
|
dxca_dxla = SIGN(dxca) / (SIGN(dxla) ? SIGN(dxla) : 1);
|
|
dyca_dyla = SIGN(dyca) / (SIGN(dyla) ? SIGN(dyla) : 1);
|
|
|
|
if (dxca_dxla >= 0) {
|
|
if (dyca_dyla >= 0) {
|
|
/* `current' is in the lower right quadrant. */
|
|
if (ABS(dxca) <= ABS(dxla)) {
|
|
if (ABS(dyca) <= ABS(dyla)) {
|
|
/* `current' is in zone I. */
|
|
|
|
NSDebugLog (@"zone I");
|
|
|
|
if (dxca != dxla) {
|
|
i = unselectRectsNo++;
|
|
tmpx = dxca > 0 ? current.x + 1 : current.x + SIGN(dxla);
|
|
unselectRect[i].x = MIN(tmpx, last.x);
|
|
unselectRect[i].y = MIN(anchor.y, current.y);
|
|
unselectRect[i].width = ABS(last.x - tmpx);
|
|
unselectRect[i].height = ABS(current.y - anchor.y);
|
|
}
|
|
|
|
if (dyca != dyla) {
|
|
i = unselectRectsNo++;
|
|
tmpy = dyca > 0 ? current.y + 1 : current.y + SIGN(dyla);
|
|
unselectRect[i].x = MIN(anchor.x, last.x);
|
|
unselectRect[i].y = MIN(tmpy, last.y);
|
|
unselectRect[i].width = ABS(last.x - anchor.x);
|
|
unselectRect[i].height = ABS(last.y - tmpy);
|
|
}
|
|
}
|
|
else {
|
|
/* `current' is in zone F. */
|
|
|
|
NSDebugLog (@"zone F");
|
|
|
|
selectRectsNo = 1;
|
|
|
|
tmpy = dyla >= 0 ? last.y + 1 : last.y - 1;
|
|
selectRect[0].x = MIN(anchor.x, current.x);
|
|
selectRect[0].y = MIN(tmpy, current.y);
|
|
selectRect[0].width = ABS(current.x - anchor.x);
|
|
selectRect[0].height = ABS(current.y - tmpy);
|
|
|
|
if (dxca != dxla) {
|
|
unselectRectsNo = 1;
|
|
tmpx = dxca > 0 ? current.x + 1 : current.x + SIGN(dxla);
|
|
unselectRect[0].x = MIN(tmpx, last.x);
|
|
unselectRect[0].y = MIN(anchor.y, last.y);
|
|
unselectRect[0].width = ABS(last.x - tmpx);
|
|
unselectRect[0].height = ABS(last.y - anchor.y);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (ABS(dyca) <= ABS(dyla)) {
|
|
/* `current' is in zone H. */
|
|
|
|
NSDebugLog (@"zone H");
|
|
selectRectsNo = 1;
|
|
|
|
tmpx = dxla >= 0 ? last.x + 1 : last.x - 1;
|
|
selectRect[0].x = MIN(tmpx, current.x);
|
|
selectRect[0].y = MIN(anchor.y, current.y);
|
|
selectRect[0].width = ABS(current.x - tmpx);
|
|
selectRect[0].height = ABS(current.y - anchor.y);
|
|
|
|
if (dyca != dyla) {
|
|
unselectRectsNo = 1;
|
|
|
|
tmpy = dyca >= 0 ? current.y + 1 : current.y - 1;
|
|
unselectRect[0].x = MIN(anchor.x, last.x);
|
|
unselectRect[0].y = MIN(tmpy, last.y);
|
|
unselectRect[0].width = ABS(last.x - anchor.x);
|
|
unselectRect[0].height = ABS(last.y - tmpy);
|
|
}
|
|
}
|
|
else {
|
|
/* `current' is in zone G. */
|
|
|
|
NSDebugLog (@"zone G");
|
|
selectRectsNo = 2;
|
|
|
|
tmpx = dxla >= 0 ? last.x + 1 : last.x - 1;
|
|
selectRect[0].x = MIN(tmpx, current.x);
|
|
selectRect[0].y = MIN(anchor.y, last.y);
|
|
selectRect[0].width = ABS(current.x - tmpx);
|
|
selectRect[0].height = ABS(last.y - anchor.y);
|
|
|
|
tmpy = dyla >= 0 ? last.y + 1 : last.y - 1;
|
|
selectRect[1].x = MIN(anchor.x, current.x);
|
|
selectRect[1].y = MIN(tmpy, current.y);
|
|
selectRect[1].width = ABS(current.x - anchor.x);
|
|
selectRect[1].height = ABS(current.y - tmpy);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* `current' is in the upper right quadrant */
|
|
|
|
if (ABS(dxca) <= ABS(dxla)) {
|
|
/* `current' is in zone B. */
|
|
|
|
NSDebugLog (@"zone B");
|
|
|
|
selectRectsNo = 1;
|
|
tmpy = dyca > 0 ? anchor.y + 1 : anchor.y - 1;
|
|
selectRect[0].x = MIN(anchor.x, current.x);
|
|
selectRect[0].y = MIN(current.y, tmpy);
|
|
selectRect[0].width = ABS(current.x - anchor.x);
|
|
selectRect[0].height = ABS(tmpy - current.y);
|
|
|
|
if (dyla) {
|
|
unselectRectsNo = 1;
|
|
tmpy = dyca < 0 ? anchor.y + 1 : anchor.y + SIGN(dyla);
|
|
unselectRect[0].x = MIN(anchor.x, current.x);
|
|
unselectRect[0].y = MIN(tmpy, last.y);
|
|
unselectRect[0].width = ABS(last.x - anchor.x);
|
|
unselectRect[0].height = ABS(last.y - tmpy);
|
|
}
|
|
|
|
if (dxla && dxca != dxla) {
|
|
i = unselectRectsNo++;
|
|
tmpx = dxca > 0 ? current.x + 1 : current.x + SIGN(dxla);
|
|
unselectRect[i].x = MIN(tmpx, last.x);
|
|
unselectRect[i].y = MIN(anchor.y, last.y);
|
|
unselectRect[i].width = ABS(last.x - tmpx);
|
|
unselectRect[i].height = ABS(last.y - anchor.y);
|
|
}
|
|
}
|
|
else {
|
|
/* `current' is in zone A. */
|
|
|
|
NSDebugLog (@"zone A");
|
|
|
|
if (dyca != dyla) {
|
|
i = selectRectsNo++;
|
|
tmpy = dyca < 0 ? anchor.y - 1 : anchor.y + 1;
|
|
selectRect[i].x = MIN(anchor.x, last.x);
|
|
selectRect[i].y = MIN(tmpy, current.y);
|
|
selectRect[i].width = ABS(last.x - anchor.x);
|
|
selectRect[i].height = ABS(current.y - tmpy);
|
|
}
|
|
|
|
i = selectRectsNo++;
|
|
tmpx = dxca > 0 ? last.x + 1 : last.x - 1;
|
|
selectRect[i].x = MIN(tmpx, current.x);
|
|
selectRect[i].y = MIN(current.y, anchor.y);
|
|
selectRect[i].width = ABS(current.x - tmpx);
|
|
selectRect[i].height = ABS(anchor.y - current.y);
|
|
|
|
if (dyla) {
|
|
unselectRectsNo = 1;
|
|
tmpy = dyca < 0 ? anchor.y + 1 : anchor.y - 1;
|
|
unselectRect[0].x = MIN(anchor.x, last.x);
|
|
unselectRect[0].y = MIN(tmpy, last.y);
|
|
unselectRect[0].width = ABS(last.x - anchor.x);
|
|
unselectRect[0].height = ABS(last.y - tmpy);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (dyca_dyla > 0) {
|
|
/* `current' is in the lower left quadrant */
|
|
if (ABS(dyca) <= ABS(dyla)) {
|
|
/* `current' is in zone D. */
|
|
|
|
NSDebugLog (@"zone D");
|
|
selectRectsNo = 1;
|
|
|
|
tmpx = dxca < 0 ? anchor.x - 1 : anchor.x + 1;
|
|
selectRect[0].x = MIN(tmpx, current.x);
|
|
selectRect[0].y = MIN(anchor.y, current.y);
|
|
selectRect[0].width = ABS(current.x - tmpx);
|
|
selectRect[0].height = ABS(current.y - anchor.y);
|
|
|
|
if (dxla) {
|
|
unselectRectsNo = 1;
|
|
tmpx = dxca < 0 ? anchor.x + 1 : anchor.x - 1;
|
|
unselectRect[0].x = MIN(tmpx, last.x);
|
|
unselectRect[0].y = MIN(anchor.y, current.y);
|
|
unselectRect[0].width = ABS(last.x - tmpx);
|
|
unselectRect[0].height = ABS(current.y - anchor.y);
|
|
}
|
|
|
|
if (dyla && dyca != dyla) {
|
|
i = unselectRectsNo++;
|
|
tmpy = dyca > 0 ? current.y + 1 : current.y + SIGN(dyla);
|
|
unselectRect[i].x = MIN(anchor.x, last.x);
|
|
unselectRect[i].y = MIN(tmpy, last.y);
|
|
unselectRect[i].width = ABS(last.x - anchor.x);
|
|
unselectRect[i].height = ABS(last.y - tmpy);
|
|
}
|
|
}
|
|
else {
|
|
/* `current' is in zone E. */
|
|
|
|
NSDebugLog (@"zone E");
|
|
|
|
i = selectRectsNo++;
|
|
tmpx = dxca > 0 ? anchor.x + 1 : anchor.x - 1;
|
|
selectRect[i].x = MIN(tmpx, current.x);
|
|
selectRect[i].y = MIN(anchor.y, last.y);
|
|
selectRect[i].width = ABS(current.x - tmpx);
|
|
selectRect[i].height = ABS(last.y - anchor.y);
|
|
|
|
i = selectRectsNo++;
|
|
tmpy = dyca > 0 ? last.y + 1 : last.y - 1;
|
|
selectRect[i].x = MIN(current.x, anchor.x);
|
|
selectRect[i].y = MIN(current.y, tmpy);
|
|
selectRect[i].width = ABS(anchor.x - current.x);
|
|
selectRect[i].height = ABS(tmpy - current.y);
|
|
|
|
if (dxla) {
|
|
unselectRectsNo = 1;
|
|
tmpx = dxca > 0 ? anchor.x - 1 : anchor.x + 1;
|
|
unselectRect[0].x = MIN(tmpx, last.x);
|
|
unselectRect[0].y = MIN(anchor.y, last.y);
|
|
unselectRect[0].width = ABS(last.x - tmpx);
|
|
unselectRect[0].height = ABS(last.y - anchor.y);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* `current' is in zone C. */
|
|
|
|
NSDebugLog (@"zone C");
|
|
selectRectsNo = 1;
|
|
|
|
selectRect[0].x = MIN(current.x, anchor.x);
|
|
selectRect[0].y = MIN(current.y, anchor.y);
|
|
selectRect[0].width = ABS(anchor.x - current.x);
|
|
selectRect[0].height = ABS(anchor.y - current.y);
|
|
|
|
if (dyca != dyla) {
|
|
unselectRectsNo = 1;
|
|
unselectRect[0].x = MIN(anchor.x, last.x);
|
|
unselectRect[0].y = MIN(anchor.y, last.y);
|
|
unselectRect[0].width = ABS(last.x - anchor.x);
|
|
unselectRect[0].height = ABS(last.y - anchor.y);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* In this point we know what are the rectangles that must be unselected and
|
|
those that must be selected. Iterate on them and do the work. First unselect
|
|
and only then do the cells selection. */
|
|
for (i = 0; i < unselectRectsNo; i++)
|
|
[self _setState: 0 inRect: unselectRect[i]];
|
|
for (i = 0; i < selectRectsNo; i++)
|
|
[self _setState: 1 inRect: selectRect[i]];
|
|
}
|
|
|
|
- (void) _setState: (int)state inRect: (MRect)matrixRect
|
|
{
|
|
int i, j, rowNo, colNo;
|
|
NSArray* row;
|
|
NSCell* aCell;
|
|
NSRect rect, upperLeftRect;
|
|
BOOL highlight = state ? YES : NO;
|
|
int cellsCount = [cells count];
|
|
int rowCount;
|
|
|
|
rect = upperLeftRect = [self cellFrameAtRow: matrixRect.y column: matrixRect.x];
|
|
|
|
for (i = 0, rowNo = matrixRect.y;
|
|
i <= matrixRect.height && rowNo < cellsCount;
|
|
i++, rowNo++) {
|
|
row = [cells objectAtIndex: rowNo];
|
|
rowCount = [row count];
|
|
rect.origin.x = upperLeftRect.origin.x;
|
|
|
|
for (j = 0, colNo = matrixRect.x;
|
|
j <= matrixRect.width && colNo < rowCount;
|
|
j++, colNo++) {
|
|
aCell = [row objectAtIndex: colNo];
|
|
[aCell setState: state];
|
|
[aCell highlight: highlight withFrame: rect inView: self];
|
|
[self setNeedsDisplayInRect: rect];
|
|
((tMatrix)selectedCells)->matrix[rowNo][colNo] = YES;
|
|
rect.origin.x += cellSize.width + intercell.width;
|
|
}
|
|
rect.origin.y -= cellSize.height + intercell.height;
|
|
}
|
|
}
|
|
|
|
|
|
/* This method is used to select and unselect the cells in the list mode with
|
|
selection by rect option disabled. This method has a far lower complexity than
|
|
the similar method used by list mode with selection by rect option. */
|
|
- (void) _selectContinuousUsingAnchor: (MPoint)anchor
|
|
last: (MPoint)last
|
|
current: (MPoint)current
|
|
{
|
|
/* The idea is to compare the points based on their linear index in matrix and
|
|
do the appropriate action. */
|
|
|
|
int anchorIndex = INDEX_FROM_POINT(anchor);
|
|
int lastIndex = INDEX_FROM_POINT(last);
|
|
int currentIndex = INDEX_FROM_POINT(current);
|
|
BOOL doSelect = NO;
|
|
MPoint selectPoint;
|
|
BOOL doUnselect = NO;
|
|
MPoint unselectPoint;
|
|
|
|
int dca = currentIndex - anchorIndex;
|
|
int dla = lastIndex - anchorIndex;
|
|
int dca_dla = SIGN(dca) / (SIGN(dla) ? SIGN(dla) : 1);
|
|
|
|
if (dca_dla >= 0) {
|
|
if (ABS(dca) >= ABS(dla)) {
|
|
doSelect = YES;
|
|
if (currentIndex > lastIndex) {
|
|
selectPoint.x = lastIndex;
|
|
selectPoint.y = currentIndex;
|
|
}
|
|
else {
|
|
selectPoint.x = currentIndex;
|
|
selectPoint.y = lastIndex;
|
|
}
|
|
}
|
|
else {
|
|
doUnselect = YES;
|
|
if (currentIndex < lastIndex) {
|
|
unselectPoint.x = currentIndex + 1;
|
|
unselectPoint.y = lastIndex;
|
|
}
|
|
else {
|
|
unselectPoint.x = lastIndex;
|
|
unselectPoint.y = currentIndex - 1;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
doSelect = YES;
|
|
if (anchorIndex < currentIndex) {
|
|
selectPoint.x = anchorIndex;
|
|
selectPoint.y = currentIndex;
|
|
}
|
|
else {
|
|
selectPoint.x = currentIndex;
|
|
selectPoint.y = anchorIndex;
|
|
}
|
|
|
|
doUnselect = YES;
|
|
if (anchorIndex < lastIndex) {
|
|
unselectPoint.x = anchorIndex;
|
|
unselectPoint.y = lastIndex;
|
|
}
|
|
else {
|
|
unselectPoint.x = lastIndex;
|
|
unselectPoint.y = anchorIndex;
|
|
}
|
|
}
|
|
|
|
if (doUnselect)
|
|
[self _setState: 0 startIndex: unselectPoint.x endIndex: unselectPoint.y];
|
|
if (doSelect)
|
|
[self _setState: 1 startIndex: selectPoint.x endIndex: selectPoint.y];
|
|
}
|
|
|
|
- (void) _setState: (int)state startIndex: (int)start endIndex: (int)end
|
|
{
|
|
int i, j, colLimit;
|
|
NSArray* row;
|
|
NSCell* aCell;
|
|
NSRect rect, upperLeftRect;
|
|
BOOL highlight = state ? YES : NO;
|
|
MPoint startPoint = POINT_FROM_INDEX(start);
|
|
MPoint endPoint = POINT_FROM_INDEX(end);
|
|
|
|
rect = upperLeftRect = [self cellFrameAtRow: startPoint.y column: 0];
|
|
|
|
for (i = startPoint.y; i <= endPoint.y; i++) {
|
|
row = [cells objectAtIndex: i];
|
|
|
|
if (i == startPoint.y) {
|
|
j = startPoint.x;
|
|
rect.origin.x = upperLeftRect.origin.x
|
|
+ j * (cellSize.width + intercell.width);
|
|
}
|
|
else {
|
|
j = 0;
|
|
rect.origin.x = upperLeftRect.origin.x;
|
|
}
|
|
|
|
if (i == endPoint.y)
|
|
colLimit = endPoint.x;
|
|
else
|
|
colLimit = numCols - 1;
|
|
|
|
for (; j <= colLimit; j++) {
|
|
aCell = [row objectAtIndex: j];
|
|
[aCell setState: state];
|
|
[aCell highlight: highlight withFrame: rect inView: self];
|
|
[self setNeedsDisplayInRect: rect];
|
|
((tMatrix)selectedCells)->matrix[i][j] = YES;
|
|
rect.origin.x += cellSize.width + intercell.width;
|
|
}
|
|
rect.origin.y -= cellSize.height + intercell.height;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
#include <stdio.h>
|
|
|
|
/* A test to exhaustively check if the list selection mode works correctly. */
|
|
- (void) _selectRect2UsingAnchor: (MPoint)anchor
|
|
last: (MPoint)last
|
|
current: (MPoint)current
|
|
{
|
|
MRect selectRect;
|
|
MRect unselectRect;
|
|
|
|
selectRect.x = MIN(anchor.x, current.x);
|
|
selectRect.y = MIN(anchor.y, current.y);
|
|
selectRect.width = ABS(current.x - anchor.x);
|
|
selectRect.height = ABS(current.y - anchor.y);
|
|
|
|
unselectRect.x = MIN(anchor.x, last.x);
|
|
unselectRect.y = MIN(anchor.y, last.y);
|
|
unselectRect.width = ABS(current.x - last.x);
|
|
unselectRect.height = ABS(current.y - last.y);
|
|
|
|
[self _setState: 0 inRect: unselectRect];
|
|
[self _setState: 1 inRect: selectRect];
|
|
}
|
|
|
|
/* This method assumes the receiver matrix has at least 5 rows and 5 columns.
|
|
*/
|
|
- (void) _test
|
|
{
|
|
NSArray* selectedCellsByMethod1;
|
|
NSArray* selectedCellsByMethod2;
|
|
NSAutoreleasePool* pool;
|
|
MPoint anchor, last, current;
|
|
int i = 1;
|
|
int noOfErrors = 0;
|
|
|
|
if (numRows < 5 || numCols < 5) {
|
|
NSLog (@"matrix should have at least 5 rows and 5 columns!");
|
|
return;
|
|
}
|
|
|
|
for (anchor.x = 0; anchor.x < 5; anchor.x++)
|
|
for (anchor.y = 0; anchor.y < 5; anchor.y++)
|
|
for (last.x = 0; last.x < 5; last.x++)
|
|
for (last.y = 0; last.y < 5; last.y++)
|
|
for (current.x = 0; current.x < 5; current.x++)
|
|
for (current.y = 0; current.y < 5; current.y++) {
|
|
pool = [NSAutoreleasePool new];
|
|
|
|
printf ("%d\r", i++);
|
|
fflush (stdout);
|
|
|
|
/* First determine the selected cells using the sure method */
|
|
[self _selectRect2UsingAnchor: anchor last: last current: current];
|
|
selectedCellsByMethod2 = [self selectedCells];
|
|
|
|
/* Then determine the same using the optimized method */
|
|
[self _selectRectUsingAnchor: anchor last: last current: current];
|
|
selectedCellsByMethod1 = [self selectedCells];
|
|
|
|
/* Compare the selected cells determined by the two methods */
|
|
if (![selectedCellsByMethod1 isEqual: selectedCellsByMethod2]) {
|
|
NSLog (@"\nSelected cells are different for: \n"
|
|
@"anchor = (%d, %d)\nlast = (%d, %d)\ncurrent = (%d, %d)",
|
|
anchor.x, anchor.y, last.x, last.y, current.x, current.y);
|
|
noOfErrors++;
|
|
}
|
|
|
|
[pool release];
|
|
}
|
|
|
|
printf ("\nready!\nnumber of errors = %d\n", noOfErrors);
|
|
fflush (stdout);
|
|
}
|
|
#endif
|
|
|
|
@end
|