ti83-sdk/tool/tilem-src/gui/breakpoints.c

1061 lines
30 KiB
C
Raw Normal View History

2023-10-08 16:33:37 +00:00
/*
* TilEm II
*
* Copyright (c) 2011 Benjamin Moody
*
* This program 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/>.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <ticalcs.h>
#include <tilem.h>
#include <tilemdb.h>
#include "gui.h"
#include "disasmview.h"
#include "fixedtreeview.h"
/**************** Add/edit breakpoint dialog ****************/
struct hex_entry {
GtkWidget *addr_label;
GtkWidget *addr_entry;
GtkWidget *page_label;
GtkWidget *page_entry;
};
struct breakpoint_dlg {
TilemDebugger *dbg;
GtkWidget *dlg;
GtkWidget *box;
GtkWidget *type_combo;
GtkWidget *access_cb[3];
GtkWidget *single_rb;
GtkWidget *range_rb;
GtkWidget *access_label;
GtkWidget *address_label;
struct hex_entry start;
struct hex_entry end;
};
static const struct {
char abbrev;
const char *desc;
const char *value_label;
int use_pages;
guint access_mask;
} type_info[] = {
{ 'M', "Memory address (logical)", "Address", 0, 7 },
{ 'M', "Memory address (absolute)", "Address", 1, 7 },
{ 'P', "I/O port", "Port Number", 0, 6 },
{ 'I', "Z80 instruction", "Opcode", 0, 0 }
};
/* Determine currently selected address type */
static guint get_bp_type(struct breakpoint_dlg *bpdlg)
{
int i = gtk_combo_box_get_active(GTK_COMBO_BOX(bpdlg->type_combo));
return (i < 0 ? 0 : i);
}
/* Format address as a string */
static void hex_entry_set_value(struct hex_entry *he, TilemDebugger *dbg,
int type, dword value)
{
const TilemCalc *calc;
char buf[20];
unsigned int page;
g_return_if_fail(dbg->emu != NULL);
g_return_if_fail(dbg->emu->calc != NULL);
calc = dbg->emu->calc;
switch (type) {
case TILEM_DB_BREAK_LOGICAL:
g_snprintf(buf, sizeof(buf), "%04X", value);
break;
case TILEM_DB_BREAK_PHYSICAL:
if (value >= calc->hw.romsize) {
value -= calc->hw.romsize;
page = (value >> 14) + calc->hw.rampagemask;
}
else {
page = (value >> 14);
}
g_snprintf(buf, sizeof(buf), "%02X", page);
gtk_entry_set_text(GTK_ENTRY(he->page_entry), buf);
g_snprintf(buf, sizeof(buf), "%04X", value & 0x3fff);
break;
case TILEM_DB_BREAK_PORT:
g_snprintf(buf, sizeof(buf), "%02X", value);
break;
case TILEM_DB_BREAK_OPCODE:
if (value < 0x100)
g_snprintf(buf, sizeof(buf), "%02X", value);
else if (value < 0x10000)
g_snprintf(buf, sizeof(buf), "%04X", value);
else if (value < 0x1000000)
g_snprintf(buf, sizeof(buf), "%06X", value);
else
g_snprintf(buf, sizeof(buf), "%08X", value);
break;
default:
g_return_if_reached();
}
gtk_entry_set_text(GTK_ENTRY(he->addr_entry), buf);
}
/* Parse contents of entry */
static gboolean parse_num(TilemDebugger *dbg, const char *s, dword *a)
{
const char *n;
char *e;
g_return_val_if_fail(s != NULL, FALSE);
if (s[0] == '$')
n = s + 1;
else if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
n = s + 2;
else
n = s;
*a = strtol(n, &e, 16);
if (e != n) {
if (*e == 'h' || *e == 'H')
e++;
if (*e == 0)
return TRUE;
}
if (dbg->dasm && tilem_disasm_get_label(dbg->dasm, s, a))
return TRUE;
return FALSE;
}
/* Parse user input from hex entry */
static gboolean hex_entry_parse_value(struct hex_entry *he, TilemDebugger *dbg,
int type, dword *value)
{
const TilemCalc *calc = dbg->emu->calc;
dword page;
const char *s;
g_return_val_if_fail(calc != NULL, 0);
s = gtk_entry_get_text(GTK_ENTRY(he->addr_entry));
if (!parse_num(dbg, s, value))
return FALSE;
if (type != TILEM_DB_BREAK_PHYSICAL)
return TRUE;
s = gtk_entry_get_text(GTK_ENTRY(he->page_entry));
if (!parse_num(dbg, s, &page))
return FALSE;
*value &= 0x3fff;
if (page >= calc->hw.rampagemask) {
*value += ((page - calc->hw.rampagemask) << 14);
*value %= calc->hw.ramsize;
*value += calc->hw.romsize;
}
else {
*value += (page << 14);
*value %= calc->hw.romsize;
}
return TRUE;
}
/* Parse input fields and check if they make sense */
static gboolean parse_input(struct breakpoint_dlg *bpdlg,
TilemDebugBreakpoint *bp)
{
int i;
dword addr0, addr1;
bp->mask = bp->start = bp->end = 0xffffffff;
bp->type = get_bp_type(bpdlg);
bp->mode = 0;
for (i = 0; i < 3; i++)
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bpdlg->access_cb[i])))
bp->mode += (1 << i);
bp->mode &= type_info[bp->type].access_mask;
if (bp->type == TILEM_DB_BREAK_OPCODE)
bp->mode = TILEM_DB_BREAK_EXEC;
else if (bp->mode == 0)
return FALSE;
if (!hex_entry_parse_value(&bpdlg->start, bpdlg->dbg,
bp->type, &addr0))
return FALSE;
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bpdlg->range_rb))) {
if (!hex_entry_parse_value(&bpdlg->end, bpdlg->dbg,
bp->type, &addr1))
return FALSE;
}
else {
addr1 = addr0;
}
if (bp->type == TILEM_DB_BREAK_LOGICAL)
bp->mask = 0xffff;
else if (bp->type == TILEM_DB_BREAK_PORT)
bp->mask = 0xff;
else
bp->mask = 0xffffffff;
bp->start = addr0 & bp->mask;
bp->end = addr1 & bp->mask;
if (bp->end < bp->start)
return FALSE;
return TRUE;
}
/* Check if input fields are valid, and enable/disable OK response as
appropriate */
static void validate(struct breakpoint_dlg *bpdlg)
{
TilemDebugBreakpoint tmpbp;
if (parse_input(bpdlg, &tmpbp))
gtk_dialog_set_response_sensitive(GTK_DIALOG(bpdlg->dlg),
GTK_RESPONSE_OK, TRUE);
else
gtk_dialog_set_response_sensitive(GTK_DIALOG(bpdlg->dlg),
GTK_RESPONSE_OK, FALSE);
}
/* Enable/disable check buttons for access mode */
static void set_access_mask(struct breakpoint_dlg *bpdlg, guint mask)
{
int i;
if (mask)
gtk_widget_show(bpdlg->access_label);
else
gtk_widget_hide(bpdlg->access_label);
for (i = 0; i < 3; i++) {
if (mask & (1 << i))
gtk_widget_show(bpdlg->access_cb[i]);
else
gtk_widget_hide(bpdlg->access_cb[i]);
}
}
/* Combo box changed */
static void addr_type_changed(G_GNUC_UNUSED GtkComboBox *combo, gpointer data)
{
struct breakpoint_dlg *bpdlg = data;
int type = get_bp_type(bpdlg);
gboolean range = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bpdlg->range_rb));
char *s;
s = g_strdup_printf("<b>%s</b>", type_info[type].value_label);
gtk_label_set_markup(GTK_LABEL(bpdlg->address_label), s);
g_free(s);
set_access_mask(bpdlg, type_info[type].access_mask);
if (type_info[type].use_pages) {
gtk_widget_show(bpdlg->start.page_label);
gtk_widget_show(bpdlg->start.page_entry);
}
else {
gtk_widget_hide(bpdlg->start.page_label);
gtk_widget_hide(bpdlg->start.page_entry);
}
if (range) {
gtk_label_set_text_with_mnemonic(GTK_LABEL(bpdlg->start.addr_label),
"_Start:");
gtk_widget_show(bpdlg->end.addr_label);
gtk_widget_show(bpdlg->end.addr_entry);
}
else {
gtk_label_set_text_with_mnemonic(GTK_LABEL(bpdlg->start.addr_label),
"_Value:");
gtk_widget_hide(bpdlg->end.addr_label);
gtk_widget_hide(bpdlg->end.addr_entry);
}
if (type_info[type].use_pages && range) {
gtk_widget_show(bpdlg->end.page_label);
gtk_widget_show(bpdlg->end.page_entry);
}
else {
gtk_widget_hide(bpdlg->end.page_label);
gtk_widget_hide(bpdlg->end.page_entry);
}
validate(bpdlg);
}
/* Access mode changed */
static void access_changed(G_GNUC_UNUSED GtkToggleButton *tb, gpointer data)
{
struct breakpoint_dlg *bpdlg = data;
validate(bpdlg);
}
/* Single/range mode changed */
static void range_mode_changed(G_GNUC_UNUSED GtkToggleButton *tb, gpointer data)
{
struct breakpoint_dlg *bpdlg = data;
addr_type_changed(NULL, bpdlg);
}
/* Text of entry changed */
static void entry_edited(G_GNUC_UNUSED GtkEntry *entry,
gpointer data)
{
struct breakpoint_dlg *bpdlg = data;
validate(bpdlg);
}
/* Key presssed in entry */
static gboolean entry_key_event(G_GNUC_UNUSED GtkWidget *entry,
GdkEventKey *ev, gpointer data)
{
struct breakpoint_dlg *bpdlg = data;
TilemDebugBreakpoint tmpbp;
if (ev->state & GDK_MODIFIER_MASK)
return FALSE;
if (ev->keyval != GDK_Return
&& ev->keyval != GDK_KP_Enter
&& ev->keyval != GDK_ISO_Enter)
return FALSE;
if (parse_input(bpdlg, &tmpbp))
gtk_dialog_response(GTK_DIALOG(bpdlg->dlg), GTK_RESPONSE_OK);
else
gtk_widget_child_focus(bpdlg->box, GTK_DIR_TAB_FORWARD);
return TRUE;
}
static void init_hex_entry(struct breakpoint_dlg *bpdlg,
struct hex_entry *he, const char *label,
GtkTable *tbl, int ypos)
{
GtkWidget *align, *lbl;
he->addr_label = lbl = gtk_label_new_with_mnemonic(label);
gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
align = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 6);
gtk_container_add(GTK_CONTAINER(align), lbl);
gtk_table_attach(tbl, align, 0, 1, ypos, ypos + 1,
GTK_FILL, GTK_FILL, 0, 0);
he->addr_entry = gtk_entry_new();
gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), he->addr_entry);
gtk_table_attach(tbl, he->addr_entry, 1, 2, ypos, ypos + 1,
GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
g_signal_connect(he->addr_entry, "changed",
G_CALLBACK(entry_edited), bpdlg);
g_signal_connect(he->addr_entry, "key-press-event",
G_CALLBACK(entry_key_event), bpdlg);
he->page_label = lbl = gtk_label_new("Page:");
gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
gtk_table_attach(tbl, lbl, 2, 3, ypos, ypos + 1,
GTK_FILL, GTK_FILL, 6, 0);
he->page_entry = gtk_entry_new();
gtk_entry_set_width_chars(GTK_ENTRY(he->page_entry), 5);
gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), he->page_entry);
gtk_table_attach(tbl, he->page_entry, 3, 4, ypos, ypos + 1,
GTK_FILL, GTK_FILL, 0, 0);
g_signal_connect(he->page_entry, "changed",
G_CALLBACK(entry_edited), bpdlg);
g_signal_connect(he->page_entry, "key-press-event",
G_CALLBACK(entry_key_event), bpdlg);
}
static gboolean edit_breakpoint(TilemDebugger *dbg,
GtkWindow *parent_window,
const char *title,
TilemDebugBreakpoint *bp,
gboolean edit_existing)
{
GtkWidget *dlg, *vbox, *frame, *tbl, *hbox, *lbl, *combo, *cb, *rb;
struct breakpoint_dlg bpdlg;
gsize i;
g_return_val_if_fail(bp != NULL, FALSE);
g_return_val_if_fail(dbg != NULL, FALSE);
g_return_val_if_fail(dbg->emu != NULL, FALSE);
g_return_val_if_fail(dbg->emu->calc != NULL, FALSE);
bpdlg.dbg = dbg;
dlg = gtk_dialog_new_with_buttons(title, parent_window,
GTK_DIALOG_MODAL,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OK, GTK_RESPONSE_OK,
NULL);
gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
GTK_RESPONSE_OK,
GTK_RESPONSE_CANCEL,
-1);
gtk_dialog_set_default_response(GTK_DIALOG(dlg),
GTK_RESPONSE_OK);
bpdlg.dlg = dlg;
bpdlg.box = gtk_vbox_new(FALSE, 6);
gtk_container_set_border_width(GTK_CONTAINER(bpdlg.box), 6);
tbl = gtk_table_new(2, 2, FALSE);
gtk_table_set_row_spacings(GTK_TABLE(tbl), 6);
gtk_table_set_col_spacings(GTK_TABLE(tbl), 6);
/* Breakpoint type */
lbl = gtk_label_new_with_mnemonic("Breakpoint _type:");
gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
gtk_table_attach(GTK_TABLE(tbl), lbl, 0, 1, 0, 1,
GTK_FILL, GTK_FILL, 0, 0);
combo = gtk_combo_box_new_text();
for (i = 0; i < G_N_ELEMENTS(type_info); i++)
gtk_combo_box_append_text(GTK_COMBO_BOX(combo), type_info[i].desc);
bpdlg.type_combo = combo;
gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), combo);
gtk_table_attach(GTK_TABLE(tbl), combo, 1, 2, 0, 1,
GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
gtk_combo_box_set_active(GTK_COMBO_BOX(combo), bp->type);
/* Access mode */
bpdlg.access_label = lbl = gtk_label_new("Break when:");
gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
gtk_table_attach(GTK_TABLE(tbl), lbl, 0, 1, 1, 2,
GTK_FILL, GTK_FILL, 0, 0);
hbox = gtk_hbox_new(FALSE, 6);
cb = gtk_check_button_new_with_mnemonic("_Reading");
gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
bpdlg.access_cb[2] = cb;
cb = gtk_check_button_new_with_mnemonic("_Writing");
gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
bpdlg.access_cb[1] = cb;
cb = gtk_check_button_new_with_mnemonic("E_xecuting");
gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
bpdlg.access_cb[0] = cb;
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bpdlg.access_cb[0]),
bp->mode & 1);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bpdlg.access_cb[1]),
bp->mode & 2);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bpdlg.access_cb[2]),
bp->mode & 4);
gtk_table_attach(GTK_TABLE(tbl), hbox, 1, 2, 1, 2,
GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
frame = new_frame("Breakpoint Condition", tbl);
gtk_box_pack_start(GTK_BOX(bpdlg.box), frame, FALSE, FALSE, 0);
/* Addresses */
tbl = gtk_table_new(3, 4, FALSE);
gtk_table_set_row_spacings(GTK_TABLE(tbl), 6);
hbox = gtk_hbox_new(FALSE, 6);
rb = gtk_radio_button_new_with_mnemonic(NULL, "Si_ngle");
gtk_box_pack_start(GTK_BOX(hbox), rb, FALSE, FALSE, 0);
bpdlg.single_rb = rb;
rb = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(rb), "R_ange");
gtk_box_pack_start(GTK_BOX(hbox), rb, FALSE, FALSE, 0);
bpdlg.range_rb = rb;
if (edit_existing && bp->end != bp->start)
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), TRUE);
gtk_table_attach(GTK_TABLE(tbl), hbox, 0, 2, 0, 1,
GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
init_hex_entry(&bpdlg, &bpdlg.start, "S_tart:", GTK_TABLE(tbl), 1);
init_hex_entry(&bpdlg, &bpdlg.end, "_End:", GTK_TABLE(tbl), 2);
frame = new_frame("Address", tbl);
bpdlg.address_label = gtk_frame_get_label_widget(GTK_FRAME(frame));
gtk_box_pack_start(GTK_BOX(bpdlg.box), frame, FALSE, FALSE, 0);
gtk_widget_show_all(bpdlg.box);
vbox = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
gtk_box_pack_start(GTK_BOX(vbox), bpdlg.box, FALSE, FALSE, 0);
if (edit_existing) {
hex_entry_set_value(&bpdlg.start, dbg, bp->type, bp->start);
hex_entry_set_value(&bpdlg.end, dbg, bp->type, bp->end);
}
g_signal_connect(combo, "changed",
G_CALLBACK(addr_type_changed), &bpdlg);
g_signal_connect(bpdlg.access_cb[0], "toggled",
G_CALLBACK(access_changed), &bpdlg);
g_signal_connect(bpdlg.access_cb[1], "toggled",
G_CALLBACK(access_changed), &bpdlg);
g_signal_connect(bpdlg.access_cb[2], "toggled",
G_CALLBACK(access_changed), &bpdlg);
g_signal_connect(bpdlg.single_rb, "toggled",
G_CALLBACK(range_mode_changed), &bpdlg);
addr_type_changed(NULL, &bpdlg);
gtk_widget_grab_focus(bpdlg.start.addr_entry);
do {
if (gtk_dialog_run(GTK_DIALOG(dlg)) != GTK_RESPONSE_OK) {
gtk_widget_destroy(dlg);
return FALSE;
}
} while (!parse_input(&bpdlg, bp));
gtk_widget_destroy(dlg);
return TRUE;
}
/**************** Breakpoint list dialog ****************/
enum {
COL_BP,
COL_START,
COL_END,
COL_TYPE_STR,
COL_START_STR,
COL_END_STR,
COL_ENABLED,
N_COLUMNS
};
struct bplist_dlg {
TilemDebugger *dbg;
GtkWidget *dlg;
GtkListStore *store;
GtkWidget *treeview;
GtkWidget *remove_btn;
GtkWidget *edit_btn;
GtkWidget *clear_btn;
};
/* Convert address into a displayable string */
static void format_address(TilemDebugger *dbg,
char *buf, int bufsize,
int type, dword value)
{
const TilemCalc *calc;
unsigned int page;
g_return_if_fail(dbg->emu != NULL);
g_return_if_fail(dbg->emu->calc != NULL);
calc = dbg->emu->calc;
switch (type) {
case TILEM_DB_BREAK_LOGICAL:
g_snprintf(buf, bufsize, "%04X", value);
break;
case TILEM_DB_BREAK_PHYSICAL:
if (value >= calc->hw.romsize) {
value -= calc->hw.romsize;
page = (value >> 14) + calc->hw.rampagemask;
}
else {
page = (value >> 14);
}
g_snprintf(buf, bufsize, "%02X:%04X", page, value & 0x3fff);
break;
case TILEM_DB_BREAK_PORT:
g_snprintf(buf, bufsize, "%02X", value);
break;
case TILEM_DB_BREAK_OPCODE:
if (value < 0x100)
g_snprintf(buf, bufsize, "%02X", value);
else if (value < 0x10000)
g_snprintf(buf, bufsize, "%04X", value);
else if (value < 0x1000000)
g_snprintf(buf, bufsize, "%06X", value);
else
g_snprintf(buf, bufsize, "%08X", value);
break;
default:
g_return_if_reached();
}
}
/* Store breakpoint properties in tree model */
static void set_iter_from_bp(struct bplist_dlg *bpldlg, GtkTreeIter *iter,
const TilemDebugBreakpoint *bp)
{
char tbuf[5], sbuf[10], ebuf[10];
int i, j;
g_return_if_fail(bp != NULL);
tbuf[0] = type_info[bp->type].abbrev;
j = 1;
for (i = 0; i < 3; i++)
if (bp->mode & (4 >> i))
tbuf[j++] = "RWX"[i];
tbuf[j] = 0;
format_address(bpldlg->dbg, sbuf, sizeof(sbuf), bp->type, bp->start);
format_address(bpldlg->dbg, ebuf, sizeof(ebuf), bp->type, bp->end);
gtk_list_store_set(bpldlg->store, iter,
COL_BP, bp,
COL_START, bp->start,
COL_END, bp->end,
COL_TYPE_STR, tbuf,
COL_START_STR, sbuf,
COL_END_STR, ebuf,
COL_ENABLED, !bp->disabled,
-1);
}
/* Get breakpoint pointer for the given tree model row */
static TilemDebugBreakpoint *get_iter_bp(struct bplist_dlg *bpldlg,
GtkTreeIter *iter)
{
gpointer ptr;
gtk_tree_model_get(GTK_TREE_MODEL(bpldlg->store), iter,
COL_BP, &ptr, -1);
return (TilemDebugBreakpoint *) ptr;
}
/* Set buttons sensitive or insensitive depending on whether any BPs
are selected */
static void update_buttons(struct bplist_dlg *bpldlg)
{
GtkTreeSelection *sel;
gboolean any_sel;
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(bpldlg->treeview));
any_sel = gtk_tree_selection_get_selected(sel, NULL, NULL);
gtk_widget_set_sensitive(bpldlg->remove_btn, any_sel);
gtk_widget_set_sensitive(bpldlg->edit_btn, any_sel);
gtk_widget_set_sensitive(bpldlg->clear_btn,
bpldlg->dbg->breakpoints != NULL);
}
/* "Add breakpoint" button clicked */
static void add_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
{
struct bplist_dlg *bpldlg = data;
TilemDebugBreakpoint tmpbp, *newbp;
TilemDebugger *dbg = bpldlg->dbg;
GtkTreeIter iter;
memset(&tmpbp, 0, sizeof(tmpbp));
tmpbp.type = dbg->last_bp_type;
tmpbp.mode = dbg->last_bp_mode;
if (!edit_breakpoint(dbg, GTK_WINDOW(bpldlg->dlg),
"Add Breakpoint", &tmpbp, FALSE))
return;
dbg->last_bp_type = tmpbp.type;
dbg->last_bp_mode = tmpbp.mode;
newbp = tilem_debugger_add_breakpoint(dbg, &tmpbp);
gtk_list_store_append(bpldlg->store, &iter);
set_iter_from_bp(bpldlg, &iter, newbp);
update_buttons(bpldlg);
}
/* "Remove breakpoint" button clicked */
static void remove_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
{
struct bplist_dlg *bpldlg = data;
GtkTreeSelection *sel;
GtkTreeIter iter;
TilemDebugBreakpoint *bp;
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(bpldlg->treeview));
if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
return;
bp = get_iter_bp(bpldlg, &iter);
g_return_if_fail(bp != NULL);
gtk_list_store_remove(bpldlg->store, &iter);
tilem_debugger_remove_breakpoint(bpldlg->dbg, bp);
update_buttons(bpldlg);
}
/* Edit an existing breakpoint */
static void edit_row(struct bplist_dlg *bpldlg, GtkTreeIter *iter)
{
TilemDebugBreakpoint *bp, tmpbp;
bp = get_iter_bp(bpldlg, iter);
g_return_if_fail(bp != NULL);
tmpbp = *bp;
if (!edit_breakpoint(bpldlg->dbg, GTK_WINDOW(bpldlg->dlg),
"Edit Breakpoint", &tmpbp, TRUE))
return;
tmpbp.disabled = 0;
tilem_debugger_change_breakpoint(bpldlg->dbg, bp, &tmpbp);
set_iter_from_bp(bpldlg, iter, bp);
update_buttons(bpldlg);
}
/* "Edit breakpoint" button clicked */
static void edit_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
{
struct bplist_dlg *bpldlg = data;
GtkTreeSelection *sel;
GtkTreeIter iter;
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(bpldlg->treeview));
if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
return;
edit_row(bpldlg, &iter);
}
/* "Clear breakpoints" button clicked */
static void clear_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
{
struct bplist_dlg *bpldlg = data;
GtkWidget *dlg;
TilemDebugBreakpoint *bp;
dlg = gtk_message_dialog_new(GTK_WINDOW(bpldlg->dlg),
GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,
GTK_BUTTONS_NONE,
"Clear all breakpoints?");
gtk_message_dialog_format_secondary_markup
(GTK_MESSAGE_DIALOG(dlg),
"All existing breakpoints will be deleted and"
" cannot be restored.");
gtk_dialog_add_buttons(GTK_DIALOG(dlg),
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_CLEAR, GTK_RESPONSE_ACCEPT,
NULL);
gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
GTK_RESPONSE_ACCEPT,
GTK_RESPONSE_CANCEL,
-1);
if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_ACCEPT) {
while (bpldlg->dbg->breakpoints) {
bp = bpldlg->dbg->breakpoints->data;
tilem_debugger_remove_breakpoint(bpldlg->dbg, bp);
}
gtk_list_store_clear(bpldlg->store);
update_buttons(bpldlg);
}
gtk_widget_destroy(dlg);
}
/* Row activated (double-clicked, usually) */
static void row_activated(G_GNUC_UNUSED GtkTreeView *treeview,
GtkTreePath *path,
G_GNUC_UNUSED GtkTreeViewColumn *col,
gpointer data)
{
struct bplist_dlg *bpldlg = data;
GtkTreeIter iter;
if (gtk_tree_model_get_iter(GTK_TREE_MODEL(bpldlg->store),
&iter, path))
edit_row(bpldlg, &iter);
}
/* Toggle button clicked for a breakpoint */
static void enabled_toggled(G_GNUC_UNUSED GtkCellRendererToggle *cell,
gchar *pathstr, gpointer data)
{
struct bplist_dlg *bpldlg = data;
GtkTreePath *path;
GtkTreeIter iter;
TilemDebugBreakpoint *bp, tmpbp;
path = gtk_tree_path_new_from_string(pathstr);
if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(bpldlg->store),
&iter, path)) {
gtk_tree_path_free(path);
return;
}
gtk_tree_path_free(path);
bp = get_iter_bp(bpldlg, &iter);
g_return_if_fail(bp != NULL);
tmpbp = *bp;
tmpbp.disabled = !tmpbp.disabled;
tilem_debugger_change_breakpoint(bpldlg->dbg, bp, &tmpbp);
set_iter_from_bp(bpldlg, &iter, bp);
}
/* Selection changed */
static void selection_changed(G_GNUC_UNUSED GtkTreeSelection *sel,
gpointer data)
{
struct bplist_dlg *bpldlg = data;
update_buttons(bpldlg);
}
/* Show a dialog letting the user add, remove, and edit breakpoints */
void tilem_debugger_edit_breakpoints(TilemDebugger *dbg)
{
struct bplist_dlg bpldlg;
GtkWidget *dlg, *hbox, *treeview, *sw, *bbox, *btn, *vbox, *vbox2,
*invalid_cb, *undoc_cb;
GtkListStore *store;
GtkTreeViewColumn *col;
GtkCellRenderer *cell;
GtkTreeIter iter;
GSList *l;
GtkTreeSelection *sel;
unsigned int flags;
g_return_if_fail(dbg != NULL);
g_return_if_fail(dbg->emu != NULL);
g_return_if_fail(dbg->emu->calc != NULL);
bpldlg.dbg = dbg;
dlg = gtk_dialog_new_with_buttons("Breakpoints",
GTK_WINDOW(dbg->window),
GTK_DIALOG_MODAL,
GTK_STOCK_CLOSE,
GTK_RESPONSE_ACCEPT,
NULL);
gtk_window_set_default_size(GTK_WINDOW(dlg), -1, 300);
store = gtk_list_store_new(N_COLUMNS,
G_TYPE_POINTER,
G_TYPE_INT,
G_TYPE_INT,
G_TYPE_STRING,
G_TYPE_STRING,
G_TYPE_STRING,
G_TYPE_BOOLEAN);
bpldlg.dlg = dlg;
bpldlg.store = store;
vbox = gtk_vbox_new(FALSE, 6);
gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
hbox = gtk_hbox_new(FALSE, 6);
for (l = dbg->breakpoints; l; l = l->next) {
gtk_list_store_append(store, &iter);
set_iter_from_bp(&bpldlg, &iter, l->data);
}
treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview), TRUE);
gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(treeview), TRUE);
bpldlg.treeview = treeview;
fixed_tree_view_init(treeview, 0,
COL_TYPE_STR, "MRWX ",
COL_START_STR, "DD:DDDD ",
COL_END_STR, "DD:DDDD ",
COL_ENABLED, TRUE,
-1);
g_signal_connect(treeview, "row-activated",
G_CALLBACK(row_activated), &bpldlg);
/* Enabled/type column */
col = gtk_tree_view_column_new();
gtk_tree_view_column_set_title(col, "Type");
gtk_tree_view_column_set_sort_column_id(col, COL_TYPE_STR);
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
cell = gtk_cell_renderer_toggle_new();
gtk_tree_view_column_pack_start(col, cell, FALSE);
gtk_tree_view_column_set_attributes(col, cell,
"active", COL_ENABLED,
NULL);
g_signal_connect(cell, "toggled",
G_CALLBACK(enabled_toggled), &bpldlg);
cell = gtk_cell_renderer_text_new();
gtk_tree_view_column_pack_start(col, cell, TRUE);
gtk_tree_view_column_set_attributes(col, cell,
"text", COL_TYPE_STR,
NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
/* Start column */
cell = gtk_cell_renderer_text_new();
col = gtk_tree_view_column_new_with_attributes
("Start", cell, "text", COL_START_STR, NULL);
gtk_tree_view_column_set_sort_column_id(col, COL_START);
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
/* End column */
cell = gtk_cell_renderer_text_new();
col = gtk_tree_view_column_new_with_attributes
("End", cell, "text", COL_END_STR, NULL);
gtk_tree_view_column_set_sort_column_id(col, COL_END);
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
sw = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
GTK_POLICY_NEVER,
GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
GTK_SHADOW_IN);
gtk_container_add(GTK_CONTAINER(sw), treeview);
gtk_box_pack_start(GTK_BOX(hbox), sw, TRUE, TRUE, 0);
/* Buttons */
bbox = gtk_vbutton_box_new();
gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START);
gtk_box_set_spacing(GTK_BOX(bbox), 6);
btn = gtk_button_new_from_stock(GTK_STOCK_ADD);
g_signal_connect(btn, "clicked",
G_CALLBACK(add_clicked), &bpldlg);
gtk_container_add(GTK_CONTAINER(bbox), btn);
btn = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
g_signal_connect(btn, "clicked",
G_CALLBACK(remove_clicked), &bpldlg);
gtk_container_add(GTK_CONTAINER(bbox), btn);
bpldlg.remove_btn = btn;
btn = gtk_button_new_from_stock(GTK_STOCK_EDIT);
g_signal_connect(btn, "clicked",
G_CALLBACK(edit_clicked), &bpldlg);
gtk_container_add(GTK_CONTAINER(bbox), btn);
bpldlg.edit_btn = btn;
btn = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
g_signal_connect(btn, "clicked",
G_CALLBACK(clear_clicked), &bpldlg);
gtk_container_add(GTK_CONTAINER(bbox), btn);
bpldlg.clear_btn = btn;
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
g_signal_connect(sel, "changed",
G_CALLBACK(selection_changed), &bpldlg);
update_buttons(&bpldlg);
gtk_box_pack_start(GTK_BOX(hbox), bbox, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
invalid_cb = gtk_check_button_new_with_mnemonic
("Break on _invalid instructions");
undoc_cb = gtk_check_button_new_with_mnemonic
("Break on _undocumented instructions");
tilem_calc_emulator_lock(dbg->emu);
flags = dbg->emu->calc->z80.emuflags;
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(invalid_cb),
(flags & TILEM_Z80_BREAK_INVALID));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(undoc_cb),
(flags & TILEM_Z80_BREAK_UNDOCUMENTED));
tilem_calc_emulator_unlock(dbg->emu);
gtk_box_pack_start(GTK_BOX(vbox), invalid_cb, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), undoc_cb, FALSE, FALSE, 0);
gtk_widget_show_all(vbox);
gtk_widget_grab_focus(treeview);
vbox2 = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
gtk_box_pack_start(GTK_BOX(vbox2), vbox, TRUE, TRUE, 0);
gtk_dialog_run(GTK_DIALOG(dlg));
tilem_calc_emulator_lock(dbg->emu);
flags = dbg->emu->calc->z80.emuflags;
flags &= ~(TILEM_Z80_BREAK_INVALID | TILEM_Z80_BREAK_UNDOCUMENTED);
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(invalid_cb)))
flags |= TILEM_Z80_BREAK_INVALID;
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(undoc_cb)))
flags |= TILEM_Z80_BREAK_UNDOCUMENTED;
dbg->emu->calc->z80.emuflags = flags;
tilem_calc_emulator_unlock(dbg->emu);
gtk_widget_destroy(dlg);
}