Skip to content

Commit 20988c4

Browse files
authored
Merge pull request #32 from morozov/modals
Replace modal windows with floating child windows
2 parents fab95f1 + 04a7782 commit 20988c4

17 files changed

Lines changed: 394 additions & 139 deletions

fusepb/controllers/DebuggerController.m

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ - (void)mainThreadDebuggerDisassemble:(NSNumber *)address;
5858
/* Is the debugger window active (as opposed to the debugger itself)? */
5959
static int debugger_active;
6060

61+
/* Semaphore that blocks the emulation thread inside ui_debugger_activate()
62+
until the user dismisses the debugger (Continue / close). */
63+
static dispatch_semaphore_t debugger_semaphore;
64+
6165
@implementation DebuggerController
6266

6367
static DebuggerController *singleton = nil;
@@ -102,6 +106,7 @@ - (id)init
102106
[super init];
103107
self = [super initWithWindowNibName:@"Debugger"];
104108
singleton = self;
109+
debugger_semaphore = dispatch_semaphore_create( 0 );
105110

106111
[self performSelectorOnMainThread:@selector(habdleCloseNotification) withObject:nil waitUntilDone:YES];
107112
[self performSelectorOnMainThread:@selector(setWindowFrameAutosaveName:) withObject:@"DebuggerWindow" waitUntilDone:YES];
@@ -142,7 +147,13 @@ - (void)handleWillClose:(NSNotification *)note
142147
[memoryMapContents release];
143148
memoryMapContents = nil;
144149

145-
debugger_run();
150+
[[self window] unpinFromParent];
151+
152+
/* Only resume execution when the user dismissed the window directly.
153+
If we arrived via deactivate_debugger(), debugger_active is already
154+
0 and the caller has set debugger_mode; calling debugger_run() now
155+
would overwrite a HALTED set by debugger_step(). */
156+
if( debugger_active ) debugger_run();
146157
}
147158

148159
- (void)eventsDoubleAction:(id)sender
@@ -191,20 +202,36 @@ - (void)debugger_activate:(id)sender
191202
{
192203
[[DisplayOpenGLView instance] pause];
193204

205+
/* Breakpoints fire unprompted. Bring FuseX (and the main window) back
206+
into view if the user has Cmd-Tabbed away, hidden the app, or
207+
minimized the main window — otherwise the Debugger window, as a
208+
child of main, would not be visible. After this point the panel
209+
behaves like any other child window: it sinks with FuseX when the
210+
user switches to another app, and rises with FuseX on return. */
211+
[NSApp activateIgnoringOtherApps:YES];
212+
NSWindow *mainWindow = [[DisplayOpenGLView instance] window];
213+
if( [mainWindow isMiniaturized] ) {
214+
[mainWindow deminiaturize:nil];
215+
}
216+
217+
BOOL wasVisible = [[singleton window] isVisible];
194218
[singleton showWindow:nil];
219+
if( !wasVisible ) {
220+
[[singleton window] makeFirstResponder:entry];
221+
}
222+
223+
[[singleton window] pinAsChildOf:mainWindow];
195224

196225
[continueButton setEnabled:YES];
197226
[breakButton setEnabled:NO];
198-
if( !debugger_active ) activate_debugger();
199-
200-
[NSApp runModalForWindow:[self window]];
227+
activate_debugger();
201228
}
202229

203230
- (void)debugger_deactivate:(int)interruptable
204231
{
205232
if( debugger_active ) deactivate_debugger();
206233

207-
[continueButton setEnabled:!interruptable ? YES : NO];
234+
[continueButton setEnabled:interruptable ? NO : YES];
208235
[breakButton setEnabled:interruptable ? YES : NO];
209236
}
210237

