]> Shamusworld >> Repos - virtualjaguar/blob - src/gui/profile.cpp
Replace QtGui includes with QtWidgets, add QT += widgets for qmake
[virtualjaguar] / src / gui / profile.cpp
1 //
2 // profile.cpp - Global profile storage/definition/manipulation
3 //
4 // by James Hammons
5 // (C) 2013 Underground Software
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // Who  When        What
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  05/01/2013  Created this file
12 // JLH  10/02/2014  Finally fixed stuff so it works the way it should
13 //
14 // This is a profile database with two parts: One, a list of devices, and two,
15 // a list of profiles each containing a pointer to the device list, and map
16 // name, a preferred slot #, and a key/button map. All the heavy lifting (incl.
17 // autoconnection of devices to profiles to slots) is done here.
18 //
19 // Basically, how this works is that we connect the device the user plugs into
20 // the computer to a profile in the database to a slot in the virtual Jaguar.
21 // Hopefully the configuration that the user gives us is sane enough for us to
22 // figure out how to do the right thing! By default, there is always a keyboard
23 // device plugged in; any other device that gets plugged in and wants to be in
24 // slot #0 can override it. This is so there is always a sane configuration if
25 // nothing is plugged in.
26 //
27 // Devices go into the database when the user plugs them in and runs VJ, and
28 // subsequently does anything to alter any of the existing profiles. Once a
29 // device has been seen, it can't be unseen!
30 //
31
32 #include "profile.h"
33 #include "gamepad.h"
34 #include "log.h"
35 #include "settings.h"
36
37
38 //#define DEBUG_PROFILES
39 #define MAX_DEVICES  64
40
41
42 Profile profile[MAX_PROFILES];
43 Profile profileBackup[MAX_PROFILES];
44 int controller1Profile;
45 int controller2Profile;
46 int gamepadIDSlot1;
47 int gamepadIDSlot2;
48 int numberOfProfiles;
49 int numberOfDevices;
50 char deviceNames[MAX_DEVICES][128];
51 static int numberOfProfilesSave;
52
53 // This is so that new devices have something reasonable to show for default
54 uint32_t defaultMap[21] = {
55         'S', 'X', 'Z', 'C', '-','7', '4', '1', '0', '8', '5', '2', '=', '9', '6',
56         '3', 'L', 'K', 'J', 'O', 'P'
57 };
58
59
60 // Function Prototypes
61 int ConnectProfileToDevice(int deviceNum, int gamepadID = -1);
62 int FindProfileForDevice(int deviceNum, int preferred, int * found);
63
64
65 //
66 // These two functions are mainly to support the controller configuration GUI.
67 // Could just as easily go there as well (and be better placed there).
68 //
69 void SaveProfiles(void)
70 {
71         numberOfProfilesSave = numberOfProfiles;
72         memcpy(&profileBackup, &profile, sizeof(Profile) * MAX_PROFILES);
73 }
74
75
76 void RestoreProfiles(void)
77 {
78         memcpy(&profile, &profileBackup, sizeof(Profile) * MAX_PROFILES);
79         numberOfProfiles = numberOfProfilesSave;
80 }
81
82
83 void ReadProfiles(QSettings * set)
84 {
85         // Assume no profiles, until we read them
86         numberOfProfiles = 0;
87
88         // There is always at least one device present, and it's the keyboard
89         // (hey, we're PC centric here ;-)
90         numberOfDevices = 1;
91         strcpy(deviceNames[0], "Keyboard");
92
93         // Read the rest of the devices (if any)
94         numberOfDevices += set->beginReadArray("devices");
95
96         for(int i=1; i<numberOfDevices; i++)
97         {
98                 set->setArrayIndex(i - 1);
99                 strcpy(deviceNames[i], set->value("deviceName").toString().toAscii().data());
100 #ifdef DEBUG_PROFILES
101 printf("Read device name: %s\n", deviceNames[i]);
102 #endif
103         }
104
105         set->endArray();
106         numberOfProfiles = set->beginReadArray("profiles");
107 #ifdef DEBUG_PROFILES
108 printf("Number of profiles: %u\n", numberOfProfiles);
109 #endif
110
111         for(int i=0; i<numberOfProfiles; i++)
112         {
113                 set->setArrayIndex(i);
114                 profile[i].device = set->value("deviceNum").toInt();
115                 strcpy(profile[i].mapName, set->value("mapName").toString().toAscii().data());
116                 profile[i].preferredSlot = set->value("preferredSlot").toInt();
117
118                 for(int j=0; j<21; j++)
119                 {
120                         QString string = QString("map%1").arg(j);
121                         profile[i].map[j] = set->value(string).toInt();
122                 }
123 #ifdef DEBUG_PROFILES
124 printf("Profile #%u: device=%u (%s)\n", i, profile[i].device, deviceNames[profile[i].device]);
125 #endif
126         }
127
128         set->endArray();
129
130 #ifdef DEBUG_PROFILES
131 printf("Number of profiles found: %u\n", numberOfProfiles);
132 #endif
133         // Set up a reasonable default if no profiles were found
134         if (numberOfProfiles == 0)
135         {
136 #ifdef DEBUG_PROFILES
137 printf("Setting up default profile...\n");
138 #endif
139                 numberOfProfiles++;
140                 profile[0].device = 0;  // Keyboard is always device #0
141                 strcpy(profile[0].mapName, "Default");
142                 profile[0].preferredSlot = CONTROLLER1;
143
144                 for(int i=0; i<21; i++)
145                         profile[0].map[i] = defaultMap[i];
146         }
147 }
148
149
150 void WriteProfiles(QSettings * set)
151 {
152 #if 0
153         // Don't write anything for now...
154         return;
155 #endif
156         // NB: Should only do this if something changed; otherwise, no need to do
157         //     this.
158         set->beginWriteArray("devices");
159
160         for(int i=1; i<numberOfDevices; i++)
161         {
162                 set->setArrayIndex(i - 1);
163                 set->setValue("deviceName", deviceNames[i]);
164         }
165
166         set->endArray();
167         set->beginWriteArray("profiles");
168
169         for(int i=0; i<numberOfProfiles; i++)
170         {
171                 set->setArrayIndex(i);
172                 set->setValue("deviceNum", profile[i].device);
173                 set->setValue("mapName", profile[i].mapName);
174                 set->setValue("preferredSlot", profile[i].preferredSlot);
175
176                 for(int j=0; j<21; j++)
177                 {
178                         QString string = QString("map%1").arg(j);
179                         set->setValue(string, profile[i].map[j]);
180                 }
181         }
182
183         set->endArray();
184 }
185
186
187 int GetFreeProfile(void)
188 {
189         // Check for too many, return -1 if so
190         if (numberOfProfiles == MAX_PROFILES)
191                 return -1;
192
193         int profileNum = numberOfProfiles;
194         numberOfProfiles++;
195         return profileNum;
196 }
197
198
199 void DeleteProfile(int profileToDelete)
200 {
201         // Sanity check
202         if (profileToDelete >= numberOfProfiles)
203                 return;
204
205         // Trivial case: Profile at end of the array
206         if (profileToDelete == (numberOfProfiles - 1))
207         {
208                 numberOfProfiles--;
209                 return;
210         }
211
212 //      memmove(dest, src, bytesToMove);
213         memmove(&profile[profileToDelete], &profile[profileToDelete + 1], ((numberOfProfiles - 1) - profileToDelete) * sizeof(Profile));
214         numberOfProfiles--;
215 }
216
217
218 int FindDeviceNumberForName(const char * name)
219 {
220         for(int i=0; i<numberOfDevices; i++)
221         {
222                 if (strcmp(deviceNames[i], name) == 0)
223 #ifdef DEBUG_PROFILES
224 {
225 printf("PROFILE: Found device #%i for name (%s)...\n", i, name);
226 #endif
227                         return i;
228 #ifdef DEBUG_PROFILES
229 }
230 #endif
231         }
232
233         if (numberOfDevices == MAX_DEVICES)
234                 return -1;
235
236 #ifdef DEBUG_PROFILES
237 printf("Device '%s' not found, creating device...\n", name);
238 #endif
239         // If the device wasn't found, it must be new; so add it to the list.
240         int deviceNum = numberOfDevices;
241         deviceNames[deviceNum][127] = 0;
242         strncpy(deviceNames[deviceNum], name, 127);
243         numberOfDevices++;
244
245         return deviceNum;
246 }
247
248
249 int FindMappingsForDevice(int deviceNum, QComboBox * combo)
250 {
251         int found = 0;
252
253         for(int i=0; i<numberOfProfiles; i++)
254         {
255 //This should *never* be the case--all profiles in list are *good*
256 //              if (profile[i].device == -1)
257 //                      continue;
258
259                 if (profile[i].device == deviceNum)
260                 {
261                         combo->addItem(profile[i].mapName, i);
262                         found++;
263                 }
264         }
265
266         // If no mappings were found, create a default one for it
267         if (found == 0)
268         {
269                 profile[numberOfProfiles].device = deviceNum;
270                 strcpy(profile[numberOfProfiles].mapName, "Default");
271                 profile[numberOfProfiles].preferredSlot = CONTROLLER1;
272
273                 for(int i=0; i<21; i++)
274                         profile[numberOfProfiles].map[i] = defaultMap[i];
275
276                 combo->addItem(profile[numberOfProfiles].mapName, numberOfProfiles);
277                 numberOfProfiles++;
278                 found++;
279         }
280
281         return found;
282 }
283
284
285 // N.B.: Unused
286 int FindUsableProfiles(QComboBox * combo)
287 {
288         int found = 0;
289
290         // Check for device #0 (keyboard) profiles first
291         for(int j=0; j<numberOfProfiles; j++)
292         {
293                 // Check for device *and* usable configuration
294                 if ((profile[j].device == 0) && (profile[j].preferredSlot))
295                 {
296                         combo->addItem(QString("Keyboard::%1").arg(profile[j].mapName), j);
297                         found++;
298                 }
299         }
300
301         // Check for connected host devices next
302         for(int i=0; i<Gamepad::numJoysticks; i++)
303         {
304                 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
305
306                 for(int j=0; j<numberOfProfiles; j++)
307                 {
308                         if ((profile[j].device == deviceNum) && (profile[j].preferredSlot))
309                         {
310                                 combo->addItem(QString("%1::%2").arg(Gamepad::GetJoystickName(i)).arg(profile[j].mapName), j);
311                                 found++;
312                         }
313                 }
314         }
315
316         return found;
317 }
318
319
320 bool ConnectProfileToController(int profileNum, int controllerNum)
321 {
322         // Sanity checks...
323         if (profileNum < 0)
324                 return false;
325
326         if (profile[profileNum].device == -1)
327                 return false;
328
329         if (controllerNum < 0 || controllerNum > 2)
330                 return false;
331
332         uint32_t * dest = (controllerNum == 0 ? &vjs.p1KeyBindings[0] : &vjs.p2KeyBindings[0]);
333
334         for(int i=0; i<21; i++)
335                 dest[i] = profile[profileNum].map[i];
336
337         WriteLog("PROFILE: Successfully mapped device '%s' (%s) to controller #%u...\n", deviceNames[profile[profileNum].device], profile[profileNum].mapName, controllerNum);
338         return true;
339 }
340
341
342 /*
343 One more stab at this...
344
345  -  Connect keyboard to slot #0.
346  -  Loop thru all connected devices. For each device:
347     -  Grab all profiles for the device. For each profile:
348        -  Check to see what its preferred device is.
349        -  If PD is slot #0, see if slot is already taken (gamepadIDSlot1 != -1).
350           If not taken, take it; otherwise put in list to tell user to solve the
351           conflict for us.
352           -  If the slot is already taken and *it's the same device* as the one
353              we're looking at, set it in slot #1.
354        -  If PD is slot #1, see if slot is already taken. If not, take it;
355           otherwise, put in list to tell user to solve conflict for us.
356        -  If PD is slot #0 & #1, see if either is already taken. Try #0 first,
357           then try #1. If both are already taken, skip it. Do this *after* we've
358           connected devices with preferred slots.
359 */
360 void AutoConnectProfiles(void)
361 {
362 //      int foundProfiles[MAX_PROFILES];
363         controller1Profile = -1;
364         controller2Profile = -1;
365         gamepadIDSlot1 = -1;
366         gamepadIDSlot2 = -1;
367
368         // Connect the keyboard automagically only if no gamepads are plugged in.
369         // Otherwise, check after all other devices have been checked, then try to
370         // add it in.
371         if (Gamepad::numJoysticks == 0)
372         {
373                 ConnectProfileToDevice(0);
374                 return;
375         }
376
377         // Connect the profiles that prefer a slot, if any.
378         // N.B.: Conflicts are detected, but ignored. 1st controller to grab a
379         //       preferred slot gets it. :-P
380         for(int i=0; i<Gamepad::numJoysticks; i++)
381         {
382                 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
383 //              bool p1Overwriteable = 
384
385                 for(int j=0; j<numberOfProfiles; j++)
386                 {
387                         if (deviceNum != profile[j].device)
388                                 continue;
389
390                         int slot = profile[j].preferredSlot;
391
392                         if (slot == CONTROLLER1)
393                         {
394                                 if (gamepadIDSlot1 == -1)
395                                         controller1Profile = j, gamepadIDSlot1 = i;
396                                 else
397                                 {
398                                         // Autoresolve simple conflict: two controllers sharing one
399                                         // profile mapped to slot #0.
400                                         if ((deviceNum == profile[controller1Profile].device) && (controller2Profile == -1))
401                                                 controller2Profile = j, gamepadIDSlot2 = i;
402                                         else
403                                                 ; // Alert user to conflict and ask to resolve
404                                 }
405                         }
406                         else if (slot == CONTROLLER2)
407                         {
408                                 if (gamepadIDSlot2 == -1)
409                                         controller2Profile = j, gamepadIDSlot2 = i;
410                                 else
411                                 {
412                                         // Autoresolve simple conflict: two controllers sharing one
413                                         // profile mapped to slot #1.
414                                         if ((deviceNum == profile[controller2Profile].device) && (controller1Profile == -1))
415                                                 controller1Profile = j, gamepadIDSlot1 = i;
416                                         else
417                                                 ; // Alert user to conflict and ask to resolve
418                                 }
419                         }
420                 }
421         }
422
423         // Connect the "don't care" states, if any. We don't roll it into the above,
424         // because it can override the profiles that have a definite preference.
425         // These should be lowest priority.
426         for(int i=0; i<Gamepad::numJoysticks; i++)
427         {
428                 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
429
430                 for(int j=0; j<numberOfProfiles; j++)
431                 {
432                         if (deviceNum != profile[j].device)
433                                 continue;
434
435                         int slot = profile[j].preferredSlot;
436
437                         if (slot == (CONTROLLER1 | CONTROLLER2))
438                         {
439                                 if (gamepadIDSlot1 == -1)
440                                         controller1Profile = j, gamepadIDSlot1 = i;
441                                 else if (gamepadIDSlot2 == -1)
442                                         controller2Profile = j, gamepadIDSlot2 = i;
443                         }
444                 }
445         }
446
447         // Connect the keyboard device (lowest priority)
448         // N.B.: The keyboard is always mapped to profile #0, so we can locate it
449         //       easily. :-)
450         int slot = profile[0].preferredSlot;
451
452         if ((slot == CONTROLLER1) && (gamepadIDSlot1 == -1))
453                 controller1Profile = 0;
454         else if ((slot == CONTROLLER2) && (gamepadIDSlot2 == -1))
455                 controller2Profile = 0;
456         else if (slot == (CONTROLLER1 | CONTROLLER2))
457         {
458                 if (gamepadIDSlot1 == -1)
459                         controller1Profile = 0;
460                 else if (gamepadIDSlot2 == -1)
461                         controller2Profile = 0;
462         }
463
464         // Finally, attempt to connect profiles to controllers
465         ConnectProfileToController(controller1Profile, 0);
466         ConnectProfileToController(controller2Profile, 1);
467 }
468
469
470 int ConnectProfileToDevice(int deviceNum, int gamepadID/*= -1*/)
471 {
472 //      bool found1 = false;
473 //      bool found2 = false;
474         int numberFoundForController1 = 0;
475         int numberFoundForController2 = 0;
476
477         for(int i=0; i<numberOfProfiles; i++)
478         {
479                 // Skip profile if it's not our device
480                 if (profile[i].device != deviceNum)
481                         continue;
482
483                 if (profile[i].preferredSlot & CONTROLLER1)
484                 {
485                         controller1Profile = i;
486                         gamepadIDSlot1 = gamepadID;
487 //                      found1 = true;
488                         numberFoundForController1++;
489                 }
490
491                 if (profile[i].preferredSlot & CONTROLLER2)
492                 {
493                         controller2Profile = i;
494                         gamepadIDSlot2 = gamepadID;
495 //                      found2 = true;
496                         numberFoundForController2++;
497                 }
498         }
499
500 //      return found;
501         return numberFoundForController1 + numberFoundForController2;
502 }
503
504
505 // N.B.: Unused
506 int FindProfileForDevice(int deviceNum, int preferred, int * found)
507 {
508         int numFound = 0;
509
510         for(int i=0; i<numberOfProfiles; i++)
511         {
512                 // Return the profile only if it matches the passed in device and
513                 // matches the passed in preference...
514                 if ((profile[i].device == deviceNum) && (profile[i].preferredSlot == preferred))
515                         found[numFound++] = i;
516         }
517
518         return numFound;
519 }
520
521
522 //
523 // Also note that we have the intersection of three things here: One the one
524 // hand, we have the detected joysticks with their IDs (typically in the range
525 // of 0-7), we have our gamepad profiles and their IDs (typically can have up to
526 // 64 of them), and we have our gamepad slots that the detected joysticks can be
527 // connected to.
528 //
529 // So, when the user plugs in a gamepad, it gets a joystick ID, then the profile
530 // manager checks to see if a profile (or profiles) for it exists. If so, then
531 // it assigns that joystick ID to a gamepad slot, based upon what the user
532 // requested for that profile.
533 //
534 // A problem (perhaps) arises when you have more than one profile for a certain
535 // device, how do you know which one to use? Perhaps you have a field in the
536 // profile saying that you use this profile 1st, that one 2nd, and so on...
537 //
538 // Some use cases, and how to resolve them:
539 //
540 // - User has two of the same device, and plugs them both in. There is only one
541 //   profile. In this case, the sane thing to do is ignore the "preferred slot"
542 //   of the dialog and use the same profile for both controllers, and plug them
543 //   both into slot #0 and #1.
544 // - User has one device, and plugs it in. There are two profiles. In this case,
545 //   the profile chosen should be based upon the "preferred slot", with slot #0
546 //   being the winner. If both profiles are set for slot #0, ask the user which
547 //   profile to use, and set a flag in the profile to say that it is a preferred
548 //   profile for that device.
549 // - In any case where there are conflicts, the user must be consulted and sane
550 //   defaults used.
551 //