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