334 lines
8.5 KiB
Objective-C
334 lines
8.5 KiB
Objective-C
/* class A2Computer (category UserInterface)
|
|
|
|
Methods having to do with user interaction: keypresses, the
|
|
joystick/paddle values and button states, states of the indicator
|
|
lights, etc.
|
|
*/
|
|
#import "LibAppleII-Priv.h"
|
|
#import "A2DiskDrive.h"
|
|
|
|
@implementation A2Computer (UserInterface)
|
|
//---------------------------------------------------------------------------
|
|
|
|
static NSRect gMouseRange;
|
|
|
|
static NSString* gNameOfModel[] =
|
|
{
|
|
@"??", // code 0 is invalid
|
|
|
|
@"][", @"][+", @"//e", @"//c", @"//c+",
|
|
};
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
- (IBAction)SignalReset:(id)sender
|
|
{/*
|
|
Informs this Apple II that the user invoked RESET. The Apple II will
|
|
respond to it at a later time.
|
|
*/
|
|
mHalts |= kfHaltReset;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
- (IBAction)SignalReboot:(id)sender
|
|
{/*
|
|
Informs this Apple II that the user invoked a reboot (a "cold reset").
|
|
The Apple II will respond to it at a later time.
|
|
*/
|
|
mMemory->RAM[0][0][1012] = mMemory->RAM[0][0][1011] = 0;
|
|
// sabotages the "power-up" byte
|
|
|
|
mHalts |= kfHaltReset;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
- (BOOL)InputChar:(unichar)ch
|
|
{/*
|
|
Informs this Apple II of a character typed on the keyboard. Returns
|
|
whether the character was successfully queued. The character code
|
|
must be plain ASCII (range 0-127), as that's all the Apple II ever
|
|
supported.
|
|
|
|
Also, this method turns on the "power" if it isn't already on.
|
|
*/
|
|
if ( (uint8_t)(mKeyQ.tail - mKeyQ.head) > 250 or ch > 127 )
|
|
return NO;
|
|
|
|
if (mModel < kA2ModelIIe)
|
|
ch = toupper(ch);
|
|
|
|
mKeyQ.buf[mKeyQ.tail++] = ch; // enqueue the character
|
|
mKeyQ.hitRecently = YES;
|
|
mHalts &= ~kfHaltNoPower;
|
|
return YES;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
- (void)InputChars:(NSString*)str
|
|
{/*
|
|
Puts the given string of characters into the Apple II's keyboard
|
|
queue, as if they had all been typed -- really really quickly.
|
|
*/
|
|
int len = [str length];
|
|
|
|
if (len > 250)
|
|
len = 250;
|
|
mKeyQ.head = mKeyQ.tail = 0;
|
|
[str getCString:(char*)(mKeyQ.buf) maxLength:len];
|
|
mKeyQ.tail = len;
|
|
mKeyQ.hitRecently = YES;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
+ (void)InputPaddlesByKeypad:(char)ch
|
|
{/*
|
|
Sets the paddle values (and joystick position) according to a digit
|
|
key on the numeric keypad.
|
|
*/
|
|
div_t d = div((ch - 1) & 15, 3);
|
|
|
|
A2G.paddle[0] = (kA2PaddleRange/2) * d.rem;
|
|
A2G.paddle[1] = (kA2PaddleRange/2) * (2 - d.quot);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
+ (void)InputPaddlesByMouse
|
|
{/*
|
|
Sets the paddle values (also joystick position) by the current
|
|
coordinates of the host machine's mouse. Only paddles #0 and #1 are
|
|
affected.
|
|
*/
|
|
NSPoint mloc = [NSEvent mouseLocation];
|
|
int p0, p1;
|
|
|
|
p0 = (mloc.x - gMouseRange.origin.x) * gMouseRange.size.width;
|
|
p1 = kA2PaddleRange -
|
|
(mloc.y - gMouseRange.origin.y) * gMouseRange.size.height;
|
|
|
|
if (p0 < 0)
|
|
p0 = 0;
|
|
A2G.paddle[0] = p0;
|
|
|
|
if (p1 < 0)
|
|
p1 = 0;
|
|
A2G.paddle[1] = p1;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
+ (void)SetMouseRangeTo:(NSRect)r
|
|
{/*
|
|
Informs the library of the extent rectangle over which the mouse's
|
|
coordinates may roam. Affects the future behavior of
|
|
+InputPaddlesByMouse.
|
|
*/
|
|
gMouseRange.origin = r.origin;
|
|
gMouseRange.size.width = kA2PaddleRange / r.size.width;
|
|
gMouseRange.size.height = kA2PaddleRange / r.size.height;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
- (void)_RespondToReset
|
|
{
|
|
#if 0
|
|
strcpy(((char*)mMemory->RAM[0][0]) + 0x309, //!!
|
|
"\xA9\xA\x20\xA8\xFC\xAD\x30\xC0\x4C\x09\3");
|
|
#endif
|
|
|
|
mFlags = kfTEXT | kfLCWRThi | kfLCBANK2 | kfC3ROM;
|
|
if (mModel < kA2ModelIIc)
|
|
mFlags |= 3UL << ksHotSlot;
|
|
memset(mVideoFlags, mFlags, sizeof(mVideoFlags));
|
|
|
|
mKeyQ.tail = mKeyQ.head = 0;
|
|
mPC = mMemory->ROM[0][0x3FFD] << 8 // load PC from $FFFC-D in ROM
|
|
| mMemory->ROM[0][0x3FFC];
|
|
|
|
// mPrinter.reg[0x8] = ??;
|
|
mPrinter.reg[0xA] = 0; // command reg
|
|
mPrinter.reg[0xB] = 0x3E; // control reg
|
|
mPrinter.lights = 0;
|
|
|
|
mIWM[0].flags = mIWM[1].flags = 0;
|
|
mIWM[0].lights = mIWM[1].lights = 0;
|
|
mIWM[0].modeReg = mIWM[1].modeReg = 0; // Does mode-reg reset??
|
|
|
|
// mSlinky.pos = 0; // Does this reset??
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
- (unsigned)Lights
|
|
{/*
|
|
Returns the status of the Apple II's indicator lights as a bit vector.
|
|
Should be called about 3 to 4 times per second from the client
|
|
application.
|
|
*/
|
|
if (mHalts & kfHaltReset) // then RESET was raised earlier
|
|
{
|
|
[self _RespondToReset];
|
|
mHalts &= ~kfHaltReset;
|
|
return 0;
|
|
}
|
|
|
|
unsigned lights = 0;
|
|
|
|
for (int i = 2; --i >= 0;)
|
|
{
|
|
A2IWM* iwm = mIWM + i;
|
|
unsigned lt = iwm->lights;
|
|
|
|
if (lt >= 16)
|
|
iwm->lights -= 16, lights |= (lt&3) << (2*i);
|
|
}
|
|
|
|
if (mPrinter.lights >= 16)
|
|
mPrinter.lights -= 16, lights |= kfA2LightPrinter;
|
|
|
|
if (not mKeyQ.hitRecently and mKeyQ.tail != mKeyQ.head)
|
|
mKeyQ.head++;
|
|
mKeyQ.hitRecently = NO;
|
|
|
|
return lights;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
static inline uint8_t ASCIIfy(int charset, uint8_t ch)
|
|
{/*
|
|
Returns the ASCII equivalent of an Apple II character screen code
|
|
(0-255), or a space character if there is no such thing.
|
|
*/
|
|
static uint8_t flip[3][8] =
|
|
{
|
|
0x40,0x00,0x00,0x40, 0xC0,0x80,0x80,0x80, // IIe std charset
|
|
0x40,0x00,0x40,0x00, 0xC0,0x80,0x80,0x80, // IIe alt (MouseText)
|
|
0x40,0x00,0x00,0x40, 0xC0,0x80,0x80,0xA0, // IIo and IIp
|
|
};
|
|
|
|
ch ^= flip[charset][ch >> 5];
|
|
return (ch < 32 or ch > 126)? 32 : ch;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
- (NSString*)TextScreenAsString:(BOOL)newLines
|
|
{/*
|
|
Returns the content of the Apple II's text screen as a giant string of
|
|
characters. Optionally puts newline characters at the end of each
|
|
screen line. Returns nil if no text is visible.
|
|
*/
|
|
unsigned f = mFlags;
|
|
|
|
if ((f & (kfTEXT | kfMIXED)) == 0) // then in full-graphics mode
|
|
return nil;
|
|
|
|
|
|
char cstr[24*81 + 1], *pout = cstr;
|
|
uint8_t* dispPage = mMemory->RAM[0][0] + 0x400;
|
|
int charset; // 0-2
|
|
|
|
charset = (mModel < kA2ModelIIe)? 2 : (f >> ksALTCHAR & 1);
|
|
if ((f & (kf80STOREv | kfPAGE2v)) == kfPAGE2v)
|
|
dispPage += 0x400;
|
|
|
|
for (int v = (f & kfTEXT)? 0 : 20; v < 24; ++v)
|
|
{
|
|
uint8_t *pin = dispPage + 128*(v&7) + 5*(v&~7);
|
|
|
|
for (int h = 0; h < 40; ++h)
|
|
{
|
|
if (f & kf80COL)
|
|
*pout++ = ASCIIfy(charset, pin[0x2000+h]);
|
|
*pout++ = ASCIIfy(charset, pin[h]);
|
|
}
|
|
if (newLines)
|
|
*pout++ = '\n';
|
|
}
|
|
|
|
return [NSString stringWithCString:cstr length:(pout-cstr)];
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
+ (void)_UpdateClock:(NSTimer*)timer
|
|
{
|
|
static struct
|
|
{
|
|
char hi[100], lo[100];
|
|
}
|
|
digit =
|
|
{
|
|
"00000000001111111111222222222233333333334444444444"
|
|
"55555555556666666666777777777788888888889999999999",
|
|
"01234567890123456789012345678901234567890123456789"
|
|
"01234567890123456789012345678901234567890123456789"
|
|
};
|
|
|
|
static time_t tPrev; // = 0
|
|
time_t t;
|
|
struct tm tm;
|
|
|
|
if (tPrev == time(&t))
|
|
return;
|
|
|
|
// NSLog(@"Clock time being updated.");
|
|
|
|
tPrev = t;
|
|
A2G.timeInGMT? gmtime_r(&t, &tm) : localtime_r(&t, &tm);
|
|
tm.tm_year %= 100;
|
|
tm.tm_mon += 1;
|
|
|
|
uint8_t str[32] =
|
|
{
|
|
tm.tm_mon << 5 | tm.tm_mday,
|
|
tm.tm_year << 1 | tm.tm_mon >> 3,
|
|
tm.tm_min, tm.tm_hour,
|
|
|
|
digit.hi[tm.tm_mon ], digit.lo[tm.tm_mon ], ',',
|
|
'0', digit.lo[tm.tm_wday], ',',
|
|
digit.hi[tm.tm_mday], digit.lo[tm.tm_mday], ',',
|
|
digit.hi[tm.tm_hour], digit.lo[tm.tm_hour], ',',
|
|
digit.hi[tm.tm_min ], digit.lo[tm.tm_min ], ',',
|
|
digit.hi[tm.tm_sec ], digit.lo[tm.tm_sec ], 13,
|
|
};
|
|
|
|
for (int i = 32/4; --i >= 0;)
|
|
((uint32_t*)(A2T.curTime))[i] = ((uint32_t*)str)[i];
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
- (id<A2PrDiskDrive>)DiskDrive:(unsigned)index
|
|
{/*
|
|
Returns the disk drive object identified by the given index (0-3).
|
|
*/
|
|
return (index > 3)? nil :
|
|
mIWM[index >> 1].drive[index & 1];
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
- (NSString*)ModelName
|
|
{ return gNameOfModel[mModel]; }
|
|
// Returns a short name for this Apple II's model.
|
|
|
|
- (unsigned)ModelCode
|
|
{ return mModel; }
|
|
// Returns the numeric code for this Apple II's model.
|
|
|
|
- (BOOL)acceptsFirstResponder
|
|
{ return YES; }
|
|
|
|
+ (BOOL)ShouldShowDiskFilename:(NSString*)path
|
|
{ return [A2DiskDrive ShouldShowFilename:path]; }
|
|
|
|
//---------------------------------------------------------------------------
|
|
@end
|