1 /** @file controllerpresets.cpp Game controller presets.
2 *
3 * @authors Copyright (c) 2015-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 *
5 * @par License
6 * GPL: http://www.gnu.org/licenses/gpl.html
7 *
8 * <small>This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version. This program is distributed in the hope that it
12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 * Public License for more details. You should have received a copy of the GNU
15 * General Public License along with this program; if not, see:
16 * http://www.gnu.org/licenses</small>
17 */
18
19 #include "ui/controllerpresets.h"
20 #include "ui/joystick.h"
21 #include "ui/inputsystem.h"
22 #include "clientapp.h"
23
24 #include <de/App>
25 #include <de/DictionaryValue>
26 #include <de/Process>
27 #include <de/RecordValue>
28 #include <de/Script>
29 #include <de/ScriptSystem>
30
31 #include <doomsday/console/var.h>
32
33 #include <QRegExp>
34
35 using namespace de;
36
37 static String VAR_CONTROLLER_PRESETS("controllerPresets");
38
DENG2_PIMPL_NOREF(ControllerPresets)39 DENG2_PIMPL_NOREF(ControllerPresets)
40 , DENG2_OBSERVES(DoomsdayApp, GameChange)
41 {
42 Record &inputModule;
43 char const *presetCVarPath = nullptr;
44 int deviceId = 0;
45
46 Impl()
47 : inputModule(App::scriptSystem()["Input"])
48 {
49 inputModule.addDictionary(VAR_CONTROLLER_PRESETS);
50 DoomsdayApp::app().audienceForGameChange() += this;
51 }
52
53 // ~Impl()
54 // {
55 // DoomsdayApp::app().audienceForGameChange() -= this;
56 // }
57
58 DictionaryValue const &presets() const
59 {
60 return inputModule.get(VAR_CONTROLLER_PRESETS).as<DictionaryValue>();
61 }
62
63 QList<QString> ids() const
64 {
65 QSet<QString> ids;
66 for (auto i : presets().elements())
67 {
68 if (auto const *value = maybeAs<RecordValue>(i.second))
69 {
70 ids.insert(value->dereference().gets("id"));
71 }
72 }
73 return ids.toList();
74 }
75
76 /**
77 * Checks the presets dictionary if any the regular expressions match
78 * the given device name. Returns a Doomsday Script object whose bind() method
79 * can be called to set the bindings for the gamepad scheme.
80 *
81 * @param deviceName Device name.
82 *
83 * @return Object defining the gamepad default bindings, or @c nullptr if not found.
84 */
85 Record const *findMatching(String const &deviceName) const
86 {
87 for (auto i : presets().elements())
88 {
89 String const key = i.first.value->asText();
90 if (!key.isEmpty() && QRegExp(key, Qt::CaseInsensitive).exactMatch(deviceName))
91 {
92 if (auto const *value = maybeAs<RecordValue>(i.second))
93 {
94 return value->record();
95 }
96 }
97 }
98 return nullptr;
99 }
100
101 Record const *findById(String const &id) const
102 {
103 for (auto i : presets().elements())
104 {
105 if (auto const *value = maybeAs<RecordValue>(i.second))
106 {
107 if (value->dereference().gets("id") == id)
108 {
109 return value->record();
110 }
111 }
112 }
113 return nullptr;
114 }
115
116 cvar_t *presetCVar() const
117 {
118 return Con_FindVariable(presetCVarPath);
119 }
120
121 void currentGameChanged(Game const &newGame)
122 {
123 DENG2_ASSERT(deviceId == IDEV_JOY1); /// @todo Expand for other devices as needed. -jk
124
125 // When loading a game, automatically apply the control scheme matching
126 // the connected game controller (unless a specific scheme is already set).
127 if (!newGame.isNull() && !Joystick_Name().isEmpty())
128 {
129 String const currentScheme = CVar_String(presetCVar());
130
131 if (auto const *gamepad = findMatching(Joystick_Name()))
132 {
133 if (currentScheme.isEmpty())
134 {
135 LOG_INPUT_NOTE("Detected a known gamepad: %s") << Joystick_Name();
136
137 // We can now automatically take this scheme into use.
138 applyPreset(gamepad);
139 }
140 }
141 }
142 }
143
144 void applyPreset(Record const *preset)
145 {
146 ClientApp::inputSystem().removeBindingsForDevice(deviceId);
147
148 if (preset)
149 {
150 LOG_INPUT_MSG("Applying bindings for %s gamepad") << preset->gets("id");
151
152 // Call the bind() function defined in the controllers.de script.
153 Script script("preset.bind()");
154 Process proc(script);
155 proc.globals().add("preset").set(new RecordValue(*preset));
156 proc.execute();
157 }
158
159 CVar_SetString(presetCVar(), preset? preset->gets("id").toUtf8() : "");
160 }
161 };
162
ControllerPresets(int deviceId,char const * presetCVarPath)163 ControllerPresets::ControllerPresets(int deviceId, char const *presetCVarPath)
164 : d(new Impl)
165 {
166 d->deviceId = deviceId;
167 d->presetCVarPath = presetCVarPath;
168 }
169
currentPreset() const170 String ControllerPresets::currentPreset() const
171 {
172 return CVar_String(d->presetCVar());
173 }
174
applyPreset(String const & presetId)175 void ControllerPresets::applyPreset(String const &presetId)
176 {
177 d->applyPreset(d->findById(presetId));
178 }
179
ids() const180 QStringList ControllerPresets::ids() const
181 {
182 return d->ids();
183 }
184