Better GUI for user errors/warnings in Cocoa

This commit is contained in:
Lior Halphon 2017-02-24 18:15:31 +02:00
parent 724153e5ef
commit c116c70bfa
5 changed files with 140 additions and 24 deletions

View File

@ -10,6 +10,7 @@
#include "display.h" #include "display.h"
#include "HexFiend/HexFiend.h" #include "HexFiend/HexFiend.h"
#include "GBMemoryByteArray.h" #include "GBMemoryByteArray.h"
#include "GBWarningPopover.h"
/* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ /* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */
/* Todo: Split into category files! This is so messy!!! */ /* Todo: Split into category files! This is so messy!!! */
@ -40,6 +41,10 @@
NSMutableData *currentPrinterImageData; NSMutableData *currentPrinterImageData;
enum {GBAccessoryNone, GBAccessoryPrinter} accessory; enum {GBAccessoryNone, GBAccessoryPrinter} accessory;
bool rom_warning_issued;
NSMutableString *capturedOutput;
} }
@property GBAudioClient *audioClient; @property GBAudioClient *audioClient;
@ -146,7 +151,13 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
GB_set_rgb_encode_callback(&gb, rgbEncode); GB_set_rgb_encode_callback(&gb, rgbEncode);
GB_set_camera_get_pixel_callback(&gb, cameraGetPixel); GB_set_camera_get_pixel_callback(&gb, cameraGetPixel);
GB_set_camera_update_request_callback(&gb, cameraRequestUpdate); GB_set_camera_update_request_callback(&gb, cameraRequestUpdate);
[self loadROM]; NSString *rom_warnings = [self captureOutputForBlock:^{
[self loadROM];
}];
if (rom_warnings && !rom_warning_issued) {
rom_warning_issued = true;
[GBWarningPopover popoverWithContents:rom_warnings onWindow:self.mainWindow];
}
} }
- (void) vblank - (void) vblank
@ -483,6 +494,12 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
- (void) log: (const char *) string withAttributes: (GB_log_attributes) attributes - (void) log: (const char *) string withAttributes: (GB_log_attributes) attributes
{ {
NSString *nsstring = @(string); // For ref-counting
if (capturedOutput) {
[capturedOutput appendString:nsstring];
return;
}
if (pendingLogLines > 128) { if (pendingLogLines > 128) {
/* The ROM causes so many errors in such a short time, and we can't handle it. */ /* The ROM causes so many errors in such a short time, and we can't handle it. */
tooMuchLogs = true; tooMuchLogs = true;
@ -493,7 +510,6 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
/* Make sure mouse is not hidden while debugging */ /* Make sure mouse is not hidden while debugging */
self.view.mouseHidingEnabled = NO; self.view.mouseHidingEnabled = NO;
NSString *nsstring = @(string); // For ref-counting
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[hex_controller reloadData]; [hex_controller reloadData];
[self reloadVRAMData: nil]; [self reloadVRAMData: nil];
@ -585,25 +601,30 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
- (IBAction)saveState:(id)sender - (IBAction)saveState:(id)sender
{ {
bool was_running = running; bool __block success = false;
if (!gb.debug_stopped) { [self performAtomicBlock:^{
[self stop]; success = GB_save_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]) == 0;
} }];
GB_save_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]);
if (was_running) { if (!success) {
[self start]; [GBWarningPopover popoverWithContents:@"Failed to write save state." onWindow:self.mainWindow];
NSBeep();
} }
} }
- (IBAction)loadState:(id)sender - (IBAction)loadState:(id)sender
{ {
bool was_running = running; bool __block success = false;
if (!gb.debug_stopped) { NSString *error =
[self stop]; [self captureOutputForBlock:^{
} success = GB_load_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]) == 0;
GB_load_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]); }];
if (was_running) {
[self start]; if (!success) {
if (error) {
[GBWarningPopover popoverWithContents:error onWindow:self.mainWindow];
}
NSBeep();
} }
} }
@ -642,6 +663,15 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
} }
} }
- (NSString *) captureOutputForBlock: (void (^)())block
{
capturedOutput = [[NSMutableString alloc] init];
[self performAtomicBlock:block];
NSString *ret = [capturedOutput stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
capturedOutput = nil;
return [ret length]? ret : nil;
}
+ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale + (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale
{ {
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef) data); CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef) data);
@ -754,29 +784,31 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
- (IBAction)hexGoTo:(id)sender - (IBAction)hexGoTo:(id)sender
{ {
[self performAtomicBlock:^{ NSString *error = [self captureOutputForBlock:^{
uint16_t addr; uint16_t addr;
if (GB_debugger_evaluate(&gb, [[sender stringValue] UTF8String], &addr, NULL)) { if (GB_debugger_evaluate(&gb, [[sender stringValue] UTF8String], &addr, NULL)) {
NSBeep();
return; return;
} }
addr -= lineRep.valueOffset; addr -= lineRep.valueOffset;
if (addr >= hex_controller.byteArray.length) { if (addr >= hex_controller.byteArray.length) {
NSBeep(); GB_log(&gb, "Value $%04x is out of range.\n", addr);
return; return;
} }
[hex_controller setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(addr, 0)]]]; [hex_controller setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(addr, 0)]]];
[hex_controller _ensureVisibilityOfLocation:addr]; [hex_controller _ensureVisibilityOfLocation:addr];
[self.memoryWindow makeFirstResponder:self.memoryView.subviews[0].subviews[0]]; [self.memoryWindow makeFirstResponder:self.memoryView.subviews[0].subviews[0]];
}]; }];
if (error) {
NSBeep();
[GBWarningPopover popoverWithContents:error onView:sender];
}
} }
- (IBAction)hexUpdateBank:(NSControl *)sender - (IBAction)hexUpdateBank:(NSControl *)sender
{ {
[self performAtomicBlock:^{ NSString *error = [self captureOutputForBlock:^{
uint16_t addr, bank; uint16_t addr, bank;
if (GB_debugger_evaluate(&gb, [[sender stringValue] UTF8String], &addr, &bank)) { if (GB_debugger_evaluate(&gb, [[sender stringValue] UTF8String], &addr, &bank)) {
NSBeep();
return; return;
} }
@ -808,6 +840,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
[(GBMemoryByteArray *)(hex_controller.byteArray) setSelectedBank:bank]; [(GBMemoryByteArray *)(hex_controller.byteArray) setSelectedBank:bank];
[hex_controller reloadData]; [hex_controller reloadData];
}]; }];
if (error) {
NSBeep();
[GBWarningPopover popoverWithContents:error onView:sender];
}
} }
- (IBAction)hexUpdateSpace:(NSPopUpButtonCell *)sender - (IBAction)hexUpdateSpace:(NSPopUpButtonCell *)sender

