diff --git a/.gitignore b/.gitignore index eb4ae4ba..2b9db9ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.user +.vs Src/.vs Src/BeebEm.aps Src/BeebEm.VC.db diff --git a/Documents/JoystickMap.txt b/Documents/JoystickMap.txt new file mode 100644 index 00000000..77ebf90b --- /dev/null +++ b/Documents/JoystickMap.txt @@ -0,0 +1,70 @@ +*** BeebEm Joystick Map *** + +# Joystick map file begins with special marker line as above. +# All empty lines and lines starting with '#' are ignored. + +# Remaining lines must consist of two or three tokens separated with +# whitespace. +# All names are case insensitive except for the marker in the first line. + +# Two-token lines are: +# +# Three-token lines are: +# + +# These two lines define the same mapping: +# Joy1Up A +# Joy1Up A SH+A + +# BBC Key names prefixed with 'SH+' indicate shifted key press. +# In the two-token lines, the 'SH+' prefix is allowed, but ignored. + +# Special BBC key names are: +# LEFT, RIGHT, UP, DOWN, BREAK, COPY, DELETE, CAPS-LOCK, TAB, CTRL +# SPACE, RETURN, ESCAPE, SHIFT, SHIFT-LOCK +# Allowed aliases: +# DEL, CAPS, CAPSLOCK, CONTROL, ESC, SHIFTLOCK + +# All other BBC keys are named by their unshifted character (i.e. 'SH+[' not '{') +# Function keys are named, as one would expect, 'F0' to 'F9' + +# Joystick inputs have the form Joy[X][Act] where [X] is the joystick number +# and [Act] is the action name. Axis and button assignments differ between +# joystick or gamepad models. + +# Xbox 360 controller has following assignments +# (with DIJOYSTATE2 fields in the middle column): +# Left, Right, Up, Down - lX, lY - Left Analog Left, Right, Up, Down +# Z+,Z- - lZ - Left Trigger, Right Trigger +# RLeft, RRight, RUp, RDown - lRx, lRy - Right Analog Left, Right, Up, Down +# HatLeft, HatRight, HatUp, HatDown - rgdwPOV[0] - Hat Up, Down, Left, Right +# Btn1-4 - A, B, X, Y +# Btn5,6 - Left Shoulder, Right Shoulder +# Btn7,8 - Back, Start +# Btn9,10 - Left Thumbstick, Right Thumbstick + +# Other common layout: +# Left, Right, Up, Down - lX, lY - Left Analog Left, Right, Up, Down +# RLeft, RRight, RUp, RDown - lZ, lRz - Right Analog Left, Right, Up, Down +# HatLeft, HatRight, HatUp, HatDown - rgdwPOV[0] - Hat Up, Down, Left, Right +# Btn1-4 - Y, B, A, X +# Btn5,6 - Left Shoulder, Right Shoulder +# Btn7,8 - Left Trigger, Right Trigger +# Btn9,10 - Select/Back, Start +# Btn11,12 - Left Thumbstick, Right Thumbstick +# Btn13 - Home + +# Sample mapping for Chuckie Egg + +Joy1Up A +Joy1Down Z +Joy1Left , +Joy1Right . +Joy1HatUp A +Joy1HatDown Z +Joy1HatLeft , +Joy1HatRight . +Joy1Btn1 SPACE +Joy1Btn6 RETURN +Joy1Btn7 1 +Joy1Btn8 S diff --git a/Help/keyboard.html b/Help/keyboard.html index 3112d690..902ca957 100644 --- a/Help/keyboard.html +++ b/Help/keyboard.html @@ -10,7 +10,7 @@ @@ -204,7 +204,7 @@

Custom Key Mappings

  1. If you are in full screen mode then switch back to Windowed mode.
  2. -
  3. Select menu item "Options -> Define User Key Mapping". A graphic +
  4. Select menu item "Options → Define User Key Mapping". A graphic showing the BBC keyboard layout will appear within the BeebEm interface.
  5. @@ -228,16 +228,104 @@

    Custom Key Mappings

  6. Repeat from step 3 for other keys you want to map.
  7. -
  8. Save your mapping using menu item "Options -> Save User Key Mapping". +
  9. Save your mapping using menu item "Options → Save User Key Mapping". You can write over the default user key mapping file (DefaultUser.kmap) or save a new file.
  10. -
  11. Select your mapping using menu item "Options -> User Defined Mapping". +
  12. Select your mapping using menu item "Options → User Defined Mapping". You can also use the "Save Preferences" option to save the default user key mapping file that gets loaded when BeebEm starts up.
- +

Mapping PC Joystick to BBC Keys

+ +

To enable translating PC joystick actions to BBC keys, use menu item + "Options → Joystick To Keyboard → Enable Joystick To Keyboard". + BeemEm supports mapping up to four joysticks or gamepads to BBC keys, + with each joystick actions (button presses or stick movements) + mapped separately.

+ +

Creating mapping of PC joystick movements and buttons to BBC keys is + similar to creating custom key mappings:

+ +
    +
  1. If you are in full screen mode then switch back to Windowed mode.
  2. + +
  3. Select menu item "Options → Joystick To Keyboard → Define Joystick Mapping". + A graphic showing the BBC keyboard layout will appear within the BeebEm + interface. BBC keys which already have assigned joystick actions will be + highlighted with light blue colour.
  4. + +
  5. Use your mouse pointer to click once on the BBC key that you are + attempting to map to your joystick or gamepad.
  6. + +
  7. Press the joystick button or move the joystick in the direction that you want + to map to the unshifted BBC key press, or click "OK" to skip that part. + If you want to map shifted joystick action to unshifted BBC key press, enable + the "Shift" checkbox before moving the joystick.
  8. + +
  9. Press the joystick button or move the joystick in the direction that you want + to map to the shifted BBC key press, or click "OK" to skip that part. + If you want to map unshifted joystick action to shifted BBC key press, disable + the "Shift" checkbox before moving the joystick.
  10. + +
  11. Repeat from step 3 for other keys you want to map.
  12. + +
  13. Save your mapping using menu item + "Options → Joystick To Keyboard → Save Joystick Mapping...". + You can save your mapping as the default user joystick mapping file + (DefaultUser.jmap) or save to a different file. If you are creating + a game-specific joystick mapping, save the mapping in the same + directory and with the same name as your game disk image, with extension + changed to ".jmap". This will make BeebEm automatically use the joystick + mapping for that disk image, if that is enabled with menu item + "Options → Joystick To Keyboard → Autoload Joystick Mapping".
  14. +
+ +

As with PC keys, you can map joystick actions to BBC keys in shifted and + unshifted state separately. The most common scenario is to assign a + joystick action to the same BBC key in both shifted and unshifted state. + To do that, just click on the BBC key that you want to map, and move + joystick or press button twice - once for unshifted and second time for + shifted state.

+ +

Additionally, you can assign one or more joystick buttons to the SHIFT key. + This can be used to assign one joystick button as a modifier for + other joystick actions, which can be useful for games that have a lot of + keyboard contols.

+ +

PC joystick to BBC keyboard mapping is independent from enabling PC joystick + acting as BBC joystick. If you enable both, primary PC joystick axes + (primary stick up, down, left and right) and first two buttons are mapped + to BBC joystick. You can map other axes and buttons to BBC keys. You can + map those axes and buttons which are acting as BBC joystick to BBC + keys as well. In that case, the PC joystick action will be seen as both BBC + joystick action and BBC key press.

+ +

To remove mapping from previously mapped joystick action, click on the + 'Unassign' button. It will display small window and wait for the + joystick action. Press the joystick button or move the joystick in the + direction that you want to unmap. The same window will be displayed again, + giving you the opportunity to unmap the joystick action in both unshifted + and shifted state at one go. You can press the "OK" button to skip the + second unassignment.

+ +

At start up BeebEm loads DefaultUser.jmap from the User Data Folder, if such + file was created by user. If this file is not present, all joystick actions + start unassigned.

+ +

Menu item "Options → Joystick To Keyboard → Autoload Joystick Mapping" + toggles Autoload Joystick Mapping option. If this option is enabled, BeebEm + will automatically look for joystick mapping file based on name of image + file started from "File → Run Disc.." or "File → Load Tape..." menu item, + or from command-line. The sought mapping file has the same name and + directory as loaded image, but extension changed to ".jmap". If no such + file is found, joystick mapping is reset to default - either empty, or that + from "DefaultUser.jmap" file. If the Autoload Joystick Mapping option is + enabled, the "Save Joystick Mapping..." dialog will automatically suggest + correct file name for currently loaded image file.

+ + diff --git a/Help/menus.html b/Help/menus.html index 1561536e..4d3dbd5a 100644 --- a/Help/menus.html +++ b/Help/menus.html @@ -534,6 +534,156 @@

AMX Menu

-

BBC MicroBBC Micro

+

Joysticks Menu

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Primary JoystickSwitch on or off primary Beeb analogue joystick support + and select which PC device the emulator should use for the primary + Beeb analogue joystick. Click on the currently selected device + again to disable primary Beeb analogue joystick. +
First PC JoystickUse first PC joystick or gamepad. + First and third PC joystick buttons are mapped to the primary Beeb + joystick fire button. Second and fourth PC joystick buttons are mapped to + the secondary Beeb joystick fire button, unless the secondary Beeb + joystick support is separately enabled and mapped to another contoller. + The "First PC Joystick" menu text is replaced with actual joystick or + gamepad model name, if it is known to the emulator. + Calibrate the joystick through the Windows control panel. +
Second PC JoystickUse second PC joystick or gamepad. + First and third PC joystick buttons are mapped to the primary Beeb + joystick firs button. Second and fourth PC joystick buttons are mapped to + the secondary Beeb joystick fire button, unless the secondary Beeb + joystick support is separately enabled and mapped to another controller. + The "Second PC Joystick" menu text is replaced with actual joystick or + gamepad model name, if it is known to the emulator. +
Analogue MousestickMap Mouse position to analogue Beeb joystick position. + Allows you to use the mouse as a joystick. The left mouse button + controls the joystick 1 fire button and the right mouse button controls + the joystick 2 fire button, unless the secondary Beeb joystick support + is separately enabled. +
Digital MousestickMap Mouse movements to digital Beeb joystick movements. + The left mouse button controls the joystick 1 fire button and the right + mouse button controls the joystick 2 fire button, unless the secondary + Beeb joystick is separately enabled. +
Primary Stick / Right ThumbstickSelect between primary Joystick/Thumbstick and secondary + Joystick/Thumbstick on a gamepad or other controller with more than one + analogue sticks. In other words select between primary and secondary + set of axes. + If both primary and secondary Beeb analogue joysticks are mapped to two + different sticks on the same PC contoller, joystick button mapping + changes so that first and third PC joystick buttons are mapped + to the first Beeb joystick fire button, and second and fourth PC joystick + buttons are mapped to the second Beeb joystick fire button. +
Secondary JoystickSwitch on or off secondary Beeb analogue joystick support + and select which PC device the emulator should use for the secondary + Beeb analogue joystick. Click on the currently selected device + again to disable secondary Beeb analogue joystick. + Available devices selection is the same as for primary Beeb analogue + joystick. +
Enable Joystick To KeyboardSwitch on or off mapping PC joystick to BBC keyboard. + See the Keyboard Mappings section. +
ThresholdMinimum PC joystick deflection to generate BBC key press.
Autoload Joystick MappingSwitch on or off automatically loading joystick mapping + files for disk or tape images. +
Define Joystick MappingAllows you to configure joystick to keyboard mapping. + See the Keyboard Mappings section. +
Reset Joystick MappingClears joystick to keyboard mapping table.
Load Joystick MappingLoads joystick to keyboard mapping from a file. +
Save Joystick MappingSaves joystick to keyboard mapping to a file. +
Joystick SensitivityJoystick sensitivity factor applied when mapping + PC Joystick or Mouse position to Beeb joystick. +
Rescan JoysticksRepeats scanning of available PC joysticks and + gamepads and retries joysticks initialization. You can use this + option if you have connected your USB gamepad after starting + BeebEm. +
Change Joystick OrderChanges assigned joystick order. This can be useful + if you have more than two joysticks to map Beeb analogue + joystick to other than first two PC joysticks or to switch PC joysticks + mapped to Beeb keyboard without having to remap each key. +
+

Hardware Menu

@@ -623,14 +773,6 @@

Hardware Menu

- - - - -
Basic Hardware OnlySwitches off Analogue to Digital (Joystick) and - Serial (printing, comms & tape) emulation. May - speed BeebEm up. -
Emulator Traps Enables the use of opcodes &x3 to access @@ -706,32 +848,6 @@

Hardware Menu

Options Menu

