1 /* memory.c: the GTK memory browser
2 Copyright (c) 2004-2005 Philip Kendall
3 Copyright (c) 2015 Stuart Brady
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19 Author contact information:
20
21 E-mail: philip-fuse@shadowmagic.org.uk
22
23 */
24
25 #include <config.h>
26
27 #include <errno.h>
28 #include <math.h>
29 #include <stdio.h>
30 #include <string.h>
31
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
34
35 #include "compat.h"
36 #include "fuse.h"
37 #include "gtkcompat.h"
38 #include "gtkinternals.h"
39 #include "memory_pages.h"
40 #include "menu.h"
41 #include "ui/ui.h"
42
43 #define VIEW_NUM_ROWS 20
44 #define VIEW_NUM_COLS 16
45
46 static libspectrum_word memaddr = 0x0000;
47
48 static libspectrum_dword mark_offset = 0xffffffff;
49
50 static int cursor_line_number = -1, cursor_char_offset;
51 static GtkTextBuffer *cursor_buffer;
52
53 static GtkTextBuffer *buffer_address, *buffer_hex, *buffer_data;
54 static GtkAdjustment *adjustment;
55
56 static gboolean
textview_wheel_scroll_event(GtkWidget * widget,GdkEvent * event,gpointer user_data)57 textview_wheel_scroll_event( GtkWidget *widget, GdkEvent *event, gpointer user_data )
58 {
59 GtkAdjustment *adjustment = user_data;
60 gdouble base, oldbase, base_limit;
61
62 base = oldbase = gtk_adjustment_get_value( adjustment );
63
64 switch( event->scroll.direction )
65 {
66 case GDK_SCROLL_UP:
67 base -= gtk_adjustment_get_page_increment( adjustment ) / 2;
68 break;
69 case GDK_SCROLL_DOWN:
70 base += gtk_adjustment_get_page_increment( adjustment ) / 2;
71 break;
72
73 #if GTK_CHECK_VERSION( 3, 4, 0 )
74 case GDK_SCROLL_SMOOTH:
75 {
76 static gdouble total_dy = 0;
77 gdouble dx, dy, page_size;
78 int delta;
79
80 if( gdk_event_get_scroll_deltas( event, &dx, &dy ) ) {
81 total_dy += dy;
82 page_size = gtk_adjustment_get_page_size( adjustment );
83 delta = total_dy * pow( page_size, 2.0 / 3.0 );
84
85 /* Is movement significative? */
86 if( delta ) {
87 base += delta;
88 total_dy = 0;
89 }
90 }
91 break;
92 }
93 #endif
94
95 default:
96 return FALSE;
97 }
98
99 if( base < 0 ) {
100 base = 0;
101 } else {
102 base_limit = gtk_adjustment_get_upper( adjustment ) -
103 gtk_adjustment_get_page_size( adjustment );
104 if( base > base_limit ) base = base_limit;
105 }
106
107 if( base != oldbase ) {
108 gtk_adjustment_set_value( adjustment, base );
109 }
110
111 return TRUE;
112 }
113
114 static gboolean
textview_key_press_event(GtkWidget * widget,GdkEventKey * event,gpointer user_data)115 textview_key_press_event( GtkWidget *widget, GdkEventKey *event, gpointer user_data )
116 {
117 GtkAdjustment *adjustment = user_data;
118 GtkTextBuffer *text_buffer;
119 GtkTextIter iter;
120 GtkTextMark *mark;
121 gdouble base, oldbase, base_limit;
122 gdouble page_size, step_increment;
123 gint line, line_offset;
124 int num_rows, line_width;
125
126 base = oldbase = gtk_adjustment_get_value( adjustment );
127 page_size = gtk_adjustment_get_page_size( adjustment );
128 step_increment = gtk_adjustment_get_step_increment( adjustment );
129 num_rows = ( page_size + 1 ) / step_increment;
130
131 text_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( widget ) );
132
133 /* Get line width (includes CR/LF) */
134 line_width = gtk_text_buffer_get_char_count( text_buffer ) /
135 gtk_text_buffer_get_line_count( text_buffer );
136
137 /* Get row and offset of cursor */
138 mark = gtk_text_buffer_get_insert( text_buffer );
139 gtk_text_buffer_get_iter_at_mark( text_buffer, &iter, mark );
140 line = gtk_text_iter_get_line( &iter );
141 line_offset = gtk_text_iter_get_line_offset( &iter );
142
143 switch( event->keyval )
144 {
145
146 case GDK_KEY_Left:
147 if( line == 0 && line_offset == 0 ) {
148 base -= step_increment;
149 cursor_buffer = text_buffer;
150 cursor_line_number = line;
151 cursor_char_offset = line_width;
152 }
153 break;
154
155 case GDK_KEY_Right:
156 if( line == num_rows - 1 && line_offset == line_width ) {
157 base += step_increment;
158 cursor_buffer = text_buffer;
159 cursor_line_number = line;
160 cursor_char_offset = 0;
161 }
162 break;
163
164 case GDK_KEY_Up:
165 if( line == 0 ) {
166 base -= step_increment;
167 cursor_buffer = text_buffer;
168 cursor_line_number = 0;
169 cursor_char_offset = line_offset;
170 }
171 break;
172
173 case GDK_KEY_Down:
174 if( line == num_rows - 1 ) {
175 base += step_increment;
176 cursor_buffer = text_buffer;
177 cursor_line_number = line;
178 cursor_char_offset = line_offset;
179 }
180 break;
181
182 case GDK_KEY_Page_Up:
183 base -= gtk_adjustment_get_page_increment( adjustment );
184 cursor_buffer = text_buffer;
185 cursor_line_number = line;
186 cursor_char_offset = line_offset;
187 break;
188
189 case GDK_KEY_Page_Down:
190 base += gtk_adjustment_get_page_increment( adjustment );
191 cursor_buffer = text_buffer;
192 cursor_line_number = line;
193 cursor_char_offset = line_offset;
194 break;
195
196 default:
197 return FALSE;
198 }
199
200 if( base < 0 ) {
201 base = 0;
202 } else {
203 base_limit = gtk_adjustment_get_upper( adjustment ) - page_size;
204 if( base > base_limit ) base = base_limit;
205 }
206
207 if( base != oldbase ) {
208 gtk_adjustment_set_value( adjustment, base );
209
210 /* As we are simulating a full-filled text view, we need to move the cursor
211 position after page movement/redraw */
212 if( cursor_line_number >= 0 && cursor_line_number < VIEW_NUM_ROWS ) {
213 gtk_text_buffer_get_iter_at_line_offset( cursor_buffer, &iter,
214 cursor_line_number,
215 cursor_char_offset );
216 gtk_text_buffer_place_cursor( cursor_buffer, &iter );
217 cursor_line_number = -1;
218 }
219
220 return TRUE;
221 }
222
223 return FALSE;
224 }
225
226 static void
update_display(libspectrum_word base)227 update_display( libspectrum_word base )
228 {
229 size_t i, j;
230 char buffer2[ 8 ];
231 char buffer3;
232 GtkTextIter iter_address, iter_hex, iter_data, start, end;
233
234 memaddr = base;
235
236 gtk_text_buffer_get_bounds( buffer_address, &start, &end );
237 gtk_text_buffer_delete( buffer_address, &start, &end );
238 gtk_text_buffer_get_bounds( buffer_hex, &start, &end );
239 gtk_text_buffer_delete( buffer_hex, &start, &end );
240 gtk_text_buffer_get_bounds( buffer_data, &start, &end );
241 gtk_text_buffer_delete( buffer_data, &start, &end );
242
243 gtk_text_buffer_get_start_iter( buffer_address, &iter_address );
244 gtk_text_buffer_get_start_iter( buffer_hex, &iter_hex );
245 gtk_text_buffer_get_start_iter( buffer_data, &iter_data );
246
247 for( i = 0; i < VIEW_NUM_ROWS; i++ ) {
248 if( i > 0 ) {
249 gtk_text_buffer_insert( buffer_address, &iter_address, "\n", -1 );
250 gtk_text_buffer_insert( buffer_hex, &iter_hex, "\n", -1 );
251 gtk_text_buffer_insert( buffer_data, &iter_data, "\n", -1 );
252 }
253
254 snprintf( buffer2, 8, "%04X", base );
255 gtk_text_buffer_insert( buffer_address, &iter_address, buffer2, -1 );
256
257 for( j = 0; j < VIEW_NUM_COLS; j++, base++ ) {
258 if( j > 0 )
259 gtk_text_buffer_insert( buffer_hex, &iter_hex, " ", -1 );
260
261 libspectrum_byte b = readbyte_internal( base );
262 snprintf( buffer2, 4, "%02X", b );
263
264 buffer3 = ( b >= 32 && b < 127 ) ? b : '.';
265
266 if( base != mark_offset ) {
267 gtk_text_buffer_insert( buffer_hex, &iter_hex, buffer2, -1 );
268 gtk_text_buffer_insert( buffer_data, &iter_data, &buffer3, 1 );
269 } else {
270 gtk_text_buffer_insert_with_tags_by_name( buffer_hex, &iter_hex,
271 buffer2, -1, "background_yellow", NULL );
272 gtk_text_buffer_insert_with_tags_by_name( buffer_data, &iter_data,
273 &buffer3, 1, "background_yellow", NULL );
274 }
275 }
276 }
277
278 gtk_text_buffer_get_bounds( buffer_address, &start, &end );
279 gtk_text_buffer_apply_tag_by_name( buffer_address, "monospace", &start, &end );
280 gtk_text_buffer_get_bounds( buffer_hex, &start, &end );
281 gtk_text_buffer_apply_tag_by_name( buffer_hex, "monospace", &start, &end );
282 gtk_text_buffer_get_bounds( buffer_data, &start, &end );
283 gtk_text_buffer_apply_tag_by_name( buffer_data, "monospace", &start, &end );
284 }
285
286 static void
scroller(GtkAdjustment * adjustment,gpointer user_data)287 scroller( GtkAdjustment *adjustment, gpointer user_data )
288 {
289 libspectrum_word base;
290
291 /* Drop the low bits before displaying anything */
292 base = gtk_adjustment_get_value( adjustment );
293 base &= 0xfff0;
294
295 update_display( base );
296 }
297
298 #if GTK_CHECK_VERSION( 3, 6, 0 )
299 static void
goto_offset(GtkWidget * widget GCC_UNUSED,gpointer user_data GCC_UNUSED)300 goto_offset( GtkWidget *widget GCC_UNUSED, gpointer user_data GCC_UNUSED )
301 {
302 long offset;
303 const gchar *entry;
304 char *endptr;
305 int base_num;
306
307 if( gtk_entry_get_text_length( GTK_ENTRY( widget ) ) == 0 )
308 return;
309
310 /* Parse address */
311 entry = gtk_entry_get_text( GTK_ENTRY( widget ) );
312 errno = 0;
313 base_num = ( g_str_has_prefix( entry, "0x" ) )? 16 : 10;
314 offset = strtol( entry, &endptr, base_num );
315
316 /* Validate address */
317 if( errno || offset < 0 || offset > 65535 || endptr == entry ||
318 *endptr != '\0' ) {
319 return;
320 }
321
322 mark_offset = offset;
323 gtk_adjustment_set_value( adjustment, offset );
324 }
325 #endif
326
327 void
menu_machine_memorybrowser(GtkAction * gtk_action GCC_UNUSED,gpointer data GCC_UNUSED)328 menu_machine_memorybrowser( GtkAction *gtk_action GCC_UNUSED,
329 gpointer data GCC_UNUSED )
330 {
331 GtkWidget *dialog, *content_area, *scrollbar, *label, *offset;
332 GtkWidget *box, *box_address, *box_hex, *box_data, *box_data_horizontal;
333 GtkAccelGroup *accel_group;
334 GtkWidget *view_address, *view_hex, *view_data;
335 GtkTextTagTable *tag_table;
336 GtkTextTag *tag;
337
338 fuse_emulation_pause();
339
340 dialog = gtkstock_dialog_new( "Fuse - Memory Browser", NULL );
341 content_area = gtk_dialog_get_content_area( GTK_DIALOG( dialog ) );
342
343 /* Keyboard shortcuts */
344 accel_group = gtk_accel_group_new();
345 gtk_window_add_accel_group( GTK_WINDOW( dialog ), accel_group );
346
347 gtkstock_create_close( dialog, accel_group, NULL, TRUE );
348
349 #if GTK_CHECK_VERSION( 3, 6, 0 )
350 /* Go to offset */
351 box = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 8 );
352 offset = gtk_search_entry_new();
353 /*
354 * Entry is max 6 chars wide ("0xXXXX") but the GTK widget adds
355 * a search icon and "clear contents" icon, which between them
356 * take up about 6 char's widths. So it needs to be wider.
357 */
358 gtk_entry_set_width_chars( GTK_ENTRY( offset ), 15 );
359 gtk_entry_set_max_length( GTK_ENTRY( offset ), 6 );
360 gtk_box_pack_end( GTK_BOX( box ), offset, FALSE, FALSE, 0 );
361
362 label = gtk_label_new( "Go to offset" );
363 gtk_box_pack_end( GTK_BOX( box ), label, FALSE, FALSE, 0 );
364
365 gtk_box_pack_start( GTK_BOX( content_area ), box, FALSE, FALSE, 8 );
366
367 g_signal_connect( G_OBJECT( offset ), "activate",
368 G_CALLBACK( goto_offset ), NULL );
369 #endif
370
371 /* Create text buffers */
372 tag_table = gtk_text_tag_table_new();
373 tag = gtk_text_tag_new( "monospace" );
374 g_object_set( tag, "family", "monospace", NULL );
375 gtk_text_tag_table_add( tag_table, tag );
376
377 tag = gtk_text_tag_new( "background_yellow" );
378 g_object_set( tag, "background", "yellow",
379 "background-full-height", TRUE, NULL );
380 gtk_text_tag_table_add( tag_table, tag );
381
382 buffer_address = gtk_text_buffer_new( tag_table );
383 buffer_hex = gtk_text_buffer_new( tag_table );
384 buffer_data = gtk_text_buffer_new( tag_table );
385
386 /* Labels */
387 box = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 8 );
388 box_address = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
389 gtk_box_pack_start( GTK_BOX( box ), box_address, FALSE, FALSE, 0 );
390 box_hex = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
391 gtk_box_pack_start( GTK_BOX( box ), box_hex, FALSE, FALSE, 0 );
392 box_data = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
393 gtk_box_pack_start( GTK_BOX( box ), box_data, FALSE, FALSE, 0 );
394 gtk_box_pack_start( GTK_BOX( content_area ), box, FALSE, FALSE, 0 );
395
396 label = gtk_label_new( "Address" );
397 gtk_box_pack_start( GTK_BOX( box_address ), label, FALSE, FALSE, 0 );
398 label = gtk_label_new( "Hex" );
399 gtk_box_pack_start( GTK_BOX( box_hex ), label, FALSE, FALSE, 0 );
400 label = gtk_label_new( "Data" );
401 gtk_box_pack_start( GTK_BOX( box_data ), label, FALSE, FALSE, 0 );
402
403 /* Add views */
404 view_address = gtk_text_view_new_with_buffer( buffer_address );
405 gtk_text_view_set_editable( GTK_TEXT_VIEW( view_address ), FALSE );
406 gtk_text_view_set_left_margin( GTK_TEXT_VIEW( view_address ), 1 );
407 gtk_text_view_set_right_margin( GTK_TEXT_VIEW( view_address ), 1 );
408 view_hex = gtk_text_view_new_with_buffer( buffer_hex );
409 gtk_text_view_set_editable( GTK_TEXT_VIEW( view_hex ), FALSE );
410 gtk_text_view_set_left_margin( GTK_TEXT_VIEW( view_hex ), 1 );
411 gtk_text_view_set_right_margin( GTK_TEXT_VIEW( view_hex ), 1 );
412 view_data = gtk_text_view_new_with_buffer( buffer_data );
413 gtk_text_view_set_editable( GTK_TEXT_VIEW( view_data ), FALSE );
414 gtk_text_view_set_left_margin( GTK_TEXT_VIEW( view_data ), 1 );
415 gtk_text_view_set_right_margin( GTK_TEXT_VIEW( view_data ), 1 );
416
417 #if GTK_CHECK_VERSION( 3, 16, 0 )
418 gtk_text_view_set_monospace( GTK_TEXT_VIEW( view_address ), TRUE );
419 gtk_text_view_set_monospace( GTK_TEXT_VIEW( view_hex ), TRUE );
420 gtk_text_view_set_monospace( GTK_TEXT_VIEW( view_data ), TRUE );
421 #endif
422
423 gtk_box_pack_start( GTK_BOX( box_address ), view_address, FALSE, FALSE, 0 );
424 gtk_box_pack_start( GTK_BOX( box_hex ), view_hex, FALSE, FALSE, 0 );
425 box_data_horizontal = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 );
426 gtk_box_pack_start( GTK_BOX( box_data ), box_data_horizontal, FALSE, FALSE, 0 );
427 gtk_box_pack_start( GTK_BOX( box_data_horizontal ), view_data, FALSE, FALSE, 0 );
428
429 /* Scroll */
430 adjustment = GTK_ADJUSTMENT(
431 gtk_adjustment_new( memaddr, 0x0000, 0xffff, VIEW_NUM_COLS,
432 ( VIEW_NUM_ROWS * VIEW_NUM_COLS ) / 2,
433 ( VIEW_NUM_ROWS * VIEW_NUM_COLS ) - 1 ) );
434 g_signal_connect( adjustment, "value-changed", G_CALLBACK( scroller ), NULL );
435 scrollbar = gtk_scrollbar_new( GTK_ORIENTATION_VERTICAL, adjustment );
436 gtk_box_pack_start( GTK_BOX( box_data_horizontal ), scrollbar, FALSE, FALSE, 0 );
437
438 /* Allow scroll on text views */
439 g_signal_connect( view_address, "scroll-event",
440 G_CALLBACK( textview_wheel_scroll_event ), adjustment );
441 g_signal_connect( view_hex, "scroll-event",
442 G_CALLBACK( textview_wheel_scroll_event ), adjustment );
443 g_signal_connect( view_data, "scroll-event",
444 G_CALLBACK( textview_wheel_scroll_event ), adjustment );
445 g_signal_connect( view_address, "key-press-event",
446 G_CALLBACK( textview_key_press_event ), adjustment );
447 g_signal_connect( view_hex, "key-press-event",
448 G_CALLBACK( textview_key_press_event ), adjustment );
449 g_signal_connect( view_data, "key-press-event",
450 G_CALLBACK( textview_key_press_event ), adjustment );
451
452 update_display( memaddr );
453
454 gtk_widget_show_all( dialog );
455 gtk_main();
456
457 fuse_emulation_unpause();
458
459 return;
460 }
461