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