@@ -651,7 +678,11 @@ - (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(int)rowIndex
651678
int
652679
ui_debugger_activate( void )
653680
{
654-
[[Emulator instance] debuggerActivate];
681+
[[Emulator instance] performSelectorOnMainThread:@selector(debuggerActivate)
682+
withObject:nil
683+
waitUntilDone:YES];
684+
685+
dispatch_semaphore_wait( debugger_semaphore, DISPATCH_TIME_FOREVER );
655686

656687
return 0;
657688
}
@@ -721,10 +752,10 @@ - (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(int)rowIndex
721752

722753
static int
723754
deactivate_debugger( void )
724-
{
725-
[NSApp stopModal];
755+
{
726756
debugger_active = 0;
727757
[[DisplayOpenGLView instance] unpause];
758+
dispatch_semaphore_signal( debugger_semaphore );
728759
return 0;
729760
}
730761

fusepb/controllers/FuseController.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,6 @@
164164
- savePanelAccessoryView;
165165
@property (getter=saveFileType,readonly) NSPopUpButton* saveFileType;
166166

167-
- (void)releaseCmdKeys:(NSString *)character withCode:(int)keyCode;
168-
- (void)releaseKey:(int)keyCode;
169-
170167
- (void)ui_menu_activate_media_cartridge:(NSNumber*)active;
171168
- (void)ui_menu_activate_media_cartridge_dock:(NSNumber*)active;
172169
- (void)ui_menu_activate_media_cartridge_dock_eject:(NSNumber*)active;

fusepb/controllers/FuseController.m

Lines changed: 84 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,56 @@ - (id)init
320320

321321
recentSnapFileNames = [NSMutableArray arrayWithCapacity:NUM_RECENT_ITEMS];
322322
[recentSnapFileNames retain];
323+
324+
/* Resync the emulator's modifier ivars whenever the main window
325+
regains key status. AppKit delivers NSFlagsChanged to the key
326+
window's first responder, so a modifier release while a utility
327+
window was key never reaches DisplayOpenGLView and
328+
Emulator.commandDown stays stuck — dropping every subsequent
329+
keystroke in Emulator.keyDown:. The user-visible form is the
330+
Cmd+, → Preferences flow: open Settings, close, then the
331+
Spectrum keyboard ignores everything until the next modifier
332+
press triggers a real NSFlagsChanged on the main window. The
333+
filter inside the handler skips notifications for other
334+
windows. */
335+
[[NSNotificationCenter defaultCenter]
336+
addObserver:self
337+
selector:@selector(mainWindowDidBecomeKey:)
338+
name:NSWindowDidBecomeKeyNotification
339+
object:nil];
323340
}
324341

325342
return singleton;
326343
}
327344

345+
- (void)mainWindowDidBecomeKey:(NSNotification *)note
346+
{
347+
/* The observer is registered with object:nil because the main window
348+
does not yet exist when the FuseController singleton is constructed
349+
during MainMenu.xib load. Filter to the main emulator window inside
350+
the handler — and bail out early if the main window does not yet
351+
exist, otherwise a non-nil note object would falsely pass the !=
352+
comparison against nil during startup. */
353+
NSWindow *mainWindow = [[DisplayOpenGLView instance] window];
354+
if( !mainWindow ) return;
355+
if( [note object] != mainWindow ) return;
356+
357+
Emulator *emu = [Emulator instance];
358+
if( !emu ) return;
359+
360+
NSEvent *sync = [NSEvent keyEventWithType:NSFlagsChanged
361+
location:NSZeroPoint
362+
modifierFlags:[NSEvent modifierFlags]
363+
timestamp:0
364+
windowNumber:0
365+
context:nil
366+
characters:@""
367+
charactersIgnoringModifiers:@""
368+
isARepeat:NO
369+
keyCode:0];
370+
[emu flagsChanged:sync];
371+
}
372+
328373
- (IBAction)disk_eject_a:(id)sender
329374
{
330375
cocoaui_disk_eject( 0 );
@@ -652,7 +697,6 @@ - (IBAction)open:(id)sender
652697

653698
[[DisplayOpenGLView instance] unpause];
654699
}
655-
[self releaseCmdKeys:@"o" withCode:QZ_o];
656700
}
657701

658702
- (IBAction)reset:(id)sender
@@ -697,13 +741,11 @@ - (IBAction)rzx_play:(id)sender
697741
- (IBAction)rzx_insert_snap:(id)sender
698742
{
699743
[[DisplayOpenGLView instance] rzxInsertSnap];
700-
[self releaseCmdKeys:@"b" withCode:QZ_b];
701744
}
702745

703746
- (IBAction)rzx_rollback:(id)sender
704747
{
705748
[[DisplayOpenGLView instance] rzxRollback];
706-
[self releaseCmdKeys:@"z" withCode:QZ_z];
707749
}
708750

709751
- (IBAction)rzx_start:(id)sender
@@ -863,7 +905,6 @@ - (IBAction)save_as:(id)sender
863905
save_as_exit:
864906
[[DisplayOpenGLView instance] unpause];
865907
}
866-
[self releaseCmdKeys:@"s" withCode:QZ_s];
867908
}
868909

