]> Shamusworld >> Repos - virtualjaguar/blobdiff - src/gui/profile.cpp
Hotfix for bug #28.
[virtualjaguar] / src / gui / profile.cpp
index 05d173d84407f716eda3c4adf6a29bf7dc5f5ced..cbb12f47efc7da55c697c34649cb9c573a68e1a1 100644 (file)
@@ -7,28 +7,49 @@
 // JLH = James Hammons <jlhamm@acm.org>
 //
 // Who  When        What
-// ---  ----------  -------------------------------------------------------------
+// ---  ----------  ------------------------------------------------------------
 // JLH  05/01/2013  Created this file
+// JLH  10/02/2014  Finally fixed stuff so it works the way it should
+//
+// This is a profile database with two parts: One, a list of devices, and two,
+// a list of profiles each containing a pointer to the device list, and map
+// name, a preferred slot #, and a key/button map. All the heavy lifting (incl.
+// autoconnection of devices to profiles to slots) is done here.
+//
+// Basically, how this works is that we connect the device the user plugs into
+// the computer to a profile in the database to a slot in the virtual Jaguar.
+// Hopefully the configuration that the user gives us is sane enough for us to
+// figure out how to do the right thing! By default, there is always a keyboard
+// device plugged in; any other device that gets plugged in and wants to be in
+// slot #0 can override it. This is so there is always a sane configuration if
+// nothing is plugged in.
+//
+// Devices go into the database when the user plugs them in and runs VJ, and
+// subsequently does anything to alter any of the existing profiles. Once a
+// device has been seen, it can't be unseen!
 //
-
 
 #include "profile.h"
 #include <QtGui>
 #include "gamepad.h"
+#include "log.h"
 #include "settings.h"
 
 
+//#define DEBUG_PROFILES
 #define MAX_DEVICES  64
 
 
 Profile profile[MAX_PROFILES];
+Profile profileBackup[MAX_PROFILES];
 int controller1Profile;
 int controller2Profile;
-int gamepad1Slot;
-int gamepad2Slot;
+int gamepadIDSlot1;
+int gamepadIDSlot2;
 int numberOfProfiles;
 int numberOfDevices;
 char deviceNames[MAX_DEVICES][128];
