1
0
mirror of https://git.code.sf.net/p/fuse-emulator/fuse synced 2026-01-27 01:41:34 +03:00
Files
fuse/ui/gtk/memory.c
2018-03-24 10:23:59 +01:00

456 lines
14 KiB
C

/* memory.c: the GTK+ memory browser
Copyright (c) 2004-2005 Philip Kendall
Copyright (c) 2015 Stuart Brady
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 2 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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Author contact information:
E-mail: philip-fuse@shadowmagic.org.uk
*/
#include <config.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include "compat.h"
#include "fuse.h"
#include "gtkcompat.h"
#include "gtkinternals.h"
#include "memory_pages.h"
#include "menu.h"
#include "ui/ui.h"
#define VIEW_NUM_ROWS 20
#define VIEW_NUM_COLS 16
static libspectrum_word memaddr = 0x0000;
static libspectrum_dword mark_offset = 0xffffffff;
static int cursor_line_number = -1, cursor_char_offset;
static GtkTextBuffer *cursor_buffer;
static GtkTextBuffer *buffer_address, *buffer_hex, *buffer_data;
static GtkAdjustment *adjustment;
static gboolean
textview_wheel_scroll_event( GtkWidget *widget, GdkEvent *event, gpointer user_data )
{
GtkAdjustment *adjustment = user_data;
gdouble base, oldbase, base_limit;
base = oldbase = gtk_adjustment_get_value( adjustment );
switch( event->scroll.direction )
{
case GDK_SCROLL_UP:
base -= gtk_adjustment_get_page_increment( adjustment ) / 2;
break;
case GDK_SCROLL_DOWN:
base += gtk_adjustment_get_page_increment( adjustment ) / 2;
break;
#if GTK_CHECK_VERSION( 3, 4, 0 )
case GDK_SCROLL_SMOOTH:
{
static gdouble total_dy = 0;
gdouble dx, dy, page_size;
int delta;
if( gdk_event_get_scroll_deltas( event, &dx, &dy ) ) {
total_dy += dy;
page_size = gtk_adjustment_get_page_size( adjustment );
delta = total_dy * pow( page_size, 2.0 / 3.0 );
/* Is movement significative? */
if( delta ) {
base += delta;
total_dy = 0;
}
}
break;
}
#endif
default:
return FALSE;
}
if( base < 0 ) {
base = 0;
} else {
base_limit = gtk_adjustment_get_upper( adjustment ) -
gtk_adjustment_get_page_size( adjustment );
if( base > base_limit ) base = base_limit;
}
if( base != oldbase ) {
gtk_adjustment_set_value( adjustment, base );
}
return TRUE;
}
static gboolean
textview_key_press_event( GtkWidget *widget, GdkEventKey *event, gpointer user_data )
{
GtkAdjustment *adjustment = user_data;
GtkTextBuffer *text_buffer;
GtkTextIter iter;
GtkTextMark *mark;
gdouble base, oldbase, base_limit;
gdouble page_size, step_increment;
gint line, line_offset;
int num_rows, line_width;
base = oldbase = gtk_adjustment_get_value( adjustment );
page_size = gtk_adjustment_get_page_size( adjustment );
step_increment = gtk_adjustment_get_step_increment( adjustment );
num_rows = ( page_size + 1 ) / step_increment;
text_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( widget ) );
/* Get line width (includes CR/LF) */
line_width = gtk_text_buffer_get_char_count( text_buffer ) /
gtk_text_buffer_get_line_count( text_buffer );
/* Get row and offset of cursor */
mark = gtk_text_buffer_get_insert( text_buffer );
gtk_text_buffer_get_iter_at_mark( text_buffer, &iter, mark );
line = gtk_text_iter_get_line( &iter );
line_offset = gtk_text_iter_get_line_offset( &iter );
switch( event->keyval )
{
case GDK_KEY_Left:
if( line == 0 && line_offset == 0 ) {
base -= step_increment;
cursor_buffer = text_buffer;
cursor_line_number = line;
cursor_char_offset = line_width;
}
break;
case GDK_KEY_Right:
if( line == num_rows - 1 && line_offset == line_width ) {
base += step_increment;
cursor_buffer = text_buffer;
cursor_line_number = line;
cursor_char_offset = 0;
}
break;
case GDK_KEY_Up:
if( line == 0 ) {
base -= step_increment;
cursor_buffer = text_buffer;
cursor_line_number = 0;
cursor_char_offset = line_offset;
}
break;
case GDK_KEY_Down:
if( line == num_rows - 1 ) {
base += step_increment;
cursor_buffer = text_buffer;
cursor_line_number = line;
cursor_char_offset = line_offset;
}
break;
case GDK_KEY_Page_Up:
base -= gtk_adjustment_get_page_increment( adjustment );
cursor_buffer = text_buffer;
cursor_line_number = line;
cursor_char_offset = line_offset;
break;
case GDK_KEY_Page_Down:
base += gtk_adjustment_get_page_increment( adjustment );
cursor_buffer = text_buffer;
cursor_line_number = line;
cursor_char_offset = line_offset;
break;
default:
return FALSE;
}
if( base < 0 ) {
base = 0;
} else {
base_limit = gtk_adjustment_get_upper( adjustment ) - page_size;
if( base > base_limit ) base = base_limit;
}
if( base != oldbase ) {
gtk_adjustment_set_value( adjustment, base );
/* As we are simulating a full-filled text view, we need to move the cursor
position after page movement/redraw */
if( cursor_line_number >= 0 && cursor_line_number < VIEW_NUM_ROWS ) {
gtk_text_buffer_get_iter_at_line_offset( cursor_buffer, &iter,
cursor_line_number,
cursor_char_offset );
gtk_text_buffer_place_cursor( cursor_buffer, &iter );
cursor_line_number = -1;
}
return TRUE;
}
return FALSE;
}
static void
update_display( libspectrum_word base )
{
size_t i, j;
char buffer2[ 8 ];
char buffer3;
GtkTextIter iter_address, iter_hex, iter_data, start, end;
memaddr = base;
gtk_text_buffer_get_bounds( buffer_address, &start, &end );
gtk_text_buffer_delete( buffer_address, &start, &end );
gtk_text_buffer_get_bounds( buffer_hex, &start, &end );
gtk_text_buffer_delete( buffer_hex, &start, &end );
gtk_text_buffer_get_bounds( buffer_data, &start, &end );
gtk_text_buffer_delete( buffer_data, &start, &end );
gtk_text_buffer_get_start_iter( buffer_address, &iter_address );
gtk_text_buffer_get_start_iter( buffer_hex, &iter_hex );
gtk_text_buffer_get_start_iter( buffer_data, &iter_data );
for( i = 0; i < VIEW_NUM_ROWS; i++ ) {
if( i > 0 ) {
gtk_text_buffer_insert( buffer_address, &iter_address, "\n", -1 );
gtk_text_buffer_insert( buffer_hex, &iter_hex, "\n", -1 );
gtk_text_buffer_insert( buffer_data, &iter_data, "\n", -1 );
}
snprintf( buffer2, 8, "%04X", base );
gtk_text_buffer_insert( buffer_address, &iter_address, buffer2, -1 );
for( j = 0; j < VIEW_NUM_COLS; j++, base++ ) {
if( j > 0 )
gtk_text_buffer_insert( buffer_hex, &iter_hex, " ", -1 );
libspectrum_byte b = readbyte_internal( base );
snprintf( buffer2, 4, "%02X", b );
buffer3 = ( b >= 32 && b < 127 ) ? b : '.';
if( base != mark_offset ) {
gtk_text_buffer_insert( buffer_hex, &iter_hex, buffer2, -1 );
gtk_text_buffer_insert( buffer_data, &iter_data, &buffer3, 1 );
} else {
gtk_text_buffer_insert_with_tags_by_name( buffer_hex, &iter_hex,
buffer2, -1, "background_yellow", NULL );
gtk_text_buffer_insert_with_tags_by_name( buffer_data, &iter_data,
&buffer3, 1, "background_yellow", NULL );
}
}
}
gtk_text_buffer_get_bounds( buffer_address, &start, &end );
gtk_text_buffer_apply_tag_by_name( buffer_address, "monospace", &start, &end );
gtk_text_buffer_get_bounds( buffer_hex, &start, &end );
gtk_text_buffer_apply_tag_by_name( buffer_hex, "monospace", &start, &end );
gtk_text_buffer_get_bounds( buffer_data, &start, &end );
gtk_text_buffer_apply_tag_by_name( buffer_data, "monospace", &start, &end );
}
static void
scroller( GtkAdjustment *adjustment, gpointer user_data )
{
libspectrum_word base;
/* Drop the low bits before displaying anything */
base = gtk_adjustment_get_value( adjustment );
base &= 0xfff0;
update_display( base );
}
#if GTK_CHECK_VERSION( 3, 6, 0 )
static void
goto_offset( GtkWidget *widget GCC_UNUSED, gpointer user_data GCC_UNUSED )
{
long offset;
const gchar *entry;
char *endptr;
int base_num;
if( gtk_entry_get_text_length( GTK_ENTRY( widget ) ) == 0 )
return;
/* Parse address */
entry = gtk_entry_get_text( GTK_ENTRY( widget ) );
errno = 0;
base_num = ( g_str_has_prefix( entry, "0x" ) )? 16 : 10;
offset = strtol( entry, &endptr, base_num );
/* Validate address */
if( errno || offset < 0 || offset > 65535 || endptr == entry ||
*endptr != '\0' ) {
return;
}
mark_offset = offset;
gtk_adjustment_set_value( adjustment, offset );
}
#endif
void
menu_machine_memorybrowser( GtkAction *gtk_action GCC_UNUSED,
gpointer data GCC_UNUSED )
{
GtkWidget *dialog, *content_area, *scrollbar, *label, *offset;
GtkWidget *box, *box_address, *box_hex, *box_data, *box_data_horizontal;
GtkAccelGroup *accel_group;
GtkWidget *view_address, *view_hex, *view_data;
GtkTextTagTable *tag_table;
GtkTextTag *tag;
fuse_emulation_pause();
dialog = gtkstock_dialog_new( "Fuse - Memory Browser", NULL );
content_area = gtk_dialog_get_content_area( GTK_DIALOG( dialog ) );
/* Keyboard shortcuts */
accel_group = gtk_accel_group_new();
gtk_window_add_accel_group( GTK_WINDOW( dialog ), accel_group );
gtkstock_create_close( dialog, accel_group, NULL, TRUE );
#if GTK_CHECK_VERSION( 3, 6, 0 )
/* Go to offset */
box = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 8 );
offset = gtk_search_entry_new();
gtk_entry_set_width_chars( GTK_ENTRY( offset ), 7 );
gtk_entry_set_max_length( GTK_ENTRY( offset ), 6 );
gtk_box_pack_end( GTK_BOX( box ), offset, FALSE, FALSE, 0 );
label = gtk_label_new( "Go to offset" );
gtk_box_pack_end( GTK_BOX( box ), label, FALSE, FALSE, 0 );
gtk_box_pack_start( GTK_BOX( content_area ), box, FALSE, FALSE, 8 );
g_signal_connect( G_OBJECT( offset ), "activate",
G_CALLBACK( goto_offset ), NULL );
#endif
/* Create text buffers */
tag_table = gtk_text_tag_table_new();
tag = gtk_text_tag_new( "monospace" );
g_object_set( tag, "family", "monospace", NULL );
gtk_text_tag_table_add( tag_table, tag );
tag = gtk_text_tag_new( "background_yellow" );
g_object_set( tag, "background", "yellow",
"background-full-height", TRUE, NULL );
gtk_text_tag_table_add( tag_table, tag );
buffer_address = gtk_text_buffer_new( tag_table );
buffer_hex = gtk_text_buffer_new( tag_table );
buffer_data = gtk_text_buffer_new( tag_table );
/* Labels */
box = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 8 );
box_address = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
gtk_box_pack_start( GTK_BOX( box ), box_address, FALSE, FALSE, 0 );
box_hex = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
gtk_box_pack_start( GTK_BOX( box ), box_hex, FALSE, FALSE, 0 );
box_data = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
gtk_box_pack_start( GTK_BOX( box ), box_data, FALSE, FALSE, 0 );
gtk_box_pack_start( GTK_BOX( content_area ), box, FALSE, FALSE, 0 );
label = gtk_label_new( "Address" );
gtk_box_pack_start( GTK_BOX( box_address ), label, FALSE, FALSE, 0 );
label = gtk_label_new( "Hex" );
gtk_box_pack_start( GTK_BOX( box_hex ), label, FALSE, FALSE, 0 );
label = gtk_label_new( "Data" );
gtk_box_pack_start( GTK_BOX( box_data ), label, FALSE, FALSE, 0 );
/* Add views */
view_address = gtk_text_view_new_with_buffer( buffer_address );
gtk_text_view_set_editable( GTK_TEXT_VIEW( view_address ), FALSE );
gtk_text_view_set_left_margin( GTK_TEXT_VIEW( view_address ), 1 );
gtk_text_view_set_right_margin( GTK_TEXT_VIEW( view_address ), 1 );
view_hex = gtk_text_view_new_with_buffer( buffer_hex );
gtk_text_view_set_editable( GTK_TEXT_VIEW( view_hex ), FALSE );
gtk_text_view_set_left_margin( GTK_TEXT_VIEW( view_hex ), 1 );
gtk_text_view_set_right_margin( GTK_TEXT_VIEW( view_hex ), 1 );
view_data = gtk_text_view_new_with_buffer( buffer_data );
gtk_text_view_set_editable( GTK_TEXT_VIEW( view_data ), FALSE );
gtk_text_view_set_left_margin( GTK_TEXT_VIEW( view_data ), 1 );
gtk_text_view_set_right_margin( GTK_TEXT_VIEW( view_data ), 1 );
#if GTK_CHECK_VERSION( 3, 16, 0 )
gtk_text_view_set_monospace( GTK_TEXT_VIEW( view_address ), TRUE );
gtk_text_view_set_monospace( GTK_TEXT_VIEW( view_hex ), TRUE );
gtk_text_view_set_monospace( GTK_TEXT_VIEW( view_data ), TRUE );
#endif
gtk_box_pack_start( GTK_BOX( box_address ), view_address, FALSE, FALSE, 0 );
gtk_box_pack_start( GTK_BOX( box_hex ), view_hex, FALSE, FALSE, 0 );
box_data_horizontal = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 );
gtk_box_pack_start( GTK_BOX( box_data ), box_data_horizontal, FALSE, FALSE, 0 );
gtk_box_pack_start( GTK_BOX( box_data_horizontal ), view_data, FALSE, FALSE, 0 );
/* Scroll */
adjustment = GTK_ADJUSTMENT(
gtk_adjustment_new( memaddr, 0x0000, 0xffff, VIEW_NUM_COLS,
( VIEW_NUM_ROWS * VIEW_NUM_COLS ) / 2,
( VIEW_NUM_ROWS * VIEW_NUM_COLS ) - 1 ) );
g_signal_connect( adjustment, "value-changed", G_CALLBACK( scroller ), NULL );
scrollbar = gtk_scrollbar_new( GTK_ORIENTATION_VERTICAL, adjustment );
gtk_box_pack_start( GTK_BOX( box_data_horizontal ), scrollbar, FALSE, FALSE, 0 );
/* Allow scroll on text views */
g_signal_connect( view_address, "scroll-event",
G_CALLBACK( textview_wheel_scroll_event ), adjustment );
g_signal_connect( view_hex, "scroll-event",
G_CALLBACK( textview_wheel_scroll_event ), adjustment );
g_signal_connect( view_data, "scroll-event",
G_CALLBACK( textview_wheel_scroll_event ), adjustment );
g_signal_connect( view_address, "key-press-event",
G_CALLBACK( textview_key_press_event ), adjustment );
g_signal_connect( view_hex, "key-press-event",
G_CALLBACK( textview_key_press_event ), adjustment );
g_signal_connect( view_data, "key-press-event",
G_CALLBACK( textview_key_press_event ), adjustment );
update_display( memaddr );
gtk_widget_show_all( dialog );
gtk_main();
fuse_emulation_unpause();
return;
}