Document updates for the R3 release
[clinton/Virtual-Jaguar-Rx.git] / src / gui / profile.cpp
CommitLineData
cf76e892
JPM
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
45Profile profile[MAX_PROFILES];
46Profile profileBackup[MAX_PROFILES];
47int controller1Profile;
48int controller2Profile;
49int gamepadIDSlot1;
50int gamepadIDSlot2;
51int numberOfProfiles;
52int numberOfDevices;
53char deviceNames[MAX_DEVICES][128];
54static int numberOfProfilesSave;
55
56// This is so that new devices have something reasonable to show for default
57uint32_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
64int ConnectProfileToDevice(int deviceNum, int gamepadID = -1);
65int 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//
72void SaveProfiles(void)
73{
74 numberOfProfilesSave = numberOfProfiles;
75 memcpy(&profileBackup, &profile, sizeof(Profile) * MAX_PROFILES);
76}
77
78
79void RestoreProfiles(void)
80{
81 memcpy(&profile, &profileBackup, sizeof(Profile) * MAX_PROFILES);
82 numberOfProfiles = numberOfProfilesSave;
83}
84
85
86void 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
104printf("Read device name: %s\n", deviceNames[i]);
105#endif
106 }
107
108 set->endArray();
109 numberOfProfiles = set->beginReadArray("profiles");
110#ifdef DEBUG_PROFILES
111printf("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
127printf("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
134printf("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
140printf("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
152
153void WriteProfiles(QSettings * set)
154{
155#if 0
156 // Don't write anything for now...
157 return;
158#endif
159 // NB: Should only do this if something changed; otherwise, no need to do
160 // this.
161 set->beginWriteArray("devices");
162
163 for(int i=1; i<numberOfDevices; i++)
164 {
165 set->setArrayIndex(i - 1);
166 set->setValue("deviceName", deviceNames[i]);
167 }
168
169 set->endArray();
170 set->beginWriteArray("profiles");
171
172 for(int i=0; i<numberOfProfiles; i++)
173 {
174 set->setArrayIndex(i);
175 set->setValue("deviceNum", profile[i].device);
176 set->setValue("mapName", profile[i].mapName);
177 set->setValue("preferredSlot", profile[i].preferredSlot);
178
179 for(int j=0; j<21; j++)
180 {
181 QString string = QString("map%1").arg(j);
182 set->setValue(string, profile[i].map[j]);
183 }
184 }
185
186 set->endArray();
187}
188
189
190int GetFreeProfile(void)
191{
192 // Check for too many, return -1 if so
193 if (numberOfProfiles == MAX_PROFILES)
194 return -1;
195
196 int profileNum = numberOfProfiles;
197 numberOfProfiles++;
198 return profileNum;
199}
200
201
202void DeleteProfile(int profileToDelete)
203{
204 // Sanity check
205 if (profileToDelete >= numberOfProfiles)
206 return;
207
208 // Trivial case: Profile at end of the array
209 if (profileToDelete == (numberOfProfiles - 1))
210 {
211 numberOfProfiles--;
212 return;
213 }
214
215// memmove(dest, src, bytesToMove);
216 memmove(&profile[profileToDelete], &profile[profileToDelete + 1], ((numberOfProfiles - 1) - profileToDelete) * sizeof(Profile));
217 numberOfProfiles--;
218}
219
220
221int FindDeviceNumberForName(const char * name)
222{
223 for(int i=0; i<numberOfDevices; i++)
224 {
225 if (strcmp(deviceNames[i], name) == 0)
226#ifdef DEBUG_PROFILES
227{
228printf("PROFILE: Found device #%i for name (%s)...\n", i, name);
229#endif
230 return i;
231#ifdef DEBUG_PROFILES
232}
233#endif
234 }
235
236 if (numberOfDevices == MAX_DEVICES)
237 return -1;
238
239#ifdef DEBUG_PROFILES
240printf("Device '%s' not found, creating device...\n", name);
241#endif
242 // If the device wasn't found, it must be new; so add it to the list.
243 int deviceNum = numberOfDevices;
244 deviceNames[deviceNum][127] = 0;
245 strncpy(deviceNames[deviceNum], name, 127);
246 numberOfDevices++;
247
248 return deviceNum;
249}
250
251
252int FindMappingsForDevice(int deviceNum, QComboBox * combo)
253{
254 int found = 0;
255
256 for(int i=0; i<numberOfProfiles; i++)
257 {
258//This should *never* be the case--all profiles in list are *good*
259// if (profile[i].device == -1)
260// continue;
261
262 if (profile[i].device == deviceNum)
263 {
264 combo->addItem(profile[i].mapName, i);
265 found++;
266 }
267 }
268
269 // If no mappings were found, create a default one for it
270 if (found == 0)
271 {
272 profile[numberOfProfiles].device = deviceNum;
273 strcpy(profile[numberOfProfiles].mapName, "Default");
274 profile[numberOfProfiles].preferredSlot = CONTROLLER1;
275
276 for(int i=0; i<21; i++)
277 profile[numberOfProfiles].map[i] = defaultMap[i];
278
279 combo->addItem(profile[numberOfProfiles].mapName, numberOfProfiles);
280 numberOfProfiles++;
281 found++;
282 }
283
284 return found;
285}
286
287
288// N.B.: Unused
289int FindUsableProfiles(QComboBox * combo)
290{
291 int found = 0;
292
293 // Check for device #0 (keyboard) profiles first
294 for(int j=0; j<numberOfProfiles; j++)
295 {
296 // Check for device *and* usable configuration
297 if ((profile[j].device == 0) && (profile[j].preferredSlot))
298 {
299 combo->addItem(QString("Keyboard::%1").arg(profile[j].mapName), j);
300 found++;
301 }
302 }
303
304 // Check for connected host devices next
305 for(int i=0; i<Gamepad::numJoysticks; i++)
306 {
307 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
308
309 for(int j=0; j<numberOfProfiles; j++)
310 {
311 if ((profile[j].device == deviceNum) && (profile[j].preferredSlot))
312 {
313 combo->addItem(QString("%1::%2").arg(Gamepad::GetJoystickName(i)).arg(profile[j].mapName), j);
314 found++;
315 }
316 }
317 }
318
319 return found;
320}
321
322
323bool ConnectProfileToController(int profileNum, int controllerNum)
324{
325 // Sanity checks...
326 if (profileNum < 0)
327 return false;
328
329 if (profile[profileNum].device == -1)
330 return false;
331
332 if (controllerNum < 0 || controllerNum > 2)
333 return false;
334
335 uint32_t * dest = (controllerNum == 0 ? &vjs.p1KeyBindings[0] : &vjs.p2KeyBindings[0]);
336
337 for(int i=0; i<21; i++)
338 dest[i] = profile[profileNum].map[i];
339
340 WriteLog("PROFILE: Successfully mapped device '%s' (%s) to controller #%u...\n", deviceNames[profile[profileNum].device], profile[profileNum].mapName, controllerNum);
341 return true;
342}
343
344
345/*
346One more stab at this...
347
348 - Connect keyboard to slot #0.
349 - Loop thru all connected devices. For each device:
350 - Grab all profiles for the device. For each profile:
351 - Check to see what its preferred device is.
352 - If PD is slot #0, see if slot is already taken (gamepadIDSlot1 != -1).
353 If not taken, take it; otherwise put in list to tell user to solve the
354 conflict for us.
355 - If the slot is already taken and *it's the same device* as the one
356 we're looking at, set it in slot #1.
357 - If PD is slot #1, see if slot is already taken. If not, take it;
358 otherwise, put in list to tell user to solve conflict for us.
359 - If PD is slot #0 & #1, see if either is already taken. Try #0 first,
360 then try #1. If both are already taken, skip it. Do this *after* we've
361 connected devices with preferred slots.
362*/
363void AutoConnectProfiles(void)
364{
365// int foundProfiles[MAX_PROFILES];
366 controller1Profile = -1;
367 controller2Profile = -1;
368 gamepadIDSlot1 = -1;
369 gamepadIDSlot2 = -1;
370
371 // Connect the keyboard automagically only if no gamepads are plugged in.
372 // Otherwise, check after all other devices have been checked, then try to
373 // add it in.
374 if (Gamepad::numJoysticks == 0)
375 {
376#ifdef DEBUG_PROFILES
377printf("AutoConnect: Setting up keyboard...\n");
378#endif
379//NO! ConnectProfileToDevice(0);
380#ifdef _MSC_VER
381#pragma message("Warning: !!! Need to set up scanning for multiple keyboard profiles !!!")
382#else
383#warning "!!! Need to set up scanning for multiple keyboard profiles !!!"
384#endif // _MSC_VER
385 ConnectProfileToController(0, 0);
386 return;
387 }
388
389 // Connect the profiles that prefer a slot, if any.
390 // N.B.: Conflicts are detected, but ignored. 1st controller to grab a
391 // preferred slot gets it. :-P
392 for(int i=0; i<Gamepad::numJoysticks; i++)
393 {
394 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
395// bool p1Overwriteable =
396#ifdef DEBUG_PROFILES
397printf("AutoConnect: Attempting to set up profile for device '%s' (%i)\n", Gamepad::GetJoystickName(i), deviceNum);
398#endif
399
400 for(int j=0; j<numberOfProfiles; j++)
401 {
402 if (deviceNum != profile[j].device)
403 continue;
404
405 int slot = profile[j].preferredSlot;
406
407 if (slot == CONTROLLER1)
408 {
409 if (gamepadIDSlot1 == -1)
410 controller1Profile = j, gamepadIDSlot1 = i;
411 else
412 {
413 // Autoresolve simple conflict: two controllers sharing one
414 // profile mapped to slot #0.
415 if ((deviceNum == profile[controller1Profile].device) && (controller2Profile == -1))
416 controller2Profile = j, gamepadIDSlot2 = i;
417 else
418 ; // Alert user to conflict and ask to resolve
419 }
420 }
421 else if (slot == CONTROLLER2)
422 {
423 if (gamepadIDSlot2 == -1)
424 controller2Profile = j, gamepadIDSlot2 = i;
425 else
426 {
427 // Autoresolve simple conflict: two controllers sharing one
428 // profile mapped to slot #1.
429 if ((deviceNum == profile[controller2Profile].device) && (controller1Profile == -1))
430 controller1Profile = j, gamepadIDSlot1 = i;
431 else
432 ; // Alert user to conflict and ask to resolve
433 }
434 }
435 }
436 }
437
438 // Connect the "don't care" states, if any. We don't roll it into the above,
439 // because it can override the profiles that have a definite preference.
440 // These should be lowest priority.
441 for(int i=0; i<Gamepad::numJoysticks; i++)
442 {
443 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
444
445 for(int j=0; j<numberOfProfiles; j++)
446 {
447 if (deviceNum != profile[j].device)
448 continue;
449
450 int slot = profile[j].preferredSlot;
451
452 if (slot == (CONTROLLER1 | CONTROLLER2))
453 {
454 if (gamepadIDSlot1 == -1)
455 controller1Profile = j, gamepadIDSlot1 = i;
456 else if (gamepadIDSlot2 == -1)
457 controller2Profile = j, gamepadIDSlot2 = i;
458 }
459 }
460 }
461
462 // Connect the keyboard device (lowest priority)
463 // N.B.: The keyboard is always mapped to profile #0, so we can locate it
464 // easily. :-)
465 int slot = profile[0].preferredSlot;
466#ifdef DEBUG_PROFILES
467printf("AutoConnect: Attempting to connect keyboard... (gamepadIDSlot1/2 = %i/%i)\n", gamepadIDSlot1, gamepadIDSlot2);
468#endif
469
470 if ((slot == CONTROLLER1) && (gamepadIDSlot1 == -1))
471 controller1Profile = 0;
472 else if ((slot == CONTROLLER2) && (gamepadIDSlot2 == -1))
473 controller2Profile = 0;
474 else if (slot == (CONTROLLER1 | CONTROLLER2))
475 {
476 if (gamepadIDSlot1 == -1)
477 controller1Profile = 0;
478 else if (gamepadIDSlot2 == -1)
479 controller2Profile = 0;
480 }
481
482#ifdef DEBUG_PROFILES
483printf("AutoConnect: Profiles found: [%i, %i]\n", controller1Profile, controller2Profile);
484#endif
485 // Finally, attempt to connect profiles to controllers
486 ConnectProfileToController(controller1Profile, 0);
487 ConnectProfileToController(controller2Profile, 1);
488}
489
490
491//unused...
492int ConnectProfileToDevice(int deviceNum, int gamepadID/*= -1*/)
493{
494// bool found1 = false;
495// bool found2 = false;
496 int numberFoundForController1 = 0;
497 int numberFoundForController2 = 0;
498
499 for(int i=0; i<numberOfProfiles; i++)
500 {
501 // Skip profile if it's not our device
502 if (profile[i].device != deviceNum)
503 continue;
504
505 if (profile[i].preferredSlot & CONTROLLER1)
506 {
507 controller1Profile = i;
508 gamepadIDSlot1 = gamepadID;
509// found1 = true;
510 numberFoundForController1++;
511 }
512
513 if (profile[i].preferredSlot & CONTROLLER2)
514 {
515 controller2Profile = i;
516 gamepadIDSlot2 = gamepadID;
517// found2 = true;
518 numberFoundForController2++;
519 }
520 }
521
522// return found;
523 return numberFoundForController1 + numberFoundForController2;
524}
525
526
527// N.B.: Unused
528int FindProfileForDevice(int deviceNum, int preferred, int * found)
529{
530 int numFound = 0;
531
532 for(int i=0; i<numberOfProfiles; i++)
533 {
534 // Return the profile only if it matches the passed in device and
535 // matches the passed in preference...
536 if ((profile[i].device == deviceNum) && (profile[i].preferredSlot == preferred))
537 found[numFound++] = i;
538 }
539
540 return numFound;
541}
542
543
544//
545// Also note that we have the intersection of three things here: One the one
546// hand, we have the detected joysticks with their IDs (typically in the range
547// of 0-7), we have our gamepad profiles and their IDs (typically can have up to
548// 64 of them), and we have our gamepad slots that the detected joysticks can be
549// connected to.
550//
551// So, when the user plugs in a gamepad, it gets a joystick ID, then the profile
552// manager checks to see if a profile (or profiles) for it exists. If so, then
553// it assigns that joystick ID to a gamepad slot, based upon what the user
554// requested for that profile.
555//
556// A problem (perhaps) arises when you have more than one profile for a certain
557// device, how do you know which one to use? Perhaps you have a field in the
558// profile saying that you use this profile 1st, that one 2nd, and so on...
559//
560// Some use cases, and how to resolve them:
561//
562// - User has two of the same device, and plugs them both in. There is only one
563// profile. In this case, the sane thing to do is ignore the "preferred slot"
564// of the dialog and use the same profile for both controllers, and plug them
565// both into slot #0 and #1.
566// - User has one device, and plugs it in. There are two profiles. In this case,
567// the profile chosen should be based upon the "preferred slot", with slot #0
568// being the winner. If both profiles are set for slot #0, ask the user which
569// profile to use, and set a flag in the profile to say that it is a preferred
570// profile for that device.
571// - In any case where there are conflicts, the user must be consulted and sane
572// defaults used.
573//