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