8
Cocoa/GBWarningPopover.h Normal file
View File

@ -0,0 +1,8 @@
#import <Cocoa/Cocoa.h>
@interface GBWarningPopover : NSPopover
+ (GBWarningPopover *) popoverWithContents:(NSString *)contents onView:(NSView *)view;
+ (GBWarningPopover *) popoverWithContents:(NSString *)contents onWindow:(NSWindow *)window;
@end

46
Cocoa/GBWarningPopover.m Normal file
View File

@ -0,0 +1,46 @@
#import "GBWarningPopover.h"
static GBWarningPopover *lastPopover;
@implementation GBWarningPopover
+ (GBWarningPopover *) popoverWithContents:(NSString *)contents onView:(NSView *)view
{
[lastPopover close];
lastPopover = [[self alloc] init];
[lastPopover setBehavior:NSPopoverBehaviorApplicationDefined];
[lastPopover setAnimates:YES];
lastPopover.contentViewController = [[NSViewController alloc] initWithNibName:@"PopoverView" bundle:nil];
NSTextField *field = (NSTextField *)lastPopover.contentViewController.view;
[field setStringValue:contents];
NSSize textSize = [field.cell cellSizeForBounds:[field.cell drawingRectForBounds:NSMakeRect(0, 0, 240, CGFLOAT_MAX)]];
textSize.width = ceil(textSize.width) + 16;
textSize.height = ceil(textSize.height) + 12;
[lastPopover setContentSize:textSize];
if (!view.window.isVisible) {
[view.window setIsVisible:YES];
}
[lastPopover showRelativeToRect:view.bounds
ofView:view
preferredEdge:NSMinYEdge];
NSRect frame = field.frame;
frame.origin.x += 8;
frame.origin.y -= 6;
field.frame = frame;
[lastPopover performSelector:@selector(close) withObject:nil afterDelay:3.0];
return lastPopover;
}
+ (GBWarningPopover *)popoverWithContents:(NSString *)contents onWindow:(NSWindow *)window
{
return [self popoverWithContents:contents onView:window.contentView.superview.subviews.lastObject];
}
@end

27
Cocoa/PopoverView.xib Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSViewController">
<connections>
<outlet property="view" destination="oUc-bq-d5t" id="FQR-Ty-0Ar"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" id="oUc-bq-d5t">
<rect key="frame" x="0.0" y="0.0" width="66" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<textFieldCell key="cell" controlSize="mini" sendsActionOnEndEditing="YES" alignment="left" title="Test" id="xyx-iy-kse">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<point key="canvasLocation" x="-93" y="211.5"/>
</textField>
</objects>
</document>

View File

@ -153,9 +153,7 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \
Misc/registers.sym \ Misc/registers.sym \
$(BIN)/SameBoy.app/Contents/Resources/dmg_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/dmg_boot.bin \
$(BIN)/SameBoy.app/Contents/Resources/cgb_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/cgb_boot.bin \
$(BIN)/SameBoy.app/Contents/Resources/Base.lproj/Document.nib \ $(patsubst %.xib,%.nib,$(addprefix $(BIN)/SameBoy.app/Contents/Resources/Base.lproj/,$(shell cd Cocoa;ls *.xib))) \
$(BIN)/SameBoy.app/Contents/Resources/Base.lproj/MainMenu.nib \
$(BIN)/SameBoy.app/Contents/Resources/Base.lproj/Preferences.nib \
$(BIN)/SameBoy.qlgenerator \ $(BIN)/SameBoy.qlgenerator \
Shaders Shaders
$(MKDIR) -p $(BIN)/SameBoy.app/Contents/Resources $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Resources