- - - - - - - - - - - - - - -
JoystickSwitch on or off PC/Beeb analogue joystick support. - Calibrate the joystick through the Windows control panel. -
Analogue MousestickSwitch on or off the mapping of Mouse position to - analogue Beeb joystick position. Allows you to use the mouse - as a joystick. The left mouse button controls the joystick 1 - fire button and the right mouse button controls the joystick 2 - fire button. -
Digital MousestickSwitch on or off the mapping of Mouse movements to - digital Beeb joystick movements. The left mouse button controls - the joystick 1 fire button and the right mouse button controls - the joystick 2 fire button. -
Freeze when inactive When selected BeebEm will freeze when you switch diff --git a/Src/6502core.cpp b/Src/6502core.cpp index 323f5210..c23d0435 100644 --- a/Src/6502core.cpp +++ b/Src/6502core.cpp @@ -240,7 +240,6 @@ bool IntDue=false; int CyclesToInt = NO_TIMER_INT_DUE; static bool Branched; // true if the instruction branched -bool BasicHardwareOnly = false; // false = all hardware, true = basic hardware only // 1 if first cycle happened // Get a two byte address from the program counter, and then post inc @@ -3112,10 +3111,8 @@ static void PollHardware(unsigned int nCycles) } VideoPoll(nCycles); - if (!BasicHardwareOnly) { - AtoD_poll(nCycles); - Serial_Poll(); - } + AtoD_poll(nCycles); + Serial_Poll(); Disc8271Poll(); Music5000Poll(nCycles); SoundPoll(); diff --git a/Src/6502core.h b/Src/6502core.h index 89914360..5c1d5779 100644 --- a/Src/6502core.h +++ b/Src/6502core.h @@ -91,7 +91,6 @@ void AdjustForIORead(void); void AdjustForIOWrite(void); extern int OpCodes; -extern bool BasicHardwareOnly; void WriteInstructionCounts(const char *FileName); diff --git a/Src/BeebEm.rc b/Src/BeebEm.rc index 71871e84..24b904ec 100644 --- a/Src/BeebEm.rc +++ b/Src/BeebEm.rc @@ -108,6 +108,8 @@ BEGIN CONTROL "_ £",IDK_UNDERSCORE,"Button",BS_OWNERDRAW | WS_TABSTOP,269,33,17,14 CONTROL "^ ~",IDK_CARET,"Button",BS_OWNERDRAW | WS_TABSTOP,243,19,17,14 CONTROL "SFT LK",IDK_SHIFT_LOCK,"Button",BS_OWNERDRAW | WS_TABSTOP,9,62,29,14 + CONTROL "Unassign",IDK_UNASSIGN,"Button",BS_OWNERDRAW | WS_TABSTOP,9,86,60,14 + PUSHBUTTON "Reset Mapping",IDK_RESET_MAPPING,9,101,60,14 END IDD_KEYBOARD_LINKS DIALOG 0, 0, 160, 66 @@ -165,7 +167,7 @@ STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPT CAPTION "Press key for unshifted press..." FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - LTEXT "Assigned to PC key(s):",IDC_STATIC,7,7,74,8 + LTEXT "Assigned to PC key(s):",IDC_ASSIGNED_KEYS_LBL,7,7,74,8 LTEXT "Static",IDC_ASSIGNED_KEYS,15,21,127,8 CONTROL "Shift",IDC_SHIFT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,15,40,31,10 PUSHBUTTON "OK",IDOK,92,39,50,14 @@ -257,6 +259,17 @@ BEGIN PUSHBUTTON "Save Config",IDC_SAVE,235,136,50,14 END +IDD_JOYSTICKORDER DIALOGEX 0, 0, 170, 133 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Joystick Order" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,113,113,50,14 + LISTBOX IDC_JOYSTICKLIST,7,7,141,103,WS_VSCROLL | WS_TABSTOP + CONTROL "",IDC_JOYSTICKSPIN,"msctls_updown32",UDS_ARROWKEYS,151,35,12,34 + CONTROL "Show disconnected",IDC_JOYSTICKSHOWALL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,115,79,10 +END + ///////////////////////////////////////////////////////////////////////////// // @@ -320,6 +333,14 @@ BEGIN TOPMARGIN, 7 BOTTOMMARGIN, 197 END + + IDD_JOYSTICKORDER, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 163 + TOPMARGIN, 7 + BOTTOMMARGIN, 127 + END END #endif // APSTUDIO_INVOKED @@ -641,6 +662,55 @@ BEGIN MENUITEM "Adjust -30%", IDM_AMX_ADJUSTM30 MENUITEM "Adjust -50%", IDM_AMX_ADJUSTM50 END + POPUP "&Joysticks" + BEGIN + POPUP "Primary Joystick" + BEGIN + MENUITEM "First PC &Joystick", IDM_JOYSTICK + MENUITEM "Second PC Joystick", IDM_JOY1_PCJOY2 + MENUITEM "&Analogue Mousestick", IDM_ANALOGUE_MOUSESTICK + MENUITEM "&Digital Mousestick", IDM_DIGITAL_MOUSESTICK + MENUITEM SEPARATOR + MENUITEM "Primary Stick", IDM_JOY1_PRIMARY + MENUITEM "Right Thumbstick", IDM_JOY1_SECONDARY1 + END + POPUP "Secondary Joystick" + BEGIN + MENUITEM "First PC &Joystick", IDM_JOY2_PCJOY1 + MENUITEM "Second PC Joystick", IDM_JOY2_PCJOY2 + MENUITEM "&Analogue Mousestick", IDM_JOY2_ANALOGUE_MOUSESTICK + MENUITEM "&Digital Mousestick", IDM_JOY2_DIGITAL_MOUSESTICK + MENUITEM SEPARATOR + MENUITEM "Primary Stick", IDM_JOY2_PRIMARY + MENUITEM "Right Thumbstick", IDM_JOY2_SECONDARY1 + END + POPUP "Joystick To Keyboard" + BEGIN + MENUITEM "Enable Joystick To Keyboard", IDM_JOYSTICK_TO_KEYS + POPUP "Threshold" + BEGIN + MENUITEM "12.5%", IDM_JOY_KEY_THRESHOLD_12_5 + MENUITEM "25%", IDM_JOY_KEY_THRESHOLD_25 + MENUITEM "50%", IDM_JOY_KEY_THRESHOLD_50 + MENUITEM "75%", IDM_JOY_KEY_THRESHOLD_75 + END + MENUITEM "Autoload Joystick Mapping", IDM_AUTOLOADJOYMAP + MENUITEM SEPARATOR + MENUITEM "Define Joystick Mapping", IDM_DEFINEJOYMAP + MENUITEM "Reset Joystick Mapping", IDM_RESETJOYMAP + MENUITEM "Load Joystick Mapping...", IDM_LOADJOYMAP + MENUITEM "Save Joystick Mapping...", IDM_SAVEJOYMAP + END + POPUP "Joystick Sensitivity" + BEGIN + MENUITEM "50%", IDM_JOY_SENSITIVITY_50 + MENUITEM "100%", IDM_JOY_SENSITIVITY_100 + MENUITEM "200%", IDM_JOY_SENSITIVITY_200 + MENUITEM "300%", IDM_JOY_SENSITIVITY_300 + END + MENUITEM "Rescan Joysticks", IDM_INIT_JOYSTICK + MENUITEM "Change Joystick Order", IDM_JOYSTICKORDER + END POPUP "Hard&ware" BEGIN POPUP "&BBC Model" @@ -688,7 +758,6 @@ BEGIN MENUITEM "SW RAM Board On/Off", IDM_SWRAMBOARD MENUITEM "Edit ROM Configuration", IDM_ROMCONFIG MENUITEM SEPARATOR - MENUITEM "Basic &Hardware Only", ID_BASIC_HARDWARE_ONLY MENUITEM "&Teletext Half Mode", ID_TELETEXTHALFMODE MENUITEM SEPARATOR MENUITEM "Econet On/Off", ID_ECONET @@ -708,9 +777,6 @@ BEGIN END POPUP "&Options" BEGIN - MENUITEM "&Joystick", IDM_JOYSTICK - MENUITEM "&Analogue Mousestick", IDM_ANALOGUE_MOUSESTICK - MENUITEM "&Digital Mousestick", IDM_DIGITAL_MOUSESTICK MENUITEM "&Freeze when inactive", IDM_FREEZEINACTIVE MENUITEM "&Hide Cursor", IDM_HIDECURSOR MENUITEM SEPARATOR @@ -758,18 +824,18 @@ END // TEXTINCLUDE // -1 TEXTINCLUDE +1 TEXTINCLUDE BEGIN "beebemrc.h\0" END -2 TEXTINCLUDE +2 TEXTINCLUDE BEGIN "#include ""winresrc.h""\r\n" "\0" END -3 TEXTINCLUDE +3 TEXTINCLUDE BEGIN "\r\n" "\0" diff --git a/Src/BeebEm.vcxproj b/Src/BeebEm.vcxproj index 18b45f89..40797d9c 100644 --- a/Src/BeebEm.vcxproj +++ b/Src/BeebEm.vcxproj @@ -104,7 +104,7 @@ true - Comctl32.lib;d3d9.lib;dsound.lib;Gdiplus.lib;Shlwapi.lib;Vfw32.lib;winmm.lib;Ws2_32.lib;%(AdditionalDependencies) + Comctl32.lib;d3d9.lib;dsound.lib;dinput8.lib;Gdiplus.lib;Shlwapi.lib;Vfw32.lib;Ws2_32.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) @@ -120,7 +120,7 @@ Windows true - Comctl32.lib;d3d9.lib;dsound.lib;Gdiplus.lib;Shlwapi.lib;Vfw32.lib;winmm.lib;Ws2_32.lib;%(AdditionalDependencies) + Comctl32.lib;d3d9.lib;dsound.lib;dinput8.lib;Gdiplus.lib;Shlwapi.lib;Vfw32.lib;Ws2_32.lib;%(AdditionalDependencies) @@ -142,7 +142,7 @@ true - Comctl32.lib;d3d9.lib;dsound.lib;Gdiplus.lib;Shlwapi.lib;Vfw32.lib;winmm.lib;Ws2_32.lib;%(AdditionalDependencies) + Comctl32.lib;d3d9.lib;dsound.lib;dinput8.lib;Gdiplus.lib;Shlwapi.lib;Vfw32.lib;Ws2_32.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) @@ -162,7 +162,7 @@ true true true - Comctl32.lib;d3d9.lib;dsound.lib;Gdiplus.lib;Shlwapi.lib;Vfw32.lib;winmm.lib;Ws2_32.lib;%(AdditionalDependencies) + Comctl32.lib;d3d9.lib;dsound.lib;dinput8.lib;Gdiplus.lib;Shlwapi.lib;Vfw32.lib;Ws2_32.lib;%(AdditionalDependencies) @@ -182,11 +182,13 @@ + + @@ -195,6 +197,7 @@ + @@ -331,6 +334,7 @@ + @@ -340,6 +344,8 @@ + + diff --git a/Src/BeebEm.vcxproj.filters b/Src/BeebEm.vcxproj.filters index 55fc2c5f..ae99f78e 100644 --- a/Src/BeebEm.vcxproj.filters +++ b/Src/BeebEm.vcxproj.filters @@ -243,6 +243,15 @@ Source Files + + Source Files + + + Source Files + + + Source Files + @@ -488,6 +497,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + diff --git a/Src/Dialog.cpp b/Src/Dialog.cpp new file mode 100644 index 00000000..93e0265b --- /dev/null +++ b/Src/Dialog.cpp @@ -0,0 +1,33 @@ +/**************************************************************** +BeebEm - BBC Micro and Master 128 Emulator +Copyright (C) 2021 Chris Needham + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public +License along with this program; if not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. +****************************************************************/ + +#include + +#include "Dialog.h" + +bool IsDlgItemChecked(HWND hDlg, int nIDDlgItem) +{ + return SendDlgItemMessage(hDlg, nIDDlgItem, BM_GETCHECK, 0, 0) == BST_CHECKED; +} + +void SetDlgItemChecked(HWND hDlg, int nIDDlgItem, bool checked) +{ + SendDlgItemMessage(hDlg, nIDDlgItem, BM_SETCHECK, checked ? BST_CHECKED : BST_UNCHECKED, 0); +} diff --git a/Src/Dialog.h b/Src/Dialog.h new file mode 100644 index 00000000..f16d0e8c --- /dev/null +++ b/Src/Dialog.h @@ -0,0 +1,27 @@ +/**************************************************************** +BeebEm - BBC Micro and Master 128 Emulator +Copyright (C) 2021 Chris Needham + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public +License along with this program; if not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. +****************************************************************/ + +#ifndef DIALOG_HEADER +#define DIALOG_HEADER + +bool IsDlgItemChecked(HWND hDlg, int nIDDlgItem); +void SetDlgItemChecked(HWND hDlg, int nIDDlgItem, bool checked); + +#endif diff --git a/Src/JoystickHandler.cpp b/Src/JoystickHandler.cpp new file mode 100644 index 00000000..850af780 --- /dev/null +++ b/Src/JoystickHandler.cpp @@ -0,0 +1,1756 @@ +/**************************************************************** +BeebEm - BBC Micro and Master 128 Emulator +Copyright (C) 1994 Nigel Magnay +Copyright (C) 1997 Mike Wyatt +Copyright (C) 1998 Robert Schmidt +Copyright (C) 2001 Richard Gellman +Copyright (C) 2004 Ken Lowe +Copyright (C) 2004 Rob O'Donnell +Copyright (C) 2005 Jon Welch +Copyright (C) 2021 Chris Needham, Tadeusz Kijkowski + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public +License along with this program; if not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. +****************************************************************/ + +#include "beebwin.h" +#include "beebemrc.h" +#include "userkybd.h" +#include "sysvia.h" +#include "filedialog.h" +#include "SelectKeyDialog.h" +#include "JoystickHandler.h" + +#include +#include +#include +#include + +// Unhide std::min, std::max +#undef min +#undef max + +#define JOYMAP_TOKEN "*** BeebEm Joystick Map ***" + +/* Backward compatibility */ +static const char* CFG_OPTIONS_STICKS = "Sticks"; + +/* New joystick options */ +static const char* CFG_OPTIONS_STICK_PCSTICK = "Joystick%dPCStick"; +static const char* CFG_OPTIONS_STICK_PCAXES = "Joystick%dPCAxes"; +static const char* CFG_OPTIONS_STICK_ANALOG = "Joystick%dAnalogMousepad"; +static const char* CFG_OPTIONS_STICK_DIGITAL = "Joystick%dDigitalMousepad"; +static const char* CFG_OPTIONS_JOYSTICK_SENSITIVITY = "JoystickSensitivity"; +static const char* CFG_OPTIONS_JOYSTICK_ORDER = "JoystickOrder%d"; + +static const char* CFG_OPTIONS_STICKS_TO_KEYS = "JoysticksToKeys"; +static const char* CFG_OPTIONS_AUTOLOAD_JOYSICK_MAP = "AutoloadJoystickMap"; +static const char* CFG_OPTIONS_STICKS_THRESHOLD = "JoysticksToKeysThreshold"; + + +/*****************************************************************************/ +/* All this stuff will go away when joystick configuration is done via dialog + */ +struct JoystickMenuIdsType { + UINT Joysticks[2]; + UINT Axes[2]; + UINT AnalogMousestick; + UINT DigitalMousestick; +}; + +/*****************************************************************************/ +constexpr JoystickMenuIdsType JoystickMenuIds[NUM_BBC_JOYSTICKS]{ + { + { IDM_JOYSTICK, IDM_JOY1_PCJOY2 }, + { IDM_JOY1_PRIMARY, IDM_JOY1_SECONDARY1 }, + IDM_ANALOGUE_MOUSESTICK, + IDM_DIGITAL_MOUSESTICK, + }, + { + { IDM_JOY2_PCJOY1, IDM_JOY2_PCJOY2 }, + { IDM_JOY2_PRIMARY, IDM_JOY2_SECONDARY1 }, + IDM_JOY2_ANALOGUE_MOUSESTICK, + IDM_JOY2_DIGITAL_MOUSESTICK, + } +}; + +/*****************************************************************************/ +std::string JoystickOrderEntry::to_string() +{ + char buf[10]; + sprintf_s(buf, "%04x:%04x", mId(), pId()); + std::string result(buf); + if (Name.length() != 0) + result += " # " + Name; + return result; +} + +/*****************************************************************************/ +bool JoystickOrderEntry::from_string(const std::string& str) +{ + Name = ""; + + size_t split = str.find('#'); + if (split != std::string::npos) + { + ++split; + while (split < str.length() && std::isspace(str[split])) + ++split; + Name = str.substr(split); + } + + return sscanf(str.c_str(), "%x:%x", &mId(), &pId()) == 2; +} + +/****************************************************************************/ +static int MenuIdToStick(int bbcIdx, UINT menuId) +{ + for (int pcIdx = 0; pcIdx < _countof(JoystickMenuIdsType::Joysticks); ++pcIdx) + { + if (menuId == JoystickMenuIds[bbcIdx].Joysticks[pcIdx]) + return pcIdx + 1; + } + return 0; +} + +/****************************************************************************/ +static UINT StickToMenuId(int bbcIdx, int pcStick) +{ + if (pcStick > 0 && pcStick - 1 < _countof(JoystickMenuIdsType::Joysticks)) + return JoystickMenuIds[bbcIdx].Joysticks[pcStick - 1]; + return 0; +} + +/****************************************************************************/ +static int MenuIdToAxes(int bbcIdx, UINT menuId) +{ + for (int axesIdx = 0; axesIdx < _countof(JoystickMenuIdsType::Axes); ++axesIdx) + { + if (menuId == JoystickMenuIds[bbcIdx].Axes[axesIdx]) + return axesIdx; + } + return 0; +} + +/****************************************************************************/ +static UINT AxesToMenuId(int bbcIdx, int pcAxes) +{ + if (pcAxes >= 0 && pcAxes < _countof(JoystickMenuIdsType::Axes)) + return JoystickMenuIds[bbcIdx].Axes[pcAxes]; + return 0; +} + +/*****************************************************************************/ +std::string JoystickDev::DisplayString() +{ + if (!m_Configured) + return std::string{}; + + std::string name = m_Name; + if (m_Instance != 1) + name += " #" + std::to_string(m_Instance); + return name; +} + +#ifndef NDEBUG +void OutputDebug(const char* format, ...) +{ + char buf[256]; + va_list var; + va_start(var, format); + vsnprintf(buf, sizeof(buf), format, var); + OutputDebugString(buf); + va_end(var); +} +#else +void OutputDebug(const char* format, ...) { (void)format; } +#endif + +/*****************************************************************************/ +bool JoystickDev::Update() +{ + HRESULT hr; + if (!m_Present) + return false; + + hr = m_Device->Poll(); + if (FAILED(hr)) { OutputDebug("Poll result %08x\n", hr); } + if (FAILED(hr)) + { + hr = m_Device->Acquire(); + OutputDebug("Acquire result %08x\n", hr); + + if (hr == DIERR_UNPLUGGED) + return false; + + return true; + } + if (FAILED(hr = m_Device->GetDeviceState(sizeof(DIJOYSTATE2), &m_JoyState))) + { + OutputDebug("GeteviceState result %08x\n", hr); + return false; + } + return true; +} + +/*****************************************************************************/ +DWORD JoystickDev::GetButtons() +{ + DWORD buttons{ 0 }; + for (int i = 0; i < JOYSTICK_MAX_BTNS && i < sizeof(DIJOYSTATE2::rgbButtons); ++i) + { + if (m_JoyState.rgbButtons[i] & 0x80) + buttons |= (1 << i); + } + return buttons; +} + +static constexpr unsigned int setbit(int bit) { return 1u << bit; } +constexpr DWORD xboxAxes = setbit(JOYSTICK_AXIS_RX_P) | setbit(JOYSTICK_AXIS_RY_P); +constexpr DWORD stdpadAxes = setbit(JOYSTICK_AXIS_Z_P) | setbit(JOYSTICK_AXIS_RZ_P); + +/*****************************************************************************/ +void JoystickDev::GetAxesValue(int axesSet, int& x, int& y) +{ + switch (axesSet) + { + case 1: + case 2: + if ((m_PresentAxes & xboxAxes) == xboxAxes) + { + x = m_JoyState.lRx; + y = m_JoyState.lRy; + } + else if ((m_PresentAxes & stdpadAxes) == stdpadAxes) + { + x = m_JoyState.lZ; + y = m_JoyState.lRz; + } + else + { + x = 32767; + y = 32767; + } + break; + default: + x = m_JoyState.lX; + y = m_JoyState.lY; + break; + } +} + +DWORD JoystickDev::GetAxesState(int threshold) +{ + DIJOYSTATE2& state = m_JoyState; + unsigned int axes = 0; + + auto Detect = [&axes, threshold](const int val, const int nbit, const int pbit) { + if (val < 32767 - threshold) + axes |= (1 << nbit); + else if (val > 32767 + threshold) + axes |= (1 << pbit); + }; + + Detect(state.lX, JOYSTICK_AXIS_LEFT, JOYSTICK_AXIS_RIGHT); + Detect(state.lY, JOYSTICK_AXIS_UP, JOYSTICK_AXIS_DOWN); + + // If axes RX and RY are not present, but Z and RZ are, + // map Z and RZ to RX and RY + if ((m_PresentAxes & xboxAxes) == 0) + { + if (m_PresentAxes & setbit(JOYSTICK_AXIS_Z_P)) + Detect(state.lZ, JOYSTICK_AXIS_RX_N, JOYSTICK_AXIS_RX_P); + if (m_PresentAxes & setbit(JOYSTICK_AXIS_RZ_P)) + Detect(state.lZ, JOYSTICK_AXIS_RY_N, JOYSTICK_AXIS_RY_P); + } + else + { + if (m_PresentAxes & setbit(JOYSTICK_AXIS_Z_P)) + Detect(state.lZ, JOYSTICK_AXIS_Z_N, JOYSTICK_AXIS_Z_P); + if (m_PresentAxes & setbit(JOYSTICK_AXIS_RX_P)) + Detect(state.lRx, JOYSTICK_AXIS_RX_N, JOYSTICK_AXIS_RX_P); + if (m_PresentAxes & setbit(JOYSTICK_AXIS_RY_P)) + Detect(state.lRy, JOYSTICK_AXIS_RY_N, JOYSTICK_AXIS_RY_P); + if (m_PresentAxes & setbit(JOYSTICK_AXIS_RZ_P)) + Detect(state.lRz, JOYSTICK_AXIS_RZ_N, JOYSTICK_AXIS_RZ_P); + } + + if (m_PresentAxes & setbit(JOYSTICK_AXIS_HAT_UP)) + { + DWORD pov = state.rgdwPOV[0]; + if (pov != -1) + { + if (pov >= 29250 || pov < 6750) + axes |= (1 << JOYSTICK_AXIS_HAT_UP); + if (pov >= 2250 && pov < 15750) + axes |= (1 << JOYSTICK_AXIS_HAT_RIGHT); + if (pov >= 11250 && pov < 24750) + axes |= (1 << JOYSTICK_AXIS_HAT_DOWN); + if (pov >= 20250 && pov < 33750) + axes |= (1 << JOYSTICK_AXIS_HAT_LEFT); + } + } + + return axes; +} + +/****************************************************************************/ +void JoystickDev::EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi) +{ + // For axes that are returned, set the DIPROP_RANGE property for the + // enumerated axis in order to scale min/max values. + if (pdidoi->dwType & DIDFT_AXIS) + { + DIPROPRANGE diprg{}; + diprg.diph.dwSize = sizeof(DIPROPRANGE); + diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER); + diprg.diph.dwHow = DIPH_BYID; + diprg.diph.dwObj = pdidoi->dwType; // Specify the enumerated axis + diprg.lMin = 0; + diprg.lMax = 65535; + + // Set the range for the axis + if (FAILED(m_Device->SetProperty(DIPROP_RANGE, &diprg.diph))) + return; + } + + if (pdidoi->dwType & DIDFT_BUTTON) + { + WORD instance = DIDFT_GETINSTANCE(pdidoi->dwType); + if (instance < JOYSTICK_MAX_BTNS) + m_PresentButtons |= setbit(instance); + } + + if (pdidoi->guidType == GUID_YAxis) + m_PresentAxes |= setbit(JOYSTICK_AXIS_UP) | setbit(JOYSTICK_AXIS_DOWN); + if (pdidoi->guidType == GUID_XAxis) + m_PresentAxes |= setbit(JOYSTICK_AXIS_LEFT) | setbit(JOYSTICK_AXIS_RIGHT); + if (pdidoi->guidType == GUID_ZAxis) + m_PresentAxes |= setbit(JOYSTICK_AXIS_Z_N) | setbit(JOYSTICK_AXIS_Z_P); + if (pdidoi->guidType == GUID_RxAxis) + m_PresentAxes |= setbit(JOYSTICK_AXIS_RX_N) | setbit(JOYSTICK_AXIS_RX_P); + if (pdidoi->guidType == GUID_RyAxis) + m_PresentAxes |= setbit(JOYSTICK_AXIS_RY_N) | setbit(JOYSTICK_AXIS_RY_P); + if (pdidoi->guidType == GUID_RzAxis) + m_PresentAxes |= setbit(JOYSTICK_AXIS_RZ_N) | setbit(JOYSTICK_AXIS_RZ_P); + if (pdidoi->guidType == GUID_POV) + m_PresentAxes |= setbit(JOYSTICK_AXIS_HAT_LEFT) | setbit(JOYSTICK_AXIS_HAT_RIGHT) + | setbit(JOYSTICK_AXIS_HAT_UP) | setbit(JOYSTICK_AXIS_HAT_DOWN); +} + +/****************************************************************************/ +JoystickDev::JoystickDev(JoystickDev&& r) +{ + m_GUIDInstance = r.m_GUIDInstance; + m_Id = r.m_Id; + std::swap(m_Name, r.m_Name); + std::swap(m_Device, r.m_Device); + m_PresentAxes = r.m_PresentAxes; + m_PresentButtons = r.m_PresentButtons; + m_Instance = r.m_Instance; + m_OnOrderList = r.m_OnOrderList; + m_JoyIndex = r.m_JoyIndex; + m_Configured = r.m_Configured; + m_Present = r.m_Present; + m_Captured = r.m_Captured; + m_PrevAxes = r.m_PrevAxes; + m_PrevBtns = r.m_PrevBtns; + m_JoystickToKeysActive = r.m_JoystickToKeysActive; + r.m_Present = false; +} + +/****************************************************************************/ +JoystickDev& JoystickDev::operator=(JoystickDev&& r) +{ + m_GUIDInstance = r.m_GUIDInstance; + m_Id = r.m_Id; + std::swap(m_Name, r.m_Name); + std::swap(m_Device, r.m_Device); + m_PresentAxes = r.m_PresentAxes; + m_PresentButtons = r.m_PresentButtons; + m_Instance = r.m_Instance; + m_OnOrderList = r.m_OnOrderList; + m_JoyIndex = r.m_JoyIndex; + m_Configured = r.m_Configured; + m_Present = r.m_Present; + m_Captured = r.m_Captured; + m_PrevAxes = r.m_PrevAxes; + m_PrevBtns = r.m_PrevBtns; + m_JoystickToKeysActive = r.m_JoystickToKeysActive; + r.m_Present = false; + return *this; +} + +/****************************************************************************/ +void JoystickDev::CloseDevice() +{ + m_Captured = false; + if (m_Device) + { + m_Device->Unacquire(); + m_Device->Release(); + m_Device = nullptr; + } +} + +/****************************************************************************/ +JoystickDev::~JoystickDev() +{ + CloseDevice(); +} + +/****************************************************************************/ +void JoystickHandler::AddDeviceInstance(const DIDEVICEINSTANCE* pdidInstance) +{ + int mid = LOWORD(pdidInstance->guidProduct.Data1); + int pid = HIWORD(pdidInstance->guidProduct.Data1); + + int index = m_JoystickDevs.size(); + + JoystickDev dev; + dev.m_GUIDInstance = pdidInstance->guidInstance; + dev.m_Id = JoystickId(mid, pid); + dev.m_Name = pdidInstance->tszInstanceName; + dev.m_Configured = true; + dev.m_Present = true; + dev.m_JoyIndex = index; + + m_JoystickDevs.emplace_back(std::move(dev)); +} + +/****************************************************************************/ + +static BOOL CALLBACK EnumJoysticksCallback(const DIDEVICEINSTANCE* pdidInstance, VOID* pContext) +{ + reinterpret_cast(pContext)->AddDeviceInstance(pdidInstance); + return DIENUM_CONTINUE; +} + +/****************************************************************************/ +HRESULT JoystickHandler::ScanJoysticks() +{ + bool usePreferredCfg = m_HavePreferredJoyCfg; + + std::map> listById; + + // Clear joystick devs vector - closes any open devices + m_JoystickDevs.clear(); + + if (FAILED(m_DirectInputInitResult)) + return m_DirectInputInitResult; + + HRESULT hr = m_pDirectInput->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumJoysticksCallback, this, DIEDFL_ATTACHEDONLY); + if (FAILED(hr)) + return hr; + + for (unsigned int devIdx = 0; devIdx < m_JoystickDevs.size(); ++devIdx) + { + JoystickDev& dev = m_JoystickDevs[devIdx]; + listById[dev.Id()].push_back(&dev); + dev.m_Instance = listById[dev.Id()].size(); + } + + std::vector sorted; + // Start with joysticks in the order list + for (JoystickOrderEntry& entry : m_JoystickOrder) + { + std::list& list = listById[entry]; + if (list.size() != 0) + { + JoystickDev& dev = *(list.front()); + list.pop_front(); + dev.m_OnOrderList = true; + dev.m_JoyIndex = sorted.size(); + if (usePreferredCfg && IsEqualGUID(dev.m_GUIDInstance, m_PreferredJoyCfg.guidInstance)) + usePreferredCfg = false; + sorted.emplace_back(std::move(dev)); + entry.JoyIndex = dev.m_JoyIndex; + } + else + { + entry.JoyIndex = -1; + } + } + + int newJoysticks = 0; + + // Add preferred joystick first + if (usePreferredCfg) + { + for (JoystickDev& dev : m_JoystickDevs) + { + if (dev.m_Present && !dev.m_OnOrderList && IsEqualGUID(dev.m_GUIDInstance, m_PreferredJoyCfg.guidInstance)) + { + dev.m_JoyIndex = sorted.size(); + sorted.emplace_back(std::move(dev)); + ++newJoysticks; + break; + } + } + } + + // Add joysticks not in the order list + for (JoystickDev& dev : m_JoystickDevs) + { + if (dev.m_Present && !dev.m_OnOrderList) + { + dev.m_JoyIndex = sorted.size(); + sorted.emplace_back(std::move(dev)); + ++newJoysticks; + } + } + + // If joystick order list is too long, remove some unconnected entries + // Remove entries from the beginning or from the end, or some other order? + int to_remove = newJoysticks + m_JoystickOrder.size() - MAX_JOYSTICK_ORDER; + unsigned int idx = 0; + while (to_remove > 0 && idx < m_JoystickOrder.size()) + { + if (m_JoystickOrder[idx].JoyIndex == -1) + { + m_JoystickOrder.erase(m_JoystickOrder.begin() + idx); + --to_remove; + } + else + ++idx; + } + + // Replace joystick device vector with sorted one + m_JoystickDevs = std::move(sorted); + + // Add new joysticks at the end of order list + for (JoystickDev& dev : m_JoystickDevs) + { + if (!dev.m_OnOrderList) + { + m_JoystickOrder.emplace_back(dev.Id(), dev.DisplayString(), dev.m_JoyIndex); + dev.m_OnOrderList = true; + } + } + + return S_OK; +} + +/****************************************************************************/ + +static BOOL CALLBACK EnumJoystickObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext) +{ + reinterpret_cast(pContext)->EnumObjectsCallback(pdidoi); + return DIENUM_CONTINUE; +} + +/****************************************************************************/ +HRESULT JoystickHandler::OpenDevice(HWND mainWindow, JoystickDev* dev) +{ + HRESULT hr; + + // Do nothing if device already open + if (dev->m_Device) + return S_OK; + + if (FAILED(hr = m_pDirectInput->CreateDevice(dev->m_GUIDInstance, &dev->m_Device, nullptr))) + return hr; + + if (FAILED(hr = dev->m_Device->SetDataFormat(&c_dfDIJoystick2))) + { + dev->CloseDevice(); + return hr; + } + + if (FAILED(hr = dev->m_Device->SetCooperativeLevel(mainWindow, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND))) + { + dev->CloseDevice(); + return hr; + } + + if (FAILED(hr = dev->m_Device->EnumObjects(EnumJoystickObjectsCallback, (VOID*)dev, DIDFT_ALL))) + { + dev->CloseDevice(); + return hr; + } + + return S_OK; +} + +/****************************************************************************/ +HRESULT JoystickHandler::InitDirectInput() +{ + if (FAILED(m_DirectInputInitResult = DirectInput8Create(GetModuleHandle(NULL), + DIRECTINPUT_VERSION, IID_IDirectInput8, (VOID**)&m_pDirectInput, NULL))) + return m_DirectInputInitResult; + + HRESULT hr; + IDirectInputJoyConfig8* pJoyConfig = nullptr; + if (FAILED(hr = m_pDirectInput->QueryInterface(IID_IDirectInputJoyConfig8, (void**)&pJoyConfig))) + return S_OK; + + m_PreferredJoyCfg.dwSize = sizeof(m_PreferredJoyCfg); + if (SUCCEEDED(pJoyConfig->GetConfig(0, &m_PreferredJoyCfg, DIJC_GUIDINSTANCE))) + m_HavePreferredJoyCfg = true; + + if (pJoyConfig) + pJoyConfig->Release(); + + return S_OK; +} + +/****************************************************************************/ +JoystickHandler::~JoystickHandler() +{ + if (m_pDirectInput) + { + m_pDirectInput->Release(); + m_pDirectInput = nullptr; + } +} + +/****************************************************************************/ +JoystickHandlerPtr::JoystickHandlerPtr() : std::unique_ptr{ std::make_unique() } {} + +/****************************************************************************/ +JoystickHandlerPtr::~JoystickHandlerPtr() {} + +/****************************************************************************/ +void BeebWin::SetJoystickTarget(HWND target) +{ + m_JoystickTarget = target; +} + +/****************************************************************************/ +// Update joystick configuration from checked menu items +void BeebWin::UpdateJoystickConfig(int bbcIdx) +{ + m_JoystickConfig[bbcIdx].Enabled = (m_MenuIdSticks[bbcIdx] != 0); + m_JoystickConfig[bbcIdx].PCStick = MenuIdToStick(bbcIdx, m_MenuIdSticks[bbcIdx]); + m_JoystickConfig[bbcIdx].PCAxes = MenuIdToAxes(bbcIdx, m_MenuIdAxes[bbcIdx]); + m_JoystickConfig[bbcIdx].AnalogMousestick = + (static_cast(m_MenuIdSticks[bbcIdx]) == JoystickMenuIds[bbcIdx].AnalogMousestick); + m_JoystickConfig[bbcIdx].DigitalMousestick = + (static_cast(m_MenuIdSticks[bbcIdx]) == JoystickMenuIds[bbcIdx].DigitalMousestick); +} + +/****************************************************************************/ +int BeebWin::GetPCJoystick(int bbcIdx) +{ + return m_JoystickConfig[bbcIdx].PCStick; +} + +/****************************************************************************/ +bool BeebWin::GetAnalogMousestick(int bbcIdx) +{ + return m_JoystickConfig[bbcIdx].AnalogMousestick; +} + +/****************************************************************************/ +bool BeebWin::GetDigitalMousestick(int bbcIdx) +{ + return m_JoystickConfig[bbcIdx].DigitalMousestick; +} + +/****************************************************************************/ +// Check if PC joystick is assigned to any BBC joystick +bool BeebWin::IsPCJoystickOn(int pcIdx) +{ + if (pcIdx >= _countof(JoystickMenuIdsType::Joysticks)) + return false; + + for (int bbcIdx = 0; bbcIdx < NUM_BBC_JOYSTICKS; ++bbcIdx) + { + if (GetPCJoystick(bbcIdx) == pcIdx + 1) + return true; + } + + return false; +} + +/*****************************************************************************/ +void BeebWin::InitJoystickMenu() +{ + for (int bbcIdx = 0; bbcIdx < NUM_BBC_JOYSTICKS; ++bbcIdx) + { + for (int pcIdx = 0; pcIdx < _countof(JoystickMenuIdsType::Joysticks); ++pcIdx) + { + CheckMenuItem(JoystickMenuIds[bbcIdx].Joysticks[pcIdx], false); + } + CheckMenuItem(JoystickMenuIds[bbcIdx].AnalogMousestick, false); + CheckMenuItem(JoystickMenuIds[bbcIdx].DigitalMousestick, false); + if (m_MenuIdSticks[bbcIdx] != 0) + CheckMenuItem(m_MenuIdSticks[bbcIdx], true); + + for (int axesIdx = 0; axesIdx < _countof(JoystickMenuIdsType::Joysticks); ++axesIdx) + { + CheckMenuItem(JoystickMenuIds[bbcIdx].Axes[axesIdx], false); + } + if (m_MenuIdAxes[bbcIdx] != 0) + CheckMenuItem(m_MenuIdAxes[bbcIdx], true); + } + + CheckMenuItem(IDM_JOYSTICK_TO_KEYS, m_JoystickToKeys); + CheckMenuItem(IDM_AUTOLOADJOYMAP, m_AutoloadJoystickMap); +} + +/****************************************************************************/ +void BeebWin::UpdateJoystickMenu() +{ + + // Check "Rescan Joysticks" menu item if any joystick is found + CheckMenuItem(IDM_INIT_JOYSTICK, m_JoystickHandler->m_JoystickDevs.size() != 0); + + // Enable axes menu items if any PC joystick is assigned to the related BBC joystick + for (int bbcIdx = 0; bbcIdx < NUM_BBC_JOYSTICKS; ++bbcIdx) + { + bool EnableAxes = false; + static const char* const menuItems[2] = { "First PC &Joystick", "Second PC Joystick" }; + + for (int pcIdx = 0; pcIdx < _countof(JoystickMenuIdsType::Joysticks); ++pcIdx) + { + JoystickDev* dev = m_JoystickHandler->GetDev(pcIdx); + if (dev) + { + SetMenuItemText(JoystickMenuIds[bbcIdx].Joysticks[pcIdx], dev->DisplayString()); + } + else + { + SetMenuItemText(JoystickMenuIds[bbcIdx].Joysticks[pcIdx], menuItems[pcIdx]); + } + } + + if (GetPCJoystick(bbcIdx) != 0) + EnableAxes = true; + + for (int axesIdx = 0; axesIdx < _countof(JoystickMenuIdsType::Axes); ++axesIdx) + EnableMenuItem(JoystickMenuIds[bbcIdx].Axes[axesIdx], EnableAxes); + } + + CheckMenuItem(IDM_JOY_SENSITIVITY_50, m_JoystickSensitivity == 0.5); + CheckMenuItem(IDM_JOY_SENSITIVITY_100, m_JoystickSensitivity == 1.0); + CheckMenuItem(IDM_JOY_SENSITIVITY_200, m_JoystickSensitivity == 2.0); + CheckMenuItem(IDM_JOY_SENSITIVITY_300, m_JoystickSensitivity == 3.0); + + CheckMenuItem(IDM_JOY_KEY_THRESHOLD_12_5, m_JoystickToKeysThreshold == 4096); + CheckMenuItem(IDM_JOY_KEY_THRESHOLD_25, m_JoystickToKeysThreshold == 8192); + CheckMenuItem(IDM_JOY_KEY_THRESHOLD_50, m_JoystickToKeysThreshold == 16384); + CheckMenuItem(IDM_JOY_KEY_THRESHOLD_75, m_JoystickToKeysThreshold == 24756); +} + +/****************************************************************************/ +void BeebWin::ProcessJoystickMenuCommand(int bbcIdx, UINT MenuId) +{ + /* Disable current selection */ + if (m_MenuIdSticks[bbcIdx] != 0) + { + CheckMenuItem(m_MenuIdSticks[bbcIdx], false); + + // Reset joystick position to centre + JoystickX[bbcIdx] = 32767; + JoystickY[bbcIdx] = 32767; + + SetJoystickButton(bbcIdx, false); + + if (bbcIdx == 0 && !m_JoystickConfig[1].Enabled) + { + SetJoystickButton(1, false); + } + } + + if (MenuId == static_cast(m_MenuIdSticks[bbcIdx])) + { + /* Joystick switched off completely */ + m_MenuIdSticks[bbcIdx] = 0; + } + else + { + /* Initialise new selection */ + m_MenuIdSticks[bbcIdx] = MenuId; + + CheckMenuItem(m_MenuIdSticks[bbcIdx], true); + } + UpdateJoystickConfig(bbcIdx); + InitJoystick(false); + +} + +/****************************************************************************/ +void BeebWin::ProcessJoystickAxesMenuCommand(int bbcIdx, UINT MenuId) +{ + CheckMenuItem(m_MenuIdAxes[bbcIdx], false); + m_MenuIdAxes[bbcIdx] = MenuId; + UpdateJoystickConfig(bbcIdx); + CheckMenuItem(m_MenuIdAxes[bbcIdx], true); +} + +/****************************************************************************/ +void BeebWin::ProcessJoystickToKeysCommand(void) +{ + m_JoystickToKeys = !m_JoystickToKeys; + InitJoystick(false); + CheckMenuItem(IDM_JOYSTICK_TO_KEYS, m_JoystickToKeys); +} + +/****************************************************************************/ +void BeebWin::ProcessAutoloadJoystickMapCommand(void) +{ + m_AutoloadJoystickMap = !m_AutoloadJoystickMap; + CheckMenuItem(IDM_AUTOLOADJOYMAP, m_AutoloadJoystickMap); +} + +/****************************************************************************/ +void BeebWin::ResetJoystick() +{ + for (JoystickDev& dev : m_JoystickHandler->m_JoystickDevs) + { + if (dev.m_Captured) + { + dev.m_Captured = false; + TranslateJoystick(dev.m_JoyIndex); + } + dev.CloseDevice(); + } +} + +/****************************************************************************/ +bool BeebWin::ScanJoysticks(bool verbose) +{ + ResetJoystick(); + + if (!m_JoystickHandler->m_DirectInputInitialized) + m_JoystickHandler->InitDirectInput(); + + if (FAILED(m_JoystickHandler->m_DirectInputInitResult)) + { + if (verbose) + { + char msg[128]; + snprintf(msg, sizeof(msg), "DirectInput initialization failed.\nError Code: %08X", m_JoystickHandler->m_DirectInputInitResult); + MessageBox(m_hWnd, msg, WindowTitle, MB_OK | MB_ICONERROR); + } + return false; + } + + HRESULT hr; + if (FAILED(hr = m_JoystickHandler->ScanJoysticks())) + { + if (verbose) + { + char msg[128]; + snprintf(msg, sizeof(msg), "Joystick enumeration failed.\nError Code: %08X", hr); + MessageBox(m_hWnd, msg, WindowTitle, MB_OK | MB_ICONERROR); + } + return false; + } + + if (m_JoystickHandler->m_JoystickDevs.size() == 0) + { + if (verbose) + { + MessageBox(m_hWnd, "No joysticks found", WindowTitle, MB_OK | MB_ICONERROR); + } + return false; + } + + return true; +} + +/****************************************************************************/ +void BeebWin::InitJoystick(bool verbose) +{ + + ResetJoystick(); + + if (m_JoystickToKeys) + { + for (int pcIdx = 0; pcIdx < NUM_PC_JOYSTICKS; ++pcIdx) + { + CaptureJoystick(pcIdx, verbose); + } + } + + int pcJoy1 = GetPCJoystick(0); + if (pcJoy1 != 0) + { + if (!m_JoystickToKeys || pcJoy1 > NUM_PC_JOYSTICKS) + CaptureJoystick(pcJoy1 - 1, verbose); + } + + int pcJoy2 = GetPCJoystick(1); + if (pcJoy2 != 0 && pcJoy2 != pcJoy1) + { + if (!m_JoystickToKeys || pcJoy2 > NUM_PC_JOYSTICKS) + CaptureJoystick(pcJoy2 - 1, verbose); + } + + if (GetPCJoystick(0) || GetPCJoystick(1) || m_JoystickToKeys) + { + if (!m_JoystickTimerRunning) + { + SetTimer(m_hWnd, 3, 20, NULL); + m_JoystickTimerRunning = true; + } + } + else + { + if (m_JoystickTimerRunning) + { + KillTimer(m_hWnd, 3); + m_JoystickTimerRunning = false; + } + } + + UpdateJoystickMenu(); +} + +/****************************************************************************/ +bool BeebWin::CaptureJoystick(int Index, bool verbose) +{ + bool success = false; + JoystickDev* dev = m_JoystickHandler->GetDev(Index); + + if (dev) + { + HRESULT hr = m_JoystickHandler->OpenDevice(m_hWnd, dev); + if (!FAILED(hr)) + { + dev->m_Captured = true; + success = true; + } + else if (verbose) + { + char str[256]; + snprintf(str, sizeof(str), "Failed to initialise %s", dev->DisplayString().c_str()); + + MessageBox(m_hWnd, str, WindowTitle, MB_OK | MB_ICONWARNING); + } + } + + return success; +} + +/****************************************************************************/ +void BeebWin::SetJoystickButton(int index, bool value) +{ + JoystickButton[index] = value; +} + +/****************************************************************************/ +void BeebWin::ScaleJoystick(int index, unsigned int x, unsigned int y, + unsigned int minX, unsigned int minY, unsigned int maxX, unsigned int maxY) +{ + /* Gain and reverse the readings */ + double sx = 0.5 + ((double)(maxX - x) / (double)(maxX - minX) - 0.5) * m_JoystickSensitivity; + double sy = 0.5 + ((double)(maxY - y) / (double)(maxY - minY) - 0.5) * m_JoystickSensitivity; + + /* Scale to 0-65535 range */ + sx = std::max(0.0, std::min(65535.0, sx * 65535.0)); + sy = std::max(0.0, std::min(65535.0, sy * 65535.0)); + + JoystickX[index] = (int)sx; + JoystickY[index] = (int)sy; +} + +/****************************************************************************/ +void BeebWin::SetMousestickButton(int index, bool value) +{ + if (index == 0) + { + // Left mouse button + for (int bbcIdx = 0; bbcIdx < NUM_BBC_JOYSTICKS; ++bbcIdx) + { + if (m_JoystickConfig[bbcIdx].AnalogMousestick || + m_JoystickConfig[bbcIdx].DigitalMousestick) + { + SetJoystickButton(bbcIdx, value); + } + } + } + else + { + // Map right mouse button to secondary fire if second joystick is + // not enabled. + if (!m_JoystickConfig[1].Enabled && + (m_JoystickConfig[0].AnalogMousestick || m_JoystickConfig[0].DigitalMousestick)) + { + SetJoystickButton(1, value); + } + } +} + +/****************************************************************************/ +void BeebWin::ScaleMousestick(unsigned int x, unsigned int y) +{ + for (int index = 0; index < 2; ++index) + { + if (m_JoystickConfig[index].AnalogMousestick) + { + ScaleJoystick(index, x, y, 0, 0, m_XWinSize, m_YWinSize); + } + else if (m_JoystickConfig[index].DigitalMousestick) + { + const int Threshold = 4000; + + /* Keep 32768.0 double to convert from unsigned int */ + double XPos = (((m_XWinSize - x) * 65535 / m_XWinSize) - 32768.0) * m_JoystickSensitivity; + double YPos = (((m_YWinSize - y) * 65535 / m_YWinSize) - 32768.0) * m_JoystickSensitivity; + + if (XPos < -Threshold) + { + JoystickX[index] = 0; + } + else if (XPos > Threshold) + { + JoystickX[index] = 65535; + } + else + { + JoystickX[index] = 32768; + } + + if (YPos < -Threshold) + { + JoystickY[index] = 0; + } + else if (YPos > Threshold) + { + JoystickY[index] = 65535; + } + else + { + JoystickY[index] = 32768; + } + } + } +} + +/****************************************************************************/ +// Translate joystick position changes to key up or down message +void BeebWin::TranslateAxes(int joyId, unsigned int axesState) +{ + JoystickDev* dev = m_JoystickHandler->GetDev(joyId); + unsigned int& prevAxes = dev->m_PrevAxes; + int vkeys = BEEB_VKEY_JOY_START + joyId * (JOYSTICK_MAX_AXES + JOYSTICK_MAX_BTNS); + + if (axesState != prevAxes) + { + for (int axId = 0; axId < JOYSTICK_MAX_AXES; ++axId) + { + if ((axesState & ~prevAxes) & (1 << axId)) + { + TranslateOrSendKey(vkeys + axId, false); + } + else if ((~axesState & prevAxes) & (1 << axId)) + { + TranslateOrSendKey(vkeys + axId, true); + } + } + prevAxes = axesState; + } +} + +/****************************************************************************/ +void BeebWin::TranslateJoystickButtons(int joyId, unsigned int buttons) +{ + JoystickDev* dev = m_JoystickHandler->GetDev(joyId); + unsigned int& prevBtns = dev->m_PrevBtns; + int vkeys = BEEB_VKEY_JOY_START + JOYSTICK_MAX_AXES + + joyId * (JOYSTICK_MAX_AXES + JOYSTICK_MAX_BTNS); + + if (buttons != prevBtns) + { + for (int btnId = 0; btnId < JOYSTICK_MAX_BTNS; ++btnId) + { + if ((buttons & ~prevBtns) & (1 << btnId)) + { + TranslateOrSendKey(vkeys + btnId, false); + } + else if ((~buttons & prevBtns) & (1 << btnId)) + { + TranslateOrSendKey(vkeys + btnId, true); + } + } + + prevBtns = buttons; + } +} + +/****************************************************************************/ +void BeebWin::TranslateOrSendKey(int vkey, bool keyUp) +{ + if (m_JoystickTarget == nullptr) + { + int row, col; + TranslateKey(vkey, keyUp, row, col); + } + else if (!keyUp) + { + // Joystick mapping dialog is visible - translate button down to key down + // message and send to dialog + PostMessage(m_JoystickTarget, WM_KEYDOWN, vkey, 0); + } +} + +/****************************************************************************/ +void BeebWin::TranslateJoystick(int joyId) +{ + JoystickDev* joyDev = m_JoystickHandler->GetDev(joyId); + bool success = false; + DWORD buttons = 0; + + if (!joyDev) + return; + + if (joyDev->m_Captured && joyDev->Update()) + { + success = true; + buttons = joyDev->GetButtons(); + } + + // If joystick is disabled or error occurs, reset all axes and buttons + if (!success) + { + if (joyDev->m_Captured) + { + // Reset 'captured' flag and update menu entry + joyDev->m_Captured = false; + UpdateJoystickMenu(); + } + } + + // PC joystick to BBC joystick + for (int bbcIndex = 0; bbcIndex < NUM_BBC_JOYSTICKS; ++bbcIndex) + { + if (GetPCJoystick(bbcIndex) == joyId + 1) + { + if (bbcIndex == 1 && m_JoystickConfig[0].PCStick == m_JoystickConfig[1].PCStick + && m_JoystickConfig[0].PCAxes != m_JoystickConfig[1].PCAxes) + { + // If both BBC joysticks are mapped to the same PC gamepad, but not + // the same axes, map buttons 2 and 4 to the Beeb's PB1 input + SetJoystickButton(bbcIndex, (buttons & (JOY_BUTTON2 | JOY_BUTTON4)) != 0); + } + else + { + SetJoystickButton(bbcIndex, (buttons & (JOY_BUTTON1 | JOY_BUTTON3)) != 0); + } + + if (bbcIndex == 0 && !m_JoystickConfig[1].Enabled) + { + // Second BBC joystick not enabled - map buttons 2 and 4 to Beeb's PB1 input + SetJoystickButton(1, (buttons & (JOY_BUTTON2 | JOY_BUTTON4)) != 0); + } + + int axesConfig = m_JoystickConfig[bbcIndex].PCAxes; + int x = 32767, y = 32767; + if (success) + joyDev->GetAxesValue(axesConfig, x, y); + ScaleJoystick(bbcIndex, x, y, 0, 0, 65535, 65535); + } + } + + // Joystick to keyboard mapping + if (joyId < NUM_PC_JOYSTICKS && m_JoystickToKeys && success) + { + DWORD axes = joyDev->GetAxesState(m_JoystickToKeysThreshold); + TranslateAxes(joyId, axes); + TranslateJoystickButtons(joyId, buttons); + joyDev->m_JoystickToKeysActive = true; + } + + // Make sure to reset keyboard state + if (!m_JoystickToKeys && joyDev->m_JoystickToKeysActive) + { + TranslateAxes(joyId, 0); + TranslateJoystickButtons(joyId, 0); + joyDev->m_JoystickToKeysActive = false; + } +} + +/*****************************************************************************/ +void BeebWin::UpdateJoysticks() +{ + int translated = 0; + + // Don't update joysticks if not in foreground + if (!m_active && m_JoystickTarget == nullptr) + return; + + // Translate first four joysticks if joystick to keyboard is enabled + if (m_JoystickToKeys) + { + for (int idx = 0; idx < NUM_PC_JOYSTICKS; ++idx) + { + JoystickDev* dev = m_JoystickHandler->GetDev(idx); + if (dev && dev->m_Captured) + { + TranslateJoystick(idx); + translated |= setbit(idx); + } + } + } + + // Translate joystick for BBC joystick 1 + if (m_JoystickConfig[0].Enabled && m_JoystickConfig[0].PCStick != 0) + { + int pcStick = m_JoystickConfig[0].PCStick - 1; + if (!(translated & setbit(pcStick))) + { + TranslateJoystick(pcStick); + translated |= setbit(pcStick); + } + } + + // Translate joystick for BBC joystick 2 + if (m_JoystickConfig[1].Enabled && m_JoystickConfig[1].PCStick != 0) + { + int pcStick = m_JoystickConfig[1].PCStick - 1; + if (!(translated & setbit(pcStick))) + { + TranslateJoystick(pcStick); + translated |= setbit(pcStick); + } + } +} + +/*****************************************************************************/ +// Look for file specific joystick map +void BeebWin::CheckForJoystickMap(const char *path) +{ + char file[_MAX_PATH]; + char drive[_MAX_DRIVE]; + char dir[_MAX_DIR]; + char filename[_MAX_FNAME]; + + if (!m_AutoloadJoystickMap || !path || !path[0]) + return; + + _splitpath(path, drive, dir, filename, NULL); + + // Look for prefs file with the same name with ".jmap" extension + _makepath(file, drive, dir, filename, "jmap"); + + if (GetFileAttributes(file) != INVALID_FILE_ATTRIBUTES) + { + ReadJoyMap(file, &JoystickMap); + } + else + { + // Mapping file not found - reset to default + ResetJoyMapToDefaultUser(); + + // Set jmap path even if the file doesn't exist + strcpy(m_JoystickMapPath, file); + } +} + +/****************************************************************************/ +void BeebWin::ResetJoystickMap() +{ + if (MessageBox(m_hWnd, "Clear joystick to keyboard mapping table?", WindowTitle, MB_YESNO | MB_ICONQUESTION) != IDYES) + return; + + ResetJoyMap(&JoystickMap); +} + +/****************************************************************************/ +void BeebWin::ResetJoyMap(JoyMap* joymap) +{ + // Initialize all input to unassigned + for (KeyPair& mapping : *joymap) + { + mapping[0].row = mapping[1].row = UNASSIGNED_ROW; + mapping[0].col = mapping[1].col = 0; + mapping[0].shift = false; + mapping[1].shift = true; + } +} + +/****************************************************************************/ +void BeebWin::ResetJoyMapToDefaultUser(void) +{ + char keymap[_MAX_PATH]; + strcpy(keymap, "DefaultUser.jmap"); + GetDataPath(m_UserDataPath, keymap); + ResetJoyMap(&JoystickMap); + if (GetFileAttributes(keymap) != INVALID_FILE_ATTRIBUTES) + ReadJoyMap(keymap, &JoystickMap); +} + +/****************************************************************************/ +static void makeupper(char* str) +{ + if (str == NULL) + return; + while (*str) + { + *str = static_cast(toupper(*str)); + ++str; + } +} + +/****************************************************************************/ +bool BeebWin::ReadJoyMap(const char *filename, JoyMap *joymap) +{ + bool success = true; + FILE *infile = OpenReadFile(filename, "joystick map", JOYMAP_TOKEN "\n"); + char buf[256]; + JoyMap newJoyMap; + int line = 1; + + if (infile == NULL) + return false; + + ResetJoyMap(&newJoyMap); + + while (fgets(buf, 255, infile) != NULL) + { + char *ptr, *inputName, *keyName1, *keyName2; + + ++line; + + // Read at most three tokens from line + inputName = strtok_s(buf, " \t\n", &ptr); + keyName1 = strtok_s(NULL, " \t\n", &ptr); + keyName2 = strtok_s(NULL, " \t\n", &ptr); + + // Ignore empty lines or lines starting with '#' + if (inputName == NULL || inputName[0] == '#') + continue; + + if (keyName1 == NULL) + { + char errstr[500]; + sprintf(errstr, "Invalid line in joystick mapping file:\n %s\n line %d\n", + filename, line); + MessageBox(m_hWnd, errstr, WindowTitle, MB_OK | MB_ICONERROR); + success = false; + break; + } + + // Get vkey number and mapping entry from input name + int vkey = SelectKeyDialog::JoyVKeyByName(inputName); + if (vkey < BEEB_VKEY_JOY_START || vkey >= BEEB_VKEY_JOY_END) + { + char errstr[500]; + sprintf(errstr, "Invalid input name in joystick mapping file:\n %s\n line %d\n", + filename, line); + MessageBox(m_hWnd, errstr, WindowTitle, MB_OK | MB_ICONERROR); + success = false; + break; + } + KeyMapping* mapping = newJoyMap[vkey - BEEB_VKEY_JOY_START]; + + // Get shift state and BBC key from key name + bool shift1 = false; + makeupper(keyName1); + + if (strncmp(keyName1, "SH+", 3) == 0) + { + shift1 = true; + keyName1 += 3; + } + + const BBCKey* key1 = GetBBCKeyByName(keyName1); + if (key1->row == UNASSIGNED_ROW && strcmp(keyName1, "NONE") != 0) + { + char errstr[500]; + sprintf(errstr, "Invalid key name in joystick mapping file:\n %s\n line %d\n", + filename, line); + MessageBox(m_hWnd, errstr, WindowTitle, MB_OK | MB_ICONERROR); + success = false; + break; + } + + if (keyName2 == NULL) + { + // Shifted and unshifted input map to the same key + mapping[0].row = mapping[1].row = key1->row; + mapping[0].col = mapping[1].col = key1->column; + mapping[0].shift = false; + mapping[1].shift = true; + } + else + { + bool shift2 = false; + makeupper(keyName2); + + if (strncmp(keyName2, "SH+", 3) == 0) + { + shift2 = true; + keyName2 += 3; + } + + const BBCKey* key2 = GetBBCKeyByName(keyName2); + if (key2->row == UNASSIGNED_ROW && strcmp(keyName2, "NONE") != 0) + { + char errstr[500]; + sprintf(errstr, "Invalid key name in joystick mapping file:\n %s\n line %d\n", + filename, line); + MessageBox(m_hWnd, errstr, WindowTitle, MB_OK | MB_ICONERROR); + success = false; + break; + } + + mapping[0].row = key1->row; + mapping[0].col = key1->column; + mapping[0].shift = shift1; + mapping[1].row = key2->row; + mapping[1].col = key2->column; + mapping[1].shift = shift2; + } + } + + if (success) + { + memcpy(joymap, &newJoyMap, sizeof(newJoyMap)); + } + + return success; +} + +/****************************************************************************/ +void BeebWin::LoadJoystickMap() +{ + char DefaultPath[_MAX_PATH]; + char FileName[_MAX_PATH]; + const char* filter = "Joystick Map File (*.jmap)\0*.jmap\0"; + + // If Autoload is enabled, look for joystick mapping near disks, + // otherwise start in user data path. + if (m_AutoloadJoystickMap) + { + m_Preferences.GetStringValue("DiscsPath", DefaultPath); + GetDataPath(GetUserDataPath(), DefaultPath); + } + else + { + strcpy(DefaultPath, GetUserDataPath()); + } + + FileDialog fileDialog(m_hWnd, FileName, sizeof(FileName), DefaultPath, filter); + + if (m_JoystickMapPath[0] == '\0' && !m_AutoloadJoystickMap) + { + fileDialog.SetInitial("DefaultUser.jmap"); + } + else + { + fileDialog.SetInitial(m_JoystickMapPath); + } + + if (fileDialog.Open()) + { + if (ReadJoyMap(FileName, &JoystickMap)) + strcpy(m_JoystickMapPath, FileName); + } +} + +/****************************************************************************/ +bool BeebWin::WriteJoyMap(const char *filename, JoyMap *joymap) +{ + FILE* outfile = OpenWriteFile(filename, "joystick map"); + + if (outfile == NULL) + return false; + + fprintf(outfile, JOYMAP_TOKEN "\n\n"); + + for (int i = 0; i < BEEB_VKEY_JOY_COUNT; ++i) + { + KeyMapping* mapping = (*joymap)[i]; + if (mapping[0].row != UNASSIGNED_ROW + || mapping[1].row != UNASSIGNED_ROW) + { + const char* inputName = SelectKeyDialog::KeyName(i + BEEB_VKEY_JOY_START); + + if (mapping[0].row == mapping[1].row + && mapping[0].col == mapping[1].col + && !mapping[0].shift && mapping[1].shift) + { + // Shifted and unshifted input map to the same key - write one name + const BBCKey* key = GetBBCKeyByRowAndCol(mapping[0].row, mapping[0].col); + fprintf(outfile, "%-13s %s\n", + inputName, + key->name); + } + else + { + // Separate mapping for shifted and unshifted + const BBCKey* key1 = GetBBCKeyByRowAndCol(mapping[0].row, mapping[0].col); + bool key1shift = mapping[0].shift && mapping[0].row != UNASSIGNED_ROW; + const BBCKey* key2 = GetBBCKeyByRowAndCol(mapping[1].row, mapping[1].col); + bool key2shift = mapping[1].shift && mapping[1].row != UNASSIGNED_ROW; + fprintf(outfile, "%-13s %s%-*s %s%s\n", + inputName, + key1shift ? "SH+" : "", + // Align for longest possible: "SHIFT-LOCK+SH" + key1shift ? 10 : 13, + key1->name, + key2shift ? "SH+" : "", + key2->name); + } + } + } + + fclose(outfile); + + return true; +} + +/****************************************************************************/ +static bool hasFileExt(const char* fileName, const char* fileExt) +{ + const size_t fileExtLen = strlen(fileExt); + const size_t fileNameLen = strlen(fileName); + + return fileNameLen >= fileExtLen && + _stricmp(fileName + fileNameLen - fileExtLen, fileExt) == 0; +} + +/****************************************************************************/ +void BeebWin::SaveJoystickMap() +{ + char DefaultPath[_MAX_PATH]; + // Add space for .jmap exstension + char FileName[_MAX_PATH + 5]; + const char* filter = "Joystick Map File (*.jmap)\0*.jmap\0"; + + // If Autoload is enabled, store joystick mapping near disks, + // otherwise start in user data path. + if (m_AutoloadJoystickMap) + { + m_Preferences.GetStringValue("DiscsPath", DefaultPath); + GetDataPath(GetUserDataPath(), DefaultPath); + } + else + { + strcpy(DefaultPath, GetUserDataPath()); + } + + FileDialog fileDialog(m_hWnd, FileName, sizeof(FileName), DefaultPath, filter); + + if (m_JoystickMapPath[0] == '\0' && !m_AutoloadJoystickMap) + { + fileDialog.SetInitial("DefaultUser.jmap"); + } + else + { + fileDialog.SetInitial(m_JoystickMapPath); + } + + if (fileDialog.Save()) + { + if (!hasFileExt(FileName, ".jmap")) + strcat(FileName, ".jmap"); + + if (WriteJoyMap(FileName, &JoystickMap)) + strcpy(m_JoystickMapPath, FileName); + } +} + +/****************************************************************************/ +static bool GetNthBoolValue(Preferences& preferences, const char* format, int idx, bool& value) +{ + char option_str[256]; + sprintf_s(option_str, format, idx); + return preferences.GetBoolValue(option_str, value); +} + +/****************************************************************************/ +static bool GetNthDWORDValue(Preferences& preferences, const char* format, int idx, DWORD& value) +{ + char option_str[256]; + sprintf_s(option_str, format, idx); + return preferences.GetDWORDValue(option_str, value); +} + +/****************************************************************************/ +static bool GetNthStringValue(Preferences& preferences, const char* format, int idx, std::string& value) +{ + char option_str[256]; + sprintf_s(option_str, format, idx); + return preferences.GetStringValue(option_str, value); +} + +/****************************************************************************/ +static void SetNthBoolValue(Preferences& preferences, const char* format, int idx, bool value) +{ + char option_str[256]; + sprintf_s(option_str, format, idx); + preferences.SetBoolValue(option_str, value); +} + +/****************************************************************************/ +static void SetNthDWORDValue(Preferences& preferences, const char* format, int idx, DWORD value) +{ + char option_str[256]; + sprintf_s(option_str, format, idx); + preferences.SetDWORDValue(option_str, value); +} + +/****************************************************************************/ +static void SetNthStringValue(Preferences& preferences, const char* format, int idx, const std::string& value) +{ + char option_str[256]; + sprintf_s(option_str, format, idx); + preferences.SetStringValue(option_str, value); +} + +/****************************************************************************/ +static void EraseNthValue(Preferences& preferences, const char* format, int idx) +{ + char option_str[256]; + sprintf_s(option_str, format, idx); + preferences.EraseValue(option_str); +} + +/****************************************************************************/ +void BeebWin::WriteJoystickOrder() +{ + std::vector& order = m_JoystickHandler->m_JoystickOrder; + for (UINT idx = 0; idx < MAX_JOYSTICK_ORDER; ++idx) + { + if (idx < order.size()) + { + SetNthStringValue(m_Preferences, CFG_OPTIONS_JOYSTICK_ORDER, idx + 1, + order[idx].to_string()); + } + else + { + EraseNthValue(m_Preferences, CFG_OPTIONS_JOYSTICK_ORDER, idx + 1); + } + } +} + +/****************************************************************************/ +void BeebWin::ReadJoystickPreferences() +{ + DWORD dword; + bool flag; + + /* Clear joystick configuration */ + for (int bbcIdx = 0; bbcIdx < NUM_BBC_JOYSTICKS; ++bbcIdx) + { + BBCJoystickConfig& config = m_JoystickConfig[bbcIdx]; + + m_MenuIdSticks[bbcIdx] = 0; + m_MenuIdAxes[bbcIdx] = 0; + + config.Enabled = false; + config.PCStick = 0; + config.PCAxes = 0; + config.AnalogMousestick = false; + config.DigitalMousestick = false; + } + + /* Backward compatibility - "Sticks" contains MenuId */ + if (m_Preferences.GetDWORDValue(CFG_OPTIONS_STICKS, dword)) + { + BBCJoystickConfig& config = m_JoystickConfig[0]; + m_MenuIdSticks[0] = dword; + + config.Enabled = true; + config.PCStick = MenuIdToStick(0, dword); + config.AnalogMousestick = (dword == JoystickMenuIds[0].AnalogMousestick); + config.DigitalMousestick = (dword == JoystickMenuIds[0].DigitalMousestick); + } + + /* New joystick options */ + for (int bbcIdx = 0; bbcIdx < NUM_BBC_JOYSTICKS; ++bbcIdx) + { + BBCJoystickConfig& config = m_JoystickConfig[bbcIdx]; + + if (GetNthBoolValue(m_Preferences, CFG_OPTIONS_STICK_ANALOG, bbcIdx + 1, flag) && flag) + { + config.AnalogMousestick = true; + config.Enabled = true; + m_MenuIdSticks[bbcIdx] = JoystickMenuIds[bbcIdx].AnalogMousestick; + } + else if (GetNthBoolValue(m_Preferences, CFG_OPTIONS_STICK_DIGITAL, bbcIdx + 1, flag) && flag) + { + config.DigitalMousestick = true; + config.Enabled = true; + m_MenuIdSticks[bbcIdx] = JoystickMenuIds[bbcIdx].DigitalMousestick; + + } + else if (GetNthDWORDValue(m_Preferences, CFG_OPTIONS_STICK_PCSTICK, bbcIdx + 1, dword) && dword != 0) + { + config.PCStick = dword; + config.Enabled = true; + m_MenuIdSticks[bbcIdx] = StickToMenuId(bbcIdx, dword); + } + + if (GetNthDWORDValue(m_Preferences, CFG_OPTIONS_STICK_PCAXES, bbcIdx + 1, dword)) + { + // Axes value 2 is obsolete, replace with 1 + if (dword == 2) + dword = 1; + config.PCAxes = dword; + } + + m_MenuIdAxes[bbcIdx] = AxesToMenuId(bbcIdx, config.PCAxes); + } + + if (!m_Preferences.GetBoolValue(CFG_OPTIONS_STICKS_TO_KEYS, m_JoystickToKeys)) + m_JoystickToKeys = false; + + if (!m_Preferences.GetBoolValue(CFG_OPTIONS_AUTOLOAD_JOYSICK_MAP, m_AutoloadJoystickMap)) + m_AutoloadJoystickMap = false; + + if (m_Preferences.GetDWORDValue(CFG_OPTIONS_STICKS_THRESHOLD, dword)) + m_JoystickToKeysThreshold = dword; + else + m_JoystickToKeysThreshold = DEFAULT_JOYSTICK_THRESHOLD; + + if (m_Preferences.GetDWORDValue(CFG_OPTIONS_JOYSTICK_SENSITIVITY, dword)) + m_JoystickSensitivity = static_cast(dword) / 0x10000; + else + m_JoystickSensitivity = 1.0; + + m_JoystickHandler->m_JoystickOrder.clear(); + for (int idx = 0; idx < MAX_JOYSTICK_ORDER; ++idx) + { + std::string value; + JoystickOrderEntry entry; + if (!GetNthStringValue(m_Preferences, CFG_OPTIONS_JOYSTICK_ORDER, idx + 1, value) + || !entry.from_string(value)) + break; + m_JoystickHandler->m_JoystickOrder.push_back(entry); + + } +} + +/****************************************************************************/ +void BeebWin::WriteJoystickPreferences() +{ + for (int bbcIdx = 0; bbcIdx < NUM_BBC_JOYSTICKS; ++bbcIdx) + { + BBCJoystickConfig& config = m_JoystickConfig[bbcIdx]; + SetNthBoolValue(m_Preferences, CFG_OPTIONS_STICK_ANALOG, bbcIdx + 1, config.AnalogMousestick); + SetNthBoolValue(m_Preferences, CFG_OPTIONS_STICK_DIGITAL, bbcIdx + 1, config.DigitalMousestick); + SetNthDWORDValue(m_Preferences, CFG_OPTIONS_STICK_PCSTICK, bbcIdx + 1, config.PCStick); + SetNthDWORDValue(m_Preferences, CFG_OPTIONS_STICK_PCAXES, bbcIdx + 1, config.PCAxes); + } + + m_Preferences.SetBoolValue(CFG_OPTIONS_STICKS_TO_KEYS, m_JoystickToKeys); + m_Preferences.SetBoolValue(CFG_OPTIONS_AUTOLOAD_JOYSICK_MAP, m_AutoloadJoystickMap); + m_Preferences.SetDWORDValue(CFG_OPTIONS_STICKS_THRESHOLD, m_JoystickToKeysThreshold); + m_Preferences.SetDWORDValue(CFG_OPTIONS_JOYSTICK_SENSITIVITY, static_cast(m_JoystickSensitivity * 0x10000)); + + /* Remove obsolete values */ + m_Preferences.EraseValue(CFG_OPTIONS_STICKS); + m_Preferences.EraseValue("Sticks2"); + m_Preferences.EraseValue("JoystickAxes1"); + m_Preferences.EraseValue("JoystickAxes2"); + m_Preferences.EraseValue("Stick1ToKeysDeadBand"); + m_Preferences.EraseValue("Stick2ToKeysDeadBand"); +} diff --git a/Src/JoystickHandler.h b/Src/JoystickHandler.h new file mode 100644 index 00000000..1ac17ea4 --- /dev/null +++ b/Src/JoystickHandler.h @@ -0,0 +1,135 @@ +/**************************************************************** +BeebEm - BBC Micro and Master 128 Emulator +Copyright (C) 1994 Nigel Magnay +Copyright (C) 1997 Mike Wyatt +Copyright (C) 1998 Robert Schmidt +Copyright (C) 2001 Richard Gellman +Copyright (C) 2004 Ken Lowe +Copyright (C) 2004 Rob O'Donnell +Copyright (C) 2005 Jon Welch +Copyright (C) 2021 Chris Needham, Tadeusz Kijkowski + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public +License along with this program; if not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. +****************************************************************/ + +#ifndef JOYSTICKHANDLER_H +#define JOYSTICKHANDLER_H + +#define DIRECTINPUT_VERSION 0x0800 + +#include + +#include "beebwin.h" + +#include +#include +#include + +/* Max number of entries on joystick order list */ +#define MAX_JOYSTICK_ORDER 16 + +struct JoystickId : std::pair +{ + using std::pair::pair; + + // Manufacturer ID aka Vendor ID + int& mId() { return first; } + // Product ID + int& pId() { return second; } +}; + +struct JoystickOrderEntry : JoystickId +{ + std::string Name{}; + int JoyIndex{ -1 }; + + JoystickOrderEntry() = default; + JoystickOrderEntry(JoystickId id, const std::string& name, int joyIndex) : + JoystickId(id), Name(name), JoyIndex(joyIndex) {} + JoystickOrderEntry(int mid, int pid, const std::string& name) : + JoystickId(mid, pid), Name(name) {} + + std::string to_string(); + bool from_string(const std::string&); +}; + +struct JoystickDev +{ + GUID m_GUIDInstance{}; + JoystickId m_Id{ 0, 0 }; + std::string m_Name; + LPDIRECTINPUTDEVICE8 m_Device{ nullptr }; + DWORD m_PresentAxes{ 0 }; + DWORD m_PresentButtons{ 0 }; + DIJOYSTATE2 m_JoyState; + + int m_Instance{ 0 }; + bool m_OnOrderList{ false }; + int m_JoyIndex{ -1 }; + bool m_Configured{ false }; + bool m_Present{ false }; + bool m_Captured{ false }; + unsigned int m_PrevAxes{ 0 }; + unsigned int m_PrevBtns{ 0 }; + bool m_JoystickToKeysActive{ false }; + + JoystickId Id() { return m_Id; } + std::string DisplayString(); + bool Update(); + DWORD GetButtons(); + DWORD GetAxesState(int threshold); + void GetAxesValue(int axesSet, int& x, int& y); + void EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi); + void CloseDevice(); + + JoystickDev() {} + JoystickDev(const JoystickDev&) = delete; + JoystickDev(JoystickDev&& r); + JoystickDev& operator=(const JoystickDev&) = delete; + JoystickDev& operator=(JoystickDev&& r); + ~JoystickDev(); +}; + +struct JoystickHandler +{ + bool m_DirectInputInitialized{ false }; + HRESULT m_DirectInputInitResult{ E_FAIL }; + LPDIRECTINPUT8 m_pDirectInput{ nullptr }; + DIJOYCONFIG m_PreferredJoyCfg{}; + bool m_HavePreferredJoyCfg{ false }; + + std::vector m_JoystickDevs; + std::vector m_JoystickOrder; + + HRESULT InitDirectInput(void); + void AddDeviceInstance(const DIDEVICEINSTANCE*); + HRESULT ScanJoysticks(void); + HRESULT OpenDevice(HWND mainWindow, JoystickDev* dev); + JoystickDev* GetDev(int pcIdx) + { + return (pcIdx >= 0 && static_cast(pcIdx) < m_JoystickDevs.size()) ? &m_JoystickDevs[pcIdx] : nullptr; + } + bool IsCaptured(int pcIdx) + { + JoystickDev* dev = GetDev(pcIdx); + return dev && dev->m_Captured; + } + + JoystickHandler() {} + ~JoystickHandler(); +}; + +#endif diff --git a/Src/JoystickOrderDialog.cpp b/Src/JoystickOrderDialog.cpp new file mode 100644 index 00000000..ddfd7a64 --- /dev/null +++ b/Src/JoystickOrderDialog.cpp @@ -0,0 +1,164 @@ +/**************************************************************** +BeebEm - BBC Micro and Master 128 Emulator +Copyright (C) 2021 Tadeusz Kijkowski + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public +License along with this program; if not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. +****************************************************************/ + +#include "JoystickOrderDialog.h" +#include "main.h" +#include "beebemrc.h" +#include "JoystickHandler.h" + +#include + +class JoystickOrderDialog +{ +public: + HWND m_hwndDlg{ nullptr }; + HWND m_hwndList{ nullptr }; + JoystickHandler* m_handler{ nullptr }; + bool m_showAll{ false }; + + void PopulateJoystickList() + { + SendMessage(m_hwndList, LB_RESETCONTENT, 0, 0); + int orderIndex = 0; + for (JoystickOrderEntry& entry : m_handler->m_JoystickOrder) + { + int joyIndex = entry.JoyIndex; + if (joyIndex != -1 || m_showAll) + { + int index = static_cast(SendMessage(m_hwndList, LB_ADDSTRING, 0, (LPARAM)entry.Name.c_str())); + SendMessage(m_hwndList, LB_SETITEMDATA, index, (LPARAM)orderIndex); + } + ++orderIndex; + } + } + + void MoveSelected(int delta) + { + int listCount = static_cast(SendMessage(m_hwndList, LB_GETCOUNT, 0, 0)); + int curSel = SendMessage(m_hwndList, LB_GETCURSEL, 0, 0); + + if (curSel == LB_ERR || delta == 0) + return; + + if (delta < -1) + delta = -1; + else if (delta > 1) + delta = 1; + + int other = curSel + delta; + if (other < 0 || other >= listCount) + return; + + int curSelIndex = static_cast(SendMessage(m_hwndList, LB_GETITEMDATA, curSel, 0)); + int otherIndex = static_cast(SendMessage(m_hwndList, LB_GETITEMDATA, other, 0)); + + JoystickOrderEntry& curEntry = m_handler->m_JoystickOrder[curSelIndex]; + JoystickOrderEntry& otherEntry = m_handler->m_JoystickOrder[otherIndex]; + std::swap(curEntry, otherEntry); + + if (curEntry.JoyIndex != -1 && otherEntry.JoyIndex != -1) + { + std::swap(m_handler->m_JoystickDevs[curEntry.JoyIndex], m_handler->m_JoystickDevs[otherEntry.JoyIndex]); + std::swap(curEntry.JoyIndex, otherEntry.JoyIndex); + } + + if (curSel < other) + { + SendMessage(m_hwndList, LB_DELETESTRING, other, 0); + SendMessage(m_hwndList, LB_DELETESTRING, curSel, 0); + + SendMessage(m_hwndList, LB_INSERTSTRING, curSel, (LPARAM)curEntry.Name.c_str()); + SendMessage(m_hwndList, LB_SETITEMDATA, curSel, (LPARAM)curSelIndex); + + SendMessage(m_hwndList, LB_INSERTSTRING, other, (LPARAM)otherEntry.Name.c_str()); + SendMessage(m_hwndList, LB_SETITEMDATA, other, (LPARAM)otherIndex); + } + else + { + SendMessage(m_hwndList, LB_DELETESTRING, curSel, 0); + SendMessage(m_hwndList, LB_DELETESTRING, other, 0); + + SendMessage(m_hwndList, LB_INSERTSTRING, other, (LPARAM)otherEntry.Name.c_str()); + SendMessage(m_hwndList, LB_SETITEMDATA, other, (LPARAM)otherIndex); + + SendMessage(m_hwndList, LB_INSERTSTRING, curSel, (LPARAM)curEntry.Name.c_str()); + SendMessage(m_hwndList, LB_SETITEMDATA, curSel, (LPARAM)curSelIndex); + } + + SendMessage(m_hwndList, LB_SETCURSEL, other, 0); + } + + INT_PTR DlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) + { + switch (message) + { + case WM_INITDIALOG: + m_hwndDlg = hwndDlg; + m_hwndList = GetDlgItem(hwndDlg, IDC_JOYSTICKLIST); + PopulateJoystickList(); + return TRUE; + + case WM_NOTIFY: + if (wParam == IDC_JOYSTICKSPIN) + { + LPNMUPDOWN lpnmud = (LPNMUPDOWN)lParam; + if (lpnmud->hdr.code == UDN_DELTAPOS) + { + MoveSelected(lpnmud->iDelta); + lpnmud->iDelta = 0; + return TRUE; + } + } + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + case IDCANCEL: + EndDialog(hwndDlg, wParam); + return TRUE; + case IDC_JOYSTICKSHOWALL: + m_showAll = (IsDlgButtonChecked(hwndDlg, IDC_JOYSTICKSHOWALL) == BST_CHECKED); + PopulateJoystickList(); + return TRUE; + } + } + return FALSE; + } + + JoystickOrderDialog(JoystickHandler* handler) : m_handler(handler) {}; +}; + +INT_PTR CALLBACK JoystickOrderDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message == WM_INITDIALOG) + SetWindowLongPtr(hwndDlg, DWLP_USER, lParam); + + JoystickOrderDialog* dlg = reinterpret_cast(GetWindowLongPtr(hwndDlg, DWLP_USER)); + if (!dlg) + return FALSE; + return dlg->DlgProc(hwndDlg, message, wParam, lParam); +} + +void ShowJoystickOrderDialog(HWND hwndParent, JoystickHandler* handler) +{ + JoystickOrderDialog dlg{ handler }; + DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_JOYSTICKORDER), hwndParent, JoystickOrderDlgProc, reinterpret_cast(&dlg)); +} diff --git a/Src/JoystickOrderDialog.h b/Src/JoystickOrderDialog.h new file mode 100644 index 00000000..c7afdadf --- /dev/null +++ b/Src/JoystickOrderDialog.h @@ -0,0 +1,30 @@ +/**************************************************************** +BeebEm - BBC Micro and Master 128 Emulator +Copyright (C) 2021 Tadeusz Kijkowski + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public +License along with this program; if not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. +****************************************************************/ + +#ifndef JOYSTICKORDERDIALOG_H +#define JOYSTICKORDERDIALOG_H + +#include + +struct JoystickHandler; + +void ShowJoystickOrderDialog(HWND hwndParent, JoystickHandler* handler); + +#endif diff --git a/Src/SelectKeyDialog.cpp b/Src/SelectKeyDialog.cpp index a6cd02a4..d4f4e71a 100644 --- a/Src/SelectKeyDialog.cpp +++ b/Src/SelectKeyDialog.cpp @@ -26,12 +26,11 @@ Boston, MA 02110-1301, USA. #include "main.h" #include "beebemrc.h" #include "SelectKeyDialog.h" +#include "Dialog.h" #include "Messages.h" /****************************************************************************/ -static bool IsDlgItemChecked(HWND hDlg, int nIDDlgItem); - SelectKeyDialog* selectKeyDialog; /****************************************************************************/ @@ -40,14 +39,17 @@ SelectKeyDialog::SelectKeyDialog( HINSTANCE hInstance, HWND hwndParent, const std::string& Title, - const std::string& SelectedKey) : + const std::string& SelectedKey, + bool doingShifted, + bool doingJoystick) : m_hInstance(hInstance), m_hwnd(nullptr), m_hwndParent(hwndParent), m_Title(Title), m_SelectedKey(SelectedKey), m_Key(-1), - m_Shift(false) + m_Shift(doingShifted), + m_Joystick(doingJoystick) { } @@ -101,16 +103,30 @@ INT_PTR SelectKeyDialog::DlgProc( SetWindowText(m_hwnd, m_Title.c_str()); SetDlgItemText(m_hwnd, IDC_ASSIGNED_KEYS, m_SelectedKey.c_str()); + + // If the selected keys is empty (as opposed to "Not assigned"), we are currently unassigning. + // Hide the "Assigned to:" label + if (m_SelectedKey.empty()) + SetDlgItemText(m_hwnd, IDC_ASSIGNED_KEYS_LBL, ""); + else if (m_Joystick) + SetDlgItemText(m_hwnd, IDC_ASSIGNED_KEYS_LBL, "Assigned to:"); + + // If doing shifted key, start with the Shift checkbox checked because that's most likely + // what the user wants + SetDlgItemChecked(m_hwnd, IDC_SHIFT, m_Shift); + return TRUE; case WM_ACTIVATE: if (LOWORD(wParam) == WA_INACTIVE) { hCurrentDialog = nullptr; + mainWin->SetJoystickTarget(nullptr); } else { hCurrentDialog = m_hwnd; + mainWin->SetJoystickTarget(m_hwnd); hCurrentAccelTable = nullptr; } break; @@ -167,11 +183,16 @@ bool SelectKeyDialog::HandleMessage(const MSG& msg) { if (msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN) { - m_Key = (int)msg.wParam; - m_Shift = IsDlgItemChecked(m_hwnd, IDC_SHIFT); + int key = (int)msg.wParam; - Close(IDOK); - return true; + if (!m_Joystick && key < 256 || + m_Joystick && key >= BEEB_VKEY_JOY_START && key < BEEB_VKEY_JOY_END) + { + m_Key = key; + m_Shift = IsDlgItemChecked(m_hwnd, IDC_SHIFT); + Close(IDOK); + return true; + } } return false; @@ -186,22 +207,75 @@ int SelectKeyDialog::Key() const /****************************************************************************/ -static bool IsDlgItemChecked(HWND hDlg, int nIDDlgItem) +bool SelectKeyDialog::Shift() const { - return SendDlgItemMessage(hDlg, nIDDlgItem, BM_GETCHECK, 0, 0) == BST_CHECKED; + return m_Shift; } /****************************************************************************/ LPCSTR SelectKeyDialog::KeyName(int Key) { + static const std::map axisNamesMap = { + { JOYSTICK_AXIS_LEFT, "Left" }, + { JOYSTICK_AXIS_RIGHT, "Right" }, + { JOYSTICK_AXIS_UP, "Up" }, + { JOYSTICK_AXIS_DOWN, "Down" }, + { JOYSTICK_AXIS_Z_N, "Z-" }, + { JOYSTICK_AXIS_Z_P, "Z+" }, + { JOYSTICK_AXIS_RX_N, "RLeft" }, + { JOYSTICK_AXIS_RX_P, "RRight" }, + { JOYSTICK_AXIS_RY_N, "RUp" }, + { JOYSTICK_AXIS_RY_P, "RDown" }, + { JOYSTICK_AXIS_RZ_N, "RZ-" }, + { JOYSTICK_AXIS_RZ_P, "RZ+" }, + { JOYSTICK_AXIS_HAT_LEFT, "HatLeft" }, + { JOYSTICK_AXIS_HAT_RIGHT, "HatRight" }, + { JOYSTICK_AXIS_HAT_UP, "HatUp" }, + { JOYSTICK_AXIS_HAT_DOWN, "HatDown" } + + }; + static CHAR Character[2]; // Used to return single characters. + if (Key >= BEEB_VKEY_JOY_START && Key < BEEB_VKEY_JOY_END) + { + static CHAR Name[16]; // Buffer for joystick button or axis name + + Key -= BEEB_VKEY_JOY_START; + + int joyIdx = Key / (JOYSTICK_MAX_AXES + JOYSTICK_MAX_BTNS); + Key -= joyIdx * (JOYSTICK_MAX_AXES + JOYSTICK_MAX_BTNS); + + sprintf(Name, "Joy%d", joyIdx + 1); + + if (Key < JOYSTICK_MAX_AXES) + { + auto iter = axisNamesMap.find(Key); + if (iter != axisNamesMap.end()) + { + strcat(Name, iter->second); + } + else + { + sprintf(Name + strlen(Name), "Axis%d", (Key / 2) + 1); + strcat(Name, (Key & 1) ? "+" : "-"); + } + } + else + { + sprintf(Name + strlen(Name), "Btn%d", Key - JOYSTICK_MAX_AXES + 1); + } + + return Name; + } + switch (Key) { case 8: return "Backspace"; case 9: return "Tab"; case 13: return "Enter"; + case 16: return "Shift"; case 17: return "Ctrl"; case 18: return "Alt"; case 19: return "Break"; @@ -267,3 +341,39 @@ LPCSTR SelectKeyDialog::KeyName(int Key) return Character; } } + +static std::string toupper(const std::string& src) +{ + std::string result = src; + + for (char& c : result) + { + c = static_cast(toupper(c)); + } + + return result; +} + +int SelectKeyDialog::JoyVKeyByName(const char* Name) +{ + using VKeyMapType = std::map; + + // Construct map on first use by lambda + static const VKeyMapType nameToVKeyMap = []() { + VKeyMapType keyMap{}; + + for (int vkey = BEEB_VKEY_JOY_START; vkey < BEEB_VKEY_JOY_END; ++vkey) + { + keyMap[toupper(KeyName(vkey))] = vkey; + } + + return keyMap; + }(); + + std::string uname = toupper(Name); + auto iter = nameToVKeyMap.find(uname); + if (iter != nameToVKeyMap.end()) + return iter->second; + + return -1; +} diff --git a/Src/SelectKeyDialog.h b/Src/SelectKeyDialog.h index a6fe7abb..faae4f5e 100644 --- a/Src/SelectKeyDialog.h +++ b/Src/SelectKeyDialog.h @@ -30,7 +30,9 @@ class SelectKeyDialog HINSTANCE hInstance, HWND hwndParent, const std::string& Title, - const std::string& SelectedKey + const std::string& SelectedKey, + bool doingShifted = false, + bool doingJoystick = false ); bool Open(); @@ -39,8 +41,10 @@ class SelectKeyDialog bool HandleMessage(const MSG& msg); int Key() const; + bool Shift() const; static LPCSTR KeyName(int Key); + static int JoyVKeyByName(const char* Name); private: static INT_PTR CALLBACK sDlgProc( @@ -65,6 +69,7 @@ class SelectKeyDialog std::string m_SelectedKey; int m_Key; bool m_Shift; + bool m_Joystick; }; extern SelectKeyDialog* selectKeyDialog; diff --git a/Src/UserPortBreakoutBox.cpp b/Src/UserPortBreakoutBox.cpp index 4b842ad8..78a8153e 100644 --- a/Src/UserPortBreakoutBox.cpp +++ b/Src/UserPortBreakoutBox.cpp @@ -26,6 +26,7 @@ Boston, MA 02110-1301, USA. #include "main.h" #include "Messages.h" #include "SelectKeyDialog.h" +#include "Dialog.h" #include "uservia.h" /****************************************************************************/ @@ -325,14 +326,14 @@ INT_PTR UserPortBreakoutDialog::DlgProc( bool UserPortBreakoutDialog::GetValue(int ctrlID) { - return SendDlgItemMessage(m_hwnd, ctrlID, BM_GETCHECK, 0, 0) == BST_CHECKED; + return IsDlgItemChecked(m_hwnd, ctrlID); } /****************************************************************************/ void UserPortBreakoutDialog::SetValue(int ctrlID, bool State) { - SendDlgItemMessage(m_hwnd, ctrlID, BM_SETCHECK, State ? 1 : 0, 0); + SetDlgItemChecked(m_hwnd, ctrlID, State); } /****************************************************************************/ diff --git a/Src/atodconv.cpp b/Src/atodconv.cpp index d8367576..2c6cb64c 100644 --- a/Src/atodconv.cpp +++ b/Src/atodconv.cpp @@ -32,11 +32,9 @@ Boston, MA 02110-1301, USA. #include "sysvia.h" #include "uefstate.h" -bool JoystickEnabled = false; - -/* X and Y positions for joystick 1 */ -int JoystickX; -int JoystickY; +/* X and Y positions for joystick 1 and 2 */ +int JoystickX[2]; +int JoystickY[2]; /* A to D state */ typedef struct AtoDStateT{ @@ -107,60 +105,47 @@ void AtoD_poll_real(void) switch (AtoDState.status & 3) { case 0: - value = JoystickX; + value = JoystickX[0]; break; case 1: - value = JoystickY; + value = JoystickY[0]; + break; + case 2: + value = JoystickX[1]; + break; + case 3: + value = JoystickY[1]; break; default: value = 0; - break; } AtoDState.status |= (value & 0xc000)>>10; - AtoDState.high = value>>8; + AtoDState.high = static_cast(value>>8); AtoDState.low = value & 0xf0; } /*--------------------------------------------------------------------------*/ -void AtoDInit(void) + +void AtoDInit() { AtoDState.datalatch = 0; AtoDState.high = 0; AtoDState.low = 0; ClearTrigger(AtoDTrigger); - /* Move joystick to middle */ - JoystickX = 32767; - JoystickY = 32767; + // Move both joysticks to middle + JoystickX[0] = 32767; + JoystickY[0] = 32767; + + JoystickX[1] = 32767; + JoystickY[1] = 32767; - /* Not busy, conversion complete (OS1.2 will then request another conversion) */ + // Not busy, conversion complete (OS1.2 will then request another conversion) AtoDState.status = 0x40; PulseSysViaCB1(); } -/*--------------------------------------------------------------------------*/ -void AtoDEnable(void) -{ - JoystickEnabled = true; - AtoDInit(); -} - -/*--------------------------------------------------------------------------*/ -void AtoDDisable(void) -{ - JoystickEnabled = false; - AtoDState.datalatch = 0; - AtoDState.status = 0x80; /* busy, conversion not complete */ - AtoDState.high = 0; - AtoDState.low = 0; - ClearTrigger(AtoDTrigger); - - /* Move joystick to middle (superpool looks at joystick even when not selected) */ - JoystickX = 32767; - JoystickY = 32767; -} - /*--------------------------------------------------------------------------*/ void SaveAtoDUEF(FILE *SUEF) { fput16(0x0474,SUEF); diff --git a/Src/atodconv.h b/Src/atodconv.h index 2ff34998..48cfe00a 100644 --- a/Src/atodconv.h +++ b/Src/atodconv.h @@ -24,15 +24,14 @@ Boston, MA 02110-1301, USA. #ifndef ATODCONV_HEADER #define ATODCONV_HEADER -extern bool JoystickEnabled; -extern int JoystickX; /* 16 bit number, 0 = right */ -extern int JoystickY; /* 16 bit number, 0 = down */ +#define NUM_BBC_JOYSTICKS 2 + +extern int JoystickX[NUM_BBC_JOYSTICKS]; /* 16 bit number, 0 = right */ +extern int JoystickY[NUM_BBC_JOYSTICKS]; /* 16 bit number, 0 = down */ void AtoDWrite(int Address, int Value); unsigned char AtoDRead(int Address); -void AtoDInit(void); -void AtoDEnable(void); -void AtoDDisable(void); +void AtoDInit(); void SaveAtoDUEF(FILE *SUEF); void LoadAtoDUEF(FILE *SUEF); diff --git a/Src/beebemrc.h b/Src/beebemrc.h index ca0c45b6..50059b47 100644 --- a/Src/beebemrc.h +++ b/Src/beebemrc.h @@ -97,6 +97,7 @@ Boston, MA 02110-1301, USA. #define IDK_BACKSLASH 81 #define IDK_UNDERSCORE 82 #define IDK_CARET 83 +#define IDK_UNASSIGN 84 #define IDR_MENU 101 #define IDI_BEEBEM 102 #define IDD_USERKYBRD 103 @@ -108,6 +109,7 @@ Boston, MA 02110-1301, USA. #define IDR_ACCELERATORS 115 #define IDD_ROMCONFIG 116 #define IDD_SELECT_KEY 117 +#define IDD_JOYSTICKORDER 118 #define IDC_DEBUGBREAK 1010 #define IDC_DEBUGINFO 1015 #define IDC_DEBUGCOMMAND 1016 @@ -186,6 +188,11 @@ Boston, MA 02110-1301, USA. #define IDC_SHIFT 1088 #define IDC_DEBUGTELETEXTBRK 1089 #define IDC_ASSIGNED_KEYS 1090 +#define IDC_ASSIGNED_KEYS_LBL 1091 +#define IDK_RESET_MAPPING 1092 +#define IDC_JOYSTICKLIST 1093 +#define IDC_JOYSTICKSPIN 1094 +#define IDC_JOYSTICKSHOWALL 1095 #define IDM_ABOUT 40001 #define IDM_DISC 40002 #define IDM_LOADDISC0 40002 @@ -309,7 +316,6 @@ Boston, MA 02110-1301, USA. #define ID_TAPE_NORMAL 40141 #define IDM_MUSIC5000 40142 #define ID_TELETEXTHALFMODE 40143 -#define ID_BASIC_HARDWARE_ONLY 40144 #define ID_PSAMPLES 40148 #define IDM_FIXEDSPEED100 40151 #define IDM_FIXEDSPEED5 40154 @@ -433,17 +439,42 @@ Boston, MA 02110-1301, USA. #define ID_VIEW_DD_2560X1440 40294 #define ID_VIEW_DD_3840X2160 40295 #define IDM_EMUPAUSED 40296 +#define IDM_JOYSTICK_TO_KEYS 40297 +#define IDM_DEFINEJOYMAP 40298 +#define IDM_LOADJOYMAP 40299 +#define IDM_SAVEJOYMAP 40300 +#define IDM_AUTOLOADJOYMAP 40301 +#define IDM_RESETJOYMAP 40302 +#define IDM_INIT_JOYSTICK 40303 +#define IDM_JOY1_PCJOY2 40305 +#define IDM_JOY1_PRIMARY 40306 +#define IDM_JOY1_SECONDARY1 40307 +#define IDM_JOY2_PCJOY1 40309 +#define IDM_JOY2_PCJOY2 40310 +#define IDM_JOY2_ANALOGUE_MOUSESTICK 40311 +#define IDM_JOY2_DIGITAL_MOUSESTICK 40312 +#define IDM_JOY2_PRIMARY 40313 +#define IDM_JOY2_SECONDARY1 40314 +#define IDM_JOYSTICKORDER 40317 #define IDM_CAPTUREMOUSE 40318 +#define IDM_JOY_SENSITIVITY_50 40319 +#define IDM_JOY_SENSITIVITY_100 40320 +#define IDM_JOY_SENSITIVITY_200 40321 +#define IDM_JOY_SENSITIVITY_300 40322 +#define IDM_JOY_KEY_THRESHOLD_12_5 40323 +#define IDM_JOY_KEY_THRESHOLD_25 40324 +#define IDM_JOY_KEY_THRESHOLD_50 40325 +#define IDM_JOY_KEY_THRESHOLD_75 40326 #define IDC_STATIC -1 // Next default values for new objects -// +// #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 -#define _APS_NEXT_RESOURCE_VALUE 118 -#define _APS_NEXT_COMMAND_VALUE 40319 -#define _APS_NEXT_CONTROL_VALUE 1090 +#define _APS_NEXT_RESOURCE_VALUE 119 +#define _APS_NEXT_COMMAND_VALUE 40327 +#define _APS_NEXT_CONTROL_VALUE 1096 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/Src/beebwin.cpp b/Src/beebwin.cpp index be81f3f2..0df240cd 100644 --- a/Src/beebwin.cpp +++ b/Src/beebwin.cpp @@ -79,6 +79,7 @@ Boston, MA 02110-1301, USA. #include "Master512CoPro.h" #include "FolderSelectDialog.h" #include "DebugTrace.h" +#include "JoystickOrderDialog.h" using namespace Gdiplus; @@ -131,6 +132,8 @@ static KeyMap logicalMapping; /* Currently selected translation table */ static KeyMap *transTable = &defaultMapping; +static JoyMap *joystickTable = &JoystickMap; + /****************************************************************************/ BeebWin::BeebWin() @@ -144,13 +147,15 @@ BeebWin::BeebWin() m_KeyMapFunc = false; m_ShiftPressed = 0; m_ShiftBooted = false; - for (int k = 0; k < 256; ++k) + + for (int k = 0; k < BEEB_VKEY_COUNT; ++k) { m_vkeyPressed[k][0][0] = -1; m_vkeyPressed[k][1][0] = -1; m_vkeyPressed[k][0][1] = -1; m_vkeyPressed[k][1][1] = -1; } + m_DisableKeysWindows = false; m_DisableKeysBreak = false; m_DisableKeysEscape = false; @@ -159,6 +164,7 @@ BeebWin::BeebWin() memset(&logicalMapping, 0, sizeof(KeyMap)); memset(&UserKeymap, 0, sizeof(KeyMap)); memset(m_UserKeyMapPath, 0, sizeof(m_UserKeyMapPath)); + memset(&JoystickMap, 0, sizeof(JoyMap)); m_hBitmap = m_hOldObj = m_hDCBitmap = NULL; m_screen = m_screen_blur = NULL; m_ScreenRefreshCount = 0; @@ -171,6 +177,7 @@ BeebWin::BeebWin() m_SpVoice = NULL; m_hTextView = NULL; m_frozen = false; + m_active = true; aviWriter = NULL; m_WriteProtectDisc[0] = !IsDiscWritable(0); m_WriteProtectDisc[1] = !IsDiscWritable(1); @@ -190,6 +197,7 @@ BeebWin::BeebWin() m_EmuPaused = false; m_StartPaused = false; m_WasPaused = false; + m_JoystickToKeysWasEnabled = false; m_KeyboardTimerElapsed = false; m_BootDiscTimerElapsed = false; memset(m_ClipboardBuffer, 0, sizeof(m_ClipboardBuffer)); @@ -202,7 +210,6 @@ BeebWin::BeebWin() m_DXSmoothMode7Only = false; m_DXResetPending = false; - m_JoystickCaptured = false; m_customip[0] = 0; m_customport = 0; m_isFullScreen = false; @@ -317,6 +324,8 @@ bool BeebWin::Initialise() ReadROMFile(RomFile, RomConfig); ApplyPrefs(); + CheckForJoystickMap(m_CommandLineFileName1); + if (m_DebugScript[0] != '\0') { DebugOpenDialog(hInst, m_hWnd); @@ -382,6 +391,8 @@ void BeebWin::ApplyPrefs() GetDataPath(m_UserDataPath, keymap); ReadKeyMap(keymap, &defaultMapping); + ResetJoyMapToDefaultUser(); + InitMenu(); ShowMenu(true); @@ -399,8 +410,8 @@ void BeebWin::ApplyPrefs() PrinterDisable(); /* Joysticks can only be initialised after the window is created (needs hwnd) */ - if (m_MenuIdSticks == IDM_JOYSTICK) - InitJoystick(); + ScanJoysticks(false); + InitJoystick(false); LoadFDC(NULL, true); RTCInit(); @@ -873,6 +884,16 @@ void BeebWin::EnableMenuItem(UINT id, bool enabled) ::EnableMenuItem(m_hMenu, id, enabled ? MF_ENABLED : MF_GRAYED); } +void BeebWin::SetMenuItemText(UINT id, const std::string& text) +{ + MENUITEMINFO mii{ 0 }; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STRING; + mii.fType = MFT_STRING; + mii.dwTypeData = const_cast(text.c_str()); + SetMenuItemInfo(m_hMenu, id, FALSE, &mii); +} + void BeebWin::InitMenu(void) { char menu_string[256]; @@ -1083,11 +1104,6 @@ void BeebWin::InitMenu(void) CheckMenuItem(ID_RTCY2KADJUST, RTCY2KAdjust); // Options - CheckMenuItem(IDM_JOYSTICK, false); - CheckMenuItem(IDM_ANALOGUE_MOUSESTICK, false); - CheckMenuItem(IDM_DIGITAL_MOUSESTICK, false); - if (m_MenuIdSticks != 0) - CheckMenuItem(m_MenuIdSticks, true); CheckMenuItem(IDM_FREEZEINACTIVE, m_FreezeWhenInactive); CheckMenuItem(IDM_HIDECURSOR, m_HideCursor); CheckMenuItem(IDM_DEFAULTKYBDMAPPING, false); @@ -1100,6 +1116,7 @@ void BeebWin::InitMenu(void) CheckMenuItem(IDM_AUTOSAVE_PREFS_CMOS, m_AutoSavePrefsCMOS); CheckMenuItem(IDM_AUTOSAVE_PREFS_FOLDERS, m_AutoSavePrefsFolders); CheckMenuItem(IDM_AUTOSAVE_PREFS_ALL, m_AutoSavePrefsAll); + InitJoystickMenu(); } void BeebWin::UpdateDisplayRendererMenu() { @@ -1223,96 +1240,6 @@ void BeebWin::SetRomMenu(void) } } -/****************************************************************************/ -void BeebWin::InitJoystick(void) -{ - MMRESULT mmresult = JOYERR_NOERROR; - - if (!m_JoystickCaptured) - { - /* Get joystick updates 10 times a second */ - mmresult = joySetCapture(m_hWnd, JOYSTICKID1, 100, FALSE); - if (mmresult == JOYERR_NOERROR) - mmresult = joyGetDevCaps(JOYSTICKID1, &m_JoystickCaps, sizeof(JOYCAPS)); - if (mmresult == JOYERR_NOERROR) - m_JoystickCaptured = true; - } - - if (mmresult == JOYERR_NOERROR) - { - AtoDEnable(); - } - else if (mmresult == JOYERR_UNPLUGGED) - { - MessageBox(m_hWnd, "Joystick is not plugged in", - WindowTitle, MB_OK|MB_ICONERROR); - } - else - { - MessageBox(m_hWnd, "Failed to initialise the joystick", - WindowTitle, MB_OK|MB_ICONERROR); - } -} - -/****************************************************************************/ -void BeebWin::ScaleJoystick(unsigned int x, unsigned int y) -{ - if (m_MenuIdSticks == IDM_JOYSTICK) - { - /* Scale and reverse the readings */ - JoystickX = (int)((double)(m_JoystickCaps.wXmax - x) * 65535.0 / - (double)(m_JoystickCaps.wXmax - m_JoystickCaps.wXmin)); - JoystickY = (int)((double)(m_JoystickCaps.wYmax - y) * 65535.0 / - (double)(m_JoystickCaps.wYmax - m_JoystickCaps.wYmin)); - } -} - -/****************************************************************************/ -void BeebWin::ResetJoystick(void) -{ - // joySetCapture() fails after a joyReleaseCapture() call (not sure why) - // so leave joystick captured. - // joyReleaseCapture(JOYSTICKID1); - AtoDDisable(); -} - -/****************************************************************************/ -void BeebWin::SetMousestickButton(int index, bool button) -{ - if (m_MenuIdSticks == IDM_ANALOGUE_MOUSESTICK || - m_MenuIdSticks == IDM_DIGITAL_MOUSESTICK) - { - JoystickButton[index] = button; - } -} - -/****************************************************************************/ -void BeebWin::ScaleMousestick(unsigned int x, unsigned int y) -{ - static int lastx = 32768; - static int lasty = 32768; - - if (m_MenuIdSticks == IDM_ANALOGUE_MOUSESTICK) - { - JoystickX = (m_XWinSize - x) * 65535 / m_XWinSize; - JoystickY = (m_YWinSize - y) * 65535 / m_YWinSize; - } - else if (m_MenuIdSticks == IDM_DIGITAL_MOUSESTICK) - { - int dx = x - lastx; - int dy = y - lasty; - - if (dx > 4) JoystickX = 0; - if (dx < -4) JoystickX = 65535; - - if (dy > 4) JoystickY = 0; - if (dy < -4) JoystickY = 65535; - - lastx = x; - lasty = y; - } -} - /****************************************************************************/ void BeebWin::SetAMXPosition(unsigned int x, unsigned int y) { @@ -1660,15 +1587,6 @@ LRESULT CALLBACK WndProc(HWND hWnd, // window handle } break; - case MM_JOY1MOVE: - mainWin->ScaleJoystick(LOWORD(lParam), HIWORD(lParam)); - break; - - case MM_JOY1BUTTONDOWN: - case MM_JOY1BUTTONUP: - JoystickButton[0] = (uParam & (JOY_BUTTON1 | JOY_BUTTON2)) != 0; - break; - case WM_INPUT: if (mainWin->m_MouseCaptured) { @@ -1803,6 +1721,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, // window handle mainWin->KillBootDiscTimer(); mainWin->DoShiftBreak(); } + else if (uParam == 3) + { + mainWin->UpdateJoysticks(); + } break; case WM_USER_KEYBOARD_DIALOG_CLOSED: @@ -1824,11 +1746,20 @@ LRESULT CALLBACK WndProc(HWND hWnd, // window handle /****************************************************************************/ int BeebWin::TranslateKey(int vkey, bool keyUp, int &row, int &col) { - if (vkey < 0 || vkey > 255) + KeyPair* table = *transTable; + int tableIdx = vkey; + + if (vkey < 0 || vkey >= BEEB_VKEY_COUNT) return -9; + if (vkey >= BEEB_VKEY_JOY_START) + { + table = *joystickTable; + tableIdx -= BEEB_VKEY_JOY_START; + } + // Key track of shift state - if ((*transTable)[vkey][0].row == 0 && (*transTable)[vkey][0].col == 0) + if (table[tableIdx][0].row == 0 && table[tableIdx][0].col == 0) { m_ShiftPressed = !keyUp; } @@ -1854,9 +1785,9 @@ int BeebWin::TranslateKey(int vkey, bool keyUp, int &row, int &col) } else // New key press - convert to beeb row + col { - row = (*transTable)[vkey][static_cast(m_ShiftPressed)].row; - col = (*transTable)[vkey][static_cast(m_ShiftPressed)].col; - bool needShift = (*transTable)[vkey][static_cast(m_ShiftPressed)].shift; + row = table[tableIdx][static_cast(m_ShiftPressed)].row; + col = table[tableIdx][static_cast(m_ShiftPressed)].col; + bool needShift = table[tableIdx][static_cast(m_ShiftPressed)].shift; if (m_KeyMapAS) { @@ -1878,8 +1809,8 @@ int BeebWin::TranslateKey(int vkey, bool keyUp, int &row, int &col) // Map F1-F10 to f0-f9 if (vkey >= 113 && vkey <= 121) { - row = (*transTable)[vkey - 1][0].row; - col = (*transTable)[vkey - 1][0].col; + row = table[tableIdx - 1][0].row; + col = table[tableIdx - 1][0].col; } else if (vkey == 112) { @@ -2680,7 +2611,6 @@ void BeebWin::UpdateLEDMenu() { } void BeebWin::UpdateOptiMenu() { - CheckMenuItem(ID_BASIC_HARDWARE_ONLY, BasicHardwareOnly); CheckMenuItem(ID_TELETEXTHALFMODE, TeletextHalfMode); CheckMenuItem(ID_PSAMPLES, PartSamples); } @@ -2894,7 +2824,7 @@ void BeebWin::HandleCommand(int MenuId) { // Also switch on analogue mousestick (touch screen uses // mousestick position) - if (m_MenuIdSticks != IDM_ANALOGUE_MOUSESTICK) + if (!GetAnalogMousestick(0)) HandleCommand(IDM_ANALOGUE_MOUSESTICK); if (EthernetPortEnabled) @@ -3346,45 +3276,82 @@ void BeebWin::HandleCommand(int MenuId) case IDM_JOYSTICK: case IDM_ANALOGUE_MOUSESTICK: case IDM_DIGITAL_MOUSESTICK: - /* Disable current selection */ - if (m_MenuIdSticks != 0) - { - CheckMenuItem(m_MenuIdSticks, false); + case IDM_JOY1_PCJOY2: + ProcessJoystickMenuCommand(0, MenuId); + break; - if (m_MenuIdSticks == IDM_JOYSTICK) - { - ResetJoystick(); - } - else /* mousestick */ - { - AtoDDisable(); - } - } + case IDM_JOY2_PCJOY1: + case IDM_JOY2_ANALOGUE_MOUSESTICK: + case IDM_JOY2_DIGITAL_MOUSESTICK: + case IDM_JOY2_PCJOY2: + ProcessJoystickMenuCommand(1, MenuId); + break; - if (MenuId == m_MenuIdSticks) - { - /* Joysticks switched off completely */ - m_MenuIdSticks = 0; - } - else - { - /* Initialise new selection */ - m_MenuIdSticks = MenuId; + case IDM_JOY1_PRIMARY: + case IDM_JOY1_SECONDARY1: + ProcessJoystickAxesMenuCommand(0, MenuId); + break; - if (m_MenuIdSticks == IDM_JOYSTICK) - { - InitJoystick(); - } - else /* mousestick */ - { - AtoDEnable(); - } + case IDM_JOY2_PRIMARY: + case IDM_JOY2_SECONDARY1: + ProcessJoystickAxesMenuCommand(1, MenuId); + break; - if (JoystickEnabled) - CheckMenuItem(m_MenuIdSticks, true); - else - m_MenuIdSticks = 0; + case IDM_INIT_JOYSTICK: + ScanJoysticks(true); + InitJoystick(true); + break; + + case IDM_JOYSTICK_TO_KEYS: + ProcessJoystickToKeysCommand(); + break; + + case IDM_AUTOLOADJOYMAP: + ProcessAutoloadJoystickMapCommand(); + break; + + case IDM_JOYSTICKORDER: + ShowJoystickOrderDialog(m_hWnd, m_JoystickHandler.get()); + UpdateJoystickMenu(); + InitJoystick(); + break; + + case IDM_JOY_SENSITIVITY_50: + case IDM_JOY_SENSITIVITY_100: + case IDM_JOY_SENSITIVITY_200: + case IDM_JOY_SENSITIVITY_300: + if (MenuId == IDM_JOY_SENSITIVITY_50) { + m_JoystickSensitivity = 0.5; + } + else if (MenuId == IDM_JOY_SENSITIVITY_200) { + m_JoystickSensitivity = 2.0; + } + else if (MenuId == IDM_JOY_SENSITIVITY_300) { + m_JoystickSensitivity = 3.0; + } + else { + m_JoystickSensitivity = 1.0; } + UpdateJoystickMenu(); + break; + + case IDM_JOY_KEY_THRESHOLD_12_5: + case IDM_JOY_KEY_THRESHOLD_25: + case IDM_JOY_KEY_THRESHOLD_50: + case IDM_JOY_KEY_THRESHOLD_75: + if (MenuId == IDM_JOY_KEY_THRESHOLD_25) { + m_JoystickToKeysThreshold = 8192; + } + else if (MenuId == IDM_JOY_KEY_THRESHOLD_50) { + m_JoystickToKeysThreshold = 16384; + } + else if (MenuId == IDM_JOY_KEY_THRESHOLD_75) { + m_JoystickToKeysThreshold = 24576; + } + else { + m_JoystickToKeysThreshold = 4096; + } + UpdateJoystickMenu(); break; case IDM_FREEZEINACTIVE: @@ -3397,8 +3364,8 @@ void BeebWin::HandleCommand(int MenuId) CheckMenuItem(IDM_HIDECURSOR, m_HideCursor); break; - case IDM_DEFINEKEYMAP: - OpenUserKeyboardDialog(); + case IDM_DEFINEJOYMAP: + OpenJoystickMapDialog(); StayMuted = true; break; @@ -3410,6 +3377,23 @@ void BeebWin::HandleCommand(int MenuId) SaveUserKeyMap(); break; + case IDM_LOADJOYMAP: + LoadJoystickMap(); + break; + + case IDM_SAVEJOYMAP: + SaveJoystickMap(); + break; + + case IDM_RESETJOYMAP: + ResetJoystickMap(); + break; + + case IDM_DEFINEKEYMAP: + OpenUserKeyboardDialog(); + StayMuted = true; + break; + case IDM_USERKYBDMAPPING: case IDM_DEFAULTKYBDMAPPING: case IDM_LOGICALKYBDMAPPING: @@ -3751,11 +3735,6 @@ void BeebWin::HandleCommand(int MenuId) UpdateOptiMenu(); break; - case ID_BASIC_HARDWARE_ONLY: - BasicHardwareOnly = !BasicHardwareOnly; - UpdateOptiMenu(); - break; - case ID_PSAMPLES: PartSamples = !PartSamples; UpdateOptiMenu(); @@ -4062,6 +4041,7 @@ void BeebWin::SetSoundMenu() void BeebWin::Activate(bool active) { + m_active = active; if (active) m_frozen = false; else if (m_FreezeWhenInactive) @@ -4195,12 +4175,37 @@ void BeebWin::OpenUserKeyboardDialog() m_WasPaused = mainWin->IsPaused(); + m_JoystickToKeysWasEnabled = GetJoystickToKeys(); + + if (!m_WasPaused) + { + TogglePause(); + } + + UserKeyboardDialog(m_hWnd, false); +} + +void BeebWin::OpenJoystickMapDialog() +{ + // Pause the emulator if not already paused. + + m_WasPaused = mainWin->IsPaused(); + if (!m_WasPaused) { TogglePause(); } - UserKeyboardDialog(m_hWnd); + // Enable mapping to capture keys in dialog + m_JoystickToKeysWasEnabled = GetJoystickToKeys(); + + if (!m_JoystickToKeysWasEnabled) + { + SetJoystickToKeys(true); + InitJoystick(false); + } + + UserKeyboardDialog(m_hWnd, true); } void BeebWin::UserKeyboardDialogClosed() @@ -4212,6 +4217,16 @@ void BeebWin::UserKeyboardDialogClosed() { mainWin->TogglePause(); } + + // Restore joystick state + if (GetJoystickToKeys() && !m_JoystickToKeysWasEnabled) + { + SetJoystickToKeys(m_JoystickToKeysWasEnabled); + InitJoystick(false); + } + + // Unmute + SetSound(SoundState::Unmuted); } /*****************************************************************************/ @@ -4386,8 +4401,7 @@ void BeebWin::CheckForLocalPrefs(const char *path, bool bLoadPrefs) HandleCommand(m_DisplayRenderer); InitMenu(); SetWindowText(m_hWnd, WindowTitle); - if (m_MenuIdSticks == IDM_JOYSTICK) - InitJoystick(); + InitJoystick(false); } } @@ -4405,6 +4419,11 @@ void BeebWin::CheckForLocalPrefs(const char *path, bool bLoadPrefs) BeebReadRoms(); } } + + if (bLoadPrefs) + { + CheckForJoystickMap(path); + } } /*****************************************************************************/ diff --git a/Src/beebwin.h b/Src/beebwin.h index e9f1c946..8b05bf09 100644 --- a/Src/beebwin.h +++ b/Src/beebwin.h @@ -32,6 +32,7 @@ Boston, MA 02110-1301, USA. #include #include #include +#include #include #include @@ -43,6 +44,7 @@ Boston, MA 02110-1301, USA. #include "port.h" #include "preferences.h" #include "video.h" +#include "atodconv.h" /* Used in message boxes */ #define GETHWND (mainWin->GethWnd()) @@ -51,13 +53,50 @@ Boston, MA 02110-1301, USA. #define CFG_KEYBOARD_LAYOUT "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layout" #define CFG_SCANCODE_MAP "Scancode Map" -typedef struct KeyMapping { +// Max number of joysticks to capture +#define NUM_PC_JOYSTICKS 4 + +// Default Joystick to keyboard threshold +#define DEFAULT_JOYSTICK_THRESHOLD 4096 + +#define JOYSTICK_MAX_AXES 16 +#define JOYSTICK_MAX_BTNS 16 + +#define JOYSTICK_AXIS_LEFT 0 +#define JOYSTICK_AXIS_RIGHT 1 +#define JOYSTICK_AXIS_UP 2 +#define JOYSTICK_AXIS_DOWN 3 +#define JOYSTICK_AXIS_Z_N 4 +#define JOYSTICK_AXIS_Z_P 5 +#define JOYSTICK_AXIS_RX_N 6 +#define JOYSTICK_AXIS_RX_P 7 +#define JOYSTICK_AXIS_RY_N 8 +#define JOYSTICK_AXIS_RY_P 9 +#define JOYSTICK_AXIS_RZ_N 10 +#define JOYSTICK_AXIS_RZ_P 11 +#define JOYSTICK_AXIS_HAT_LEFT 12 +#define JOYSTICK_AXIS_HAT_RIGHT 13 +#define JOYSTICK_AXIS_HAT_UP 14 +#define JOYSTICK_AXIS_HAT_DOWN 15 + +#define BEEB_VKEY_JOY_START 256 +#define BEEB_VKEY_JOY_COUNT (NUM_PC_JOYSTICKS * \ + (JOYSTICK_MAX_AXES + JOYSTICK_MAX_BTNS)) // 4*32 = 128 +#define BEEB_VKEY_JOY_END (BEEB_VKEY_JOY_START + BEEB_VKEY_JOY_COUNT) // 256+128 = 384 + +#define BEEB_VKEY_COUNT BEEB_VKEY_JOY_END + +struct KeyMapping { int row; // Beeb row int col; // Beeb col bool shift; // Beeb shift state -} KeyMapping; +}; + +typedef KeyMapping KeyPair[2]; +typedef KeyPair KeyMap[256]; // Indices are: [Virt key][shift state] +typedef KeyPair JoyMap[BEEB_VKEY_JOY_COUNT]; -typedef KeyMapping KeyMap[256][2]; // Indices are: [Virt key][shift state] +#define UNASSIGNED_ROW -9 extern const char *WindowTitle; @@ -127,6 +166,24 @@ enum class MessageType { Info }; +struct BBCJoystickConfig +{ + bool Enabled{ false }; + int PCStick{ 0 }; + int PCAxes{ 0 }; + bool AnalogMousestick{ false }; + bool DigitalMousestick{ false }; +}; + +struct JoystickHandler; + +struct JoystickHandlerPtr : std::unique_ptr +{ + // Delegate constructor and destructor + JoystickHandlerPtr(); + ~JoystickHandlerPtr(); +}; + class BeebWin { public: @@ -213,9 +270,18 @@ class BeebWin { bool IsWindowMinimized() const; void DisplayClientAreaText(HDC hdc); void DisplayFDCBoardInfo(HDC hDC, int x, int y); - void ScaleJoystick(unsigned int x, unsigned int y); + void SetJoystickTarget(HWND target); + void SetJoystickButton(int index, bool value); + void ScaleJoystick(int index, unsigned int x, unsigned int y, + unsigned int minX, unsigned int minY, + unsigned int maxX, unsigned int maxY); void SetMousestickButton(int index, bool button); void ScaleMousestick(unsigned int x, unsigned int y); + void UpdateJoysticks(void); + void TranslateJoystick(int joyId); + void TranslateOrSendKey(int vkey, bool keyUp); + void TranslateAxes(int joyId, unsigned int axesState); + void TranslateJoystickButtons(int joyId, unsigned int buttons); void HandleCommand(int MenuId); void SetAMXPosition(unsigned int x, unsigned int y); void ChangeAMXPosition(int deltaX, int deltaY); @@ -229,6 +295,7 @@ class BeebWin { void TogglePause(); bool IsPaused(); void OpenUserKeyboardDialog(); + void OpenJoystickMapDialog(); void UserKeyboardDialogClosed(); void ShowMenu(bool on); void HideMenu(bool hide); @@ -278,6 +345,7 @@ class BeebWin { HMENU m_hMenu; bool m_frozen; + bool m_active; char* m_screen; char* m_screen_blur; double m_RealTimeTarget; @@ -311,9 +379,17 @@ class BeebWin { int m_MenuIdVolume; int m_MenuIdTiming; int m_FPSTarget; - bool m_JoystickCaptured; - JOYCAPS m_JoystickCaps; - int m_MenuIdSticks; + JoystickHandlerPtr m_JoystickHandler; + bool m_JoystickTimerRunning{ false }; + int m_MenuIdSticks[NUM_BBC_JOYSTICKS]{}; + int m_MenuIdAxes[NUM_BBC_JOYSTICKS]{}; + BBCJoystickConfig m_JoystickConfig[NUM_BBC_JOYSTICKS]; + int m_JoystickToKeysThreshold{ DEFAULT_JOYSTICK_THRESHOLD }; + double m_JoystickSensitivity{ 1.0 }; + bool m_JoystickToKeys{ false }; + bool m_AutoloadJoystickMap{ false }; + HWND m_JoystickTarget{ nullptr }; + char m_JoystickMapPath[_MAX_PATH]{}; bool m_HideCursor; bool m_CaptureMouse; bool m_MouseCaptured; @@ -324,7 +400,8 @@ class BeebWin { bool m_KeyMapFunc; char m_UserKeyMapPath[_MAX_PATH]; bool m_ShiftPressed; - int m_vkeyPressed[256][2][2]; + /* indexes are [vkey][0: row/1: col][shift pressed] */ + int m_vkeyPressed[BEEB_VKEY_COUNT][2][2]; char m_AppPath[_MAX_PATH]; char m_UserDataPath[_MAX_PATH]; bool m_CustomData; @@ -406,6 +483,7 @@ class BeebWin { bool m_EmuPaused; bool m_StartPaused; bool m_WasPaused; + bool m_JoystickToKeysWasEnabled; bool m_AutoBootDisc; bool m_KeyboardTimerElapsed; bool m_BootDiscTimerElapsed; @@ -487,8 +565,25 @@ class BeebWin { void UpdateSoundStreamerMenu(); + void InitJoystickMenu(void); + void UpdateJoystickMenu(void); + void ProcessJoystickMenuCommand(int bbcIdx, UINT menuId); + void ProcessJoystickAxesMenuCommand(int bbcIdx, UINT menuId); + void ProcessJoystickToKeysCommand(); + void ProcessAutoloadJoystickMapCommand(); + + void UpdateJoystickConfig(int bbcIdx); + bool GetAnalogMousestick(int bbcIdx); + bool GetDigitalMousestick(int bbcIdx); + int GetPCJoystick(int bbcIdx); + bool IsPCJoystickOn(int pcIdx); + + bool GetJoystickToKeys() { return m_JoystickToKeys; } + void SetJoystickToKeys(bool value) { m_JoystickToKeys = value; } + void CheckMenuItem(UINT id, bool checked); void EnableMenuItem(UINT id, bool enabled); + void SetMenuItemText(UINT id, const std::string& text); // DirectX - calls DDraw or DX9 fn void InitDX(void); @@ -517,7 +612,9 @@ class BeebWin { int ReadDisc(int Drive, bool bCheckForPrefs); void Load1770DiscImage(const char *FileName, int Drive, DiscType Type); void LoadTape(void); - void InitJoystick(void); + bool ScanJoysticks(bool verbose = false); + void InitJoystick(bool verbose = false); + bool CaptureJoystick(int Index, bool verbose); void ResetJoystick(void); void RestoreState(void); void SaveState(void); @@ -555,9 +652,21 @@ class BeebWin { bool RebootSystem(); void LoadUserKeyMap(void); void SaveUserKeyMap(void); + FILE* OpenReadFile(const char *filename, const char *typeDescr, + const char *token); + FILE* OpenWriteFile(const char *filename, const char *typeDescr); bool ReadKeyMap(const char *filename, KeyMap *keymap); bool WriteKeyMap(const char *filename, KeyMap *keymap); + void CheckForJoystickMap(const char* path); + void ResetJoystickMap(void); + void ResetJoyMap(JoyMap* joymap); + void ResetJoyMapToDefaultUser(void); + bool ReadJoyMap(const char* filename, JoyMap* joymap); + bool WriteJoyMap(const char* filename, JoyMap* joymap); + void LoadJoystickMap(void); + void SaveJoystickMap(void); + void Report(MessageType type, const char *format, ...); bool RegCreateKey(HKEY hKeyRoot, LPCSTR lpSubKey); @@ -570,6 +679,9 @@ class BeebWin { // Preferences void LoadPreferences(); void SavePreferences(bool saveAll); + void ReadJoystickPreferences(); + void WriteJoystickPreferences(); + void WriteJoystickOrder(); private: enum class PaletteType : char { diff --git a/Src/beebwinio.cpp b/Src/beebwinio.cpp index 0c9a2c3d..46154764 100644 --- a/Src/beebwinio.cpp +++ b/Src/beebwinio.cpp @@ -32,6 +32,7 @@ Boston, MA 02110-1301, USA. #include "beebmem.h" #include "beebemrc.h" #include "filedialog.h" +#include "SelectKeyDialog.h" #include "uservia.h" #include "beebsound.h" #include "disc8271.h" @@ -301,6 +302,8 @@ void BeebWin::LoadTape(void) else if (hasFileExt(FileName, ".csw")) { LoadCSWTape(FileName); } + + CheckForJoystickMap(FileName); } } @@ -1161,7 +1164,7 @@ void BeebWin::LoadUserKeyMap() /****************************************************************************/ void BeebWin::SaveUserKeyMap() { - char FileName[_MAX_PATH]; + char FileName[_MAX_PATH + 5]; const char* filter = "Key Map File (*.kmap)\0*.kmap\0"; FileDialog fileDialog(m_hWnd, FileName, sizeof(FileName), m_UserDataPath, filter); @@ -1176,111 +1179,127 @@ void BeebWin::SaveUserKeyMap() } } +FILE* BeebWin::OpenReadFile(const char *filename, const char *typeDescr, + const char *token) +{ + FILE *infile = fopen(filename, "r"); + + if (infile == NULL) + { + char errstr[500]; + sprintf(errstr, "Failed to read %s file:\n %s", typeDescr, filename); + MessageBox(GETHWND, errstr, WindowTitle, MB_OK | MB_ICONERROR); + } + else + { + char buf[256]; + + if (fgets(buf, 255, infile) == NULL || + strcmp(buf, token) != 0) + { + char errstr[500]; + sprintf(errstr, "Invalid %s file:\n %s\n", typeDescr, filename); + MessageBox(GETHWND, errstr, WindowTitle, MB_OK | MB_ICONERROR); + fclose(infile); + infile = NULL; + } + } + + return infile; +} + /****************************************************************************/ bool BeebWin::ReadKeyMap(const char *filename, KeyMap *keymap) { bool success = true; + FILE *infile = OpenReadFile(filename, "key map", KEYMAP_TOKEN "\n"); char buf[256]; - FILE *infile = fopen(filename,"r"); - if (infile == NULL) + return false; + + fgets(buf, 255, infile); + + for (int i = 0; i < 256; ++i) { - char errstr[500]; - sprintf(errstr, "Failed to read key map file:\n %s", filename); - MessageBox(GETHWND, errstr, WindowTitle, MB_OK|MB_ICONERROR); - success = false; - } - else - { - if (fgets(buf, 255, infile) == NULL || - strcmp(buf, KEYMAP_TOKEN "\n") != 0) + if (fgets(buf, 255, infile) == NULL) { char errstr[500]; - sprintf(errstr, "Invalid key map file:\n %s\n", filename); - MessageBox(GETHWND, errstr, WindowTitle, MB_OK|MB_ICONERROR); + sprintf(errstr, "Data missing from key map file:\n %s\n", filename); + MessageBox(GETHWND, errstr, WindowTitle, MB_OK | MB_ICONERROR); success = false; + break; } - else - { - fgets(buf, 255, infile); - for (int i = 0; i < 256; ++i) - { - if (fgets(buf, 255, infile) == NULL) - { - char errstr[500]; - sprintf(errstr, "Data missing from key map file:\n %s\n", filename); - MessageBox(GETHWND, errstr, WindowTitle, MB_OK|MB_ICONERROR); - success = false; - break; - } - - int shift0 = 0, shift1 = 0; + int shift0 = 0, shift1 = 0; - sscanf(buf, "%d %d %d %d %d %d", - &(*keymap)[i][0].row, - &(*keymap)[i][0].col, - &shift0, - &(*keymap)[i][1].row, - &(*keymap)[i][1].col, - &shift1); - - (*keymap)[i][0].shift = shift0 != 0; - (*keymap)[i][1].shift = shift1 != 0; - } - } + sscanf(buf, "%d %d %d %d %d %d", + &(*keymap)[i][0].row, + &(*keymap)[i][0].col, + &shift0, + &(*keymap)[i][1].row, + &(*keymap)[i][1].col, + &shift1); - fclose(infile); + (*keymap)[i][0].shift = shift0 != 0; + (*keymap)[i][1].shift = shift1 != 0; } + fclose(infile); + return success; } /****************************************************************************/ -bool BeebWin::WriteKeyMap(const char *filename, KeyMap *keymap) +FILE* BeebWin::OpenWriteFile(const char *filename, const char *typeDescr) { - bool success = true; - /* First check if file already exists */ - FILE *outfile = fopen(filename,"r"); + FILE *outfile = fopen(filename, "r"); if (outfile != NULL) { fclose(outfile); char errstr[200]; sprintf(errstr, "File already exists:\n %s\n\nOverwrite file?", filename); - if (MessageBox(GETHWND,errstr,WindowTitle,MB_YESNO|MB_ICONQUESTION) != IDYES) - return false; + if (MessageBox(GETHWND, errstr, WindowTitle, MB_YESNO | MB_ICONQUESTION) != IDYES) + return NULL; } - outfile=fopen(filename,"w"); + outfile = fopen(filename, "w"); if (outfile == NULL) { char errstr[500]; - sprintf(errstr, "Failed to write key map file:\n %s", filename); - MessageBox(GETHWND, errstr, WindowTitle, MB_OK|MB_ICONERROR); - success = false; + sprintf(errstr, "Failed to write %s file:\n %s", typeDescr, filename); + MessageBox(GETHWND, errstr, WindowTitle, MB_OK | MB_ICONERROR); } - else - { - fprintf(outfile, KEYMAP_TOKEN "\n\n"); - for (int i = 0; i < 256; ++i) - { - fprintf(outfile, "%d %d %d %d %d %d\n", - (*keymap)[i][0].row, - (*keymap)[i][0].col, - (*keymap)[i][0].shift, - (*keymap)[i][1].row, - (*keymap)[i][1].col, - (*keymap)[i][1].shift); - } + return outfile; +} - fclose(outfile); +/****************************************************************************/ +bool BeebWin::WriteKeyMap(const char *filename, KeyMap *keymap) +{ + FILE* outfile = OpenWriteFile(filename, "key map"); + + if (outfile == NULL) + return false; + + fprintf(outfile, KEYMAP_TOKEN "\n\n"); + + for (int i = 0; i < 256; ++i) + { + fprintf(outfile, "%d %d %d %d %d %d\n", + (*keymap)[i][0].row, + (*keymap)[i][0].col, + (*keymap)[i][0].shift, + (*keymap)[i][1].row, + (*keymap)[i][1].col, + (*keymap)[i][1].shift); } - return success; + fclose(outfile); + + return true; } /****************************************************************************/ diff --git a/Src/beebwinprefs.cpp b/Src/beebwinprefs.cpp index 86ec96c7..b845e786 100644 --- a/Src/beebwinprefs.cpp +++ b/Src/beebwinprefs.cpp @@ -59,7 +59,6 @@ static const char *CFG_VIEW_MONITOR = "Monitor"; static const char *CFG_SOUND_SAMPLE_RATE = "SampleRate"; static const char *CFG_SOUND_VOLUME = "SoundVolume"; static const char *CFG_SOUND_ENABLED = "SoundEnabled"; -static const char *CFG_OPTIONS_STICKS = "Sticks"; static const char *CFG_OPTIONS_KEY_MAPPING = "KeyMapping"; static const char *CFG_OPTIONS_USER_KEY_MAP_FILE = "UserKeyMapFile"; static const char *CFG_OPTIONS_FREEZEINACTIVE = "FreezeWhenInactive"; @@ -253,10 +252,7 @@ void BeebWin::LoadPreferences() if (!m_Preferences.GetBoolValue("Music5000Enabled", Music5000Enabled)) Music5000Enabled = false; - if (m_Preferences.GetDWORDValue(CFG_OPTIONS_STICKS, dword)) - m_MenuIdSticks = dword; - else - m_MenuIdSticks = 0; + ReadJoystickPreferences(); if (!m_Preferences.GetBoolValue(CFG_OPTIONS_FREEZEINACTIVE, m_FreezeWhenInactive)) m_FreezeWhenInactive = true; @@ -444,8 +440,6 @@ void BeebWin::LoadPreferences() } } - if (!m_Preferences.GetBoolValue("Basic Hardware", BasicHardwareOnly)) - BasicHardwareOnly = false; if (!m_Preferences.GetBoolValue("Teletext Half Mode", TeletextHalfMode)) TeletextHalfMode = false; @@ -642,7 +636,6 @@ void BeebWin::SavePreferences(bool saveAll) m_Preferences.SetBoolValue("TextToSpeechEnabled", m_TextToSpeechEnabled); m_Preferences.SetBoolValue("Music5000Enabled", Music5000Enabled); - m_Preferences.SetDWORDValue( CFG_OPTIONS_STICKS, m_MenuIdSticks); m_Preferences.SetBoolValue(CFG_OPTIONS_FREEZEINACTIVE, m_FreezeWhenInactive); m_Preferences.SetBoolValue(CFG_OPTIONS_HIDE_CURSOR, m_HideCursor); m_Preferences.SetBoolValue(CFG_OPTIONS_CAPTURE_MOUSE, m_CaptureMouse); @@ -699,7 +692,6 @@ void BeebWin::SavePreferences(bool saveAll) m_Preferences.SetBinaryValue(CFG_TUBE_TYPE, &TubeType, 1); - m_Preferences.SetBoolValue("Basic Hardware", BasicHardwareOnly); m_Preferences.SetBoolValue("Teletext Half Mode", TeletextHalfMode); m_Preferences.SetBoolValue("TeletextAdapterEnabled", TeletextAdapterEnabled); m_Preferences.SetBoolValue("TeletextLocalhost", TeletextLocalhost); @@ -725,6 +717,8 @@ void BeebWin::SavePreferences(bool saveAll) m_Preferences.SetDWORDValue("BitmapCaptureResolution", m_MenuIdCaptureResolution); m_Preferences.SetDWORDValue("BitmapCaptureFormat", m_MenuIdCaptureFormat); + WriteJoystickPreferences(); + RECT rect; GetWindowRect(m_hWnd,&rect); m_Preferences.SetBinaryValue("WindowPos", &rect, sizeof(rect)); @@ -742,6 +736,8 @@ void BeebWin::SavePreferences(bool saveAll) m_Preferences.SetBoolValue("WriteInstructionCounts", m_WriteInstructionCounts); + WriteJoystickOrder(); + if (m_Preferences.Save(m_PrefsFile) == Preferences::Result::Success) { m_AutoSavePrefsChanged = false; } diff --git a/Src/debug.cpp b/Src/debug.cpp index 6f2114d4..28c63069 100644 --- a/Src/debug.cpp +++ b/Src/debug.cpp @@ -45,6 +45,7 @@ Boston, MA 02110-1301, USA. #include "debug.h" #include "z80mem.h" #include "z80.h" +#include "Dialog.h" #include "FileDialog.h" #include "StringUtils.h" #include "DebugTrace.h" @@ -988,16 +989,6 @@ static const InstInfo* GetOpcodeTable(bool host) } } -static bool IsDlgItemChecked(HWND hDlg, int nIDDlgItem) -{ - return SendDlgItemMessage(hDlg, nIDDlgItem, BM_GETCHECK, 0, 0) == BST_CHECKED; -} - -static void SetDlgItemChecked(HWND hDlg, int nIDDlgItem, bool checked) -{ - SendDlgItemMessage(hDlg, nIDDlgItem, BM_SETCHECK, checked ? BST_CHECKED : BST_UNCHECKED, 0); -} - void DebugOpenDialog(HINSTANCE hinst, HWND /* hwndMain */) { if (hwndInvisibleOwner == 0) diff --git a/Src/filedialog.cpp b/Src/filedialog.cpp index c60d9f95..65f2c761 100644 --- a/Src/filedialog.cpp +++ b/Src/filedialog.cpp @@ -36,12 +36,11 @@ FileDialog::FileDialog(HWND hwndOwner, LPTSTR result, DWORD resultLength, m_ofn.nMaxFile = resultLength; m_ofn.lpstrInitialDir = initialFolder; m_ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY; + m_ofn.lpstrFile[0] = 0; } bool FileDialog::ShowDialog(bool open) { - m_ofn.lpstrFile[0] = 0; - if (open) return GetOpenFileName(&m_ofn) != 0; else diff --git a/Src/filedialog.h b/Src/filedialog.h index 6e54d79b..5124f256 100644 --- a/Src/filedialog.h +++ b/Src/filedialog.h @@ -51,6 +51,11 @@ class FileDialog m_ofn.lpstrTitle = title; } + void SetInitial(LPCTSTR initial) + { + strcpy(m_ofn.lpstrFile, initial); + } + // Show dialog bool Open() { diff --git a/Src/preferences.cpp b/Src/preferences.cpp index 0e7dd78a..e4ce734a 100644 --- a/Src/preferences.cpp +++ b/Src/preferences.cpp @@ -158,6 +158,24 @@ bool Preferences::GetStringValue(const char *id, char *str) //----------------------------------------------------------------------------- +bool Preferences::GetStringValue(const char *id, std::string& str) +{ + bool found = true; + + PrefsMap::const_iterator i = m_Prefs.find(id); + + if (i != m_Prefs.end()) { + str = i->second; + } + else { + found = false; + } + + return found; +} + +//----------------------------------------------------------------------------- + void Preferences::SetStringValue(const char *id, const char *str) { m_Prefs[id] = str; @@ -165,6 +183,13 @@ void Preferences::SetStringValue(const char *id, const char *str) //----------------------------------------------------------------------------- +void Preferences::SetStringValue(const char *id, const std::string& str) +{ + m_Prefs[id] = str; +} + +//----------------------------------------------------------------------------- + bool Preferences::GetDWORDValue(const char *id, DWORD &dw) { bool found = true; diff --git a/Src/preferences.h b/Src/preferences.h index d8342174..ae16a51f 100644 --- a/Src/preferences.h +++ b/Src/preferences.h @@ -46,7 +46,9 @@ class Preferences { bool GetBinaryValue(const char *id, void *bin, size_t binsize); void SetBinaryValue(const char *id, const void *bin, size_t binsize); bool GetStringValue(const char *id, char *str); + bool GetStringValue(const char *id, std::string& str); void SetStringValue(const char *id, const char *str); + void SetStringValue(const char *id, const std::string& str); bool GetDWORDValue(const char *id, DWORD &dw); void SetDWORDValue(const char *id, DWORD dw); bool GetBoolValue(const char *id, bool &b); diff --git a/Src/serialdevices.cpp b/Src/serialdevices.cpp index 60db3f40..938c45f6 100644 --- a/Src/serialdevices.cpp +++ b/Src/serialdevices.cpp @@ -243,8 +243,8 @@ void TouchScreenReadScreen(bool check) { static int last_x = -1, last_y = -1, last_m = -1; - int x = (65535 - JoystickX) / (65536 / 120) + 1; - int y = JoystickY / (65536 / 90) + 1; + int x = (65535 - JoystickX[0]) / (65536 / 120) + 1; + int y = JoystickY[0] / (65536 / 90) + 1; if (last_x != x || last_y != y || last_m != AMXButtons || !check) { diff --git a/Src/userkybd.cpp b/Src/userkybd.cpp index 6e61eca1..0277700b 100644 --- a/Src/userkybd.cpp +++ b/Src/userkybd.cpp @@ -24,13 +24,14 @@ Boston, MA 02110-1301, USA. #include #include #include +#include #include "main.h" #include "beebemrc.h" #include "userkybd.h" #include "SelectKeyDialog.h" #include "Messages.h" -static void SetKeyColour(COLORREF aColour); +static void SetKeyColour(COLORREF aColour, HWND keyCtrl = NULL); static void SelectKeyMapping(HWND hwnd, UINT ctrlID, HWND hwndCtrl); static void SetBBCKeyForVKEY(int Key, bool shift); static void SetRowCol(UINT ctrlID); @@ -44,37 +45,245 @@ static void DrawBorder(HDC hDC, RECT rect, BOOL Depressed); static void DrawText(HDC hDC, RECT rect, HWND hwndCtrl, COLORREF colour, bool Depressed); static COLORREF GetKeyColour(UINT ctrlID); static std::string GetKeysUsed(); +static void FillAssignedKeysCount(); +static void UpdateAssignedKeysCount(int row, int col, int change, bool redrawColour = false); +static void RedrawAllKeys(); +static int GetBBCKeyIndex(const BBCKey* key); // Colour used to highlight the selected key. static const COLORREF HighlightColour = 0x00FF0080; // Purple static const COLORREF FunctionKeyColour = 0x000000FF; // Red static const COLORREF NormalKeyColour = 0x00000000; // Black -static COLORREF oldKeyColour; +static const COLORREF JoyAssignedKeyColour = 0x00802020; // Blueish static HWND hwndBBCKey; // Holds the BBCKey control handle which is now selected. static UINT selectedCtrlID; // Holds ctrlId of selected key (or 0 if none selected). static HWND hwndMain; // Holds the BeebWin window handle. static HWND hwndUserKeyboard; +static bool doingJoystick; // Doing joystick vs user keyboard mapping static int BBCRow; // Used to store the Row and Col values while we wait static int BBCCol; // for a key press from the User. static bool doingShifted; // Selecting shifted or unshifted key press +static bool doingUnassign; // Removing key assignment // Initialised to defaultMapping. KeyMap UserKeymap; -static const char* szSelectKeyDialogTitle[2] = { - "Press key for unshifted press...", - "Press key for shifted press..." +JoyMap JoystickMap; + +static const char* szSelectKeyDialogTitle[2][3] = { + { + "Press key for unshifted press...", + "Press key for shifted press...", + "Press key to unassign..." + }, + { + // This can't be too long so 'unshifted' part is visible + "Move stick for unshifted press...", + "Move stick for shifted press...", + "Move stick to unassign..." + } +}; + +// Table of BBC keys +const BBCKey BBCKeys[] = { + // Unassigned must go first + { IDK_UNASSIGN, "NONE", -9, 0 }, + + // Letter keys + { IDK_A, "A", 4, 1 }, + { IDK_B, "B", 6, 4 }, + { IDK_C, "C", 5, 2 }, + { IDK_D, "D", 3, 2 }, + { IDK_E, "E", 2, 2 }, + { IDK_F, "F", 4, 3 }, + { IDK_G, "G", 5, 3 }, + { IDK_H, "H", 5, 4 }, + { IDK_I, "I", 2, 5 }, + { IDK_J, "J", 4, 5 }, + { IDK_K, "K", 4, 6 }, + { IDK_L, "L", 5, 6 }, + { IDK_M, "M", 6, 5 }, + { IDK_N, "N", 5, 5 }, + { IDK_O, "O", 3, 6 }, + { IDK_P, "P", 3, 7 }, + { IDK_Q, "Q", 1, 0 }, + { IDK_R, "R", 3, 3 }, + { IDK_S, "S", 5, 1 }, + { IDK_T, "T", 2, 3 }, + { IDK_U, "U", 3, 5 }, + { IDK_V, "V", 6, 3 }, + { IDK_W, "W", 2, 1 }, + { IDK_X, "X", 4, 2 }, + { IDK_Y, "Y", 4, 4 }, + { IDK_Z, "Z", 6, 1 }, + + // Number keys + { IDK_0, "0", 2, 7 }, + { IDK_1, "1", 3, 0 }, + { IDK_2, "2", 3, 1 }, + { IDK_3, "3", 1, 1 }, + { IDK_4, "4", 1, 2 }, + { IDK_5, "5", 1, 3 }, + { IDK_6, "6", 3, 4 }, + { IDK_7, "7", 2, 4 }, + { IDK_8, "8", 1, 5 }, + { IDK_9, "9", 2, 6 }, + + // Function keys. + { IDK_F0, "F0", 2, 0 }, + { IDK_F1, "F1", 7, 1 }, + { IDK_F2, "F2", 7, 2 }, + { IDK_F3, "F3", 7, 3 }, + { IDK_F4, "F4", 1, 4 }, + { IDK_F5, "F5", 7, 4 }, + { IDK_F6, "F6", 7, 5 }, + { IDK_F7, "F7", 1, 6 }, + { IDK_F8, "F8", 7, 6 }, + { IDK_F9, "F9", 7, 7 }, + + // Special keys. + { IDK_LEFT, "LEFT", 1, 9 }, + { IDK_RIGHT, "RIGHT", 7, 9 }, + { IDK_UP, "UP", 3, 9 }, + { IDK_DOWN, "DOWN", 2, 9 }, + { IDK_BREAK, "BREAK", -2, -2 }, + { IDK_COPY, "COPY", 6, 9 }, + { IDK_DEL, "DELETE", 5, 9 }, + { IDK_CAPS, "CAPS-LOCK", 4, 0 }, + { IDK_TAB, "TAB", 6, 0 }, + { IDK_CTRL, "CTRL", 0, 1 }, + { IDK_SPACE, "SPACE", 6, 2 }, + { IDK_RETURN, "RETURN", 4, 9 }, + { IDK_ESC, "ESCAPE", 7, 0 }, + { IDK_SHIFT_L, "SHIFT", 0, 0 }, + { IDK_SHIFT_R, "SHIFT", 0, 0 }, + { IDK_SHIFT_LOCK, "SHIFT-LOCK", 5, 0 }, + + // Special Character keys. + { IDK_SEMI_COLON, ";", 5, 7 }, + { IDK_EQUALS, "=", 1, 7 }, + { IDK_COMMA, ",", 6, 6 }, + { IDK_CARET, "^", 1, 8 }, + { IDK_DOT, ".", 6, 7 }, + { IDK_FWDSLASH, "/", 6, 8 }, + { IDK_STAR, "*", 4, 8 }, + { IDK_OPEN_SQUARE, "[", 3, 8 }, + { IDK_BACKSLASH, "\\", 7, 8 }, + { IDK_CLOSE_SQUARE, "]", 5, 8 }, + { IDK_AT, "@", 4, 7 }, + { IDK_UNDERSCORE, "_", 2, 8 }, +}; + +const std::pair BBCKeyAliases[] = { + { "DEL", "DELETE" }, + { "CAPS", "CAPS-LOCK" }, + { "CAPSLOCK", "CAPS-LOCK" }, + { "CONTROL", "CTRL" }, + { "ESC", "ESCAPE" }, + { "SHIFTLOCK", "SHIFT-LOCK" } }; +// Number of inputs assigned to each key (for joystick dialog highlight) +static int assignedKeysCount[_countof(BBCKeys)] = {}; + /****************************************************************************/ -bool UserKeyboardDialog(HWND hwndParent) +const BBCKey* GetBBCKeyByResId(int ctrlId) +{ + using resIdToKeyMapType = std::map; + + // Construct map on first use by lambda + static const resIdToKeyMapType resIdToKeyMap = []() + { + resIdToKeyMapType keyMap{}; + + for (const BBCKey& theKey : BBCKeys) + keyMap[theKey.ctrlId] = &theKey; + return keyMap; + } (); + + auto iter = resIdToKeyMap.find(ctrlId); + if (iter == resIdToKeyMap.end()) + return &BBCKeys[0]; + + return iter->second; +} + +/****************************************************************************/ + +const BBCKey* GetBBCKeyByName(const std::string& name) +{ + using nameToKeyMapType = std::map; + + // Construct map on first use by lambda + static const nameToKeyMapType nameToKeyMap = []() + { + nameToKeyMapType keyMap{}; + + for (const BBCKey& theKey : BBCKeys) + keyMap[theKey.name] = &theKey; + + for (auto& alias : BBCKeyAliases) + keyMap[alias.first] = keyMap[alias.second]; + + return keyMap; + } (); + + auto iter = nameToKeyMap.find(name); + if (iter == nameToKeyMap.end()) + return &BBCKeys[0]; + + return iter->second; +} + +/****************************************************************************/ + +const BBCKey* GetBBCKeyByRowAndCol(int row, int col) +{ + using posPair = std::pair; + using posToKeyMapType = std::map; + + // Construct map on first use by lambda + static const posToKeyMapType posToKeyMap = []() + { + posToKeyMapType keyMap{}; + + for (const BBCKey& theKey : BBCKeys) + keyMap[posPair{ theKey.row, theKey.column }] = &theKey; + return keyMap; + } (); + + auto iter = posToKeyMap.find(posPair{ row, col }); + if (iter == posToKeyMap.end()) + return &BBCKeys[0]; + + return iter->second; +} + +/****************************************************************************/ +// Get index for assignedKeysCount. The 'key' parameter must be a pointer to +// item in BBCKeys table, not a copy of it. +int GetBBCKeyIndex(const BBCKey* key) +{ + int index = key - BBCKeys; + if (index >= 0 && index < _countof(BBCKeys)) + return index; + return 0; +} + +/****************************************************************************/ + +bool UserKeyboardDialog(HWND hwndParent, bool joystick) { // Initialise locals used during this window's life. hwndMain = hwndParent; selectedCtrlID = 0; + doingJoystick = joystick; + + if (doingJoystick) + FillAssignedKeysCount(); // Open the dialog box. This is created as a modeless dialog so that // the "select key" dialog box can handle key-press messages. @@ -97,13 +306,16 @@ bool UserKeyboardDialog(HWND hwndParent) /****************************************************************************/ -static void SetKeyColour(COLORREF aColour) +static void SetKeyColour(COLORREF aColour, HWND keyCtrl) { - HDC hdc = GetDC(hwndBBCKey); + if (keyCtrl == NULL) + keyCtrl = hwndBBCKey; + + HDC hdc = GetDC(keyCtrl); SetBkColor(hdc, aColour); - ReleaseDC(hwndBBCKey, hdc); - InvalidateRect(hwndBBCKey, nullptr, TRUE); - UpdateWindow(hwndBBCKey); + ReleaseDC(keyCtrl, hdc); + InvalidateRect(keyCtrl, nullptr, TRUE); + UpdateWindow(keyCtrl); } /****************************************************************************/ @@ -113,21 +325,22 @@ static void SelectKeyMapping(HWND hwnd, UINT ctrlID, HWND hwndCtrl) // Set the placeholders. SetRowCol(ctrlID); - oldKeyColour = GetKeyColour(ctrlID); - hwndBBCKey = hwndCtrl; selectedCtrlID = ctrlID; doingShifted = false; + doingUnassign = (BBCRow == -9); - std::string UsedKeys = GetKeysUsed(); + std::string UsedKeys = doingUnassign ? "" : GetKeysUsed(); // Now ask the user to input the PC key to assign to the BBC key. selectKeyDialog = new SelectKeyDialog( hInst, hwnd, - szSelectKeyDialogTitle[doingShifted ? 1 : 0], - UsedKeys + szSelectKeyDialogTitle[(int)doingJoystick][doingUnassign ? 2 : doingShifted ? 1 : 0], + UsedKeys, + doingShifted, + doingJoystick ); selectKeyDialog->Open(); @@ -137,7 +350,7 @@ static void SelectKeyMapping(HWND hwnd, UINT ctrlID, HWND hwndCtrl) static void SetBBCKeyForVKEY(int Key, bool Shift) { - if (Key >= 0 && Key < 256) + if (!doingJoystick && Key >= 0 && Key < BEEB_VKEY_JOY_START) { UserKeymap[Key][static_cast(Shift)].row = BBCRow; UserKeymap[Key][static_cast(Shift)].col = BBCCol; @@ -146,101 +359,24 @@ static void SetBBCKeyForVKEY(int Key, bool Shift) // DebugTrace("SetBBCKey: key=%d, shift=%d, row=%d, col=%d, bbcshift=%d\n", // Key, shift, BBCRow, BBCCol, doingShifted); } + else if (doingJoystick && Key >= BEEB_VKEY_JOY_START && Key < BEEB_VKEY_JOY_END) + { + KeyMapping& entry = JoystickMap[Key - BEEB_VKEY_JOY_START][static_cast(Shift)]; + UpdateAssignedKeysCount(entry.row, entry.col, -1, true); + entry.row = BBCRow; + entry.col = BBCCol; + entry.shift = doingShifted; + UpdateAssignedKeysCount(entry.row, entry.col, +1); + } } /****************************************************************************/ static void SetRowCol(UINT ctrlID) { - switch (ctrlID) - { - // Character keys. - case IDK_A: BBCRow = 4; BBCCol = 1; break; - case IDK_B: BBCRow = 6; BBCCol = 4; break; - case IDK_C: BBCRow = 5; BBCCol = 2; break; - case IDK_D: BBCRow = 3; BBCCol = 2; break; - case IDK_E: BBCRow = 2; BBCCol = 2; break; - case IDK_F: BBCRow = 4; BBCCol = 3; break; - case IDK_G: BBCRow = 5; BBCCol = 3; break; - case IDK_H: BBCRow = 5; BBCCol = 4; break; - case IDK_I: BBCRow = 2; BBCCol = 5; break; - case IDK_J: BBCRow = 4; BBCCol = 5; break; - case IDK_K: BBCRow = 4; BBCCol = 6; break; - case IDK_L: BBCRow = 5; BBCCol = 6; break; - case IDK_M: BBCRow = 6; BBCCol = 5; break; - case IDK_N: BBCRow = 5; BBCCol = 5; break; - case IDK_O: BBCRow = 3; BBCCol = 6; break; - case IDK_P: BBCRow = 3; BBCCol = 7; break; - case IDK_Q: BBCRow = 1; BBCCol = 0; break; - case IDK_R: BBCRow = 3; BBCCol = 3; break; - case IDK_S: BBCRow = 5; BBCCol = 1; break; - case IDK_T: BBCRow = 2; BBCCol = 3; break; - case IDK_U: BBCRow = 3; BBCCol = 5; break; - case IDK_V: BBCRow = 6; BBCCol = 3; break; - case IDK_W: BBCRow = 2; BBCCol = 1; break; - case IDK_X: BBCRow = 4; BBCCol = 2; break; - case IDK_Y: BBCRow = 4; BBCCol = 4; break; - case IDK_Z: BBCRow = 6; BBCCol = 1; break; - - // Number keys. - case IDK_0: BBCRow = 2; BBCCol = 7; break; - case IDK_1: BBCRow = 3; BBCCol = 0; break; - case IDK_2: BBCRow = 3; BBCCol = 1; break; - case IDK_3: BBCRow = 1; BBCCol = 1; break; - case IDK_4: BBCRow = 1; BBCCol = 2; break; - case IDK_5: BBCRow = 1; BBCCol = 3; break; - case IDK_6: BBCRow = 3; BBCCol = 4; break; - case IDK_7: BBCRow = 2; BBCCol = 4; break; - case IDK_8: BBCRow = 1; BBCCol = 5; break; - case IDK_9: BBCRow = 2; BBCCol = 6; break; - - // Function keys. - case IDK_F0: BBCRow = 2; BBCCol = 0; break; - case IDK_F1: BBCRow = 7; BBCCol = 1; break; - case IDK_F2: BBCRow = 7; BBCCol = 2; break; - case IDK_F3: BBCRow = 7; BBCCol = 3; break; - case IDK_F4: BBCRow = 1; BBCCol = 4; break; - case IDK_F5: BBCRow = 7; BBCCol = 4; break; - case IDK_F6: BBCRow = 7; BBCCol = 5; break; - case IDK_F7: BBCRow = 1; BBCCol = 6; break; - case IDK_F8: BBCRow = 7; BBCCol = 6; break; - case IDK_F9: BBCRow = 7; BBCCol = 7; break; - - // Special keys. - case IDK_LEFT: BBCRow = 1; BBCCol = 9; break; - case IDK_RIGHT: BBCRow = 7; BBCCol = 9; break; - case IDK_UP: BBCRow = 3; BBCCol = 9; break; - case IDK_DOWN: BBCRow = 2; BBCCol = 9; break; - case IDK_BREAK: BBCRow = -2; BBCCol = -2; break; - case IDK_COPY: BBCRow = 6; BBCCol = 9; break; - case IDK_DEL: BBCRow = 5; BBCCol = 9; break; - case IDK_CAPS: BBCRow = 4; BBCCol = 0; break; - case IDK_TAB: BBCRow = 6; BBCCol = 0; break; - case IDK_CTRL: BBCRow = 0; BBCCol = 1; break; - case IDK_SPACE: BBCRow = 6; BBCCol = 2; break; - case IDK_RETURN: BBCRow = 4; BBCCol = 9; break; - case IDK_ESC: BBCRow = 7; BBCCol = 0; break; - case IDK_SHIFT_L: BBCRow = 0; BBCCol = 0; break; - case IDK_SHIFT_R: BBCRow = 0; BBCCol = 0; break; - case IDK_SHIFT_LOCK: BBCRow = 5; BBCCol = 0; break; - - // Special Character keys. - case IDK_SEMI_COLON: BBCRow = 5; BBCCol = 7; break; - case IDK_EQUALS: BBCRow = 1; BBCCol = 7; break; - case IDK_COMMA: BBCRow = 6; BBCCol = 6; break; - case IDK_CARET: BBCRow = 1; BBCCol = 8; break; - case IDK_DOT: BBCRow = 6; BBCCol = 7; break; - case IDK_FWDSLASH: BBCRow = 6; BBCCol = 8; break; - case IDK_STAR: BBCRow = 4; BBCCol = 8; break; - case IDK_OPEN_SQUARE: BBCRow = 3; BBCCol = 8; break; - case IDK_BACKSLASH: BBCRow = 7; BBCCol = 8; break; - case IDK_CLOSE_SQUARE: BBCRow = 5; BBCCol = 8; break; - case IDK_AT: BBCRow = 4; BBCCol = 7; break; - case IDK_UNDERSCORE: BBCRow = 2; BBCCol = 8; break; - - default: - BBCRow = 0; BBCCol = 0; - } + const BBCKey* key = GetBBCKeyByResId(ctrlID); + BBCRow = key->row; + BBCCol = key->column; } /****************************************************************************/ @@ -252,6 +388,17 @@ static INT_PTR CALLBACK UserKeyboardDlgProc(HWND hwnd, { switch (nMessage) { + case WM_INITDIALOG: + if (doingJoystick) + { + SetWindowText(hwnd, "Joystick To Keyboard Mapping"); + } + else + { + ShowWindow(GetDlgItem(hwnd, IDK_RESET_MAPPING), SW_HIDE); + } + return FALSE; + case WM_COMMAND: switch (wParam) { @@ -264,6 +411,12 @@ static INT_PTR CALLBACK UserKeyboardDlgProc(HWND hwnd, PostMessage(hwndMain, WM_USER_KEYBOARD_DIALOG_CLOSED, 0, 0); break; + case IDK_RESET_MAPPING: + SendMessage(hwndMain, WM_COMMAND, IDM_RESETJOYMAP, 0); + FillAssignedKeysCount(); + RedrawAllKeys(); + break; + default: SelectKeyMapping(hwnd, (UINT)wParam, (HWND)lParam); break; @@ -281,7 +434,7 @@ static INT_PTR CALLBACK UserKeyboardDlgProc(HWND hwnd, // Assign the BBC key to the PC key. SetBBCKeyForVKEY( selectKeyDialog->Key(), - doingShifted + selectKeyDialog->Shift() ); } @@ -290,25 +443,39 @@ static INT_PTR CALLBACK UserKeyboardDlgProc(HWND hwnd, if ((wParam == IDOK || wParam == IDCONTINUE) && !doingShifted) { - doingShifted = true; + doingShifted = !doingShifted; - std::string UsedKeys = GetKeysUsed(); + std::string UsedKeys = doingUnassign ? "" : GetKeysUsed(); selectKeyDialog = new SelectKeyDialog( hInst, hwndUserKeyboard, - szSelectKeyDialogTitle[doingShifted ? 1 : 0], - UsedKeys + szSelectKeyDialogTitle[(int)doingJoystick][doingUnassign ? 2 : doingShifted ? 1 : 0], + UsedKeys, + doingShifted, + doingJoystick ); selectKeyDialog->Open(); } else { + int ctrlID = selectedCtrlID; selectedCtrlID = 0; // Show the key as not depressed, i.e., normal. - SetKeyColour(oldKeyColour); + SetKeyColour(GetKeyColour(ctrlID)); + + if (ctrlID == IDK_SHIFT_L) + { + HWND rShiftCtrl = GetDlgItem(hwndUserKeyboard, IDK_SHIFT_R); + SetKeyColour(GetKeyColour(IDK_SHIFT_R), rShiftCtrl); + } + else if (ctrlID == IDK_SHIFT_R) + { + HWND lShiftCtrl = GetDlgItem(hwndUserKeyboard, IDK_SHIFT_L); + SetKeyColour(GetKeyColour(IDK_SHIFT_L), lShiftCtrl); + } } return TRUE; @@ -436,6 +603,13 @@ static COLORREF GetKeyColour(UINT ctrlID) return HighlightColour; } + if (doingJoystick) + { + int index = GetBBCKeyIndex(GetBBCKeyByResId(ctrlID)); + if (index != 0 && assignedKeysCount[index] > 0) + return JoyAssignedKeyColour; + } + switch (ctrlID) { case IDK_F0: @@ -461,16 +635,36 @@ static std::string GetKeysUsed() { std::string Keys; + KeyPair* table; + int start, end; + int offset; // First Vkey code in table + + if (!doingJoystick) + { + table = UserKeymap; + offset = 0; + start = 1; + end = 256; + } + else + { + table = JoystickMap; + offset = BEEB_VKEY_JOY_START; + start = BEEB_VKEY_JOY_START; + end = BEEB_VKEY_JOY_END; + } + // First see if this key is defined. - if (BBCRow != 0 || BBCCol != 0) + // Row 0 is Shift key, row -2 is Break, row -9 is unassigned + if (BBCRow != -9) { - for (int i = 1; i < 256; i++) + for (int i = start; i < end; i++) { for (int s = 0; s < 2; ++s) { - if (UserKeymap[i][s].row == BBCRow && - UserKeymap[i][s].col == BBCCol && - UserKeymap[i][s].shift == doingShifted) + if (table[i - offset][s].row == BBCRow && + table[i - offset][s].col == BBCCol && + table[i - offset][s].shift == doingShifted) { // We have found a key that matches. if (!Keys.empty()) @@ -496,3 +690,78 @@ static std::string GetKeysUsed() return Keys; } + +/****************************************************************************/ + +void FillAssignedKeysCount() +{ + std::fill(std::begin(assignedKeysCount), std::end(assignedKeysCount), 0); + + KeyPair* table; + int start, end; + int offset; // First Vkey code in table + + if (!doingJoystick) + { + table = UserKeymap; + offset = 0; + start = 1; + end = 256; + } + else + { + table = JoystickMap; + offset = BEEB_VKEY_JOY_START; + start = BEEB_VKEY_JOY_START; + end = BEEB_VKEY_JOY_END; + } + + for (int i = start; i < end; i++) + { + KeyPair& pair = table[i - offset]; + for (int s = 0; s < 2; s++) + { + UpdateAssignedKeysCount(pair[s].row, pair[s].col, +1); + } + } +} + +/****************************************************************************/ + +static void UpdateAssignedKeysCount(int row, int col, int change, bool redrawColour) +{ + const BBCKey* key = GetBBCKeyByRowAndCol(row, col); + int index = GetBBCKeyIndex(key); + if (index < _countof(BBCKeys)) + { + assignedKeysCount[index] += change; + if (redrawColour && key->ctrlId != selectedCtrlID) + { + HWND keyCtrl = GetDlgItem(hwndUserKeyboard, key->ctrlId); + SetKeyColour(GetKeyColour(key->ctrlId), keyCtrl); + } + /* If it's shift, update the other one */ + if (key->column == 0 && key->row == 0) + { + key = GetBBCKeyByResId((IDK_SHIFT_L + IDK_SHIFT_R) - key->ctrlId); + index = GetBBCKeyIndex(key); + assignedKeysCount[index] += change; + if (redrawColour && key->ctrlId != selectedCtrlID) + { + HWND keyCtrl = GetDlgItem(hwndUserKeyboard, key->ctrlId); + SetKeyColour(GetKeyColour(key->ctrlId), keyCtrl); + } + } + } +} + +/****************************************************************************/ + +static void RedrawAllKeys() +{ + for (const BBCKey& key : BBCKeys) + { + HWND keyCtrl = GetDlgItem(hwndUserKeyboard, key.ctrlId); + SetKeyColour(GetKeyColour(key.ctrlId), keyCtrl); + } +} \ No newline at end of file diff --git a/Src/userkybd.h b/Src/userkybd.h index fcc47152..617b9cbb 100644 --- a/Src/userkybd.h +++ b/Src/userkybd.h @@ -25,8 +25,20 @@ Boston, MA 02110-1301, USA. // Public declarations. -bool UserKeyboardDialog(HWND hwndParent); +struct BBCKey +{ + unsigned int ctrlId; + const char* name; + int row; + int column; +}; + +const BBCKey* GetBBCKeyByName(const std::string& name); +const BBCKey* GetBBCKeyByRowAndCol(int row, int col); + +bool UserKeyboardDialog(HWND hwndParent, bool joystick); extern KeyMap UserKeymap; +extern JoyMap JoystickMap; #endif