1/* MoveMatrix.m
2 *
3 * Copyright (C) 1993-2005 by vhf interservice GmbH
4 * Author: Georg Fleischmann
5 *
6 * Created:  1993-05-17
7 * Modified: 2005-11-08
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the vhf Public License as
11 * published by vhf interservice GmbH. Among other things, the
12 * License requires that the copyright notices and this notice
13 * be preserved on all copies.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 * See the vhf Public License for more details.
19 *
20 * You should have received a copy of the vhf Public License along
21 * with this program; see the file LICENSE. If not, write to vhf.
22 *
23 * vhf interservice GmbH, Im Marxle 3, 72119 Altingen, Germany
24 * eMail: info@vhf.de
25 * http://www.vhf.de
26 */
27
28#include <AppKit/AppKit.h>
29#include <VHFShared/vhfCompatibility.h>
30#include "FlippedView.h"
31#include "MoveCell.h"
32#include "MoveMatrix.h"
33
34@implementation MoveMatrix
35
36- (id)initCellClass:aCellClass
37{   NSRect	scrollRect, matrixRect;
38    NSSize	interCellSpacing = {0.0, 0.0}, newCellSize;
39
40    scrollRect = [[[self superview] superview] frame];	// get the scrollView's dimensions
41
42    matrixRect.size = [NSScrollView contentSizeForFrameSize:(scrollRect.size)
43                                      hasHorizontalScroller:NO hasVerticalScroller:YES
44                                                 borderType:NSBezelBorder];
45
46    //[self setMode:NSListModeMatrix];			// prepare a matrix to the right state
47    //	Wenn dieser Status gesetzt ist, kommt das trackMouse nicht mehr in der LayerCell an
48    //  Andererseits funktioniert dann das Move nicht mehr ganz so gut
49
50    [self setCellClass:aCellClass];
51    [self renewRows:0 columns:1];
52
53    [self setIntercellSpacing:interCellSpacing];	// we don't want any space between the matrix's cells
54
55    newCellSize = [self cellSize];			// resize the matrix's cells and size the
56    newCellSize.width = NSWidth(matrixRect);		// matrix to contain them
57
58    [self setCellSize:newCellSize];
59    [self sizeToCells];
60    [self setAutosizesCells:YES];
61    [self setAutoscroll:YES];
62
63    return self;
64}
65
66- (void)calcCellSize
67{
68    if ( [self numberOfRows] > 0 )
69    {   id	cell = [self cellAtRow:0 column:0];
70        NSSize	size = [cell cellSize];
71        NSRect	frame = [[self superview] frame];
72
73        if ( frame.size.width )
74        {   size.width = frame.size.width;
75            [self setCellSize:size];
76        }
77    }
78}
79
80/* Traegt automatisch in die neue Cell sich selbst als MoveMatrix ein
81 */
82- (NSCell *)makeCellAtRow:(int)row column:(int)col;
83{   MoveCell	*newCell = (MoveCell*)[super makeCellAtRow:row column:col];
84
85    if ( [newCell respondsToSelector:@selector(setMatrix:)] )
86        [newCell setMatrix:self];
87    else
88        NSLog(@"MoveMatrix (makeCellAt::): Cell does not respond to setMatrix");
89
90    return newCell;
91}
92
93
94/*
95 * Timers used to automatically scroll when the mouse is
96 * outside the drawing view and not moving.
97 */
98#define startTimer(inTimerLoop) if (!inTimerLoop) { [NSEvent startPeriodicEventsAfterDelay:0.1 withPeriod:0.01]; inTimerLoop=YES; }
99#define stopTimer(inTimerLoop) if (inTimerLoop) { [NSEvent stopPeriodicEvents]; inTimerLoop=NO; }
100
101#define MOVE_MASK NSLeftMouseUpMask|NSLeftMouseDraggedMask
102
103extern NSEvent *periodicEventWithLocationSetToPoint(NSEvent *oldEvent, NSPoint point);
104
105- (void)mouseDown:(NSEvent *)theEvent
106{   NSPoint		mouseDownLocation, mouseUpLocation, mouseLocation;
107    NSInteger	row, column, newRow;
108    NSRect		visibleRect, cellCacheBounds, cellFrame;
109    float		dy;
110    NSEvent		*event;
111    BOOL 		scrolled = NO, inTimerLoop = NO;
112
113    if ( ! ([theEvent modifierFlags] & NSControlKeyMask) )
114    {	if ( [theEvent clickCount] == 1 )   // we only handle single click here to avoid issues
115            [super mouseDown:theEvent];
116        //else  // double click is making trouble (see commetn in IPLayerCell.m -trackMouse)
117        //    NSLog(@"Double click");
118        return;
119    }
120
121    /* shuffle Cell */
122
123    /* prepare the cell and matrix cache windows */
124    [self setupCache];
125
126    /* find the cell that got clicked on and select it */
127    mouseDownLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil];
128    [self getRow:&row column:&column forPoint:mouseDownLocation];
129    activeCell = [[self cellAtRow:row column:column] retain];
130    [self selectCell:activeCell];
131    cellFrame = [self cellFrameAtRow:row column:column];
132
133    /* do whatever's required for a single-click */
134    [self sendAction];
135
136    /* draw a "well" in place of the selected cell (see drawSelf::) */
137    [self display];
138
139    /* copy what's currently visible into the matrix cache */
140#if defined(GNUSTEP_BASE_VERSION)
141    [matrixCache lockFocus];
142    visibleRect = [self convertRect:[self visibleRect] toView:nil];
143    PScomposite(NSMinX(visibleRect), NSMinY(visibleRect), NSWidth(visibleRect), NSHeight(visibleRect), [[self window] gState], 0.0, 0.0, NSCompositeCopy);
144    [matrixCache unlockFocus];
145#elif defined(__APPLE__)
146    [matrixCache release];
147    matrixCache = [[NSImage alloc] initWithData:[self dataWithPDFInsideRect:[self visibleRect]]];
148#else
149    [matrixCache lockFocus];
150    visibleRect = [self convertRect:[self visibleRect] toView:nil];
151    PScomposite(NSMinX(visibleRect), NSMinY(visibleRect), NSWidth(visibleRect), NSHeight(visibleRect), [[self window] gState], 0.0, NSHeight(visibleRect), NSCompositeCopy);
152    [matrixCache unlockFocus];
153#endif
154
155/* debug: draw cache into document view */
156/*{   NSRect	bRect = [self bounds];
157    [[[NSApp currentDocument] documentView] lockFocus];
158    [matrixCache compositeToPoint:NSZeroPoint
159                         fromRect:NSMakeRect(0.0, 0.0, bRect.size.width, bRect.size.height)
160                        operation:NSCompositeCopy];
161    [[[NSApp currentDocument] documentView] unlockFocus];
162    [[[NSApp currentDocument] window] flushWindow];
163}*/
164
165    PSWait();
166
167    /* image the cell into its cache */
168    [cellCache lockFocus];
169    cellCacheBounds.origin = NSZeroPoint;
170    cellCacheBounds.size = [cellCache size];
171    [activeCell drawWithFrame:cellCacheBounds inView:[NSView focusView]];
172    [cellCache unlockFocus];
173
174    /* save the mouse's location relative to the cell's origin */
175    dy = mouseDownLocation.y - cellFrame.origin.y;
176
177    /* from now on we'll be drawing into ourself */
178    [self lockFocus];
179
180    event = theEvent;
181    while ([event type] != NSLeftMouseUp)
182    {
183        /* erase the active cell using the image in the matrix cache */
184	visibleRect = [self visibleRect];
185#ifdef GNUSTEP_BASE_VERSION
186        /* origin y of cellFrame is upper/left, composite origin is lower/left on GNUstep */
187        /*[matrixCache compositeToPoint:NSMakePoint(NSMinX(cellFrame), NSMinY(cellFrame))
188                             fromRect:NSMakeRect(0.0, NSHeight(visibleRect) - NSMinY(cellFrame) +
189                                                      NSMinY(visibleRect) - NSHeight(cellFrame),
190                                                 NSWidth(cellFrame), NSHeight(cellFrame))
191                            operation:NSCompositeCopy];*/
192        /* there is something wrong with NSImage on GNUstep, the obove doesn't work */
193        [matrixCache compositeToPoint:NSMakePoint(NSMinX(cellFrame), NSMaxY(visibleRect))
194                             fromRect:NSMakeRect(0.0, 0.0, NSWidth(cellFrame), NSHeight(visibleRect))
195                            operation:NSCompositeCopy];
196#else
197        [matrixCache compositeToPoint:NSMakePoint(NSMinX(cellFrame), NSMaxY(cellFrame))
198                             fromRect:NSMakeRect(0.0, NSHeight(visibleRect) - NSMinY(cellFrame) +
199                                                      NSMinY(visibleRect) - NSHeight(cellFrame),
200                                                 NSWidth(cellFrame), NSHeight(cellFrame))
201                            operation:NSCompositeCopy];
202#endif
203
204        /* move the active cell */
205        mouseLocation = [self convertPoint:[event locationInWindow] fromView:nil];
206        cellFrame.origin.y = mouseLocation.y - dy;
207
208        /* constrain the cell's location to our bounds */
209	if (NSMinY(cellFrame) < NSMinX([self bounds]) )
210	    cellFrame.origin.y = NSMinX([self bounds]);
211        else if (NSMaxY(cellFrame) > NSMaxY([self bounds]))
212            cellFrame.origin.y = NSHeight([self bounds]) - NSHeight(cellFrame);
213
214        /* make sure the cell will be entirely visible in its new location (if
215         * we're in a scrollView, it may not be)
216         */
217        if (!NSContainsRect(visibleRect, cellFrame) && [self isAutoscroll])
218        {
219            /* the cell won't be entirely visible, so scroll, dood, scroll, but
220	         * don't display on-screen yet
221	         */
222            [[self window] disableFlushWindow];
223            [self scrollRectToVisible:cellFrame];
224            [[self window] enableFlushWindow];
225
226            /* copy the new image to the matrix cache */
227            [matrixCache lockFocus];
228            //visibleRect = [self convertRect:[self visibleRect] fromView:[self superview]];
229            //visibleRect = [self convertRect:visibleRect toView:nil];
230            visibleRect = [self convertRect:[self visibleRect] toView:nil];
231#ifdef GNUSTEP_BASE_VERSION
232            PScomposite(NSMinX(visibleRect), NSMinY(visibleRect), NSWidth(visibleRect), NSHeight(visibleRect),
233                        [[self window] gState], 0.0, 0.0, NSCompositeCopy);
234#else
235            PScomposite(NSMinX(visibleRect), NSMinY(visibleRect), NSWidth(visibleRect), NSHeight(visibleRect),
236                        [[self window] gState], 0.0, NSHeight(visibleRect), NSCompositeCopy);
237#endif
238            [matrixCache unlockFocus];
239
240            /* note that we scrolled and start generating timer events for autoscrolling */
241            scrolled = YES;
242            startTimer(inTimerLoop);
243        }
244        else
245            stopTimer(inTimerLoop);
246
247        /* composite the active cell's image on top of ourself */
248        [cellCache compositeToPoint:NSMakePoint(cellFrame.origin.x, NSMaxY(cellFrame))
249                           fromRect:NSMakeRect(0.0, 0.0, NSWidth(cellFrame), NSHeight(cellFrame))
250                          operation:NSCompositeCopy];
251
252        /* now show what we've done */
253        [[self window] flushWindow];
254
255        if (scrolled)		// if we autoscrolled, flush any lingering
256        {   PSWait();		// window server events to make the scrolling
257            scrolled = NO;	// smooth
258        }
259
260        /* save the current mouse location, just in case we need it again */
261        mouseLocation = [event locationInWindow];
262
263        /* no mouse moved available, then take mouseMoved, mouseUp, or timer */
264        if (![[self window] nextEventMatchingMask:MOVE_MASK untilDate:[NSDate date]
265                                           inMode:NSEventTrackingRunLoopMode dequeue:NO])
266            event = [[self window] nextEventMatchingMask:MOVE_MASK | NSPeriodicMask];
267        else
268            event = [[self window] nextEventMatchingMask:MOVE_MASK];
269
270        if ([event type] == NSPeriodic)
271            event = periodicEventWithLocationSetToPoint(event, mouseLocation);
272    }
273
274    stopTimer(inTimerLoop);
275    [self unlockFocus];
276
277    /* find the cell under the mouse's location */
278    mouseUpLocation = [event locationInWindow];
279    mouseUpLocation = [self convertPoint:mouseUpLocation fromView:nil];
280    if (![self getRow:&newRow column:&column forPoint:mouseUpLocation])
281        [self getRow:&newRow column:&column forPoint:(cellFrame.origin)];
282
283    /* we need to shuffle cells if the active cell's going to a new location */
284    if ( ![[self cellAtRow:row column:0] dependant] && ![[self cellAtRow:newRow column:0] dependant] )
285    {   [self shuffleCell:row to:newRow];
286        if ( [[self cellAtRow:(row<newRow)?row:row+1 column:0] dependant] )
287            [self shuffleCell:(row<newRow)?row:row+1 to:(row<newRow)?newRow:newRow+1];
288    }
289
290    /* make sure the active cell's visible if we're autoscrolling */
291    if ([self isAutoscroll])
292        [self scrollCellToVisibleAtRow:newRow column:0];
293
294    /* no longer dragging the cell */
295    [activeCell release];
296    activeCell = 0;
297
298    /* size to cells after all this shuffling and turn autodisplay back on */
299    [self sizeToCells];
300    [self display];
301}
302
303
304- (void)shuffleCell:(int)row to:(int)newRow
305{   NSCell	*tmpCell;
306    int		r = row, nr = newRow;
307    int		selectedRow = [self selectedRow];
308
309    if (newRow != row)
310    {
311	activeCell = [[self cellAtRow:row column:0] retain];
312        if (newRow > row)
313        {
314            if (selectedRow <= newRow)					// adjust selected row if before
315                selectedRow--;						// new active cell location
316
317            while (row++ < newRow)					// push all cells above the active
318            {	tmpCell = [self cellAtRow:row column:0];		// cell's new location up one row
319                [self putCell:tmpCell atRow:row-1 column:0];		// so that we fill the vacant spot
320                [tmpCell setTag:row-1];
321            }
322
323            [self putCell:activeCell atRow:newRow column:0];    // now place the active cell
324            [(NSCell*)activeCell setTag:newRow];				// in its new home
325        }
326        else if (newRow < row)
327        {
328            if (selectedRow >= newRow)                          // adjust selected row if after
329                selectedRow++;                                  // new active cell location
330
331            while (row-- > newRow)                              // push all cells below the active
332            {	tmpCell = [self cellAtRow:row column:0];		// cell's new location down one
333                [self putCell:tmpCell atRow:row+1 column:0];    // row so that we fill
334                [tmpCell setTag:row+1];                         // the vacant spot
335            }
336
337            [self putCell:activeCell atRow:newRow column:0];    // now place the active cell
338            [(NSCell*)activeCell setTag:newRow];                // in its new home
339        }
340
341        if ([activeCell state])                                 // if the active cell is selected,
342            selectedRow = newRow;                               // note its new row
343        [self selectCellAtRow:selectedRow column:0];
344
345        activeCell = nil;						// no longer dragging the cell
346
347        if (delegate && [delegate respondsToSelector:@selector(matrixDidShuffleCellFrom:to:)])
348            [delegate matrixDidShuffleCellFrom:r to:nr];
349    }
350    else
351        activeCell = nil;
352}
353
354
355- (void)drawRect:(NSRect)rect;
356{   NSInteger   row, col;
357    NSRect      cellBorder;
358    NSRectEdge  sides[] = {NSMinXEdge, NSMinYEdge, NSMaxXEdge, NSMaxYEdge, NSMinXEdge, NSMinYEdge};
359    CGFloat     grays[] = {NSDarkGray, NSDarkGray, NSWhite, NSWhite, NSBlack, NSBlack};
360
361    /* do the regular drawing */
362    [super drawRect:rect];
363
364    /* draw a "well" if the user's dragging a cell */
365    if (activeCell)
366    {
367        /* get the cell's frame */
368        [self getRow:&row column:&col ofCell:activeCell];
369        cellBorder = [self cellFrameAtRow:row column:col];
370        /* draw the well */
371        cellBorder = NSDrawTiledRects(cellBorder, cellBorder, sides, grays, 6);
372        [[NSColor colorWithDeviceWhite:0.17 alpha:1.0] set];
373        NSRectFill(cellBorder);
374    }
375}
376
377
378- (void)setupCache
379{   NSRect  visibleRect;
380
381    /* create the matrix cache window */
382    visibleRect = [self visibleRect];
383    matrixCache = [self sizeCache:matrixCache to:(visibleRect.size)];
384
385    /* create the cell cache window */
386    cellCache = [self sizeCache:cellCache to:[self cellSize]];
387}
388- (NSImage*)sizeCache:(NSImage*)cache to:(NSSize)newSize
389{
390    /* This is a workaround for [NSImage setSize:] on GNUstep. */
391#ifdef GNUSTEP_BASE_VERSION
392    if (cache)
393    {   [cache release];
394        cache = nil;
395    }
396#endif
397    if (!cache)
398        cache = [[FlippedImage alloc] initWithSize:newSize];
399    /* make sure the cache window's the right size */
400    else
401    {   NSSize	size = [cache size];
402
403        if (size.width != newSize.width || size.height != newSize.height)
404            [cache setSize:newSize];
405    }
406    return cache;
407}
408
409
410- (void)setDelegate:(id)anObject
411{
412    delegate = anObject;
413}
414
415
416- delegate
417{
418    return delegate;
419}
420
421- (void)dealloc
422{
423    [matrixCache release];
424    [cellCache release];
425
426    [super dealloc];
427}
428
429@end
430