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