PDP-8-E-Simulator/CPUWindow/CPUMemoryViewController.m

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