1 /* win32ui.c: Win32 routines for dealing with the user interface
2 Copyright (c) 2003-2007 Marek Januszewski, Philip Kendall, Stuart Brady
3
4 $Id: win32ui.c 4968 2013-05-19 16:11:17Z zubzero $
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 Author contact information:
21
22 E-mail: philip-fuse@shadowmagic.org.uk
23
24 */
25
26 #include <config.h>
27 #include <tchar.h>
28
29 #include "debugger/debugger.h"
30 #include "display.h"
31 #include "fuse.h"
32 #include "keyboard.h"
33 #include "menu.h"
34 #include "menu_data.h"
35 #include "peripherals/joystick.h"
36 #include "psg.h"
37 #include "rzx.h"
38 #include "screenshot.h"
39 #include "select_template.h"
40 #include "settings.h"
41 #include "snapshot.h"
42 #include "tape.h"
43 #include "timer/timer.h"
44 #include "ui/ui.h"
45 #include "utils.h"
46 #include "win32internals.h"
47 #include "win32joystick.h"
48
49 /* fuse_hPrevInstance is needed only to register window class */
50 static HINSTANCE fuse_hPrevInstance;
51
52 /* specifies how the main window should be shown: minimized, maximized, etc */
53 static int fuse_nCmdShow;
54
55 /* handle to accelartors/keyboard shortcuts */
56 static HACCEL hAccels;
57
58 /* machine select dialog's font object */
59 static HFONT h_ms_font = NULL;
60
61 /* monospaced font to use with debugger and memory browser */
62 static HFONT monospaced_font = NULL;
63
64 /* True if we were paused via the Machine/Pause menu item */
65 static int paused = 0;
66
67 /* this helps pause fuse while the main window is minimized */
68 static int size_paused = 0;
69
70 /* this helps to finalize some blocking operations */
71 static int exit_process_messages = 0;
72
73 /* Structure used by the radio button selection widgets (eg the
74 graphics filter selectors and Machine/Select) */
75 typedef struct win32ui_select_info {
76
77 int length;
78 int selected;
79 const char **labels;
80 TCHAR *dialog_title;
81
82 } win32ui_select_info;
83
84 static BOOL win32ui_make_menu( void );
85
86 static int win32ui_lose_focus( HWND hWnd, WPARAM wParam, LPARAM lParam );
87 static int win32ui_gain_focus( HWND hWnd, WPARAM wParam, LPARAM lParam );
88
89 static int win32ui_window_paint( HWND hWnd, WPARAM wParam, LPARAM lParam );
90 static int win32ui_window_resize( HWND hWnd, WPARAM wParam, LPARAM lParam );
91 static BOOL win32ui_window_resizing( HWND hWnd, WPARAM wParam, LPARAM lParam );
92
93 static int
94 selector_dialog( win32ui_select_info *items );
95
96 #define DIM(X) sizeof((X)) / sizeof((X)[0])
97
98 static void
handle_drop(HDROP hDrop)99 handle_drop( HDROP hDrop )
100 {
101 size_t bufsize;
102 char *namebuf;
103
104 /* Check that only one file was dropped */
105 if( DragQueryFile( hDrop, ~0UL, NULL, 0 ) == 1) {
106 bufsize = DragQueryFile( hDrop, 0, NULL, 0 ) + 1;
107 if( ( namebuf = malloc( bufsize ) ) ) {
108 DragQueryFile( hDrop, 0, namebuf, bufsize );
109
110 fuse_emulation_pause();
111
112 utils_open_file( namebuf, tape_can_autoload(), NULL );
113
114 free( namebuf );
115
116 display_refresh_all();
117
118 fuse_emulation_unpause();
119 }
120 }
121 DragFinish( hDrop );
122 }
123
124 static LRESULT WINAPI
fuse_window_proc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)125 fuse_window_proc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
126 {
127 switch( msg ) {
128
129 #if defined USE_JOYSTICK && !defined HAVE_JSW_H
130
131 case WM_CREATE:
132 if( joysticks_supported > 0 )
133 if( joySetCapture( hWnd, JOYSTICKID1, 0, FALSE ) )
134 ui_error( UI_ERROR_ERROR, "Couldn't start capture for joystick 1" );
135 if( joysticks_supported > 1 )
136 if( joySetCapture( hWnd, JOYSTICKID2, 0, FALSE ) )
137 ui_error( UI_ERROR_ERROR, "Couldn't start capture for joystick 2" );
138 break;
139
140 #endif /* if defined USE_JOYSTICK && !defined HAVE_JSW_H */
141
142 case WM_COMMAND:
143 if( ! handle_menu( LOWORD( wParam ), hWnd ) )
144 return 0;
145 break;
146
147 case WM_DROPFILES:
148 handle_drop( ( HDROP )wParam );
149 return 0;
150
151 case WM_CLOSE:
152 menu_file_exit( 0 );
153 return 0;
154
155 case WM_KEYDOWN:
156 win32keyboard_keypress( wParam, lParam );
157 return 0;
158
159 case WM_KEYUP:
160 win32keyboard_keyrelease( wParam, lParam );
161 return 0;
162
163 case WM_PAINT:
164 if( ! win32ui_window_paint( hWnd, wParam, lParam ) )
165 return 0;
166 break;
167
168 case WM_SIZING:
169 if( win32ui_window_resizing( hWnd, wParam, lParam ) )
170 return TRUE;
171 break;
172
173 case WM_SIZE:
174 if( ! win32ui_window_resize( hWnd, wParam, lParam ) )
175 return 0;
176 break;
177
178 case WM_DRAWITEM:
179 if( wParam == ID_STATUSBAR ) {
180 win32statusbar_redraw( hWnd, lParam );
181 return TRUE;
182 }
183 break;
184
185 case WM_DESTROY:
186 fuse_exiting = 1;
187 PostQuitMessage( 0 );
188
189 /* Stop the paused state to allow us to exit (occurs from main
190 emulation loop) */
191 if( paused ) menu_machine_pause( 0 );
192 return 0;
193
194 case WM_ENTERMENULOOP:
195 case WM_ENTERSIZEMOVE:
196 {
197 fuse_emulation_pause();
198 return 0;
199 }
200
201 case WM_EXITMENULOOP:
202 case WM_EXITSIZEMOVE:
203 {
204 fuse_emulation_unpause();
205 return 0;
206 }
207
208 case WM_LBUTTONUP:
209 win32mouse_button( 1, 0 );
210 return 0;
211
212 case WM_LBUTTONDOWN:
213 win32mouse_button( 1, 1 );
214 return 0;
215
216 case WM_MBUTTONUP:
217 win32mouse_button( 2, 0 );
218 return 0;
219
220 case WM_MBUTTONDOWN:
221 win32mouse_button( 2, 1 );
222 return 0;
223
224 case WM_RBUTTONUP:
225 win32mouse_button( 3, 0 );
226 return 0;
227
228 case WM_RBUTTONDOWN:
229 win32mouse_button( 3, 1 );
230 return 0;
231
232 case WM_MOUSEMOVE:
233 win32mouse_position( lParam );
234 return 0;
235
236 case WM_SETCURSOR:
237 /* prevent the cursor from being redrawn if fuse has grabbed the mouse */
238 if( ui_mouse_grabbed )
239 return TRUE;
240 else
241 return( DefWindowProc( hWnd, msg, wParam, lParam ) );
242
243 case WM_ACTIVATE:
244 if( ( LOWORD( wParam ) == WA_ACTIVE ) ||
245 ( LOWORD( wParam ) == WA_CLICKACTIVE ) )
246 win32ui_gain_focus( hWnd, wParam, lParam );
247 else if( LOWORD( wParam ) == WA_INACTIVE )
248 win32ui_lose_focus( hWnd, wParam, lParam );
249 /* We'll call DefWindowProc to get keyboard focus when debugger window
250 is open and inactive */
251 break;
252
253 case WM_USER_EXIT_PROCESS_MESSAGES:
254 /* Odd case when message loop is overridden by a modal dialog. This
255 should not be caught here, so we delay this notification */
256 exit_process_messages++;
257 return 0;
258
259 case WM_ERASEBKGND:
260 /* Improves speed and avoid flickering when main window is invalidated by
261 another window */
262 return TRUE;
263
264 #if defined USE_JOYSTICK && !defined HAVE_JSW_H
265
266 case MM_JOY1BUTTONDOWN:
267 win32joystick_buttonevent( 0, 1, wParam );
268 break;
269
270 case MM_JOY1BUTTONUP:
271 win32joystick_buttonevent( 0, 0, wParam );
272 break;
273
274 case MM_JOY2BUTTONDOWN:
275 win32joystick_buttonevent( 1, 1, wParam );
276 break;
277
278 case MM_JOY2BUTTONUP:
279 win32joystick_buttonevent( 1, 0, wParam );
280 break;
281
282 case MM_JOY1MOVE:
283 win32joystick_move( 0, LOWORD( lParam ), HIWORD( lParam ) );
284 break;
285
286 case MM_JOY2MOVE:
287 win32joystick_move( 1, LOWORD( lParam ), HIWORD( lParam ) );
288 break;
289
290 #endif /* if defined USE_JOYSTICK && !defined HAVE_JSW_H */
291
292 }
293 return( DefWindowProc( hWnd, msg, wParam, lParam ) );
294 }
295
296 /* this is where windows program begins */
297 int WINAPI
WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)298 WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
299 int nCmdShow )
300 {
301 /* remember those values for window creation in ui_init */
302 fuse_hInstance = hInstance;
303 fuse_nCmdShow = nCmdShow;
304 fuse_hPrevInstance = hPrevInstance;
305
306 return fuse_main(__argc, __argv);
307 /* FIXME: how do deal with returning wParam */
308 }
309
310 int
ui_init(int * argc,char *** argv)311 ui_init( int *argc, char ***argv )
312 {
313 /* register window class */
314 WNDCLASS wc;
315
316 if( !fuse_hPrevInstance ) {
317 wc.lpszClassName = "Fuse";
318 wc.lpfnWndProc = fuse_window_proc;
319 wc.style = CS_OWNDC;
320 wc.hInstance = fuse_hInstance;
321 wc.hIcon = LoadIcon( fuse_hInstance, "win32_icon" );
322 wc.hCursor = LoadCursor( NULL, IDC_ARROW );
323 wc.hbrBackground = (HBRUSH)( COLOR_WINDOW+1 );
324 wc.lpszMenuName = "win32_menu";
325 wc.cbClsExtra = 0;
326 wc.cbWndExtra = 0;
327
328 if( !RegisterClass( &wc ) )
329 return 0;
330 }
331
332 /* create the window */
333 fuse_hWnd = CreateWindow( "Fuse", "Fuse", WS_OVERLAPPED | WS_CAPTION |
334 WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_CLIPCHILDREN,
335 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
336 NULL, NULL, fuse_hInstance, NULL );
337
338 /* init windows controls such as the status bar */
339 InitCommonControls();
340
341 /* menu is created in rc file, but we still need to set initial state */
342 win32ui_make_menu();
343
344 /* load keyboard shortcuts */
345 hAccels = LoadAccelerators( fuse_hInstance, "win32_accel" );
346
347 /* status bar */
348 win32statusbar_create( fuse_hWnd );
349
350 /* set the initial size of the drawing area */
351 RECT wr, cr, statr;
352 int w_ofs, h_ofs;
353
354 GetWindowRect( fuse_hWnd, &wr );
355 GetClientRect( fuse_hWnd, &cr );
356 GetClientRect( fuse_hStatusWindow, &statr );
357
358 w_ofs = ( wr.right - wr.left ) - ( cr.right - cr.left );
359 h_ofs = ( wr.bottom - wr.top ) - ( cr.bottom - cr.top );
360 if( settings_current.statusbar ) h_ofs += ( statr.bottom - statr.top );
361
362 MoveWindow( fuse_hWnd, wr.left, wr.top,
363 DISPLAY_ASPECT_WIDTH + w_ofs,
364 DISPLAY_SCREEN_HEIGHT + h_ofs,
365 FALSE );
366
367 /* init the display area */
368 if( win32display_init() ) return 1;
369
370 /* show the window finally */
371 ShowWindow( fuse_hWnd, fuse_nCmdShow );
372 UpdateWindow( fuse_hWnd );
373
374 /* window will accept dragging and dropping */
375 DragAcceptFiles( fuse_hWnd, TRUE );
376
377 ui_mouse_present = 1;
378
379 return 0;
380 }
381
382 static BOOL
win32ui_make_menu(void)383 win32ui_make_menu( void )
384 {
385 /* Start various menus in the 'off' state */
386 ui_menu_activate( UI_MENU_ITEM_AY_LOGGING, 0 );
387 ui_menu_activate( UI_MENU_ITEM_FILE_MOVIE_RECORDING, 0 );
388 ui_menu_activate( UI_MENU_ITEM_MACHINE_PROFILER, 0 );
389 ui_menu_activate( UI_MENU_ITEM_RECORDING, 0 );
390 ui_menu_activate( UI_MENU_ITEM_RECORDING_ROLLBACK, 0 );
391 ui_menu_activate( UI_MENU_ITEM_TAPE_RECORDING, 0 );
392
393 return FALSE;
394 }
395
396 int
ui_event(void)397 ui_event( void )
398 {
399 win32ui_process_messages( 1 );
400
401 return 0;
402 }
403
404 int
ui_end(void)405 ui_end( void )
406 {
407 int error;
408
409 error = win32display_end(); if( error ) return error;
410
411 /* close the monospaced font handle */
412 if( monospaced_font ) {
413 DeleteObject( monospaced_font );
414 monospaced_font = NULL;
415 }
416
417 return 0;
418 }
419
420 /* Create a dialog box with the given error message */
421 int
ui_error_specific(ui_error_level severity,const char * message)422 ui_error_specific( ui_error_level severity, const char *message )
423 {
424 /* If we don't have a UI yet, we can't output widgets */
425 if( !display_ui_initialised ) return 0;
426
427 switch( severity ) {
428
429 case UI_ERROR_INFO:
430 MessageBox( fuse_hWnd, message, "Fuse - Info", MB_ICONINFORMATION | MB_OK );
431 break;
432 case UI_ERROR_WARNING:
433 MessageBox( fuse_hWnd, message, "Fuse - Warning", MB_ICONWARNING | MB_OK );
434 break;
435 case UI_ERROR_ERROR:
436 MessageBox( fuse_hWnd, message, "Fuse - Error", MB_ICONERROR | MB_OK );
437 break;
438 default:
439 MessageBox( fuse_hWnd, message, "Fuse - (Unknown Error Level)",
440 MB_ICONINFORMATION | MB_OK );
441 break;
442
443 }
444
445 return 0;
446 }
447
448 /* The callbacks used by various routines */
449
450 static int
win32ui_lose_focus(HWND hWnd,WPARAM wParam,LPARAM lParam)451 win32ui_lose_focus( HWND hWnd, WPARAM wParam, LPARAM lParam )
452 {
453 keyboard_release_all();
454 ui_mouse_suspend();
455 return 0;
456 }
457
458 static int
win32ui_gain_focus(HWND hWnd,WPARAM wParam,LPARAM lParam)459 win32ui_gain_focus( HWND hWnd, WPARAM wParam, LPARAM lParam )
460 {
461 ui_mouse_resume();
462 return 0;
463 }
464
465 /* Called by the menu when File/Exit selected */
466 void
menu_file_exit(int action)467 menu_file_exit( int action )
468 {
469 /* FIXME: this should really be sending WM_CLOSE, not duplicate code */
470 if( win32ui_confirm( "Exit Fuse?" ) ) {
471
472 if( menu_check_media_changed() ) return;
473
474 DestroyWindow(fuse_hWnd);
475 }
476 }
477
478 /* Select a graphics filter from those for which `available' returns
479 true */
480 scaler_type
menu_get_scaler(scaler_available_fn selector)481 menu_get_scaler( scaler_available_fn selector )
482 {
483 scaler_type selected_scaler = SCALER_NUM;
484 win32ui_select_info items;
485 int count, i, selection;
486 scaler_type scaler;
487
488 /* Get count of currently applicable scalars first */
489 count = 0;
490 for( scaler = 0; scaler < SCALER_NUM; scaler++ ) {
491 if( selector( scaler ) ) count++;
492 }
493
494 /* Populate win32ui_select_info */
495 items.dialog_title = TEXT( "Fuse - Select Scaler" );
496 items.labels = malloc( count * sizeof( char * ) );
497 items.length = count;
498
499 /* Populate the labels with currently applicable scalars */
500 count = 0;
501
502 for( scaler = 0; scaler < SCALER_NUM; scaler++ ) {
503
504 if( !selector( scaler ) ) continue;
505
506 items.labels[ count ] = scaler_name( scaler );
507
508 if( current_scaler == scaler ) {
509 items.selected = count;
510 }
511
512 count++;
513 }
514
515 /* Start the selection dialog box */
516 selection = selector_dialog( &items );
517
518 if( selection >= 0 ) {
519 /* Apply the selected scalar */
520 count = 0;
521
522 for( i = 0; i < SCALER_NUM; i++ ) {
523 if( !selector( i ) ) continue;
524
525 if( selection == count ) {
526 selected_scaler = i;
527 }
528
529 count++;
530 }
531 }
532
533 free( items.labels );
534
535 return selected_scaler;
536 }
537
538 /* Machine/Pause */
539 void
menu_machine_pause(int action)540 menu_machine_pause( int action )
541 {
542 if( paused ) {
543 paused = 0;
544 ui_statusbar_update( UI_STATUSBAR_ITEM_PAUSED,
545 UI_STATUSBAR_STATE_INACTIVE );
546 timer_estimate_reset();
547 PostMessage( fuse_hWnd, WM_USER_EXIT_PROCESS_MESSAGES, 0, 0 );
548
549 /* Resume emulation */
550 fuse_emulation_unpause();
551 } else {
552
553 /* Stop emulation */
554 fuse_emulation_pause();
555
556 paused = 1;
557 ui_statusbar_update( UI_STATUSBAR_ITEM_PAUSED, UI_STATUSBAR_STATE_ACTIVE );
558 win32ui_process_messages( 0 );
559 }
560 }
561
562 /* Called by the menu when Machine/Reset selected */
563 void
menu_machine_reset(int action)564 menu_machine_reset( int action )
565 {
566 int hard_reset = action;
567 const char *message = "Reset?";
568
569 if( hard_reset )
570 message = "Hard reset?";
571
572 if( win32ui_confirm( message ) && machine_reset( hard_reset ) ) {
573 ui_error( UI_ERROR_ERROR, "couldn't reset machine: giving up!" );
574
575 /* FIXME: abort() seems a bit extreme here, but it'll do for now */
576 fuse_abort();
577 }
578 }
579
580 /* Called by the menu when Machine/Select selected */
581 void
menu_machine_select(int action)582 menu_machine_select( int action )
583 {
584 /* FIXME: choosing spectrum SE crashes Fuse sound_frame () at sound.c:477 "ay_change[f].ofs = ( ay_change[f].tstates * sfreq ) / cpufreq;" */
585 /* FIXME: choosing some Timexes crashes (win32) fuse as well */
586
587 int selected_machine;
588 win32ui_select_info items;
589 int i;
590
591 /* Stop emulation */
592 fuse_emulation_pause();
593
594 /* Populate win32ui_select_info */
595 items.dialog_title = TEXT( "Fuse - Select Machine" );
596 items.labels = malloc( machine_count * sizeof( char * ) );
597 items.length = machine_count;
598
599 for( i=0; i<machine_count; i++ ) {
600
601 items.labels[i] = libspectrum_machine_name( machine_types[i]->machine );
602
603 if( machine_current == machine_types[i] ) {
604 items.selected = i;
605 }
606 }
607
608 /* start the machine select dialog box */
609 selected_machine = selector_dialog( &items );
610
611 if( selected_machine >= 0 &&
612 machine_types[ selected_machine ] != machine_current ) {
613 machine_select( machine_types[ selected_machine ]->machine );
614 }
615
616 free( items.labels );
617
618 /* Resume emulation */
619 fuse_emulation_unpause();
620 }
621
622 void
menu_machine_debugger(int action)623 menu_machine_debugger( int action )
624 {
625 debugger_mode = DEBUGGER_MODE_HALTED;
626 if( paused ) ui_debugger_activate();
627 }
628
629 /* Called on machine selection */
630 int
ui_widgets_reset(void)631 ui_widgets_reset( void )
632 {
633 win32ui_pokefinder_clear();
634 return 0;
635 }
636
637 void
menu_help_keyboard(int action)638 menu_help_keyboard( int action )
639 {
640 win32ui_picture( "keyboard.scr", 0 );
641 }
642
643 /* Functions to activate and deactivate certain menu items */
644 static int
set_active(HMENU menu,const char * path,int active)645 set_active( HMENU menu, const char *path, int active )
646 {
647 int i, menu_count;
648 char menu_text[255];
649 MENUITEMINFO mii;
650
651 if( *path == '/' ) path++;
652
653 menu_count = GetMenuItemCount( menu );
654 for( i = 0; i < menu_count; i++ ) {
655
656 if( GetMenuString( menu, i, menu_text, 255, MF_BYPOSITION ) == 0 ) continue;
657
658 const char *p = menu_text, *q = path;
659
660 /* Compare the two strings, but skip hotkey-delimiter characters */
661 /* Anything after \t is a shortcut key on Win32 */
662 do {
663 if( *p == '&' ) p++;
664 if( ! *p || *p == '\t' || *p != *q ) break;
665 p++; q++;
666 } while( 1 );
667
668 if( *p && *p != '\t' ) continue; /* not matched */
669
670 /* match, but with a submenu */
671 if( *q == '/' ) return set_active( GetSubMenu( menu, i ), q, active );
672
673 if( *q ) continue; /* not matched */
674
675 /* we have a match */
676 mii.fState = active ? MFS_ENABLED : MFS_DISABLED;
677 mii.fMask = MIIM_STATE;
678 mii.cbSize = sizeof( MENUITEMINFO );
679 SetMenuItemInfo( menu, i, TRUE, &mii );
680
681 return 0;
682 }
683
684 return 1;
685 }
686
687 int
ui_menu_item_set_active(const char * path,int active)688 ui_menu_item_set_active( const char *path, int active )
689 {
690 return set_active( GetMenu( fuse_hWnd ), path, active );
691 }
692
693 ui_confirm_joystick_t
ui_confirm_joystick(libspectrum_joystick libspectrum_type,int inputs)694 ui_confirm_joystick( libspectrum_joystick libspectrum_type, int inputs )
695 {
696 win32ui_select_info items;
697 char title[ 80 ];
698 int i, selection;
699 int selected_joystick;
700
701 if( !settings_current.joy_prompt ) return UI_CONFIRM_JOYSTICK_NONE;
702
703 /* Some space to store the radio options in */
704 items.labels = malloc( JOYSTICK_CONN_COUNT * sizeof( char * ) );
705 if( !items.labels ) {
706 ui_error( UI_ERROR_ERROR, "out of memory at %s:%d", __FILE__, __LINE__ );
707 return UI_CONFIRM_JOYSTICK_NONE;
708 }
709
710 /* Stop emulation */
711 fuse_emulation_pause();
712
713 /* Populate win32ui_select_info */
714 _sntprintf( title, sizeof( title ), "Fuse - Configure %s Joystick",
715 libspectrum_joystick_name( libspectrum_type ) );
716 items.dialog_title = title;
717 items.length = JOYSTICK_CONN_COUNT;
718
719 for( i=0; i<JOYSTICK_CONN_COUNT; i++ ) {
720 items.labels[i] = joystick_connection[ i ];
721 }
722
723 items.selected = UI_CONFIRM_JOYSTICK_NONE;
724
725 /* start the joystick select dialog box */
726 selection = selector_dialog( &items );
727
728 selected_joystick = ( selection >= 0 )? selection : UI_CONFIRM_JOYSTICK_NONE;
729
730 free( items.labels );
731
732 /* And then carry on with emulation again */
733 fuse_emulation_unpause();
734
735 return selected_joystick;
736 }
737
738 /*
739 * Font code
740 */
741
742 int
win32ui_get_monospaced_font(HFONT * font)743 win32ui_get_monospaced_font( HFONT *font )
744 {
745 if( ! monospaced_font ) {
746 /* Get font height in pixels for current DPI resolution */
747 HDC hdc = GetDC( NULL );
748 long font_height = -MulDiv( 8, GetDeviceCaps( hdc, LOGPIXELSY ), 72 );
749 ReleaseDC( NULL, hdc );
750
751 *font = CreateFont( font_height, 0, 0, 0, 400, FALSE, FALSE, FALSE, 0,
752 400, 2, 1, 1, TEXT( "Courier New" ) );
753 if( !font ) {
754 ui_error( UI_ERROR_ERROR, "couldn't find a monospaced font" );
755 return 1;
756 }
757 monospaced_font = *font;
758 }
759 else {
760 *font = monospaced_font;
761 }
762
763 return 0;
764 }
765
766 int
window_recommended_width(HWND hwndDlg,TCHAR * title)767 window_recommended_width( HWND hwndDlg, TCHAR *title )
768 {
769 HDC dc;
770 SIZE sz;
771 LONG_PTR window_style;
772 NONCLIENTMETRICS ncm;
773 HFONT hCaptionFont, hDefaultFont;
774 RECT cr, wr;
775 int width, buttons;
776
777 /* Get window style */
778 window_style = GetWindowLongPtr( hwndDlg, GWL_STYLE );
779 if( !( window_style & WS_CAPTION ) ) return 0;
780
781 /* Get caption bar font */
782 dc = GetDC( hwndDlg );
783 if( !dc ) return 0;
784 ncm.cbSize = sizeof( NONCLIENTMETRICS );
785 /* FIXME: iPaddedBorderWidth,
786 http://msdn.microsoft.com/en-us/library/ms724506%28VS.85%29.aspx */
787 SystemParametersInfo( SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0 );
788 hCaptionFont = CreateFontIndirect( &ncm.lfCaptionFont );
789 hDefaultFont = SelectObject( dc, hCaptionFont );
790
791 /* Calculate title width (pixels) */
792 SetMapMode( dc, MM_TEXT );
793 GetTextExtentPoint32( dc, title, _tcslen( title ), &sz );
794 width = sz.cx; /* Actually in pixels because of MM_TEXT map mode */
795 SelectObject( dc, hDefaultFont );
796 DeleteObject( hCaptionFont );
797 ReleaseDC( hwndDlg, dc );
798
799 /* Calculate buttons width (pixels) */
800 buttons = 1; /* close button */
801 if( window_style & WS_MAXIMIZEBOX ) buttons++;
802 if( window_style & WS_MINIMIZEBOX ) buttons++;
803 width += ncm.iCaptionWidth * buttons;
804
805 /* Window decorations width (pixels) */
806 GetWindowRect( hwndDlg, &wr );
807 GetClientRect( hwndDlg, &cr );
808 width += ( wr.right - wr.left ) - ( cr.right - cr.left );
809
810 /* Icon width (pixels) */
811 window_style = GetWindowLongPtr( hwndDlg, GWL_EXSTYLE );
812 if( !(window_style & WS_EX_DLGMODALFRAME) )
813 width += GetSystemMetrics( SM_CXSMICON );
814
815 /* Padding, space between text and buttons */
816 width += 20;
817
818 return width;
819 }
820
821 void
win32ui_set_font(HWND hDlg,int nIDDlgItem,HFONT font)822 win32ui_set_font( HWND hDlg, int nIDDlgItem, HFONT font )
823 {
824 SendDlgItemMessage( hDlg, nIDDlgItem , WM_SETFONT, (WPARAM) font, FALSE );
825 }
826
827 static void
selector_dialog_build(HWND hwndDlg,win32ui_select_info * items)828 selector_dialog_build( HWND hwndDlg, win32ui_select_info *items )
829 {
830 int i, left, caption_width, decor_width, decor_height;
831 int window_width, window_height, client_width, client_height;
832 DWORD dwStyle;
833 RECT wr, cr, or;
834
835 /* set the title of the window */
836 SendMessage( hwndDlg, WM_SETTEXT, 0, (LPARAM) items->dialog_title );
837
838 /* Get decorations (pixels) */
839 GetWindowRect( hwndDlg, &wr );
840 GetClientRect( hwndDlg, &cr );
841 decor_width = ( wr.right - wr.left ) - ( cr.right - cr.left );
842 decor_height = ( wr.bottom - wr.top ) - ( cr.bottom - cr.top );
843
844 /* calculate window width (pixels) */
845 or.left = or.top = or.bottom = 0;
846 or.right = 100 + 14 + 5; /* 2 buttons, 2 margins, 1 separation (DLUs) */
847 MapDialogRect( hwndDlg, &or );
848
849 window_width = or.right + decor_width;
850 caption_width = window_recommended_width( hwndDlg, items->dialog_title );
851 if( caption_width > window_width ) window_width = caption_width;
852 client_width = window_width - decor_width;
853
854 /* create radio buttons */
855 client_height = 7; /* Top margin (DLUs) */
856 for( i=0; i< items->length; i++ ) {
857
858 dwStyle = WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_AUTORADIOBUTTON;
859 /* Need for WS_GROUP to allow using arrow up/down to cycle thru this group */
860 if( i == 0 ) dwStyle = dwStyle | WS_GROUP;
861
862 or.left = 7; /* Left margin (DLUs) */
863 or.top = client_height; /* Top position (DLUs) */
864 or.right = 160; /* Control width (DLUs) */
865 or.bottom = 9; /* Control height (DLUs) */
866 MapDialogRect( hwndDlg, &or );
867 client_height += 9; /* Control height (DLUs) */
868
869 CreateWindow( TEXT( "BUTTON" ), items->labels[i],
870 dwStyle,
871 or.left, or.top, or.right, or.bottom,
872 hwndDlg, (HMENU) (LONG_PTR) ( IDC_SELECT_OFFSET + i ), fuse_hInstance, 0 );
873 SendDlgItemMessage( hwndDlg, ( IDC_SELECT_OFFSET + i ), WM_SETFONT,
874 (WPARAM) h_ms_font, FALSE );
875
876 /* check the radiobutton corresponding to current label */
877 if( i == items->selected ) {
878 SendDlgItemMessage( hwndDlg, ( IDC_SELECT_OFFSET + i ), BM_SETCHECK,
879 BST_CHECKED, 0 );
880 }
881
882 client_height += 2; /* Separation between radio buttons (DLUs) */
883 }
884 client_height += 5; /* Space after radio buttons (actually 7 DLUs) */
885
886 /* create OK and Cancel buttons */
887 or.left = 5; /* Vertical space between buttons (DLUs) */
888 or.top = client_height; /* Position Y (DLUs) */
889 or.right = 50; /* Typical width of buttons (DLUs) */
890 or.bottom = 14; /* Typical height of buttons (DLUs) */
891 MapDialogRect( hwndDlg, &or );
892
893 left = ( client_width - or.left - ( or.right * 2 ) ) / 2; /* centered */
894 CreateWindow( TEXT( "BUTTON" ), TEXT( "&OK" ),
895 WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_GROUP | BS_DEFPUSHBUTTON,
896 left, or.top, or.right, or.bottom,
897 hwndDlg, (HMENU) IDOK, fuse_hInstance, 0 );
898 SendDlgItemMessage( hwndDlg, IDOK, WM_SETFONT,
899 (WPARAM) h_ms_font, FALSE );
900
901 left += or.right + or.left;
902 CreateWindow( TEXT( "BUTTON" ), TEXT( "&Cancel" ),
903 WS_VISIBLE | WS_CHILD | WS_TABSTOP,
904 left, or.top, or.right, or.bottom,
905 hwndDlg, (HMENU) IDCANCEL, fuse_hInstance, 0 );
906 SendDlgItemMessage( hwndDlg, IDCANCEL, WM_SETFONT,
907 (WPARAM) h_ms_font, FALSE );
908
909 client_height += 21; /* Button height (14) + bottom margin (7) (DLUs) */
910
911 /* Calculate window heigth (pixels) */
912 wr.left = wr.top = wr.right = 0;
913 wr.bottom = client_height;
914 MapDialogRect( hwndDlg, &wr );
915 window_height = decor_height + wr.bottom;
916
917 /* the following will only change the size of the window */
918 SetWindowPos( hwndDlg, NULL, 0, 0, window_width, window_height,
919 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE );
920 }
921
922 static INT_PTR CALLBACK
selector_dialog_proc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)923 selector_dialog_proc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
924 {
925 int i;
926 HWND next_item = NULL;
927
928 switch( uMsg )
929 {
930 case WM_INITDIALOG:
931 /* items are passed to WM_INITDIALOG as lParam */
932 selector_dialog_build( hwndDlg, ( win32ui_select_info * ) lParam );
933 return TRUE;
934
935 case WM_SETFONT:
936 h_ms_font = (HFONT) wParam;
937 return TRUE; /* "This message does not return a value." */
938
939 case WM_COMMAND:
940 if ( HIWORD( wParam ) != BN_CLICKED ) break;
941
942 /* service OK and Cancel buttons */
943 switch LOWORD( wParam )
944 {
945 case IDOK:
946 {
947 /* check which radiobutton is selected and return the selection */
948 i = 0;
949 while( ( next_item = GetNextDlgGroupItem( hwndDlg, next_item,
950 FALSE ) ) != NULL ) {
951 if( SendDlgItemMessage( hwndDlg, ( IDC_SELECT_OFFSET + i ),
952 BM_GETCHECK, 0, 0 ) == BST_CHECKED ) {
953 EndDialog( hwndDlg, i );
954 return TRUE;
955 }
956 i++;
957 }
958 break; /* program should never reach here */
959 }
960 case IDCANCEL:
961 EndDialog( hwndDlg, -1 );
962 return TRUE;
963 }
964 /* service clicking radiobuttons */
965 /* FIXME should also be checking if wParam < offset + radio count */
966 if( LOWORD( wParam ) >= IDC_SELECT_OFFSET )
967 return 0;
968 break;
969
970 case WM_DESTROY:
971 EndDialog( hwndDlg, -1 );
972 return TRUE;
973 }
974 return FALSE;
975 }
976
977 static int
selector_dialog(win32ui_select_info * items)978 selector_dialog( win32ui_select_info *items )
979 {
980 /* selector_dialog will display a modal dialog, with a list of grouped
981 radiobuttons, OK and Cancel buttons. The radiobuttons' labels, their count,
982 current selection and dialog title are provided via win32ui_select_info.
983 The function returns an int corresponding to the selected radiobutton */
984
985 /* FIXME: fix accelerators for this window */
986
987 return DialogBoxParam( fuse_hInstance, MAKEINTRESOURCE( IDD_SELECT_DIALOG ),
988 fuse_hWnd, selector_dialog_proc, ( LPARAM )items );
989 }
990
991 void
win32_verror(int is_error)992 win32_verror( int is_error )
993 {
994 if( !is_error ) return;
995
996 DWORD last_error;
997 static LPVOID err_msg;
998 last_error = GetLastError();
999 FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
1000 FORMAT_MESSAGE_FROM_SYSTEM,
1001 NULL, last_error, LANG_USER_DEFAULT,
1002 (LPTSTR) &err_msg, 0, NULL );
1003 MessageBox( fuse_hWnd, err_msg, "Error", MB_OK );
1004 }
1005
1006 /* Handler for the main window's WM_PAINT notification.
1007 The handler is an equivalent of GTK's expose_event */
1008 static int
win32ui_window_paint(HWND hWnd,WPARAM wParam,LPARAM lParam)1009 win32ui_window_paint( HWND hWnd, WPARAM wParam, LPARAM lParam )
1010 {
1011 blit();
1012 return 0;
1013 }
1014
1015 /* Handler for the main window's WM_SIZE notification */
1016 static int
win32ui_window_resize(HWND hWnd,WPARAM wParam,LPARAM lParam)1017 win32ui_window_resize( HWND hWnd, WPARAM wParam, LPARAM lParam )
1018 {
1019 if( wParam == SIZE_MINIMIZED ) {
1020 if( !size_paused ) {
1021 size_paused = 1;
1022 fuse_emulation_pause();
1023 }
1024 } else {
1025 win32display_drawing_area_resize( LOWORD( lParam ), HIWORD( lParam ), 1 );
1026
1027 /* Resize statusbar and inner parts */
1028 SendMessage( fuse_hStatusWindow, WM_SIZE, wParam, lParam );
1029 win32statusbar_resize( hWnd, wParam, lParam );
1030
1031 if( size_paused ) {
1032 size_paused = 0;
1033 fuse_emulation_unpause();
1034 }
1035 }
1036
1037 return 0;
1038 }
1039
1040 /* Handler for the main window's WM_SIZING notification.
1041 The handler is an equivalent of setting window geometry in GTK */
1042 static BOOL
win32ui_window_resizing(HWND hWnd,WPARAM wParam,LPARAM lParam)1043 win32ui_window_resizing( HWND hWnd, WPARAM wParam, LPARAM lParam )
1044 {
1045 RECT *selr, wr, cr, statr, or;
1046 int width, height, w_ofs, h_ofs, w_max, h_max;
1047
1048 selr = (RECT *)lParam;
1049 GetWindowRect( fuse_hWnd, &wr );
1050 GetClientRect( fuse_hWnd, &cr );
1051 GetClientRect( fuse_hStatusWindow, &statr );
1052
1053 w_ofs = ( wr.right - wr.left ) - ( cr.right - cr.left );
1054 h_ofs = ( wr.bottom - wr.top ) - ( cr.bottom - cr.top );
1055 if( settings_current.statusbar ) h_ofs += ( statr.bottom - statr.top );
1056
1057 /* max scaler size in desktop workarea */
1058 SystemParametersInfo( SPI_GETWORKAREA, 0, &or, 0 );
1059 w_max = ( or.right - or.left ) - w_ofs + DISPLAY_ASPECT_WIDTH / 2;
1060 w_max /= DISPLAY_ASPECT_WIDTH;
1061 h_max = ( or.bottom - or.top ) - h_ofs + DISPLAY_SCREEN_HEIGHT / 2;
1062 h_max /= DISPLAY_SCREEN_HEIGHT;
1063
1064 if( w_max < h_max ) {
1065 h_max = w_max;
1066 } else {
1067 w_max = h_max;
1068 }
1069
1070 /* current scaler */
1071 width = selr->right - selr->left + DISPLAY_ASPECT_WIDTH / 2;
1072 height = selr->bottom - selr->top + DISPLAY_SCREEN_HEIGHT / 2;
1073
1074 width -= w_ofs; height -= h_ofs;
1075 width /= DISPLAY_ASPECT_WIDTH; height /= DISPLAY_SCREEN_HEIGHT;
1076
1077 if( wParam == WMSZ_LEFT || wParam == WMSZ_RIGHT ) {
1078 height = width;
1079 } else if( wParam == WMSZ_TOP || wParam == WMSZ_BOTTOM ) {
1080 width = height;
1081 }
1082
1083 if( width < 1 || height < 1 ) {
1084 width = 1; height = 1;
1085 }
1086
1087 if( width > w_max || height > h_max ) {
1088 width = w_max; height = h_max;
1089 }
1090
1091 if( width > 3 || height > 3 ) {
1092 width = 3; height = 3;
1093 }
1094
1095 if( width < height ) {
1096 height = width;
1097 } else {
1098 width = height;
1099 }
1100
1101 width *= DISPLAY_ASPECT_WIDTH; height *= DISPLAY_SCREEN_HEIGHT;
1102 width += w_ofs; height += h_ofs;
1103
1104 /* Set window size */
1105 if( wParam == WMSZ_TOP ||
1106 wParam == WMSZ_TOPLEFT ||
1107 wParam == WMSZ_TOPRIGHT ) {
1108 selr->top = selr->bottom - height;
1109 } else {
1110 selr->bottom = selr->top + height;
1111 }
1112 if( wParam == WMSZ_LEFT ||
1113 wParam == WMSZ_TOPLEFT ||
1114 wParam == WMSZ_BOTTOMLEFT ) {
1115 selr->left = selr->right - width;
1116 } else {
1117 selr->right = selr->left + width;
1118 }
1119
1120 return TRUE;
1121 }
1122
1123 /* The function is an equivalent of GTK's gtk_window_resize,
1124 take care of resizing main window into visible screen */
1125 void
win32ui_fuse_resize(int width,int height)1126 win32ui_fuse_resize( int width, int height )
1127 {
1128 RECT wr, cr, statr, or;
1129 int w_ofs, h_ofs;
1130
1131 /* Calculate decorations before resizing */
1132 GetWindowRect( fuse_hWnd, &wr );
1133 GetClientRect( fuse_hWnd, &cr );
1134 GetClientRect( fuse_hStatusWindow, &statr );
1135
1136 w_ofs = ( wr.right - wr.left ) - ( cr.right - cr.left );
1137 h_ofs = ( wr.bottom - wr.top ) - ( cr.bottom - cr.top );
1138 if( settings_current.statusbar ) h_ofs += ( statr.bottom - statr.top );
1139
1140 /* Set position inside workarea */
1141 SystemParametersInfo( SPI_GETWORKAREA, 0, &or, 0 );
1142 if( wr.left + width + w_ofs > or.right ) wr.left = or.right - width - w_ofs;
1143 if( wr.top + height + h_ofs > or.bottom ) wr.top = or.bottom - height - h_ofs;
1144 if( wr.left < or.left ) wr.left = or.left;
1145 if( wr.top < or.top ) wr.top = or.top;
1146
1147 MoveWindow( fuse_hWnd, wr.left, wr.top,
1148 width + w_ofs,
1149 height + h_ofs,
1150 TRUE );
1151 }
1152
1153 /* win32ui_process_messages() is an equivalent of gtk's gtk_main(),
1154 * it processes messages until it receives WM_USER_EXIT_PROCESS_MESSAGES
1155 * message, which should be sent using:
1156 * PostMessage( fuse_hWnd, WM_USER_EXIT_PROCESS_MESSAGES, 0, 0 );
1157 * ( equivalent of gtk_main_quit() );
1158 * With process_queue_once = 1 it checks for messages pending for fuse
1159 * window, processes them and exists.
1160 * With process_queue_once = 0 it processes the messages until it receives
1161 * WM_USER_EXIT_PROCESS_MESSAGES message.
1162 */
1163 void
win32ui_process_messages(int process_queue_once)1164 win32ui_process_messages( int process_queue_once )
1165 {
1166 MSG msg;
1167 int i, processMsg;
1168 HWND hModelessDlgs[] = { fuse_hPFWnd, fuse_hDBGWnd, fuse_hABOWnd };
1169
1170 while( 1 ) {
1171 while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) {
1172 /* FIXME: rethink this loop, IsDialogMessage in particular */
1173 processMsg = TRUE;
1174
1175 for( i = 0; processMsg && i < DIM( hModelessDlgs ); i++) {
1176 if( IsDialogMessage( hModelessDlgs[i], &msg ) ) processMsg = FALSE;
1177 }
1178
1179 if( processMsg ) {
1180 if( !TranslateAccelerator( fuse_hWnd, hAccels, &msg ) ) {
1181 if( ( LOWORD( msg.message ) == WM_QUIT ) ||
1182 ( LOWORD( msg.message ) == WM_USER_EXIT_PROCESS_MESSAGES ) )
1183 return;
1184 /* FIXME: set exit flag somewhere */
1185 TranslateMessage( &msg );
1186 DispatchMessage( &msg );
1187 }
1188 }
1189 }
1190 if( process_queue_once ) return;
1191
1192 /* If we miss WM_USER_EXIT_PROCESS_MESSAGES, the window procedure will
1193 kindly notify us */
1194 if( exit_process_messages ) {
1195 exit_process_messages--;
1196 return;
1197 }
1198
1199 WaitMessage();
1200 }
1201 /* FIXME: somewhere there should be return msg.wParam */
1202 }
1203