|
|
1.1 root 1: // NiftyMatrix.m
2: // By Jayson Adams, NeXT Developer Support Team
3: // You may freely copy, distribute and reuse the code in this example.
4: // NeXT disclaims any warranty of any kind, expressed or implied, as to its
5: // fitness for any particular use.
6:
7: #import <dpsclient/psops.h>
8: #import <dpsclient/wraps.h>
9: #import <appkit/timer.h>
10: #import <appkit/Cell.h>
11: #import <appkit/Window.h>
12: #import <appkit/Application.h>
13:
14: #import "NiftyMatrix.h"
15:
16:
17: @implementation NiftyMatrix
18:
19:
20: /* #defines stolen from Draw */
21:
22: #define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.01);
23:
24: #define stopTimer(timer) if (timer) { \
25: NXEndTimer(timer); \
26: timer = NULL; \
27: }
28:
29: #define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK
30:
31:
32: /* instance methods */
33:
34: - free
35: {
36: [matrixCache free];
37: [cellCache free];
38:
39: return [super free];
40: }
41:
42: - mouseDown:(NXEvent *)theEvent
43: {
44: NXPoint mouseDownLocation, mouseUpLocation, mouseLocation;
45: int eventMask, row, column, newRow;
46: NXRect visibleRect, cellCacheBounds, cellFrame;
47: id matrixCacheContentView, cellCacheContentView;
48: float dy;
49: NXEvent *event, peek;
50: NXTrackingTimer *timer = NULL;
51: BOOL scrolled = NO;
52:
53: /* if the Control key isn't down, show normal behavior */
54: if (!(theEvent->flags & NX_CONTROLMASK)) {
55: return [super mouseDown:theEvent];
56: }
57:
58: /* prepare the cell and matrix cache windows */
59: [self setupCacheWindows];
60:
61: /* we're now interested in mouse dragged events */
62: eventMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
63:
64: /* find the cell that got clicked on and select it */
65: mouseDownLocation = theEvent->location;
66: [self convertPoint:&mouseDownLocation fromView:nil];
67: [self getRow:&row andCol:&column forPoint:&mouseDownLocation];
68: activeCell = [self cellAt:row :column];
69: [self selectCell:activeCell];
70: [self getCellFrame:&cellFrame at:row :column];
71:
72: /* do whatever's required for a single-click */
73: [self sendAction];
74:
75: /* draw a "well" in place of the selected cell (see drawSelf::) */
76: [self display:&cellFrame :1];
77:
78: /* copy what's currently visible into the matrix cache */
79: matrixCacheContentView = [matrixCache contentView];
80: [matrixCacheContentView lockFocus];
81: [self getVisibleRect:&visibleRect];
82: [self convertRect:&visibleRect toView:nil];
83: PScomposite(NX_X(&visibleRect), NX_Y(&visibleRect),
84: NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect),
85: [window gState], 0.0, NX_HEIGHT(&visibleRect), NX_COPY);
86: [matrixCacheContentView unlockFocus];
87:
88: /* image the cell into its cache */
89: cellCacheContentView = [cellCache contentView];
90: [cellCacheContentView lockFocus];
91: [cellCacheContentView getBounds:&cellCacheBounds];
92: [activeCell drawSelf:&cellCacheBounds inView:cellCacheContentView];
93: [cellCacheContentView unlockFocus];
94:
95: /* save the mouse's location relative to the cell's origin */
96: dy = mouseDownLocation.y - cellFrame.origin.y;
97:
98: /* from now on we'll be drawing into ourself */
99: [self lockFocus];
100:
101: event = theEvent;
102: while (event->type != NX_MOUSEUP) {
103:
104: /* erase the active cell using the image in the matrix cache */
105: [self getVisibleRect:&visibleRect];
106: PScomposite(NX_X(&cellFrame), NX_HEIGHT(&visibleRect) -
107: NX_Y(&cellFrame) + NX_Y(&visibleRect) -
108: NX_HEIGHT(&cellFrame), NX_WIDTH(&cellFrame),
109: NX_HEIGHT(&cellFrame), [matrixCache gState],
110: NX_X(&cellFrame), NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame),
111: NX_COPY);
112:
113: /* move the active cell */
114: mouseLocation = event->location;
115: [self convertPoint:&mouseLocation fromView:nil];
116: cellFrame.origin.y = mouseLocation.y - dy;
117:
118: /* constrain the cell's location to our bounds */
119: if (NX_Y(&cellFrame) < NX_X(&bounds) ) {
120: cellFrame.origin.y = NX_X(&bounds);
121: } else if (NX_MAXY(&cellFrame) > NX_MAXY(&bounds)) {
122: cellFrame.origin.y = NX_HEIGHT(&bounds) - NX_HEIGHT(&cellFrame);
123: }
124:
125: /*
126: * make sure the cell will be entirely visible in its new location (if
127: * we're in a scrollView, it may not be)
128: */
129: if (!NXContainsRect(&visibleRect, &cellFrame) && mFlags.autoscroll) {
130: /*
131: * the cell won't be entirely visible, so scroll, dood, scroll, but
132: * don't display on-screen yet
133: */
134: [window disableFlushWindow];
135: [self scrollRectToVisible:&cellFrame];
136: [window reenableFlushWindow];
137:
138: /* copy the new image to the matrix cache */
139: [matrixCacheContentView lockFocus];
140: [self getVisibleRect:&visibleRect];
141: [self convertRectFromSuperview:&visibleRect];
142: [self convertRect:&visibleRect toView:nil];
143: PScomposite(NX_X(&visibleRect), NX_Y(&visibleRect),
144: NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect),
145: [window gState], 0.0, NX_HEIGHT(&visibleRect),
146: NX_COPY);
147: [matrixCacheContentView unlockFocus];
148:
149: /*
150: * note that we scrolled and start generating timer events for
151: * autoscrolling
152: */
153: scrolled = YES;
154: startTimer(timer);
155: } else {
156: /* no scrolling, so stop any timer */
157: stopTimer(timer);
158: }
159:
160: /* composite the active cell's image on top of ourself */
161: PScomposite(0.0, 0.0, NX_WIDTH(&cellFrame), NX_HEIGHT(&cellFrame),
162: [cellCache gState], NX_X(&cellFrame),
163: NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame), NX_COPY);
164:
165: /* now show what we've done */
166: [window flushWindow];
167:
168: /*
169: * if we autoscrolled, flush any lingering window server events to make
170: * the scrolling smooth
171: */
172: if (scrolled) {
173: NXPing();
174: scrolled = NO;
175: }
176:
177: /* save the current mouse location, just in case we need it again */
178: mouseLocation = event->location;
179:
180: if (![NXApp peekNextEvent:MOVE_MASK into:&peek]) {
181: /*
182: * no mouseMoved or mouseUp event immediately avaiable, so take
183: * mouseMoved, mouseUp, or timer
184: */
185: event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK];
186: } else {
187: /* get the mouseMoved or mouseUp event in the queue */
188: event = [NXApp getNextEvent:MOVE_MASK];
189: }
190:
191: /* if a timer event, mouse location isn't valid, so we'll set it */
192: if (event->type == NX_TIMER) {
193: event->location = mouseLocation;
194: }
195: }
196:
197: /* mouseUp, so stop any timer and unlock focus */
198: stopTimer(timer);
199: [self unlockFocus];
200:
201: /* find the cell under the mouse's location */
202: mouseUpLocation = event->location;
203: [self convertPoint:&mouseUpLocation fromView:nil];
204: if (![self getRow:&newRow andCol:&column forPoint:&mouseUpLocation]) {
205: /* mouse is out of bounds, so find the cell the active cell covers */
206: [self getRow:&newRow andCol:&column forPoint:&(cellFrame.origin)];
207: }
208:
209: /* we need to shuffle cells if the active cell's going to a new location */
210: if (newRow != row) {
211: /* no autodisplay while we move cells around */
212: [self setAutodisplay:NO];
213: if (newRow > row) {
214: /* adjust selected row if before new active cell location */
215: if (selectedRow <= newRow) {
216: selectedRow--;
217: }
218:
219: /*
220: * push all cells above the active cell's new location up one row so
221: * that we fill the vacant spot
222: */
223: while (row++ < newRow) {
224: cell = [self cellAt:row :0];
225: [self putCell:cell at:(row - 1) :0];
226: }
227: /* now place the active cell in its new home */
228: [self putCell:activeCell at:newRow :0];
229: } else if (newRow < row) {
230: /* adjust selected row if after new active cell location */
231: if (selectedRow >= newRow) {
232: selectedRow++;
233: }
234:
235: /*
236: * push all cells below the active cell's new location down one row
237: * so that we fill the vacant spot
238: */
239: while (row-- > newRow) {
240: cell = [self cellAt:row :0];
241: [self putCell:cell at:(row + 1) :0];
242: }
243: /* now place the active cell in its new home */
244: [self putCell:activeCell at:newRow :0];
245: }
246:
247: /* if the active cell is selected, note its new row */
248: if ([activeCell state]) {
249: selectedRow = newRow;
250: }
251:
252: /* make sure the active cell's visible if we're autoscrolling */
253: if (mFlags.autoscroll) {
254: [self scrollCellToVisible:newRow :0];
255: }
256:
257: /* no longer dragging the cell */
258: activeCell = 0;
259:
260: /* size to cells after all this shuffling and turn autodisplay back on */
261: [[self sizeToCells] setAutodisplay:YES];
262: } else {
263: /* no longer dragging the cell */
264: activeCell = 0;
265: }
266:
267: /* now redraw ourself */
268: [self display];
269:
270: /* set the event mask to normal */
271: [window setEventMask:eventMask];
272:
273: return self;
274: }
275:
276: - drawSelf:(NXRect *)rects :(int)count
277: {
278: int row, col;
279: NXRect cellBorder;
280: int sides[] = {NX_XMIN, NX_YMIN, NX_XMAX, NX_YMAX, NX_XMIN,
281: NX_YMIN};
282: float grays[] = {NX_DKGRAY, NX_DKGRAY, NX_WHITE, NX_WHITE, NX_BLACK,
283: NX_BLACK};
284:
285: /* do the regular drawing */
286: [super drawSelf:rects :count];
287:
288: /* draw a "well" if the user's dragging a cell */
289: if (activeCell) {
290: /* get the cell's frame */
291: [self getRow:&row andCol:&col ofCell:activeCell];
292: [self getCellFrame:&cellBorder at:row :col];
293:
294: /* draw the well */
295: if (NXIntersectsRect(&cellBorder, &(rects[0]))) {
296: NXDrawTiledRects(&cellBorder, (NXRect *)0, sides, grays, 6);
297: PSsetgray(0.17);
298: NXRectFill(&cellBorder);
299: }
300: }
301:
302: return self;
303: }
304:
305: - setupCacheWindows
306: {
307: NXRect visibleRect;
308:
309: /* create the matrix cache window */
310: [self getVisibleRect:&visibleRect];
311: matrixCache = [self sizeCacheWindow:matrixCache to:&(visibleRect.size)];
312:
313: /* create the cell cache window */
314: cellCache = [self sizeCacheWindow:cellCache to:&cellSize];
315:
316: return self;
317: }
318:
319: - sizeCacheWindow:cacheWindow to:(NXSize *)windowSize
320: {
321: NXRect cacheFrame;
322:
323: if (!cacheWindow) {
324: /* create the cache window if it doesn't exist */
325: cacheFrame.origin.x = cacheFrame.origin.y = 0.0;
326: cacheFrame.size = *windowSize;
327: cacheWindow = [[[Window alloc] initContent:&cacheFrame
328: style:NX_PLAINSTYLE
329: backing:NX_RETAINED
330: buttonMask:0
331: defer:NO] reenableDisplay];
332: /* flip the contentView since we are flipped */
333: [[cacheWindow contentView] setFlipped:YES];
334: } else {
335: /* make sure the cache window's the right size */
336: [cacheWindow getFrame:&cacheFrame];
337: if (cacheFrame.size.width != windowSize->width ||
338: cacheFrame.size.height != windowSize->height) {
339: [cacheWindow sizeWindow:windowSize->width
340: :windowSize->height];
341: }
342: }
343:
344: return cacheWindow;
345: }
346:
347: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.