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