Catakig/Source/Cocoa/ScreenView.m

484 lines
13 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* class ScreenView
The big view taking up most of an Apple II window, which displays the
computer's live video. Subclass of NSOpenGLView.
*/
#import "Catakig-Cocoa.h"
#import "MyDocument.h"
#import "ScreenView.h"
#import "IndicatorLight.h"
@implementation ScreenView
//---------------------------------------------------------------------------
enum
{
kCaptureAllScreens = NO,
// kUseShieldWindow = NO,
kUseOwnGState = YES,
kUseSetSystemUIMode = NO,
kLockPixels = YES,
kTexWidth = 1024, // width and ...
kTexHeight = 256, // height of shared GL texture
kPixelFormat = GL_COLOR_INDEX,
kPixelType = GL_UNSIGNED_BYTE,
// kBestScreenDepth = 16,
kfFlash = 1 << 6,
kfMonochrome = 1 << 7,
};
static struct
{
ScreenView* fullScreenScreen;
NSOpenGLContext* fullScreenContext;
NSOpenGLPixelFormat* sharedPixelFormat;
NSOpenGLContext* sharedContext;
GLuint displayListID;
GLuint textureID;
uint8_t* pixels; // [kTexHeight][kTexWidth]
} g;
//---------------------------------------------------------------------------
static GLuint MakeDisplayList(void)
{
GLuint listID = glGenLists(1);
if (listID < 1) // then failed to allocate
return 0;
const GLfloat cw = kA2ScreenWidth / (double)kTexWidth,
ch = (kA2ScreenHeight/2) / (double)kTexHeight;
glNewList(listID, GL_COMPILE);
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex2i(0, 0);
glTexCoord2f(0, ch); glVertex2i(0, kA2ScreenHeight);
glTexCoord2f(cw, ch); glVertex2i(kA2ScreenWidth, kA2ScreenHeight);
glTexCoord2f(cw, 0); glVertex2i(kA2ScreenWidth, 0);
glEnd();
glEndList();
return listID;
}
//---------------------------------------------------------------------------
static GLuint MakeTextureObject(void)
{
GLuint textureID = 0;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_PRIORITY, 0.); // 0 or 1??
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_STORAGE_HINT_APPLE,
GL_STORAGE_SHARED_APPLE); // extension!!
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, kTexWidth, kTexHeight, 0,
kPixelFormat, kPixelType, g.pixels);
return textureID;
}
//---------------------------------------------------------------------------
static void BlessThisContext(NSOpenGLContext* context) //!!
{/*
Sets up an OpenGLContext the way we like it. Also makes 'context'
the current context and leaves it that way on exit.
*/
[context makeCurrentContext];
[context SetSwapInterval:1L];
glDisable(GL_DITHER);
glDisable(GL_BLEND);
glDisable(GL_FOG);
glDisable(GL_LIGHTING);
glDisable(GL_ALPHA_TEST);
glDisable(GL_STENCIL_TEST);
glDisable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
// glEnableClientState(GL_VERTEX_ARRAY);
// glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glShadeModel(GL_FLAT);
glDepthMask(NO); // helpful??
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); // need??
glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT, GL_FASTEST); // extension!!
glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, YES); // extension!!
glPixelStorei(GL_UNPACK_ALIGNMENT, 8); // helpful??
glPixelStorei(GL_UNPACK_ROW_LENGTH, kTexWidth);
glPixelTransferi(GL_MAP_COLOR, GL_TRUE);
if (g.displayListID == 0)
g.displayListID = MakeDisplayList();
if (g.textureID == 0)
g.textureID = MakeTextureObject();
glBindTexture(GL_TEXTURE_2D, g.textureID);
GL_PreparePalette();
}
//---------------------------------------------------------------------------
static NSOpenGLContext* MakeFullScreenContext(CGDirectDisplayID dpy)
{/*
Assumes full-screen mode is already active.
*/
NSOpenGLPixelFormatAttribute pixFmtAttrs[] =
{
NSOpenGLPFAFullScreen,
NSOpenGLPFAScreenMask, CGDisplayIDToOpenGLDisplayMask(dpy),
NSOpenGLPFASingleRenderer,
NSOpenGLPFAAccelerated,
NSOpenGLPFANoRecovery,
NSOpenGLPFADoubleBuffer,
// NSOpenGLPFAColorSize, 16,
NSOpenGLPFAAlphaSize, 0,
NSOpenGLPFADepthSize, 0,
NSOpenGLPFAStencilSize, 0,
NSOpenGLPFAAccumSize, 0,
0 };
NSOpenGLPixelFormat* pixFmt;
NSOpenGLContext* context = nil;
[(pixFmt = [[NSOpenGLPixelFormat alloc]
initWithAttributes:pixFmtAttrs]) autorelease];
context = [[NSOpenGLContext alloc]
initWithFormat:pixFmt shareContext:nil];
BlessThisContext(context);
MakeDisplayList();
glBindTexture(GL_TEXTURE_2D, MakeTextureObject());
GL_PrepareViewport(CGDisplayPixelsWide(dpy), CGDisplayPixelsHigh(dpy));
GL_ClearBothBuffers();
[context setFullScreen];
return context;
}
//---------------------------------------------------------------------------
+ (void)initialize
{
NSOpenGLPixelFormatAttribute pixFmtAttrs[] =
{
NSOpenGLPFAWindow,
NSOpenGLPFAAccelerated,
NSOpenGLPFANoRecovery,
NSOpenGLPFADoubleBuffer,
// NSOpenGLPFABackingStore,
// NSOpenGLPFAPixelBuffer,
NSOpenGLPFAColorSize, 16,
NSOpenGLPFAAlphaSize, 0,
NSOpenGLPFADepthSize, 0,
NSOpenGLPFAStencilSize, 0,
NSOpenGLPFAAccumSize, 0,
0 };
g.pixels = NSAllocateMemoryPages(kTexWidth * kTexHeight);
if (g.pixels != nil)
{
madvise(g.pixels, kTexWidth * kTexHeight, MADV_SEQUENTIAL);
if (kLockPixels)
mlock(g.pixels, kTexWidth * kTexHeight);
}
g.sharedPixelFormat = [[NSOpenGLPixelFormat alloc]
initWithAttributes:pixFmtAttrs];
g.sharedContext = [[NSOpenGLContext alloc]
initWithFormat:g.sharedPixelFormat shareContext:nil];
BlessThisContext(g.sharedContext);
// NSLog(@"GL version = '%s'", glGetString(GL_VERSION));//!!
}
//---------------------------------------------------------------------------
- (id)initWithFrame:(NSRect)frame
{
// NSLog(@"SV -initWithFrame called"); //!!
self = [super initWithFrame:frame pixelFormat:g.sharedPixelFormat];
if (self == nil)
return nil;
if (kUseOwnGState)
[self allocateGState]; // helpful??
mRenderScreen = (void*) [A2Computer instanceMethodForSelector:
@selector(RenderScreen::) ];
mRunForOneStep = (void*) [A2Computer instanceMethodForSelector:
@selector(RunForOneStep:) ];
// Need to release this view's previous GLContext??
[self setOpenGLContext:[[NSOpenGLContext alloc]
initWithFormat: g.sharedPixelFormat
shareContext: g.sharedContext ]];
BlessThisContext([self openGLContext]);
return self;
}
//---------------------------------------------------------------------------
- (void)dealloc
{
if (kUseOwnGState)
[self releaseGState]; // need this??
[super dealloc];
}
//---------------------------------------------------------------------------
- (void)prepareOpenGL
{/*
"Used by subclasses to initialize OpenGL state."
"This method is called only once after the OpenGL context is made the
current context. Subclasses that implement this method can use it to
configure the Open GL state in preparation for drawing."
*/
// NSLog(@"SV -prepareOpenGL called"); //!!
}
//---------------------------------------------------------------------------
- (void)reshape
{/*
"Called by Cocoa when the view's visible rectangle or bounds change."
"Called if the visible rectangle or bounds of the receiver change
(for scrolling or resize). The default implementation does nothing.
Override this method if you need to adjust the viewport and display
frustum."
*/
NSSize vsize = NSIntegralRect([self bounds]).size;
GL_PrepareViewport(vsize.width, vsize.height);
GL_ClearBothBuffers();
// NSLog(@"SV -reshape called"); //!!
}
//---------------------------------------------------------------------------
- (void)drawRect:(NSRect)r
{
NSOpenGLContext* context;
if (g.fullScreenScreen == self)
[(context = g.fullScreenContext) makeCurrentContext];
else
context = [self openGLContext];
mRenderScreen(mA2, nil, g.pixels, kTexWidth);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kA2ScreenWidth, 192,
kPixelFormat, kPixelType, g.pixels);
glCallList(g.displayListID);
[context flushBuffer];
// if (g.fullScreenScreen == self)
// [self MakeCurrentContext]; // need to restore previous context??
}
//---------------------------------------------------------------------------
- (IBAction)ToggleColorVideo:(id)sender
{/*
Toggles this Apple II's video style between monochrome and color.
*/
mVideoStyle ^= kfMonochrome;
[self setNeedsDisplay:YES];
}
//---------------------------------------------------------------------------
- (IBAction)ToggleFullScreen:(id)sender
{/*
Toggles this Apple II between full-screen mode and windowed mode.
*/
static CGDirectDisplayID dpy;
static CFDictionaryRef prevMode;
[mDocument Pause];
if (g.fullScreenScreen == nil) // then take over user's screen
{
dpy = CGMainDisplayID(); // always main display??
prevMode = CGDisplayCurrentMode(dpy);
[NSCursor hide];
[NSMenu setMenuBarVisible:NO];
if (kCaptureAllScreens)
CGCaptureAllDisplays();
else
CGDisplayCapture(dpy);
CGDisplaySwitchToMode(dpy,
CGDisplayBestModeForParameters(dpy, 16, 640, 480, nil) );
if (kUseSetSystemUIMode)
SetSystemUIMode(kUIModeAllHidden, 0); // disables cmd-tab, etc.
g.fullScreenContext = MakeFullScreenContext(dpy);
g.fullScreenScreen = self;
}
else // relinquish screen and restore desktop
{
[self MakeCurrentContext];
GL_ClearBothBuffers(); // erase old content
g.fullScreenScreen = nil;
g.fullScreenContext = [g.fullScreenContext Release];
if (kUseSetSystemUIMode)
SetSystemUIMode(kUIModeNormal, 0);
CGDisplaySwitchToMode(dpy, prevMode);
CGReleaseAllDisplays();
[NSMenu setMenuBarVisible:YES];
[NSCursor unhide];
}
SetMouseRange();
[self setNeedsDisplay:YES];
[mDocument Unpause];
}
//---------------------------------------------------------------------------
- (void)Flash
{/*
Should be called a few times per second (say 3 or 4) to implement
flashing, and to update the indicator lights.
*/
unsigned lchg = mPrevLights ^ [mA2 Lights];
if (lchg != 0)
{
mPrevLights ^= lchg;
if (lchg & kfA2LightPrinter) [mPrinterLight ToggleState];
if (lchg & kfA2LightDDrive0) [mDDLight0 ToggleState];
if (lchg & kfA2LightDDrive1) [mDDLight1 ToggleState];
}
if (g.fullScreenContext)
[g.fullScreenContext makeCurrentContext];
else
[self MakeCurrentContext];
glPixelTransferi(GL_INDEX_OFFSET, (mVideoStyle ^= kfFlash));
[self setNeedsDisplay:YES];
#if 0
if ([mDocument IsRunning])
[[self window] setDocumentEdited:YES];
#endif
}
//---------------------------------------------------------------------------
- (void)keyDown:(NSEvent*)event
{
// if (not [mDocument IsRunning]) // then ignore keypresses
// return;
// NSString* chstr = [event charactersIgnoringModifiers];
NSString* chstr = [event characters];
if (chstr == nil or [chstr length] < 1)
return;
unsigned mods = [event modifierFlags],
ch = [chstr characterAtIndex:0];
BOOL keyInNumPad = ((mods & NSNumericPadKeyMask) != 0);
// NSLog(@"char %04X, mods %08X", ch, mods); //!!
switch (ch)
{
case NSLeftArrowFunctionKey: ch = 8; break;
case NSRightArrowFunctionKey: ch = 21; break;
case NSDownArrowFunctionKey: ch = 10; break;
case NSUpArrowFunctionKey: ch = 11; break;
case NSClearLineFunctionKey: ch = 27; break;
case 3: if (keyInNumPad) // then it's 'enter', not ctrl-C
ch = 13;
break;
case '7': case '8': case '9':
case '4': case '5': case '6':
case '1': case '2': case '3':
if (keyInNumPad and G.prefs.joystickControl == kJoyKeypad)
{
[A2Computer InputPaddlesByKeypad:ch];
return;
}
break;
}
[mA2 InputChar:ch];
}
//---------------------------------------------------------------------------
- (BOOL)performKeyEquivalent:(NSEvent*)event
{/*
"Returns YES if theEvent is a key equivalent that the receiver handled,
NO if it is not a key equivalent that it should handle."
"If the receivers key equivalent is the same as the characters of the
key-down event theEvent, as returned by charactersIgnoringModifiers,
the receiver should take the appropriate action and return YES.
Otherwise, it should return the result of invoking supers
implementation. The default implementation of this method simply passes
the message down the view hierarchy (from superviews to subviews) and
returns NO if none of the receivers subviews responds YES."
*/
if (g.fullScreenScreen != nil and
([event modifierFlags] & NSCommandKeyMask) )
return YES;
return [super performKeyEquivalent:event];
}
//---------------------------------------------------------------------------
//- (BOOL)isFlipped { return YES; }
+ (void)FullScreenOff
{ [g.fullScreenScreen ToggleFullScreen:self]; }
+ (void)AllNeedDisplay
{ [MyDocument AllNeedDisplay]; }
- (BOOL)acceptsFirstResponder
{ return YES; } // yes, we want those keypresses
- (BOOL)isOpaque
{ return YES; } // eliminates unsightly flickering
- (BOOL)wantsDefaultClipping
{ return NO; } // bypasses clip-rect preparation
//---------------------------------------------------------------------------
@end