1 /*
2  * TilEm II
3  *
4  * Copyright (c) 2011 Benjamin Moody
5  *
6  * This program is free software: you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <ticalcs.h>
30 #include <tilem.h>
31 #include <tilemdb.h>
32 
33 #include "gui.h"
34 #include "disasmview.h"
35 #include "fixedtreeview.h"
36 
37 /**************** Add/edit breakpoint dialog ****************/
38 
39 struct hex_entry {
40 	GtkWidget *addr_label;
41 	GtkWidget *addr_entry;
42 	GtkWidget *page_label;
43 	GtkWidget *page_entry;
44 };
45 
46 struct breakpoint_dlg {
47 	TilemDebugger *dbg;
48 
49 	GtkWidget *dlg;
50 	GtkWidget *box;
51 
52 	GtkWidget *type_combo;
53 	GtkWidget *access_cb[3];
54 	GtkWidget *single_rb;
55 	GtkWidget *range_rb;
56 	GtkWidget *access_label;
57 	GtkWidget *address_label;
58 
59 	struct hex_entry start;
60 	struct hex_entry end;
61 };
62 
63 static const struct {
64 	char abbrev;
65 	const char *desc;
66 	const char *value_label;
67 	int use_pages;
68 	guint access_mask;
69 } type_info[] = {
70 	{ 'M', "Memory address (logical)", "Address", 0, 7 },
71 	{ 'M', "Memory address (absolute)", "Address", 1, 7 },
72 	{ 'P', "I/O port", "Port Number", 0, 6 },
73 	{ 'I', "Z80 instruction", "Opcode", 0, 0 }
74 };
75 
76 /* Determine currently selected address type */
get_bp_type(struct breakpoint_dlg * bpdlg)77 static guint get_bp_type(struct breakpoint_dlg *bpdlg)
78 {
79 	int i = gtk_combo_box_get_active(GTK_COMBO_BOX(bpdlg->type_combo));
80 	return (i < 0 ? 0 : i);
81 }
82 
83 /* Format address as a string */
hex_entry_set_value(struct hex_entry * he,TilemDebugger * dbg,int type,dword value)84 static void hex_entry_set_value(struct hex_entry *he, TilemDebugger *dbg,
85                                 int type, dword value)
86 {
87 	const TilemCalc *calc;
88 	char buf[20];
89 	unsigned int page;
90 
91 	g_return_if_fail(dbg->emu != NULL);
92 	g_return_if_fail(dbg->emu->calc != NULL);
93 
94 	calc = dbg->emu->calc;
95 
96 	switch (type) {
97 	case TILEM_DB_BREAK_LOGICAL:
98 		g_snprintf(buf, sizeof(buf), "%04X", value);
99 		break;
100 
101 	case TILEM_DB_BREAK_PHYSICAL:
102 		if (value >= calc->hw.romsize) {
103 			value -= calc->hw.romsize;
104 			page = (value >> 14) + calc->hw.rampagemask;
105 		}
106 		else {
107 			page = (value >> 14);
108 		}
109 
110 		g_snprintf(buf, sizeof(buf), "%02X", page);
111 		gtk_entry_set_text(GTK_ENTRY(he->page_entry), buf);
112 
113 		g_snprintf(buf, sizeof(buf), "%04X", value & 0x3fff);
114 		break;
115 
116 	case TILEM_DB_BREAK_PORT:
117 		g_snprintf(buf, sizeof(buf), "%02X", value);
118 		break;
119 
120 	case TILEM_DB_BREAK_OPCODE:
121 		if (value < 0x100)
122 			g_snprintf(buf, sizeof(buf), "%02X", value);
123 		else if (value < 0x10000)
124 			g_snprintf(buf, sizeof(buf), "%04X", value);
125 		else if (value < 0x1000000)
126 			g_snprintf(buf, sizeof(buf), "%06X", value);
127 		else
128 			g_snprintf(buf, sizeof(buf), "%08X", value);
129 		break;
130 
131 	default:
132 		g_return_if_reached();
133 	}
134 
135 	gtk_entry_set_text(GTK_ENTRY(he->addr_entry), buf);
136 }
137 
138 /* Parse contents of entry */
parse_num(TilemDebugger * dbg,const char * s,dword * a)139 static gboolean parse_num(TilemDebugger *dbg, const char *s, dword *a)
140 {
141 	const char *n;
142 	char *e;
143 
144 	g_return_val_if_fail(s != NULL, FALSE);
145 
146 	if (s[0] == '$')
147 		n = s + 1;
148 	else if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
149 		n = s + 2;
150 	else
151 		n = s;
152 
153 	*a = strtol(n, &e, 16);
154 	if (e != n) {
155 		if (*e == 'h' || *e == 'H')
156 			e++;
157 		if (*e == 0)
158 			return TRUE;
159 	}
160 
161 	if (dbg->dasm && tilem_disasm_get_label(dbg->dasm, s, a))
162 		return TRUE;
163 
164 	return FALSE;
165 }
166 
167 /* Parse user input from hex entry */
hex_entry_parse_value(struct hex_entry * he,TilemDebugger * dbg,int type,dword * value)168 static gboolean hex_entry_parse_value(struct hex_entry *he, TilemDebugger *dbg,
169                                       int type, dword *value)
170 {
171 	const TilemCalc *calc = dbg->emu->calc;
172 	dword page;
173 	const char *s;
174 
175 	g_return_val_if_fail(calc != NULL, 0);
176 
177 	s = gtk_entry_get_text(GTK_ENTRY(he->addr_entry));
178 	if (!parse_num(dbg, s, value))
179 		return FALSE;
180 
181 	if (type != TILEM_DB_BREAK_PHYSICAL)
182 		return TRUE;
183 
184 	s = gtk_entry_get_text(GTK_ENTRY(he->page_entry));
185 	if (!parse_num(dbg, s, &page))
186 		return FALSE;
187 
188 	*value &= 0x3fff;
189 
190 	if (page >= calc->hw.rampagemask) {
191 		*value += ((page - calc->hw.rampagemask) << 14);
192 		*value %= calc->hw.ramsize;
193 		*value += calc->hw.romsize;
194 	}
195 	else {
196 		*value += (page << 14);
197 		*value %= calc->hw.romsize;
198 	}
199 
200 	return TRUE;
201 }
202 
203 /* Parse input fields and check if they make sense */
parse_input(struct breakpoint_dlg * bpdlg,TilemDebugBreakpoint * bp)204 static gboolean parse_input(struct breakpoint_dlg *bpdlg,
205                             TilemDebugBreakpoint *bp)
206 {
207 	int i;
208 	dword addr0, addr1;
209 
210 	bp->mask = bp->start = bp->end = 0xffffffff;
211 	bp->type = get_bp_type(bpdlg);
212 	bp->mode = 0;
213 
214 	for (i = 0; i < 3; i++)
215 		if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bpdlg->access_cb[i])))
216 			bp->mode += (1 << i);
217 
218 	bp->mode &= type_info[bp->type].access_mask;
219 	if (bp->type == TILEM_DB_BREAK_OPCODE)
220 		bp->mode = TILEM_DB_BREAK_EXEC;
221 	else if (bp->mode == 0)
222 		return FALSE;
223 
224 	if (!hex_entry_parse_value(&bpdlg->start, bpdlg->dbg,
225 	                           bp->type, &addr0))
226 		return FALSE;
227 
228 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bpdlg->range_rb))) {
229 		if (!hex_entry_parse_value(&bpdlg->end, bpdlg->dbg,
230 		                           bp->type, &addr1))
231 			return FALSE;
232 	}
233 	else {
234 		addr1 = addr0;
235 	}
236 
237 	if (bp->type == TILEM_DB_BREAK_LOGICAL)
238 		bp->mask = 0xffff;
239 	else if (bp->type == TILEM_DB_BREAK_PORT)
240 		bp->mask = 0xff;
241 	else
242 		bp->mask = 0xffffffff;
243 
244 	bp->start = addr0 & bp->mask;
245 	bp->end = addr1 & bp->mask;
246 	if (bp->end < bp->start)
247 		return FALSE;
248 
249 	return TRUE;
250 }
251 
252 /* Check if input fields are valid, and enable/disable OK response as
253    appropriate */
validate(struct breakpoint_dlg * bpdlg)254 static void validate(struct breakpoint_dlg *bpdlg)
255 {
256 	TilemDebugBreakpoint tmpbp;
257 
258 	if (parse_input(bpdlg, &tmpbp))
259 		gtk_dialog_set_response_sensitive(GTK_DIALOG(bpdlg->dlg),
260 		                                  GTK_RESPONSE_OK, TRUE);
261 	else
262 		gtk_dialog_set_response_sensitive(GTK_DIALOG(bpdlg->dlg),
263 		                                  GTK_RESPONSE_OK, FALSE);
264 }
265 
266 /* Enable/disable check buttons for access mode */
set_access_mask(struct breakpoint_dlg * bpdlg,guint mask)267 static void set_access_mask(struct breakpoint_dlg *bpdlg, guint mask)
268 {
269 	int i;
270 
271 	if (mask)
272 		gtk_widget_show(bpdlg->access_label);
273 	else
274 		gtk_widget_hide(bpdlg->access_label);
275 
276 	for (i = 0; i < 3; i++) {
277 		if (mask & (1 << i))
278 			gtk_widget_show(bpdlg->access_cb[i]);
279 		else
280 			gtk_widget_hide(bpdlg->access_cb[i]);
281 	}
282 }
283 
284 /* Combo box changed */
addr_type_changed(G_GNUC_UNUSED GtkComboBox * combo,gpointer data)285 static void addr_type_changed(G_GNUC_UNUSED GtkComboBox *combo, gpointer data)
286 {
287 	struct breakpoint_dlg *bpdlg = data;
288 	int type = get_bp_type(bpdlg);
289 	gboolean range = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bpdlg->range_rb));
290 	char *s;
291 
292 	s = g_strdup_printf("<b>%s</b>", type_info[type].value_label);
293 	gtk_label_set_markup(GTK_LABEL(bpdlg->address_label), s);
294 	g_free(s);
295 
296 	set_access_mask(bpdlg, type_info[type].access_mask);
297 
298 	if (type_info[type].use_pages) {
299 		gtk_widget_show(bpdlg->start.page_label);
300 		gtk_widget_show(bpdlg->start.page_entry);
301 	}
302 	else {
303 		gtk_widget_hide(bpdlg->start.page_label);
304 		gtk_widget_hide(bpdlg->start.page_entry);
305 	}
306 
307 	if (range) {
308 		gtk_label_set_text_with_mnemonic(GTK_LABEL(bpdlg->start.addr_label),
309 		                                 "_Start:");
310 		gtk_widget_show(bpdlg->end.addr_label);
311 		gtk_widget_show(bpdlg->end.addr_entry);
312 	}
313 	else {
314 		gtk_label_set_text_with_mnemonic(GTK_LABEL(bpdlg->start.addr_label),
315 		                                 "_Value:");
316 		gtk_widget_hide(bpdlg->end.addr_label);
317 		gtk_widget_hide(bpdlg->end.addr_entry);
318 	}
319 
320 	if (type_info[type].use_pages && range) {
321 		gtk_widget_show(bpdlg->end.page_label);
322 		gtk_widget_show(bpdlg->end.page_entry);
323 	}
324 	else {
325 		gtk_widget_hide(bpdlg->end.page_label);
326 		gtk_widget_hide(bpdlg->end.page_entry);
327 	}
328 
329 	validate(bpdlg);
330 }
331 
332 /* Access mode changed */
access_changed(G_GNUC_UNUSED GtkToggleButton * tb,gpointer data)333 static void access_changed(G_GNUC_UNUSED GtkToggleButton *tb, gpointer data)
334 {
335 	struct breakpoint_dlg *bpdlg = data;
336 	validate(bpdlg);
337 }
338 
339 /* Single/range mode changed */
range_mode_changed(G_GNUC_UNUSED GtkToggleButton * tb,gpointer data)340 static void range_mode_changed(G_GNUC_UNUSED GtkToggleButton *tb, gpointer data)
341 {
342 	struct breakpoint_dlg *bpdlg = data;
343 	addr_type_changed(NULL, bpdlg);
344 }
345 
346 /* Text of entry changed */
entry_edited(G_GNUC_UNUSED GtkEntry * entry,gpointer data)347 static void entry_edited(G_GNUC_UNUSED GtkEntry *entry,
348                          gpointer data)
349 {
350 	struct breakpoint_dlg *bpdlg = data;
351 	validate(bpdlg);
352 }
353 
354 /* Key presssed in entry */
entry_key_event(G_GNUC_UNUSED GtkWidget * entry,GdkEventKey * ev,gpointer data)355 static gboolean entry_key_event(G_GNUC_UNUSED GtkWidget *entry,
356                                 GdkEventKey *ev, gpointer data)
357 {
358 	struct breakpoint_dlg *bpdlg = data;
359 	TilemDebugBreakpoint tmpbp;
360 
361 	if (ev->state & GDK_MODIFIER_MASK)
362 		return FALSE;
363 
364 	if (ev->keyval != GDK_Return
365 	    && ev->keyval != GDK_KP_Enter
366 	    && ev->keyval != GDK_ISO_Enter)
367 		return FALSE;
368 
369 	if (parse_input(bpdlg, &tmpbp))
370 		gtk_dialog_response(GTK_DIALOG(bpdlg->dlg), GTK_RESPONSE_OK);
371 	else
372 		gtk_widget_child_focus(bpdlg->box, GTK_DIR_TAB_FORWARD);
373 
374 	return TRUE;
375 }
376 
init_hex_entry(struct breakpoint_dlg * bpdlg,struct hex_entry * he,const char * label,GtkTable * tbl,int ypos)377 static void init_hex_entry(struct breakpoint_dlg *bpdlg,
378                            struct hex_entry *he, const char *label,
379                            GtkTable *tbl, int ypos)
380 {
381 	GtkWidget *align, *lbl;
382 
383 	he->addr_label = lbl = gtk_label_new_with_mnemonic(label);
384 	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
385 	align = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
386 	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 6);
387 	gtk_container_add(GTK_CONTAINER(align), lbl);
388 	gtk_table_attach(tbl, align, 0, 1, ypos, ypos + 1,
389 	                 GTK_FILL, GTK_FILL, 0, 0);
390 
391 	he->addr_entry = gtk_entry_new();
392 	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), he->addr_entry);
393 	gtk_table_attach(tbl, he->addr_entry, 1, 2, ypos, ypos + 1,
394 	                 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
395 
396 	g_signal_connect(he->addr_entry, "changed",
397 	                 G_CALLBACK(entry_edited), bpdlg);
398 	g_signal_connect(he->addr_entry, "key-press-event",
399 	                 G_CALLBACK(entry_key_event), bpdlg);
400 
401 	he->page_label = lbl = gtk_label_new("Page:");
402 	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
403 	gtk_table_attach(tbl, lbl, 2, 3, ypos, ypos + 1,
404 	                 GTK_FILL, GTK_FILL, 6, 0);
405 
406 	he->page_entry = gtk_entry_new();
407 	gtk_entry_set_width_chars(GTK_ENTRY(he->page_entry), 5);
408 	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), he->page_entry);
409 	gtk_table_attach(tbl, he->page_entry, 3, 4, ypos, ypos + 1,
410 	                 GTK_FILL, GTK_FILL, 0, 0);
411 
412 	g_signal_connect(he->page_entry, "changed",
413 	                 G_CALLBACK(entry_edited), bpdlg);
414 	g_signal_connect(he->page_entry, "key-press-event",
415 	                 G_CALLBACK(entry_key_event), bpdlg);
416 }
417 
edit_breakpoint(TilemDebugger * dbg,GtkWindow * parent_window,const char * title,TilemDebugBreakpoint * bp,gboolean edit_existing)418 static gboolean edit_breakpoint(TilemDebugger *dbg,
419                                 GtkWindow *parent_window,
420                                 const char *title,
421                                 TilemDebugBreakpoint *bp,
422                                 gboolean edit_existing)
423 {
424 	GtkWidget *dlg, *vbox, *frame, *tbl, *hbox, *lbl, *combo, *cb, *rb;
425 	struct breakpoint_dlg bpdlg;
426 	gsize i;
427 
428 	g_return_val_if_fail(bp != NULL, FALSE);
429 	g_return_val_if_fail(dbg != NULL, FALSE);
430 	g_return_val_if_fail(dbg->emu != NULL, FALSE);
431 	g_return_val_if_fail(dbg->emu->calc != NULL, FALSE);
432 
433 	bpdlg.dbg = dbg;
434 
435 	dlg = gtk_dialog_new_with_buttons(title, parent_window,
436 	                                  GTK_DIALOG_MODAL,
437 	                                  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
438 	                                  GTK_STOCK_OK, GTK_RESPONSE_OK,
439 	                                  NULL);
440 	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
441 	                                        GTK_RESPONSE_OK,
442 	                                        GTK_RESPONSE_CANCEL,
443 	                                        -1);
444 	gtk_dialog_set_default_response(GTK_DIALOG(dlg),
445 	                                GTK_RESPONSE_OK);
446 
447 	bpdlg.dlg = dlg;
448 
449 	bpdlg.box = gtk_vbox_new(FALSE, 6);
450 	gtk_container_set_border_width(GTK_CONTAINER(bpdlg.box), 6);
451 
452 	tbl = gtk_table_new(2, 2, FALSE);
453 	gtk_table_set_row_spacings(GTK_TABLE(tbl), 6);
454 	gtk_table_set_col_spacings(GTK_TABLE(tbl), 6);
455 
456 	/* Breakpoint type */
457 
458 	lbl = gtk_label_new_with_mnemonic("Breakpoint _type:");
459 	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
460 	gtk_table_attach(GTK_TABLE(tbl), lbl, 0, 1, 0, 1,
461 	                 GTK_FILL, GTK_FILL, 0, 0);
462 
463 	combo = gtk_combo_box_new_text();
464 	for (i = 0; i < G_N_ELEMENTS(type_info); i++)
465 		gtk_combo_box_append_text(GTK_COMBO_BOX(combo), type_info[i].desc);
466 
467 	bpdlg.type_combo = combo;
468 	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), combo);
469 	gtk_table_attach(GTK_TABLE(tbl), combo, 1, 2, 0, 1,
470 	                 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
471 
472 	gtk_combo_box_set_active(GTK_COMBO_BOX(combo), bp->type);
473 
474 	/* Access mode */
475 
476 	bpdlg.access_label = lbl = gtk_label_new("Break when:");
477 	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
478 	gtk_table_attach(GTK_TABLE(tbl), lbl, 0, 1, 1, 2,
479 	                 GTK_FILL, GTK_FILL, 0, 0);
480 
481 	hbox = gtk_hbox_new(FALSE, 6);
482 
483 	cb = gtk_check_button_new_with_mnemonic("_Reading");
484 	gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
485 	bpdlg.access_cb[2] = cb;
486 
487 	cb = gtk_check_button_new_with_mnemonic("_Writing");
488 	gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
489 	bpdlg.access_cb[1] = cb;
490 
491 	cb = gtk_check_button_new_with_mnemonic("E_xecuting");
492 	gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
493 	bpdlg.access_cb[0] = cb;
494 
495 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bpdlg.access_cb[0]),
496 	                             bp->mode & 1);
497 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bpdlg.access_cb[1]),
498 	                             bp->mode & 2);
499 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bpdlg.access_cb[2]),
500 	                             bp->mode & 4);
501 
502 	gtk_table_attach(GTK_TABLE(tbl), hbox, 1, 2, 1, 2,
503 	                 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
504 
505 	frame = new_frame("Breakpoint Condition", tbl);
506 	gtk_box_pack_start(GTK_BOX(bpdlg.box), frame, FALSE, FALSE, 0);
507 
508 	/* Addresses */
509 
510 	tbl = gtk_table_new(3, 4, FALSE);
511 	gtk_table_set_row_spacings(GTK_TABLE(tbl), 6);
512 
513 	hbox = gtk_hbox_new(FALSE, 6);
514 
515 	rb = gtk_radio_button_new_with_mnemonic(NULL, "Si_ngle");
516 	gtk_box_pack_start(GTK_BOX(hbox), rb, FALSE, FALSE, 0);
517 	bpdlg.single_rb = rb;
518 
519 	rb = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(rb), "R_ange");
520 	gtk_box_pack_start(GTK_BOX(hbox), rb, FALSE, FALSE, 0);
521 	bpdlg.range_rb = rb;
522 
523 	if (edit_existing && bp->end != bp->start)
524 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), TRUE);
525 
526 	gtk_table_attach(GTK_TABLE(tbl), hbox, 0, 2, 0, 1,
527 	                 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
528 
529 	init_hex_entry(&bpdlg, &bpdlg.start, "S_tart:", GTK_TABLE(tbl), 1);
530 	init_hex_entry(&bpdlg, &bpdlg.end, "_End:", GTK_TABLE(tbl), 2);
531 
532 	frame = new_frame("Address", tbl);
533 	bpdlg.address_label = gtk_frame_get_label_widget(GTK_FRAME(frame));
534 	gtk_box_pack_start(GTK_BOX(bpdlg.box), frame, FALSE, FALSE, 0);
535 	gtk_widget_show_all(bpdlg.box);
536 
537 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
538 	gtk_box_pack_start(GTK_BOX(vbox), bpdlg.box, FALSE, FALSE, 0);
539 
540 	if (edit_existing) {
541 		hex_entry_set_value(&bpdlg.start, dbg, bp->type, bp->start);
542 		hex_entry_set_value(&bpdlg.end, dbg, bp->type, bp->end);
543 	}
544 
545 	g_signal_connect(combo, "changed",
546 	                 G_CALLBACK(addr_type_changed), &bpdlg);
547 	g_signal_connect(bpdlg.access_cb[0], "toggled",
548 	                 G_CALLBACK(access_changed), &bpdlg);
549 	g_signal_connect(bpdlg.access_cb[1], "toggled",
550 	                 G_CALLBACK(access_changed), &bpdlg);
551 	g_signal_connect(bpdlg.access_cb[2], "toggled",
552 	                 G_CALLBACK(access_changed), &bpdlg);
553 	g_signal_connect(bpdlg.single_rb, "toggled",
554 	                 G_CALLBACK(range_mode_changed), &bpdlg);
555 
556 	addr_type_changed(NULL, &bpdlg);
557 
558 	gtk_widget_grab_focus(bpdlg.start.addr_entry);
559 
560 	do {
561 		if (gtk_dialog_run(GTK_DIALOG(dlg)) != GTK_RESPONSE_OK) {
562 			gtk_widget_destroy(dlg);
563 			return FALSE;
564 		}
565 	} while (!parse_input(&bpdlg, bp));
566 
567 	gtk_widget_destroy(dlg);
568 	return TRUE;
569 }
570 
571 /**************** Breakpoint list dialog ****************/
572 
573 enum {
574 	COL_BP,
575 	COL_START,
576 	COL_END,
577 	COL_TYPE_STR,
578 	COL_START_STR,
579 	COL_END_STR,
580 	COL_ENABLED,
581 	N_COLUMNS
582 };
583 
584 struct bplist_dlg {
585 	TilemDebugger *dbg;
586 	GtkWidget *dlg;
587 	GtkListStore *store;
588 	GtkWidget *treeview;
589 	GtkWidget *remove_btn;
590 	GtkWidget *edit_btn;
591 	GtkWidget *clear_btn;
592 };
593 
594 /* Convert address into a displayable string */
format_address(TilemDebugger * dbg,char * buf,int bufsize,int type,dword value)595 static void format_address(TilemDebugger *dbg,
596                            char *buf, int bufsize,
597                            int type, dword value)
598 {
599 	const TilemCalc *calc;
600 	unsigned int page;
601 
602 	g_return_if_fail(dbg->emu != NULL);
603 	g_return_if_fail(dbg->emu->calc != NULL);
604 
605 	calc = dbg->emu->calc;
606 
607 	switch (type) {
608 	case TILEM_DB_BREAK_LOGICAL:
609 		g_snprintf(buf, bufsize, "%04X", value);
610 		break;
611 
612 	case TILEM_DB_BREAK_PHYSICAL:
613 		if (value >= calc->hw.romsize) {
614 			value -= calc->hw.romsize;
615 			page = (value >> 14) + calc->hw.rampagemask;
616 		}
617 		else {
618 			page = (value >> 14);
619 		}
620 
621 		g_snprintf(buf, bufsize, "%02X:%04X", page, value & 0x3fff);
622 		break;
623 
624 	case TILEM_DB_BREAK_PORT:
625 		g_snprintf(buf, bufsize, "%02X", value);
626 		break;
627 
628 	case TILEM_DB_BREAK_OPCODE:
629 		if (value < 0x100)
630 			g_snprintf(buf, bufsize, "%02X", value);
631 		else if (value < 0x10000)
632 			g_snprintf(buf, bufsize, "%04X", value);
633 		else if (value < 0x1000000)
634 			g_snprintf(buf, bufsize, "%06X", value);
635 		else
636 			g_snprintf(buf, bufsize, "%08X", value);
637 		break;
638 
639 	default:
640 		g_return_if_reached();
641 	}
642 }
643 
644 /* Store breakpoint properties in tree model */
set_iter_from_bp(struct bplist_dlg * bpldlg,GtkTreeIter * iter,const TilemDebugBreakpoint * bp)645 static void set_iter_from_bp(struct bplist_dlg *bpldlg, GtkTreeIter *iter,
646                              const TilemDebugBreakpoint *bp)
647 {
648 	char tbuf[5], sbuf[10], ebuf[10];
649 	int i, j;
650 
651 	g_return_if_fail(bp != NULL);
652 
653 	tbuf[0] = type_info[bp->type].abbrev;
654 	j = 1;
655 	for (i = 0; i < 3; i++)
656 		if (bp->mode & (4 >> i))
657 			tbuf[j++] = "RWX"[i];
658 	tbuf[j] = 0;
659 
660 	format_address(bpldlg->dbg, sbuf, sizeof(sbuf), bp->type, bp->start);
661 	format_address(bpldlg->dbg, ebuf, sizeof(ebuf), bp->type, bp->end);
662 
663 	gtk_list_store_set(bpldlg->store, iter,
664 	                   COL_BP, bp,
665 	                   COL_START, bp->start,
666 	                   COL_END, bp->end,
667 	                   COL_TYPE_STR, tbuf,
668 	                   COL_START_STR, sbuf,
669 	                   COL_END_STR, ebuf,
670 	                   COL_ENABLED, !bp->disabled,
671 	                   -1);
672 }
673 
674 /* Get breakpoint pointer for the given tree model row */
get_iter_bp(struct bplist_dlg * bpldlg,GtkTreeIter * iter)675 static TilemDebugBreakpoint *get_iter_bp(struct bplist_dlg *bpldlg,
676                                          GtkTreeIter *iter)
677 {
678 	gpointer ptr;
679 	gtk_tree_model_get(GTK_TREE_MODEL(bpldlg->store), iter,
680 	                   COL_BP, &ptr, -1);
681 	return (TilemDebugBreakpoint *) ptr;
682 }
683 
684 /* Set buttons sensitive or insensitive depending on whether any BPs
685    are selected */
update_buttons(struct bplist_dlg * bpldlg)686 static void update_buttons(struct bplist_dlg *bpldlg)
687 {
688 	GtkTreeSelection *sel;
689 	gboolean any_sel;
690 
691 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(bpldlg->treeview));
692 
693 	any_sel = gtk_tree_selection_get_selected(sel, NULL, NULL);
694 	gtk_widget_set_sensitive(bpldlg->remove_btn, any_sel);
695 	gtk_widget_set_sensitive(bpldlg->edit_btn, any_sel);
696 
697 	gtk_widget_set_sensitive(bpldlg->clear_btn,
698 	                         bpldlg->dbg->breakpoints != NULL);
699 }
700 
701 /* "Add breakpoint" button clicked */
add_clicked(G_GNUC_UNUSED GtkButton * btn,gpointer data)702 static void add_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
703 {
704 	struct bplist_dlg *bpldlg = data;
705 	TilemDebugBreakpoint tmpbp, *newbp;
706 	TilemDebugger *dbg = bpldlg->dbg;
707 	GtkTreeIter iter;
708 
709 	memset(&tmpbp, 0, sizeof(tmpbp));
710 	tmpbp.type = dbg->last_bp_type;
711 	tmpbp.mode = dbg->last_bp_mode;
712 
713 	if (!edit_breakpoint(dbg, GTK_WINDOW(bpldlg->dlg),
714 	                     "Add Breakpoint", &tmpbp, FALSE))
715 		return;
716 
717 	dbg->last_bp_type = tmpbp.type;
718 	dbg->last_bp_mode = tmpbp.mode;
719 
720 	newbp = tilem_debugger_add_breakpoint(dbg, &tmpbp);
721 	gtk_list_store_append(bpldlg->store, &iter);
722 	set_iter_from_bp(bpldlg, &iter, newbp);
723 
724 	update_buttons(bpldlg);
725 }
726 
727 /* "Remove breakpoint" button clicked */
remove_clicked(G_GNUC_UNUSED GtkButton * btn,gpointer data)728 static void remove_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
729 {
730 	struct bplist_dlg *bpldlg = data;
731 	GtkTreeSelection *sel;
732 	GtkTreeIter iter;
733 	TilemDebugBreakpoint *bp;
734 
735 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(bpldlg->treeview));
736 
737 	if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
738 		return;
739 
740 	bp = get_iter_bp(bpldlg, &iter);
741 	g_return_if_fail(bp != NULL);
742 	gtk_list_store_remove(bpldlg->store, &iter);
743 	tilem_debugger_remove_breakpoint(bpldlg->dbg, bp);
744 
745 	update_buttons(bpldlg);
746 }
747 
748 /* Edit an existing breakpoint */
edit_row(struct bplist_dlg * bpldlg,GtkTreeIter * iter)749 static void edit_row(struct bplist_dlg *bpldlg, GtkTreeIter *iter)
750 {
751 	TilemDebugBreakpoint *bp, tmpbp;
752 
753 	bp = get_iter_bp(bpldlg, iter);
754 	g_return_if_fail(bp != NULL);
755 	tmpbp = *bp;
756 
757 	if (!edit_breakpoint(bpldlg->dbg, GTK_WINDOW(bpldlg->dlg),
758 	                     "Edit Breakpoint", &tmpbp, TRUE))
759 		return;
760 
761 	tmpbp.disabled = 0;
762 	tilem_debugger_change_breakpoint(bpldlg->dbg, bp, &tmpbp);
763 	set_iter_from_bp(bpldlg, iter, bp);
764 
765 	update_buttons(bpldlg);
766 }
767 
768 /* "Edit breakpoint" button clicked */
edit_clicked(G_GNUC_UNUSED GtkButton * btn,gpointer data)769 static void edit_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
770 {
771 	struct bplist_dlg *bpldlg = data;
772 	GtkTreeSelection *sel;
773 	GtkTreeIter iter;
774 
775 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(bpldlg->treeview));
776 
777 	if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
778 		return;
779 
780 	edit_row(bpldlg, &iter);
781 }
782 
783 /* "Clear breakpoints" button clicked */
clear_clicked(G_GNUC_UNUSED GtkButton * btn,gpointer data)784 static void clear_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
785 {
786 	struct bplist_dlg *bpldlg = data;
787 	GtkWidget *dlg;
788 	TilemDebugBreakpoint *bp;
789 
790 	dlg = gtk_message_dialog_new(GTK_WINDOW(bpldlg->dlg),
791 	                             GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,
792 	                             GTK_BUTTONS_NONE,
793 	                             "Clear all breakpoints?");
794 	gtk_message_dialog_format_secondary_markup
795 		(GTK_MESSAGE_DIALOG(dlg),
796 		 "All existing breakpoints will be deleted and"
797 		 " cannot be restored.");
798 	gtk_dialog_add_buttons(GTK_DIALOG(dlg),
799 	                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
800 	                       GTK_STOCK_CLEAR, GTK_RESPONSE_ACCEPT,
801 	                       NULL);
802 	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
803 	                                        GTK_RESPONSE_ACCEPT,
804 	                                        GTK_RESPONSE_CANCEL,
805 	                                        -1);
806 
807 	if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_ACCEPT) {
808 		while (bpldlg->dbg->breakpoints) {
809 			bp = bpldlg->dbg->breakpoints->data;
810 			tilem_debugger_remove_breakpoint(bpldlg->dbg, bp);
811 		}
812 		gtk_list_store_clear(bpldlg->store);
813 		update_buttons(bpldlg);
814 	}
815 
816 	gtk_widget_destroy(dlg);
817 }
818 
819 /* Row activated (double-clicked, usually) */
row_activated(G_GNUC_UNUSED GtkTreeView * treeview,GtkTreePath * path,G_GNUC_UNUSED GtkTreeViewColumn * col,gpointer data)820 static void row_activated(G_GNUC_UNUSED GtkTreeView *treeview,
821                           GtkTreePath *path,
822                           G_GNUC_UNUSED GtkTreeViewColumn *col,
823                           gpointer data)
824 {
825 	struct bplist_dlg *bpldlg = data;
826 	GtkTreeIter iter;
827 
828 	if (gtk_tree_model_get_iter(GTK_TREE_MODEL(bpldlg->store),
829 	                            &iter, path))
830 		edit_row(bpldlg, &iter);
831 }
832 
833 /* Toggle button clicked for a breakpoint */
enabled_toggled(G_GNUC_UNUSED GtkCellRendererToggle * cell,gchar * pathstr,gpointer data)834 static void enabled_toggled(G_GNUC_UNUSED GtkCellRendererToggle *cell,
835                             gchar *pathstr, gpointer data)
836 {
837 	struct bplist_dlg *bpldlg = data;
838 	GtkTreePath *path;
839 	GtkTreeIter iter;
840 	TilemDebugBreakpoint *bp, tmpbp;
841 
842 	path = gtk_tree_path_new_from_string(pathstr);
843 	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(bpldlg->store),
844 	                             &iter, path)) {
845 		gtk_tree_path_free(path);
846 		return;
847 	}
848 	gtk_tree_path_free(path);
849 
850 	bp = get_iter_bp(bpldlg, &iter);
851 	g_return_if_fail(bp != NULL);
852 	tmpbp = *bp;
853 	tmpbp.disabled = !tmpbp.disabled;
854 	tilem_debugger_change_breakpoint(bpldlg->dbg, bp, &tmpbp);
855 	set_iter_from_bp(bpldlg, &iter, bp);
856 }
857 
858 /* Selection changed */
selection_changed(G_GNUC_UNUSED GtkTreeSelection * sel,gpointer data)859 static void selection_changed(G_GNUC_UNUSED GtkTreeSelection *sel,
860                               gpointer data)
861 {
862 	struct bplist_dlg *bpldlg = data;
863 	update_buttons(bpldlg);
864 }
865 
866 /* Show a dialog letting the user add, remove, and edit breakpoints */
tilem_debugger_edit_breakpoints(TilemDebugger * dbg)867 void tilem_debugger_edit_breakpoints(TilemDebugger *dbg)
868 {
869 	struct bplist_dlg bpldlg;
870 	GtkWidget *dlg, *hbox, *treeview, *sw, *bbox, *btn, *vbox, *vbox2,
871 		*invalid_cb, *undoc_cb;
872 	GtkListStore *store;
873 	GtkTreeViewColumn *col;
874 	GtkCellRenderer *cell;
875 	GtkTreeIter iter;
876 	GSList *l;
877 	GtkTreeSelection *sel;
878 	unsigned int flags;
879 
880 	g_return_if_fail(dbg != NULL);
881 	g_return_if_fail(dbg->emu != NULL);
882 	g_return_if_fail(dbg->emu->calc != NULL);
883 
884 	bpldlg.dbg = dbg;
885 
886 	dlg = gtk_dialog_new_with_buttons("Breakpoints",
887 	                                  GTK_WINDOW(dbg->window),
888 	                                  GTK_DIALOG_MODAL,
889 	                                  GTK_STOCK_CLOSE,
890 	                                  GTK_RESPONSE_ACCEPT,
891 	                                  NULL);
892 
893 	gtk_window_set_default_size(GTK_WINDOW(dlg), -1, 300);
894 
895 	store = gtk_list_store_new(N_COLUMNS,
896 	                           G_TYPE_POINTER,
897 	                           G_TYPE_INT,
898 	                           G_TYPE_INT,
899 	                           G_TYPE_STRING,
900 	                           G_TYPE_STRING,
901 	                           G_TYPE_STRING,
902 	                           G_TYPE_BOOLEAN);
903 
904 	bpldlg.dlg = dlg;
905 	bpldlg.store = store;
906 
907 	vbox = gtk_vbox_new(FALSE, 6);
908 	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
909 
910 	hbox = gtk_hbox_new(FALSE, 6);
911 
912 	for (l = dbg->breakpoints; l; l = l->next) {
913 		gtk_list_store_append(store, &iter);
914 		set_iter_from_bp(&bpldlg, &iter, l->data);
915 	}
916 
917 	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
918 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
919 	gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview), TRUE);
920 	gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(treeview), TRUE);
921 	bpldlg.treeview = treeview;
922 
923 	fixed_tree_view_init(treeview, 0,
924 	                     COL_TYPE_STR, "MRWX ",
925 	                     COL_START_STR, "DD:DDDD ",
926 	                     COL_END_STR, "DD:DDDD ",
927 	                     COL_ENABLED, TRUE,
928 	                     -1);
929 
930 	g_signal_connect(treeview, "row-activated",
931 	                 G_CALLBACK(row_activated), &bpldlg);
932 
933 	/* Enabled/type column */
934 
935 	col = gtk_tree_view_column_new();
936 	gtk_tree_view_column_set_title(col, "Type");
937 	gtk_tree_view_column_set_sort_column_id(col, COL_TYPE_STR);
938 	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
939 
940 	cell = gtk_cell_renderer_toggle_new();
941 	gtk_tree_view_column_pack_start(col, cell, FALSE);
942 	gtk_tree_view_column_set_attributes(col, cell,
943 	                                    "active", COL_ENABLED,
944 	                                    NULL);
945 	g_signal_connect(cell, "toggled",
946 	                 G_CALLBACK(enabled_toggled), &bpldlg);
947 
948 	cell = gtk_cell_renderer_text_new();
949 	gtk_tree_view_column_pack_start(col, cell, TRUE);
950 	gtk_tree_view_column_set_attributes(col, cell,
951 	                                    "text", COL_TYPE_STR,
952 	                                    NULL);
953 
954 	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
955 
956 	/* Start column */
957 
958 	cell = gtk_cell_renderer_text_new();
959 	col = gtk_tree_view_column_new_with_attributes
960 		("Start", cell, "text", COL_START_STR, NULL);
961 	gtk_tree_view_column_set_sort_column_id(col, COL_START);
962 	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
963 
964 	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
965 
966 	/* End column */
967 
968 	cell = gtk_cell_renderer_text_new();
969 	col = gtk_tree_view_column_new_with_attributes
970 		("End", cell, "text", COL_END_STR, NULL);
971 	gtk_tree_view_column_set_sort_column_id(col, COL_END);
972 	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
973 
974 	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
975 
976 	sw = gtk_scrolled_window_new(NULL, NULL);
977 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
978 	                               GTK_POLICY_NEVER,
979 	                               GTK_POLICY_AUTOMATIC);
980 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
981 	                                    GTK_SHADOW_IN);
982 	gtk_container_add(GTK_CONTAINER(sw), treeview);
983 
984 	gtk_box_pack_start(GTK_BOX(hbox), sw, TRUE, TRUE, 0);
985 
986 	/* Buttons */
987 
988 	bbox = gtk_vbutton_box_new();
989 	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START);
990 	gtk_box_set_spacing(GTK_BOX(bbox), 6);
991 
992 	btn = gtk_button_new_from_stock(GTK_STOCK_ADD);
993 	g_signal_connect(btn, "clicked",
994 	                 G_CALLBACK(add_clicked), &bpldlg);
995 	gtk_container_add(GTK_CONTAINER(bbox), btn);
996 
997 	btn = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
998 	g_signal_connect(btn, "clicked",
999 	                 G_CALLBACK(remove_clicked), &bpldlg);
1000 	gtk_container_add(GTK_CONTAINER(bbox), btn);
1001 	bpldlg.remove_btn = btn;
1002 
1003 	btn = gtk_button_new_from_stock(GTK_STOCK_EDIT);
1004 	g_signal_connect(btn, "clicked",
1005 	                 G_CALLBACK(edit_clicked), &bpldlg);
1006 	gtk_container_add(GTK_CONTAINER(bbox), btn);
1007 	bpldlg.edit_btn = btn;
1008 
1009 	btn = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
1010 	g_signal_connect(btn, "clicked",
1011 	                 G_CALLBACK(clear_clicked), &bpldlg);
1012 	gtk_container_add(GTK_CONTAINER(bbox), btn);
1013 	bpldlg.clear_btn = btn;
1014 
1015 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
1016 	g_signal_connect(sel, "changed",
1017 	                 G_CALLBACK(selection_changed), &bpldlg);
1018 
1019 	update_buttons(&bpldlg);
1020 
1021 	gtk_box_pack_start(GTK_BOX(hbox), bbox, FALSE, FALSE, 0);
1022 	gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1023 
1024 	invalid_cb = gtk_check_button_new_with_mnemonic
1025 		("Break on _invalid instructions");
1026 	undoc_cb = gtk_check_button_new_with_mnemonic
1027 		("Break on _undocumented instructions");
1028 
1029 	tilem_calc_emulator_lock(dbg->emu);
1030 	flags = dbg->emu->calc->z80.emuflags;
1031 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(invalid_cb),
1032 	                             (flags & TILEM_Z80_BREAK_INVALID));
1033 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(undoc_cb),
1034 	                             (flags & TILEM_Z80_BREAK_UNDOCUMENTED));
1035 	tilem_calc_emulator_unlock(dbg->emu);
1036 
1037 	gtk_box_pack_start(GTK_BOX(vbox), invalid_cb, FALSE, FALSE, 0);
1038 	gtk_box_pack_start(GTK_BOX(vbox), undoc_cb, FALSE, FALSE, 0);
1039 
1040 	gtk_widget_show_all(vbox);
1041 
1042 	gtk_widget_grab_focus(treeview);
1043 
1044 	vbox2 = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
1045 	gtk_box_pack_start(GTK_BOX(vbox2), vbox, TRUE, TRUE, 0);
1046 
1047 	gtk_dialog_run(GTK_DIALOG(dlg));
1048 
1049 	tilem_calc_emulator_lock(dbg->emu);
1050 	flags = dbg->emu->calc->z80.emuflags;
1051 	flags &= ~(TILEM_Z80_BREAK_INVALID | TILEM_Z80_BREAK_UNDOCUMENTED);
1052 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(invalid_cb)))
1053 		flags |= TILEM_Z80_BREAK_INVALID;
1054 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(undoc_cb)))
1055 		flags |= TILEM_Z80_BREAK_UNDOCUMENTED;
1056 	dbg->emu->calc->z80.emuflags = flags;
1057 	tilem_calc_emulator_unlock(dbg->emu);
1058 
1059 	gtk_widget_destroy(dlg);
1060 }
1061