]> Shamusworld >> Repos - virtualjaguar/blob - src/gui/controllertab.cpp
bc8274f3dd3ccd588693eb4bf8c362978357052f
[virtualjaguar] / src / gui / controllertab.cpp
1 //
2 // controllertab.cpp: "Controller" tab on the config dialog
3 //
4 // Part of the Virtual Jaguar Project
5 // (C) 2011 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
7 //
8 // JLH = James Hammons <jlhamm@acm.org>
9 //
10 // WHO  WHEN        WHAT
11 // ---  ----------  ------------------------------------------------------------
12 // JLH  06/23/2011  Created this file
13 // JLH  07/20/2011  Fixed a bunch of stuff
14 //
15
16 #include "controllertab.h"
17
18 #include "controllerwidget.h"
19 #include "gamepad.h"
20 #include "joystick.h"
21 #include "keygrabber.h"
22 #include "profile.h"
23
24
25 ControllerTab::ControllerTab(QWidget * parent/*= 0*/): QWidget(parent),
26         label1(new QLabel(tr("Host Device:"))),
27         label2(new QLabel(tr("Map Name:"))),
28         label3(new QLabel(tr("Maps to:"))),
29         deviceList(new QComboBox(this)),
30         mapNameList(new QComboBox(this)),
31         mapToList(new QComboBox(this)),
32         addMapName(new QPushButton(tr("+"))),
33         deleteMapName(new QPushButton(tr("-"))),
34         redefineAll(new QPushButton(tr("Define All Inputs"))),
35         controllerWidget(new ControllerWidget(this))
36 {
37         QVBoxLayout * layout = new QVBoxLayout;
38         QHBoxLayout * top = new QHBoxLayout;
39         QVBoxLayout * left = new QVBoxLayout;
40         QVBoxLayout * right = new QVBoxLayout;
41         QHBoxLayout * middle = new QHBoxLayout;
42         top->addLayout(left, 0);
43         top->addLayout(right, 1);
44         layout->addLayout(top);
45         left->addWidget(label1, 0, Qt::AlignRight);
46         left->addWidget(label2, 0, Qt::AlignRight);
47         left->addWidget(label3, 0, Qt::AlignRight);
48         right->addWidget(deviceList);
49
50         right->addLayout(middle);
51         middle->addWidget(mapNameList, 1);
52         middle->addWidget(addMapName, 0);
53         middle->addWidget(deleteMapName, 0);
54
55         right->addWidget(mapToList);
56         layout->addWidget(controllerWidget);
57         layout->addWidget(redefineAll, 0, Qt::AlignHCenter);
58         setLayout(layout);
59         // At least by doing this, it keeps the QComboBox from resizing itself too
60         // large and breaking the layout. :-P
61         setFixedWidth(sizeHint().width());
62
63         connect(redefineAll, SIGNAL(clicked()), this, SLOT(DefineAllKeys()));
64         connect(deviceList, SIGNAL(activaed(int)), this, SLOT(ChangeDevice(int)));
65         connect(mapNameList, SIGNAL(activated(int)), this, SLOT(ChangeMapName(int)));
66         connect(addMapName, SIGNAL(clicked()), this, SLOT(AddMapName()));
67         connect(deleteMapName, SIGNAL(clicked()), this, SLOT(DeleteMapName()));
68         connect(controllerWidget, SIGNAL(KeyDefined(int, uint32_t)), this, SLOT(UpdateProfileKeys(int, uint32_t)));
69         connect(mapToList, SIGNAL(activated(int)), this, SLOT(UpdateProfileConnections(int)));
70
71         // Set up the device combobox (Keyboard is the default, and always
72         // present)
73         deviceList->addItem(tr("Keyboard"), 0);
74
75         for(int i=0; i<Gamepad::numJoysticks; i++)
76         {
77                 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
78                 deviceList->addItem(Gamepad::GetJoystickName(i), deviceNum);
79         }
80
81         // Set up "Map To" combobox
82         mapToList->addItem(tr("None"), 0);
83         mapToList->addItem(tr("Controller #1"), CONTROLLER1);
84         mapToList->addItem(tr("Controller #2"), CONTROLLER2);
85         mapToList->addItem(tr("Either one that's free"), CONTROLLER1 | CONTROLLER2);
86 }
87 /*
88 So now we come to implementation. When changing devices, could have a helper function
89 in profile.cpp that fills the mapNameList combobox with the appropriate names/profile
90 numbers.
91
92 There needs to be some way of getting data from the ControllerWidget and the current
93 profile.
94
95 Gamepad will have to have some way of knowing which profile is mapped to which
96 Jaguar controllers and filtering out everything else.
97
98 Will have to have some intelligent handling of profiles when first run, to see first
99 what is connected and second, to assign profiles to Jaguar controllers. In this
100 case, keyboard is the lowest priority--if a controller is plugged in and assigned to
101 the same Jaguar controller as a keyboard, the controller is used. Not sure what to
102 do in the case of multiple controllers plugged in and assigned to the same Jaguar
103 controller.
104
105 Also, need a way to load/save profiles.
106
107 Meaning of checkboxes: None checked == profile not used.
108 1 checked == prefer connection to Jaguar controller X.
109 2 checked == no preference, use any available.
110
111 Single mapping cannot be deleted ("-" will be disabled). Can always add, up to the max
112 limit of profiles (MAX_PROFILES).
113
114 ------------------------------
115
116 Now the main window passes in/removes the last edited profile #. From here, when starting
117 up, we need to pull that number from the profile store and populate all our boxes.
118
119 -------------------------------
120
121 Need to do AutoConnectProfiles from here, and detect any conflicts
122 */
123
124
125 ControllerTab::~ControllerTab()
126 {
127 }
128
129
130 void ControllerTab::SetupLastUsedProfile(void)
131 {
132         int deviceNumIndex = deviceList->findData(profile[profileNum].device);
133         int mapNumIndex = mapNameList->findText(profile[profileNum].mapName);
134
135         if (deviceNumIndex == -1 || mapNumIndex == -1)
136         {
137                 // We're doing the default, so set it up...
138 //              mapToList->setCurrentIndex(mapToList->findData(profile[0].preferredController));
139 //printf("ControllerTab::SetupLastUsedProfile: [FAILED] profileNum=%i, controllerIndex=%i, preferredController=%i\n", profileNum, controllerIndex, profile[0].preferredController);
140 //              return;
141                 deviceNumIndex = 0;
142                 mapNumIndex = 0;
143                 profileNum = 0;
144         }
145
146         deviceList->setCurrentIndex(deviceNumIndex);
147         mapNameList->setCurrentIndex(mapNumIndex);
148 //no more: #warning "!!! bug in here where it doesn't save your preferred controller !!!"
149
150         int controllerIndex = mapToList->findData(profile[profileNum].preferredController);
151         mapToList->setCurrentIndex(controllerIndex);
152 //printf("ControllerTab::SetupLastUsedProfile: profileNum=%i, controllerIndex=%i, preferredController=%i\n", profileNum, controllerIndex, profile[profileNum].preferredController);
153
154         // We have to do this manually, since it's no longer done automagically...
155         ChangeDevice(deviceNumIndex);
156         ChangeMapName(mapNumIndex);
157 }
158
159
160 void ControllerTab::DefineAllKeys(void)
161 {
162 //      char jagButtonName[21][10] = { "Up", "Down", "Left", "Right",
163 //              "*", "7", "4", "1", "0", "8", "5", "2", "#", "9", "6", "3",
164 //              "A", "B", "C", "Option", "Pause" };
165         int orderToDefine[21] = { 0, 1, 2, 3, 18, 17, 16, 20, 19, 7, 11, 15, 6, 10, 14, 5, 9, 13, 8, 4, 12 };
166         KeyGrabber keyGrab(this);
167
168         for(int i=BUTTON_FIRST; i<=BUTTON_LAST; i++)
169         {
170                 keyGrab.SetKeyText(orderToDefine[i]);
171                 keyGrab.exec();
172                 int key = keyGrab.key;
173
174                 if (key == Qt::Key_Escape)
175                         break;
176
177                 // Otherwise, populate the appropriate spot in the settings & update screen...
178                 controllerWidget->keys[orderToDefine[i]] = key;
179                 controllerWidget->update();
180                 profile[profileNum].map[orderToDefine[i]] = key;
181         }
182 }
183
184
185 void ControllerTab::UpdateProfileKeys(int mapPosition, uint32_t key)
186 {
187         profile[profileNum].map[mapPosition] = key;
188 }
189
190
191 void ControllerTab::UpdateProfileConnections(int selection)
192 {
193 //      profile[profileNum].preferredController = (controller1->isChecked() ? CONTROLLER1 : 0) | (controller2->isChecked() ? CONTROLLER2 : 0);
194         profile[profileNum].preferredController = mapToList->itemData(selection).toInt();
195 //printf("Setting profile #%i 'Maps To' to %i...\n", profileNum, mapToList->itemData(selection).toInt());
196 }
197
198
199 void ControllerTab::ChangeDevice(int selection)
200 {
201 //printf("ControllerTab::ChangeDevice\n");
202         int deviceNum = deviceList->itemData(selection).toInt();
203         mapNameList->clear();
204         int numberOfMappings = FindMappingsForDevice(deviceNum, mapNameList);
205         deleteMapName->setDisabled(numberOfMappings == 1 ? true : false);
206 //printf("Found %i mappings for device #%u...\n", numberOfMappings, deviceNum);
207 }
208
209
210 void ControllerTab::ChangeMapName(int selection)
211 {
212 //printf("ControllerTab::ChangeMapName\n");
213         profileNum = mapNameList->itemData(selection).toInt();
214 //printf("You selected mapping: %s (profile #%u)\n", (mapNameList->itemText(selection)).toAscii().data(), profileNum);
215
216         for(int i=BUTTON_FIRST; i<=BUTTON_LAST; i++)
217                 controllerWidget->keys[i] = profile[profileNum].map[i];
218
219         controllerWidget->update();
220 //      controller1->setChecked(profile[profileNum].preferredController & CONTROLLER1);
221 //      controller2->setChecked(profile[profileNum].preferredController & CONTROLLER2);
222         mapToList->setCurrentIndex(mapToList->findData(profile[profileNum].preferredController));
223 }
224
225
226 void ControllerTab::AddMapName(void)
227 {
228         int freeProfile = GetFreeProfile();
229
230         if (freeProfile == -1)
231         {
232                 // Oh crap, we're out of room! Alert the media!
233                 // (Really, tho, we should pop this up *before* asking for user input.
234                 // Which we now do!)
235 #if 0
236                 QMessageBox msg;
237                 msg.setText(QString(tr("Can't create any more profiles!")));
238                 msg.setIcon(QMessageBox::Warning);
239                 msg.exec();
240 #else
241                 QMessageBox::warning(this, tr("Houston, we have a problem..."), tr("Can't create any more profiles!"));
242 #endif
243
244                 return;
245         }
246
247         QString text = QInputDialog::getText(this, tr("Add Map Name"), tr("Map name:"), QLineEdit::Normal);
248
249         if (text.isEmpty())
250                 return;
251
252         // Add mapping...
253         profileNum = freeProfile;
254         profile[profileNum].device = deviceList->itemData(deviceList->currentIndex()).toInt();
255         strncpy(profile[profileNum].mapName, text.toAscii().data(), 31);
256         profile[profileNum].mapName[31] = 0;
257         profile[profileNum].preferredController = CONTROLLER1;
258
259         for(int i=BUTTON_FIRST; i<BUTTON_LAST; i++)
260                 profile[profileNum].map[i] = '*';
261
262         mapNameList->addItem(text, profileNum);
263         mapNameList->setCurrentIndex(mapNameList->count() - 1);
264 }
265
266
267 void ControllerTab::DeleteMapName(void)
268 {
269 //printf("Delete current mapping (TODO)...\n");
270
271         // hmm, don't need to check this... Because presumably, it's already been checked for.
272 //      if (mapNameList->count() == 1) ;
273
274         QMessageBox::StandardButton retVal = QMessageBox::question(this, tr("Remove Mapping"), tr("Are you sure you want to remove this mapping?"), QMessageBox::No | QMessageBox::Yes, QMessageBox::No);
275
276         if (retVal == QMessageBox::No)
277                 return;
278
279         int index = mapNameList->currentIndex();
280         int profileToRemove = profileNum;
281         mapNameList->removeItem(index);
282         DeleteProfile(profileToRemove);
283 }
284
285
286 /*
287 The profiles need the following:
288
289  - The name of the controller
290  - A unique human readable ID
291  - The key definitions for that controller (keyboard keys can be mixed in)
292
293 So there can be more than one profile for each unique controller; the
294 relationship is many-to-one. So basically, how it works it like this: SDL
295 reports all connected controllers. If there are none connected, the default
296 controller is the keyboard (which can have multiple profiles). The UI only
297 presents those profiles which are usuable with the controllers that are plugged
298 in, all else is ignored. The user can pick the profile for the controller and
299 configure the keys for it; the UI automagically saves everything.
300
301 How to handle the case of identical controllers being plugged in? How does the
302 UI know which is which? Each controller will have a mapping to a default
303 Jaguar controller (#1 or #2). Still doesn't prevent confusion though. Actually,
304 it can: The profile can have a field that maps it to a preferred Jaguar
305 controller, which can also be both (#1 AND #2--in this case we can set it to
306 zero which means no preference). If the UI detects two of the same controller
307 and each can be mapped to the same profile, it assigns them in order since it
308 doesn't matter, the profiles are identical.
309
310 The default profile is always available and is the keyboard (hey, we're PC
311 centric here). The default profile is usually #0.
312
313 Can there be more than one keyboard profile? Why not? You will need separate
314 ones for controller #1 and controller #2.
315
316 A profile might look like this:
317
318 Field 1: Nostomo N45 Analog
319 Field 2: Dad's #1
320 Field 3: Jaguar controller #1
321 Field 4: The button/stick mapping
322
323 Profile # would be implicit in the order that they are stored in the internal
324 data structure.
325
326 When a new controller is plugged in with no profiles attached, it defaults to
327 a set keyboard layout which the user can change. So every new controller will
328 always have at least one profile.
329
330 Data structures:
331 The Gamepad class has the name of the controller (except for Keyboard)
332 The profile list is just a list
333 The controller name index + profile index makes a unique key
334 Probably the best way to deal with it is to stuff the name/profile indices
335 into the key definition structure.
336
337 #define CONTROLLER1 0x01
338 #define CONTROLLER2 0x02
339
340 struct Profile
341 {
342         int device;                                     // Host device number
343         char mapName[32];                       // Human readable map name
344         int preferredController;        // CONTROLLER1 and/or CONTROLLER2
345         int map[21];                            // Keys/buttons/axes
346 };
347
348 NOTE that device is an int, and the list is maintained elsewhere. It is
349 *not* the same as what you see in GetJoystickName(); the device names have
350 to be able to persist even when not available.
351
352 Where to store the master profile list? It has to be accessible to this class.
353 vjs.profile[x] would be good, but it's not really a concern for the Jaguar core.
354 So it shouldn't go there. There should be a separate global setting place for
355 GUI stuff...
356 */
357