869910
- (IBAction)open_screen:(id)sender
@@ -991,13 +1032,11 @@ - (IBAction)hard_reset:(id)sender
9911032
- (IBAction)joystick_keyboard:(id)sender
9921033
{
9931034
[[DisplayOpenGLView instance] joystickToggleKeyboard];
994-
[self releaseCmdKeys:@"j" withCode:QZ_j];
9951035
}
9961036

9971037
- (IBAction)keyboard_recreated:(id)sender
9981038
{
9991039
[[DisplayOpenGLView instance] keyboardToggleRecreatedZXSpectrum];
1000-
[self releaseCmdKeys:@"r" withCode:QZ_r];
10011040
}
10021041

10031042
- (IBAction)keyboard_arrows_shifted:(id)sender
@@ -1032,7 +1071,6 @@ - (IBAction)tape_open:(id)sender
10321071
- (IBAction)tape_play:(id)sender
10331072
{
10341073
[[DisplayOpenGLView instance] tapeTogglePlay];
1035-
[self releaseCmdKeys:@"p" withCode:QZ_p];
10361074
}
10371075

10381076
- (IBAction)tape_rewind:(id)sender
@@ -1124,35 +1162,49 @@ - (IBAction)quit:(id)sender
11241162
if( !settings_current.full_screen ) {
11251163
[[NSApp keyWindow] performClose:self];
11261164
}
1127-
[self releaseCmdKeys:@"q" withCode:QZ_q];
11281165
}
11291166

11301167
- (IBAction)hide:(id)sender
11311168
{
11321169
[NSApp hide:self];
1133-
[self releaseCmdKeys:@"h" withCode:QZ_h];
11341170
}
11351171

11361172
- (IBAction)help:(id)sender
11371173
{
11381174
if( !settings_current.full_screen ) {
11391175
[NSApp showHelp:self];
11401176
}
1141-
[self releaseCmdKeys:@"?" withCode:QZ_SLASH];
11421177
}
11431178

11441179
- (IBAction)cocoa_break:(id)sender
11451180
{
11461181
if ( gdbserver_debugging_enabled ) {
11471182
return;
11481183
}
1184+
1185+
/* Re-entry: if the Debugger is already showing, bring it forward and
1186+
leave state alone. Calling debugger_activate: a second time would
1187+
double-pause and unbalance the counter. */
1188+
DebuggerController *dbg = [DebuggerController loadedSingleton];
1189+
if ( dbg && [[dbg window] isVisible] ) {
1190+
[[dbg window] makeKeyAndOrderFront:nil];
1191+
return;
1192+
}
1193+
11491194
if ( paused ) {
1150-
debugger_mode = DEBUGGER_MODE_HALTED;
1195+
/* User-pause: release the user-pause contribution so the timer can
1196+
run, then set HALTED. The emulator detects HALTED on the next
1197+
instruction and ui_debugger_activate parks the thread on the
1198+
semaphore via the proper path. The user-pause is not restored on
1199+
dismissal — the user explicitly opened the Debugger from a paused
1200+
state, and Continue means "resume." */
11511201
paused = 0;
1152-
[[DebuggerController singleton] debugger_activate:nil];
1153-
} else {
1154-
[[DisplayOpenGLView instance] cocoaBreak];
1202+
[[DisplayOpenGLView instance] unpause];
11551203
}
1204+
/* The utility-window case (fuse_emulation_paused > 0 && !paused) is
1205+
unreachable: validateMenuItem: disables the Debugger menu item in
1206+
that state. */
1207+
[[DisplayOpenGLView instance] cocoaBreak];
11561208
[self setPauseState];
11571209
}
11581210

@@ -1186,7 +1238,6 @@ - (IBAction)showKeyboardPane:(id)sender
11861238
[keyboardController showCloseWindow:self];
11871239
}
11881240
}
1189-
[self releaseCmdKeys:@"k" withCode:QZ_k];
11901241
}
11911242

11921243
- (IBAction)showLoadBinaryPane:(id)sender
@@ -1248,7 +1299,6 @@ - (IBAction)showPreferencesPane:(id)sender;
12481299
}
12491300
[preferencesController showWindow:self];
12501301
}
1251-
[self releaseCmdKeys:@"." withCode:QZ_PERIOD];
12521302
}
12531303

12541304
- (IBAction)saveFileTypeClicked:(id)sender;
@@ -1280,6 +1330,8 @@ - (IBAction)resetUserDefaults:(id)sender
12801330

