/* widget.c: Simple dialog boxes for all user interfaces. Copyright (c) 2001-2005 Matan Ziv-Av, Philip Kendall, Russell Marks $Id: widget.c,v 1.114 2007/02/02 16:22:00 pak21 Exp $ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Author contact information: E-mail: philip-fuse@shadowmagic.org.uk */ #include #ifdef USE_WIDGET #include #include #include #include #include #include #include #include #include #include #include "fuse.h" #include "display.h" #include "machine.h" #include "ui/uidisplay.h" #include "keyboard.h" #include "options.h" #include "periph.h" #include "pokefinder/pokefinder.h" #include "screenshot.h" #include "timer.h" #include "utils.h" #include "widget_internals.h" #ifdef WIN32 #include #endif static void widget_putpixel( int x, int y, int colour ); /* Bitmap font storage */ typedef struct { libspectrum_byte bitmap[15], left, width, defined; } widget_font_character; static widget_font_character *widget_font[1] = {0}; static const widget_font_character default_invalid = { { 0x7E, 0xDF, 0x9F, 0xB5, 0xA5, 0x8F, 0xDF, 0x7E }, 0, 8, 1 }; /* "(?)" (inv) */ static const widget_font_character default_unknown = { { 0x7C, 0xDE, 0xBE, 0xAA, 0xDE, 0x7C }, 1, 6, 1 }; /* "(?)" */ static const widget_font_character default_keyword = { { 0x7C, 0x82, 0xEE, 0xD6, 0xBA, 0x7C }, 1, 6, 1 }; /* "(K)" */ /* The current widget keyhandler */ widget_keyhandler_fn widget_keyhandler; /* The data used for recursive widgets */ typedef struct widget_recurse_t { widget_type type; /* Which type of widget are we? */ void *data; /* What data were we passed? */ int finished; /* Have we finished this widget yet? */ } widget_recurse_t; static widget_recurse_t widget_return[10]; /* The stack to recurse on */ /* The settings used whilst playing with an options dialog box */ settings_info widget_options_settings; static int widget_read_font( const char *filename ) { int fd; utils_file file; int error; int i; fd = utils_find_auxiliary_file( filename, UTILS_AUXILIARY_WIDGET ); if( fd == -1 ) { ui_error( UI_ERROR_ERROR, "couldn't find font file '%s'", filename ); return 1; } error = utils_read_fd( fd, filename, &file ); if( error ) return error; i = 0; while( i < file.length ) { int code, page, left, width; if( i + 3 > file.length ) { ui_error( UI_ERROR_ERROR, "font contains invalid character" ); utils_close_file( &file ); return 1; } code = file.buffer[i]; page = file.buffer[i+1]; if( page == 0 && ( code == 0xA3 || ( code < 0x7F && code != 0x60 ) ) ) { left = file.buffer[i+2] & 7; } else { left = -1; } width = file.buffer[i+2] >> 4 & 15; /* weed out invalid character codes and misdefined characters */ if( page != 0 /* we don't currently have more than page 0 */ || i + 3 + width > file.length || (left >= 0 && left + width > 8) ) { ui_error( UI_ERROR_ERROR, "font contains invalid character" ); utils_close_file( &file ); return 1; } if( !widget_font[page] ) { widget_font[page] = calloc( 256, sizeof( widget_font_character ) ); if( !widget_font[page] ) { ui_error( UI_ERROR_ERROR, "out of memory" ); utils_close_file( &file ); return 1; } } widget_font[page][code].defined = 1; widget_font[page][code].left = left < 0 ? 0 : left; widget_font[page][code].width = width ? width : 3; memcpy( &widget_font[page][code].bitmap, &file.buffer[i+3], width ); i += 3 + width; } error = utils_close_file( &file ); if( error ) return error; return 0; } static const widget_font_character * widget_char( int pp ) { if( pp < 0 || pp >= 256 ) return &default_invalid; if( !widget_font[pp >> 8] || !widget_font[pp >> 8][pp & 255].defined ) return &default_unknown; return &widget_font[ pp >> 8 ][ pp & 255 ]; } size_t widget_left_one_char( const char *s, size_t index ) { if( index == -1 ) index = strlen( s ); if( !index ) return 0; return index - 1; } static int printchar( int x, int y, int col, int ch ) { int mx, my; const widget_font_character *bitmap = widget_char( ch ); for( mx = 0; mx < bitmap->width; mx++ ) { int b = bitmap->bitmap[mx]; for( my = 0; my < 8; my++ ) if( b & 128 >> my ) widget_putpixel( x + mx, y + my, col ); } return x + bitmap->width + 1; } int widget_printstring( int x, int y, int col, const char *s ) { int c; int shadow = 0; if( !s ) return x; while( x < 256 + DISPLAY_BORDER_ASPECT_WIDTH && ( c = *(libspectrum_byte *)s++ ) != 0 ) { if( c && c < 9 ) { col = c - 1; continue; } if( c < 18 ) { shadow = c - 9; continue; } if( shadow && col ) { printchar( x - 1, y, shadow - 1, c ); printchar( x + 1, y, shadow - 1, c ); printchar( x, y - 1, shadow - 1, c ); printchar( x, y + 1, shadow - 1, c ); printchar( x + 1, y + 1, shadow - 1, c ); x = printchar( x, y, col ^ 8, c ); } else x = printchar( x, y, col, c ); } return x; } int widget_printstring_fixed( int x, int y, int col, const char *s ) { int c; if( !s ) return x; while( x < 256 + DISPLAY_BORDER_ASPECT_WIDTH && ( c = *(libspectrum_byte *)s++ ) != 0 ) { widget_printchar_fixed(x, y, col, c); ++x; } return x; } void widget_printchar_fixed( int x, int y, int col, int c ) { int mx, my; int inverse = 0; const widget_font_character *bitmap; x *= 8; y *= 8; if( c < 128 ) bitmap = widget_char( c ); else if( c < 144 ) { if( c & 1 ) widget_rectangle( x + 4, y , 4, 4, col ); if( c & 2 ) widget_rectangle( x , y , 4, 4, col ); if( c & 4 ) widget_rectangle( x + 4, y + 4, 4, 4, col ); if( c & 8 ) widget_rectangle( x , y + 4, 4, 4, col ); return; } else if( c < 165 ) { inverse = 1; bitmap = widget_char( c - 144 + 'A' ); } else { bitmap = &default_keyword; } x += bitmap->left; for( mx = 0; mx < bitmap->width; mx++ ) { int b = bitmap->bitmap[mx]; if( inverse ) b = ~b; for( my = 0; my < 8; my++ ) if( b & 128 >> my ) widget_putpixel( x + mx, y + my, col ); } } void widget_print_title( int y, int col, const char *s ) { char buffer[128]; snprintf( buffer, sizeof( buffer ), "\x0A%s", s ); widget_printstring( 128 - widget_stringwidth( buffer ) / 2, y, col, buffer ); } void widget_printstring_right( int x, int y, int col, const char *s ) { widget_printstring( x - widget_stringwidth( s ), y, col, s ); } size_t widget_stringwidth( const char *s ) { return widget_substringwidth( s, UINT_MAX ); } size_t widget_substringwidth( const char *s, size_t count ) { size_t width = 0; int c; if( !s ) return 0; while( count-- && (c = *(libspectrum_byte *)s++) != 0 ) { if( c < 18 ) continue; width += widget_char( c )->width + 1; } return width - 1; } size_t widget_charwidth( int c ) { return widget_char( c )->width; } void widget_rectangle( int x, int y, int w, int h, int col ) { int mx, my; for( my = 0; my < h; my++ ) for( mx = 0; mx < w; mx++ ) widget_putpixel( x + mx, y + my, col ); } void widget_print_checkbox( int x, int y, int value ) { static const int CHECK_COLOR=7; int z; y += 2; x += 6; widget_rectangle( x, y - 1, 3, 3, WIDGET_COLOUR_BACKGROUND ); widget_rectangle( x - 5, y, 5, 5, 0 ); widget_rectangle( x - 4, y + 1, 3, 3, WIDGET_COLOUR_BACKGROUND ); if( value ) { /* checked */ for( z = -1; z < 3; z++ ) { widget_putpixel( x - z, y + z, CHECK_COLOR ); widget_putpixel( x - z + 1, y + z, CHECK_COLOR ); } widget_putpixel( x - z + 1, y + z, CHECK_COLOR ); widget_putpixel( x - z, y + z - 1, CHECK_COLOR ); widget_putpixel( x - z, y + z - 2, CHECK_COLOR ); widget_putpixel( x - z - 1, y + z - 2, CHECK_COLOR ); } } /* Arrows for any scrollable widget */ void widget_up_arrow( int x, int y, int colour ) { int i, j; x = x * 8; y = y * 8 + 7; for( j = 6; j; --j ) { for( i = (j + 1) / 2; i < 4; ++i ) { widget_putpixel( x + i, y - j, colour ); widget_putpixel( x + 7 - i, y - j, colour ); } } } void widget_down_arrow( int x, int y, int colour ) { int i, j; x = x * 8; y = y * 8; for( j = 6; j; --j ) { for( i = (j + 1) / 2; i < 4; ++i ) { widget_putpixel( x + i, y + j, colour ); widget_putpixel( x + 7 - i, y + j, colour ); } } } /* Force screen rasters y to (y+h) inclusive to be redrawn */ void widget_display_rasters( int y, int h ) { int scale = machine_current->timex ? 2 : 1; uidisplay_area( 0, scale * ( DISPLAY_BORDER_HEIGHT + y ), scale * DISPLAY_ASPECT_WIDTH, scale * h ); uidisplay_frame_end(); } /* Global initialisation/end routines */ int widget_init( void ) { int error; error = widget_read_font( "fuse.font" ); if( error ) return error; widget_filenames = NULL; widget_numfiles = 0; ui_menu_activate( UI_MENU_ITEM_AY_LOGGING, 0 ); ui_menu_activate( UI_MENU_ITEM_FILE_MOVIES_RECORDING, 0 ); ui_menu_activate( UI_MENU_ITEM_MACHINE_PROFILER, 0 ); ui_menu_activate( UI_MENU_ITEM_RECORDING, 0 ); ui_menu_activate( UI_MENU_ITEM_RECORDING_ROLLBACK, 0 ); return 0; } int widget_end( void ) { size_t i; if( widget_filenames ) { for( i=0; iname ); free( widget_filenames[i] ); } free( widget_filenames ); } return 0; } /* General widget routine */ /* We don't start in a widget */ int widget_level = -1; int widget_do( widget_type which, void *data ) { int error; /* If we don't have a UI yet, we can't output widgets */ if( !display_ui_initialised ) return 1; /* We're now one widget level deeper */ widget_level++; /* Store what type of widget we are and what data we were given */ widget_return[widget_level].type = which; widget_return[widget_level].data = data; /* Draw this widget */ error = widget_data[ which ].draw( data ); /* Set up the keyhandler for this widget */ widget_keyhandler = widget_data[which].keyhandler; /* Process this widget until it returns */ widget_return[widget_level].finished = 0; while( ! widget_return[widget_level].finished ) { /* Go to sleep for a bit */ timer_sleep_ms( 10 ); /* Process any events */ ui_event(); } /* Do any post-widget processing if it exists */ if( widget_data[which].finish ) { widget_data[which].finish( widget_return[widget_level].finished ); } /* Now return to the previous widget level */ widget_level--; if( widget_level >= 0 ) { /* If we're going back to another widget, set up its keyhandler and draw it again, unless it's already finished */ if( ! widget_return[widget_level].finished ) { widget_keyhandler = widget_data[ widget_return[widget_level].type ].keyhandler; widget_data[ widget_return[widget_level].type ].draw( widget_return[widget_level].data ); } } else { /* Refresh the Spectrum's display, including the border */ display_refresh_all(); } return 0; } /* End the currently running widget */ int widget_end_widget( widget_finish_state state ) { widget_return[ widget_level ].finished = state; return 0; } /* End all currently running widgets */ int widget_end_all( widget_finish_state state ) { int i; for( i=0; i<=widget_level; i++ ) widget_return[i].finished = state; return 0; } void widget_finish( void ) { widget_end_all( WIDGET_FINISHED_OK ); } int widget_dialog( int x, int y, int width, int height ) { widget_rectangle( 8*x, 8*y, 8*width, 8*height, WIDGET_COLOUR_BACKGROUND ); return 0; } int widget_dialog_with_border( int x, int y, int width, int height ) { int i; widget_rectangle( 8*x-5, 8*y, 8*width+10, 8*height, WIDGET_COLOUR_BACKGROUND ); for( i = 0; i < 5; ++i) { widget_rectangle (8*x+i-5, 8*y-i-1, 8*width+10-2*i, 1, WIDGET_COLOUR_BACKGROUND ); widget_rectangle (8*x+i-5, 8*(y+height)+i, 8*width+10-2*i, 1, WIDGET_COLOUR_BACKGROUND ); } for( i=(8*x)-1; i<(8*(x+width))+1; i++ ) { widget_putpixel( i, 8 * y - 4, WIDGET_COLOUR_FOREGROUND ); widget_putpixel( i, 8 * ( y + height ) + 3, WIDGET_COLOUR_FOREGROUND ); } for( i=(8*y)-1; i<(8*(y+height))+1; i++ ) { widget_putpixel( 8 * x - 4, i, WIDGET_COLOUR_FOREGROUND ); widget_putpixel( 8 * ( x + width ) + 3, i, WIDGET_COLOUR_FOREGROUND ); } for( i=0; i<2; i++ ) { widget_putpixel( 8 * x - 3 + i, 8 * y - 2 - i, WIDGET_COLOUR_FOREGROUND ); widget_putpixel( 8 * ( x + width ) + 2 - i, 8 * y - 2 - i, WIDGET_COLOUR_FOREGROUND ); widget_putpixel( 8 * x - 3 + i, 8 * ( y + height ) + 1 + i, WIDGET_COLOUR_FOREGROUND ); widget_putpixel( 8 * ( x + width ) + 2 - i, 8 * ( y + height ) + 1 + i, WIDGET_COLOUR_FOREGROUND ); } widget_display_lines( y-1, height+2 ); return 0; } static void widget_putpixel( int x, int y, int colour ) { uidisplay_putpixel( x + DISPLAY_BORDER_ASPECT_WIDTH, y + DISPLAY_BORDER_HEIGHT, colour ); } /* General functions used by the options dialogs */ static int widget_options_print_label( int number, const char *string ); static int widget_options_print_data( int number, const char *string ); int widget_options_print_option( int number, const char* string, int value ) { widget_options_print_label( number, string ); widget_options_print_value( number, value ); return 0; } static int widget_options_print_label( int number, const char *string ) { char buffer[128]; size_t l, w; snprintf( buffer, sizeof( buffer ), "%s", string ); l = strlen( buffer ); if( l >= sizeof( buffer ) ) l = sizeof( buffer ) - 1; while( ( w = widget_substringwidth( string, l ) ) >= 224 ) --l; buffer[l] = '\0'; w = widget_printstring( 17, number*8+28, WIDGET_COLOUR_FOREGROUND, buffer ) - 1; while ((w += 3) < 240) widget_putpixel (w, number*8+35, 0); return 0; } int widget_options_print_value( int number, int value ) { widget_rectangle( 233, number * 8 + 28, 7, 8, WIDGET_COLOUR_BACKGROUND ); widget_print_checkbox( 233, number * 8 + 28, value ); widget_display_rasters( number * 8 + 28, 8 ); return 0; } static int widget_options_print_data( int number, const char *string ) { size_t width = widget_stringwidth( string ); widget_rectangle( 240 - width - 2, number * 8 + 28, width + 2, 8, WIDGET_COLOUR_BACKGROUND ); widget_printstring( 240 - width, number * 8 + 28, WIDGET_COLOUR_FOREGROUND, string ); widget_display_rasters( number * 8 + 28, 8 ); return 0; } int widget_options_print_entry( int number, const char *prefix, int value, const char *suffix ) { char buffer[128]; widget_options_print_label( number, prefix ); snprintf( buffer, sizeof( buffer ), "%d %s", value, suffix ); return widget_options_print_data( number, buffer ); } int widget_options_finish( widget_finish_state finished ) { int error; /* If we exited normally, actually set the options */ if( finished == WIDGET_FINISHED_OK ) { error = settings_copy( &settings_current, &widget_options_settings ); settings_free( &widget_options_settings ); memset( &widget_options_settings, 0, sizeof( settings_info ) ); if( error ) return error; /* Bring the peripherals list into sync with the new options */ periph_update(); } return 0; } /* The widgets actually available. Make sure the order here matches the order defined in enum widget_type (widget.h) */ widget_t widget_data[] = { { widget_filesel_load_draw, widget_filesel_finish, widget_filesel_keyhandler }, { widget_filesel_save_draw, widget_filesel_finish, widget_filesel_keyhandler }, { widget_general_draw, widget_options_finish, widget_general_keyhandler }, { widget_picture_draw, NULL, widget_picture_keyhandler }, { widget_menu_draw, NULL, widget_menu_keyhandler }, { widget_select_draw, widget_select_finish, widget_select_keyhandler }, { widget_sound_draw, widget_options_finish, widget_sound_keyhandler }, { widget_error_draw, NULL, widget_error_keyhandler }, { widget_rzx_draw, widget_options_finish, widget_rzx_keyhandler }, { widget_browse_draw, widget_browse_finish, widget_browse_keyhandler }, { widget_text_draw, widget_text_finish, widget_text_keyhandler }, { widget_debugger_draw, NULL, widget_debugger_keyhandler }, { widget_pokefinder_draw, NULL, widget_pokefinder_keyhandler }, { widget_memory_draw, NULL, widget_memory_keyhandler }, { widget_roms_draw, widget_roms_finish, widget_roms_keyhandler }, { widget_peripherals_draw, widget_options_finish, widget_peripherals_keyhandler }, { widget_query_draw, NULL, widget_query_keyhandler }, { widget_query_save_draw,NULL, widget_query_save_keyhandler }, }; #ifndef UI_SDL /* The statusbar handling functions */ /* TODO: make these do something useful */ int ui_statusbar_update( ui_statusbar_item item, ui_statusbar_state state ) { return 0; } int ui_statusbar_update_speed( float speed ) { return 0; } #endif /* #ifndef UI_SDL */ /* Tape browser update function. The dialog box is created every time it is displayed, so no need to do anything here */ int ui_tape_browser_update( ui_tape_browser_update_type change, libspectrum_tape_block *block ) { return 0; } ui_confirm_save_t ui_confirm_save( const char *message ) { if( widget_do( WIDGET_TYPE_QUERY_SAVE, (void *) message ) ) return UI_CONFIRM_SAVE_CANCEL; return widget_query.save; } /* FIXME: make this do something useful */ int ui_get_rollback_point( GSList *points ) { return -1; } static const char *joystick_connection[] = { "None", "Keyboard", "Joystick 1", "Joystick 2", }; ui_confirm_joystick_t ui_confirm_joystick( libspectrum_joystick libspectrum_type, int inputs ) { widget_select_t info; int error; char title[80]; if( !settings_current.joy_prompt ) return UI_CONFIRM_JOYSTICK_NONE; snprintf( title, sizeof( title ), "Configure %s joystick", libspectrum_joystick_name( libspectrum_type ) ); info.title = title; info.options = joystick_connection; info.count = sizeof( joystick_connection ) / sizeof( joystick_connection[0] ); info.current = UI_CONFIRM_JOYSTICK_NONE; error = widget_do( WIDGET_TYPE_SELECT, &info ); if( error ) return UI_CONFIRM_JOYSTICK_NONE; return (ui_confirm_joystick_t)info.result; } int ui_widgets_reset( void ) { pokefinder_clear(); return 0; } #endif /* #ifdef USE_WIDGET */