mirror of
https://github.com/DerekK19/PDP-8-E-Simulator.git
synced 2024-10-13 21:24:28 +00:00
655 lines
23 KiB
Objective-C
655 lines
23 KiB
Objective-C
/*
|
|
* PDP-8/E Simulator
|
|
*
|
|
* Copyright © 1994-2015 Bernhard Baehr
|
|
*
|
|
* CPUMemoryViewController.m - Controller for the CPU window memory view
|
|
*
|
|
* This file is part of PDP-8/E Simulator.
|
|
*
|
|
* PDP-8/E Simulator 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
#import <Carbon/Carbon.h> // for Help Manger functions
|
|
|
|
#import "CPUMemoryViewController.h"
|
|
#import "PDP8.h"
|
|
#import "Opcode.h"
|
|
#import "Breakpoint.h"
|
|
#import "BreakpointArray.h"
|
|
#import "NSTableView+Scrolling.h"
|
|
#import "NonWrappingTableView.h"
|
|
#import "OpcodeFormatter.h"
|
|
#import "Disassembler.h"
|
|
#import "Unicode.h"
|
|
#import "Utilities.h"
|
|
|
|
|
|
#define PC_ARROW_BLUE_IMAGE @"pcArrowBlue"
|
|
#define PC_ARROW_GRAPHITE_IMAGE @"pcArrowGraphite"
|
|
#define PC_ARROW_DRAG_TYPE @"pcArrowDragType"
|
|
#define UPDATE_MEMORY_NOTIFICATION @"UpdateMemoryNotification"
|
|
|
|
#define PC_DEFAULT_ROW_PREFS_KEY @"DefaultPCRow"
|
|
|
|
#define PC_DEFAULT_ROW 10
|
|
|
|
#define PC_COLUMN 0
|
|
#define PC_COLUMN_STR @"0"
|
|
#define BP_COLUMN 1
|
|
#define ADDR_COLUMN 2
|
|
#define WORD_COLUMN 3
|
|
#define OPCODE_COLUMN 4
|
|
|
|
#define CONTEXTMENU_SET_BREAKPOINT 0
|
|
#define CONTEXTMENU_SET_BREAKOPCODE 1
|
|
#define CONTEXTMENU_SET_SYSTEM_BREAKOPCODE 2
|
|
#define CONTEXTMENU_SET_USER_BREAKOPCODE 3
|
|
#define CONTEXTMENU_SET_PC 4
|
|
#define CONTEXTMENU_GO_AND_STOP_HERE 5
|
|
#define CONTEXTMENU_TRACE_AND_STOP_HERE 6
|
|
#define CONTEXTMENU_SCROLL_TO_PC 7
|
|
#define CONTEXTMENU_SET_DEFAULT_PC_ROW 8
|
|
|
|
|
|
@interface CPUMemoryTableView : NonWrappingTableView <OpcodeFormatterAddressGetter>
|
|
{
|
|
}
|
|
@end
|
|
|
|
|
|
@implementation CPUMemoryTableView
|
|
|
|
|
|
- (BOOL) canDragRowsWithIndexes:(NSIndexSet *)rows atPoint:(NSPoint)mouseDownPoint
|
|
{
|
|
// allow drag only for current PC row to avoid minor screen flicker with the clicked cell with Yosemite
|
|
return [rows count] == 1 && [[self delegate] tableView:self
|
|
objectValueForTableColumn:[self tableColumnWithIdentifier:PC_COLUMN_STR] row:[rows firstIndex]]
|
|
!= nil;
|
|
}
|
|
|
|
|
|
- (NSImage *) dragImageForRowsWithIndexes:(NSIndexSet *)dragRows tableColumns:(NSArray *)tableColumns
|
|
event:(NSEvent*)dragEvent offset:(NSPointPointer)dragImageOffset
|
|
{
|
|
return [NSImage imageNamed:[NSColor currentControlTint] == NSGraphiteControlTint ?
|
|
PC_ARROW_GRAPHITE_IMAGE : PC_ARROW_BLUE_IMAGE];
|
|
}
|
|
|
|
|
|
- (NSMenu *) menuForEvent:(NSEvent *)event
|
|
{
|
|
int row = [self rowAtPoint:[self convertPoint:[event locationInWindow] fromView:nil]];
|
|
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
|
|
return [self menu];
|
|
}
|
|
|
|
|
|
- (void) mouseDown:(NSEvent *)event
|
|
{
|
|
CFStringRef tipContent;
|
|
|
|
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
|
|
int row = [self rowAtPoint:point];
|
|
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
|
|
// why does [super mouseDown:event] not select the clicked row immediately?
|
|
if (([event modifierFlags] & NSShiftKeyMask) && [self columnAtPoint:point] == OPCODE_COLUMN &&
|
|
(tipContent = (CFStringRef) [[self delegate] operandInfoAtAddress:row])) {
|
|
HMHelpContentRec tip;
|
|
tip.version = kMacHelpVersion;
|
|
NSRect rect = [self frameOfCellAtColumn:OPCODE_COLUMN row:row];
|
|
rect.origin = [[self window] convertBaseToScreen:
|
|
[self convertPoint:rect.origin toView:nil]];
|
|
float scale = [[self window] userSpaceScaleFactor];
|
|
tip.absHotRect.left = rect.origin.x / scale + 33;
|
|
tip.absHotRect.right = (rect.origin.x + rect.size.width) / scale;
|
|
tip.absHotRect.top =
|
|
([[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.origin.y) / scale;
|
|
tip.absHotRect.bottom = tip.absHotRect.top + rect.size.height / scale;
|
|
tip.tagSide = kHMOutsideBottomLeftAligned;
|
|
tip.content[0].contentType = tip.content[1].contentType = kHMCFStringContent;
|
|
tip.content[0].u.tagCFString = tip.content[1].u.tagCFString = tipContent;
|
|
HMDisplayTag (&tip);
|
|
}
|
|
[super mouseDown:event];
|
|
HMHideTag ();
|
|
}
|
|
|
|
|
|
- (void) selectRowIndexes:(NSIndexSet *)indexes byExtendingSelection:(BOOL)extend
|
|
{
|
|
if ((int) [indexes firstIndex] != [self selectedRow])
|
|
HMHideTag ();
|
|
[super selectRowIndexes:indexes byExtendingSelection:extend];
|
|
}
|
|
|
|
|
|
- (int) getCurrentAddress // OpcodeFormatterAddressGetter protocol
|
|
{
|
|
return [self selectedRow];
|
|
}
|
|
|
|
|
|
@end
|
|
|
|
|
|
@implementation CPUMemoryViewController
|
|
|
|
|
|
- (void) setYosemiteTitleKerning:(NSCell *)cell
|
|
{
|
|
// otherwise, the title of the PC and BP columns is clipped
|
|
// (capitals are longer with Helvetica Neue than with Lucida Grande
|
|
if (runningOnYosemiteOrNewer()) {
|
|
double kern = 0.4;
|
|
if (runningOnElCapitanOrNewer())
|
|
kern = -0.5;
|
|
[cell setTitle:[[[NSAttributedString alloc] initWithString:[cell title] attributes:
|
|
[NSDictionary dictionaryWithObject:[NSNumber numberWithDouble:kern] forKey:NSKernAttributeName]]
|
|
autorelease]];
|
|
}
|
|
}
|
|
|
|
|
|
- (void) awakeFromNib
|
|
{
|
|
NSSize size;
|
|
|
|
NSFont *font = [NSFont userFixedPitchFontOfSize:11];
|
|
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
|
|
|
|
[self setYosemiteTitleKerning:[[[memoryView tableColumns] objectAtIndex:PC_COLUMN] headerCell]];
|
|
[self setYosemiteTitleKerning:[[[memoryView tableColumns] objectAtIndex:BP_COLUMN] headerCell]];
|
|
adjustTableHeaderForElCapitan (memoryView);
|
|
size.width = 1;
|
|
size.height = [memoryView rowHeight] + 2;
|
|
[[memoryView window] setResizeIncrements:size];
|
|
[[[[memoryView tableColumns] objectAtIndex:ADDR_COLUMN] dataCell] setFont:font];
|
|
[[[[memoryView tableColumns] objectAtIndex:WORD_COLUMN] dataCell] setFont:font];
|
|
[[[[memoryView tableColumns] objectAtIndex:OPCODE_COLUMN] dataCell] setFont:font];
|
|
[[[[memoryView tableColumns] objectAtIndex:OPCODE_COLUMN] dataCell] setFormatter:
|
|
[OpcodeFormatter formatterWithPDP8:pdp8 addressGetter:memoryView]];
|
|
[memoryView setTarget:self];
|
|
[memoryView setDoubleAction:@selector(memoryViewDoubleClick:)];
|
|
[memoryView registerForDraggedTypes:[NSArray arrayWithObject:PC_ARROW_DRAG_TYPE]];
|
|
pcDefaultRow = [[NSUserDefaults standardUserDefaults] integerForKey:PC_DEFAULT_ROW_PREFS_KEY];
|
|
if (pcDefaultRow == 0)
|
|
pcDefaultRow = PC_DEFAULT_ROW;
|
|
[memoryView scrollRowToTop:0200 + 1 - pcDefaultRow];
|
|
// frame changed notification seems not work for the tableview itself
|
|
[[memoryView superview] setPostsFrameChangedNotifications:YES];
|
|
// save the visible range now to be able to scroll to the correct location after "go"
|
|
visibleMemoryRange = [memoryView rowsInRect:[memoryView visibleRect]];
|
|
ignoreUpdateMemoryNotification = NO;
|
|
ignorePCChangedNotification = NO;
|
|
[defaultCenter addObserver:self selector:@selector(notifyMemoryViewSizeChanged:)
|
|
name:NSViewFrameDidChangeNotification object:[memoryView superview]];
|
|
[defaultCenter addObserver:self selector:@selector(notifyMemoryChanged:)
|
|
name:MEMORY_CHANGED_NOTIFICATION object:nil];
|
|
[defaultCenter addObserver:self selector:@selector(notifyMemoryChanged:)
|
|
name:EAE_MODE_CHANGED_NOTIFICATION object:nil];
|
|
[defaultCenter addObserver:self selector:@selector(notifyMemoryChanged:)
|
|
name:BREAKPOINTS_CHANGED_NOTIFICATION object:nil];
|
|
[defaultCenter addObserver:self selector:@selector(notifyMemoryChanged:)
|
|
name:PROGRAM_COUNTER_CHANGED_NOTIFICATION object:nil];
|
|
[defaultCenter addObserver:self selector:@selector(notifyMemoryChanged:)
|
|
name:DF_CHANGED_NOTIFICATION object:nil];
|
|
[defaultCenter addObserver:self selector:@selector(notifyUpdateMemoryView:)
|
|
name:UPDATE_MEMORY_NOTIFICATION object:nil];
|
|
[defaultCenter addObserver:self selector:@selector(notifyGoPDP8:)
|
|
name:PDP8_GO_NOTIFICATION object:nil];
|
|
[defaultCenter addObserver:self selector:@selector(notifyStepPDP8:)
|
|
name:PDP8_STEP_NOTIFICATION object:nil];
|
|
[defaultCenter addObserver:self selector:@selector(notifyStopPDP8:)
|
|
name:PDP8_STOP_NOTIFICATION object:nil];
|
|
[defaultCenter addObserver:self selector:@selector(notifyPCChanged:)
|
|
name:PROGRAM_COUNTER_CHANGED_NOTIFICATION object:nil];
|
|
if (runningOnYosemiteOrNewer()) {
|
|
[defaultCenter addObserver:self selector:@selector(notifyMainOrKeyWindowChanged:)
|
|
name:NSWindowDidBecomeMainNotification object:nil];
|
|
[defaultCenter addObserver:self selector:@selector(notifyMainOrKeyWindowChanged:)
|
|
name:NSWindowDidResignMainNotification object:nil];
|
|
} else {
|
|
[defaultCenter addObserver:self selector:@selector(notifyMainOrKeyWindowChanged:)
|
|
name:NSWindowDidBecomeKeyNotification object:nil];
|
|
[defaultCenter addObserver:self selector:@selector(notifyMainOrKeyWindowChanged:)
|
|
name:NSWindowDidResignKeyNotification object:nil];
|
|
}
|
|
}
|
|
|
|
|
|
- (void) updateVisibleMemoryRange
|
|
{
|
|
// save the visible range now to be able to scroll to the correct location after "go"
|
|
// calculate number of rows manually because otherwise it is one to large when the view is not exactly aligned
|
|
// see also [MemoryInspectorController visibleRange] and [NSTableView(Scrolling) scrollRowToTop:]
|
|
NSRect rect = [memoryView visibleRect];
|
|
if (rect.size.height > 0) { // zero immediately after "Stop" when the window is not yet enlarged
|
|
if (runningOnElCapitanOrNewer())
|
|
rect.origin.y += [memoryView rectOfRow:0].size.height;
|
|
unsigned pixelPerRow = (unsigned) ([memoryView rowHeight] + [memoryView intercellSpacing].height);
|
|
visibleMemoryRange.location = [memoryView rowsInRect:rect].location;
|
|
visibleMemoryRange.length = rect.size.height / pixelPerRow;
|
|
}
|
|
}
|
|
|
|
|
|
- (BOOL) tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)column row:(int)row
|
|
{
|
|
return 0 <= row && row < [pdp8 memorySize];
|
|
}
|
|
|
|
|
|
- (BOOL) tableView:(NSTableView *)tableView shouldSelectRow:(int)row
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
|
|
- (void) scrollToPC
|
|
{
|
|
[self updateVisibleMemoryRange];
|
|
[memoryView scrollRowToTop:max(0, min((int) (PDP8_MEMSIZE - visibleMemoryRange.length),
|
|
(int) ([pdp8 getProgramCounter] - pcDefaultRow + 1)))];
|
|
}
|
|
|
|
|
|
- (BOOL) tableView:(NSTableView *)tableView shouldSelectTableColumn:(NSTableColumn *)column
|
|
{
|
|
if ([[column identifier] intValue] == PC_COLUMN)
|
|
[self scrollToPC];
|
|
return NO;
|
|
}
|
|
|
|
|
|
- (BOOL) tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rows
|
|
toPasteboard:(NSPasteboard *)pboard
|
|
{
|
|
if ([rows count] != 1 || [rows firstIndex] != [pdp8 getProgramCounter])
|
|
return NO;
|
|
[pboard declareTypes:[NSArray arrayWithObject:PC_ARROW_DRAG_TYPE] owner:nil];
|
|
return YES;
|
|
}
|
|
|
|
|
|
- (NSDragOperation) tableView:(NSTableView *)tableView validateDrop:(id <NSDraggingInfo>)info
|
|
proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
|
|
{
|
|
return operation == NSTableViewDropOn ? NSDragOperationPrivate : NSDragOperationNone;
|
|
}
|
|
|
|
|
|
- (BOOL) tableView:(NSTableView *)tableView acceptDrop:(id <NSDraggingInfo>)info
|
|
row:(int)row dropOperation:(NSTableViewDropOperation)operation
|
|
{
|
|
if ((row > 07777) & ! [pdp8 hasKM8E])
|
|
return NO;
|
|
[pdp8 setProgramCounter:row];
|
|
return YES;
|
|
}
|
|
|
|
|
|
- (int) numberOfRowsInTableView:(NSTableView *)tableView
|
|
{
|
|
return PDP8_MEMSIZE;
|
|
}
|
|
|
|
|
|
- (id) tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(int)row
|
|
{
|
|
switch ([[column identifier] intValue]) {
|
|
case PC_COLUMN :
|
|
if (row == [pdp8 getProgramCounter])
|
|
return [NSImage imageNamed:([NSColor currentControlTint] == NSBlueControlTint)
|
|
&& (runningOnYosemiteOrNewer() ?
|
|
[[tableView window] isMainWindow] : [[tableView window] isKeyWindow]) ?
|
|
PC_ARROW_BLUE_IMAGE : PC_ARROW_GRAPHITE_IMAGE];
|
|
break;
|
|
case BP_COLUMN :
|
|
if ([breakpoints valueForIdentifier:row])
|
|
return [NSImage imageNamed:@"breakpoint"];
|
|
switch ([breakopcodes valueForIdentifier:[pdp8 memoryAt:row]]) {
|
|
case BREAKOPCODE :
|
|
return [NSImage imageNamed:@"breakOpcode"];
|
|
case USERMODE_BREAKOPCODE :
|
|
return [NSImage imageNamed:@"breakOpcodeU"];
|
|
case SYSTEMMODE_BREAKOPCODE :
|
|
return [NSImage imageNamed:@"breakOpcodeS"];
|
|
}
|
|
break;
|
|
case ADDR_COLUMN :
|
|
if ((row & 007770) == 000010) { // autoincrement locations are underlined
|
|
NSMutableParagraphStyle *style =
|
|
[[[NSMutableParagraphStyle alloc] init] autorelease];
|
|
[style setAlignment:NSCenterTextAlignment];
|
|
return [[[NSAttributedString alloc]
|
|
initWithString:[NSString stringWithFormat:@"%5.5o", row]
|
|
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithInt:NSSingleUnderlineStyle],
|
|
NSUnderlineStyleAttributeName,
|
|
style, NSParagraphStyleAttributeName,
|
|
nil]] autorelease];
|
|
}
|
|
return [NSString stringWithFormat:@"%5.5o", row];
|
|
case WORD_COLUMN :
|
|
return [NSString stringWithFormat:@"%4.4o", [pdp8 memoryAt:row]];
|
|
case OPCODE_COLUMN :
|
|
return [Opcode opcodeWithAddress:row value:[pdp8 memoryAt:row]];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
|
|
- (NSString *) tableView:(NSTableView *)tableView toolTipForCell:(NSCell *)cell
|
|
rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)column row:(int)row
|
|
mouseLocation:(NSPoint)mouseLocation
|
|
{
|
|
switch ([[column identifier] intValue]) {
|
|
case PC_COLUMN :
|
|
return NSLocalizedString(
|
|
@"In this column, an arrow indicates the PDP-8/E program counter.\n\n"
|
|
"Drag the arrow or double-click a location to modify the program counter.\n\n"
|
|
"Click the column header to scroll to the current program counter location.", @"");
|
|
case BP_COLUMN :
|
|
return NSLocalizedString(@"This column shows breakpoints with a red dot. "
|
|
"Break opcodes are indicated with a yellow dot. "
|
|
"A small " UNICODE_LEFT_DOUBLEQUOTE_UTF8 "s" UNICODE_RIGHT_DOUBLEQUOTE_UTF8 " or "
|
|
UNICODE_LEFT_DOUBLEQUOTE_UTF8 "u" UNICODE_RIGHT_DOUBLEQUOTE_UTF8 " in the dot "
|
|
"indicate system or user mode break opcodes.\n\n"
|
|
"Double-click sets or clears a breakpoint.\n\n"
|
|
"Option-double-click and command-option-double-click toogles break opcodes.", @"");
|
|
case ADDR_COLUMN :
|
|
return NSLocalizedString(@"This column displays the memory adresses. "
|
|
"Adresses of autoincrement memory locations are underlined.", @"");
|
|
case WORD_COLUMN :
|
|
return NSLocalizedString(@"This column displays the octal memory content.", @"");
|
|
case OPCODE_COLUMN :
|
|
return NSLocalizedString(@"This column displays the disassembled PDP-8 instruction.\n\n"
|
|
"Shift-click to view the operands of MRIs.", @"");
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
|
|
- (void) tableView:(NSTableView *)tableView setObjectValue:(Opcode *)opcode
|
|
forTableColumn:(NSTableColumn *)column row:(int)row
|
|
{
|
|
// why do we get this setObjectValue message for read-only cells?
|
|
if (row < [pdp8 memorySize]) {
|
|
ignoreUpdateMemoryNotification = YES;
|
|
[pdp8 setMemoryAtAddress:row toValue:[opcode word0]];
|
|
if ([opcode word1] >= 0)
|
|
[pdp8 setMemoryAtNextAddress:row toValue:[opcode word1]];
|
|
}
|
|
}
|
|
|
|
|
|
- (BOOL) control:(NSControl *)control didFailToFormatString:(NSString *)string
|
|
errorDescription:(NSString *)error
|
|
{
|
|
NSRange range;
|
|
|
|
NSScanner *scanner = [NSScanner scannerWithString:error];
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
|
[scanner scanInt:(signed *) &range.location];
|
|
range.length = [string length] - range.location;
|
|
[[control currentEditor] setSelectedRange:range];
|
|
[alert setMessageText:[error substringFromIndex:[scanner scanLocation] + 1]];
|
|
[alert beginSheetModalForWindow:[control window]
|
|
modalDelegate:nil didEndSelector:nil contextInfo:nil];
|
|
[alert release];
|
|
return NO;
|
|
}
|
|
|
|
|
|
- (BOOL) control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
|
|
{
|
|
if (command == @selector(cancelOperation:)) {
|
|
// ESC aborts editing of the current cell
|
|
[control abortEditing];
|
|
return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
|
|
- (void) memoryViewDoubleClick:(id)sender
|
|
{
|
|
unsigned modifiers;
|
|
|
|
int row = [sender clickedRow];
|
|
if (row < 0 || [pdp8 isRunning])
|
|
return;
|
|
switch ([sender clickedColumn]) {
|
|
case PC_COLUMN :
|
|
if ([pdp8 hasKM8E] || row < 010000)
|
|
[pdp8 setProgramCounter:row];
|
|
break;
|
|
case BP_COLUMN :
|
|
case ADDR_COLUMN :
|
|
modifiers = [[NSApp currentEvent] modifierFlags];
|
|
if (modifiers & NSAlternateKeyMask) {
|
|
unsigned opcode = [pdp8 memoryAt:row];
|
|
unsigned value = [breakopcodes valueForIdentifier:opcode];
|
|
if (modifiers & NSCommandKeyMask)
|
|
value = (value + BREAKOPCODE) & BREAKOPCODE;
|
|
else
|
|
value = value ? 0 : BREAKOPCODE;
|
|
[breakopcodes setBreakpointWithIdentifier:opcode value:value];
|
|
} else
|
|
[breakpoints setBreakpointWithIdentifier:row
|
|
value:[breakpoints valueForIdentifier:row] ? 0 : BREAKPOINT];
|
|
[sender reloadData];
|
|
break;
|
|
case WORD_COLUMN :
|
|
break;
|
|
case OPCODE_COLUMN :
|
|
// with Leopard, this is required, don't know why: for editable cells, the
|
|
// double action should not be called, but the editing should start automatically
|
|
// (NSTableView documentation)
|
|
if (row < [pdp8 memorySize])
|
|
[sender editColumn:OPCODE_COLUMN row:row withEvent:nil select:YES];
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
- (BOOL) validateMenuItem:(NSMenuItem *)menuItem
|
|
{
|
|
if ([pdp8 isRunning])
|
|
return FALSE;
|
|
int row = [memoryView selectedRow];
|
|
unsigned breakop = [breakopcodes valueForIdentifier:[pdp8 memoryAt:row]];
|
|
switch ([menuItem tag]) {
|
|
case CONTEXTMENU_SET_BREAKPOINT :
|
|
[menuItem setTitle:[breakpoints valueForIdentifier:row] ?
|
|
NSLocalizedString(@"Clear Breakpoint", @"") :
|
|
NSLocalizedString(@"Set Breakpoint", @"")];
|
|
return TRUE;
|
|
case CONTEXTMENU_SET_BREAKOPCODE :
|
|
[menuItem setTitle:breakop == BREAKOPCODE ?
|
|
NSLocalizedString(@"Clear Break Opcode", @"") :
|
|
NSLocalizedString(@"Set Break Opcode", @"")];
|
|
return breakop == BREAKOPCODE || breakop == 0;
|
|
case CONTEXTMENU_SET_SYSTEM_BREAKOPCODE :
|
|
[menuItem setTitle:(breakop & SYSTEMMODE_BREAKOPCODE) ?
|
|
NSLocalizedString(@"Clear System Mode Break Opcode", @"") :
|
|
NSLocalizedString(@"Set System Mode Break Opcode", @"")];
|
|
return TRUE;
|
|
case CONTEXTMENU_SET_USER_BREAKOPCODE :
|
|
[menuItem setTitle:(breakop & USERMODE_BREAKOPCODE) ?
|
|
NSLocalizedString(@"Clear User Mode Break Opcode", @"") :
|
|
NSLocalizedString(@"Set User Mode Break Opcode", @"")];
|
|
return TRUE;
|
|
case CONTEXTMENU_SET_PC :
|
|
case CONTEXTMENU_GO_AND_STOP_HERE :
|
|
case CONTEXTMENU_TRACE_AND_STOP_HERE :
|
|
return row < 010000 || [pdp8 hasKM8E];
|
|
case CONTEXTMENU_SCROLL_TO_PC :
|
|
case CONTEXTMENU_SET_DEFAULT_PC_ROW :
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
- (IBAction) handleContextMenu:(id)sender
|
|
{
|
|
int row = [memoryView selectedRow];
|
|
unsigned opcode = [pdp8 memoryAt:row];
|
|
unsigned breakop = [breakopcodes valueForIdentifier:opcode];
|
|
switch ([sender tag]) {
|
|
case CONTEXTMENU_SET_BREAKPOINT :
|
|
[breakpoints setBreakpointWithIdentifier:row
|
|
value:[breakpoints valueForIdentifier:row] ? 0 : BREAKPOINT];
|
|
break;
|
|
case CONTEXTMENU_SET_BREAKOPCODE :
|
|
[breakopcodes setBreakpointWithIdentifier:opcode value:breakop ? 0 : BREAKOPCODE];
|
|
break;
|
|
case CONTEXTMENU_SET_SYSTEM_BREAKOPCODE :
|
|
[breakopcodes setBreakpointWithIdentifier:opcode value:breakop ^ SYSTEMMODE_BREAKOPCODE];
|
|
break;
|
|
case CONTEXTMENU_SET_USER_BREAKOPCODE :
|
|
[breakopcodes setBreakpointWithIdentifier:opcode value:breakop ^ USERMODE_BREAKOPCODE];
|
|
break;
|
|
case CONTEXTMENU_SET_PC :
|
|
if ([pdp8 hasKM8E] || row < 010000)
|
|
[pdp8 setProgramCounter:row];
|
|
break;
|
|
case CONTEXTMENU_GO_AND_STOP_HERE :
|
|
[pdp8 go:row];
|
|
break;
|
|
case CONTEXTMENU_TRACE_AND_STOP_HERE :
|
|
[pdp8 trace:row];
|
|
break;
|
|
case CONTEXTMENU_SCROLL_TO_PC :
|
|
[self scrollToPC];
|
|
break;
|
|
case CONTEXTMENU_SET_DEFAULT_PC_ROW :
|
|
[self updateVisibleMemoryRange];
|
|
pcDefaultRow = row - visibleMemoryRange.location + 1;
|
|
[[NSUserDefaults standardUserDefaults] setInteger:pcDefaultRow
|
|
forKey:PC_DEFAULT_ROW_PREFS_KEY];
|
|
[self scrollToPC];
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
- (NSString *) operandInfoAtAddress:(int)addr
|
|
{
|
|
return [[Disassembler sharedDisassembler] operandInfoForPDP8:pdp8 atAddress:addr];
|
|
}
|
|
|
|
|
|
#pragma mark Notifications
|
|
|
|
|
|
- (void) notifyMainOrKeyWindowChanged:(NSNotification *)notification
|
|
{
|
|
// NSLog (@"CPUMemoryViewController notifyMainOrKeyWindowChanged");
|
|
if ([[notification object] isEqual:[memoryView window]])
|
|
[memoryView setNeedsDisplayInRect:
|
|
[memoryView frameOfCellAtColumn:PC_COLUMN row:[pdp8 getProgramCounter]]];
|
|
}
|
|
|
|
|
|
- (void) notifyMemoryViewSizeChanged:(NSNotification *)notification
|
|
{
|
|
// NSLog (@"CPUMemoryViewController notifyMemoryViewSizeChanged");
|
|
[self updateVisibleMemoryRange];
|
|
if (visibleMemoryRange.length < pcDefaultRow) {
|
|
pcDefaultRow = PC_DEFAULT_ROW;
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:PC_DEFAULT_ROW_PREFS_KEY];
|
|
}
|
|
}
|
|
|
|
|
|
- (void) notifyMemoryChanged:(NSNotification *)notification
|
|
{
|
|
/* coalesc the multiple MEMORY_CHANGED_NOTIFICATIONS (caused by the different register update
|
|
notifications) to one UPDATE_MEMORY_NOTIFICATION to avoid time consuming repeated memory
|
|
view updates */
|
|
// NSLog (@"CPUMemoryViewController notifyMemoryChanged");
|
|
[[NSNotificationQueue defaultQueue] enqueueNotification:
|
|
[NSNotification notificationWithName:UPDATE_MEMORY_NOTIFICATION object:self]
|
|
postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil];
|
|
}
|
|
|
|
|
|
- (void) notifyUpdateMemoryView:(NSNotification *)notification
|
|
{
|
|
// NSLog (@"CPUMemoryViewController notifyUpdateMemoryView ign=%d", ignoreUpdateMemoryNotification);
|
|
if (ignoreUpdateMemoryNotification)
|
|
ignoreUpdateMemoryNotification = NO;
|
|
else
|
|
[memoryView reloadData];
|
|
}
|
|
|
|
|
|
- (void) notifyGoPDP8:(NSNotification *)notification
|
|
{
|
|
// NSLog (@"CPUMemoryViewController notifyGoPDP8");
|
|
[self updateVisibleMemoryRange];
|
|
}
|
|
|
|
|
|
- (void) notifyStepPDP8:(NSNotification *)notification
|
|
{
|
|
// NSLog (@"CPUMemoryViewController notifyStepPDP8");
|
|
[self updateVisibleMemoryRange];
|
|
unsigned pc = [pdp8 getProgramCounter];
|
|
if (visibleMemoryRange.location + visibleMemoryRange.length == pc)
|
|
[memoryView scrollRowToVisible:pc];
|
|
else if (pc < visibleMemoryRange.location || visibleMemoryRange.location + visibleMemoryRange.length <= pc)
|
|
[memoryView scrollRowToTop:
|
|
max(0, min((int) (PDP8_MEMSIZE - visibleMemoryRange.length), (int) (pc - pcDefaultRow + 1)))];
|
|
[memoryView reloadData];
|
|
ignoreUpdateMemoryNotification = YES;
|
|
ignorePCChangedNotification = YES;
|
|
}
|
|
|
|
|
|
- (void) notifyStopPDP8:(NSNotification *)notification
|
|
{
|
|
// NSLog (@"CPUMemoryViewController notifyStopPDP8");
|
|
[self updateVisibleMemoryRange];
|
|
unsigned pc = [pdp8 getProgramCounter];
|
|
if (pc < visibleMemoryRange.location || visibleMemoryRange.location + visibleMemoryRange.length <= pc)
|
|
[memoryView scrollRowToTop:
|
|
max(0, min((int) (PDP8_MEMSIZE - visibleMemoryRange.length), (int) (pc - pcDefaultRow + 1)))];
|
|
ignorePCChangedNotification = YES;
|
|
}
|
|
|
|
|
|
- (void) notifyPCChanged:(NSNotification *)notification
|
|
{
|
|
// NSLog (@"CPUMemoryViewController notifyPCChanged %d", ignorePCChangedNotification);
|
|
if (ignorePCChangedNotification) {
|
|
ignorePCChangedNotification = NO;
|
|
return;
|
|
}
|
|
[self updateVisibleMemoryRange];
|
|
unsigned pc = [pdp8 getProgramCounter];
|
|
if (pc < visibleMemoryRange.location || visibleMemoryRange.location + visibleMemoryRange.length <= pc)
|
|
[memoryView scrollRowToTop:
|
|
max(0, min((int) (PDP8_MEMSIZE - visibleMemoryRange.length), (int) (pc - pcDefaultRow + 1)))];
|
|
}
|
|
|
|
|
|
@end
|