/* * 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 . */ #import #import // 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 { } @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 )info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation { return operation == NSTableViewDropOn ? NSDragOperationPrivate : NSDragOperationNone; } - (BOOL) tableView:(NSTableView *)tableView acceptDrop:(id )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