mirror of
https://github.com/kanjitalk755/macemu.git
synced 2024-06-01 22:41:29 +00:00
551 lines
12 KiB
Plaintext
551 lines
12 KiB
Plaintext
/*
|
|
* EmulatorView.mm - Custom NSView for Basilisk II windowed graphics output
|
|
*
|
|
* $Id$
|
|
*
|
|
* Basilisk II (C) 1997-2008 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 <debug.h>
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
|
|
#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 <adb.h>
|
|
|
|
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\nTry setting the emulation to millions of colours?");
|
|
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
|