/* * EmulatorView.mm - Custom NSView for Basilisk II windowed graphics output * * $Id: EmulatorView.mm,v 1.14 2005/01/30 21:42:13 gbeauche Exp $ * * Basilisk II (C) 1997-2005 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #import "sysdeps.h" // Types used in Basilisk C++ code, #define DEBUG 0 #import #import #import "main_macosx.h" // For WarningAlert() et al prototypes #import "misc_macosx.h" // For InfoSheet() prototype #import "video_macosx.h" // For init_* globals, and bitmap drawing strategy #import "EmulatorView.h" @implementation EmulatorView // // Standard NSView methods that we override // - (id) initWithFrame: (NSRect) frameRect { self = [super initWithFrame: frameRect]; output = self; // Set global for access by Basilisk C++ code // bitmap = nil; // Set by readyToDraw: drawView = NO; // Disable drawing until later fullScreen = NO; return self; } - (void) awakeFromNib { // Here we store the height of the screen which the app was opened on. // NSApplication's sendEvent: always uses that screen for its mouse co-ords screen_height = (int) [[NSScreen mainScreen] frame].size.height; } // Mouse click in this window. If window is not active, // should the click be passed to this view? - (BOOL) acceptsFirstMouse: (NSEvent *) event { return [self mouseInView]; } // // Key event processing. // OS X doesn't send us separate events for the modifier keys // (shift/control/command), so we need to monitor them separately // #include static int prevFlags; - (void) flagsChanged: (NSEvent *) event { int flags = [event modifierFlags]; if ( (flags & NSAlphaShiftKeyMask) != (prevFlags & NSAlphaShiftKeyMask) ) if ( flags & NSAlphaShiftKeyMask ) ADBKeyDown(0x39); // CAPS_LOCK else ADBKeyUp(0x39); if ( (flags & NSShiftKeyMask) != (prevFlags & NSShiftKeyMask) ) if ( flags & NSShiftKeyMask ) ADBKeyDown(0x38); // SHIFT_LEFT else ADBKeyUp(0x38); if ( (flags & NSControlKeyMask) != (prevFlags & NSControlKeyMask) ) if ( flags & NSControlKeyMask ) ADBKeyDown(0x36); // CTL_LEFT else ADBKeyUp(0x36); if ( (flags & NSAlternateKeyMask) != (prevFlags & NSAlternateKeyMask) ) if ( flags & NSAlternateKeyMask ) ADBKeyDown(0x3a); // OPTION_LEFT else ADBKeyUp(0x3a); if ( (flags & NSCommandKeyMask) != (prevFlags & NSCommandKeyMask) ) if ( flags & NSCommandKeyMask ) ADBKeyDown(0x37); // APPLE_LEFT else ADBKeyUp(0x37); prevFlags = flags; } // // Windowed mode. We only send mouse/key events // if the OS X mouse is within the little screen // - (BOOL) mouseInView: (NSEvent *) event { NSRect box; NSPoint loc; if ( fullScreen ) { box = displayBox; loc = [NSEvent mouseLocation]; } else { box = [self frame]; loc = [event locationInWindow]; } D(NSLog (@"%s - loc.x=%f, loc.y=%f, box.origin.x=%f, box.origin.y=%f, box.size.width=%f, box.size.height=%f", __PRETTY_FUNCTION__, loc.x, loc.y, box.origin.x, box.origin.y, box.size.width, box.size.height)); return [self mouse: loc inRect: box]; } - (BOOL) mouseInView { NSPoint loc = [[self window] mouseLocationOutsideOfEventStream]; NSRect box = [self frame]; D(NSLog (@"%s - loc.x=%f, loc.y=%f, box.origin.x=%f, box.origin.y=%f", __PRETTY_FUNCTION__, loc.x, loc.y, box.origin.x, box.origin.y)); return [self mouse: loc inRect: box]; } // // Custom methods // - (void) benchmark { int i; float seconds; NSDate *startDate; char *method; if ( ! drawView ) { WarningSheet (@"The emulator has not been setup yet.", @"Try to run, then pause the emulator, first.", nil, [self window]); return; } drawView = NO; [self lockFocus]; startDate = [NSDate date]; for (i = 1; i < 300; ++i ) #ifdef NSBITMAP [bitmap draw]; #endif #ifdef CGIMAGEREF cgDrawInto([self bounds], cgImgRep); #endif #ifdef CGDRAWBITMAP [self CGDrawBitmap]; #endif seconds = -[startDate timeIntervalSinceNow]; [self unlockFocus]; drawView = YES; #ifdef NSBITMAP method = "NSBITMAP"; #endif #ifdef CGIMAGEREF method = "CGIMAGEREF"; #endif #ifdef CGDRAWBITMAP method = "CGDRAWBITMAP"; #endif InfoSheet(@"Ran benchmark (300 screen redraws)", [NSString stringWithFormat: @"%.2f seconds, %.3f frames per second (using %s implementation)", seconds, i/seconds, method], @"Thanks", [self window]); } // Return a TIFF for a snapshot of the screen image - (NSData *) TIFFrep { #ifdef NSBITMAP return [bitmap TIFFRepresentation]; #else NSBitmapImageRep *b = [NSBitmapImageRep alloc]; b = [b initWithBitmapDataPlanes: (unsigned char **) &bitmap pixelsWide: x pixelsHigh: y #ifdef CGIMAGEREF bitsPerSample: CGImageGetBitsPerComponent(cgImgRep) samplesPerPixel: 3 hasAlpha: NO isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: CGImageGetBytesPerRow(cgImgRep) bitsPerPixel: CGImageGetBitsPerPixel(cgImgRep)]; #endif #ifdef CGDRAWBITMAP bitsPerSample: bps samplesPerPixel: spp hasAlpha: hasAlpha isPlanar: isPlanar colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: bytesPerRow bitsPerPixel: bpp]; #endif if ( ! b ) { ErrorAlert("Could not allocate an NSBitmapImageRep for the TIFF"); return nil; } return [b TIFFRepresentation]; #endif } // Enable display of, and drawing into, the view #ifdef NSBITMAP - (void) readyToDraw: (NSBitmapImageRep *) theBitmap imageWidth: (short) width imageHeight: (short) height { numBytes = [theBitmap bytesPerRow] * height; #endif #ifdef CGIMAGEREF - (void) readyToDraw: (CGImageRef) image bitmap: (void *) theBitmap imageWidth: (short) width imageHeight: (short) height { cgImgRep = image; numBytes = CGImageGetBytesPerRow(image) * height; #endif #ifdef CGDRAWBITMAP - (void) readyToDraw: (void *) theBitmap width: (short) width height: (short) height bps: (short) bitsPerSample spp: (short) samplesPerPixel bpp: (short) bitsPerPixel bpr: (int) bpr isPlanar: (BOOL) planar hasAlpha: (BOOL) alpha { bps = bitsPerSample; spp = samplesPerPixel; bpp = bitsPerPixel; bytesPerRow = bpr; isPlanar = planar; hasAlpha = alpha; numBytes = bpr * height; #endif D(NSLog(@"readyToDraw: theBitmap=%lx\n", theBitmap)); bitmap = theBitmap; x = width, y = height; drawView = YES; [[self window] setAcceptsMouseMovedEvents: YES]; // [[self window] setInitialFirstResponder: self]; [[self window] makeFirstResponder: self]; } - (void) disableDrawing { drawView = NO; } - (void) startedFullScreen: (CGDirectDisplayID) display { CGRect displayBounds = CGDisplayBounds(display); fullScreen = YES; memcpy(&displayBox, &displayBounds, sizeof(displayBox)); } - (short) width { return (short)[self bounds].size.width; } - (short) height { return (short)[self bounds].size.height; } - (BOOL) isFullScreen { return fullScreen; } - (BOOL) isOpaque { return drawView; } - (BOOL) processKeyEvent: (NSEvent *) event { if ( fullScreen || [self acceptsFirstMouse: event] ) if ( [event isARepeat] ) return NO; else return YES; [self interpretKeyEvents:[NSArray arrayWithObject:event]]; return NO; } - (void) keyDown: (NSEvent *) event { if ( [self processKeyEvent: event] ) { int code = [event keyCode]; if ( code == 126 ) code = 0x3e; // CURS_UP if ( code == 125 ) code = 0x3d; // CURS_DOWN if ( code == 124 ) code = 0x3c; // CURS_RIGHT if ( code == 123 ) code = 0x3b; // CURS_LEFT ADBKeyDown(code); } } - (void) keyUp: (NSEvent *) event { if ( [self processKeyEvent: event] ) { int code = [event keyCode]; if ( code == 126 ) code = 0x3e; // CURS_UP if ( code == 125 ) code = 0x3d; // CURS_DOWN if ( code == 124 ) code = 0x3c; // CURS_RIGHT if ( code == 123 ) code = 0x3b; // CURS_LEFT ADBKeyUp(code); } } - (void) fullscreenMouseMove { NSPoint location = [NSEvent mouseLocation]; D(NSLog (@"%s - loc.x=%f, loc.y=%f", __PRETTY_FUNCTION__, location.x, location.y)); D(NSLog (@"%s - Sending ADBMouseMoved(%d,%d). (%d-%d)", __PRETTY_FUNCTION__, (int)location.x, screen_height - (int)location.y, screen_height, (int)location.y)); ADBMouseMoved((int)location.x, screen_height - (int)location.y); } static NSPoint mouse; // Previous/current mouse location - (BOOL) processMouseMove: (NSEvent *) event { if ( ! drawView ) { D(NSLog(@"Unable to process event - Emulator has not started yet")); return NO; } if ( fullScreen ) { [self fullscreenMouseMove]; return YES; } NSPoint location = [self convertPoint: [event locationInWindow] fromView:nil]; D(NSLog (@"%s - loc.x=%f, loc.y=%f", __PRETTY_FUNCTION__, location.x, location.y)); if ( NSEqualPoints(location, mouse) ) return NO; mouse = location; int mouseY = y - (int) (y * mouse.y / [self height]); int mouseX = (int) (x * mouse.x / [self width]); // If the view was not resizable, then this would be simpler: // int mouseY = y - (int) mouse.y; // int mouseX = (int) mouse.x; ADBMouseMoved(mouseX, mouseY); return YES; } - (void) mouseDown: (NSEvent *) event { [self processMouseMove: event]; ADBMouseDown(0); } - (void) mouseDragged: (NSEvent *) event { [self processMouseMove: event]; } - (void) mouseMoved: (NSEvent *) event { #if DEBUG if ( ! [self mouseInView] ) { NSLog (@"%s - Received event while outside of view", __PRETTY_FUNCTION__); return; } #endif [self processMouseMove: event]; } - (void) mouseUp: (NSEvent *) event { [self processMouseMove: event]; ADBMouseUp(0); } #if DEBUG - (void) randomise // Draw some coloured snow in the bitmap { unsigned char *data, *pixel; #ifdef NSBITMAP data = [bitmap bitmapData]; #else data = bitmap; #endif for ( int i = 0; i < 1000; ++i ) { pixel = data + (int) (numBytes * rand() / RAND_MAX); *pixel = (unsigned char) (256.0 * rand() / RAND_MAX); } } #endif - (void) drawRect: (NSRect) rect { if ( ! drawView ) // If the emulator is still being setup, return; // we do not want to draw #if DEBUG NSLog(@"In drawRect"); [self randomise]; #endif #ifdef NSBITMAP NSRectClip(rect); [bitmap draw]; #endif #ifdef CGIMAGEREF cgDrawInto(rect, cgImgRep); #endif #ifdef CGDRAWBITMAP [self CGDrawBitmap]; #endif } - (void) setTo: (int) val // Set all of bitmap to val { unsigned char *data #ifdef NSBITMAP = [bitmap bitmapData]; #else = (unsigned char *) bitmap; #endif memset(data, val, (long unsigned)numBytes); } - (void) blacken // Set bitmap black { [self setTo: 0]; } - (void) clear // Set bitmap white { [self setTo: 0xFF]; } // // Extra drawing stuff // #ifdef CGDRAWBITMAP extern "C" void CGDrawBitmap(...); - (void) CGDrawBitmap { CGContextRef cgContext = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort]; NSRect rect = [self bounds]; CGRect cgRect = { {rect.origin.x, rect.origin.y}, {rect.size.width, rect.size.height} }; CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); // CGContextSetShouldAntialias(cgContext, NO); // Seems to have no effect? CGDrawBitmap(cgContext, cgRect, x, y, bps, spp, bpp, bytesPerRow, isPlanar, hasAlpha, colourSpace, &bitmap); } #endif #ifdef CGIMAGEREF void cgDrawInto(NSRect rect, CGImageRef cgImgRep) { CGContextRef cgContext = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort]; CGRect cgRect = { {rect.origin.x, rect.origin.y}, {rect.size.width, rect.size.height} }; // CGContextSetShouldAntialias(cgContext, NO); // Seems to have no effect? CGContextDrawImage(cgContext, cgRect, cgImgRep); } #endif @end