/* * 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 . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #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("%s", 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); }