]> git.lizzy.rs Git - minetest.git/blob - src/gui/guiKeyChangeMenu.cpp
Add keybind to swap items between hands
[minetest.git] / src / gui / guiKeyChangeMenu.cpp
1 /*
2  Minetest
3  Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4  Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
5  Copyright (C) 2013 teddydestodes <derkomtur@schattengang.net>
6
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU Lesser General Public License as published by
9  the Free Software Foundation; either version 2.1 of the License, or
10  (at your option) any later version.
11
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  GNU Lesser General Public License for more details.
16
17  You should have received a copy of the GNU Lesser General Public License along
18  with this program; if not, write to the Free Software Foundation, Inc.,
19  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "guiKeyChangeMenu.h"
23 #include "debug.h"
24 #include "guiButton.h"
25 #include "serialization.h"
26 #include <string>
27 #include <IGUICheckBox.h>
28 #include <IGUIEditBox.h>
29 #include <IGUIButton.h>
30 #include <IGUIStaticText.h>
31 #include <IGUIFont.h>
32 #include "settings.h"
33 #include <algorithm>
34
35 #include "mainmenumanager.h"  // for g_gamecallback
36
37 #define KMaxButtonPerColumns 12
38
39 extern MainGameCallback *g_gamecallback;
40
41 enum
42 {
43         GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR,
44         // buttons
45         GUI_ID_KEY_FORWARD_BUTTON,
46         GUI_ID_KEY_BACKWARD_BUTTON,
47         GUI_ID_KEY_LEFT_BUTTON,
48         GUI_ID_KEY_RIGHT_BUTTON,
49         GUI_ID_KEY_AUX1_BUTTON,
50         GUI_ID_KEY_FLY_BUTTON,
51         GUI_ID_KEY_FAST_BUTTON,
52         GUI_ID_KEY_JUMP_BUTTON,
53         GUI_ID_KEY_NOCLIP_BUTTON,
54         GUI_ID_KEY_PITCH_MOVE,
55         GUI_ID_KEY_CHAT_BUTTON,
56         GUI_ID_KEY_CMD_BUTTON,
57         GUI_ID_KEY_CMD_LOCAL_BUTTON,
58         GUI_ID_KEY_CONSOLE_BUTTON,
59         GUI_ID_KEY_SNEAK_BUTTON,
60         GUI_ID_KEY_DROP_BUTTON,
61         GUI_ID_KEY_SWAP_OFFHAND_BUTTON,
62         GUI_ID_KEY_INVENTORY_BUTTON,
63         GUI_ID_KEY_HOTBAR_PREV_BUTTON,
64         GUI_ID_KEY_HOTBAR_NEXT_BUTTON,
65         GUI_ID_KEY_MUTE_BUTTON,
66         GUI_ID_KEY_DEC_VOLUME_BUTTON,
67         GUI_ID_KEY_INC_VOLUME_BUTTON,
68         GUI_ID_KEY_RANGE_BUTTON,
69         GUI_ID_KEY_ZOOM_BUTTON,
70         GUI_ID_KEY_CAMERA_BUTTON,
71         GUI_ID_KEY_MINIMAP_BUTTON,
72         GUI_ID_KEY_SCREENSHOT_BUTTON,
73         GUI_ID_KEY_CHATLOG_BUTTON,
74         GUI_ID_KEY_BLOCK_BOUNDS_BUTTON,
75         GUI_ID_KEY_HUD_BUTTON,
76         GUI_ID_KEY_FOG_BUTTON,
77         GUI_ID_KEY_DEC_RANGE_BUTTON,
78         GUI_ID_KEY_INC_RANGE_BUTTON,
79         GUI_ID_KEY_AUTOFWD_BUTTON,
80         // other
81         GUI_ID_CB_AUX1_DESCENDS,
82         GUI_ID_CB_DOUBLETAP_JUMP,
83         GUI_ID_CB_AUTOJUMP,
84 };
85
86 GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env,
87                 gui::IGUIElement* parent, s32 id, IMenuManager *menumgr,
88                 ISimpleTextureSource *tsrc) :
89                 GUIModalMenu(env, parent, id, menumgr),
90                 m_tsrc(tsrc)
91 {
92         init_keys();
93 }
94
95 GUIKeyChangeMenu::~GUIKeyChangeMenu()
96 {
97         removeAllChildren();
98         key_used_text = nullptr;
99
100         for (key_setting *ks : key_settings) {
101                 delete[] ks->button_name;
102                 delete ks;
103         }
104         key_settings.clear();
105 }
106
107 void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
108 {
109         removeAllChildren();
110         key_used_text = nullptr;
111
112         const float s = m_gui_scale;
113         DesiredRect = core::rect<s32>(
114                 screensize.X / 2 - 835 * s / 2,
115                 screensize.Y / 2 - 430 * s / 2,
116                 screensize.X / 2 + 835 * s / 2,
117                 screensize.Y / 2 + 430 * s / 2
118         );
119         recalculateAbsolutePosition(false);
120
121         v2s32 size = DesiredRect.getSize();
122         v2s32 topleft(0, 0);
123
124         {
125                 core::rect<s32> rect(0, 0, 600 * s, 40 * s);
126                 rect += topleft + v2s32(25 * s, 3 * s);
127                 //gui::IGUIStaticText *t =
128                 const wchar_t *text = wgettext("Keybindings.");
129                 Environment->addStaticText(text,
130                                                                    rect, false, true, this, -1);
131                 delete[] text;
132                 //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
133         }
134
135         // Build buttons
136
137         v2s32 offset(25 * s, 60 * s);
138
139         for(size_t i = 0; i < key_settings.size(); i++)
140         {
141                 key_setting *k = key_settings.at(i);
142                 {
143                         core::rect<s32> rect(0, 0, 150 * s, 20 * s);
144                         rect += topleft + v2s32(offset.X, offset.Y);
145                         Environment->addStaticText(k->button_name, rect, false, true, this, -1);
146                 }
147
148                 {
149                         core::rect<s32> rect(0, 0, 100 * s, 30 * s);
150                         rect += topleft + v2s32(offset.X + 150 * s, offset.Y - 5 * s);
151                         const wchar_t *text = wgettext(k->key.name());
152                         k->button = GUIButton::addButton(Environment, rect, m_tsrc, this, k->id, text);
153                         delete[] text;
154                 }
155                 if ((i + 1) % KMaxButtonPerColumns == 0) {
156                         offset.X += 260 * s;
157                         offset.Y = 60 * s;
158                 } else {
159                         offset += v2s32(0, 25 * s);
160                 }
161         }
162
163         {
164                 s32 option_x = offset.X;
165                 s32 option_y = offset.Y + 5 * s;
166                 u32 option_w = 180 * s;
167                 {
168                         core::rect<s32> rect(0, 0, option_w, 30 * s);
169                         rect += topleft + v2s32(option_x, option_y);
170                         const wchar_t *text = wgettext("\"Aux1\" = climb down");
171                         Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this,
172                                         GUI_ID_CB_AUX1_DESCENDS, text);
173                         delete[] text;
174                 }
175                 offset += v2s32(0, 25 * s);
176         }
177
178         {
179                 s32 option_x = offset.X;
180                 s32 option_y = offset.Y + 5 * s;
181                 u32 option_w = 280 * s;
182                 {
183                         core::rect<s32> rect(0, 0, option_w, 30 * s);
184                         rect += topleft + v2s32(option_x, option_y);
185                         const wchar_t *text = wgettext("Double tap \"jump\" to toggle fly");
186                         Environment->addCheckBox(g_settings->getBool("doubletap_jump"), rect, this,
187                                         GUI_ID_CB_DOUBLETAP_JUMP, text);
188                         delete[] text;
189                 }
190                 offset += v2s32(0, 25 * s);
191         }
192
193         {
194                 s32 option_x = offset.X;
195                 s32 option_y = offset.Y + 5 * s;
196                 u32 option_w = 280;
197                 {
198                         core::rect<s32> rect(0, 0, option_w, 30 * s);
199                         rect += topleft + v2s32(option_x, option_y);
200                         const wchar_t *text = wgettext("Automatic jumping");
201                         Environment->addCheckBox(g_settings->getBool("autojump"), rect, this,
202                                         GUI_ID_CB_AUTOJUMP, text);
203                         delete[] text;
204                 }
205                 offset += v2s32(0, 25);
206         }
207
208         {
209                 core::rect<s32> rect(0, 0, 100 * s, 30 * s);
210                 rect += topleft + v2s32(size.X / 2 - 105 * s, size.Y - 40 * s);
211                 const wchar_t *text =  wgettext("Save");
212                 GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_BACK_BUTTON, text);
213                 delete[] text;
214         }
215         {
216                 core::rect<s32> rect(0, 0, 100 * s, 30 * s);
217                 rect += topleft + v2s32(size.X / 2 + 5 * s, size.Y - 40 * s);
218                 const wchar_t *text = wgettext("Cancel");
219                 GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_ABORT_BUTTON, text);
220                 delete[] text;
221         }
222 }
223
224 void GUIKeyChangeMenu::drawMenu()
225 {
226         gui::IGUISkin* skin = Environment->getSkin();
227         if (!skin)
228                 return;
229         video::IVideoDriver* driver = Environment->getVideoDriver();
230
231         video::SColor bgcolor(140, 0, 0, 0);
232         driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
233
234         gui::IGUIElement::draw();
235 }
236
237 bool GUIKeyChangeMenu::acceptInput()
238 {
239         for (key_setting *k : key_settings) {
240                 std::string default_key;
241                 Settings::getLayer(SL_DEFAULTS)->getNoEx(k->setting_name, default_key);
242
243                 if (k->key.sym() != default_key)
244                         g_settings->set(k->setting_name, k->key.sym());
245                 else
246                         g_settings->remove(k->setting_name);
247         }
248
249         {
250                 gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUX1_DESCENDS);
251                 if(e && e->getType() == gui::EGUIET_CHECK_BOX)
252                         g_settings->setBool("aux1_descends", ((gui::IGUICheckBox*)e)->isChecked());
253         }
254         {
255                 gui::IGUIElement *e = getElementFromId(GUI_ID_CB_DOUBLETAP_JUMP);
256                 if(e && e->getType() == gui::EGUIET_CHECK_BOX)
257                         g_settings->setBool("doubletap_jump", ((gui::IGUICheckBox*)e)->isChecked());
258         }
259         {
260                 gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUTOJUMP);
261                 if(e && e->getType() == gui::EGUIET_CHECK_BOX)
262                         g_settings->setBool("autojump", ((gui::IGUICheckBox*)e)->isChecked());
263         }
264
265         clearKeyCache();
266
267         g_gamecallback->signalKeyConfigChange();
268
269         return true;
270 }
271
272 bool GUIKeyChangeMenu::resetMenu()
273 {
274         if (active_key) {
275                 const wchar_t *text = wgettext(active_key->key.name());
276                 active_key->button->setText(text);
277                 delete[] text;
278                 active_key = nullptr;
279                 return false;
280         }
281         return true;
282 }
283 bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
284 {
285         if (event.EventType == EET_KEY_INPUT_EVENT && active_key
286                         && event.KeyInput.PressedDown) {
287
288                 bool prefer_character = shift_down;
289                 KeyPress kp(event.KeyInput, prefer_character);
290
291                 if (event.KeyInput.Key == irr::KEY_DELETE)
292                         kp = KeyPress(""); // To erase key settings
293                 else if (event.KeyInput.Key == irr::KEY_ESCAPE)
294                         kp = active_key->key; // Cancel
295
296                 bool shift_went_down = false;
297                 if(!shift_down &&
298                                 (event.KeyInput.Key == irr::KEY_SHIFT ||
299                                 event.KeyInput.Key == irr::KEY_LSHIFT ||
300                                 event.KeyInput.Key == irr::KEY_RSHIFT))
301                         shift_went_down = true;
302
303                 // Display Key already in use message
304                 bool key_in_use = false;
305                 if (strcmp(kp.sym(), "") != 0) {
306                         for (key_setting *ks : key_settings) {
307                                 if (ks != active_key && ks->key == kp) {
308                                         key_in_use = true;
309                                         break;
310                                 }
311                         }
312                 }
313
314                 if (key_in_use && !this->key_used_text) {
315                         core::rect<s32> rect(0, 0, 600, 40);
316                         rect += v2s32(0, 0) + v2s32(25, 30);
317                         const wchar_t *text = wgettext("Key already in use");
318                         this->key_used_text = Environment->addStaticText(text,
319                                         rect, false, true, this, -1);
320                         delete[] text;
321                 } else if (!key_in_use && this->key_used_text) {
322                         this->key_used_text->remove();
323                         this->key_used_text = nullptr;
324                 }
325
326                 // But go on
327                 {
328                         active_key->key = kp;
329                         const wchar_t *text = wgettext(kp.name());
330                         active_key->button->setText(text);
331                         delete[] text;
332
333                         // Allow characters made with shift
334                         if (shift_went_down){
335                                 shift_down = true;
336                                 return false;
337                         }
338
339                         active_key = nullptr;
340                         return true;
341                 }
342         } else if (event.EventType == EET_KEY_INPUT_EVENT && !active_key
343                         && event.KeyInput.PressedDown
344                         && event.KeyInput.Key == irr::KEY_ESCAPE) {
345                 quitMenu();
346                 return true;
347         } else if (event.EventType == EET_GUI_EVENT) {
348                 if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
349                         && isVisible())
350                 {
351                         if (!canTakeFocus(event.GUIEvent.Element))
352                         {
353                                 infostream << "GUIKeyChangeMenu: Not allowing focus change."
354                                 << std::endl;
355                                 // Returning true disables focus change
356                                 return true;
357                         }
358                 }
359                 if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED)
360                 {
361                         switch (event.GUIEvent.Caller->getID())
362                         {
363                                 case GUI_ID_BACK_BUTTON: //back
364                                         acceptInput();
365                                         quitMenu();
366                                         return true;
367                                 case GUI_ID_ABORT_BUTTON: //abort
368                                         quitMenu();
369                                         return true;
370                                 default:
371                                         resetMenu();
372                                         for (key_setting *ks : key_settings) {
373                                                 if (ks->id == event.GUIEvent.Caller->getID()) {
374                                                         active_key = ks;
375                                                         break;
376                                                 }
377                                         }
378                                         FATAL_ERROR_IF(!active_key, "Key setting not found");
379
380                                         shift_down = false;
381                                         const wchar_t *text = wgettext("press key");
382                                         active_key->button->setText(text);
383                                         delete[] text;
384                                         break;
385                         }
386                         Environment->setFocus(this);
387                 }
388         }
389         return Parent ? Parent->OnEvent(event) : false;
390 }
391
392 void GUIKeyChangeMenu::add_key(int id, const wchar_t *button_name, const std::string &setting_name)
393 {
394         key_setting *k = new key_setting;
395         k->id = id;
396
397         k->button_name = button_name;
398         k->setting_name = setting_name;
399         k->key = getKeySetting(k->setting_name.c_str());
400         key_settings.push_back(k);
401 }
402
403 void GUIKeyChangeMenu::init_keys()
404 {
405         this->add_key(GUI_ID_KEY_FORWARD_BUTTON,      wgettext("Forward"),          "keymap_forward");
406         this->add_key(GUI_ID_KEY_BACKWARD_BUTTON,     wgettext("Backward"),         "keymap_backward");
407         this->add_key(GUI_ID_KEY_LEFT_BUTTON,         wgettext("Left"),             "keymap_left");
408         this->add_key(GUI_ID_KEY_RIGHT_BUTTON,        wgettext("Right"),            "keymap_right");
409         this->add_key(GUI_ID_KEY_AUX1_BUTTON,         wgettext("Aux1"),             "keymap_aux1");
410         this->add_key(GUI_ID_KEY_JUMP_BUTTON,         wgettext("Jump"),             "keymap_jump");
411         this->add_key(GUI_ID_KEY_SNEAK_BUTTON,        wgettext("Sneak"),            "keymap_sneak");
412         this->add_key(GUI_ID_KEY_DROP_BUTTON,         wgettext("Drop"),             "keymap_drop");
413         this->add_key(GUI_ID_KEY_SWAP_OFFHAND_BUTTON, wgettext("Swap hand items"),  "keymap_swap_offhand");
414         this->add_key(GUI_ID_KEY_INVENTORY_BUTTON,    wgettext("Inventory"),        "keymap_inventory");
415         this->add_key(GUI_ID_KEY_HOTBAR_PREV_BUTTON,  wgettext("Prev. item"),       "keymap_hotbar_previous");
416         this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON,  wgettext("Next item"),        "keymap_hotbar_next");
417         this->add_key(GUI_ID_KEY_ZOOM_BUTTON,         wgettext("Zoom"),             "keymap_zoom");
418         this->add_key(GUI_ID_KEY_CAMERA_BUTTON,       wgettext("Change camera"),    "keymap_camera_mode");
419         this->add_key(GUI_ID_KEY_MINIMAP_BUTTON,      wgettext("Toggle minimap"),   "keymap_minimap");
420         this->add_key(GUI_ID_KEY_FLY_BUTTON,          wgettext("Toggle fly"),       "keymap_freemove");
421         this->add_key(GUI_ID_KEY_PITCH_MOVE,          wgettext("Toggle pitchmove"), "keymap_pitchmove");
422         this->add_key(GUI_ID_KEY_FAST_BUTTON,         wgettext("Toggle fast"),      "keymap_fastmove");
423         this->add_key(GUI_ID_KEY_NOCLIP_BUTTON,       wgettext("Toggle noclip"),    "keymap_noclip");
424         this->add_key(GUI_ID_KEY_MUTE_BUTTON,         wgettext("Mute"),             "keymap_mute");
425         this->add_key(GUI_ID_KEY_DEC_VOLUME_BUTTON,   wgettext("Dec. volume"),      "keymap_decrease_volume");
426         this->add_key(GUI_ID_KEY_INC_VOLUME_BUTTON,   wgettext("Inc. volume"),      "keymap_increase_volume");
427         this->add_key(GUI_ID_KEY_AUTOFWD_BUTTON,      wgettext("Autoforward"),      "keymap_autoforward");
428         this->add_key(GUI_ID_KEY_CHAT_BUTTON,         wgettext("Chat"),             "keymap_chat");
429         this->add_key(GUI_ID_KEY_SCREENSHOT_BUTTON,   wgettext("Screenshot"),       "keymap_screenshot");
430         this->add_key(GUI_ID_KEY_RANGE_BUTTON,        wgettext("Range select"),     "keymap_rangeselect");
431         this->add_key(GUI_ID_KEY_DEC_RANGE_BUTTON,    wgettext("Dec. range"),       "keymap_decrease_viewing_range_min");
432         this->add_key(GUI_ID_KEY_INC_RANGE_BUTTON,    wgettext("Inc. range"),       "keymap_increase_viewing_range_min");
433         this->add_key(GUI_ID_KEY_CONSOLE_BUTTON,      wgettext("Console"),          "keymap_console");
434         this->add_key(GUI_ID_KEY_CMD_BUTTON,          wgettext("Command"),          "keymap_cmd");
435         this->add_key(GUI_ID_KEY_CMD_LOCAL_BUTTON,    wgettext("Local command"),    "keymap_cmd_local");
436         this->add_key(GUI_ID_KEY_BLOCK_BOUNDS_BUTTON, wgettext("Block bounds"),     "keymap_toggle_block_bounds");
437         this->add_key(GUI_ID_KEY_HUD_BUTTON,          wgettext("Toggle HUD"),       "keymap_toggle_hud");
438         this->add_key(GUI_ID_KEY_CHATLOG_BUTTON,      wgettext("Toggle chat log"),  "keymap_toggle_chat");
439         this->add_key(GUI_ID_KEY_FOG_BUTTON,          wgettext("Toggle fog"),       "keymap_toggle_fog");
440 }