12811331
- (void)dealloc
12821332
{
1333+
[[NSNotificationCenter defaultCenter] removeObserver:self];
1334+
12831335
[tapeBrowserController release];
12841336
[keyboardController release];
12851337
[saveBinaryController release];
@@ -1291,55 +1343,6 @@ - (void)dealloc
12911343
[super dealloc];
12921344
}
12931345

1294-
/*------------------------------------------------------------------------------
1295-
* releaseCmdKeys - This method fixes an issue when modal windows are used with
1296-
* the Mac OSX version of the SDL library.
1297-
* As the SDL normally captures all keystrokes, but we need to type in some
1298-
* Mac windows, all of the control menu windows run in modal mode. However,
1299-
* when this happens, the release of the command key and the shortcut key
1300-
* are not sent to SDL. We have to manually cause these events to happen
1301-
* to keep the SDL library in a sane state, otherwise only every other
1302-
* shortcut keypress will work.
1303-
*-----------------------------------------------------------------------------*/
1304-
- (void) releaseCmdKeys:(NSString *)character withCode:(int)keyCode
1305-
{
1306-
NSEvent *event1, *event2;
1307-
NSPoint point = { 0, 0 };
1308-
1309-
event1 = [NSEvent keyEventWithType:NSKeyUp location:point modifierFlags:0
1310-
timestamp:0 windowNumber:0 context:nil characters:character
1311-
charactersIgnoringModifiers:character isARepeat:NO
1312-
keyCode:keyCode];
1313-
[NSApp postEvent:event1 atStart:NO];
1314-
1315-
event2 = [NSEvent keyEventWithType:NSFlagsChanged location:point
1316-
modifierFlags:0 timestamp:0 windowNumber:0 context:nil
1317-
characters:nil charactersIgnoringModifiers:nil isARepeat:NO
1318-
keyCode:0];
1319-
[NSApp postEvent:event2 atStart:NO];
1320-
}
1321-
1322-
/*------------------------------------------------------------------------------
1323-
* releaseKey - This method fixes an issue when modal windows are used with
1324-
* the Mac OSX version of the SDL library.
1325-
* As the SDL normally captures all keystrokes, but we need to type in some
1326-
* Mac windows, all of the control menu windows run in modal mode. However,
1327-
* when this happens, the release of function key which started the process
1328-
* is not sent to SDL. We have to manually cause these events to happen
1329-
* to keep the SDL library in a sane state, otherwise only everyother shortcut
1330-
* keypress will work.
1331-
*-----------------------------------------------------------------------------*/
1332-
- (void) releaseKey:(int)keyCode
1333-
{
1334-
NSEvent *event1;
1335-
NSPoint point = { 0, 0 };
1336-
1337-
event1 = [NSEvent keyEventWithType:NSKeyUp location:point modifierFlags:0
1338-
timestamp:0 windowNumber:0 context:nil characters:@" "
1339-
charactersIgnoringModifiers:@" " isARepeat:NO keyCode:keyCode];
1340-
[NSApp postEvent:event1 atStart:NO];
1341-
}
1342-
13431346
- (void)ui_menu_activate_media_cartridge:(NSNumber*)active
13441347
{
13451348
[cartridge setEnabled:[active boolValue]];
@@ -2112,7 +2115,22 @@ - (BOOL)validateMenuItem:(NSMenuItem*)menuItem
21122115
return multiface == 0 ? NO : YES;
21132116
break;
21142117
case 176:
2115-
return debuggerEnabled == 0 ? NO : YES;
2118+
if( !debuggerEnabled ) return NO;
2119+
/* Disable Machine→Debugger when a utility window is holding the
2120+
emulator paused and the Debugger itself is not already up. The
2121+
menu cannot do anything useful in that state: cocoa_break: sets
2122+
HALTED, but z80 is not executing because the utility window's
2123+
pause stopped the timer, so no trap fires and the Debugger
2124+
window never appears until the user closes the utility. The
2125+
comparison must subtract the user-pause contribution so the
2126+
guard fires regardless of whether Machine→Pause is also set —
2127+
the utility-window holds are the part that breaks the trap
2128+
path. */
2129+
if( fuse_emulation_paused > (paused ? 1 : 0) ) {
2130+
DebuggerController *dbg = [DebuggerController loadedSingleton];
2131+
if( !dbg || ![[dbg window] isVisible] ) return NO;
2132+
}
2133+
return YES;
21162134
break;
21172135
default:
21182136
return YES;

0 commit comments

Comments
 (0)