Make non-formspec modal menus respect gui scale (#7850)
[oweals/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 "serialization.h"
25 #include <string>
26 #include <IGUICheckBox.h>
27 #include <IGUIEditBox.h>
28 #include <IGUIButton.h>
29 #include <IGUIStaticText.h>
30 #include <IGUIFont.h>
31 #include "settings.h"
32 #include <algorithm>
33
34 #include "mainmenumanager.h"  // for g_gamecallback
35
36 #define KMaxButtonPerColumns 12
37
38 extern MainGameCallback *g_gamecallback;
39
40 enum
41 {
42         GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR,
43         // buttons
44         GUI_ID_KEY_FORWARD_BUTTON,
45         GUI_ID_KEY_BACKWARD_BUTTON,
46         GUI_ID_KEY_LEFT_BUTTON,
47         GUI_ID_KEY_RIGHT_BUTTON,
48         GUI_ID_KEY_USE_BUTTON,
49         GUI_ID_KEY_FLY_BUTTON,
50         GUI_ID_KEY_FAST_BUTTON,
51         GUI_ID_KEY_JUMP_BUTTON,
52         GUI_ID_KEY_NOCLIP_BUTTON,
53         GUI_ID_KEY_CINEMATIC_BUTTON,
54         GUI_ID_KEY_CHAT_BUTTON,
55         GUI_ID_KEY_CMD_BUTTON,
56         GUI_ID_KEY_CMD_LOCAL_BUTTON,
57         GUI_ID_KEY_CONSOLE_BUTTON,
58         GUI_ID_KEY_SNEAK_BUTTON,
59         GUI_ID_KEY_DROP_BUTTON,
60         GUI_ID_KEY_INVENTORY_BUTTON,
61         GUI_ID_KEY_HOTBAR_PREV_BUTTON,
62         GUI_ID_KEY_HOTBAR_NEXT_BUTTON,
63         GUI_ID_KEY_MUTE_BUTTON,
64         GUI_ID_KEY_DEC_VOLUME_BUTTON,
65         GUI_ID_KEY_INC_VOLUME_BUTTON,
66         GUI_ID_KEY_RANGE_BUTTON,
67         GUI_ID_KEY_ZOOM_BUTTON,
68         GUI_ID_KEY_CAMERA_BUTTON,
69         GUI_ID_KEY_MINIMAP_BUTTON,
70         GUI_ID_KEY_SCREENSHOT_BUTTON,
71         GUI_ID_KEY_CHATLOG_BUTTON,
72         GUI_ID_KEY_HUD_BUTTON,
73         GUI_ID_KEY_FOG_BUTTON,
74         GUI_ID_KEY_DEC_RANGE_BUTTON,
75         GUI_ID_KEY_INC_RANGE_BUTTON,
76         GUI_ID_KEY_AUTOFWD_BUTTON,
77         // other
78         GUI_ID_CB_AUX1_DESCENDS,
79         GUI_ID_CB_DOUBLETAP_JUMP,
80         GUI_ID_CB_AUTOJUMP,
81 };
82
83 GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env,
84                                 gui::IGUIElement* parent, s32 id, IMenuManager *menumgr) :
85 GUIModalMenu(env, parent, id, menumgr)
86 {
87         init_keys();
88         for (key_setting *ks : key_settings)
89                 key_used.push_back(ks->key);
90 }
91
92 GUIKeyChangeMenu::~GUIKeyChangeMenu()
93 {
94         removeChildren();
95
96         for (key_setting *ks : key_settings) {
97                 delete[] ks->button_name;
98                 delete ks;
99         }
100         key_settings.clear();
101 }
102
103 void GUIKeyChangeMenu::removeChildren()
104 {
105         const core::list<gui::IGUIElement*> &children = getChildren();
106         core::list<gui::IGUIElement*> children_copy;
107         for (gui::IGUIElement*i : children) {
108                 children_copy.push_back(i);
109         }
110
111         for (gui::IGUIElement *i : children_copy) {
112                 i->remove();
113         }
114 }
115
116 void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
117 {
118         removeChildren();
119
120         const float s = m_gui_scale;
121         DesiredRect = core::rect<s32>(
122                 screensize.X / 2 - 745 * s / 2,
123                 screensize.Y / 2 - 430 * s / 2,
124                 screensize.X / 2 + 745 * s / 2,
125                 screensize.Y / 2 + 430 * s / 2
126         );
127         recalculateAbsolutePosition(false);
128
129         v2s32 size = DesiredRect.getSize();
130         v2s32 topleft(0, 0);
131
132         {
133                 core::rect<s32> rect(0, 0, 600 * s, 40 * s);
134                 rect += topleft + v2s32(25 * s, 3 * s);
135                 //gui::IGUIStaticText *t =
136                 const wchar_t *text = wgettext("Keybindings. (If this menu screws up, remove stuff from minetest.conf)");
137                 Environment->addStaticText(text,
138                                                                    rect, false, true, this, -1);
139                 delete[] text;
140                 //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
141         }
142
143         // Build buttons
144
145         v2s32 offset(25 * s, 60 * s);
146
147         for(size_t i = 0; i < key_settings.size(); i++)
148         {
149                 key_setting *k = key_settings.at(i);
150                 {
151                         core::rect<s32> rect(0, 0, 150 * s, 20 * s);
152                         rect += topleft + v2s32(offset.X, offset.Y);
153                         Environment->addStaticText(k->button_name, rect, false, true, this, -1);
154                 }
155
156                 {
157                         core::rect<s32> rect(0, 0, 100 * s, 30 * s);
158                         rect += topleft + v2s32(offset.X + 120 * s, offset.Y - 5 * s);
159                         const wchar_t *text = wgettext(k->key.name());
160                         k->button = Environment->addButton(rect, this, k->id, text);
161                         delete[] text;
162                 }
163                 if ((i + 1) % KMaxButtonPerColumns == 0) {
164                         offset.X += 230 * s;
165                         offset.Y = 60 * s;
166                 } else {
167                         offset += v2s32(0, 25 * s);
168                 }
169         }
170
171         {
172                 s32 option_x = offset.X;
173                 s32 option_y = offset.Y + 5 * s;
174                 u32 option_w = 180 * s;
175                 {
176                         core::rect<s32> rect(0, 0, option_w, 30 * s);
177                         rect += topleft + v2s32(option_x, option_y);
178                         const wchar_t *text = wgettext("\"Special\" = climb down");
179                         Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this,
180                                         GUI_ID_CB_AUX1_DESCENDS, text);
181                         delete[] text;
182                 }
183                 offset += v2s32(0, 25 * s);
184         }
185
186         {
187                 s32 option_x = offset.X;
188                 s32 option_y = offset.Y + 5 * s;
189                 u32 option_w = 280 * s;
190                 {
191                         core::rect<s32> rect(0, 0, option_w, 30 * s);
192                         rect += topleft + v2s32(option_x, option_y);
193                         const wchar_t *text = wgettext("Double tap \"jump\" to toggle fly");
194                         Environment->addCheckBox(g_settings->getBool("doubletap_jump"), rect, this,
195                                         GUI_ID_CB_DOUBLETAP_JUMP, text);
196                         delete[] text;
197                 }
198                 offset += v2s32(0, 25 * s);
199         }
200
201         {
202                 s32 option_x = offset.X;
203                 s32 option_y = offset.Y + 5 * s;
204                 u32 option_w = 280;
205                 {
206                         core::rect<s32> rect(0, 0, option_w, 30 * s);
207                         rect += topleft + v2s32(option_x, option_y);
208                         const wchar_t *text = wgettext("Automatic jumping");
209                         Environment->addCheckBox(g_settings->getBool("autojump"), rect, this,
210                                         GUI_ID_CB_AUTOJUMP, text);
211                         delete[] text;
212                 }
213                 offset += v2s32(0, 25);
214         }
215
216         {
217                 core::rect<s32> rect(0, 0, 100 * s, 30 * s);
218                 rect += topleft + v2s32(size.X / 2 - 105 * s, size.Y - 40 * s);
219                 const wchar_t *text =  wgettext("Save");
220                 Environment->addButton(rect, this, GUI_ID_BACK_BUTTON,
221                                  text);
222                 delete[] text;
223         }
224         {
225                 core::rect<s32> rect(0, 0, 100 * s, 30 * s);
226                 rect += topleft + v2s32(size.X / 2 + 5 * s, size.Y - 40 * s);
227                 const wchar_t *text = wgettext("Cancel");
228                 Environment->addButton(rect, this, GUI_ID_ABORT_BUTTON,
229                                 text);
230                 delete[] text;
231         }
232 }
233
234 void GUIKeyChangeMenu::drawMenu()
235 {
236         gui::IGUISkin* skin = Environment->getSkin();
237         if (!skin)
238                 return;
239         video::IVideoDriver* driver = Environment->getVideoDriver();
240
241         video::SColor bgcolor(140, 0, 0, 0);
242         driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
243
244         gui::IGUIElement::draw();
245 }
246
247 bool GUIKeyChangeMenu::acceptInput()
248 {
249         for (key_setting *k : key_settings) {
250                 g_settings->set(k->setting_name, k->key.sym());
251         }
252
253         {
254                 gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUX1_DESCENDS);
255                 if(e && e->getType() == gui::EGUIET_CHECK_BOX)
256                         g_settings->setBool("aux1_descends", ((gui::IGUICheckBox*)e)->isChecked());
257         }
258         {
259                 gui::IGUIElement *e = getElementFromId(GUI_ID_CB_DOUBLETAP_JUMP);
260                 if(e && e->getType() == gui::EGUIET_CHECK_BOX)
261                         g_settings->setBool("doubletap_jump", ((gui::IGUICheckBox*)e)->isChecked());
262         }
263         {
264                 gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUTOJUMP);
265                 if(e && e->getType() == gui::EGUIET_CHECK_BOX)
266                         g_settings->setBool("autojump", ((gui::IGUICheckBox*)e)->isChecked());
267         }
268
269         clearKeyCache();
270
271         g_gamecallback->signalKeyConfigChange();
272
273         return true;
274 }
275
276 bool GUIKeyChangeMenu::resetMenu()
277 {
278         if (activeKey >= 0)
279         {
280                 for (key_setting *k : key_settings) {
281                         if (k->id == activeKey) {
282                                 const wchar_t *text = wgettext(k->key.name());
283                                 k->button->setText(text);
284                                 delete[] text;
285                                 break;
286                         }
287                 }
288                 activeKey = -1;
289                 return false;
290         }
291         return true;
292 }
293 bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
294 {
295         if (event.EventType == EET_KEY_INPUT_EVENT && activeKey >= 0
296                         && event.KeyInput.PressedDown) {
297
298                 bool prefer_character = shift_down;
299                 KeyPress kp(event.KeyInput, prefer_character);
300
301                 bool shift_went_down = false;
302                 if(!shift_down &&
303                                 (event.KeyInput.Key == irr::KEY_SHIFT ||
304                                 event.KeyInput.Key == irr::KEY_LSHIFT ||
305                                 event.KeyInput.Key == irr::KEY_RSHIFT))
306                         shift_went_down = true;
307
308                 // Remove Key already in use message
309                 if(this->key_used_text)
310                 {
311                         this->key_used_text->remove();
312                         this->key_used_text = NULL;
313                 }
314                 // Display Key already in use message
315                 if (std::find(this->key_used.begin(), this->key_used.end(), kp) != this->key_used.end())
316                 {
317                         core::rect < s32 > rect(0, 0, 600, 40);
318                         rect += v2s32(0, 0) + v2s32(25, 30);
319                         const wchar_t *text = wgettext("Key already in use");
320                         this->key_used_text = Environment->addStaticText(text,
321                                         rect, false, true, this, -1);
322                         delete[] text;
323                         //infostream << "Key already in use" << std::endl;
324                 }
325
326                 // But go on
327                 {
328                         key_setting *k = NULL;
329                         for (key_setting *ks : key_settings) {
330                                 if (ks->id == activeKey) {
331                                         k = ks;
332                                         break;
333                                 }
334                         }
335                         FATAL_ERROR_IF(k == NULL, "Key setting not found");
336                         k->key = kp;
337                         const wchar_t *text = wgettext(k->key.name());
338                         k->button->setText(text);
339                         delete[] text;
340
341                         this->key_used.push_back(kp);
342
343                         // Allow characters made with shift
344                         if(shift_went_down){
345                                 shift_down = true;
346                                 return false;
347                         }
348
349                         activeKey = -1;
350                         return true;
351                 }
352         } else if (event.EventType == EET_KEY_INPUT_EVENT && activeKey < 0
353                         && event.KeyInput.PressedDown
354                         && event.KeyInput.Key == irr::KEY_ESCAPE) {
355                 quitMenu();
356                 return true;
357         } else if (event.EventType == EET_GUI_EVENT) {
358                 if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
359                         && isVisible())
360                 {
361                         if (!canTakeFocus(event.GUIEvent.Element))
362                         {
363                                 dstream << "GUIMainMenu: Not allowing focus change."
364                                 << std::endl;
365                                 // Returning true disables focus change
366                                 return true;
367                         }
368                 }
369                 if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED)
370                 {
371                         switch (event.GUIEvent.Caller->getID())
372                         {
373                                 case GUI_ID_BACK_BUTTON: //back
374                                         acceptInput();
375                                         quitMenu();
376                                         return true;
377                                 case GUI_ID_ABORT_BUTTON: //abort
378                                         quitMenu();
379                                         return true;
380                                 default:
381                                         key_setting *k = NULL;
382
383                                         for (key_setting *ks : key_settings) {
384                                                 if (ks->id == event.GUIEvent.Caller->getID()) {
385                                                         k = ks;
386                                                         break;
387                                                 }
388                                         }
389                                         FATAL_ERROR_IF(k == NULL, "Key setting not found");
390
391                                         resetMenu();
392                                         shift_down = false;
393                                         activeKey = event.GUIEvent.Caller->getID();
394                                         const wchar_t *text = wgettext("press key");
395                                         k->button->setText(text);
396                                         delete[] text;
397                                         this->key_used.erase(std::remove(this->key_used.begin(),
398                                                         this->key_used.end(), k->key), this->key_used.end());
399                                         break;
400                         }
401                         Environment->setFocus(this);
402                 }
403         }
404         return Parent ? Parent->OnEvent(event) : false;
405 }
406
407 void GUIKeyChangeMenu::add_key(int id, const wchar_t *button_name, const std::string &setting_name)
408 {
409         key_setting *k = new key_setting;
410         k->id = id;
411
412         k->button_name = button_name;
413         k->setting_name = setting_name;
414         k->key = getKeySetting(k->setting_name.c_str());
415         key_settings.push_back(k);
416 }
417
418 void GUIKeyChangeMenu::init_keys()
419 {
420         this->add_key(GUI_ID_KEY_FORWARD_BUTTON,   wgettext("Forward"),          "keymap_forward");
421         this->add_key(GUI_ID_KEY_BACKWARD_BUTTON,  wgettext("Backward"),         "keymap_backward");
422         this->add_key(GUI_ID_KEY_LEFT_BUTTON,      wgettext("Left"),             "keymap_left");
423         this->add_key(GUI_ID_KEY_RIGHT_BUTTON,     wgettext("Right"),            "keymap_right");
424         this->add_key(GUI_ID_KEY_USE_BUTTON,       wgettext("Special"),          "keymap_special1");
425         this->add_key(GUI_ID_KEY_JUMP_BUTTON,      wgettext("Jump"),             "keymap_jump");
426         this->add_key(GUI_ID_KEY_SNEAK_BUTTON,     wgettext("Sneak"),            "keymap_sneak");
427         this->add_key(GUI_ID_KEY_DROP_BUTTON,      wgettext("Drop"),             "keymap_drop");
428         this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wgettext("Inventory"),        "keymap_inventory");
429         this->add_key(GUI_ID_KEY_HOTBAR_PREV_BUTTON,wgettext("Prev. item"),      "keymap_hotbar_previous");
430         this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON,wgettext("Next item"),       "keymap_hotbar_next");
431         this->add_key(GUI_ID_KEY_ZOOM_BUTTON,      wgettext("Zoom"),             "keymap_zoom");
432         this->add_key(GUI_ID_KEY_CAMERA_BUTTON,    wgettext("Change camera"),    "keymap_camera_mode");
433         this->add_key(GUI_ID_KEY_CINEMATIC_BUTTON, wgettext("Toggle Cinematic"), "keymap_cinematic");
434         this->add_key(GUI_ID_KEY_MINIMAP_BUTTON,   wgettext("Toggle minimap"),   "keymap_minimap");
435         this->add_key(GUI_ID_KEY_FLY_BUTTON,       wgettext("Toggle fly"),       "keymap_freemove");
436         this->add_key(GUI_ID_KEY_FAST_BUTTON,      wgettext("Toggle fast"),      "keymap_fastmove");
437         this->add_key(GUI_ID_KEY_NOCLIP_BUTTON,    wgettext("Toggle noclip"),    "keymap_noclip");
438         this->add_key(GUI_ID_KEY_MUTE_BUTTON,      wgettext("Mute"),             "keymap_mute");
439         this->add_key(GUI_ID_KEY_DEC_VOLUME_BUTTON,wgettext("Dec. volume"),      "keymap_decrease_volume");
440         this->add_key(GUI_ID_KEY_INC_VOLUME_BUTTON,wgettext("Inc. volume"),      "keymap_increase_volume");
441         this->add_key(GUI_ID_KEY_AUTOFWD_BUTTON,   wgettext("Autoforward"),      "keymap_autoforward");
442         this->add_key(GUI_ID_KEY_CHAT_BUTTON,      wgettext("Chat"),             "keymap_chat");
443         this->add_key(GUI_ID_KEY_SCREENSHOT_BUTTON,wgettext("Screenshot"),       "keymap_screenshot");
444         this->add_key(GUI_ID_KEY_RANGE_BUTTON,     wgettext("Range select"),     "keymap_rangeselect");
445         this->add_key(GUI_ID_KEY_DEC_RANGE_BUTTON, wgettext("Dec. range"),       "keymap_decrease_viewing_range_min");
446         this->add_key(GUI_ID_KEY_INC_RANGE_BUTTON, wgettext("Inc. range"),       "keymap_increase_viewing_range_min");
447         this->add_key(GUI_ID_KEY_CONSOLE_BUTTON,   wgettext("Console"),          "keymap_console");
448         this->add_key(GUI_ID_KEY_CMD_BUTTON,       wgettext("Command"),          "keymap_cmd");
449         this->add_key(GUI_ID_KEY_CMD_LOCAL_BUTTON, wgettext("Local command"),    "keymap_cmd_local");
450         this->add_key(GUI_ID_KEY_HUD_BUTTON,       wgettext("Toggle HUD"),       "keymap_toggle_hud");
451         this->add_key(GUI_ID_KEY_CHATLOG_BUTTON,   wgettext("Toggle chat log"),  "keymap_toggle_chat");
452         this->add_key(GUI_ID_KEY_FOG_BUTTON,       wgettext("Toggle fog"),       "keymap_toggle_fog");
453 }
454