1// license:BSD-3-Clause
2// copyright-holders:Vas Crabb
3//============================================================
4//
5//  debugwindowhandler.m - MacOS X Cocoa debug window handling
6//
7//============================================================
8
9#include "emu.h"
10#import "debugwindowhandler.h"
11
12#import "debugconsole.h"
13#import "debugcommandhistory.h"
14#import "debugview.h"
15
16#include "debugger.h"
17#include "debug/debugcon.h"
18
19#include "util/xmlfile.h"
20
21
22//============================================================
23//  NOTIFICATIONS
24//============================================================
25
26NSString *const MAMEHideDebuggerNotification = @"MAMEHideDebuggerNotification";
27NSString *const MAMEShowDebuggerNotification = @"MAMEShowDebuggerNotification";
28NSString *const MAMEAuxiliaryDebugWindowWillCloseNotification = @"MAMEAuxiliaryDebugWindowWillCloseNotification";
29NSString *const MAMESaveDebuggerConfigurationNotification = @"MAMESaveDebuggerConfigurationNotification";
30
31
32//============================================================
33//  MAMEDebugWindowHandler class
34//============================================================
35
36@implementation MAMEDebugWindowHandler
37
38+ (void)addCommonActionItems:(NSMenu *)menu {
39	[menu addItemWithTitle:@"Break"
40					action:@selector(debugBreak:)
41			 keyEquivalent:@""];
42
43	NSMenuItem *runParentItem = [menu addItemWithTitle:@"Run"
44												action:@selector(debugRun:)
45										 keyEquivalent:[NSString stringWithFormat:@"%C", (short)NSF5FunctionKey]];
46	NSMenu *runMenu = [[NSMenu alloc] initWithTitle:@"Run"];
47	[runParentItem setSubmenu:runMenu];
48	[runMenu release];
49	[runParentItem setKeyEquivalentModifierMask:0];
50	[[runMenu addItemWithTitle:@"and Hide Debugger"
51						action:@selector(debugRunAndHide:)
52				 keyEquivalent:[NSString stringWithFormat:@"%C", (short)NSF12FunctionKey]]
53	 setKeyEquivalentModifierMask:0];
54	[[runMenu addItemWithTitle:@"to Next CPU"
55						action:@selector(debugRunToNextCPU:)
56				 keyEquivalent:[NSString stringWithFormat:@"%C", (short)NSF6FunctionKey]]
57	 setKeyEquivalentModifierMask:0];
58	[[runMenu addItemWithTitle:@"until Next Interrupt on Current CPU"
59						action:@selector(debugRunToNextInterrupt:)
60				 keyEquivalent:[NSString stringWithFormat:@"%C", (short)NSF7FunctionKey]]
61	 setKeyEquivalentModifierMask:0];
62	[[runMenu addItemWithTitle:@"until Next VBLANK"
63						action:@selector(debugRunToNextVBLANK:)
64				 keyEquivalent:[NSString stringWithFormat:@"%C", (short)NSF8FunctionKey]]
65	 setKeyEquivalentModifierMask:0];
66
67	NSMenuItem *stepParentItem = [menu addItemWithTitle:@"Step" action:NULL keyEquivalent:@""];
68	NSMenu *stepMenu = [[NSMenu alloc] initWithTitle:@"Step"];
69	[stepParentItem setSubmenu:stepMenu];
70	[stepMenu release];
71	[[stepMenu addItemWithTitle:@"Into"
72						 action:@selector(debugStepInto:)
73				  keyEquivalent:[NSString stringWithFormat:@"%C", (short)NSF11FunctionKey]]
74	 setKeyEquivalentModifierMask:0];
75	[[stepMenu addItemWithTitle:@"Over"
76						 action:@selector(debugStepOver:)
77				  keyEquivalent:[NSString stringWithFormat:@"%C", (short)NSF10FunctionKey]]
78	 setKeyEquivalentModifierMask:0];
79	[[stepMenu addItemWithTitle:@"Out"
80						 action:@selector(debugStepOut:)
81				  keyEquivalent:[NSString stringWithFormat:@"%C", (short)NSF10FunctionKey]]
82	 setKeyEquivalentModifierMask:NSShiftKeyMask];
83
84	NSMenuItem *resetParentItem = [menu addItemWithTitle:@"Reset" action:NULL keyEquivalent:@""];
85	NSMenu *resetMenu = [[NSMenu alloc] initWithTitle:@"Reset"];
86	[resetParentItem setSubmenu:resetMenu];
87	[resetMenu release];
88	[[resetMenu addItemWithTitle:@"Soft"
89						  action:@selector(debugSoftReset:)
90				   keyEquivalent:[NSString stringWithFormat:@"%C", (short)NSF3FunctionKey]]
91	 setKeyEquivalentModifierMask:0];
92	[[resetMenu addItemWithTitle:@"Hard"
93						  action:@selector(debugHardReset:)
94				   keyEquivalent:[NSString stringWithFormat:@"%C", (short)NSF3FunctionKey]]
95	 setKeyEquivalentModifierMask:NSShiftKeyMask];
96
97	[menu addItem:[NSMenuItem separatorItem]];
98
99	NSMenuItem *newParentItem = [menu addItemWithTitle:@"New" action:NULL keyEquivalent:@""];
100	NSMenu *newMenu = [[NSMenu alloc] initWithTitle:@"New"];
101	[newParentItem setSubmenu:newMenu];
102	[newMenu release];
103	[newMenu addItemWithTitle:@"Memory Window"
104					   action:@selector(debugNewMemoryWindow:)
105				keyEquivalent:@"d"];
106	[newMenu addItemWithTitle:@"Disassembly Window"
107					   action:@selector(debugNewDisassemblyWindow:)
108				keyEquivalent:@"a"];
109	[newMenu addItemWithTitle:@"Error Log Window"
110					   action:@selector(debugNewErrorLogWindow:)
111				keyEquivalent:@"l"];
112	[newMenu addItemWithTitle:@"(Break|Watch)points Window"
113					   action:@selector(debugNewPointsWindow:)
114				keyEquivalent:@"b"];
115	[newMenu addItemWithTitle:@"Devices Window"
116					   action:@selector(debugNewDevicesWindow:)
117				keyEquivalent:@"D"];
118
119	[menu addItem:[NSMenuItem separatorItem]];
120
121	[menu addItemWithTitle:@"Close Window" action:@selector(performClose:) keyEquivalent:@"w"];
122	[menu addItemWithTitle:@"Quit" action:@selector(debugExit:) keyEquivalent:@"q"];
123}
124
125
126+ (NSPopUpButton *)newActionButtonWithFrame:(NSRect)frame {
127	NSPopUpButton *actionButton = [[NSPopUpButton alloc] initWithFrame:frame pullsDown:YES];
128	[actionButton setTitle:@""];
129	[actionButton addItemWithTitle:@""];
130	[actionButton setBezelStyle:NSShadowlessSquareBezelStyle];
131	[actionButton setFocusRingType:NSFocusRingTypeNone];
132	[[actionButton cell] setArrowPosition:NSPopUpArrowAtCenter];
133	[[self class] addCommonActionItems:[actionButton menu]];
134	return actionButton;
135}
136
137
138- (id)initWithMachine:(running_machine &)m title:(NSString *)t {
139	if (!(self = [super init]))
140		return nil;
141
142	window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 320, 240)
143										 styleMask:(NSTitledWindowMask |
144													NSClosableWindowMask |
145													NSMiniaturizableWindowMask |
146													NSResizableWindowMask)
147										   backing:NSBackingStoreBuffered
148											 defer:YES];
149	[window setReleasedWhenClosed:NO];
150	[window setDelegate:self];
151	[window setTitle:t];
152	[window setContentMinSize:NSMakeSize(320, 240)];
153
154	[[NSNotificationCenter defaultCenter] addObserver:self
155											 selector:@selector(saveConfig:)
156												 name:MAMESaveDebuggerConfigurationNotification
157											   object:nil];
158	[[NSNotificationCenter defaultCenter] addObserver:self
159											 selector:@selector(showDebugger:)
160												 name:MAMEShowDebuggerNotification
161											   object:nil];
162	[[NSNotificationCenter defaultCenter] addObserver:self
163											 selector:@selector(hideDebugger:)
164												 name:MAMEHideDebuggerNotification
165											   object:nil];
166
167	machine = &m;
168
169	return self;
170}
171
172
173- (void)dealloc {
174	[[NSNotificationCenter defaultCenter] removeObserver:self];
175
176	if (window != nil)
177	{
178		[window orderOut:self];
179		[window release];
180	}
181
182	[super dealloc];
183}
184
185
186- (void)activate {
187	[window makeKeyAndOrderFront:self];
188}
189
190
191- (IBAction)debugBreak:(id)sender {
192	if (machine->debug_flags & DEBUG_FLAG_ENABLED)
193		machine->debugger().console().get_visible_cpu()->debug()->halt_on_next_instruction("User-initiated break\n");
194}
195
196
197- (IBAction)debugRun:(id)sender {
198	machine->debugger().console().get_visible_cpu()->debug()->go();
199}
200
201
202- (IBAction)debugRunAndHide:(id)sender {
203	[[NSNotificationCenter defaultCenter] postNotificationName:MAMEHideDebuggerNotification
204														object:self
205													  userInfo:[NSDictionary dictionaryWithObject:[NSValue valueWithPointer:machine]
206																						   forKey:@"MAMEDebugMachine"]];
207	machine->debugger().console().get_visible_cpu()->debug()->go();
208}
209
210
211- (IBAction)debugRunToNextCPU:(id)sender {
212	machine->debugger().console().get_visible_cpu()->debug()->go_next_device();
213}
214
215
216- (IBAction)debugRunToNextInterrupt:(id)sender {
217	machine->debugger().console().get_visible_cpu()->debug()->go_interrupt();
218}
219
220
221- (IBAction)debugRunToNextVBLANK:(id)sender {
222	machine->debugger().console().get_visible_cpu()->debug()->go_vblank();
223}
224
225
226- (IBAction)debugStepInto:(id)sender {
227	machine->debugger().console().get_visible_cpu()->debug()->single_step();
228}
229
230
231- (IBAction)debugStepOver:(id)sender {
232	machine->debugger().console().get_visible_cpu()->debug()->single_step_over();
233}
234
235
236- (IBAction)debugStepOut:(id)sender {
237	machine->debugger().console().get_visible_cpu()->debug()->single_step_out();
238}
239
240
241- (IBAction)debugSoftReset:(id)sender {
242	machine->schedule_soft_reset();
243	machine->debugger().console().get_visible_cpu()->debug()->go();
244}
245
246
247- (IBAction)debugHardReset:(id)sender {
248	machine->schedule_hard_reset();
249}
250
251
252- (IBAction)debugExit:(id)sender {
253	machine->schedule_exit();
254}
255
256
257- (void)showDebugger:(NSNotification *)notification {
258	running_machine *m = (running_machine *)[[[notification userInfo] objectForKey:@"MAMEDebugMachine"] pointerValue];
259	if (m == machine)
260	{
261		if (![window isVisible] && ![window isMiniaturized])
262			[window orderFront:self];
263	}
264}
265
266
267- (void)hideDebugger:(NSNotification *)notification {
268	running_machine *m = (running_machine *)[[[notification userInfo] objectForKey:@"MAMEDebugMachine"] pointerValue];
269	if (m == machine)
270		[window orderOut:self];
271}
272
273
274- (void)saveConfig:(NSNotification *)notification {
275	running_machine *m = (running_machine *)[[[notification userInfo] objectForKey:@"MAMEDebugMachine"] pointerValue];
276	if (m == machine)
277	{
278		util::xml::data_node *parentnode = (util::xml::data_node *)[[[notification userInfo] objectForKey:@"MAMEDebugParentNode"] pointerValue];
279		util::xml::data_node *node = parentnode->add_child("window", nullptr);
280		if (node)
281			[self saveConfigurationToNode:node];
282	}
283}
284
285
286- (void)saveConfigurationToNode:(util::xml::data_node *)node {
287	NSRect frame = [window frame];
288	node->set_attribute_float("position_x", frame.origin.x);
289	node->set_attribute_float("position_y", frame.origin.y);
290	node->set_attribute_float("size_x", frame.size.width);
291	node->set_attribute_float("size_y", frame.size.height);
292}
293
294
295- (void)restoreConfigurationFromNode:(util::xml::data_node const *)node {
296	NSRect frame = [window frame];
297	frame.origin.x = node->get_attribute_float("position_x", frame.origin.x);
298	frame.origin.y = node->get_attribute_float("position_y", frame.origin.y);
299	frame.size.width = node->get_attribute_float("size_x", frame.size.width);
300	frame.size.height = node->get_attribute_float("size_y", frame.size.height);
301
302	NSSize min = [window minSize];
303	frame.size.width = std::max(frame.size.width, min.width);
304	frame.size.height = std::max(frame.size.height, min.height);
305
306	NSSize max = [window maxSize];
307	frame.size.width = std::min(frame.size.width, max.width);
308	frame.size.height = std::min(frame.size.height, max.height);
309
310	[window setFrame:frame display:YES];
311}
312
313@end
314
315
316//============================================================
317//  MAMEAuxiliaryDebugWindowHandler class
318//============================================================
319
320@implementation MAMEAuxiliaryDebugWindowHandler
321
322+ (void)cascadeWindow:(NSWindow *)window {
323	static NSPoint lastPosition = { 0, 0 };
324	if (NSEqualPoints(lastPosition, NSZeroPoint)) {
325		NSRect available = [[NSScreen mainScreen] visibleFrame];
326		lastPosition = NSMakePoint(available.origin.x + 12, available.origin.y + available.size.height - 8);
327	}
328	lastPosition = [window cascadeTopLeftFromPoint:lastPosition];
329}
330
331
332- (id)initWithMachine:(running_machine &)m title:(NSString *)t console:(MAMEDebugConsole *)c {
333	if (!(self = [super initWithMachine:m title:t]))
334		return nil;
335	console = c;
336	return self;
337}
338
339
340- (void)dealloc {
341	[super dealloc];
342}
343
344
345- (IBAction)debugNewMemoryWindow:(id)sender {
346	[console debugNewMemoryWindow:sender];
347}
348
349
350- (IBAction)debugNewDisassemblyWindow:(id)sender {
351	[console debugNewDisassemblyWindow:sender];
352}
353
354
355- (IBAction)debugNewErrorLogWindow:(id)sender {
356	[console debugNewErrorLogWindow:sender];
357}
358
359
360- (IBAction)debugNewPointsWindow:(id)sender {
361	[console debugNewPointsWindow:sender];
362}
363
364
365- (IBAction)debugNewDevicesWindow:(id)sender {
366	[console debugNewDevicesWindow:sender];
367}
368
369
370- (void)windowWillClose:(NSNotification *)notification {
371	[[NSNotificationCenter defaultCenter] postNotificationName:MAMEAuxiliaryDebugWindowWillCloseNotification
372														object:self];
373}
374
375- (void)cascadeWindowWithDesiredSize:(NSSize)desired forView:(NSView *)view {
376	// convert desired size to an adjustment and apply it to the current window frame
377	NSSize const current = [view frame].size;
378	desired.width -= current.width;
379	desired.height -= current.height;
380	NSRect windowFrame = [window frame];
381	windowFrame.size.width += desired.width;
382	windowFrame.size.height += desired.height;
383
384	// limit the size to the minimum size
385	NSSize const minimum = [window minSize];
386	windowFrame.size.width = std::max(windowFrame.size.width, minimum.width);
387	windowFrame.size.height = std::max(windowFrame.size.height, minimum.height);
388
389	// limit the size to the main screen size
390	NSRect const available = [[NSScreen mainScreen] visibleFrame];
391	windowFrame.size.width = std::min(windowFrame.size.width, available.size.width);
392	windowFrame.size.height = std::min(windowFrame.size.height, available.size.height);
393
394	// arbitrary additional height limit
395	windowFrame.size.height = std::min(windowFrame.size.height, CGFloat(320));
396
397	// place it in the bottom right corner and apply
398	windowFrame.origin.x = available.origin.x + available.size.width - windowFrame.size.width;
399	windowFrame.origin.y = available.origin.y;
400	[window setFrame:windowFrame display:YES];
401	[[self class] cascadeWindow:window];
402}
403
404@end
405
406
407//============================================================
408//  MAMEExpreesionAuxiliaryDebugWindowHandler class
409//============================================================
410
411@implementation MAMEExpressionAuxiliaryDebugWindowHandler
412
413- (id)initWithMachine:(running_machine &)m title:(NSString *)t console:(MAMEDebugConsole *)c {
414	if (!(self = [super initWithMachine:m title:t console:c]))
415		return nil;
416	history = [[MAMEDebugCommandHistory alloc] init];
417	return self;
418}
419
420
421- (void)dealloc {
422	if (history != nil)
423		[history release];
424	[super dealloc];
425}
426
427
428- (id <MAMEDebugViewExpressionSupport>)documentView {
429	return nil;
430}
431
432
433- (NSString *)expression {
434	return [[self documentView] expression];
435}
436
437- (void)setExpression:(NSString *)expression {
438	[history add:expression];
439	[[self documentView] setExpression:expression];
440	[expressionField setStringValue:expression];
441	[expressionField selectText:self];
442}
443
444
445- (IBAction)doExpression:(id)sender {
446	NSString *expr = [sender stringValue];
447	if ([expr length] > 0) {
448		[history add:expr];
449		[[self documentView] setExpression:expr];
450	} else {
451		[sender setStringValue:[[self documentView] expression]];
452		[history reset];
453	}
454	[sender selectText:self];
455}
456
457
458- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor
459{
460	if (control == expressionField)
461		[history edit];
462
463	return YES;
464}
465
466
467- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command {
468	if (control == expressionField) {
469		if (command == @selector(cancelOperation:)) {
470			[history reset];
471			[expressionField setStringValue:[[self documentView] expression]];
472			[expressionField selectText:self];
473			return YES;
474		} else if (command == @selector(moveUp:)) {
475			NSString *hist = [history previous:[expressionField stringValue]];
476			if (hist != nil) {
477				[expressionField setStringValue:hist];
478				[expressionField selectText:self];
479			}
480			return YES;
481		} else if (command == @selector(moveDown:)) {
482			NSString *hist = [history next:[expressionField stringValue]];
483			if (hist != nil) {
484				[expressionField setStringValue:hist];
485				[expressionField selectText:self];
486			}
487			return YES;
488		}
489	}
490	return NO;
491}
492
493
494- (void)saveConfigurationToNode:(util::xml::data_node *)node {
495	[super saveConfigurationToNode:node];
496	node->add_child("expression", [[self expression] UTF8String]);
497}
498
499
500- (void)restoreConfigurationFromNode:(util::xml::data_node const *)node {
501	[super restoreConfigurationFromNode:node];
502	util::xml::data_node const *const expr = node->get_child("expression");
503	if (expr && expr->get_value())
504		[self setExpression:[NSString stringWithUTF8String:expr->get_value()]];
505}
506
507@end
508