1 /*
2  * TilEm II
3  *
4  * Copyright (c) 2011-2012 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 <string.h>
26 #include <gtk/gtk.h>
27 #include <ticalcs.h>
28 #include <tilem.h>
29 #include <tilemdb.h>
30 
31 #include "gui.h"
32 #include "disasmview.h"
33 
34 G_DEFINE_TYPE(TilemDisasmView, tilem_disasm_view, GTK_TYPE_TREE_VIEW);
35 
36 /*
37   This is a HORRIBLE kludge.  Don't ever do anything like this.  ;)
38 
39   We want a widget that has the look and feel of a GtkTreeView.  But
40   our "data model" isn't consistent with a GtkTreeModel, since it
41   changes depending on where we are.
42 
43   This widget keeps track of how high each row will be once rendered,
44   and uses that to construct a GtkListStore with the appropriate
45   number of rows to fill the window.  We also override the move-cursor
46   signal so that we can handle the boundaries.
47  */
48 
49 /* Model columns */
50 enum {
51 	COL_POSITION,
52 	COL_ADDRESS,
53 	COL_MNEMONIC,
54 	COL_ARGUMENTS,
55 	COL_SHOW_MNEMONIC,
56 	COL_ICON,
57 	NUM_COLUMNS
58 };
59 
60 static GtkTreeViewClass *parent_class;
61 
62 /* We define two "positions" for each actual address; the second is
63    used if there's a label to be displayed at that address. */
64 
65 #define POS_TO_ADDR(x) ((x) >> 1)
66 #define ADDR_TO_POS(x) ((x) << 1)
67 
68 /* Disassembly */
69 
70 /* Convert physical to logical address; if address is not currently
71    mapped, use the bank-A address */
default_ptol(TilemCalc * calc,dword addr)72 static dword default_ptol(TilemCalc *calc, dword addr)
73 {
74 	dword addr_l;
75 
76 	g_return_val_if_fail(calc != NULL, 0);
77 
78 	addr_l = (*calc->hw.mem_ptol)(calc, addr);
79 	if (addr_l == 0xffffffff)
80 		addr_l = (addr & 0x3fff) | 0x4000;
81 
82 	return addr_l;
83 }
84 
85 /* Check for a label at the given address (physical or logical
86    depending on the mode of the DisasmView) */
get_label(TilemDisasmView * dv,TilemCalc * calc,dword addr)87 static const char *get_label(TilemDisasmView *dv, TilemCalc *calc,
88                              dword addr)
89 {
90 	g_return_val_if_fail(calc != NULL, NULL);
91 	g_return_val_if_fail(dv->dbg->dasm != NULL, NULL);
92 
93 	if (!dv->use_logical)
94 		addr = default_ptol(calc, addr);
95 
96 	return tilem_disasm_get_label_at_address(dv->dbg->dasm, addr);
97 }
98 
99 /* Disassemble a line */
disassemble(TilemDisasmView * dv,TilemCalc * calc,dword pos,dword * nextpos,char ** mnemonic,char ** args)100 static void disassemble(TilemDisasmView *dv, TilemCalc *calc, dword pos,
101                         dword *nextpos, char **mnemonic, char **args)
102 {
103 	dword addr = POS_TO_ADDR(pos);
104 	const char *lbl;
105 	char buf[500], *p;
106 
107 	g_return_if_fail(calc != NULL);
108 	g_return_if_fail(dv->dbg->dasm != NULL);
109 
110 	if (!(pos & 1) && (lbl = get_label(dv, calc, addr))) {
111 		if (mnemonic) {
112 			*mnemonic = NULL;
113 			*args = g_strdup_printf("%s:", lbl);
114 		}
115 
116 		if (nextpos)
117 			*nextpos = pos + 1;
118 	}
119 	else if (mnemonic) {
120 		tilem_disasm_disassemble(dv->dbg->dasm, calc,
121 		                         !dv->use_logical, addr,
122 		                         &addr, buf, sizeof(buf));
123 
124 		p = strchr(buf, '\t');
125 		if (p) {
126 			*mnemonic = g_strndup(buf, p - buf);
127 			*args = g_strdup(p + 1);
128 		}
129 		else {
130 			*mnemonic = g_strdup(buf);
131 			*args = NULL;
132 		}
133 
134 		if (nextpos)
135 			*nextpos = ADDR_TO_POS(addr);
136 	}
137 	else {
138 		tilem_disasm_disassemble(dv->dbg->dasm, calc,
139 		                         !dv->use_logical, addr,
140 		                         &addr, NULL, 0);
141 		if (nextpos)
142 			*nextpos = ADDR_TO_POS(addr);
143 	}
144 }
145 
146 /* Get "next" position */
get_next_pos(TilemDisasmView * dv,TilemCalc * calc,dword pos)147 static dword get_next_pos(TilemDisasmView *dv, TilemCalc *calc, dword pos)
148 {
149 	disassemble(dv, calc, pos, &pos, NULL, NULL);
150 	return pos;
151 }
152 
153 /* Get "previous" position */
get_prev_pos(TilemDisasmView * dv,TilemCalc * calc,dword pos)154 static dword get_prev_pos(TilemDisasmView *dv, TilemCalc *calc, dword pos)
155 {
156 	dword addr = POS_TO_ADDR(pos);
157 
158 	g_return_val_if_fail(calc != NULL, 0);
159 
160 	if (pos & 1) {
161 		return pos - 1;
162 	}
163 	else {
164 		if (addr > 0)
165 			addr--;
166 		else if (dv->use_logical)
167 			addr = 0xffff;
168 		else
169 			addr = (calc->hw.romsize + calc->hw.ramsize - 1);
170 
171 		if (get_label(dv, calc, addr))
172 			return ADDR_TO_POS(addr) + 1;
173 		else
174 			return ADDR_TO_POS(addr);
175 	}
176 }
177 
178 /* Convert physical to logical position */
pos_ptol(TilemCalc * calc,dword pos)179 static dword pos_ptol(TilemCalc *calc, dword pos)
180 {
181 	dword addr;
182 
183 	g_return_val_if_fail(calc != NULL, 0);
184 
185 	if (pos == (dword) -1)
186 		return pos;
187 
188 	addr = default_ptol(calc, POS_TO_ADDR(pos));
189 	return ADDR_TO_POS(addr) + (pos & 1);
190 }
191 
192 /* Convert logical to physical position */
pos_ltop(TilemCalc * calc,dword pos)193 static dword pos_ltop(TilemCalc *calc, dword pos)
194 {
195 	dword addr;
196 
197 	g_return_val_if_fail(calc != NULL, 0);
198 
199 	if (pos == (dword) -1)
200 		return pos;
201 
202 	addr = (*calc->hw.mem_ltop)(calc, POS_TO_ADDR(pos));
203 	return ADDR_TO_POS(addr) + (pos & 1);
204 }
205 
206 /* Icons */
207 
get_icon(TilemDisasmView * dv,gboolean ispc,gboolean isbp)208 static GdkPixbuf *get_icon(TilemDisasmView *dv, gboolean ispc, gboolean isbp)
209 {
210 	const char *name;
211 
212 	if (ispc && isbp)
213 		name = "tilem-disasm-break-pc";
214 	else if (isbp)
215 		name = "tilem-disasm-break";
216 	else if (ispc)
217 		name = "tilem-disasm-pc";
218 	else
219 		return NULL;
220 
221 	return gtk_widget_render_icon(GTK_WIDGET(dv), name,
222 	                              GTK_ICON_SIZE_MENU, NULL);
223 }
224 
225 /* List model management */
226 
227 /* Create a new list store for disassembly */
new_dasm_model()228 static GtkTreeModel * new_dasm_model()
229 {
230 	GtkListStore *store;
231 
232 	g_assert(NUM_COLUMNS == 6);
233 	store = gtk_list_store_new(6,
234 	                           G_TYPE_INT,
235 	                           G_TYPE_STRING,
236 	                           G_TYPE_STRING,
237 	                           G_TYPE_STRING,
238 	                           G_TYPE_BOOLEAN,
239 	                           GDK_TYPE_PIXBUF);
240 
241 	return GTK_TREE_MODEL(store);
242 }
243 
244 /* Append dummy data to the model; used for sizing */
append_dummy_line(TilemDisasmView * dv,GtkTreeModel * model,GtkTreeIter * iter)245 static void append_dummy_line(TilemDisasmView *dv, GtkTreeModel *model,
246                               GtkTreeIter *iter)
247 {
248 	GtkTreeIter iter1;
249 	GdkPixbuf *icon;
250 
251 	gtk_list_store_append(GTK_LIST_STORE(model), &iter1);
252 
253 	icon = get_icon(dv, TRUE, FALSE);
254 
255 	gtk_list_store_set(GTK_LIST_STORE(model), &iter1,
256 	                   COL_ICON, icon,
257 	                   COL_ADDRESS, "DD:DDDD",
258 	                   COL_MNEMONIC, "ROM_CALL",
259 	                   COL_ARGUMENTS, "_fnord",
260 	                   COL_SHOW_MNEMONIC, TRUE,
261 	                   -1);
262 
263 	if (icon)
264 		g_object_unref(icon);
265 
266 	if (iter)
267 		*iter = iter1;
268 }
269 
270 /* Check if given logical address is a breakpoint (according to
271    current mapping) */
find_bp_logical(TilemDebugger * dbg,TilemCalc * calc,dword addr)272 static TilemDebugBreakpoint *find_bp_logical(TilemDebugger *dbg,
273                                              TilemCalc *calc,
274                                              dword addr)
275 {
276 	GSList *l;
277 	TilemDebugBreakpoint *bp;
278 	dword pa = (*calc->hw.mem_ltop)(calc, addr);
279 
280 	for (l = dbg->breakpoints; l; l = l->next) {
281 		bp = l->data;
282 		if (!(bp->mode & TILEM_DB_BREAK_EXEC))
283 			continue;
284 
285 		if (bp->type == TILEM_DB_BREAK_LOGICAL
286 		    && bp->start <= addr
287 		    && bp->end >= addr
288 		    && !bp->disabled)
289 			return bp;
290 
291 		if (bp->type == TILEM_DB_BREAK_PHYSICAL
292 		    && bp->start <= pa
293 		    && bp->end >= pa
294 		    && !bp->disabled)
295 			return bp;
296 	}
297 
298 	return NULL;
299 }
300 
301 /* Check if given physical address is a breakpoint (according to
302    current mapping) */
find_bp_physical(TilemDebugger * dbg,TilemCalc * calc,dword addr)303 static TilemDebugBreakpoint *find_bp_physical(TilemDebugger *dbg,
304                                               TilemCalc *calc,
305                                               dword addr)
306 {
307 	GSList *l;
308 	TilemDebugBreakpoint *bp;
309 	dword la, pa;
310 	int i, mapped[4];
311 
312 	/* NOTE: this assumes that bits 0-13 are unaffected by the
313 	   mapping!  This is true for all current models, but might
314 	   need to be changed in the future */
315 	for (i = 0; i < 4; i++) {
316 		la = (i << 14) + (addr & 0x3fff);
317 		pa = (*calc->hw.mem_ltop)(calc, la);
318 		mapped[i] = (addr == pa);
319 	}
320 
321 	for (l = dbg->breakpoints; l; l = l->next) {
322 		bp = l->data;
323 		if (!(bp->mode & TILEM_DB_BREAK_EXEC))
324 			continue;
325 
326 		if (bp->type == TILEM_DB_BREAK_PHYSICAL
327 		    && bp->start <= addr
328 		    && bp->end >= addr
329 		    && !bp->disabled)
330 			return bp;
331 
332 		if (bp->type == TILEM_DB_BREAK_LOGICAL
333 		    && !bp->disabled) {
334 			for (i = 0; i < 4; i++) {
335 				la = (i << 14) + (addr & 0x3fff);
336 				if (bp->start <= la
337 				    && bp->end >= la
338 				    && mapped[i])
339 					return bp;
340 			}
341 		}
342 	}
343 
344 	return NULL;
345 }
346 
347 /* Check if line has a breakpoint set */
find_line_bp(TilemDisasmView * dv,dword pos)348 static TilemDebugBreakpoint *find_line_bp(TilemDisasmView *dv, dword pos)
349 {
350 	TilemDebugBreakpoint *bp;
351 	dword addr = POS_TO_ADDR(pos);
352 	TilemCalc *calc;
353 
354 	tilem_calc_emulator_lock(dv->dbg->emu);
355 	calc = dv->dbg->emu->calc;
356 
357 	if (dv->use_logical)
358 		bp = find_bp_logical(dv->dbg, calc, addr);
359 	else
360 		bp = find_bp_physical(dv->dbg, calc, addr);
361 
362 	tilem_calc_emulator_unlock(dv->dbg->emu);
363 
364 	return bp;
365 }
366 
367 /* Enable breakpoint on the given line */
enable_line_bp(TilemDisasmView * dv,dword pos)368 static void enable_line_bp(TilemDisasmView *dv, dword pos)
369 {
370 	TilemDebugBreakpoint *bp, tmpbp;
371 
372 	if ((bp = find_line_bp(dv, pos)))
373 		return;
374 
375 	tmpbp.type = (dv->use_logical
376 	              ? TILEM_DB_BREAK_LOGICAL
377 	              : TILEM_DB_BREAK_PHYSICAL);
378 	tmpbp.mode = TILEM_DB_BREAK_EXEC;
379 	tmpbp.start = POS_TO_ADDR(pos);
380 	tmpbp.end = POS_TO_ADDR(pos);
381 	tmpbp.mask = (dv->use_logical ? 0xffff : 0xffffffff);
382 	tmpbp.disabled = 0;
383 	tilem_debugger_add_breakpoint(dv->dbg, &tmpbp);
384 }
385 
386 /* Disable breakpoint on the given line */
disable_line_bp(TilemDisasmView * dv,dword pos)387 static void disable_line_bp(TilemDisasmView *dv, dword pos)
388 {
389 	TilemDebugBreakpoint *bp, tmpbp;
390 
391 	if (!(bp = find_line_bp(dv, pos)))
392 		return;
393 
394 	if (bp->mode != TILEM_DB_BREAK_EXEC || bp->start != bp->end) {
395 		/* special breakpoint; do not delete it, just disable it */
396 		tmpbp = *bp;
397 		tmpbp.disabled = 1;
398 		tilem_debugger_change_breakpoint(dv->dbg, bp, &tmpbp);
399 	}
400 	else {
401 		/* regular breakpoint */
402 		tilem_debugger_remove_breakpoint(dv->dbg, bp);
403 	}
404 }
405 
406 /* Append a line to the dasm model */
append_dasm_line(TilemDisasmView * dv,TilemCalc * calc,GtkTreeModel * model,GtkTreeIter * iter,dword pos,dword * nextpos)407 static void append_dasm_line(TilemDisasmView *dv, TilemCalc *calc,
408                              GtkTreeModel *model, GtkTreeIter *iter,
409                              dword pos, dword *nextpos)
410 {
411 	GtkTreeIter iter1;
412 	char *astr, *mnem, *args;
413 	dword addr, pc;
414 	gboolean ispc;
415 	TilemDebugBreakpoint *bp;
416 	GdkPixbuf *icon;
417 
418 	g_return_if_fail(calc != NULL);
419 
420 	gtk_list_store_append(GTK_LIST_STORE(model), &iter1);
421 
422 	addr = POS_TO_ADDR(pos);
423 	astr = tilem_format_addr(dv->dbg, addr, !dv->use_logical);
424 
425 	disassemble(dv, calc, pos, nextpos, &mnem, &args);
426 
427 	if (!mnem)
428 		bp = NULL;
429 	else if (dv->use_logical)
430 		bp = find_bp_logical(dv->dbg, calc, addr);
431 	else
432 		bp = find_bp_physical(dv->dbg, calc, addr);
433 
434 	if (!mnem || !dv->dbg->emu->paused) {
435 		ispc = FALSE;
436 	}
437 	else {
438 		pc = calc->z80.r.pc.w.l;
439 		if (!dv->use_logical)
440 			pc = (*calc->hw.mem_ltop)(calc, pc);
441 		ispc = (addr == pc);
442 	}
443 
444 	icon = get_icon(dv, ispc, (bp != NULL));
445 
446 	gtk_list_store_set(GTK_LIST_STORE(model), &iter1,
447 	                   COL_POSITION, (int) pos,
448 	                   COL_ADDRESS, astr,
449 	                   COL_MNEMONIC, mnem,
450 	                   COL_SHOW_MNEMONIC, (mnem ? TRUE : FALSE),
451 	                   COL_ARGUMENTS, args,
452 	                   COL_ICON, icon,
453 	                   -1);
454 
455 	if (icon)
456 		g_object_unref(icon);
457 
458 	g_free(astr);
459 	g_free(mnem);
460 	g_free(args);
461 
462 	if (iter)
463 		*iter = iter1;
464 }
465 
466 /* Refresh the view by creating and populating a new model */
refresh_disassembly(TilemDisasmView * dv,dword pos,int nlines,dword selectpos)467 static void refresh_disassembly(TilemDisasmView *dv, dword pos, int nlines,
468                                 dword selectpos)
469 {
470 	GtkTreeModel *model;
471 	GtkTreeIter iter;
472 	GtkTreePath *selectpath = NULL;
473 	TilemCalc *calc;
474 	dword nextpos;
475 	int i;
476 
477 	model = new_dasm_model();
478 
479 	dv->startpos = pos;
480 
481 	tilem_calc_emulator_lock(dv->dbg->emu);
482 	calc = dv->dbg->emu->calc;
483 
484 	if (!calc)
485 		nlines = 0;
486 
487 	for (i = 0; i < nlines; i++) {
488 		append_dasm_line(dv, calc, model, &iter, pos, &nextpos);
489 
490 		if (pos == selectpos)
491 			selectpath = gtk_tree_model_get_path(model, &iter);
492 
493 		pos = nextpos;
494 	}
495 
496 	tilem_calc_emulator_unlock(dv->dbg->emu);
497 
498 	dv->endpos = pos;
499 	dv->nlines = nlines;
500 
501 	gtk_tree_view_set_model(GTK_TREE_VIEW(dv), model);
502 	g_object_unref(model);
503 
504 	if (selectpath) {
505 		gtk_tree_view_set_cursor(GTK_TREE_VIEW(dv), selectpath,
506 		                         NULL, FALSE);
507 		gtk_tree_path_free(selectpath);
508 	}
509 }
510 
511 /* Determine the (absolute) position and (display-relative) line
512    number of the cursor, if any */
get_cursor_line(TilemDisasmView * dv,dword * pos,int * linenum)513 static gboolean get_cursor_line(TilemDisasmView *dv, dword *pos,
514                                 int *linenum)
515 {
516 	GtkTreePath *path;
517 	GtkTreeModel *model;
518 	GtkTreeIter iter;
519 	gint *i, p;
520 
521 	gtk_tree_view_get_cursor(GTK_TREE_VIEW(dv), &path, NULL);
522 	if (!path) {
523 		if (pos) *pos = (dword) -1;
524 		if (linenum) *linenum = -1;
525 		return FALSE;
526 	}
527 
528 	if (pos) {
529 		model = gtk_tree_view_get_model(GTK_TREE_VIEW(dv));
530 		if (gtk_tree_model_get_iter(model, &iter, path)) {
531 			gtk_tree_model_get(model, &iter,
532 			                   COL_POSITION, &p, -1);
533 			*pos = p;
534 		}
535 		else {
536 			*pos = (dword) -1;
537 		}
538 	}
539 
540 	if (linenum) {
541 		i = gtk_tree_path_get_indices(path);
542 		*linenum = i[0];
543 	}
544 
545 	gtk_tree_path_free(path);
546 
547 	return TRUE;
548 }
549 
550 /* Size allocation */
551 
552 /* Get the desired height for the tree view (based on size of the data
553    we've inserted into the model) */
get_parent_request_height(GtkWidget * w)554 static int get_parent_request_height(GtkWidget *w)
555 {
556 	GtkRequisition req;
557 	(*GTK_WIDGET_CLASS(parent_class)->size_request)(w, &req);
558 	return req.height;
559 }
560 
561 /* Widget is assigned a size and position */
tilem_disasm_view_size_allocate(GtkWidget * w,GtkAllocation * alloc)562 static void tilem_disasm_view_size_allocate(GtkWidget *w,
563                                             GtkAllocation *alloc)
564 {
565 	TilemDisasmView *dv;
566 	GtkTreeModel *model;
567 	dword curpos;
568 	int n, height1, height2;
569 
570 	g_return_if_fail(TILEM_IS_DISASM_VIEW(w));
571 	dv = TILEM_DISASM_VIEW(w);
572 
573 	(*GTK_WIDGET_CLASS(parent_class)->size_allocate)(w, alloc);
574 
575 	if (alloc->height < 1)
576 		return;
577 
578 	get_cursor_line(dv, &curpos, NULL);
579 
580 	/* Calculate line height */
581 	if (!dv->line_height) {
582 		model = new_dasm_model();
583 
584 		append_dummy_line(dv, model, NULL);
585 		gtk_tree_view_set_model(GTK_TREE_VIEW(dv), model);
586 		height1 = get_parent_request_height(w);
587 
588 		append_dummy_line(dv, model, NULL);
589 		height2 = get_parent_request_height(w);
590 
591 		dv->line_height = height2 - height1;
592 		dv->base_height = height1 - dv->line_height;
593 
594 		g_object_unref(model);
595 
596 		dv->nlines = 0;
597 
598 		if (dv->line_height <= 0) {
599 			dv->line_height = 0;
600 			return;
601 		}
602 	}
603 
604 	n = (alloc->height - dv->base_height) / dv->line_height;
605 
606 	if (n < 1)
607 		n = 1;
608 
609 	if (n != dv->nlines)
610 		refresh_disassembly(dv, dv->startpos, n, curpos);
611 }
612 
613 /* Get widget's desired size */
tilem_disasm_view_size_request(GtkWidget * w,GtkRequisition * req)614 static void tilem_disasm_view_size_request(GtkWidget *w, GtkRequisition *req)
615 {
616 	(*GTK_WIDGET_CLASS(parent_class)->size_request)(w, req);
617 	req->height = 100;	/* ignore requested height */
618 }
619 
620 /* Widget style set */
tilem_disasm_view_style_set(GtkWidget * w,GtkStyle * oldstyle)621 static void tilem_disasm_view_style_set(GtkWidget *w, GtkStyle *oldstyle)
622 {
623 	TilemDisasmView *dv;
624 	GtkTreeModel *model;
625 	GtkTreeIter iter;
626 	GList *cols, *cp;
627 	GtkTreeViewColumn *col;
628 	int width;
629 
630 	g_return_if_fail(TILEM_IS_DISASM_VIEW(w));
631 	dv = TILEM_DISASM_VIEW(w);
632 
633 	(*GTK_WIDGET_CLASS(parent_class)->style_set)(w, oldstyle);
634 
635 	/* line height must be recalculated */
636 	dv->line_height = 0;
637 
638 	/* set column widths based on a dummy model */
639 
640 	model = new_dasm_model();
641 	append_dummy_line(dv, model, &iter);
642 
643 	cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(dv));
644 	for (cp = cols; cp; cp = cp->next) {
645 		col = cp->data;
646 		gtk_tree_view_column_cell_set_cell_data(col, model, &iter,
647 		                                        FALSE, FALSE);
648 		gtk_tree_view_column_cell_get_size(col, NULL, NULL, NULL,
649 		                                   &width, NULL);
650 		gtk_tree_view_column_set_fixed_width(col, width + 2);
651 	}
652 	g_list_free(cols);
653 
654 	g_object_unref(model);
655 }
656 
657 /* Cursor movement commands */
658 
659 /* Move up by COUNT lines */
move_up_lines(TilemDisasmView * dv,int count)660 static gboolean move_up_lines(TilemDisasmView *dv, int count)
661 {
662 	TilemCalc *calc;
663 	dword pos;
664 	int linenum;
665 
666 	if (!get_cursor_line(dv, NULL, &linenum))
667 		linenum = 0;
668 
669 	if (linenum >= count)
670 		return FALSE;
671 
672 	tilem_calc_emulator_lock(dv->dbg->emu);
673 	calc = dv->dbg->emu->calc;
674 
675 	pos = dv->startpos;
676 	count -= linenum;
677 	while (count > 0) {
678 		pos = get_prev_pos(dv, calc, pos);
679 		count--;
680 	}
681 
682 	tilem_calc_emulator_unlock(dv->dbg->emu);
683 
684 	refresh_disassembly(dv, pos, dv->nlines, pos);
685 
686 	return TRUE;
687 }
688 
689 /* Move down by COUNT lines */
move_down_lines(TilemDisasmView * dv,int count)690 static gboolean move_down_lines(TilemDisasmView *dv, int count)
691 {
692 	TilemCalc *calc;
693 	dword startpos, selpos;
694 	int linenum;
695 
696 	if (!get_cursor_line(dv, NULL, &linenum))
697 		linenum = -1;
698 
699 	if (linenum + count < dv->nlines)
700 		return FALSE;
701 
702 	tilem_calc_emulator_lock(dv->dbg->emu);
703 	calc = dv->dbg->emu->calc;
704 
705 	startpos = get_next_pos(dv, calc, dv->startpos);
706 	selpos = dv->endpos;
707 	count -= dv->nlines - linenum;
708 
709 	while (count > 0) {
710 		startpos = get_next_pos(dv, calc, startpos);
711 		selpos = get_next_pos(dv, calc, selpos);
712 		count--;
713 	}
714 
715 	tilem_calc_emulator_unlock(dv->dbg->emu);
716 
717 	refresh_disassembly(dv, startpos, dv->nlines, selpos);
718 
719 	return TRUE;
720 }
721 
722 /* Move down by COUNT bytes */
move_bytes(TilemDisasmView * dv,int count)723 static void move_bytes(TilemDisasmView *dv, int count)
724 {
725 	dword pos, addr;
726 	const TilemCalc *calc = dv->dbg->emu->calc;
727 
728 	g_return_if_fail(calc != NULL);
729 
730 	if (!get_cursor_line(dv, &pos, NULL))
731 		pos = dv->startpos;
732 
733 	addr = POS_TO_ADDR(pos);
734 
735 	if (dv->use_logical)
736 		addr = (addr + count) & 0xffff;
737 	else {
738 		addr += calc->hw.romsize + calc->hw.ramsize + count;
739 		addr %= calc->hw.romsize + calc->hw.ramsize;
740 	}
741 
742 	pos = ADDR_TO_POS(addr);
743 	refresh_disassembly(dv, pos, dv->nlines, pos - 1);
744 }
745 
746 /* Move the cursor (action signal) */
tilem_disasm_view_move_cursor(GtkTreeView * tv,GtkMovementStep step,gint count)747 static gboolean tilem_disasm_view_move_cursor(GtkTreeView *tv,
748                                               GtkMovementStep step,
749                                               gint count)
750 {
751 	TilemDisasmView *dv;
752 
753 	g_return_val_if_fail(TILEM_IS_DISASM_VIEW(tv), FALSE);
754 	dv = TILEM_DISASM_VIEW(tv);
755 
756 	if (!dv->dbg->emu->calc)
757 		return FALSE;
758 
759 	switch (step) {
760 	case GTK_MOVEMENT_DISPLAY_LINES:
761 		if (count < 0) {
762 			if (move_up_lines(dv, -count))
763 				return TRUE;
764 		}
765 		else {
766 			if (move_down_lines(dv, count))
767 				return TRUE;
768 		}
769 		break;
770 
771 	case GTK_MOVEMENT_PARAGRAPHS:
772 	case GTK_MOVEMENT_PARAGRAPH_ENDS:
773 	case GTK_MOVEMENT_PAGES:
774 		/* FIXME: might be better to move by actual "pages" of code */
775 		move_bytes(dv, count * 0x100);
776 		return TRUE;
777 
778 	case GTK_MOVEMENT_BUFFER_ENDS:
779 		move_bytes(dv, count * 0x4000);
780 		return TRUE;
781 
782 	case GTK_MOVEMENT_LOGICAL_POSITIONS:
783 	case GTK_MOVEMENT_VISUAL_POSITIONS:
784 	case GTK_MOVEMENT_WORDS:
785 	case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
786 	case GTK_MOVEMENT_HORIZONTAL_PAGES:
787 	default:
788 		break;
789 	}
790 
791 	return (*GTK_TREE_VIEW_CLASS(parent_class)->move_cursor)(tv, step, count);
792 }
793 
794 /* Popup menu */
795 
toggle_bp(G_GNUC_UNUSED GtkCheckMenuItem * item,gpointer data)796 static void toggle_bp(G_GNUC_UNUSED GtkCheckMenuItem *item, gpointer data)
797 {
798 	TilemDisasmView *dv = data;
799 	tilem_disasm_view_toggle_breakpoint(dv);
800 }
801 
tilem_disasm_view_toggle_breakpoint(TilemDisasmView * dv)802 void tilem_disasm_view_toggle_breakpoint(TilemDisasmView *dv)
803 {
804 	dword curpos;
805 
806 	g_return_if_fail(TILEM_IS_DISASM_VIEW(dv));
807 
808 	get_cursor_line(dv, &curpos, NULL);
809 	if (curpos == (dword) -1)
810 		return;
811 
812 	if (find_line_bp(dv, curpos))
813 		disable_line_bp(dv, curpos);
814 	else
815 		enable_line_bp(dv, curpos);
816 }
817 
prompt_go_to(G_GNUC_UNUSED GtkMenuItem * item,gpointer data)818 static void prompt_go_to(G_GNUC_UNUSED GtkMenuItem *item, gpointer data)
819 {
820 	TilemDisasmView *dv = data;
821 	GtkWidget *window;
822 	dword curpos, addr;
823 
824 	window = gtk_widget_get_toplevel(GTK_WIDGET(dv));
825 
826 	get_cursor_line(dv, &curpos, NULL);
827 	addr = POS_TO_ADDR(curpos);
828 
829 	if (tilem_prompt_address(dv->dbg, GTK_WINDOW(window),
830 	                         "Go to Address", "Address:",
831 	                         &addr, !dv->use_logical,
832 	                         (curpos != (dword) -1)))
833 		tilem_disasm_view_go_to_address(dv, addr, dv->use_logical);
834 }
835 
go_to_pc(G_GNUC_UNUSED GtkMenuItem * item,gpointer data)836 static void go_to_pc(G_GNUC_UNUSED GtkMenuItem *item, gpointer data)
837 {
838 	TilemDisasmView *dv = data;
839 	TilemCalc *calc;
840 	dword pc;
841 
842 	g_return_if_fail(dv->dbg != NULL);
843 	g_return_if_fail(dv->dbg->emu != NULL);
844 	g_return_if_fail(dv->dbg->emu->calc != NULL);
845 
846 	tilem_calc_emulator_lock(dv->dbg->emu);
847 	calc = dv->dbg->emu->calc;
848 	pc = calc->z80.r.pc.w.l;
849 	tilem_calc_emulator_unlock(dv->dbg->emu);
850 
851 	tilem_disasm_view_go_to_address(dv, pc, TRUE);
852 }
853 
854 /* Determine where to pop up menu (if not activated by a mouse event) */
place_menu(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer data)855 static void place_menu(GtkMenu *menu, gint *x, gint *y,
856                        gboolean *push_in, gpointer data)
857 {
858 	TilemDisasmView *dv = data;
859 	GtkTreePath *path;
860 	GdkRectangle rect;
861 	GdkWindow *win;
862 	GdkScreen *screen;
863 	int n;
864 
865 	win = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(dv));
866 	gdk_window_get_origin(win, x, y);
867 
868 	gtk_tree_view_get_cursor(GTK_TREE_VIEW(dv), &path, NULL);
869 	if (path) {
870 		gtk_tree_view_get_cell_area(GTK_TREE_VIEW(dv), path, NULL, &rect);
871 		gtk_tree_path_free(path);
872 		*y += rect.y + rect.height;
873 	}
874 
875 	screen = gdk_drawable_get_screen(win);
876 	n = gdk_screen_get_monitor_at_point(screen, *x, *y);
877 	gtk_menu_set_monitor(menu, n);
878 
879 	*push_in = FALSE;
880 }
881 
882 /* Create and show the popup menu */
show_popup_menu(TilemDisasmView * dv,GdkEventButton * event)883 static void show_popup_menu(TilemDisasmView *dv, GdkEventButton *event)
884 {
885 	GtkWidget *menu, *item;
886 	dword curpos;
887 
888 	if (dv->popup_menu)
889 		gtk_widget_destroy(dv->popup_menu);
890 	dv->popup_menu = menu = gtk_menu_new();
891 
892 	/* Enable/disable breakpoint */
893 
894 	item = gtk_check_menu_item_new_with_mnemonic("_Breakpoint Here");
895 
896 	get_cursor_line(dv, &curpos, NULL);
897 	if (curpos == (dword) -1)
898 		gtk_widget_set_sensitive(item, FALSE);
899 	else if (find_line_bp(dv, curpos))
900 		gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
901 
902 	g_signal_connect(item, "toggled",
903 	                 G_CALLBACK(toggle_bp), dv);
904 
905 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
906 	gtk_widget_show(item);
907 
908 	item = gtk_separator_menu_item_new();
909 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
910 	gtk_widget_show(item);
911 
912 	/* Jump to address */
913 
914 	item = gtk_menu_item_new_with_mnemonic("_Go to Address...");
915 	g_signal_connect(item, "activate", G_CALLBACK(prompt_go_to), dv);
916 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
917 	gtk_widget_show(item);
918 
919 	item = gtk_menu_item_new_with_mnemonic("Go to P_C");
920 	g_signal_connect(item, "activate", G_CALLBACK(go_to_pc), dv);
921 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
922 	gtk_widget_show(item);
923 
924 	if (event)
925 		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
926 		               event->button, event->time);
927 	else
928 		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, &place_menu, dv,
929 		               0, gtk_get_current_event_time());
930 }
931 
932 /* Button pressed */
tilem_disasm_view_button_press(GtkWidget * w,GdkEventButton * event)933 static gboolean tilem_disasm_view_button_press(GtkWidget *w,
934                                                GdkEventButton *event)
935 {
936 	g_return_val_if_fail(TILEM_IS_DISASM_VIEW(w), FALSE);
937 
938 	(*GTK_WIDGET_CLASS(parent_class)->button_press_event)(w, event);
939 
940 	if (event->button == 3)
941 		show_popup_menu(TILEM_DISASM_VIEW(w), event);
942 
943 	return TRUE;
944 }
945 
946 /* Key pressed to activate context menu */
tilem_disasm_view_popup_menu(GtkWidget * w)947 static gboolean tilem_disasm_view_popup_menu(GtkWidget *w)
948 {
949 	g_return_val_if_fail(TILEM_IS_DISASM_VIEW(w), FALSE);
950 	show_popup_menu(TILEM_DISASM_VIEW(w), NULL);
951 	return TRUE;
952 }
953 
954 /* Row activated (double-clicked) */
tilem_disasm_view_row_activated(GtkTreeView * tv,GtkTreePath * path,GtkTreeViewColumn * col)955 static void tilem_disasm_view_row_activated(GtkTreeView *tv, GtkTreePath *path,
956                                             GtkTreeViewColumn *col)
957 {
958 	TilemDisasmView *dv = TILEM_DISASM_VIEW(tv);
959 	GtkTreeModel *model;
960 	GtkTreeIter iter;
961 	gint pos;
962 
963 	model = gtk_tree_view_get_model(tv);
964 	if (!gtk_tree_model_get_iter(model, &iter, path))
965 		return;
966 
967 	gtk_tree_model_get(model, &iter, COL_POSITION, &pos, -1);
968 
969 	if (col == dv->icon_column) {
970 		if (find_line_bp(dv, pos))
971 			disable_line_bp(dv, pos);
972 		else
973 			enable_line_bp(dv, pos);
974 	}
975 }
976 
977 /* Unrealize widget */
tilem_disasm_view_unrealize(GtkWidget * w)978 static void tilem_disasm_view_unrealize(GtkWidget *w)
979 {
980 	TilemDisasmView *dv = TILEM_DISASM_VIEW(w);
981 
982 	if (dv->popup_menu)
983 		gtk_widget_destroy(dv->popup_menu);
984 	dv->popup_menu = NULL;
985 
986 	(*GTK_WIDGET_CLASS(parent_class)->unrealize)(w);
987 }
988 
989 /* Initialize a new TilemDisasmView */
tilem_disasm_view_init(TilemDisasmView * dv)990 static void tilem_disasm_view_init(TilemDisasmView *dv)
991 {
992 	GtkTreeView *tv = GTK_TREE_VIEW(dv);
993 	GtkCellRenderer *cell;
994 	GtkTreeViewColumn *col;
995 
996 	dv->use_logical = TRUE;
997 
998 	gtk_tree_view_set_enable_search(tv, FALSE);
999 	gtk_tree_view_set_fixed_height_mode(tv, TRUE);
1000 	gtk_tree_view_set_headers_visible(tv, FALSE);
1001 
1002 	cell = gtk_cell_renderer_pixbuf_new();
1003 	col = gtk_tree_view_column_new_with_attributes(NULL, cell,
1004 	                                               "pixbuf", COL_ICON,
1005 	                                               NULL);
1006 	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
1007 	gtk_tree_view_append_column(tv, col);
1008 	dv->icon_column = col;
1009 
1010 	cell = gtk_cell_renderer_text_new();
1011 	col = gtk_tree_view_column_new_with_attributes("Addr", cell,
1012 	                                               "text", COL_ADDRESS,
1013 	                                               NULL);
1014 	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
1015 	gtk_tree_view_append_column(tv, col);
1016 
1017 	col = gtk_tree_view_column_new();
1018 	gtk_tree_view_column_set_title(col, "Disassembly");
1019 
1020 	cell = gtk_cell_renderer_text_new();
1021 	g_object_set(cell, "xpad", 10, NULL);
1022 	gtk_tree_view_column_pack_start(col, cell, FALSE);
1023 	gtk_tree_view_column_set_attributes(col, cell,
1024 	                                    "text", COL_MNEMONIC,
1025 	                                    "visible", COL_SHOW_MNEMONIC,
1026 	                                    NULL);
1027 
1028 	cell = gtk_cell_renderer_text_new();
1029 	gtk_tree_view_column_pack_start(col, cell, TRUE);
1030 	gtk_tree_view_column_set_attributes(col, cell,
1031 	                                    "text", COL_ARGUMENTS,
1032 	                                    NULL);
1033 
1034 	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
1035 	gtk_tree_view_column_set_expand(col, TRUE);
1036 	gtk_tree_view_append_column(tv, col);
1037 }
1038 
1039 static const char default_style[] =
1040 	"style \"tilem-disasm-default\" { font_name = \"Monospace\" } "
1041 	"widget \"*.TilemDisasmView\" style:application \"tilem-disasm-default\"";
1042 
1043 /* Initialize the TilemDisasmView class */
tilem_disasm_view_class_init(TilemDisasmViewClass * klass)1044 static void tilem_disasm_view_class_init(TilemDisasmViewClass *klass)
1045 {
1046 	GtkTreeViewClass *tv_class = GTK_TREE_VIEW_CLASS(klass);
1047 	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1048 
1049 	gtk_rc_parse_string(default_style);
1050 
1051 	parent_class = g_type_class_peek_parent(klass);
1052 
1053 	widget_class->style_set = &tilem_disasm_view_style_set;
1054 	widget_class->size_request = &tilem_disasm_view_size_request;
1055 	widget_class->size_allocate = &tilem_disasm_view_size_allocate;
1056 	widget_class->button_press_event = &tilem_disasm_view_button_press;
1057 	widget_class->popup_menu = &tilem_disasm_view_popup_menu;
1058 	widget_class->unrealize = &tilem_disasm_view_unrealize;
1059 	tv_class->move_cursor = &tilem_disasm_view_move_cursor;
1060 	tv_class->row_activated = &tilem_disasm_view_row_activated;
1061 }
1062 
tilem_disasm_view_new(TilemDebugger * dbg)1063 GtkWidget * tilem_disasm_view_new(TilemDebugger *dbg)
1064 {
1065 	TilemDisasmView *dv;
1066 
1067 	g_return_val_if_fail(dbg != NULL, NULL);
1068 
1069 	dv = g_object_new(TILEM_TYPE_DISASM_VIEW, NULL);
1070 	dv->dbg = dbg;
1071 
1072 	return GTK_WIDGET(dv);
1073 }
1074 
1075 /* Select memory addressing mode. */
tilem_disasm_view_set_logical(TilemDisasmView * dv,gboolean logical)1076 void tilem_disasm_view_set_logical(TilemDisasmView *dv, gboolean logical)
1077 {
1078 	dword start, curpos;
1079 	TilemCalc *calc;
1080 
1081 	g_return_if_fail(TILEM_IS_DISASM_VIEW(dv));
1082 	g_return_if_fail(dv->dbg->emu->calc != NULL);
1083 
1084 	get_cursor_line(dv, &curpos, NULL);
1085 
1086 	if (logical && !dv->use_logical) {
1087 		tilem_calc_emulator_lock(dv->dbg->emu);
1088 		calc = dv->dbg->emu->calc;
1089 		curpos = pos_ptol(calc, curpos);
1090 		start = pos_ptol(calc, dv->startpos);
1091 		tilem_calc_emulator_unlock(dv->dbg->emu);
1092 
1093 		dv->use_logical = TRUE;
1094 		refresh_disassembly(dv, start, dv->nlines, curpos);
1095 	}
1096 	else if (!logical && dv->use_logical) {
1097 		tilem_calc_emulator_lock(dv->dbg->emu);
1098 		calc = dv->dbg->emu->calc;
1099 		curpos = pos_ltop(calc, curpos);
1100 		start = pos_ltop(calc, dv->startpos);
1101 		tilem_calc_emulator_unlock(dv->dbg->emu);
1102 
1103 		dv->use_logical = FALSE;
1104 		refresh_disassembly(dv, start, dv->nlines, curpos);
1105 	}
1106 }
1107 
1108 /* Refresh contents of view. */
tilem_disasm_view_refresh(TilemDisasmView * dv)1109 void tilem_disasm_view_refresh(TilemDisasmView *dv)
1110 {
1111 	dword curpos;
1112 	g_return_if_fail(TILEM_IS_DISASM_VIEW(dv));
1113 	get_cursor_line(dv, &curpos, NULL);
1114 	refresh_disassembly(dv, dv->startpos, dv->nlines, curpos);
1115 }
1116 
1117 /* Find tree path for the given position */
find_path_for_position(GtkTreeModel * model,int pos)1118 static GtkTreePath *find_path_for_position(GtkTreeModel *model, int pos)
1119 {
1120 	gint p;
1121 	GtkTreeIter iter;
1122 
1123 	if (!gtk_tree_model_get_iter_first(model, &iter))
1124 		return NULL;
1125 
1126 	do {
1127 		gtk_tree_model_get(model, &iter, COL_POSITION, &p, -1);
1128 		if (p == pos) {
1129 			return gtk_tree_model_get_path(model, &iter);
1130 		}
1131 	} while (gtk_tree_model_iter_next(model, &iter));
1132 
1133 	return NULL;
1134 }
1135 
1136 /* Highlight the specified address. */
tilem_disasm_view_go_to_address(TilemDisasmView * dv,dword addr,gboolean logical)1137 void tilem_disasm_view_go_to_address(TilemDisasmView *dv, dword addr,
1138                                      gboolean logical)
1139 {
1140 	dword pos;
1141 	GtkTreeModel *model;
1142 	GtkTreePath *path;
1143 	TilemCalc *calc;
1144 
1145 	g_return_if_fail(TILEM_IS_DISASM_VIEW(dv));
1146 
1147 	tilem_calc_emulator_lock(dv->dbg->emu);
1148 	calc = dv->dbg->emu->calc;
1149 
1150 	if (logical) {
1151 		addr &= 0xffff;
1152 		if (dv->use_logical)
1153 			pos = ADDR_TO_POS(addr);
1154 		else
1155 			pos = pos_ltop(calc, ADDR_TO_POS(addr));
1156 	}
1157 	else {
1158 		if (dv->use_logical) {
1159 			addr = (*calc->hw.mem_ptol)(calc, addr);
1160 			if (addr == (dword) -1) {
1161 				tilem_calc_emulator_unlock(dv->dbg->emu);
1162 				return;
1163 			}
1164 		}
1165 		pos = ADDR_TO_POS(addr);
1166 	}
1167 
1168 	tilem_calc_emulator_unlock(dv->dbg->emu);
1169 
1170 	if (pos >= dv->startpos && pos < dv->endpos) {
1171 		model = gtk_tree_view_get_model(GTK_TREE_VIEW(dv));
1172 		path = find_path_for_position(model, pos);
1173 		if (path) {
1174 			gtk_tree_view_set_cursor(GTK_TREE_VIEW(dv), path,
1175 			                         NULL, FALSE);
1176 			gtk_tree_path_free(path);
1177 			return;
1178 		}
1179 	}
1180 
1181 	refresh_disassembly(dv, pos, dv->nlines, pos);
1182 }
1183 
1184 /* Get currently selected address. */
tilem_disasm_view_get_cursor(TilemDisasmView * dv,dword * addr,gboolean * is_logical)1185 gboolean tilem_disasm_view_get_cursor(TilemDisasmView *dv, dword *addr,
1186                                       gboolean *is_logical)
1187 {
1188 	dword pos;
1189 
1190 	g_return_val_if_fail(TILEM_IS_DISASM_VIEW(dv), FALSE);
1191 
1192 	if (is_logical) *is_logical = dv->use_logical;
1193 
1194 	if (!get_cursor_line(dv, &pos, NULL))
1195 		return FALSE;
1196 
1197 	if (addr) *addr = POS_TO_ADDR(pos);
1198 	return TRUE;
1199 }
1200 
1201