mirror of
https://github.com/DerekK19/PDP-8-E-Simulator.git
synced 2024-05-31 15:41:30 +00:00
449 lines
15 KiB
Mathematica
449 lines
15 KiB
Mathematica
|
/*
|
||
|
* PDP-8/E Simulator
|
||
|
*
|
||
|
* Copyright © 1994-2015 Bernhard Baehr
|
||
|
*
|
||
|
* MemoryInspectorController.m - Controller for the Memory Inspectors Drawer
|
||
|
*
|
||
|
* 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 <objc/objc-runtime.h>
|
||
|
|
||
|
#import "MemoryInspectorController.h"
|
||
|
#import "MemoryInspectorProtocol.h"
|
||
|
#import "NonWrappingTableView.h"
|
||
|
#import "NSTableView+Scrolling.h"
|
||
|
#import "TableCornerView.h"
|
||
|
#import "OctalFormatter.h"
|
||
|
#import "Utilities.h"
|
||
|
#import "PDP8.h"
|
||
|
|
||
|
|
||
|
#define ADDR_COLUMN 0
|
||
|
#define OCTAL_COLUMN 1
|
||
|
#define CONTENT_COLUMN 2
|
||
|
|
||
|
#define ADDR_COLUMN_ID @"0"
|
||
|
#define OCTAL_COLUMN_ID @"1"
|
||
|
#define CONTENT_COLUMN_ID @"2"
|
||
|
|
||
|
#define CURRENT_INSPECTOR_CLASS_PREFS_KEY @"MemInspectorClass"
|
||
|
#define TOP_ROW_PREFS_KEY @"MemInspectorTopRow"
|
||
|
#define ALIGNMENT_PREFS_KEY @"MemInspectorAlign"
|
||
|
#define INSPECTOR_OPEN_PREFS_KEY @"MemInspectorOpen"
|
||
|
|
||
|
|
||
|
@implementation NSFormatter (OrderInMemoryInspectorMenu)
|
||
|
|
||
|
|
||
|
- (NSComparisonResult) compareOrderInMemoryInspectorMenu:(id <MemoryInspector>)inspector
|
||
|
{
|
||
|
return [[(id <MemoryInspector>) self orderInMemoryInspectorMenu]
|
||
|
compare:[inspector orderInMemoryInspectorMenu]];
|
||
|
}
|
||
|
|
||
|
|
||
|
@end
|
||
|
|
||
|
|
||
|
@implementation MemoryInspectorScrollView : NSScrollView
|
||
|
|
||
|
|
||
|
- (void) setFrame:(NSRect)frameRect
|
||
|
/* Auto resizing of the drawer makes the memory table view overlap the memory format popup
|
||
|
menu when the CPU window shrinks to the title bar (while "go" mode). (Cocoa bug?)
|
||
|
This methods calculates the scroll view height from the drawer height and the initial,
|
||
|
correct height delta between drawer and scroll view height. */
|
||
|
{
|
||
|
static float delta = (float) 0.0;
|
||
|
|
||
|
if (delta == 0.0) {
|
||
|
if ([self window])
|
||
|
delta = [[self window] frame].size.height - frameRect.size.height;
|
||
|
} else
|
||
|
frameRect.size.height = [[self window] frame].size.height - delta;
|
||
|
[super setFrame:frameRect];
|
||
|
}
|
||
|
|
||
|
|
||
|
@end
|
||
|
|
||
|
|
||
|
@implementation MemoryInspectorController
|
||
|
|
||
|
|
||
|
- (void) cancelEditingInInspector
|
||
|
{
|
||
|
NSResponder *first, *next;
|
||
|
|
||
|
for (first = next = [[NSApp keyWindow] firstResponder]; next; next = [next nextResponder]) {
|
||
|
if (next == [memoryInspectorDrawer contentView] &&
|
||
|
[[first class] isSubclassOfClass:[NSTextView class]]) {
|
||
|
[first doCommandBySelector:@selector(cancelOperation:)];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
- (NSArray *) allMemoryInspectors
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
int numClasses = 0;
|
||
|
int newNumClasses = objc_getClassList(NULL, 0);
|
||
|
|
||
|
Class *allClasses = NULL;
|
||
|
while (numClasses < newNumClasses) {
|
||
|
numClasses = newNumClasses;
|
||
|
allClasses = realloc(allClasses, sizeof(Class) * numClasses);
|
||
|
newNumClasses = objc_getClassList(allClasses, numClasses);
|
||
|
}
|
||
|
NSMutableArray *inspectors = [NSMutableArray array];
|
||
|
for (i = 0; i < numClasses; i++) {
|
||
|
Class currentClass = allClasses[i];
|
||
|
while (currentClass) {
|
||
|
if (currentClass->super_class == [NSFormatter class] &&
|
||
|
[currentClass conformsToProtocol:@protocol(MemoryInspector)]) {
|
||
|
[inspectors addObject:[[[allClasses[i] alloc] init] autorelease]];
|
||
|
break;
|
||
|
}
|
||
|
currentClass = currentClass->super_class;
|
||
|
}
|
||
|
}
|
||
|
free (allClasses);
|
||
|
[inspectors sortUsingSelector:@selector(compareOrderInMemoryInspectorMenu:)];
|
||
|
return inspectors;
|
||
|
}
|
||
|
|
||
|
|
||
|
- (NSRange) visibleRange
|
||
|
{
|
||
|
// see also [CPUMemoryViewController updateVisibleMemoryRange] and [NSTableView(Scrolling) scrollRowToTop:]
|
||
|
NSRange range;
|
||
|
range.location = range.length = 0;
|
||
|
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);
|
||
|
range.location = [memoryView rowsInRect:rect].location;
|
||
|
range.length = rect.size.height / pixelPerRow;
|
||
|
}
|
||
|
return range;
|
||
|
}
|
||
|
|
||
|
|
||
|
- (IBAction) selectMemoryInspector:(id)sender
|
||
|
{
|
||
|
NSSize drawerSize;
|
||
|
|
||
|
// get the new inspector
|
||
|
NSFormatter <MemoryInspector> *newInspector = [memoryInspectors objectAtIndex:[sender indexOfSelectedItem]];
|
||
|
if ([currentInspector isEqual:newInspector])
|
||
|
return;
|
||
|
|
||
|
// stop editing in the old inspector
|
||
|
[self cancelEditingInInspector];
|
||
|
|
||
|
NSTableColumn *addrColumn = [memoryView tableColumnWithIdentifier:ADDR_COLUMN_ID];
|
||
|
NSTableColumn *octalColumn = [memoryView tableColumnWithIdentifier:OCTAL_COLUMN_ID];
|
||
|
NSTableColumn *contentColumn = [memoryView tableColumnWithIdentifier:CONTENT_COLUMN_ID];
|
||
|
|
||
|
// set octal column width
|
||
|
drawerSize.width = [addrColumn width];
|
||
|
float width = [newInspector wordsPerRow] * 35;
|
||
|
drawerSize.width += width;
|
||
|
[octalColumn setMinWidth:width];
|
||
|
[octalColumn setMaxWidth:width];
|
||
|
[octalColumn setWidth:width];
|
||
|
|
||
|
// set content column width
|
||
|
width = [newInspector contentWidthInCharacters] * 7 + 4;
|
||
|
drawerSize.width += width;
|
||
|
[contentColumn setMinWidth:width];
|
||
|
[contentColumn setMaxWidth:width];
|
||
|
[contentColumn setWidth:width];
|
||
|
|
||
|
// resize drawer
|
||
|
drawerSize.width += 42;
|
||
|
drawerSize.height = 0;
|
||
|
[memoryInspectorDrawer setMaxContentSize:drawerSize];
|
||
|
[memoryInspectorDrawer setMinContentSize:drawerSize];
|
||
|
[memoryInspectorDrawer setContentSize:drawerSize];
|
||
|
|
||
|
// scroll to a reasonable location
|
||
|
int newTopRow = 0;
|
||
|
int newSelectedRow = -1;
|
||
|
if (currentInspector) {
|
||
|
NSRange visibleRange = [self visibleRange];
|
||
|
int selectedRow = [memoryView selectedRow];
|
||
|
if (NSLocationInRange(selectedRow, visibleRange)) {
|
||
|
newTopRow = selectedRow * [currentInspector wordsPerRow] /
|
||
|
[newInspector wordsPerRow] - (selectedRow - visibleRange.location);
|
||
|
newSelectedRow = newTopRow + (selectedRow - visibleRange.location);
|
||
|
} else
|
||
|
newTopRow = visibleRange.location * [currentInspector wordsPerRow] /
|
||
|
[newInspector wordsPerRow];
|
||
|
int lastAddress = (newTopRow + visibleRange.length) * [newInspector wordsPerRow];
|
||
|
if (lastAddress >= PDP8_MEMSIZE) // don't show white space at the end of the table view
|
||
|
newTopRow -= (lastAddress - PDP8_MEMSIZE) / [newInspector wordsPerRow] + 1;
|
||
|
}
|
||
|
|
||
|
// switch to the new inspector, set the formatter and reload data
|
||
|
alignment = 0;
|
||
|
currentInspector = newInspector;
|
||
|
[[octalColumn dataCell] setObjectValue:nil]; // remove old value with wrong number of words
|
||
|
[[[octalColumn dataCell] formatter] setNumberOfWords:[currentInspector wordsPerRow]];
|
||
|
[[contentColumn dataCell] setObjectValue:nil]; // remove old value with wrong number of words
|
||
|
[[contentColumn dataCell] setFormatter:currentInspector];
|
||
|
[memoryView reloadData];
|
||
|
|
||
|
// set selected row
|
||
|
[memoryView scrollRowToTop:newTopRow];
|
||
|
if (newSelectedRow >= 0)
|
||
|
[memoryView selectRowIndexes:[NSIndexSet indexSetWithIndex:newSelectedRow]
|
||
|
byExtendingSelection:NO];
|
||
|
else
|
||
|
[memoryView deselectAll:self];
|
||
|
|
||
|
// enable or disable corner view for memory alignment
|
||
|
if ([currentInspector needsMemoryAlignment]) {
|
||
|
[cornerView setImageNamed:@"alignMemoryArrow" toolTip:
|
||
|
NSLocalizedString(@"Click or option-click to align multiword formats", @"")];
|
||
|
[cornerView setClickable:YES];
|
||
|
} else {
|
||
|
[cornerView setImageNamed:nil toolTip:nil];
|
||
|
[cornerView setClickable:NO];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
- (IBAction) alignMemory:(id)sender
|
||
|
{
|
||
|
if ([currentInspector needsMemoryAlignment]) {
|
||
|
[self cancelEditingInInspector];
|
||
|
int wordsPerRow = [currentInspector wordsPerRow];
|
||
|
alignment = (alignment + wordsPerRow +
|
||
|
(([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) ? -1 : 1)) % wordsPerRow;
|
||
|
[memoryView reloadData];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) notifyMemoryChanged:(NSNotification *)notification
|
||
|
{
|
||
|
// NSLog (@"MemoryInspectorController notifyMemoryChanged");
|
||
|
[memoryView reloadData];
|
||
|
}
|
||
|
|
||
|
|
||
|
- (BOOL) tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)column row:(int)row
|
||
|
{
|
||
|
return [currentInspector wordsPerRow] * (row + 1) + alignment <= [pdp8 memorySize]
|
||
|
&& ([[column identifier] intValue] != CONTENT_COLUMN || [currentInspector allowsEditing]);
|
||
|
}
|
||
|
|
||
|
|
||
|
- (int) numberOfRowsInTableView:(NSTableView *)tableView
|
||
|
{
|
||
|
int wordsPerRow = [currentInspector wordsPerRow];
|
||
|
return wordsPerRow ? (PDP8_MEMSIZE + wordsPerRow - alignment - 1) / wordsPerRow : 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
- (id) tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(int)row
|
||
|
{
|
||
|
int i;
|
||
|
NSMutableArray *value;
|
||
|
|
||
|
int wordsPerRow = [currentInspector wordsPerRow];
|
||
|
switch ([[column identifier] intValue]) {
|
||
|
case ADDR_COLUMN :
|
||
|
return [NSString stringWithFormat:@"%5.5o", wordsPerRow * row + alignment];
|
||
|
case OCTAL_COLUMN :
|
||
|
value = [NSMutableArray arrayWithCapacity:wordsPerRow];
|
||
|
for (i = 0; i < wordsPerRow && wordsPerRow * row + alignment + i < PDP8_MEMSIZE; i++)
|
||
|
[value addObject:[NSNumber numberWithInt:
|
||
|
[pdp8 memoryAt:wordsPerRow * row + alignment + i]]];
|
||
|
return value;
|
||
|
case CONTENT_COLUMN :
|
||
|
if (wordsPerRow * (row + 1) /* + alignment */ > [pdp8 memorySize])
|
||
|
return NSLocalizedString(@"n/a", @"");
|
||
|
value = [NSMutableArray arrayWithCapacity:wordsPerRow];
|
||
|
for (i = 0; i < wordsPerRow; i++)
|
||
|
[value addObject:[NSNumber numberWithInt:
|
||
|
(wordsPerRow * row + alignment + i < PDP8_MEMSIZE) ?
|
||
|
[pdp8 memoryAt:wordsPerRow * row + alignment + i] : 0]];
|
||
|
return value;
|
||
|
}
|
||
|
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 ADDR_COLUMN :
|
||
|
return NSLocalizedString(@"This column displays the memory adresses.", @"");
|
||
|
case OCTAL_COLUMN :
|
||
|
return NSLocalizedString(@"This column displays the octal memory content.", @"");
|
||
|
case CONTENT_COLUMN :
|
||
|
return [currentInspector toolTipForContentColumn];
|
||
|
}
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) tableView:(NSTableView *)tableView setObjectValue:(NSArray *)values
|
||
|
forTableColumn:(NSTableColumn *)column row:(int)row
|
||
|
{
|
||
|
/* value == nil when the user tabs (without editing) over cells with
|
||
|
output strings that are not valid input strings, e. g. "(IEEE overflow)".
|
||
|
In this case, the formatter is called with error == nil, returns the
|
||
|
value nil for the invalid input string, but Cocoa does not call
|
||
|
control:didFailToFormatString:errorDescription: */
|
||
|
if (values)
|
||
|
[pdp8 setMemoryAtAddress:row * [currentInspector wordsPerRow] + alignment
|
||
|
toValues:values withMask:[[column identifier] intValue] == CONTENT_COLUMN];
|
||
|
}
|
||
|
|
||
|
|
||
|
- (BOOL) control:(NSControl *)control didFailToFormatString:(NSString *)string
|
||
|
errorDescription:(NSString *)error
|
||
|
{
|
||
|
NSRange range;
|
||
|
range.location = 0;
|
||
|
range.length = -1;
|
||
|
[[control currentEditor] setSelectedRange:range];
|
||
|
NSAlert *alert = [[NSAlert alloc] init];
|
||
|
[alert setMessageText:error];
|
||
|
[alert beginSheetModalForWindow:[memoryInspectorDrawer parentWindow]
|
||
|
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 cell
|
||
|
[control abortEditing];
|
||
|
return YES;
|
||
|
}
|
||
|
return NO;
|
||
|
}
|
||
|
|
||
|
|
||
|
#pragma mark Delegate and Notification
|
||
|
|
||
|
|
||
|
- (void) drawerWillClose:(NSNotification *)notification
|
||
|
{
|
||
|
[self cancelEditingInInspector];
|
||
|
lastTopRow = [self visibleRange].location;
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) scrollDrawerToLastTopRow
|
||
|
{
|
||
|
[memoryView scrollRowToTop:lastTopRow];
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) drawerDidOpen:(NSNotification *)notification
|
||
|
{
|
||
|
// When the PDP-8 runs, the CPU window shrinks to the title bar, so the memory view scrolls to row 0
|
||
|
// We can't scroll in drawerWillOpen:; even when this method runs, often the CPU window is still shrunk,
|
||
|
// so a direct [memoryView scrollRowToTop:lastTopRow] has no effect, and the memory view stays at location 0.
|
||
|
// So we delay the scrolling via performSelector:, accepting the side effect that the scrolling is visible.
|
||
|
[self performSelector:@selector(scrollDrawerToLastTopRow) withObject:nil afterDelay:0];
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) notifyApplicationWillTerminate:(NSNotification *)notification
|
||
|
{
|
||
|
// NSLog (@"MemoryInspectorController notifyApplicationWillTerminate");
|
||
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||
|
[defaults setObject:[currentInspector className] forKey:CURRENT_INSPECTOR_CLASS_PREFS_KEY];
|
||
|
[defaults setObject:[NSNumber numberWithInt:[self visibleRange].location] forKey:TOP_ROW_PREFS_KEY];
|
||
|
[defaults setObject:[NSNumber numberWithInt:alignment] forKey:ALIGNMENT_PREFS_KEY];
|
||
|
int s = [memoryInspectorDrawer state];
|
||
|
[defaults setBool:s == NSDrawerOpenState || s == NSDrawerOpeningState
|
||
|
forKey:INSPECTOR_OPEN_PREFS_KEY];
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) notifyApplicationDidFinishLaunching:(NSNotification *)notification
|
||
|
/* Look for memory inspector classes at "did finish launching", after plugins have been loaded at
|
||
|
"will finish launching", so we find inspector classes of plugins, too. */
|
||
|
{
|
||
|
unsigned i;
|
||
|
|
||
|
memoryInspectors = [[self allMemoryInspectors] retain];
|
||
|
[popupButton removeAllItems];
|
||
|
for (i = 0; i < [memoryInspectors count]; i++)
|
||
|
[popupButton addItemWithTitle:[[memoryInspectors objectAtIndex:i] menuTitle]];
|
||
|
NSFont *font = [NSFont userFixedPitchFontOfSize:11];
|
||
|
[[[[memoryView tableColumns] objectAtIndex:ADDR_COLUMN] dataCell] setFont:font];
|
||
|
[[[[memoryView tableColumns] objectAtIndex:OCTAL_COLUMN] dataCell] setFont:font];
|
||
|
[[[[memoryView tableColumns] objectAtIndex:CONTENT_COLUMN] dataCell] setFont:font];
|
||
|
[[[[memoryView tableColumns] objectAtIndex:OCTAL_COLUMN] dataCell]
|
||
|
setFormatter:[OctalFormatter formatterWithBitMask:07777 wildcardAllowed:NO]];
|
||
|
// restore preferences
|
||
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||
|
NSString *currentInspectorClass = [defaults stringForKey:CURRENT_INSPECTOR_CLASS_PREFS_KEY];
|
||
|
alignment = 0;
|
||
|
lastTopRow = 0;
|
||
|
for (i = 0; i < [memoryInspectors count]; i++) {
|
||
|
if ([[[memoryInspectors objectAtIndex:i] className] isEqualToString:currentInspectorClass]) {
|
||
|
[popupButton selectItemAtIndex:i];
|
||
|
alignment = [defaults integerForKey:ALIGNMENT_PREFS_KEY];
|
||
|
lastTopRow = [defaults integerForKey:TOP_ROW_PREFS_KEY];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
[self selectMemoryInspector:popupButton];
|
||
|
[memoryView scrollRowToTop:lastTopRow];
|
||
|
if ([[NSUserDefaults standardUserDefaults] boolForKey:INSPECTOR_OPEN_PREFS_KEY]
|
||
|
&& [[memoryInspectorDrawer parentWindow] isVisible])
|
||
|
[memoryInspectorDrawer open];
|
||
|
}
|
||
|
|
||
|
|
||
|
- (void) awakeFromNib
|
||
|
{
|
||
|
adjustTableHeaderForElCapitan (memoryView);
|
||
|
// set notifications
|
||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||
|
selector:@selector(notifyApplicationDidFinishLaunching:)
|
||
|
name:NSApplicationDidFinishLaunchingNotification object:nil];
|
||
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyMemoryChanged:)
|
||
|
name:MEMORY_CHANGED_NOTIFICATION object:nil];
|
||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||
|
selector:@selector(notifyApplicationWillTerminate:)
|
||
|
name:NSApplicationWillTerminateNotification object:nil];
|
||
|
}
|
||
|
|
||
|
|
||
|
@end
|