+static int numberOfProfilesSave;
 
 // This is so that new devices have something reasonable to show for default
 uint32_t defaultMap[21] = {
@@ -37,6 +58,29 @@ uint32_t defaultMap[21] = {
 };
 
 
+// Function Prototypes
+int ConnectProfileToDevice(int deviceNum, int gamepadID = -1);
+int FindProfileForDevice(int deviceNum, int preferred, int * found);
+
+
+//
+// These two functions are mainly to support the controller configuration GUI.
+// Could just as easily go there as well (and be better placed there).
+//
+void SaveProfiles(void)
+{
+       numberOfProfilesSave = numberOfProfiles;
+       memcpy(&profileBackup, &profile, sizeof(Profile) * MAX_PROFILES);
+}
+
+
+void RestoreProfiles(void)
+{
+       memcpy(&profile, &profileBackup, sizeof(Profile) * MAX_PROFILES);
+       numberOfProfiles = numberOfProfilesSave;
+}
+
+
 void ReadProfiles(QSettings * set)
 {
        // Assume no profiles, until we read them
@@ -54,39 +98,49 @@ void ReadProfiles(QSettings * set)
        {
                set->setArrayIndex(i - 1);
                strcpy(deviceNames[i], set->value("deviceName").toString().toAscii().data());
-//printf("Read device name: %s\n", deviceNames[i]);
+#ifdef DEBUG_PROFILES
+printf("Read device name: %s\n", deviceNames[i]);
+#endif
        }
 
        set->endArray();
        numberOfProfiles = set->beginReadArray("profiles");
-//printf("Number of profiles: %u\n", numberOfProfiles);
+#ifdef DEBUG_PROFILES
+printf("Number of profiles: %u\n", numberOfProfiles);
+#endif
 
        for(int i=0; i<numberOfProfiles; i++)
        {
                set->setArrayIndex(i);
                profile[i].device = set->value("deviceNum").toInt();
                strcpy(profile[i].mapName, set->value("mapName").toString().toAscii().data());
-               profile[i].preferredController = set->value("preferredController").toInt();
+               profile[i].preferredSlot = set->value("preferredSlot").toInt();
 
                for(int j=0; j<21; j++)
                {
                        QString string = QString("map%1").arg(j);
                        profile[i].map[j] = set->value(string).toInt();
                }
-//printf("Profile #%u: device=%u (%s)\n", i, profile[i].device, deviceNames[profile[i].device]);
+#ifdef DEBUG_PROFILES
+printf("Profile #%u: device=%u (%s)\n", i, profile[i].device, deviceNames[profile[i].device]);
+#endif
        }
 
        set->endArray();
 
-//printf("Number of profiles found: %u\n", numberOfProfiles);
+#ifdef DEBUG_PROFILES
+printf("Number of profiles found: %u\n", numberOfProfiles);
+#endif
        // Set up a reasonable default if no profiles were found
        if (numberOfProfiles == 0)
        {
-//printf("Setting up default profile...\n");
+#ifdef DEBUG_PROFILES
+printf("Setting up default profile...\n");
+#endif
                numberOfProfiles++;
                profile[0].device = 0;  // Keyboard is always device #0
                strcpy(profile[0].mapName, "Default");
-               profile[0].preferredController = CONTROLLER1;
+               profile[0].preferredSlot = CONTROLLER1;
 
                for(int i=0; i<21; i++)
                        profile[0].map[i] = defaultMap[i];
@@ -118,7 +172,7 @@ void WriteProfiles(QSettings * set)
                set->setArrayIndex(i);
                set->setValue("deviceNum", profile[i].device);
                set->setValue("mapName", profile[i].mapName);
-               set->setValue("preferredController", profile[i].preferredController);
+               set->setValue("preferredSlot", profile[i].preferredSlot);
 
                for(int j=0; j<21; j++)
                {
@@ -131,17 +185,58 @@ void WriteProfiles(QSettings * set)
 }
 
 
+int GetFreeProfile(void)
+{
+       // Check for too many, return -1 if so
+       if (numberOfProfiles == MAX_PROFILES)
+               return -1;
+
+       int profileNum = numberOfProfiles;
+       numberOfProfiles++;
+       return profileNum;
+}
+
+
+void DeleteProfile(int profileToDelete)
+{
+       // Sanity check
+       if (profileToDelete >= numberOfProfiles)
+               return;
+
+       // Trivial case: Profile at end of the array
+       if (profileToDelete == (numberOfProfiles - 1))
+       {
+               numberOfProfiles--;
+               return;
+       }
+
+//     memmove(dest, src, bytesToMove);
+       memmove(&profile[profileToDelete], &profile[profileToDelete + 1], ((numberOfProfiles - 1) - profileToDelete) * sizeof(Profile));
+       numberOfProfiles--;
+}
+
+
 int FindDeviceNumberForName(const char * name)
 {
        for(int i=0; i<numberOfDevices; i++)
        {
                if (strcmp(deviceNames[i], name) == 0)
+#ifdef DEBUG_PROFILES
+{
+printf("PROFILE: Found device #%i for name (%s)...\n", i, name);
+#endif
                        return i;
+#ifdef DEBUG_PROFILES
+}
+#endif
        }
 
        if (numberOfDevices == MAX_DEVICES)
                return -1;
 
+#ifdef DEBUG_PROFILES
+printf("Device '%s' not found, creating device...\n", name);
+#endif
        // If the device wasn't found, it must be new; so add it to the list.
        int deviceNum = numberOfDevices;
        deviceNames[deviceNum][127] = 0;
@@ -158,8 +253,9 @@ int FindMappingsForDevice(int deviceNum, QComboBox * combo)
 
        for(int i=0; i<numberOfProfiles; i++)
        {
-               if (profile[i].device == -1)
-                       continue;
+//This should *never* be the case--all profiles in list are *good*
+//             if (profile[i].device == -1)
+//                     continue;
 
                if (profile[i].device == deviceNum)
                {
@@ -173,7 +269,7 @@ int FindMappingsForDevice(int deviceNum, QComboBox * combo)
        {
                profile[numberOfProfiles].device = deviceNum;
                strcpy(profile[numberOfProfiles].mapName, "Default");
-               profile[numberOfProfiles].preferredController = CONTROLLER1;
+               profile[numberOfProfiles].preferredSlot = CONTROLLER1;
 
                for(int i=0; i<21; i++)
                        profile[numberOfProfiles].map[i] = defaultMap[i];
@@ -187,8 +283,47 @@ int FindMappingsForDevice(int deviceNum, QComboBox * combo)
 }
 
 
+// N.B.: Unused
+int FindUsableProfiles(QComboBox * combo)
+{
+       int found = 0;
+
+       // Check for device #0 (keyboard) profiles first
+       for(int j=0; j<numberOfProfiles; j++)
+       {
+               // Check for device *and* usable configuration
+               if ((profile[j].device == 0) && (profile[j].preferredSlot))
+               {
+                       combo->addItem(QString("Keyboard::%1").arg(profile[j].mapName), j);
+                       found++;
+               }
+       }
+
+       // Check for connected host devices next
+       for(int i=0; i<Gamepad::numJoysticks; i++)
+       {
+               int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
+
+               for(int j=0; j<numberOfProfiles; j++)
+               {
+                       if ((profile[j].device == deviceNum) && (profile[j].preferredSlot))
+                       {
+                               combo->addItem(QString("%1::%2").arg(Gamepad::GetJoystickName(i)).arg(profile[j].mapName), j);
+                               found++;
+                       }
+               }
+       }
+
+       return found;
+}
+
+
 bool ConnectProfileToController(int profileNum, int controllerNum)
 {
+       // Sanity checks...
+       if (profileNum < 0)
+               return false;
+
        if (profile[profileNum].device == -1)
                return false;
 
@@ -200,91 +335,218 @@ bool ConnectProfileToController(int profileNum, int controllerNum)
        for(int i=0; i<21; i++)
                dest[i] = profile[profileNum].map[i];
 
-printf("Successfully mapped device '%s' (%s) to controller #%u...\n", deviceNames[profile[profileNum].device], profile[profileNum].mapName, controllerNum);
+       WriteLog("PROFILE: Successfully mapped device '%s' (%s) to controller #%u...\n", deviceNames[profile[profileNum].device], profile[profileNum].mapName, controllerNum);
        return true;
 }
 
 
-//
-// This is a pretty crappy way of doing autodetection. What it does is scan for
-// keyboard profiles first, then look for plugged in gamepads next. If more
-// than one plugged in gamepad matches a preferred controller slot, the last
-// one found is chosen.
-//
-// There has to be a better way to do this, I just can't think of what it
-// should be ATM... :-P
-//
 /*
-Also, there is a problem with this approach and having multiple devices
-that are the same. Currently, if two of the same device are plugged in
-and the profile is set to both controllers, it will broadcast buttons
-pressed from either gamepad, no matter who is pressing them. This is
-BAD(tm). [Not true, but there's a different problem described under 'How to
-solve?', so GOOD(tm).]
-
-Also, the gamepad logic doesn't distinguish inputs by controller, it just
-grabs them all regardless. This is also BAD(tm). [Actually, it doesn't. It
-properly segregates the inputs. So this is GOOD(tm).]
-
-How to solve?
-
-Seems there's yet ANOTHER dimension to all this: The physical gamepads
-plugged into their ports. Now the device # can map these fine if they're
-different, but we still run into problems with the handling in the MainWin
-because it's hardwired to take pad 0 in slot 0 and pad 1 in slot 1. If you have
-them configured other than this, you won't get anything. So we need to also
-map the physical devices to their respective slots.
+One more stab at this...
+
+ -  Connect keyboard to slot #0.
+ -  Loop thru all connected devices. For each device:
+    -  Grab all profiles for the device. For each profile:
+       -  Check to see what its preferred device is.
+       -  If PD is slot #0, see if slot is already taken (gamepadIDSlot1 != -1).
+          If not taken, take it; otherwise put in list to tell user to solve the
+          conflict for us.
+          -  If the slot is already taken and *it's the same device* as the one
+             we're looking at, set it in slot #1.
+       -  If PD is slot #1, see if slot is already taken. If not, take it;
+          otherwise, put in list to tell user to solve conflict for us.
+       -  If PD is slot #0 & #1, see if either is already taken. Try #0 first,
+          then try #1. If both are already taken, skip it. Do this *after* we've
+          connected devices with preferred slots.
 */
 void AutoConnectProfiles(void)
 {
-       int controller1Profile = -1;
-       int controller2Profile = -1;
+//     int foundProfiles[MAX_PROFILES];
+       controller1Profile = -1;
+       controller2Profile = -1;
+       gamepadIDSlot1 = -1;
+       gamepadIDSlot2 = -1;
+
+       // Connect the keyboard automagically only if no gamepads are plugged in.
+       // Otherwise, check after all other devices have been checked, then try to
+       // add it in.
+       if (Gamepad::numJoysticks == 0)
+       {
+               ConnectProfileToDevice(0);
+               return;
+       }
 
-       // Nothing plugged in, we fall back to the default keyboard device profiles
-//     if (Gamepad::numJoysticks == 0)
+       // Connect the profiles that prefer a slot, if any.
+       // N.B.: Conflicts are detected, but ignored. 1st controller to grab a
+       //       preferred slot gets it. :-P
+       for(int i=0; i<Gamepad::numJoysticks; i++)
        {
-// Check for Keyboard device first, if anything else is plugged in it will
-// default to it instead
-               for(int i=0; i<numberOfProfiles; i++)
+               int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
+//             bool p1Overwriteable = 
+
+               for(int j=0; j<numberOfProfiles; j++)
                {
-                       // Skip profile if it's not Keyboard device
-                       if (profile[i].device != 0)
+                       if (deviceNum != profile[j].device)
                                continue;
 
-                       if (profile[i].preferredController & CONTROLLER1)
-                               controller1Profile = i;
+                       int slot = profile[j].preferredSlot;
 
-                       if (profile[i].preferredController & CONTROLLER2)
-                               controller2Profile = i;
+                       if (slot == CONTROLLER1)
+                       {
+                               if (gamepadIDSlot1 == -1)
+                                       controller1Profile = j, gamepadIDSlot1 = i;
+                               else
+                               {
+                                       // Autoresolve simple conflict: two controllers sharing one
+                                       // profile mapped to slot #0.
+                                       if ((deviceNum == profile[controller1Profile].device) && (controller2Profile == -1))
+                                               controller2Profile = j, gamepadIDSlot2 = i;
+                                       else
+                                               ; // Alert user to conflict and ask to resolve
+                               }
+                       }
+                       else if (slot == CONTROLLER2)
+                       {
+                               if (gamepadIDSlot2 == -1)
+                                       controller2Profile = j, gamepadIDSlot2 = i;
+                               else
+                               {
+                                       // Autoresolve simple conflict: two controllers sharing one
+                                       // profile mapped to slot #1.
+                                       if ((deviceNum == profile[controller2Profile].device) && (controller1Profile == -1))
+                                               controller1Profile = j, gamepadIDSlot1 = i;
+                                       else
+                                               ; // Alert user to conflict and ask to resolve
+                               }
+                       }
                }
        }
-//     else
+
+       // Connect the "don't care" states, if any. We don't roll it into the above,
+       // because it can override the profiles that have a definite preference.
+       // These should be lowest priority.
+       for(int i=0; i<Gamepad::numJoysticks; i++)
        {
-//printf("Number of gamepads found: %u\n", Gamepad::numJoysticks);
-               for(int i=0; i<Gamepad::numJoysticks; i++)
+               int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
+
+               for(int j=0; j<numberOfProfiles; j++)
                {
-                       int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
-//printf("Attempting to find valid gamepad profile. Device=%u\n", deviceNum);
+                       if (deviceNum != profile[j].device)
+                               continue;
+
+                       int slot = profile[j].preferredSlot;
 
-                       for(int j=0; j<numberOfProfiles; j++)
+                       if (slot == (CONTROLLER1 | CONTROLLER2))
                        {
-                               // Skip profile if it's not discovered device
-                               if (profile[j].device != deviceNum)
-                                       continue;
+                               if (gamepadIDSlot1 == -1)
+                                       controller1Profile = j, gamepadIDSlot1 = i;
+                               else if (gamepadIDSlot2 == -1)
+                                       controller2Profile = j, gamepadIDSlot2 = i;
+                       }
+               }
+       }
 
-                               if (profile[j].preferredController & CONTROLLER1)
-                                       controller1Profile = j;
+       // Connect the keyboard device (lowest priority)
+       // N.B.: The keyboard is always mapped to profile #0, so we can locate it
+       //       easily. :-)
+       int slot = profile[0].preferredSlot;
 
-                               if (profile[j].preferredController & CONTROLLER2)
-                                       controller2Profile = j;
-                       }
+       if ((slot == CONTROLLER1) && (gamepadIDSlot1 == -1))
+               controller1Profile = 0;
+       else if ((slot == CONTROLLER2) && (gamepadIDSlot2 == -1))
+               controller2Profile = 0;
+       else if (slot == (CONTROLLER1 | CONTROLLER2))
+       {
+               if (gamepadIDSlot1 == -1)
+                       controller1Profile = 0;
+               else if (gamepadIDSlot2 == -1)
+                       controller2Profile = 0;
+       }
+
+       // Finally, attempt to connect profiles to controllers
+       ConnectProfileToController(controller1Profile, 0);
+       ConnectProfileToController(controller2Profile, 1);
+}
+
+
+int ConnectProfileToDevice(int deviceNum, int gamepadID/*= -1*/)
+{
+//     bool found1 = false;
+//     bool found2 = false;
+       int numberFoundForController1 = 0;
+       int numberFoundForController2 = 0;
+
+       for(int i=0; i<numberOfProfiles; i++)
+       {
+               // Skip profile if it's not our device
+               if (profile[i].device != deviceNum)
+                       continue;
+
+               if (profile[i].preferredSlot & CONTROLLER1)
+               {
+                       controller1Profile = i;
+                       gamepadIDSlot1 = gamepadID;
+//                     found1 = true;
+                       numberFoundForController1++;
+               }
+
+               if (profile[i].preferredSlot & CONTROLLER2)
+               {
+                       controller2Profile = i;
+                       gamepadIDSlot2 = gamepadID;
+//                     found2 = true;
+                       numberFoundForController2++;
                }
        }
 
-       if (controller1Profile != -1)
-               ConnectProfileToController(controller1Profile, 0);
+//     return found;
+       return numberFoundForController1 + numberFoundForController2;
+}
+
+
+// N.B.: Unused
+int FindProfileForDevice(int deviceNum, int preferred, int * found)
+{
+       int numFound = 0;
+
+       for(int i=0; i<numberOfProfiles; i++)
+       {
+               // Return the profile only if it matches the passed in device and
+               // matches the passed in preference...
+               if ((profile[i].device == deviceNum) && (profile[i].preferredSlot == preferred))
+                       found[numFound++] = i;
+       }
 
-       if (controller2Profile != -1)
-               ConnectProfileToController(controller2Profile, 1);
+       return numFound;
 }
 
+
+//
+// Also note that we have the intersection of three things here: One the one
+// hand, we have the detected joysticks with their IDs (typically in the range
+// of 0-7), we have our gamepad profiles and their IDs (typically can have up to
+// 64 of them), and we have our gamepad slots that the detected joysticks can be
+// connected to.
+//
+// So, when the user plugs in a gamepad, it gets a joystick ID, then the profile
+// manager checks to see if a profile (or profiles) for it exists. If so, then
+// it assigns that joystick ID to a gamepad slot, based upon what the user
+// requested for that profile.
+//
+// A problem (perhaps) arises when you have more than one profile for a certain
+// device, how do you know which one to use? Perhaps you have a field in the
+// profile saying that you use this profile 1st, that one 2nd, and so on...
+//
+// Some use cases, and how to resolve them:
+//
+// - User has two of the same device, and plugs them both in. There is only one
+//   profile. In this case, the sane thing to do is ignore the "preferred slot"
+//   of the dialog and use the same profile for both controllers, and plug them
+//   both into slot #0 and #1.
+// - User has one device, and plugs it in. There are two profiles. In this case,
+//   the profile chosen should be based upon the "preferred slot", with slot #0
+//   being the winner. If both profiles are set for slot #0, ask the user which
+//   profile to use, and set a flag in the profile to say that it is a preferred
+//   profile for that device.
+// - In any case where there are conflicts, the user must be consulted and sane
+//   defaults used.
+//