]> git.lizzy.rs Git - dragonfireclient.git/blob - src/gui/guiFormSpecMenu.cpp
ed3445b5b58efd49c068040e2180b96ae3751075
[dragonfireclient.git] / src / gui / guiFormSpecMenu.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20
21 #include <cstdlib>
22 #include <algorithm>
23 #include <iterator>
24 #include <limits>
25 #include <sstream>
26 #include "guiFormSpecMenu.h"
27 #include "guiScrollBar.h"
28 #include "guiTable.h"
29 #include "constants.h"
30 #include "gamedef.h"
31 #include "client/keycode.h"
32 #include "util/strfnd.h"
33 #include <IGUIButton.h>
34 #include <IGUICheckBox.h>
35 #include <IGUIComboBox.h>
36 #include <IGUIEditBox.h>
37 #include <IGUIStaticText.h>
38 #include <IGUIFont.h>
39 #include <IGUITabControl.h>
40 #include "client/renderingengine.h"
41 #include "log.h"
42 #include "client/tile.h" // ITextureSource
43 #include "client/hud.h" // drawItemStack
44 #include "filesys.h"
45 #include "gettime.h"
46 #include "gettext.h"
47 #include "scripting_server.h"
48 #include "mainmenumanager.h"
49 #include "porting.h"
50 #include "settings.h"
51 #include "client/client.h"
52 #include "client/fontengine.h"
53 #include "util/hex.h"
54 #include "util/numeric.h"
55 #include "util/string.h" // for parseColorString()
56 #include "irrlicht_changes/static_text.h"
57 #include "client/guiscalingfilter.h"
58 #include "guiBackgroundImage.h"
59 #include "guiBox.h"
60 #include "guiButton.h"
61 #include "guiEditBoxWithScrollbar.h"
62 #include "guiItemImage.h"
63 #include "guiScrollBar.h"
64 #include "guiTable.h"
65 #include "intlGUIEditBox.h"
66 #include "guiHyperText.h"
67
68 #define MY_CHECKPOS(a,b)                                                                                                        \
69         if (v_pos.size() != 2) {                                                                                                \
70                 errorstream<< "Invalid pos for element " << a << "specified: \""        \
71                         << parts[b] << "\"" << std::endl;                                                               \
72                         return;                                                                                                                 \
73         }
74
75 #define MY_CHECKGEOM(a,b)                                                                                                       \
76         if (v_geom.size() != 2) {                                                                                               \
77                 errorstream<< "Invalid geometry for element " << a <<                           \
78                         "specified: \"" << parts[b] << "\"" << std::endl;                               \
79                         return;                                                                                                                 \
80         }
81 /*
82         GUIFormSpecMenu
83 */
84 static unsigned int font_line_height(gui::IGUIFont *font)
85 {
86         return font->getDimension(L"Ay").Height + font->getKerningHeight();
87 }
88
89 inline u32 clamp_u8(s32 value)
90 {
91         return (u32) MYMIN(MYMAX(value, 0), 255);
92 }
93
94 GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick,
95                 gui::IGUIElement *parent, s32 id, IMenuManager *menumgr,
96                 Client *client, ISimpleTextureSource *tsrc, IFormSource *fsrc, TextDest *tdst,
97                 const std::string &formspecPrepend,
98                 bool remap_dbl_click):
99         GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr),
100         m_invmgr(client),
101         m_tsrc(tsrc),
102         m_client(client),
103         m_formspec_prepend(formspecPrepend),
104         m_form_src(fsrc),
105         m_text_dst(tdst),
106         m_joystick(joystick),
107         m_remap_dbl_click(remap_dbl_click)
108 {
109         current_keys_pending.key_down = false;
110         current_keys_pending.key_up = false;
111         current_keys_pending.key_enter = false;
112         current_keys_pending.key_escape = false;
113
114         m_doubleclickdetect[0].time = 0;
115         m_doubleclickdetect[1].time = 0;
116
117         m_doubleclickdetect[0].pos = v2s32(0, 0);
118         m_doubleclickdetect[1].pos = v2s32(0, 0);
119
120         m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay");
121         m_tooltip_append_itemname = g_settings->getBool("tooltip_append_itemname");
122 }
123
124 GUIFormSpecMenu::~GUIFormSpecMenu()
125 {
126         removeChildren();
127
128         for (auto &table_it : m_tables)
129                 table_it.second->drop();
130         for (auto &inventorylist_it : m_inventorylists)
131                 inventorylist_it.e->drop();
132         for (auto &checkbox_it : m_checkboxes)
133                 checkbox_it.second->drop();
134         for (auto &scrollbar_it : m_scrollbars)
135                 scrollbar_it.second->drop();
136         for (auto &background_it : m_backgrounds)
137                 background_it->drop();
138         for (auto &tooltip_rect_it : m_tooltip_rects)
139                 tooltip_rect_it.first->drop();
140
141         delete m_selected_item;
142         delete m_form_src;
143         delete m_text_dst;
144 }
145
146 void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client,
147         JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest,
148         const std::string &formspecPrepend)
149 {
150         if (cur_formspec == nullptr) {
151                 cur_formspec = new GUIFormSpecMenu(joystick, guiroot, -1, &g_menumgr,
152                         client, client->getTextureSource(), fs_src, txt_dest, formspecPrepend);
153                 cur_formspec->doPause = false;
154
155                 /*
156                         Caution: do not call (*cur_formspec)->drop() here --
157                         the reference might outlive the menu, so we will
158                         periodically check if *cur_formspec is the only
159                         remaining reference (i.e. the menu was removed)
160                         and delete it in that case.
161                 */
162
163         } else {
164                 cur_formspec->setFormspecPrepend(formspecPrepend);
165                 cur_formspec->setFormSource(fs_src);
166                 cur_formspec->setTextDest(txt_dest);
167         }
168 }
169
170 void GUIFormSpecMenu::removeChildren()
171 {
172         const core::list<gui::IGUIElement*> &children = getChildren();
173
174         while (!children.empty()) {
175                 (*children.getLast())->remove();
176         }
177
178         if (m_tooltip_element) {
179                 m_tooltip_element->remove();
180                 m_tooltip_element->drop();
181                 m_tooltip_element = nullptr;
182         }
183 }
184
185 void GUIFormSpecMenu::setInitialFocus()
186 {
187         // Set initial focus according to following order of precedence:
188         // 1. first empty editbox
189         // 2. first editbox
190         // 3. first table
191         // 4. last button
192         // 5. first focusable (not statictext, not tabheader)
193         // 6. first child element
194
195         core::list<gui::IGUIElement*> children = getChildren();
196
197         // in case "children" contains any NULL elements, remove them
198         for (core::list<gui::IGUIElement*>::Iterator it = children.begin();
199                         it != children.end();) {
200                 if (*it)
201                         ++it;
202                 else
203                         it = children.erase(it);
204         }
205
206         // 1. first empty editbox
207         for (gui::IGUIElement *it : children) {
208                 if (it->getType() == gui::EGUIET_EDIT_BOX
209                                 && it->getText()[0] == 0) {
210                         Environment->setFocus(it);
211                         return;
212                 }
213         }
214
215         // 2. first editbox
216         for (gui::IGUIElement *it : children) {
217                 if (it->getType() == gui::EGUIET_EDIT_BOX) {
218                         Environment->setFocus(it);
219                         return;
220                 }
221         }
222
223         // 3. first table
224         for (gui::IGUIElement *it : children) {
225                 if (it->getTypeName() == std::string("GUITable")) {
226                         Environment->setFocus(it);
227                         return;
228                 }
229         }
230
231         // 4. last button
232         for (core::list<gui::IGUIElement*>::Iterator it = children.getLast();
233                         it != children.end(); --it) {
234                 if ((*it)->getType() == gui::EGUIET_BUTTON) {
235                         Environment->setFocus(*it);
236                         return;
237                 }
238         }
239
240         // 5. first focusable (not statictext, not tabheader)
241         for (gui::IGUIElement *it : children) {
242                 if (it->getType() != gui::EGUIET_STATIC_TEXT &&
243                         it->getType() != gui::EGUIET_TAB_CONTROL) {
244                         Environment->setFocus(it);
245                         return;
246                 }
247         }
248
249         // 6. first child element
250         if (children.empty())
251                 Environment->setFocus(this);
252         else
253                 Environment->setFocus(*(children.begin()));
254 }
255
256 GUITable* GUIFormSpecMenu::getTable(const std::string &tablename)
257 {
258         for (auto &table : m_tables) {
259                 if (tablename == table.first.fname)
260                         return table.second;
261         }
262         return 0;
263 }
264
265 std::vector<std::string>* GUIFormSpecMenu::getDropDownValues(const std::string &name)
266 {
267         for (auto &dropdown : m_dropdowns) {
268                 if (name == dropdown.first.fname)
269                         return &dropdown.second;
270         }
271         return NULL;
272 }
273
274 v2s32 GUIFormSpecMenu::getElementBasePos(const std::vector<std::string> *v_pos)
275 {
276         v2f32 pos_f = v2f32(padding.X, padding.Y) + pos_offset * spacing;
277         if (v_pos) {
278                 pos_f.X += stof((*v_pos)[0]) * spacing.X;
279                 pos_f.Y += stof((*v_pos)[1]) * spacing.Y;
280         }
281         return v2s32(pos_f.X, pos_f.Y);
282 }
283
284 v2s32 GUIFormSpecMenu::getRealCoordinateBasePos(const std::vector<std::string> &v_pos)
285 {
286         return v2s32((stof(v_pos[0]) + pos_offset.X) * imgsize.X,
287                 (stof(v_pos[1]) + pos_offset.Y) * imgsize.Y);
288 }
289
290 v2s32 GUIFormSpecMenu::getRealCoordinateGeometry(const std::vector<std::string> &v_geom)
291 {
292         return v2s32(stof(v_geom[0]) * imgsize.X, stof(v_geom[1]) * imgsize.Y);
293 }
294
295 void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element)
296 {
297         std::vector<std::string> parts = split(element,',');
298
299         if (((parts.size() == 2) || parts.size() == 3) ||
300                 ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
301         {
302                 if (parts[1].find(';') != std::string::npos)
303                         parts[1] = parts[1].substr(0,parts[1].find(';'));
304
305                 data->invsize.X = MYMAX(0, stof(parts[0]));
306                 data->invsize.Y = MYMAX(0, stof(parts[1]));
307
308                 lockSize(false);
309 #ifndef __ANDROID__
310                 if (parts.size() == 3) {
311                         if (parts[2] == "true") {
312                                 lockSize(true,v2u32(800,600));
313                         }
314                 }
315 #endif
316                 data->explicit_size = true;
317                 return;
318         }
319         errorstream<< "Invalid size element (" << parts.size() << "): '" << element << "'"  << std::endl;
320 }
321
322 void GUIFormSpecMenu::parseContainer(parserData* data, const std::string &element)
323 {
324         std::vector<std::string> parts = split(element, ',');
325
326         if (parts.size() >= 2) {
327                 if (parts[1].find(';') != std::string::npos)
328                         parts[1] = parts[1].substr(0, parts[1].find(';'));
329
330                 container_stack.push(pos_offset);
331                 pos_offset.X += stof(parts[0]);
332                 pos_offset.Y += stof(parts[1]);
333                 return;
334         }
335         errorstream<< "Invalid container start element (" << parts.size() << "): '" << element << "'"  << std::endl;
336 }
337
338 void GUIFormSpecMenu::parseContainerEnd(parserData* data)
339 {
340         if (container_stack.empty()) {
341                 errorstream<< "Invalid container end element, no matching container start element"  << std::endl;
342         } else {
343                 pos_offset = container_stack.top();
344                 container_stack.pop();
345         }
346 }
347
348 void GUIFormSpecMenu::parseList(parserData* data, const std::string &element)
349 {
350         if (m_client == 0) {
351                 warningstream<<"invalid use of 'list' with m_client==0"<<std::endl;
352                 return;
353         }
354
355         std::vector<std::string> parts = split(element,';');
356
357         if (((parts.size() == 4) || (parts.size() == 5)) ||
358                 ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
359         {
360                 std::string location = parts[0];
361                 std::string listname = parts[1];
362                 std::vector<std::string> v_pos  = split(parts[2],',');
363                 std::vector<std::string> v_geom = split(parts[3],',');
364                 std::string startindex;
365                 if (parts.size() == 5)
366                         startindex = parts[4];
367
368                 MY_CHECKPOS("list",2);
369                 MY_CHECKGEOM("list",3);
370
371                 InventoryLocation loc;
372
373                 if (location == "context" || location == "current_name")
374                         loc = m_current_inventory_location;
375                 else
376                         loc.deSerialize(location);
377
378                 v2s32 geom;
379                 geom.X = stoi(v_geom[0]);
380                 geom.Y = stoi(v_geom[1]);
381
382                 s32 start_i = 0;
383                 if (!startindex.empty())
384                         start_i = stoi(startindex);
385
386                 if (geom.X < 0 || geom.Y < 0 || start_i < 0) {
387                         errorstream<< "Invalid list element: '" << element << "'"  << std::endl;
388                         return;
389                 }
390
391                 // check for the existence of inventory and list
392                 Inventory *inv = m_invmgr->getInventory(loc);
393                 if (!inv) {
394                         warningstream << "GUIFormSpecMenu::parseList(): "
395                                         << "The inventory location "
396                                         << "\"" << loc.dump() << "\" doesn't exist"
397                                         << std::endl;
398                         return;
399                 }
400                 InventoryList *ilist = inv->getList(listname);
401                 if (!ilist) {
402                         warningstream << "GUIFormSpecMenu::parseList(): "
403                                         << "The inventory list \"" << listname << "\" "
404                                         << "@ \"" << loc.dump() << "\" doesn't exist"
405                                         << std::endl;
406                         return;
407                 }
408
409                 // trim geom if it is larger than the actual inventory size
410                 s32 list_size = (s32)ilist->getSize();
411                 if (list_size < geom.X * geom.Y + start_i) {
412                         list_size -= MYMAX(start_i, 0);
413                         geom.Y = list_size / geom.X;
414                         geom.Y += list_size % geom.X > 0 ? 1 : 0;
415                         if (geom.Y <= 1)
416                                 geom.X = list_size;
417                 }
418
419                 if (!data->explicit_size)
420                         warningstream << "invalid use of list without a size[] element" << std::endl;
421
422                 FieldSpec spec(
423                         "",
424                         L"",
425                         L"",
426                         258 + m_fields.size(),
427                         3
428                 );
429
430                 v2s32 pos;
431                 core::rect<s32> rect;
432
433                 if (data->real_coordinates) {
434                         pos = getRealCoordinateBasePos(v_pos);
435                         rect = core::rect<s32>(pos.X, pos.Y,
436                                         pos.X + (geom.X - 1) * (imgsize.X * 1.25) + imgsize.X,
437                                         pos.Y + (geom.Y - 1) * (imgsize.Y * 1.25) + imgsize.Y);
438                 } else {
439                         pos = getElementBasePos(&v_pos);
440                         rect = core::rect<s32>(pos.X, pos.Y,
441                                         pos.X + (geom.X - 1) * spacing.X + imgsize.X,
442                                         pos.Y + (geom.Y - 1) * spacing.Y + imgsize.Y);
443                 }
444
445                 gui::IGUIElement *e = new gui::IGUIElement(EGUIET_ELEMENT, Environment,
446                                 this, spec.fid, rect);
447
448                 m_inventorylists.emplace_back(loc, listname, e, geom, start_i,
449                                 data->real_coordinates);
450                 m_fields.push_back(spec);
451                 return;
452         }
453         errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'"  << std::endl;
454 }
455
456 void GUIFormSpecMenu::parseListRing(parserData* data, const std::string &element)
457 {
458         if (m_client == 0) {
459                 errorstream << "WARNING: invalid use of 'listring' with m_client==0" << std::endl;
460                 return;
461         }
462
463         std::vector<std::string> parts = split(element, ';');
464
465         if (parts.size() == 2) {
466                 std::string location = parts[0];
467                 std::string listname = parts[1];
468
469                 InventoryLocation loc;
470
471                 if (location == "context" || location == "current_name")
472                         loc = m_current_inventory_location;
473                 else
474                         loc.deSerialize(location);
475
476                 m_inventory_rings.emplace_back(loc, listname);
477                 return;
478         }
479
480         if (element.empty() && m_inventorylists.size() > 1) {
481                 size_t siz = m_inventorylists.size();
482                 // insert the last two inv list elements into the list ring
483                 const ListDrawSpec &spa = m_inventorylists[siz - 2];
484                 const ListDrawSpec &spb = m_inventorylists[siz - 1];
485                 m_inventory_rings.emplace_back(spa.inventoryloc, spa.listname);
486                 m_inventory_rings.emplace_back(spb.inventoryloc, spb.listname);
487                 return;
488         }
489
490         errorstream<< "Invalid list ring element(" << parts.size() << ", "
491                 << m_inventorylists.size() << "): '" << element << "'"  << std::endl;
492 }
493
494 void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element)
495 {
496         std::vector<std::string> parts = split(element,';');
497
498         if (((parts.size() >= 3) && (parts.size() <= 4)) ||
499                 ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
500         {
501                 std::vector<std::string> v_pos = split(parts[0],',');
502                 std::string name = parts[1];
503                 std::string label = parts[2];
504                 std::string selected;
505
506                 if (parts.size() >= 4)
507                         selected = parts[3];
508
509                 MY_CHECKPOS("checkbox",0);
510
511                 bool fselected = false;
512
513                 if (selected == "true")
514                         fselected = true;
515
516                 std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
517                 const core::dimension2d<u32> label_size = m_font->getDimension(wlabel.c_str());
518                 s32 cb_size = Environment->getSkin()->getSize(gui::EGDS_CHECK_BOX_WIDTH);
519                 s32 y_center = (std::max(label_size.Height, (u32)cb_size) + 1) / 2;
520
521                 v2s32 pos;
522                 core::rect<s32> rect;
523
524                 if (data->real_coordinates) {
525                         pos = getRealCoordinateBasePos(v_pos);
526
527                         rect = core::rect<s32>(
528                                         pos.X,
529                                         pos.Y - y_center,
530                                         pos.X + label_size.Width + cb_size + 7,
531                                         pos.Y + y_center
532                                 );
533                 } else {
534                         pos = getElementBasePos(&v_pos);
535                         rect = core::rect<s32>(
536                                         pos.X,
537                                         pos.Y + imgsize.Y / 2 - y_center,
538                                         pos.X + label_size.Width + cb_size + 7,
539                                         pos.Y + imgsize.Y / 2 + y_center
540                                 );
541                 }
542
543                 FieldSpec spec(
544                                 name,
545                                 wlabel, //Needed for displaying text on MSVC
546                                 wlabel,
547                                 258+m_fields.size()
548                         );
549
550                 spec.ftype = f_CheckBox;
551
552                 gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect, this,
553                                         spec.fid, spec.flabel.c_str());
554
555                 auto style = getStyleForElement("checkbox", name);
556                 e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
557
558                 if (spec.fname == data->focused_fieldname) {
559                         Environment->setFocus(e);
560                 }
561
562                 e->grab();
563                 m_checkboxes.emplace_back(spec, e);
564                 m_fields.push_back(spec);
565                 return;
566         }
567         errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'"  << std::endl;
568 }
569
570 void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &element)
571 {
572         std::vector<std::string> parts = split(element,';');
573
574         if (parts.size() >= 5) {
575                 std::vector<std::string> v_pos = split(parts[0],',');
576                 std::vector<std::string> v_geom = split(parts[1],',');
577                 std::string name = parts[3];
578                 std::string value = parts[4];
579
580                 MY_CHECKPOS("scrollbar",0);
581                 MY_CHECKGEOM("scrollbar",1);
582
583                 v2s32 pos;
584                 v2s32 dim;
585
586                 if (data->real_coordinates) {
587                         pos = getRealCoordinateBasePos(v_pos);
588                         dim = getRealCoordinateGeometry(v_geom);
589                 } else {
590                         pos = getElementBasePos(&v_pos);
591                         dim.X = stof(v_geom[0]) * spacing.X;
592                         dim.Y = stof(v_geom[1]) * spacing.Y;
593                 }
594
595                 core::rect<s32> rect =
596                                 core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y);
597
598                 FieldSpec spec(
599                                 name,
600                                 L"",
601                                 L"",
602                                 258+m_fields.size()
603                         );
604
605                 bool is_horizontal = true;
606
607                 if (parts[2] == "vertical")
608                         is_horizontal = false;
609
610                 spec.ftype = f_ScrollBar;
611                 spec.send  = true;
612                 GUIScrollBar *e = new GUIScrollBar(Environment, this, spec.fid, rect,
613                                 is_horizontal, true);
614
615                 auto style = getStyleForElement("scrollbar", name);
616                 e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
617                 e->setArrowsVisible(data->scrollbar_options.arrow_visiblity);
618
619                 s32 max = data->scrollbar_options.max;
620                 s32 min = data->scrollbar_options.min;
621
622                 e->setMax(max);
623                 e->setMin(min);
624
625                 e->setPos(stoi(parts[4]));
626
627                 e->setSmallStep(data->scrollbar_options.small_step);
628                 e->setLargeStep(data->scrollbar_options.large_step);
629
630                 s32 scrollbar_size = is_horizontal ? dim.X : dim.Y;
631
632                 e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size);
633
634                 m_scrollbars.emplace_back(spec,e);
635                 m_fields.push_back(spec);
636                 return;
637         }
638         errorstream << "Invalid scrollbar element(" << parts.size() << "): '" << element
639                 << "'" << std::endl;
640 }
641
642 void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string &element)
643 {
644         std::vector<std::string> parts = split(element, ';');
645
646         if (parts.size() == 0) {
647                 warningstream << "Invalid scrollbaroptions element(" << parts.size() << "): '" <<
648                         element << "'"  << std::endl;
649                 return;
650         }
651
652         for (const std::string &i : parts) {
653                 std::vector<std::string> options = split(i, '=');
654
655                 if (options.size() != 2) {
656                         warningstream << "Invalid scrollbaroptions option syntax: '" <<
657                                 element << "'" << std::endl;
658                         continue; // Go to next option
659                 }
660
661                 if (options[0] == "max") {
662                         data->scrollbar_options.max = stoi(options[1]);
663                         continue;
664                 } else if (options[0] == "min") {
665                         data->scrollbar_options.min = stoi(options[1]);
666                         continue;
667                 } else if (options[0] == "smallstep") {
668                         int value = stoi(options[1]);
669                         data->scrollbar_options.small_step = value < 0 ? 10 : value;
670                         continue;
671                 } else if (options[0] == "largestep") {
672                         int value = stoi(options[1]);
673                         data->scrollbar_options.large_step = value < 0 ? 100 : value;
674                         continue;
675                 } else if (options[0] == "thumbsize") {
676                         int value = stoi(options[1]);
677                         data->scrollbar_options.thumb_size = value <= 0 ? 1 : value;
678                         continue;
679                 } else if (options[0] == "arrows") {
680                         std::string value = trim(options[1]);
681                         if (value == "hide")
682                                 data->scrollbar_options.arrow_visiblity = GUIScrollBar::HIDE;
683                         else if (value == "show")
684                                 data->scrollbar_options.arrow_visiblity = GUIScrollBar::SHOW;
685                         else // Auto hide/show
686                                 data->scrollbar_options.arrow_visiblity = GUIScrollBar::DEFAULT;
687                         continue;
688                 }
689
690                 warningstream << "Invalid scrollbaroptions option(" << options[0] <<
691                         "): '" << element << "'" << std::endl;
692         }
693 }
694
695 void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
696 {
697         std::vector<std::string> parts = split(element,';');
698
699         if ((parts.size() == 3) ||
700                 ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
701         {
702                 std::vector<std::string> v_pos = split(parts[0],',');
703                 std::vector<std::string> v_geom = split(parts[1],',');
704                 std::string name = unescape_string(parts[2]);
705
706                 MY_CHECKPOS("image", 0);
707                 MY_CHECKGEOM("image", 1);
708
709                 v2s32 pos;
710                 v2s32 geom;
711
712                 if (data->real_coordinates) {
713                         pos = getRealCoordinateBasePos(v_pos);
714                         geom = getRealCoordinateGeometry(v_geom);
715                 } else {
716                         pos = getElementBasePos(&v_pos);
717                         geom.X = stof(v_geom[0]) * (float)imgsize.X;
718                         geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
719                 }
720
721                 if (!data->explicit_size)
722                         warningstream<<"invalid use of image without a size[] element"<<std::endl;
723
724                 video::ITexture *texture = m_tsrc->getTexture(name);
725                 if (!texture) {
726                         errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:"
727                                         << std::endl << "\t" << name << std::endl;
728                         return;
729                 }
730
731                 FieldSpec spec(
732                         name,
733                         L"",
734                         L"",
735                         258 + m_fields.size(),
736                         1
737                 );
738                 core::rect<s32> rect(pos, pos + geom);
739                 gui::IGUIImage *e = Environment->addImage(rect, this, spec.fid, 0, true);
740                 e->setImage(texture);
741                 e->setScaleImage(true);
742                 e->setNotClipped(true);
743                 m_fields.push_back(spec);
744
745                 return;
746         }
747
748         if (parts.size() == 2) {
749                 std::vector<std::string> v_pos = split(parts[0],',');
750                 std::string name = unescape_string(parts[1]);
751
752                 MY_CHECKPOS("image", 0);
753
754                 v2s32 pos = getElementBasePos(&v_pos);
755
756                 if (!data->explicit_size)
757                         warningstream<<"invalid use of image without a size[] element"<<std::endl;
758
759                 video::ITexture *texture = m_tsrc->getTexture(name);
760                 if (!texture) {
761                         errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:"
762                                         << std::endl << "\t" << name << std::endl;
763                         return;
764                 }
765
766                 FieldSpec spec(
767                         name,
768                         L"",
769                         L"",
770                         258 + m_fields.size()
771                 );
772                 gui::IGUIImage *e = Environment->addImage(texture, pos, true, this,
773                                 spec.fid, 0);
774                 e->setNotClipped(true);
775                 m_fields.push_back(spec);
776
777                 return;
778         }
779         errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'"  << std::endl;
780 }
781
782 void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &element)
783 {
784         std::vector<std::string> parts = split(element,';');
785
786         if ((parts.size() == 3) ||
787                 ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
788         {
789                 std::vector<std::string> v_pos = split(parts[0],',');
790                 std::vector<std::string> v_geom = split(parts[1],',');
791                 std::string name = parts[2];
792
793                 MY_CHECKPOS("itemimage",0);
794                 MY_CHECKGEOM("itemimage",1);
795
796                 v2s32 pos;
797                 v2s32 geom;
798
799                 if (data->real_coordinates) {
800                         pos = getRealCoordinateBasePos(v_pos);
801                         geom = getRealCoordinateGeometry(v_geom);
802                 } else {
803                         pos = getElementBasePos(&v_pos);
804                         geom.X = stof(v_geom[0]) * (float)imgsize.X;
805                         geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
806                 }
807
808                 if(!data->explicit_size)
809                         warningstream<<"invalid use of item_image without a size[] element"<<std::endl;
810
811                 FieldSpec spec(
812                         "",
813                         L"",
814                         L"",
815                         258 + m_fields.size(),
816                         2
817                 );
818                 spec.ftype = f_ItemImage;
819
820                 GUIItemImage *e = new GUIItemImage(Environment, this, spec.fid,
821                                 core::rect<s32>(pos, pos + geom), name, m_font, m_client);
822                 e->drop();
823
824                 m_fields.push_back(spec);
825                 return;
826         }
827         errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'"  << std::endl;
828 }
829
830 void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
831                 const std::string &type)
832 {
833         std::vector<std::string> parts = split(element,';');
834
835         if ((parts.size() == 4) ||
836                 ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
837         {
838                 std::vector<std::string> v_pos = split(parts[0],',');
839                 std::vector<std::string> v_geom = split(parts[1],',');
840                 std::string name = parts[2];
841                 std::string label = parts[3];
842
843                 MY_CHECKPOS("button",0);
844                 MY_CHECKGEOM("button",1);
845
846                 v2s32 pos;
847                 v2s32 geom;
848                 core::rect<s32> rect;
849
850                 if (data->real_coordinates) {
851                         pos = getRealCoordinateBasePos(v_pos);
852                         geom = getRealCoordinateGeometry(v_geom);
853                         rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
854                                 pos.Y+geom.Y);
855                 } else {
856                         pos = getElementBasePos(&v_pos);
857                         geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
858                         pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
859
860                         rect = core::rect<s32>(pos.X, pos.Y - m_btn_height,
861                                                 pos.X + geom.X, pos.Y + m_btn_height);
862                 }
863
864                 if(!data->explicit_size)
865                         warningstream<<"invalid use of button without a size[] element"<<std::endl;
866
867                 std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
868
869                 FieldSpec spec(
870                         name,
871                         wlabel,
872                         L"",
873                         258 + m_fields.size()
874                 );
875                 spec.ftype = f_Button;
876                 if(type == "button_exit")
877                         spec.is_exit = true;
878
879                 GUIButton *e = GUIButton::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str());
880
881                 auto style = getStyleForElement(type, name, (type != "button") ? "button" : "");
882                 if (style.isNotDefault(StyleSpec::BGCOLOR)) {
883                         e->setColor(style.getColor(StyleSpec::BGCOLOR));
884                 }
885                 if (style.isNotDefault(StyleSpec::BGCOLOR_HOVERED)) {
886                         e->setHoveredColor(style.getColor(StyleSpec::BGCOLOR_HOVERED));
887                 }
888                 if (style.isNotDefault(StyleSpec::BGCOLOR_PRESSED)) {
889                         e->setPressedColor(style.getColor(StyleSpec::BGCOLOR_PRESSED));
890                 }
891
892                 if (style.isNotDefault(StyleSpec::TEXTCOLOR)) {
893                         e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR));
894                 }
895                 e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
896                 e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
897
898                 if (style.isNotDefault(StyleSpec::BGIMG)) {
899                         std::string image_name = style.get(StyleSpec::BGIMG, "");
900                         std::string hovered_image_name = style.get(StyleSpec::BGIMG_HOVERED, "");
901                         std::string pressed_image_name = style.get(StyleSpec::BGIMG_PRESSED, "");
902
903                         video::ITexture *texture = 0;
904                         video::ITexture *hovered_texture = 0;
905                         video::ITexture *pressed_texture = 0;
906                         texture = m_tsrc->getTexture(image_name);
907                         if (!hovered_image_name.empty())
908                                 hovered_texture = m_tsrc->getTexture(hovered_image_name);
909                         else
910                                 hovered_texture = texture;
911                         if (!pressed_image_name.empty())
912                                 pressed_texture = m_tsrc->getTexture(pressed_image_name);
913                         else
914                                 pressed_texture = texture;
915
916                         e->setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true));
917                         e->setImage(guiScalingImageButton(
918                                         Environment->getVideoDriver(), texture, geom.X, geom.Y));
919                         e->setHoveredImage(guiScalingImageButton(
920                                         Environment->getVideoDriver(), hovered_texture, geom.X, geom.Y));
921                         e->setPressedImage(guiScalingImageButton(
922                                         Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
923                         e->setScaleImage(true);
924                 }
925
926                 if (spec.fname == data->focused_fieldname) {
927                         Environment->setFocus(e);
928                 }
929
930                 m_fields.push_back(spec);
931                 return;
932         }
933         errorstream<< "Invalid button element(" << parts.size() << "): '" << element << "'"  << std::endl;
934 }
935
936 void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &element)
937 {
938         std::vector<std::string> parts = split(element,';');
939
940         if ((parts.size() >= 3 && parts.size() <= 5) ||
941                         (parts.size() > 5 && m_formspec_version > FORMSPEC_API_VERSION)) {
942                 std::vector<std::string> v_pos = split(parts[0],',');
943                 std::vector<std::string> v_geom = split(parts[1],',');
944                 std::string name = unescape_string(parts[2]);
945
946                 MY_CHECKPOS("background",0);
947                 MY_CHECKGEOM("background",1);
948
949                 v2s32 pos;
950                 v2s32 geom;
951
952                 if (data->real_coordinates) {
953                         pos = getRealCoordinateBasePos(v_pos);
954                         geom = getRealCoordinateGeometry(v_geom);
955                 } else {
956                         pos = getElementBasePos(&v_pos);
957                         pos.X -= (spacing.X - (float)imgsize.X) / 2;
958                         pos.Y -= (spacing.Y - (float)imgsize.Y) / 2;
959
960                         geom.X = stof(v_geom[0]) * spacing.X;
961                         geom.Y = stof(v_geom[1]) * spacing.Y;
962                 }
963
964                 bool clip = false;
965                 if (parts.size() >= 4 && is_yes(parts[3])) {
966                         if (data->real_coordinates) {
967                                 pos = getRealCoordinateBasePos(v_pos) * -1;
968                                 geom = v2s32(0, 0);
969                         } else {
970                                 pos.X = stoi(v_pos[0]); //acts as offset
971                                 pos.Y = stoi(v_pos[1]);
972                         }
973                         clip = true;
974                 }
975
976                 core::rect<s32> middle;
977                 if (parts.size() >= 5) {
978                         std::vector<std::string> v_middle = split(parts[4], ',');
979                         if (v_middle.size() == 1) {
980                                 s32 x = stoi(v_middle[0]);
981                                 middle.UpperLeftCorner = core::vector2di(x, x);
982                                 middle.LowerRightCorner = core::vector2di(-x, -x);
983                         } else if (v_middle.size() == 2) {
984                                 s32 x = stoi(v_middle[0]);
985                                 s32 y = stoi(v_middle[1]);
986                                 middle.UpperLeftCorner = core::vector2di(x, y);
987                                 middle.LowerRightCorner = core::vector2di(-x, -y);
988                                 // `-x` is interpreted as `w - x`
989                         } else if (v_middle.size() == 4) {
990                                 middle.UpperLeftCorner = core::vector2di(stoi(v_middle[0]), stoi(v_middle[1]));
991                                 middle.LowerRightCorner = core::vector2di(stoi(v_middle[2]), stoi(v_middle[3]));
992                         } else {
993                                 warningstream << "Invalid rectangle given to middle param of background[] element" << std::endl;
994                         }
995                 }
996
997                 if (!data->explicit_size && !clip)
998                         warningstream << "invalid use of unclipped background without a size[] element" << std::endl;
999
1000                 FieldSpec spec(
1001                         name,
1002                         L"",
1003                         L"",
1004                         258 + m_fields.size()
1005                 );
1006
1007                 core::rect<s32> rect;
1008                 if (!clip) {
1009                         // no auto_clip => position like normal image
1010                         rect = core::rect<s32>(pos, pos + geom);
1011                 } else {
1012                         // it will be auto-clipped when drawing
1013                         rect = core::rect<s32>(-pos, pos);
1014                 }
1015
1016                 GUIBackgroundImage *e = new GUIBackgroundImage(Environment, this, spec.fid,
1017                                 rect, name, middle, m_tsrc, clip);
1018
1019                 FATAL_ERROR_IF(!e, "Failed to create background formspec element");
1020
1021                 e->setNotClipped(true);
1022
1023                 e->setVisible(false); // the element is drawn manually before all others
1024
1025                 m_backgrounds.push_back(e);
1026                 m_fields.push_back(spec);
1027                 return;
1028         }
1029         errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'"  << std::endl;
1030 }
1031
1032 void GUIFormSpecMenu::parseTableOptions(parserData* data, const std::string &element)
1033 {
1034         std::vector<std::string> parts = split(element,';');
1035
1036         data->table_options.clear();
1037         for (const std::string &part : parts) {
1038                 // Parse table option
1039                 std::string opt = unescape_string(part);
1040                 data->table_options.push_back(GUITable::splitOption(opt));
1041         }
1042 }
1043
1044 void GUIFormSpecMenu::parseTableColumns(parserData* data, const std::string &element)
1045 {
1046         std::vector<std::string> parts = split(element,';');
1047
1048         data->table_columns.clear();
1049         for (const std::string &part : parts) {
1050                 std::vector<std::string> col_parts = split(part,',');
1051                 GUITable::TableColumn column;
1052                 // Parse column type
1053                 if (!col_parts.empty())
1054                         column.type = col_parts[0];
1055                 // Parse column options
1056                 for (size_t j = 1; j < col_parts.size(); ++j) {
1057                         std::string opt = unescape_string(col_parts[j]);
1058                         column.options.push_back(GUITable::splitOption(opt));
1059                 }
1060                 data->table_columns.push_back(column);
1061         }
1062 }
1063
1064 void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
1065 {
1066         std::vector<std::string> parts = split(element,';');
1067
1068         if (((parts.size() == 4) || (parts.size() == 5)) ||
1069                 ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
1070         {
1071                 std::vector<std::string> v_pos = split(parts[0],',');
1072                 std::vector<std::string> v_geom = split(parts[1],',');
1073                 std::string name = parts[2];
1074                 std::vector<std::string> items = split(parts[3],',');
1075                 std::string str_initial_selection;
1076                 std::string str_transparent = "false";
1077
1078                 if (parts.size() >= 5)
1079                         str_initial_selection = parts[4];
1080
1081                 MY_CHECKPOS("table",0);
1082                 MY_CHECKGEOM("table",1);
1083
1084                 v2s32 pos;
1085                 v2s32 geom;
1086
1087                 if (data->real_coordinates) {
1088                         pos = getRealCoordinateBasePos(v_pos);
1089                         geom = getRealCoordinateGeometry(v_geom);
1090                 } else {
1091                         pos = getElementBasePos(&v_pos);
1092                         geom.X = stof(v_geom[0]) * spacing.X;
1093                         geom.Y = stof(v_geom[1]) * spacing.Y;
1094                 }
1095
1096                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
1097
1098                 FieldSpec spec(
1099                         name,
1100                         L"",
1101                         L"",
1102                         258 + m_fields.size()
1103                 );
1104
1105                 spec.ftype = f_Table;
1106
1107                 for (std::string &item : items) {
1108                         item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
1109                 }
1110
1111                 //now really show table
1112                 GUITable *e = new GUITable(Environment, this, spec.fid, rect, m_tsrc);
1113
1114                 if (spec.fname == data->focused_fieldname) {
1115                         Environment->setFocus(e);
1116                 }
1117
1118                 e->setTable(data->table_options, data->table_columns, items);
1119
1120                 if (data->table_dyndata.find(name) != data->table_dyndata.end()) {
1121                         e->setDynamicData(data->table_dyndata[name]);
1122                 }
1123
1124                 if (!str_initial_selection.empty() && str_initial_selection != "0")
1125                         e->setSelected(stoi(str_initial_selection));
1126
1127                 auto style = getStyleForElement("table", name);
1128                 e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
1129
1130                 m_tables.emplace_back(spec, e);
1131                 m_fields.push_back(spec);
1132                 return;
1133         }
1134         errorstream<< "Invalid table element(" << parts.size() << "): '" << element << "'"  << std::endl;
1135 }
1136
1137 void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element)
1138 {
1139         std::vector<std::string> parts = split(element,';');
1140
1141         if (((parts.size() == 4) || (parts.size() == 5) || (parts.size() == 6)) ||
1142                 ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
1143         {
1144                 std::vector<std::string> v_pos = split(parts[0],',');
1145                 std::vector<std::string> v_geom = split(parts[1],',');
1146                 std::string name = parts[2];
1147                 std::vector<std::string> items = split(parts[3],',');
1148                 std::string str_initial_selection;
1149                 std::string str_transparent = "false";
1150
1151                 if (parts.size() >= 5)
1152                         str_initial_selection = parts[4];
1153
1154                 if (parts.size() >= 6)
1155                         str_transparent = parts[5];
1156
1157                 MY_CHECKPOS("textlist",0);
1158                 MY_CHECKGEOM("textlist",1);
1159
1160                 v2s32 pos;
1161                 v2s32 geom;
1162
1163                 if (data->real_coordinates) {
1164                         pos = getRealCoordinateBasePos(v_pos);
1165                         geom = getRealCoordinateGeometry(v_geom);
1166                 } else {
1167                         pos = getElementBasePos(&v_pos);
1168                         geom.X = stof(v_geom[0]) * spacing.X;
1169                         geom.Y = stof(v_geom[1]) * spacing.Y;
1170                 }
1171
1172                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
1173
1174                 FieldSpec spec(
1175                         name,
1176                         L"",
1177                         L"",
1178                         258 + m_fields.size()
1179                 );
1180
1181                 spec.ftype = f_Table;
1182
1183                 for (std::string &item : items) {
1184                         item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
1185                 }
1186
1187                 //now really show list
1188                 GUITable *e = new GUITable(Environment, this, spec.fid, rect, m_tsrc);
1189
1190                 if (spec.fname == data->focused_fieldname) {
1191                         Environment->setFocus(e);
1192                 }
1193
1194                 e->setTextList(items, is_yes(str_transparent));
1195
1196                 if (data->table_dyndata.find(name) != data->table_dyndata.end()) {
1197                         e->setDynamicData(data->table_dyndata[name]);
1198                 }
1199
1200                 if (!str_initial_selection.empty() && str_initial_selection != "0")
1201                         e->setSelected(stoi(str_initial_selection));
1202
1203                 auto style = getStyleForElement("textlist", name);
1204                 e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
1205
1206                 m_tables.emplace_back(spec, e);
1207                 m_fields.push_back(spec);
1208                 return;
1209         }
1210         errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'"  << std::endl;
1211 }
1212
1213
1214 void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element)
1215 {
1216         std::vector<std::string> parts = split(element,';');
1217
1218         if ((parts.size() == 5) ||
1219                 ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
1220         {
1221                 std::vector<std::string> v_pos = split(parts[0],',');
1222                 std::string name = parts[2];
1223                 std::vector<std::string> items = split(parts[3],',');
1224                 std::string str_initial_selection;
1225                 str_initial_selection = parts[4];
1226
1227                 MY_CHECKPOS("dropdown",0);
1228
1229                 v2s32 pos;
1230                 v2s32 geom;
1231                 core::rect<s32> rect;
1232
1233                 if (data->real_coordinates) {
1234                         std::vector<std::string> v_geom = split(parts[1],',');
1235
1236                         if (v_geom.size() == 1)
1237                                 v_geom.emplace_back("1");
1238
1239                         MY_CHECKGEOM("dropdown",1);
1240
1241                         pos = getRealCoordinateBasePos(v_pos);
1242                         geom = getRealCoordinateGeometry(v_geom);
1243                         rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
1244                 } else {
1245                         pos = getElementBasePos(&v_pos);
1246
1247                         s32 width = stof(parts[1]) * spacing.Y;
1248
1249                         rect = core::rect<s32>(pos.X, pos.Y,
1250                                         pos.X + width, pos.Y + (m_btn_height * 2));
1251                 }
1252
1253                 FieldSpec spec(
1254                         name,
1255                         L"",
1256                         L"",
1257                         258 + m_fields.size()
1258                 );
1259
1260                 spec.ftype = f_DropDown;
1261                 spec.send = true;
1262
1263                 //now really show list
1264                 gui::IGUIComboBox *e = Environment->addComboBox(rect, this, spec.fid);
1265
1266                 if (spec.fname == data->focused_fieldname) {
1267                         Environment->setFocus(e);
1268                 }
1269
1270                 for (const std::string &item : items) {
1271                         e->addItem(unescape_translate(unescape_string(
1272                                 utf8_to_wide(item))).c_str());
1273                 }
1274
1275                 if (!str_initial_selection.empty())
1276                         e->setSelected(stoi(str_initial_selection)-1);
1277
1278                 auto style = getStyleForElement("dropdown", name);
1279                 e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
1280
1281                 m_fields.push_back(spec);
1282
1283                 m_dropdowns.emplace_back(spec, std::vector<std::string>());
1284                 std::vector<std::string> &values = m_dropdowns.back().second;
1285                 for (const std::string &item : items) {
1286                         values.push_back(unescape_string(item));
1287                 }
1288
1289                 return;
1290         }
1291         errorstream << "Invalid dropdown element(" << parts.size() << "): '"
1292                                 << element << "'"  << std::endl;
1293 }
1294
1295 void GUIFormSpecMenu::parseFieldCloseOnEnter(parserData *data, const std::string &element)
1296 {
1297         std::vector<std::string> parts = split(element,';');
1298         if (parts.size() == 2 ||
1299                         (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) {
1300                 field_close_on_enter[parts[0]] = is_yes(parts[1]);
1301         }
1302 }
1303
1304 void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element)
1305 {
1306         std::vector<std::string> parts = split(element,';');
1307
1308         if ((parts.size() == 4) || (parts.size() == 5) ||
1309                 ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
1310         {
1311                 std::vector<std::string> v_pos = split(parts[0],',');
1312                 std::vector<std::string> v_geom = split(parts[1],',');
1313                 std::string name = parts[2];
1314                 std::string label = parts[3];
1315
1316                 MY_CHECKPOS("pwdfield",0);
1317                 MY_CHECKGEOM("pwdfield",1);
1318
1319                 v2s32 pos;
1320                 v2s32 geom;
1321
1322                 if (data->real_coordinates) {
1323                         pos = getRealCoordinateBasePos(v_pos);
1324                         geom = getRealCoordinateGeometry(v_geom);
1325                 } else {
1326                         pos = getElementBasePos(&v_pos);
1327                         pos -= padding;
1328
1329                         geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
1330
1331                         pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
1332                         pos.Y -= m_btn_height;
1333                         geom.Y = m_btn_height*2;
1334                 }
1335
1336                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
1337
1338                 std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
1339
1340                 FieldSpec spec(
1341                         name,
1342                         wlabel,
1343                         L"",
1344                         258 + m_fields.size()
1345                         );
1346
1347                 spec.send = true;
1348                 gui::IGUIEditBox * e = Environment->addEditBox(0, rect, true, this, spec.fid);
1349
1350                 if (spec.fname == data->focused_fieldname) {
1351                         Environment->setFocus(e);
1352                 }
1353
1354                 if (label.length() >= 1) {
1355                         int font_height = g_fontengine->getTextHeight();
1356                         rect.UpperLeftCorner.Y -= font_height;
1357                         rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
1358                         gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
1359                                 this, 0);
1360                 }
1361
1362                 e->setPasswordBox(true,L'*');
1363
1364                 auto style = getStyleForElement("pwdfield", name, "field");
1365                 e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
1366                 e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
1367                 e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
1368
1369                 irr::SEvent evt;
1370                 evt.EventType            = EET_KEY_INPUT_EVENT;
1371                 evt.KeyInput.Key         = KEY_END;
1372                 evt.KeyInput.Char        = 0;
1373                 evt.KeyInput.Control     = false;
1374                 evt.KeyInput.Shift       = false;
1375                 evt.KeyInput.PressedDown = true;
1376                 e->OnEvent(evt);
1377
1378                 if (parts.size() >= 5) {
1379                         // TODO: remove after 2016-11-03
1380                         warningstream << "pwdfield: use field_close_on_enter[name, enabled]" <<
1381                                         " instead of the 5th param" << std::endl;
1382                         field_close_on_enter[name] = is_yes(parts[4]);
1383                 }
1384
1385                 m_fields.push_back(spec);
1386                 return;
1387         }
1388         errorstream<< "Invalid pwdfield element(" << parts.size() << "): '" << element << "'"  << std::endl;
1389 }
1390
1391 void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
1392         core::rect<s32> &rect, bool is_multiline)
1393 {
1394         bool is_editable = !spec.fname.empty();
1395         if (!is_editable && !is_multiline) {
1396                 // spec field id to 0, this stops submit searching for a value that isn't there
1397                 gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
1398                                 this, spec.fid);
1399                 return;
1400         }
1401
1402         if (is_editable) {
1403                 spec.send = true;
1404         } else if (is_multiline &&
1405                         spec.fdefault.empty() && !spec.flabel.empty()) {
1406                 // Multiline textareas: swap default and label for backwards compat
1407                 spec.flabel.swap(spec.fdefault);
1408         }
1409
1410         gui::IGUIEditBox *e = nullptr;
1411         static constexpr bool use_intl_edit_box = USE_FREETYPE &&
1412                 IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9;
1413
1414         if (use_intl_edit_box && g_settings->getBool("freetype")) {
1415                 e = new gui::intlGUIEditBox(spec.fdefault.c_str(), true, Environment,
1416                                 this, spec.fid, rect, is_editable, is_multiline);
1417         } else {
1418                 if (is_multiline) {
1419                         e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true,
1420                                         Environment, this, spec.fid, rect, is_editable, true);
1421                 } else if (is_editable) {
1422                         e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this,
1423                                         spec.fid);
1424                         e->grab();
1425                 }
1426         }
1427
1428         auto style = getStyleForElement(is_multiline ? "textarea" : "field", spec.fname);
1429
1430         if (e) {
1431                 if (is_editable && spec.fname == data->focused_fieldname)
1432                         Environment->setFocus(e);
1433
1434                 if (is_multiline) {
1435                         e->setMultiLine(true);
1436                         e->setWordWrap(true);
1437                         e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT);
1438                 } else {
1439                         irr::SEvent evt;
1440                         evt.EventType            = EET_KEY_INPUT_EVENT;
1441                         evt.KeyInput.Key         = KEY_END;
1442                         evt.KeyInput.Char        = 0;
1443                         evt.KeyInput.Control     = 0;
1444                         evt.KeyInput.Shift       = 0;
1445                         evt.KeyInput.PressedDown = true;
1446                         e->OnEvent(evt);
1447                 }
1448
1449                 e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
1450                 e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
1451                 e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
1452                 if (style.get(StyleSpec::BGCOLOR, "") == "transparent") {
1453                         e->setDrawBackground(false);
1454                 }
1455
1456                 e->drop();
1457         }
1458
1459         if (!spec.flabel.empty()) {
1460                 int font_height = g_fontengine->getTextHeight();
1461                 rect.UpperLeftCorner.Y -= font_height;
1462                 rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
1463                 IGUIElement *t = gui::StaticText::add(Environment, spec.flabel.c_str(),
1464                                 rect, false, true, this, 0);
1465
1466                 if (t)
1467                         t->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
1468         }
1469 }
1470
1471 void GUIFormSpecMenu::parseSimpleField(parserData *data,
1472         std::vector<std::string> &parts)
1473 {
1474         std::string name = parts[0];
1475         std::string label = parts[1];
1476         std::string default_val = parts[2];
1477
1478         core::rect<s32> rect;
1479
1480         if (data->explicit_size)
1481                 warningstream << "invalid use of unpositioned \"field\" in inventory" << std::endl;
1482
1483         v2s32 pos = getElementBasePos(nullptr);
1484         pos.Y = (data->simple_field_count + 2) * 60;
1485         v2s32 size = DesiredRect.getSize();
1486
1487         rect = core::rect<s32>(
1488                         size.X / 2 - 150,       pos.Y,
1489                         size.X / 2 - 150 + 300, pos.Y + m_btn_height * 2
1490         );
1491
1492
1493         if (m_form_src)
1494                 default_val = m_form_src->resolveText(default_val);
1495
1496
1497         std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
1498
1499         FieldSpec spec(
1500                 name,
1501                 wlabel,
1502                 utf8_to_wide(unescape_string(default_val)),
1503                 258 + m_fields.size()
1504         );
1505
1506         createTextField(data, spec, rect, false);
1507
1508         m_fields.push_back(spec);
1509
1510         data->simple_field_count++;
1511 }
1512
1513 void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& parts,
1514                 const std::string &type)
1515 {
1516         std::vector<std::string> v_pos = split(parts[0],',');
1517         std::vector<std::string> v_geom = split(parts[1],',');
1518         std::string name = parts[2];
1519         std::string label = parts[3];
1520         std::string default_val = parts[4];
1521
1522         MY_CHECKPOS(type,0);
1523         MY_CHECKGEOM(type,1);
1524
1525         v2s32 pos;
1526         v2s32 geom;
1527
1528         if (data->real_coordinates) {
1529                 pos = getRealCoordinateBasePos(v_pos);
1530                 geom = getRealCoordinateGeometry(v_geom);
1531         } else {
1532                 pos = getElementBasePos(&v_pos);
1533                 pos -= padding;
1534
1535                 geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
1536
1537                 if (type == "textarea")
1538                 {
1539                         geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y);
1540                         pos.Y += m_btn_height;
1541                 }
1542                 else
1543                 {
1544                         pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
1545                         pos.Y -= m_btn_height;
1546                         geom.Y = m_btn_height*2;
1547                 }
1548         }
1549
1550         core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
1551
1552         if(!data->explicit_size)
1553                 warningstream<<"invalid use of positioned "<<type<<" without a size[] element"<<std::endl;
1554
1555         if(m_form_src)
1556                 default_val = m_form_src->resolveText(default_val);
1557
1558
1559         std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
1560
1561         FieldSpec spec(
1562                 name,
1563                 wlabel,
1564                 utf8_to_wide(unescape_string(default_val)),
1565                 258 + m_fields.size()
1566         );
1567
1568         createTextField(data, spec, rect, type == "textarea");
1569
1570         if (parts.size() >= 6) {
1571                 // TODO: remove after 2016-11-03
1572                 warningstream << "field/textarea: use field_close_on_enter[name, enabled]" <<
1573                                 " instead of the 6th param" << std::endl;
1574                 field_close_on_enter[name] = is_yes(parts[5]);
1575         }
1576
1577         m_fields.push_back(spec);
1578 }
1579
1580 void GUIFormSpecMenu::parseField(parserData* data, const std::string &element,
1581                 const std::string &type)
1582 {
1583         std::vector<std::string> parts = split(element,';');
1584
1585         if (parts.size() == 3 || parts.size() == 4) {
1586                 parseSimpleField(data,parts);
1587                 return;
1588         }
1589
1590         if ((parts.size() == 5) || (parts.size() == 6) ||
1591                 ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
1592         {
1593                 parseTextArea(data,parts,type);
1594                 return;
1595         }
1596         errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'"  << std::endl;
1597 }
1598
1599 void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &element)
1600 {
1601         std::vector<std::string> parts = split(element, ';');
1602
1603         if (parts.size() != 4 && m_formspec_version < FORMSPEC_API_VERSION) {
1604                 errorstream << "Invalid text element(" << parts.size() << "): '" << element << "'"  << std::endl;
1605                 return;
1606         }
1607
1608         std::vector<std::string> v_pos = split(parts[0], ',');
1609         std::vector<std::string> v_geom = split(parts[1], ',');
1610         std::string name = parts[2];
1611         std::string text = parts[3];
1612
1613         MY_CHECKPOS("hypertext", 0);
1614         MY_CHECKGEOM("hypertext", 1);
1615
1616         v2s32 pos;
1617         v2s32 geom;
1618
1619         if (data->real_coordinates) {
1620                 pos = getRealCoordinateBasePos(v_pos);
1621                 geom = getRealCoordinateGeometry(v_geom);
1622         } else {
1623                 pos = getElementBasePos(&v_pos);
1624                 pos -= padding;
1625
1626                 pos.X += stof(v_pos[0]) * spacing.X;
1627                 pos.Y += stof(v_pos[1]) * spacing.Y + (m_btn_height * 2);
1628
1629                 geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
1630                 geom.Y = (stof(v_geom[1]) * imgsize.Y) - (spacing.Y - imgsize.Y);
1631         }
1632
1633         core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y);
1634
1635         if(m_form_src)
1636                 text = m_form_src->resolveText(text);
1637
1638         FieldSpec spec(
1639                 name,
1640                 utf8_to_wide(unescape_string(text)),
1641                 L"",
1642                 258 + m_fields.size()
1643         );
1644
1645         spec.ftype = f_Unknown;
1646         new GUIHyperText(
1647                 spec.flabel.c_str(), Environment, this, spec.fid, rect, m_client, m_tsrc);
1648
1649         m_fields.push_back(spec);
1650 }
1651
1652 void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
1653 {
1654         std::vector<std::string> parts = split(element,';');
1655
1656         if ((parts.size() == 2) ||
1657                 ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
1658         {
1659                 std::vector<std::string> v_pos = split(parts[0],',');
1660                 std::string text = parts[1];
1661
1662                 MY_CHECKPOS("label",0);
1663
1664                 if(!data->explicit_size)
1665                         warningstream<<"invalid use of label without a size[] element"<<std::endl;
1666
1667                 std::vector<std::string> lines = split(text, '\n');
1668
1669                 for (unsigned int i = 0; i != lines.size(); i++) {
1670                         std::wstring wlabel_colors = translate_string(
1671                                 utf8_to_wide(unescape_string(lines[i])));
1672                         // Without color escapes to get the font dimensions
1673                         std::wstring wlabel_plain = unescape_enriched(wlabel_colors);
1674
1675                         core::rect<s32> rect;
1676
1677                         if (data->real_coordinates) {
1678                                 // Lines are spaced at the distance of 1/2 imgsize.
1679                                 // This alows lines that line up with the new elements
1680                                 // easily without sacrificing good line distance.  If
1681                                 // it was one whole imgsize, it would have too much
1682                                 // spacing.
1683                                 v2s32 pos = getRealCoordinateBasePos(v_pos);
1684
1685                                 // Labels are positioned by their center, not their top.
1686                                 pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2);
1687
1688                                 rect = core::rect<s32>(
1689                                         pos.X, pos.Y,
1690                                         pos.X + m_font->getDimension(wlabel_plain.c_str()).Width,
1691                                         pos.Y + imgsize.Y);
1692
1693                         } else {
1694                                 // Lines are spaced at the nominal distance of
1695                                 // 2/5 inventory slot, even if the font doesn't
1696                                 // quite match that.  This provides consistent
1697                                 // form layout, at the expense of sometimes
1698                                 // having sub-optimal spacing for the font.
1699                                 // We multiply by 2 and then divide by 5, rather
1700                                 // than multiply by 0.4, to get exact results
1701                                 // in the integer cases: 0.4 is not exactly
1702                                 // representable in binary floating point.
1703
1704                                 v2s32 pos = getElementBasePos(nullptr);
1705                                 pos.X += stof(v_pos[0]) * spacing.X;
1706                                 pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y;
1707
1708                                 pos.Y += ((float) i) * spacing.Y * 2.0 / 5.0;
1709
1710                                 rect = core::rect<s32>(
1711                                         pos.X, pos.Y - m_btn_height,
1712                                         pos.X + m_font->getDimension(wlabel_plain.c_str()).Width,
1713                                         pos.Y + m_btn_height);
1714                         }
1715
1716                         FieldSpec spec(
1717                                 "",
1718                                 wlabel_colors,
1719                                 L"",
1720                                 258 + m_fields.size(),
1721                                 4
1722                         );
1723                         gui::IGUIStaticText *e = gui::StaticText::add(Environment,
1724                                         spec.flabel.c_str(), rect, false, false, this, spec.fid);
1725                         e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER);
1726
1727                         auto style = getStyleForElement("label", spec.fname);
1728                         e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
1729                         e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
1730
1731                         m_fields.push_back(spec);
1732                 }
1733
1734                 return;
1735         }
1736         errorstream << "Invalid label element(" << parts.size() << "): '" << element
1737                 << "'"  << std::endl;
1738 }
1739
1740 void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &element)
1741 {
1742         std::vector<std::string> parts = split(element,';');
1743
1744         if ((parts.size() == 2) ||
1745                 ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
1746         {
1747                 std::vector<std::string> v_pos = split(parts[0],',');
1748                 std::wstring text = unescape_translate(
1749                         unescape_string(utf8_to_wide(parts[1])));
1750
1751                 MY_CHECKPOS("vertlabel",1);
1752
1753                 v2s32 pos;
1754                 core::rect<s32> rect;
1755
1756                 if (data->real_coordinates) {
1757                         pos = getRealCoordinateBasePos(v_pos);
1758
1759                         // Vertlabels are positioned by center, not left.
1760                         pos.X -= imgsize.X / 2;
1761
1762                         // We use text.length + 1 because without it, the rect
1763                         // isn't quite tall enough and cuts off the text.
1764                         rect = core::rect<s32>(pos.X, pos.Y,
1765                                 pos.X + imgsize.X,
1766                                 pos.Y + font_line_height(m_font) *
1767                                 (text.length() + 1));
1768
1769                 } else {
1770                         pos = getElementBasePos(&v_pos);
1771
1772                         // As above, the length must be one longer. The width of
1773                         // the rect (15 pixels) seems rather arbitrary, but
1774                         // changing it might break something.
1775                         rect = core::rect<s32>(
1776                                 pos.X, pos.Y+((imgsize.Y/2) - m_btn_height),
1777                                 pos.X+15, pos.Y +
1778                                         font_line_height(m_font) *
1779                                         (text.length() + 1) +
1780                                         ((imgsize.Y/2) - m_btn_height));
1781                 }
1782
1783                 if(!data->explicit_size)
1784                         warningstream<<"invalid use of label without a size[] element"<<std::endl;
1785
1786                 std::wstring label;
1787
1788                 for (wchar_t i : text) {
1789                         label += i;
1790                         label += L"\n";
1791                 }
1792
1793                 FieldSpec spec(
1794                         "",
1795                         label,
1796                         L"",
1797                         258 + m_fields.size()
1798                 );
1799                 gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(),
1800                                 rect, false, false, this, spec.fid);
1801                 e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
1802
1803                 auto style = getStyleForElement("vertlabel", spec.fname, "label");
1804                 e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
1805                 e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
1806
1807                 m_fields.push_back(spec);
1808                 return;
1809         }
1810         errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'"  << std::endl;
1811 }
1812
1813 void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &element,
1814                 const std::string &type)
1815 {
1816         std::vector<std::string> parts = split(element,';');
1817
1818         if ((((parts.size() >= 5) && (parts.size() <= 8)) && (parts.size() != 6)) ||
1819                 ((parts.size() > 8) && (m_formspec_version > FORMSPEC_API_VERSION)))
1820         {
1821                 std::vector<std::string> v_pos = split(parts[0],',');
1822                 std::vector<std::string> v_geom = split(parts[1],',');
1823                 std::string image_name = parts[2];
1824                 std::string name = parts[3];
1825                 std::string label = parts[4];
1826
1827                 MY_CHECKPOS("imagebutton",0);
1828                 MY_CHECKGEOM("imagebutton",1);
1829
1830                 bool noclip     = false;
1831                 bool drawborder = true;
1832                 std::string pressed_image_name;
1833
1834                 if (parts.size() >= 7) {
1835                         if (parts[5] == "true")
1836                                 noclip = true;
1837                         if (parts[6] == "false")
1838                                 drawborder = false;
1839                 }
1840
1841                 if (parts.size() >= 8) {
1842                         pressed_image_name = parts[7];
1843                 }
1844
1845                 v2s32 pos;
1846                 v2s32 geom;
1847
1848                 if (data->real_coordinates) {
1849                         pos = getRealCoordinateBasePos(v_pos);
1850                         geom = getRealCoordinateGeometry(v_geom);
1851                 } else {
1852                         pos = getElementBasePos(&v_pos);
1853                         geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
1854                         geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y);
1855                 }
1856
1857                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
1858                         pos.Y+geom.Y);
1859
1860                 if (!data->explicit_size)
1861                         warningstream<<"invalid use of image_button without a size[] element"<<std::endl;
1862
1863                 image_name = unescape_string(image_name);
1864                 pressed_image_name = unescape_string(pressed_image_name);
1865
1866                 std::wstring wlabel = utf8_to_wide(unescape_string(label));
1867
1868                 FieldSpec spec(
1869                         name,
1870                         wlabel,
1871                         utf8_to_wide(image_name),
1872                         258 + m_fields.size()
1873                 );
1874                 spec.ftype = f_Button;
1875                 if (type == "image_button_exit")
1876                         spec.is_exit = true;
1877
1878                 video::ITexture *texture = 0;
1879                 video::ITexture *pressed_texture = 0;
1880                 texture = m_tsrc->getTexture(image_name);
1881                 if (!pressed_image_name.empty())
1882                         pressed_texture = m_tsrc->getTexture(pressed_image_name);
1883                 else
1884                         pressed_texture = texture;
1885
1886                 GUIButton *e = GUIButton::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str());
1887
1888                 if (spec.fname == data->focused_fieldname) {
1889                         Environment->setFocus(e);
1890                 }
1891
1892                 auto style = getStyleForElement("image_button", spec.fname);
1893
1894                 e->setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true));
1895                 e->setImage(guiScalingImageButton(
1896                         Environment->getVideoDriver(), texture, geom.X, geom.Y));
1897                 e->setPressedImage(guiScalingImageButton(
1898                         Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
1899                 e->setScaleImage(true);
1900                 if (parts.size() >= 7) {
1901                         e->setNotClipped(noclip);
1902                         e->setDrawBorder(drawborder);
1903                 } else {
1904                         e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
1905                         e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
1906                 }
1907
1908                 m_fields.push_back(spec);
1909                 return;
1910         }
1911
1912         errorstream<< "Invalid imagebutton element(" << parts.size() << "): '" << element << "'"  << std::endl;
1913 }
1914
1915 void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &element)
1916 {
1917         std::vector<std::string> parts = split(element, ';');
1918
1919         if (((parts.size() == 4) || (parts.size() == 6)) || (parts.size() == 7 &&
1920                 data->real_coordinates) || ((parts.size() > 6) &&
1921                 (m_formspec_version > FORMSPEC_API_VERSION)))
1922         {
1923                 std::vector<std::string> v_pos = split(parts[0],',');
1924
1925                 // If we're using real coordinates, add an extra field for height.
1926                 // Width is not here because tabs are the width of the text, and
1927                 // there's no reason to change that.
1928                 unsigned int i = 0;
1929                 std::vector<std::string> v_geom = {"1", "0.75"}; // Dummy width and default height
1930                 bool auto_width = true;
1931                 if (parts.size() == 7) {
1932                         i++;
1933
1934                         v_geom = split(parts[1], ',');
1935                         if (v_geom.size() == 1)
1936                                 v_geom.insert(v_geom.begin(), "1"); // Dummy value
1937                         else
1938                                 auto_width = false;
1939                 }
1940
1941                 std::string name = parts[i+1];
1942                 std::vector<std::string> buttons = split(parts[i+2], ',');
1943                 std::string str_index = parts[i+3];
1944                 bool show_background = true;
1945                 bool show_border = true;
1946                 int tab_index = stoi(str_index) - 1;
1947
1948                 MY_CHECKPOS("tabheader", 0);
1949
1950                 if (parts.size() == 6 + i) {
1951                         if (parts[4+i] == "true")
1952                                 show_background = false;
1953                         if (parts[5+i] == "false")
1954                                 show_border = false;
1955                 }
1956
1957                 FieldSpec spec(
1958                         name,
1959                         L"",
1960                         L"",
1961                         258 + m_fields.size()
1962                 );
1963
1964                 spec.ftype = f_TabHeader;
1965
1966                 v2s32 pos;
1967                 v2s32 geom;
1968
1969                 if (data->real_coordinates) {
1970                         pos = getRealCoordinateBasePos(v_pos);
1971
1972                         geom = getRealCoordinateGeometry(v_geom);
1973                         pos.Y -= geom.Y; // TabHeader base pos is the bottom, not the top.
1974                         if (auto_width)
1975                                 geom.X = DesiredRect.getWidth(); // Set automatic width
1976
1977                         MY_CHECKGEOM("tabheader", 1);
1978                 } else {
1979                         v2f32 pos_f = pos_offset * spacing;
1980                         pos_f.X += stof(v_pos[0]) * spacing.X;
1981                         pos_f.Y += stof(v_pos[1]) * spacing.Y - m_btn_height * 2;
1982                         pos = v2s32(pos_f.X, pos_f.Y);
1983
1984                         geom.Y = m_btn_height * 2;
1985                         geom.X = DesiredRect.getWidth();
1986                 }
1987
1988                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
1989                                 pos.Y+geom.Y);
1990
1991                 gui::IGUITabControl *e = Environment->addTabControl(rect, this,
1992                                 show_background, show_border, spec.fid);
1993                 e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT,
1994                                 irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT);
1995                 e->setTabHeight(geom.Y);
1996
1997                 if (spec.fname == data->focused_fieldname) {
1998                         Environment->setFocus(e);
1999                 }
2000
2001                 auto style = getStyleForElement("tabheader", name);
2002                 e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true));
2003
2004                 for (const std::string &button : buttons) {
2005                         auto tab = e->addTab(unescape_translate(unescape_string(
2006                                 utf8_to_wide(button))).c_str(), -1);
2007                         if (style.isNotDefault(StyleSpec::BGCOLOR))
2008                                 tab->setBackgroundColor(style.getColor(StyleSpec::BGCOLOR));
2009
2010                         tab->setTextColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
2011                 }
2012
2013                 if ((tab_index >= 0) &&
2014                                 (buttons.size() < INT_MAX) &&
2015                                 (tab_index < (int) buttons.size()))
2016                         e->setActiveTab(tab_index);
2017
2018                 m_fields.push_back(spec);
2019                 return;
2020         }
2021         errorstream << "Invalid TabHeader element(" << parts.size() << "): '"
2022                         << element << "'"  << std::endl;
2023 }
2024
2025 void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element)
2026 {
2027
2028         if (m_client == 0) {
2029                 warningstream << "invalid use of item_image_button with m_client==0"
2030                         << std::endl;
2031                 return;
2032         }
2033
2034         std::vector<std::string> parts = split(element,';');
2035
2036         if ((parts.size() == 5) ||
2037                 ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
2038         {
2039                 std::vector<std::string> v_pos = split(parts[0],',');
2040                 std::vector<std::string> v_geom = split(parts[1],',');
2041                 std::string item_name = parts[2];
2042                 std::string name = parts[3];
2043                 std::string label = parts[4];
2044
2045                 label = unescape_string(label);
2046                 item_name = unescape_string(item_name);
2047
2048                 MY_CHECKPOS("itemimagebutton",0);
2049                 MY_CHECKGEOM("itemimagebutton",1);
2050
2051                 v2s32 pos;
2052                 v2s32 geom;
2053
2054                 if (data->real_coordinates) {
2055                         pos = getRealCoordinateBasePos(v_pos);
2056                         geom = getRealCoordinateGeometry(v_geom);
2057                 } else {
2058                         pos = getElementBasePos(&v_pos);
2059                         geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
2060                         geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y);
2061                 }
2062
2063                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
2064
2065                 if(!data->explicit_size)
2066                         warningstream<<"invalid use of item_image_button without a size[] element"<<std::endl;
2067
2068                 IItemDefManager *idef = m_client->idef();
2069                 ItemStack item;
2070                 item.deSerialize(item_name, idef);
2071
2072                 m_tooltips[name] =
2073                         TooltipSpec(utf8_to_wide(item.getDefinition(idef).description),
2074                                                 m_default_tooltip_bgcolor,
2075                                                 m_default_tooltip_color);
2076
2077                 // the spec for the button
2078                 FieldSpec spec_btn(
2079                         name,
2080                         utf8_to_wide(label),
2081                         utf8_to_wide(item_name),
2082                         258 + m_fields.size(),
2083                         2
2084                 );
2085
2086                 gui::IGUIButton *e_btn = GUIButton::addButton(Environment, rect, this, spec_btn.fid, L"");
2087
2088                 auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button");
2089                 e_btn->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
2090                 e_btn->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
2091
2092                 if (spec_btn.fname == data->focused_fieldname) {
2093                         Environment->setFocus(e_btn);
2094                 }
2095
2096                 spec_btn.ftype = f_Button;
2097                 rect += data->basepos-padding;
2098                 spec_btn.rect = rect;
2099                 m_fields.push_back(spec_btn);
2100
2101                 // the spec for the item-image
2102                 FieldSpec spec_img(
2103                         name,
2104                         L"",
2105                         L"",
2106                         258 + m_fields.size(),
2107                         2
2108                 );
2109
2110                 GUIItemImage *e_img = new GUIItemImage(Environment, e_btn, spec_img.fid,
2111                                 core::rect<s32>(0, 0, geom.X, geom.Y), item_name, m_font, m_client);
2112
2113                 e_img->setText(utf8_to_wide(label).c_str());
2114
2115                 e_img->drop();
2116
2117                 m_fields.push_back(spec_img);
2118                 return;
2119         }
2120         errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'"  << std::endl;
2121 }
2122
2123 void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
2124 {
2125         std::vector<std::string> parts = split(element,';');
2126
2127         if ((parts.size() == 3) ||
2128                 ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
2129         {
2130                 std::vector<std::string> v_pos = split(parts[0],',');
2131                 std::vector<std::string> v_geom = split(parts[1],',');
2132
2133                 MY_CHECKPOS("box",0);
2134                 MY_CHECKGEOM("box",1);
2135
2136                 v2s32 pos;
2137                 v2s32 geom;
2138
2139                 if (data->real_coordinates) {
2140                         pos = getRealCoordinateBasePos(v_pos);
2141                         geom = getRealCoordinateGeometry(v_geom);
2142                 } else {
2143                         pos = getElementBasePos(&v_pos);
2144                         geom.X = stof(v_geom[0]) * spacing.X;
2145                         geom.Y = stof(v_geom[1]) * spacing.Y;
2146                 }
2147
2148                 video::SColor tmp_color;
2149
2150                 if (parseColorString(parts[2], tmp_color, false, 0x8C)) {
2151                         FieldSpec spec(
2152                                 "",
2153                                 L"",
2154                                 L"",
2155                                 258 + m_fields.size(),
2156                                 -2
2157                         );
2158                         spec.ftype = f_Box;
2159
2160                         core::rect<s32> rect(pos, pos + geom);
2161
2162                         GUIBox *e = new GUIBox(Environment, this, spec.fid, rect, tmp_color);
2163
2164                         e->setNotClipped(true);
2165
2166                         e->drop();
2167
2168                         m_fields.push_back(spec);
2169
2170                 } else {
2171                         errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'  INVALID COLOR"  << std::endl;
2172                 }
2173                 return;
2174         }
2175         errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'"  << std::endl;
2176 }
2177
2178 void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element)
2179 {
2180         std::vector<std::string> parts = split(element,';');
2181         const u32 parameter_count = parts.size();
2182
2183         if ((parameter_count > 2 && m_formspec_version < 3) ||
2184                         (parameter_count > 3 && m_formspec_version <= FORMSPEC_API_VERSION)) {
2185                 errorstream << "Invalid bgcolor element(" << parameter_count << "): '"
2186                                 << element << "'" << std::endl;
2187                 return;
2188         }
2189
2190         // bgcolor
2191         if (parameter_count >= 1 && parts[0] != "")
2192                 parseColorString(parts[0], m_bgcolor, false);
2193
2194         // fullscreen
2195         if (parameter_count >= 2) {
2196                 if (parts[1] == "both") {
2197                         m_bgnonfullscreen = true;
2198                         m_bgfullscreen = true;
2199                 } else if (parts[1] == "neither") {
2200                         m_bgnonfullscreen = false;
2201                         m_bgfullscreen = false;
2202                 } else if (parts[1] != "" || m_formspec_version < 3) {
2203                         m_bgfullscreen = is_yes(parts[1]);
2204                         m_bgnonfullscreen = !m_bgfullscreen;
2205                 }
2206         }
2207
2208         // fbgcolor
2209         if (parameter_count >= 3 && parts[2] != "")
2210                 parseColorString(parts[2], m_fullscreen_bgcolor, false);
2211 }
2212
2213 void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &element)
2214 {
2215         std::vector<std::string> parts = split(element,';');
2216
2217         if (((parts.size() == 2) || (parts.size() == 3) || (parts.size() == 5)) ||
2218                 ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
2219         {
2220                 parseColorString(parts[0], m_slotbg_n, false);
2221                 parseColorString(parts[1], m_slotbg_h, false);
2222
2223                 if (parts.size() >= 3) {
2224                         if (parseColorString(parts[2], m_slotbordercolor, false)) {
2225                                 m_slotborder = true;
2226                         }
2227                 }
2228                 if (parts.size() == 5) {
2229                         video::SColor tmp_color;
2230
2231                         if (parseColorString(parts[3], tmp_color, false))
2232                                 m_default_tooltip_bgcolor = tmp_color;
2233                         if (parseColorString(parts[4], tmp_color, false))
2234                                 m_default_tooltip_color = tmp_color;
2235                 }
2236                 return;
2237         }
2238         errorstream<< "Invalid listcolors element(" << parts.size() << "): '" << element << "'"  << std::endl;
2239 }
2240
2241 void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element)
2242 {
2243         std::vector<std::string> parts = split(element,';');
2244         if (parts.size() < 2) {
2245                 errorstream << "Invalid tooltip element(" << parts.size() << "): '"
2246                                 << element << "'"  << std::endl;
2247                 return;
2248         }
2249
2250         // Get mode and check size
2251         bool rect_mode = parts[0].find(',') != std::string::npos;
2252         size_t base_size = rect_mode ? 3 : 2;
2253         if (parts.size() != base_size && parts.size() != base_size + 2) {
2254                 errorstream << "Invalid tooltip element(" << parts.size() << "): '"
2255                                 << element << "'"  << std::endl;
2256                 return;
2257         }
2258
2259         // Read colors
2260         video::SColor bgcolor = m_default_tooltip_bgcolor;
2261         video::SColor color   = m_default_tooltip_color;
2262         if (parts.size() == base_size + 2 &&
2263                         (!parseColorString(parts[base_size], bgcolor, false) ||
2264                                 !parseColorString(parts[base_size + 1], color, false))) {
2265                 errorstream << "Invalid color in tooltip element(" << parts.size()
2266                                 << "): '" << element << "'"  << std::endl;
2267                 return;
2268         }
2269
2270         // Make tooltip spec
2271         std::string text = unescape_string(parts[rect_mode ? 2 : 1]);
2272         TooltipSpec spec(utf8_to_wide(text), bgcolor, color);
2273
2274         // Add tooltip
2275         if (rect_mode) {
2276                 std::vector<std::string> v_pos  = split(parts[0], ',');
2277                 std::vector<std::string> v_geom = split(parts[1], ',');
2278
2279                 MY_CHECKPOS("tooltip", 0);
2280                 MY_CHECKGEOM("tooltip", 1);
2281
2282                 v2s32 pos;
2283                 v2s32 geom;
2284
2285                 if (data->real_coordinates) {
2286                         pos = getRealCoordinateBasePos(v_pos);
2287                         geom = getRealCoordinateGeometry(v_geom);
2288                 } else {
2289                         pos = getElementBasePos(&v_pos);
2290                         geom.X = stof(v_geom[0]) * spacing.X;
2291                         geom.Y = stof(v_geom[1]) * spacing.Y;
2292                 }
2293
2294                 FieldSpec fieldspec(
2295                         "",
2296                         L"",
2297                         L"",
2298                         258 + m_fields.size()
2299                 );
2300
2301                 core::rect<s32> rect(pos, pos + geom);
2302
2303                 gui::IGUIElement *e = new gui::IGUIElement(EGUIET_ELEMENT, Environment,
2304                                 this, fieldspec.fid, rect);
2305
2306                 m_fields.push_back(fieldspec);
2307                 m_tooltip_rects.emplace_back(e, spec);
2308
2309         } else {
2310                 m_tooltips[parts[0]] = spec;
2311         }
2312 }
2313
2314 bool GUIFormSpecMenu::parseVersionDirect(const std::string &data)
2315 {
2316         //some prechecks
2317         if (data.empty())
2318                 return false;
2319
2320         std::vector<std::string> parts = split(data,'[');
2321
2322         if (parts.size() < 2) {
2323                 return false;
2324         }
2325
2326         if (parts[0] != "formspec_version") {
2327                 return false;
2328         }
2329
2330         if (is_number(parts[1])) {
2331                 m_formspec_version = mystoi(parts[1]);
2332                 return true;
2333         }
2334
2335         return false;
2336 }
2337
2338 bool GUIFormSpecMenu::parseSizeDirect(parserData* data, const std::string &element)
2339 {
2340         if (element.empty())
2341                 return false;
2342
2343         std::vector<std::string> parts = split(element,'[');
2344
2345         if (parts.size() < 2)
2346                 return false;
2347
2348         std::string type = trim(parts[0]);
2349         std::string description = trim(parts[1]);
2350
2351         if (type != "size" && type != "invsize")
2352                 return false;
2353
2354         if (type == "invsize")
2355                 log_deprecated("Deprecated formspec element \"invsize\" is used");
2356
2357         parseSize(data, description);
2358
2359         return true;
2360 }
2361
2362 bool GUIFormSpecMenu::parsePositionDirect(parserData *data, const std::string &element)
2363 {
2364         if (element.empty())
2365                 return false;
2366
2367         std::vector<std::string> parts = split(element, '[');
2368
2369         if (parts.size() != 2)
2370                 return false;
2371
2372         std::string type = trim(parts[0]);
2373         std::string description = trim(parts[1]);
2374
2375         if (type != "position")
2376                 return false;
2377
2378         parsePosition(data, description);
2379
2380         return true;
2381 }
2382
2383 void GUIFormSpecMenu::parsePosition(parserData *data, const std::string &element)
2384 {
2385         std::vector<std::string> parts = split(element, ',');
2386
2387         if (parts.size() == 2) {
2388                 data->offset.X = stof(parts[0]);
2389                 data->offset.Y = stof(parts[1]);
2390                 return;
2391         }
2392
2393         errorstream << "Invalid position element (" << parts.size() << "): '" << element << "'" << std::endl;
2394 }
2395
2396 bool GUIFormSpecMenu::parseAnchorDirect(parserData *data, const std::string &element)
2397 {
2398         if (element.empty())
2399                 return false;
2400
2401         std::vector<std::string> parts = split(element, '[');
2402
2403         if (parts.size() != 2)
2404                 return false;
2405
2406         std::string type = trim(parts[0]);
2407         std::string description = trim(parts[1]);
2408
2409         if (type != "anchor")
2410                 return false;
2411
2412         parseAnchor(data, description);
2413
2414         return true;
2415 }
2416
2417 void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element)
2418 {
2419         std::vector<std::string> parts = split(element, ',');
2420
2421         if (parts.size() == 2) {
2422                 data->anchor.X = stof(parts[0]);
2423                 data->anchor.Y = stof(parts[1]);
2424                 return;
2425         }
2426
2427         errorstream << "Invalid anchor element (" << parts.size() << "): '" << element
2428                         << "'" << std::endl;
2429 }
2430
2431 bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, bool style_type)
2432 {
2433         std::vector<std::string> parts = split(element, ';');
2434
2435         if (parts.size() < 2) {
2436                 errorstream << "Invalid style element (" << parts.size() << "): '" << element
2437                                         << "'" << std::endl;
2438                 return false;
2439         }
2440
2441         std::string selector = trim(parts[0]);
2442         if (selector.empty()) {
2443                 errorstream << "Invalid style element (Selector required): '" << element
2444                                         << "'" << std::endl;
2445                 return false;
2446         }
2447
2448         StyleSpec spec;
2449
2450         for (size_t i = 1; i < parts.size(); i++) {
2451                 size_t equal_pos = parts[i].find('=');
2452                 if (equal_pos == std::string::npos) {
2453                         errorstream << "Invalid style element (Property missing value): '" << element
2454                                                 << "'" << std::endl;
2455                         return false;
2456                 }
2457
2458                 std::string propname = trim(parts[i].substr(0, equal_pos));
2459                 std::string value    = trim(unescape_string(parts[i].substr(equal_pos + 1)));
2460
2461                 std::transform(propname.begin(), propname.end(), propname.begin(), ::tolower);
2462
2463                 StyleSpec::Property prop = StyleSpec::GetPropertyByName(propname);
2464                 if (prop == StyleSpec::NONE) {
2465                         if (property_warned.find(propname) != property_warned.end()) {
2466                                 warningstream << "Invalid style element (Unknown property " << propname << "): '"
2467                                                 << element
2468                                                 << "'" << std::endl;
2469                                 property_warned.insert(propname);
2470                         }
2471                         return false;
2472                 }
2473
2474                 spec.set(prop, value);
2475         }
2476
2477         if (style_type) {
2478                 theme_by_type[selector] |= spec;
2479         } else {
2480                 theme_by_name[selector] |= spec;
2481         }
2482
2483         return true;
2484 }
2485
2486 void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
2487 {
2488         //some prechecks
2489         if (element.empty())
2490                 return;
2491
2492         if (parseVersionDirect(element))
2493                 return;
2494
2495         std::vector<std::string> parts = split(element,'[');
2496
2497         // ugly workaround to keep compatibility
2498         if (parts.size() > 2) {
2499                 if (trim(parts[0]) == "image") {
2500                         for (unsigned int i=2;i< parts.size(); i++) {
2501                                 parts[1] += "[" + parts[i];
2502                         }
2503                 }
2504                 else { return; }
2505         }
2506
2507         if (parts.size() < 2) {
2508                 return;
2509         }
2510
2511         std::string type = trim(parts[0]);
2512         std::string description = trim(parts[1]);
2513
2514         if (type == "container") {
2515                 parseContainer(data, description);
2516                 return;
2517         }
2518
2519         if (type == "container_end") {
2520                 parseContainerEnd(data);
2521                 return;
2522         }
2523
2524         if (type == "list") {
2525                 parseList(data, description);
2526                 return;
2527         }
2528
2529         if (type == "listring") {
2530                 parseListRing(data, description);
2531                 return;
2532         }
2533
2534         if (type == "checkbox") {
2535                 parseCheckbox(data, description);
2536                 return;
2537         }
2538
2539         if (type == "image") {
2540                 parseImage(data, description);
2541                 return;
2542         }
2543
2544         if (type == "item_image") {
2545                 parseItemImage(data, description);
2546                 return;
2547         }
2548
2549         if (type == "button" || type == "button_exit") {
2550                 parseButton(data, description, type);
2551                 return;
2552         }
2553
2554         if (type == "background" || type == "background9") {
2555                 parseBackground(data, description);
2556                 return;
2557         }
2558
2559         if (type == "tableoptions"){
2560                 parseTableOptions(data,description);
2561                 return;
2562         }
2563
2564         if (type == "tablecolumns"){
2565                 parseTableColumns(data,description);
2566                 return;
2567         }
2568
2569         if (type == "table"){
2570                 parseTable(data,description);
2571                 return;
2572         }
2573
2574         if (type == "textlist"){
2575                 parseTextList(data,description);
2576                 return;
2577         }
2578
2579         if (type == "dropdown"){
2580                 parseDropDown(data,description);
2581                 return;
2582         }
2583
2584         if (type == "field_close_on_enter") {
2585                 parseFieldCloseOnEnter(data, description);
2586                 return;
2587         }
2588
2589         if (type == "pwdfield") {
2590                 parsePwdField(data,description);
2591                 return;
2592         }
2593
2594         if ((type == "field") || (type == "textarea")){
2595                 parseField(data,description,type);
2596                 return;
2597         }
2598
2599         if (type == "hypertext") {
2600                 parseHyperText(data,description);
2601                 return;
2602         }
2603
2604         if (type == "label") {
2605                 parseLabel(data,description);
2606                 return;
2607         }
2608
2609         if (type == "vertlabel") {
2610                 parseVertLabel(data,description);
2611                 return;
2612         }
2613
2614         if (type == "item_image_button") {
2615                 parseItemImageButton(data,description);
2616                 return;
2617         }
2618
2619         if ((type == "image_button") || (type == "image_button_exit")) {
2620                 parseImageButton(data,description,type);
2621                 return;
2622         }
2623
2624         if (type == "tabheader") {
2625                 parseTabHeader(data,description);
2626                 return;
2627         }
2628
2629         if (type == "box") {
2630                 parseBox(data,description);
2631                 return;
2632         }
2633
2634         if (type == "bgcolor") {
2635                 parseBackgroundColor(data,description);
2636                 return;
2637         }
2638
2639         if (type == "listcolors") {
2640                 parseListColors(data,description);
2641                 return;
2642         }
2643
2644         if (type == "tooltip") {
2645                 parseTooltip(data,description);
2646                 return;
2647         }
2648
2649         if (type == "scrollbar") {
2650                 parseScrollBar(data, description);
2651                 return;
2652         }
2653
2654         if (type == "real_coordinates") {
2655                 data->real_coordinates = is_yes(description);
2656                 return;
2657         }
2658
2659         if (type == "style") {
2660                 parseStyle(data, description, false);
2661                 return;
2662         }
2663
2664         if (type == "style_type") {
2665                 parseStyle(data, description, true);
2666                 return;
2667         }
2668
2669         if (type == "scrollbaroptions") {
2670                 parseScrollBarOptions(data, description);
2671                 return;
2672         }
2673
2674         // Ignore others
2675         infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
2676                         << std::endl;
2677 }
2678
2679 void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
2680 {
2681         /* useless to regenerate without a screensize */
2682         if ((screensize.X <= 0) || (screensize.Y <= 0)) {
2683                 return;
2684         }
2685
2686         parserData mydata;
2687
2688         //preserve tables
2689         for (auto &m_table : m_tables) {
2690                 std::string tablename = m_table.first.fname;
2691                 GUITable *table = m_table.second;
2692                 mydata.table_dyndata[tablename] = table->getDynamicData();
2693         }
2694
2695         //set focus
2696         if (!m_focused_element.empty())
2697                 mydata.focused_fieldname = m_focused_element;
2698
2699         //preserve focus
2700         gui::IGUIElement *focused_element = Environment->getFocus();
2701         if (focused_element && focused_element->getParent() == this) {
2702                 s32 focused_id = focused_element->getID();
2703                 if (focused_id > 257) {
2704                         for (const GUIFormSpecMenu::FieldSpec &field : m_fields) {
2705                                 if (field.fid == focused_id) {
2706                                         mydata.focused_fieldname = field.fname;
2707                                         break;
2708                                 }
2709                         }
2710                 }
2711         }
2712
2713         // Remove children
2714         removeChildren();
2715
2716         for (auto &table_it : m_tables)
2717                 table_it.second->drop();
2718         for (auto &inventorylist_it : m_inventorylists)
2719                 inventorylist_it.e->drop();
2720         for (auto &checkbox_it : m_checkboxes)
2721                 checkbox_it.second->drop();
2722         for (auto &scrollbar_it : m_scrollbars)
2723                 scrollbar_it.second->drop();
2724         for (auto &background_it : m_backgrounds)
2725                 background_it->drop();
2726         for (auto &tooltip_rect_it : m_tooltip_rects)
2727                 tooltip_rect_it.first->drop();
2728
2729         mydata.size= v2s32(100,100);
2730         mydata.screensize = screensize;
2731         mydata.offset = v2f32(0.5f, 0.5f);
2732         mydata.anchor = v2f32(0.5f, 0.5f);
2733         mydata.simple_field_count = 0;
2734
2735         // Base position of contents of form
2736         mydata.basepos = getBasePos();
2737
2738         /* Convert m_init_draw_spec to m_inventorylists */
2739
2740         m_inventorylists.clear();
2741         m_backgrounds.clear();
2742         m_tables.clear();
2743         m_checkboxes.clear();
2744         m_scrollbars.clear();
2745         m_fields.clear();
2746         m_tooltips.clear();
2747         m_tooltip_rects.clear();
2748         m_inventory_rings.clear();
2749         m_dropdowns.clear();
2750         theme_by_name.clear();
2751         theme_by_type.clear();
2752
2753         m_bgnonfullscreen = true;
2754         m_bgfullscreen = false;
2755
2756         m_formspec_version = 1;
2757
2758         {
2759                 v3f formspec_bgcolor = g_settings->getV3F("formspec_default_bg_color");
2760                 m_bgcolor = video::SColor(
2761                         (u8) clamp_u8(g_settings->getS32("formspec_default_bg_opacity")),
2762                         clamp_u8(myround(formspec_bgcolor.X)),
2763                         clamp_u8(myround(formspec_bgcolor.Y)),
2764                         clamp_u8(myround(formspec_bgcolor.Z))
2765                 );
2766         }
2767
2768         {
2769                 v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color");
2770                 m_fullscreen_bgcolor = video::SColor(
2771                         (u8) clamp_u8(g_settings->getS32("formspec_fullscreen_bg_opacity")),
2772                         clamp_u8(myround(formspec_bgcolor.X)),
2773                         clamp_u8(myround(formspec_bgcolor.Y)),
2774                         clamp_u8(myround(formspec_bgcolor.Z))
2775                 );
2776         }
2777
2778         m_slotbg_n = video::SColor(255,128,128,128);
2779         m_slotbg_h = video::SColor(255,192,192,192);
2780
2781         m_default_tooltip_bgcolor = video::SColor(255,110,130,60);
2782         m_default_tooltip_color = video::SColor(255,255,255,255);
2783
2784         m_slotbordercolor = video::SColor(200,0,0,0);
2785         m_slotborder = false;
2786
2787         // Add tooltip
2788         {
2789                 assert(!m_tooltip_element);
2790                 // Note: parent != this so that the tooltip isn't clipped by the menu rectangle
2791                 m_tooltip_element = gui::StaticText::add(Environment, L"",
2792                         core::rect<s32>(0, 0, 110, 18));
2793                 m_tooltip_element->enableOverrideColor(true);
2794                 m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor);
2795                 m_tooltip_element->setDrawBackground(true);
2796                 m_tooltip_element->setDrawBorder(true);
2797                 m_tooltip_element->setOverrideColor(m_default_tooltip_color);
2798                 m_tooltip_element->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
2799                 m_tooltip_element->setWordWrap(false);
2800                 //we're not parent so no autograb for this one!
2801                 m_tooltip_element->grab();
2802         }
2803
2804         std::vector<std::string> elements = split(m_formspec_string,']');
2805         unsigned int i = 0;
2806
2807         /* try to read version from first element only */
2808         if (!elements.empty()) {
2809                 if (parseVersionDirect(elements[0])) {
2810                         i++;
2811                 }
2812         }
2813
2814         /* we need size first in order to calculate image scale */
2815         mydata.explicit_size = false;
2816         for (; i< elements.size(); i++) {
2817                 if (!parseSizeDirect(&mydata, elements[i])) {
2818                         break;
2819                 }
2820         }
2821
2822         /* "position" element is always after "size" element if it used */
2823         for (; i< elements.size(); i++) {
2824                 if (!parsePositionDirect(&mydata, elements[i])) {
2825                         break;
2826                 }
2827         }
2828
2829         /* "anchor" element is always after "position" (or  "size" element) if it used */
2830         for (; i< elements.size(); i++) {
2831                 if (!parseAnchorDirect(&mydata, elements[i])) {
2832                         break;
2833                 }
2834         }
2835
2836         /* "no_prepend" element is always after "position" (or  "size" element) if it used */
2837         bool enable_prepends = true;
2838         for (; i < elements.size(); i++) {
2839                 if (elements[i].empty())
2840                         break;
2841
2842                 std::vector<std::string> parts = split(elements[i], '[');
2843                 if (trim(parts[0]) == "no_prepend")
2844                         enable_prepends = false;
2845                 else
2846                         break;
2847         }
2848
2849         /* Copy of the "real_coordinates" element for after the form size. */
2850         mydata.real_coordinates = m_formspec_version >= 2;
2851         for (; i < elements.size(); i++) {
2852                 std::vector<std::string> parts = split(elements[i], '[');
2853                 std::string name = trim(parts[0]);
2854                 if (name != "real_coordinates" || parts.size() != 2)
2855                         break; // Invalid format
2856
2857                 mydata.real_coordinates = is_yes(trim(parts[1]));
2858         }
2859
2860         if (mydata.explicit_size) {
2861                 // compute scaling for specified form size
2862                 if (m_lock) {
2863                         v2u32 current_screensize = RenderingEngine::get_video_driver()->getScreenSize();
2864                         v2u32 delta = current_screensize - m_lockscreensize;
2865
2866                         if (current_screensize.Y > m_lockscreensize.Y)
2867                                 delta.Y /= 2;
2868                         else
2869                                 delta.Y = 0;
2870
2871                         if (current_screensize.X > m_lockscreensize.X)
2872                                 delta.X /= 2;
2873                         else
2874                                 delta.X = 0;
2875
2876                         offset = v2s32(delta.X,delta.Y);
2877
2878                         mydata.screensize = m_lockscreensize;
2879                 } else {
2880                         offset = v2s32(0,0);
2881                 }
2882
2883                 double gui_scaling = g_settings->getFloat("gui_scaling");
2884                 double screen_dpi = RenderingEngine::getDisplayDensity() * 96;
2885
2886                 double use_imgsize;
2887                 if (m_lock) {
2888                         // In fixed-size mode, inventory image size
2889                         // is 0.53 inch multiplied by the gui_scaling
2890                         // config parameter.  This magic size is chosen
2891                         // to make the main menu (15.5 inventory images
2892                         // wide, including border) just fit into the
2893                         // default window (800 pixels wide) at 96 DPI
2894                         // and default scaling (1.00).
2895                         use_imgsize = 0.5555 * screen_dpi * gui_scaling;
2896                 } else {
2897                         // In variable-size mode, we prefer to make the
2898                         // inventory image size 1/15 of screen height,
2899                         // multiplied by the gui_scaling config parameter.
2900                         // If the preferred size won't fit the whole
2901                         // form on the screen, either horizontally or
2902                         // vertically, then we scale it down to fit.
2903                         // (The magic numbers in the computation of what
2904                         // fits arise from the scaling factors in the
2905                         // following stanza, including the form border,
2906                         // help text space, and 0.1 inventory slot spare.)
2907                         // However, a minimum size is also set, that
2908                         // the image size can't be less than 0.3 inch
2909                         // multiplied by gui_scaling, even if this means
2910                         // the form doesn't fit the screen.
2911 #ifdef __ANDROID__
2912                         // For mobile devices these magic numbers are
2913                         // different and forms should always use the
2914                         // maximum screen space available.
2915                         double prefer_imgsize = mydata.screensize.Y / 10 * gui_scaling;
2916                         double fitx_imgsize = mydata.screensize.X /
2917                                 ((12.0 / 8.0) * (0.5 + mydata.invsize.X));
2918                         double fity_imgsize = mydata.screensize.Y /
2919                                 ((15.0 / 11.0) * (0.85 + mydata.invsize.Y));
2920                         use_imgsize = MYMIN(prefer_imgsize,
2921                                         MYMIN(fitx_imgsize, fity_imgsize));
2922 #else
2923                         double prefer_imgsize = mydata.screensize.Y / 15 * gui_scaling;
2924                         double fitx_imgsize = mydata.screensize.X /
2925                                 ((5.0 / 4.0) * (0.5 + mydata.invsize.X));
2926                         double fity_imgsize = mydata.screensize.Y /
2927                                 ((15.0 / 13.0) * (0.85 * mydata.invsize.Y));
2928                         double screen_dpi = RenderingEngine::getDisplayDensity() * 96;
2929                         double min_imgsize = 0.3 * screen_dpi * gui_scaling;
2930                         use_imgsize = MYMAX(min_imgsize, MYMIN(prefer_imgsize,
2931                                 MYMIN(fitx_imgsize, fity_imgsize)));
2932 #endif
2933                 }
2934
2935                 // Everything else is scaled in proportion to the
2936                 // inventory image size.  The inventory slot spacing
2937                 // is 5/4 image size horizontally and 15/13 image size
2938                 // vertically.  The padding around the form (incorporating
2939                 // the border of the outer inventory slots) is 3/8
2940                 // image size.  Font height (baseline to baseline)
2941                 // is 2/5 vertical inventory slot spacing, and button
2942                 // half-height is 7/8 of font height.
2943                 imgsize = v2s32(use_imgsize, use_imgsize);
2944                 spacing = v2f32(use_imgsize*5.0/4, use_imgsize*15.0/13);
2945                 padding = v2s32(use_imgsize*3.0/8, use_imgsize*3.0/8);
2946                 m_btn_height = use_imgsize*15.0/13 * 0.35;
2947
2948                 m_font = g_fontengine->getFont();
2949
2950                 if (mydata.real_coordinates) {
2951                         mydata.size = v2s32(
2952                                 mydata.invsize.X*imgsize.X,
2953                                 mydata.invsize.Y*imgsize.Y
2954                         );
2955                 } else {
2956                         mydata.size = v2s32(
2957                                 padding.X*2+spacing.X*(mydata.invsize.X-1.0)+imgsize.X,
2958                                 padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0
2959                         );
2960                 }
2961
2962                 DesiredRect = mydata.rect = core::rect<s32>(
2963                                 (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * (f32)mydata.size.X) + offset.X,
2964                                 (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * (f32)mydata.size.Y) + offset.Y,
2965                                 (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * (f32)mydata.size.X) + offset.X,
2966                                 (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * (f32)mydata.size.Y) + offset.Y
2967                 );
2968         } else {
2969                 // Non-size[] form must consist only of text fields and
2970                 // implicit "Proceed" button.  Use default font, and
2971                 // temporary form size which will be recalculated below.
2972                 m_font = g_fontengine->getFont();
2973                 m_btn_height = font_line_height(m_font) * 0.875;
2974                 DesiredRect = core::rect<s32>(
2975                         (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * 580.0),
2976                         (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * 300.0),
2977                         (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * 580.0),
2978                         (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * 300.0)
2979                 );
2980         }
2981         recalculateAbsolutePosition(false);
2982         mydata.basepos = getBasePos();
2983         m_tooltip_element->setOverrideFont(m_font);
2984
2985         gui::IGUISkin *skin = Environment->getSkin();
2986         sanity_check(skin);
2987         gui::IGUIFont *old_font = skin->getFont();
2988         skin->setFont(m_font);
2989
2990         pos_offset = v2f32();
2991
2992         // used for formspec versions < 3
2993         core::list<IGUIElement *>::Iterator legacy_sort_start = Children.getLast();
2994
2995         if (enable_prepends) {
2996                 // Backup the coordinates so that prepends can use the coordinates of choice.
2997                 bool rc_backup = mydata.real_coordinates;
2998                 u16 version_backup = m_formspec_version;
2999                 mydata.real_coordinates = false; // Old coordinates by default.
3000
3001                 std::vector<std::string> prepend_elements = split(m_formspec_prepend, ']');
3002                 for (const auto &element : prepend_elements)
3003                         parseElement(&mydata, element);
3004
3005                 // legacy sorting for formspec versions < 3
3006                 if (m_formspec_version >= 3)
3007                         // prepends do not need to be reordered
3008                         legacy_sort_start = Children.getLast();
3009                 else if (version_backup >= 3)
3010                         // only prepends elements have to be reordered
3011                         legacySortElements(legacy_sort_start);
3012
3013                 m_formspec_version = version_backup;
3014                 mydata.real_coordinates = rc_backup; // Restore coordinates
3015         }
3016
3017         for (; i< elements.size(); i++) {
3018                 parseElement(&mydata, elements[i]);
3019         }
3020
3021         if (!container_stack.empty()) {
3022                 errorstream << "Invalid formspec string: container was never closed!"
3023                         << std::endl;
3024         }
3025
3026         // If there are fields without explicit size[], add a "Proceed"
3027         // button and adjust size to fit all the fields.
3028         if (mydata.simple_field_count > 0 && !mydata.explicit_size) {
3029                 mydata.rect = core::rect<s32>(
3030                                 mydata.screensize.X / 2 - 580 / 2,
3031                                 mydata.screensize.Y / 2 - 300 / 2,
3032                                 mydata.screensize.X / 2 + 580 / 2,
3033                                 mydata.screensize.Y / 2 + 240 / 2 + mydata.simple_field_count * 60
3034                 );
3035
3036                 DesiredRect = mydata.rect;
3037                 recalculateAbsolutePosition(false);
3038                 mydata.basepos = getBasePos();
3039
3040                 {
3041                         v2s32 pos = mydata.basepos;
3042                         pos.Y = (mydata.simple_field_count + 2) * 60;
3043
3044                         v2s32 size = DesiredRect.getSize();
3045                         mydata.rect = core::rect<s32>(
3046                                         size.X / 2 - 70,       pos.Y,
3047                                         size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2
3048                         );
3049                         const wchar_t *text = wgettext("Proceed");
3050                         GUIButton::addButton(Environment, mydata.rect, this, 257, text);
3051                         delete[] text;
3052                 }
3053         }
3054
3055         //set initial focus if parser didn't set it
3056         focused_element = Environment->getFocus();
3057         if (!focused_element
3058                         || !isMyChild(focused_element)
3059                         || focused_element->getType() == gui::EGUIET_TAB_CONTROL)
3060                 setInitialFocus();
3061
3062         skin->setFont(old_font);
3063
3064         // legacy sorting
3065         if (m_formspec_version < 3)
3066                 legacySortElements(legacy_sort_start);
3067 }
3068
3069 void GUIFormSpecMenu::legacySortElements(core::list<IGUIElement *>::Iterator from)
3070 {
3071         /*
3072                 Draw order for formspec_version <= 2:
3073                 -3  bgcolor
3074                 -2  background
3075                 -1  box
3076                 0   All other elements
3077                 1   image
3078                 2   item_image, item_image_button
3079                 3   list
3080                 4   label
3081         */
3082
3083         if (from == Children.end())
3084                 from = Children.begin();
3085         else
3086                 from++;
3087
3088         core::list<IGUIElement *>::Iterator to = Children.end();
3089         // 1: Copy into a sortable container
3090         std::vector<IGUIElement *> elements;
3091         for (auto it = from; it != to; ++it)
3092                 elements.emplace_back(*it);
3093
3094         // 2: Sort the container
3095         std::sort(elements.begin(), elements.end(),
3096                         [this] (const IGUIElement *a, const IGUIElement *b) -> bool {
3097                 const FieldSpec *spec_a = getSpecByID(a->getID());
3098                 const FieldSpec *spec_b = getSpecByID(b->getID());
3099                 return spec_a && spec_b &&
3100                         spec_a->priority < spec_b->priority;
3101         });
3102
3103         // 3: Re-assign the pointers
3104         for (auto e : elements) {
3105                 *from = e;
3106                 from++;
3107         }
3108 }
3109
3110 #ifdef __ANDROID__
3111 bool GUIFormSpecMenu::getAndroidUIInput()
3112 {
3113         if (!hasAndroidUIInput())
3114                 return false;
3115
3116         std::string fieldname = m_jni_field_name;
3117         m_jni_field_name.clear();
3118
3119         for(std::vector<FieldSpec>::iterator iter =  m_fields.begin();
3120                         iter != m_fields.end(); ++iter) {
3121
3122                 if (iter->fname != fieldname) {
3123                         continue;
3124                 }
3125                 IGUIElement *tochange = getElementFromId(iter->fid, true);
3126
3127                 if (tochange == 0) {
3128                         return false;
3129                 }
3130
3131                 if (tochange->getType() != irr::gui::EGUIET_EDIT_BOX) {
3132                         return false;
3133                 }
3134
3135                 std::string text = porting::getInputDialogValue();
3136
3137                 ((gui::IGUIEditBox *)tochange)->setText(utf8_to_wide(text).c_str());
3138         }
3139         return false;
3140 }
3141 #endif
3142
3143 GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
3144 {
3145         core::rect<s32> imgrect(0, 0, imgsize.X, imgsize.Y);
3146
3147         for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
3148                 core::rect<s32> clipping_rect = s.e->getAbsoluteClippingRect();
3149                 v2s32 base_pos = s.e->getAbsolutePosition().UpperLeftCorner;
3150                 for(s32 i=0; i<s.geom.X*s.geom.Y; i++) {
3151                         s32 item_i = i + s.start_item_i;
3152
3153                         s32 x;
3154                         s32 y;
3155                         if (s.real_coordinates) {
3156                                 x = (i%s.geom.X) * (imgsize.X * 1.25);
3157                                 y = (i/s.geom.X) * (imgsize.Y * 1.25);
3158                         } else {
3159                                 x = (i%s.geom.X) * spacing.X;
3160                                 y = (i/s.geom.X) * spacing.Y;
3161                         }
3162                         v2s32 p0(x,y);
3163                         core::rect<s32> rect = imgrect + base_pos + p0;
3164                         rect.clipAgainst(clipping_rect);
3165                         if (rect.getArea() > 0 && rect.isPointInside(p))
3166                                 return ItemSpec(s.inventoryloc, s.listname, item_i);
3167                 }
3168         }
3169
3170         return ItemSpec(InventoryLocation(), "", -1);
3171 }
3172
3173 void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int layer,
3174                 bool &item_hovered)
3175 {
3176         video::IVideoDriver* driver = Environment->getVideoDriver();
3177
3178         Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
3179         if (!inv) {
3180                 warningstream<<"GUIFormSpecMenu::drawList(): "
3181                                 << "The inventory location "
3182                                 << "\"" << s.inventoryloc.dump() << "\" doesn't exist anymore"
3183                                 << std::endl;
3184                 return;
3185         }
3186         InventoryList *ilist = inv->getList(s.listname);
3187         if (!ilist) {
3188                 warningstream << "GUIFormSpecMenu::drawList(): "
3189                                 << "The inventory list \"" << s.listname << "\" @ \""
3190                                 << s.inventoryloc.dump() << "\" doesn't exist anymore"
3191                                 << std::endl;
3192                 return;
3193         }
3194
3195         core::rect<s32> imgrect(0, 0, imgsize.X, imgsize.Y);
3196         core::rect<s32> clipping_rect = s.e->getAbsoluteClippingRect();
3197         v2s32 base_pos = s.e->getAbsolutePosition().UpperLeftCorner;
3198
3199         for (s32 i = 0; i < s.geom.X * s.geom.Y; i++) {
3200                 s32 item_i = i + s.start_item_i;
3201                 if (item_i >= (s32)ilist->getSize())
3202                         break;
3203
3204                 s32 x;
3205                 s32 y;
3206                 if (s.real_coordinates) {
3207                         x = (i%s.geom.X) * (imgsize.X * 1.25);
3208                         y = (i/s.geom.X) * (imgsize.Y * 1.25);
3209                 } else {
3210                         x = (i%s.geom.X) * spacing.X;
3211                         y = (i/s.geom.X) * spacing.Y;
3212                 }
3213                 v2s32 p(x,y);
3214                 core::rect<s32> rect = imgrect + base_pos + p;
3215                 ItemStack item = ilist->getItem(item_i);
3216
3217                 bool selected = m_selected_item
3218                         && m_invmgr->getInventory(m_selected_item->inventoryloc) == inv
3219                         && m_selected_item->listname == s.listname
3220                         && m_selected_item->i == item_i;
3221                 core::rect<s32> clipped_rect(rect);
3222                 clipped_rect.clipAgainst(clipping_rect);
3223                 bool hovering = clipped_rect.getArea() > 0 &&
3224                                 clipped_rect.isPointInside(m_pointer);
3225                 ItemRotationKind rotation_kind = selected ? IT_ROT_SELECTED :
3226                         (hovering ? IT_ROT_HOVERED : IT_ROT_NONE);
3227
3228                 if (layer == 0) {
3229                         if (hovering) {
3230                                 item_hovered = true;
3231                                 driver->draw2DRectangle(m_slotbg_h, rect, &clipping_rect);
3232                         } else {
3233                                 driver->draw2DRectangle(m_slotbg_n, rect, &clipping_rect);
3234                         }
3235                 }
3236
3237                 //Draw inv slot borders
3238                 if (m_slotborder) {
3239                         s32 x1 = rect.UpperLeftCorner.X;
3240                         s32 y1 = rect.UpperLeftCorner.Y;
3241                         s32 x2 = rect.LowerRightCorner.X;
3242                         s32 y2 = rect.LowerRightCorner.Y;
3243                         s32 border = 1;
3244                         driver->draw2DRectangle(m_slotbordercolor,
3245                                 core::rect<s32>(v2s32(x1 - border, y1 - border),
3246                                                                 v2s32(x2 + border, y1)), &clipping_rect);
3247                         driver->draw2DRectangle(m_slotbordercolor,
3248                                 core::rect<s32>(v2s32(x1 - border, y2),
3249                                                                 v2s32(x2 + border, y2 + border)), &clipping_rect);
3250                         driver->draw2DRectangle(m_slotbordercolor,
3251                                 core::rect<s32>(v2s32(x1 - border, y1),
3252                                                                 v2s32(x1, y2)), &clipping_rect);
3253                         driver->draw2DRectangle(m_slotbordercolor,
3254                                 core::rect<s32>(v2s32(x2, y1),
3255                                                                 v2s32(x2 + border, y2)), &clipping_rect);
3256                 }
3257
3258                 if (layer == 1) {
3259                         if (selected)
3260                                 item.takeItem(m_selected_amount);
3261
3262                         if (!item.empty()) {
3263                                 // Draw item stack
3264                                 drawItemStack(driver, m_font, item,
3265                                         rect, &clipping_rect, m_client, rotation_kind);
3266                                 // Draw tooltip
3267                                 if (hovering && !m_selected_item) {
3268                                         std::string tooltip = item.getDescription(m_client->idef());
3269                                         if (m_tooltip_append_itemname)
3270                                                 tooltip += "\n[" + item.name + "]";
3271                                         showTooltip(utf8_to_wide(tooltip), m_default_tooltip_color,
3272                                                         m_default_tooltip_bgcolor);
3273                                 }
3274                         }
3275                 }
3276         }
3277 }
3278
3279 void GUIFormSpecMenu::drawSelectedItem()
3280 {
3281         video::IVideoDriver* driver = Environment->getVideoDriver();
3282
3283         if (!m_selected_item) {
3284                 // reset rotation time
3285                 drawItemStack(driver, m_font, ItemStack(),
3286                                 core::rect<s32>(v2s32(0, 0), v2s32(0, 0)), NULL,
3287                                 m_client, IT_ROT_DRAGGED);
3288                 return;
3289         }
3290
3291         Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
3292         sanity_check(inv);
3293         InventoryList *list = inv->getList(m_selected_item->listname);
3294         sanity_check(list);
3295         ItemStack stack = list->getItem(m_selected_item->i);
3296         stack.count = m_selected_amount;
3297
3298         core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
3299         core::rect<s32> rect = imgrect + (m_pointer - imgrect.getCenter());
3300         rect.constrainTo(driver->getViewPort());
3301         drawItemStack(driver, m_font, stack, rect, NULL, m_client, IT_ROT_DRAGGED);
3302 }
3303
3304 void GUIFormSpecMenu::drawMenu()
3305 {
3306         if (m_form_src) {
3307                 const std::string &newform = m_form_src->getForm();
3308                 if (newform != m_formspec_string) {
3309                         m_formspec_string = newform;
3310                         regenerateGui(m_screensize_old);
3311                 }
3312         }
3313
3314         gui::IGUISkin* skin = Environment->getSkin();
3315         sanity_check(skin != NULL);
3316         gui::IGUIFont *old_font = skin->getFont();
3317         skin->setFont(m_font);
3318
3319         updateSelectedItem();
3320
3321         video::IVideoDriver* driver = Environment->getVideoDriver();
3322
3323         /*
3324                 Draw background color
3325         */
3326         v2u32 screenSize = driver->getScreenSize();
3327         core::rect<s32> allbg(0, 0, screenSize.X, screenSize.Y);
3328
3329         if (m_bgfullscreen)
3330                 driver->draw2DRectangle(m_fullscreen_bgcolor, allbg, &allbg);
3331         if (m_bgnonfullscreen)
3332                 driver->draw2DRectangle(m_bgcolor, AbsoluteRect, &AbsoluteClippingRect);
3333
3334         /*
3335                 Draw rect_mode tooltip
3336         */
3337         m_tooltip_element->setVisible(false);
3338
3339         for (const auto &pair : m_tooltip_rects) {
3340                 const core::rect<s32> &rect = pair.first->getAbsoluteClippingRect();
3341                 if (rect.getArea() > 0 && rect.isPointInside(m_pointer)) {
3342                         const std::wstring &text = pair.second.tooltip;
3343                         if (!text.empty()) {
3344                                 showTooltip(text, pair.second.color, pair.second.bgcolor);
3345                                 break;
3346                         }
3347                 }
3348         }
3349
3350         /*
3351                 Draw backgrounds
3352         */
3353         for (gui::IGUIElement *e : m_backgrounds) {
3354                 e->setVisible(true);
3355                 e->draw();
3356                 e->setVisible(false);
3357         }
3358
3359         /*
3360                 Call base class
3361         */
3362         gui::IGUIElement::draw();
3363
3364         /*
3365                 Draw items
3366                 Layer 0: Item slot rectangles
3367                 Layer 1: Item images; prepare tooltip
3368         */
3369         bool item_hovered = false;
3370         for (int layer = 0; layer < 2; layer++) {
3371                 for (const GUIFormSpecMenu::ListDrawSpec &spec : m_inventorylists) {
3372                         drawList(spec, layer, item_hovered);
3373                 }
3374         }
3375         if (!item_hovered) {
3376                 // reset rotation time
3377                 drawItemStack(driver, m_font, ItemStack(),
3378                         core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
3379                         NULL, m_client, IT_ROT_HOVERED);
3380         }
3381
3382 /* TODO find way to show tooltips on touchscreen */
3383 #ifndef HAVE_TOUCHSCREENGUI
3384         m_pointer = RenderingEngine::get_raw_device()->getCursorControl()->getPosition();
3385 #endif
3386
3387         /*
3388                 Draw fields/buttons tooltips
3389         */
3390         gui::IGUIElement *hovered =
3391                         Environment->getRootGUIElement()->getElementFromPoint(m_pointer);
3392
3393         if (hovered != NULL) {
3394                 s32 id = hovered->getID();
3395
3396                 u64 delta = 0;
3397                 if (id == -1) {
3398                         m_old_tooltip_id = id;
3399                 } else {
3400                         if (id == m_old_tooltip_id) {
3401                                 delta = porting::getDeltaMs(m_hovered_time, porting::getTimeMs());
3402                         } else {
3403                                 m_hovered_time = porting::getTimeMs();
3404                                 m_old_tooltip_id = id;
3405                         }
3406                 }
3407
3408                 // Find and update the current tooltip
3409                 if (id != -1 && delta >= m_tooltip_show_delay) {
3410                         for (const FieldSpec &field : m_fields) {
3411
3412                                 if (field.fid != id)
3413                                         continue;
3414
3415                                 const std::wstring &text = m_tooltips[field.fname].tooltip;
3416                                 if (!text.empty())
3417                                         showTooltip(text, m_tooltips[field.fname].color,
3418                                                 m_tooltips[field.fname].bgcolor);
3419
3420                                 break;
3421                         }
3422                 }
3423         }
3424
3425         m_tooltip_element->draw();
3426
3427         /*
3428                 Draw dragged item stack
3429         */
3430         drawSelectedItem();
3431
3432         skin->setFont(old_font);
3433 }
3434
3435
3436 void GUIFormSpecMenu::showTooltip(const std::wstring &text,
3437         const irr::video::SColor &color, const irr::video::SColor &bgcolor)
3438 {
3439         const std::wstring ntext = translate_string(text);
3440         m_tooltip_element->setOverrideColor(color);
3441         m_tooltip_element->setBackgroundColor(bgcolor);
3442         setStaticText(m_tooltip_element, ntext.c_str());
3443
3444         // Tooltip size and offset
3445         s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
3446 #if (IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 8 && IRRLICHT_VERSION_REVISION < 2) || USE_FREETYPE == 1
3447         std::vector<std::wstring> text_rows = str_split(ntext, L'\n');
3448         s32 tooltip_height = m_tooltip_element->getTextHeight() * text_rows.size() + 5;
3449 #else
3450         s32 tooltip_height = m_tooltip_element->getTextHeight() + 5;
3451 #endif
3452         v2u32 screenSize = Environment->getVideoDriver()->getScreenSize();
3453         int tooltip_offset_x = m_btn_height;
3454         int tooltip_offset_y = m_btn_height;
3455 #ifdef __ANDROID__
3456         tooltip_offset_x *= 3;
3457         tooltip_offset_y  = 0;
3458         if (m_pointer.X > (s32)screenSize.X / 2)
3459                 tooltip_offset_x = -(tooltip_offset_x + tooltip_width);
3460 #endif
3461
3462         // Calculate and set the tooltip position
3463         s32 tooltip_x = m_pointer.X + tooltip_offset_x;
3464         s32 tooltip_y = m_pointer.Y + tooltip_offset_y;
3465         if (tooltip_x + tooltip_width > (s32)screenSize.X)
3466                 tooltip_x = (s32)screenSize.X - tooltip_width  - m_btn_height;
3467         if (tooltip_y + tooltip_height > (s32)screenSize.Y)
3468                 tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height;
3469
3470         m_tooltip_element->setRelativePosition(
3471                 core::rect<s32>(
3472                         core::position2d<s32>(tooltip_x, tooltip_y),
3473                         core::dimension2d<s32>(tooltip_width, tooltip_height)
3474                 )
3475         );
3476
3477         // Display the tooltip
3478         m_tooltip_element->setVisible(true);
3479         bringToFront(m_tooltip_element);
3480 }
3481
3482 void GUIFormSpecMenu::updateSelectedItem()
3483 {
3484         verifySelectedItem();
3485
3486         // If craftresult is nonempty and nothing else is selected, select it now.
3487         if (!m_selected_item) {
3488                 for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
3489                         if (s.listname != "craftpreview")
3490                                 continue;
3491
3492                         Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
3493                         if (!inv)
3494                                 continue;
3495
3496                         InventoryList *list = inv->getList("craftresult");
3497
3498                         if (!list || list->getSize() == 0)
3499                                 continue;
3500
3501                         const ItemStack &item = list->getItem(0);
3502                         if (item.empty())
3503                                 continue;
3504
3505                         // Grab selected item from the crafting result list
3506                         m_selected_item = new ItemSpec;
3507                         m_selected_item->inventoryloc = s.inventoryloc;
3508                         m_selected_item->listname = "craftresult";
3509                         m_selected_item->i = 0;
3510                         m_selected_amount = item.count;
3511                         m_selected_dragging = false;
3512                         break;
3513                 }
3514         }
3515
3516         // If craftresult is selected, keep the whole stack selected
3517         if (m_selected_item && m_selected_item->listname == "craftresult")
3518                 m_selected_amount = verifySelectedItem().count;
3519 }
3520
3521 ItemStack GUIFormSpecMenu::verifySelectedItem()
3522 {
3523         // If the selected stack has become empty for some reason, deselect it.
3524         // If the selected stack has become inaccessible, deselect it.
3525         // If the selected stack has become smaller, adjust m_selected_amount.
3526         // Return the selected stack.
3527
3528         if(m_selected_item)
3529         {
3530                 if(m_selected_item->isValid())
3531                 {
3532                         Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
3533                         if(inv)
3534                         {
3535                                 InventoryList *list = inv->getList(m_selected_item->listname);
3536                                 if(list && (u32) m_selected_item->i < list->getSize())
3537                                 {
3538                                         ItemStack stack = list->getItem(m_selected_item->i);
3539                                         if (!m_selected_swap.empty()) {
3540                                                 if (m_selected_swap.name == stack.name &&
3541                                                                 m_selected_swap.count == stack.count)
3542                                                         m_selected_swap.clear();
3543                                         } else {
3544                                                 m_selected_amount = std::min(m_selected_amount, stack.count);
3545                                         }
3546
3547                                         if (!stack.empty())
3548                                                 return stack;
3549                                 }
3550                         }
3551                 }
3552
3553                 // selection was not valid
3554                 delete m_selected_item;
3555                 m_selected_item = NULL;
3556                 m_selected_amount = 0;
3557                 m_selected_dragging = false;
3558         }
3559         return ItemStack();
3560 }
3561
3562 void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
3563 {
3564         if(m_text_dst)
3565         {
3566                 StringMap fields;
3567
3568                 if (quitmode == quit_mode_accept) {
3569                         fields["quit"] = "true";
3570                 }
3571
3572                 if (quitmode == quit_mode_cancel) {
3573                         fields["quit"] = "true";
3574                         m_text_dst->gotText(fields);
3575                         return;
3576                 }
3577
3578                 if (current_keys_pending.key_down) {
3579                         fields["key_down"] = "true";
3580                         current_keys_pending.key_down = false;
3581                 }
3582
3583                 if (current_keys_pending.key_up) {
3584                         fields["key_up"] = "true";
3585                         current_keys_pending.key_up = false;
3586                 }
3587
3588                 if (current_keys_pending.key_enter) {
3589                         fields["key_enter"] = "true";
3590                         current_keys_pending.key_enter = false;
3591                 }
3592
3593                 if (!current_field_enter_pending.empty()) {
3594                         fields["key_enter_field"] = current_field_enter_pending;
3595                         current_field_enter_pending = "";
3596                 }
3597
3598                 if (current_keys_pending.key_escape) {
3599                         fields["key_escape"] = "true";
3600                         current_keys_pending.key_escape = false;
3601                 }
3602
3603                 for (const GUIFormSpecMenu::FieldSpec &s : m_fields) {
3604                         if(s.send) {
3605                                 std::string name = s.fname;
3606                                 if (s.ftype == f_Button) {
3607                                         fields[name] = wide_to_utf8(s.flabel);
3608                                 } else if (s.ftype == f_Table) {
3609                                         GUITable *table = getTable(s.fname);
3610                                         if (table) {
3611                                                 fields[name] = table->checkEvent();
3612                                         }
3613                                 }
3614                                 else if(s.ftype == f_DropDown) {
3615                                         // no dynamic cast possible due to some distributions shipped
3616                                         // without rtti support in irrlicht
3617                                         IGUIElement *element = getElementFromId(s.fid, true);
3618                                         gui::IGUIComboBox *e = NULL;
3619                                         if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) {
3620                                                 e = static_cast<gui::IGUIComboBox*>(element);
3621                                         } else {
3622                                                 warningstream << "GUIFormSpecMenu::acceptInput: dropdown "
3623                                                                 << "field without dropdown element" << std::endl;
3624                                                 continue;
3625                                         }
3626                                         s32 selected = e->getSelected();
3627                                         if (selected >= 0) {
3628                                                 std::vector<std::string> *dropdown_values =
3629                                                         getDropDownValues(s.fname);
3630                                                 if (dropdown_values && selected < (s32)dropdown_values->size()) {
3631                                                         fields[name] = (*dropdown_values)[selected];
3632                                                 }
3633                                         }
3634                                 }
3635                                 else if (s.ftype == f_TabHeader) {
3636                                         // no dynamic cast possible due to some distributions shipped
3637                                         // without rttzi support in irrlicht
3638                                         IGUIElement *element = getElementFromId(s.fid, true);
3639                                         gui::IGUITabControl *e = nullptr;
3640                                         if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) {
3641                                                 e = static_cast<gui::IGUITabControl *>(element);
3642                                         }
3643
3644                                         if (e != 0) {
3645                                                 std::stringstream ss;
3646                                                 ss << (e->getActiveTab() +1);
3647                                                 fields[name] = ss.str();
3648                                         }
3649                                 }
3650                                 else if (s.ftype == f_CheckBox) {
3651                                         // no dynamic cast possible due to some distributions shipped
3652                                         // without rtti support in irrlicht
3653                                         IGUIElement *element = getElementFromId(s.fid, true);
3654                                         gui::IGUICheckBox *e = nullptr;
3655                                         if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) {
3656                                                 e = static_cast<gui::IGUICheckBox*>(element);
3657                                         }
3658
3659                                         if (e != 0) {
3660                                                 if (e->isChecked())
3661                                                         fields[name] = "true";
3662                                                 else
3663                                                         fields[name] = "false";
3664                                         }
3665                                 }
3666                                 else if (s.ftype == f_ScrollBar) {
3667                                         // no dynamic cast possible due to some distributions shipped
3668                                         // without rtti support in irrlicht
3669                                         IGUIElement *element = getElementFromId(s.fid, true);
3670                                         GUIScrollBar *e = nullptr;
3671                                         if (element && element->getType() == gui::EGUIET_ELEMENT)
3672                                                 e = static_cast<GUIScrollBar *>(element);
3673
3674                                         if (e) {
3675                                                 std::stringstream os;
3676                                                 os << e->getPos();
3677                                                 if (s.fdefault == L"Changed")
3678                                                         fields[name] = "CHG:" + os.str();
3679                                                 else
3680                                                         fields[name] = "VAL:" + os.str();
3681                                         }
3682                                 }
3683                                 else {
3684                                         IGUIElement *e = getElementFromId(s.fid, true);
3685                                         if (e)
3686                                                 fields[name] = wide_to_utf8(e->getText());
3687                                 }
3688                         }
3689                 }
3690
3691                 m_text_dst->gotText(fields);
3692         }
3693 }
3694
3695 static bool isChild(gui::IGUIElement * tocheck, gui::IGUIElement * parent)
3696 {
3697         while(tocheck != NULL) {
3698                 if (tocheck == parent) {
3699                         return true;
3700                 }
3701                 tocheck = tocheck->getParent();
3702         }
3703         return false;
3704 }
3705
3706 bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
3707 {
3708         // The IGUITabControl renders visually using the skin's selected
3709         // font, which we override for the duration of form drawing,
3710         // but computes tab hotspots based on how it would have rendered
3711         // using the font that is selected at the time of button release.
3712         // To make these two consistent, temporarily override the skin's
3713         // font while the IGUITabControl is processing the event.
3714         if (event.EventType == EET_MOUSE_INPUT_EVENT &&
3715                         event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
3716                 s32 x = event.MouseInput.X;
3717                 s32 y = event.MouseInput.Y;
3718                 gui::IGUIElement *hovered =
3719                         Environment->getRootGUIElement()->getElementFromPoint(
3720                                 core::position2d<s32>(x, y));
3721                 if (hovered && isMyChild(hovered) &&
3722                                 hovered->getType() == gui::EGUIET_TAB_CONTROL) {
3723                         gui::IGUISkin* skin = Environment->getSkin();
3724                         sanity_check(skin != NULL);
3725                         gui::IGUIFont *old_font = skin->getFont();
3726                         skin->setFont(m_font);
3727                         bool retval = hovered->OnEvent(event);
3728                         skin->setFont(old_font);
3729                         return retval;
3730                 }
3731         }
3732
3733         // Fix Esc/Return key being eaten by checkboxen and tables
3734         if(event.EventType==EET_KEY_INPUT_EVENT) {
3735                 KeyPress kp(event.KeyInput);
3736                 if (kp == EscapeKey || kp == CancelKey
3737                                 || kp == getKeySetting("keymap_inventory")
3738                                 || event.KeyInput.Key==KEY_RETURN) {
3739                         gui::IGUIElement *focused = Environment->getFocus();
3740                         if (focused && isMyChild(focused) &&
3741                                         (focused->getType() == gui::EGUIET_LIST_BOX ||
3742                                         focused->getType() == gui::EGUIET_CHECK_BOX) &&
3743                                         (focused->getParent()->getType() != gui::EGUIET_COMBO_BOX ||
3744                                         event.KeyInput.Key != KEY_RETURN)) {
3745                                 OnEvent(event);
3746                                 return true;
3747                         }
3748                 }
3749         }
3750         // Mouse wheel and move events: send to hovered element instead of focused
3751         if (event.EventType == EET_MOUSE_INPUT_EVENT &&
3752                         (event.MouseInput.Event == EMIE_MOUSE_WHEEL ||
3753                          event.MouseInput.Event == EMIE_MOUSE_MOVED)) {
3754                 s32 x = event.MouseInput.X;
3755                 s32 y = event.MouseInput.Y;
3756                 gui::IGUIElement *hovered =
3757                         Environment->getRootGUIElement()->getElementFromPoint(
3758                                 core::position2d<s32>(x, y));
3759                 if (hovered && isMyChild(hovered)) {
3760                         hovered->OnEvent(event);
3761                         return event.MouseInput.Event == EMIE_MOUSE_WHEEL;
3762                 }
3763         }
3764
3765         if (event.EventType == EET_MOUSE_INPUT_EVENT) {
3766                 s32 x = event.MouseInput.X;
3767                 s32 y = event.MouseInput.Y;
3768                 gui::IGUIElement *hovered =
3769                         Environment->getRootGUIElement()->getElementFromPoint(
3770                                 core::position2d<s32>(x, y));
3771                 if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
3772                         m_old_tooltip_id = -1;
3773                 }
3774                 if (!isChild(hovered,this)) {
3775                         if (DoubleClickDetection(event)) {
3776                                 return true;
3777                         }
3778                 }
3779         }
3780
3781         if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
3782                 /* TODO add a check like:
3783                 if (event.JoystickEvent != joystick_we_listen_for)
3784                         return false;
3785                 */
3786                 bool handled = m_joystick->handleEvent(event.JoystickEvent);
3787                 if (handled) {
3788                         if (m_joystick->wasKeyDown(KeyType::ESC)) {
3789                                 tryClose();
3790                         } else if (m_joystick->wasKeyDown(KeyType::JUMP)) {
3791                                 if (m_allowclose) {
3792                                         acceptInput(quit_mode_accept);
3793                                         quitMenu();
3794                                 }
3795                         }
3796                 }
3797                 return handled;
3798         }
3799
3800         return GUIModalMenu::preprocessEvent(event);
3801 }
3802
3803 /******************************************************************************/
3804 bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event)
3805 {
3806         /* The following code is for capturing double-clicks of the mouse button
3807          * and translating the double-click into an EET_KEY_INPUT_EVENT event
3808          * -- which closes the form -- under some circumstances.
3809          *
3810          * There have been many github issues reporting this as a bug even though it
3811          * was an intended feature.  For this reason, remapping the double-click as
3812          * an ESC must be explicitly set when creating this class via the
3813          * /p remap_dbl_click parameter of the constructor.
3814          */
3815
3816         if (!m_remap_dbl_click)
3817                 return false;
3818
3819         if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
3820                 m_doubleclickdetect[0].pos  = m_doubleclickdetect[1].pos;
3821                 m_doubleclickdetect[0].time = m_doubleclickdetect[1].time;
3822
3823                 m_doubleclickdetect[1].pos  = m_pointer;
3824                 m_doubleclickdetect[1].time = porting::getTimeMs();
3825         }
3826         else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
3827                 u64 delta = porting::getDeltaMs(m_doubleclickdetect[0].time, porting::getTimeMs());
3828                 if (delta > 400) {
3829                         return false;
3830                 }
3831
3832                 double squaredistance =
3833                                 m_doubleclickdetect[0].pos
3834                                 .getDistanceFromSQ(m_doubleclickdetect[1].pos);
3835
3836                 if (squaredistance > (30*30)) {
3837                         return false;
3838                 }
3839
3840                 SEvent* translated = new SEvent();
3841                 assert(translated != 0);
3842                 //translate doubleclick to escape
3843                 memset(translated, 0, sizeof(SEvent));
3844                 translated->EventType = irr::EET_KEY_INPUT_EVENT;
3845                 translated->KeyInput.Key         = KEY_ESCAPE;
3846                 translated->KeyInput.Control     = false;
3847                 translated->KeyInput.Shift       = false;
3848                 translated->KeyInput.PressedDown = true;
3849                 translated->KeyInput.Char        = 0;
3850                 OnEvent(*translated);
3851
3852                 // no need to send the key up event as we're already deleted
3853                 // and no one else did notice this event
3854                 delete translated;
3855                 return true;
3856         }
3857
3858         return false;
3859 }
3860
3861 void GUIFormSpecMenu::tryClose()
3862 {
3863         if (m_allowclose) {
3864                 doPause = false;
3865                 acceptInput(quit_mode_cancel);
3866                 quitMenu();
3867         } else {
3868                 m_text_dst->gotText(L"MenuQuit");
3869         }
3870 }
3871
3872 enum ButtonEventType : u8
3873 {
3874         BET_LEFT,
3875         BET_RIGHT,
3876         BET_MIDDLE,
3877         BET_WHEEL_UP,
3878         BET_WHEEL_DOWN,
3879         BET_UP,
3880         BET_DOWN,
3881         BET_MOVE,
3882         BET_OTHER
3883 };
3884
3885 bool GUIFormSpecMenu::OnEvent(const SEvent& event)
3886 {
3887         if (event.EventType==EET_KEY_INPUT_EVENT) {
3888                 KeyPress kp(event.KeyInput);
3889                 if (event.KeyInput.PressedDown && (
3890                                 (kp == EscapeKey) || (kp == CancelKey) ||
3891                                 ((m_client != NULL) && (kp == getKeySetting("keymap_inventory"))))) {
3892                         tryClose();
3893                         return true;
3894                 }
3895
3896                 if (m_client != NULL && event.KeyInput.PressedDown &&
3897                                 (kp == getKeySetting("keymap_screenshot"))) {
3898                         m_client->makeScreenshot();
3899                 }
3900                 if (event.KeyInput.PressedDown &&
3901                         (event.KeyInput.Key==KEY_RETURN ||
3902                          event.KeyInput.Key==KEY_UP ||
3903                          event.KeyInput.Key==KEY_DOWN)
3904                         ) {
3905                         switch (event.KeyInput.Key) {
3906                                 case KEY_RETURN:
3907                                         current_keys_pending.key_enter = true;
3908                                         break;
3909                                 case KEY_UP:
3910                                         current_keys_pending.key_up = true;
3911                                         break;
3912                                 case KEY_DOWN:
3913                                         current_keys_pending.key_down = true;
3914                                         break;
3915                                 break;
3916                                 default:
3917                                         //can't happen at all!
3918                                         FATAL_ERROR("Reached a source line that can't ever been reached");
3919                                         break;
3920                         }
3921                         if (current_keys_pending.key_enter && m_allowclose) {
3922                                 acceptInput(quit_mode_accept);
3923                                 quitMenu();
3924                         } else {
3925                                 acceptInput();
3926                         }
3927                         return true;
3928                 }
3929
3930         }
3931
3932         /* Mouse event other than movement, or crossing the border of inventory
3933           field while holding right mouse button
3934          */
3935         if (event.EventType == EET_MOUSE_INPUT_EVENT &&
3936                         (event.MouseInput.Event != EMIE_MOUSE_MOVED ||
3937                          (event.MouseInput.Event == EMIE_MOUSE_MOVED &&
3938                           event.MouseInput.isRightPressed() &&
3939                           getItemAtPos(m_pointer).i != getItemAtPos(m_old_pointer).i))) {
3940
3941                 // Get selected item and hovered/clicked item (s)
3942
3943                 m_old_tooltip_id = -1;
3944                 updateSelectedItem();
3945                 ItemSpec s = getItemAtPos(m_pointer);
3946
3947                 Inventory *inv_selected = NULL;
3948                 Inventory *inv_s = NULL;
3949                 InventoryList *list_s = NULL;
3950
3951                 if (m_selected_item) {
3952                         inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc);
3953                         sanity_check(inv_selected);
3954                         sanity_check(inv_selected->getList(m_selected_item->listname) != NULL);
3955                 }
3956
3957                 u32 s_count = 0;
3958
3959                 if (s.isValid())
3960                 do { // breakable
3961                         inv_s = m_invmgr->getInventory(s.inventoryloc);
3962
3963                         if (!inv_s) {
3964                                 errorstream << "InventoryMenu: The selected inventory location "
3965                                                 << "\"" << s.inventoryloc.dump() << "\" doesn't exist"
3966                                                 << std::endl;
3967                                 s.i = -1;  // make it invalid again
3968                                 break;
3969                         }
3970
3971                         list_s = inv_s->getList(s.listname);
3972                         if (list_s == NULL) {
3973                                 verbosestream << "InventoryMenu: The selected inventory list \""
3974                                                 << s.listname << "\" does not exist" << std::endl;
3975                                 s.i = -1;  // make it invalid again
3976                                 break;
3977                         }
3978
3979                         if ((u32)s.i >= list_s->getSize()) {
3980                                 infostream << "InventoryMenu: The selected inventory list \""
3981                                                 << s.listname << "\" is too small (i=" << s.i << ", size="
3982                                                 << list_s->getSize() << ")" << std::endl;
3983                                 s.i = -1;  // make it invalid again
3984                                 break;
3985                         }
3986
3987                         s_count = list_s->getItem(s.i).count;
3988                 } while(0);
3989
3990                 bool identical = m_selected_item && s.isValid() &&
3991                         (inv_selected == inv_s) &&
3992                         (m_selected_item->listname == s.listname) &&
3993                         (m_selected_item->i == s.i);
3994
3995                 ButtonEventType button = BET_LEFT;
3996                 ButtonEventType updown = BET_OTHER;
3997                 switch (event.MouseInput.Event) {
3998                 case EMIE_LMOUSE_PRESSED_DOWN:
3999                         button = BET_LEFT; updown = BET_DOWN;
4000                         break;
4001                 case EMIE_RMOUSE_PRESSED_DOWN:
4002                         button = BET_RIGHT; updown = BET_DOWN;
4003                         break;
4004                 case EMIE_MMOUSE_PRESSED_DOWN:
4005                         button = BET_MIDDLE; updown = BET_DOWN;
4006                         break;
4007                 case EMIE_MOUSE_WHEEL:
4008                         button = (event.MouseInput.Wheel > 0) ?
4009                                 BET_WHEEL_UP : BET_WHEEL_DOWN;
4010                         updown = BET_DOWN;
4011                         break;
4012                 case EMIE_LMOUSE_LEFT_UP:
4013                         button = BET_LEFT; updown = BET_UP;
4014                         break;
4015                 case EMIE_RMOUSE_LEFT_UP:
4016                         button = BET_RIGHT; updown = BET_UP;
4017                         break;
4018                 case EMIE_MMOUSE_LEFT_UP:
4019                         button = BET_MIDDLE; updown = BET_UP;
4020                         break;
4021                 case EMIE_MOUSE_MOVED:
4022                         updown = BET_MOVE;
4023                         break;
4024                 default:
4025                         break;
4026                 }
4027
4028                 // Set this number to a positive value to generate a move action
4029                 // from m_selected_item to s.
4030                 u32 move_amount = 0;
4031
4032                 // Set this number to a positive value to generate a move action
4033                 // from s to the next inventory ring.
4034                 u32 shift_move_amount = 0;
4035
4036                 // Set this number to a positive value to generate a drop action
4037                 // from m_selected_item.
4038                 u32 drop_amount = 0;
4039
4040                 // Set this number to a positive value to generate a craft action at s.
4041                 u32 craft_amount = 0;
4042
4043                 switch (updown) {
4044                 case BET_DOWN:
4045                         // Some mouse button has been pressed
4046
4047                         //infostream<<"Mouse button "<<button<<" pressed at p=("
4048                         //      <<p.X<<","<<p.Y<<")"<<std::endl;
4049
4050                         m_selected_dragging = false;
4051
4052                         if (s.isValid() && s.listname == "craftpreview") {
4053                                 // Craft preview has been clicked: craft
4054                                 craft_amount = (button == BET_MIDDLE ? 10 : 1);
4055                         } else if (!m_selected_item) {
4056                                 if (s_count && button != BET_WHEEL_UP) {
4057                                         // Non-empty stack has been clicked: select or shift-move it
4058                                         m_selected_item = new ItemSpec(s);
4059
4060                                         u32 count;
4061                                         if (button == BET_RIGHT)
4062                                                 count = (s_count + 1) / 2;
4063                                         else if (button == BET_MIDDLE)
4064                                                 count = MYMIN(s_count, 10);
4065                                         else if (button == BET_WHEEL_DOWN)
4066                                                 count = 1;
4067                                         else  // left
4068                                                 count = s_count;
4069
4070                                         if (!event.MouseInput.Shift) {
4071                                                 // no shift: select item
4072                                                 m_selected_amount = count;
4073                                                 m_selected_dragging = button != BET_WHEEL_DOWN;
4074                                                 m_auto_place = false;
4075                                         } else {
4076                                                 // shift pressed: move item, right click moves 1
4077                                                 shift_move_amount = button == BET_RIGHT ? 1 : count;
4078                                         }
4079                                 }
4080                         } else { // m_selected_item != NULL
4081                                 assert(m_selected_amount >= 1);
4082
4083                                 if (s.isValid()) {
4084                                         // Clicked a slot: move
4085                                         if (button == BET_RIGHT || button == BET_WHEEL_UP)
4086                                                 move_amount = 1;
4087                                         else if (button == BET_MIDDLE)
4088                                                 move_amount = MYMIN(m_selected_amount, 10);
4089                                         else if (button == BET_LEFT)
4090                                                 move_amount = m_selected_amount;
4091                                         // else wheeldown
4092
4093                                         if (identical) {
4094                                                 if (button == BET_WHEEL_DOWN) {
4095                                                         if (m_selected_amount < s_count)
4096                                                                 ++m_selected_amount;
4097                                                 } else {
4098                                                         if (move_amount >= m_selected_amount)
4099                                                                 m_selected_amount = 0;
4100                                                         else
4101                                                                 m_selected_amount -= move_amount;
4102                                                         move_amount = 0;
4103                                                 }
4104                                         }
4105                                 } else if (!getAbsoluteClippingRect().isPointInside(m_pointer)
4106                                                 && button != BET_WHEEL_DOWN) {
4107                                         // Clicked outside of the window: drop
4108                                         if (button == BET_RIGHT || button == BET_WHEEL_UP)
4109                                                 drop_amount = 1;
4110                                         else if (button == BET_MIDDLE)
4111                                                 drop_amount = MYMIN(m_selected_amount, 10);
4112                                         else  // left
4113                                                 drop_amount = m_selected_amount;
4114                                 }
4115                         }
4116                 break;
4117                 case BET_UP:
4118                         // Some mouse button has been released
4119
4120                         //infostream<<"Mouse button "<<button<<" released at p=("
4121                         //      <<p.X<<","<<p.Y<<")"<<std::endl;
4122
4123                         if (m_selected_dragging && m_selected_item) {
4124                                 if (s.isValid()) {
4125                                         if (!identical) {
4126                                                 // Dragged to different slot: move all selected
4127                                                 move_amount = m_selected_amount;
4128                                         }
4129                                 } else if (!getAbsoluteClippingRect().isPointInside(m_pointer)) {
4130                                         // Dragged outside of window: drop all selected
4131                                         drop_amount = m_selected_amount;
4132                                 }
4133                         }
4134
4135                         m_selected_dragging = false;
4136                         // Keep track of whether the mouse button be released
4137                         // One click is drag without dropping. Click + release
4138                         // + click changes to drop item when moved mode
4139                         if (m_selected_item)
4140                                 m_auto_place = true;
4141                 break;
4142                 case BET_MOVE:
4143                         // Mouse has been moved and rmb is down and mouse pointer just
4144                         // entered a new inventory field (checked in the entry-if, this
4145                         // is the only action here that is generated by mouse movement)
4146                         if (m_selected_item && s.isValid() && s.listname != "craftpreview") {
4147                                 // Move 1 item
4148                                 // TODO: middle mouse to move 10 items might be handy
4149                                 if (m_auto_place) {
4150                                         // Only move an item if the destination slot is empty
4151                                         // or contains the same item type as what is going to be
4152                                         // moved
4153                                         InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
4154                                         InventoryList *list_to = list_s;
4155                                         assert(list_from && list_to);
4156                                         ItemStack stack_from = list_from->getItem(m_selected_item->i);
4157                                         ItemStack stack_to = list_to->getItem(s.i);
4158                                         if (stack_to.empty() || stack_to.name == stack_from.name)
4159                                                 move_amount = 1;
4160                                 }
4161                         }
4162                 break;
4163                 default:
4164                         break;
4165                 }
4166
4167                 // Possibly send inventory action to server
4168                 if (move_amount > 0) {
4169                         // Send IAction::Move
4170
4171                         assert(m_selected_item && m_selected_item->isValid());
4172                         assert(s.isValid());
4173
4174                         assert(inv_selected && inv_s);
4175                         InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
4176                         InventoryList *list_to = list_s;
4177                         assert(list_from && list_to);
4178                         ItemStack stack_from = list_from->getItem(m_selected_item->i);
4179                         ItemStack stack_to = list_to->getItem(s.i);
4180
4181                         // Check how many items can be moved
4182                         move_amount = stack_from.count = MYMIN(move_amount, stack_from.count);
4183                         ItemStack leftover = stack_to.addItem(stack_from, m_client->idef());
4184                         bool move = true;
4185                         // If source stack cannot be added to destination stack at all,
4186                         // they are swapped
4187                         if (leftover.count == stack_from.count &&
4188                                         leftover.name == stack_from.name) {
4189
4190                                 if (m_selected_swap.empty()) {
4191                                         m_selected_amount = stack_to.count;
4192                                         m_selected_dragging = false;
4193
4194                                         // WARNING: BLACK MAGIC, BUT IN A REDUCED SET
4195                                         // Skip next validation checks due async inventory calls
4196                                         m_selected_swap = stack_to;
4197                                 } else {
4198                                         move = false;
4199                                 }
4200                         }
4201                         // Source stack goes fully into destination stack
4202                         else if (leftover.empty()) {
4203                                 m_selected_amount -= move_amount;
4204                         }
4205                         // Source stack goes partly into destination stack
4206                         else {
4207                                 move_amount -= leftover.count;
4208                                 m_selected_amount -= move_amount;
4209                         }
4210
4211                         if (move) {
4212                                 infostream << "Handing IAction::Move to manager" << std::endl;
4213                                 IMoveAction *a = new IMoveAction();
4214                                 a->count = move_amount;
4215                                 a->from_inv = m_selected_item->inventoryloc;
4216                                 a->from_list = m_selected_item->listname;
4217                                 a->from_i = m_selected_item->i;
4218                                 a->to_inv = s.inventoryloc;
4219                                 a->to_list = s.listname;
4220                                 a->to_i = s.i;
4221                                 m_invmgr->inventoryAction(a);
4222                         }
4223                 } else if (shift_move_amount > 0) {
4224                         u32 mis = m_inventory_rings.size();
4225                         u32 i = 0;
4226                         for (; i < mis; i++) {
4227                                 const ListRingSpec &sp = m_inventory_rings[i];
4228                                 if (sp.inventoryloc == s.inventoryloc
4229                                                 && sp.listname == s.listname)
4230                                         break;
4231                         }
4232                         do {
4233                                 if (i >= mis) // if not found
4234                                         break;
4235                                 u32 to_inv_ind = (i + 1) % mis;
4236                                 const ListRingSpec &to_inv_sp = m_inventory_rings[to_inv_ind];
4237                                 InventoryList *list_from = list_s;
4238                                 if (!s.isValid())
4239                                         break;
4240                                 Inventory *inv_to = m_invmgr->getInventory(to_inv_sp.inventoryloc);
4241                                 if (!inv_to)
4242                                         break;
4243                                 InventoryList *list_to = inv_to->getList(to_inv_sp.listname);
4244                                 if (!list_to)
4245                                         break;
4246                                 ItemStack stack_from = list_from->getItem(s.i);
4247                                 assert(shift_move_amount <= stack_from.count);
4248
4249                                 infostream << "Handing IAction::Move to manager" << std::endl;
4250                                 IMoveAction *a = new IMoveAction();
4251                                 a->count = shift_move_amount;
4252                                 a->from_inv = s.inventoryloc;
4253                                 a->from_list = s.listname;
4254                                 a->from_i = s.i;
4255                                 a->to_inv = to_inv_sp.inventoryloc;
4256                                 a->to_list = to_inv_sp.listname;
4257                                 a->move_somewhere = true;
4258                                 m_invmgr->inventoryAction(a);
4259                         } while (0);
4260                 } else if (drop_amount > 0) {
4261                         // Send IAction::Drop
4262
4263                         assert(m_selected_item && m_selected_item->isValid());
4264                         assert(inv_selected);
4265                         InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
4266                         assert(list_from);
4267                         ItemStack stack_from = list_from->getItem(m_selected_item->i);
4268
4269                         // Check how many items can be dropped
4270                         drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count);
4271                         assert(drop_amount > 0 && drop_amount <= m_selected_amount);
4272                         m_selected_amount -= drop_amount;
4273
4274                         infostream << "Handing IAction::Drop to manager" << std::endl;
4275                         IDropAction *a = new IDropAction();
4276                         a->count = drop_amount;
4277                         a->from_inv = m_selected_item->inventoryloc;
4278                         a->from_list = m_selected_item->listname;
4279                         a->from_i = m_selected_item->i;
4280                         m_invmgr->inventoryAction(a);
4281                 } else if (craft_amount > 0) {
4282                         assert(s.isValid());
4283
4284                         // if there are no items selected or the selected item
4285                         // belongs to craftresult list, proceed with crafting
4286                         if (m_selected_item == NULL ||
4287                                         !m_selected_item->isValid() || m_selected_item->listname == "craftresult") {
4288
4289                                 assert(inv_s);
4290
4291                                 // Send IACTION_CRAFT
4292                                 infostream << "Handing IACTION_CRAFT to manager" << std::endl;
4293                                 ICraftAction *a = new ICraftAction();
4294                                 a->count = craft_amount;
4295                                 a->craft_inv = s.inventoryloc;
4296                                 m_invmgr->inventoryAction(a);
4297                         }
4298                 }
4299
4300                 // If m_selected_amount has been decreased to zero, deselect
4301                 if (m_selected_amount == 0) {
4302                         m_selected_swap.clear();
4303                         delete m_selected_item;
4304                         m_selected_item = NULL;
4305                         m_selected_amount = 0;
4306                         m_selected_dragging = false;
4307                 }
4308                 m_old_pointer = m_pointer;
4309         }
4310
4311         if (event.EventType == EET_GUI_EVENT) {
4312                 if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED
4313                                 && isVisible()) {
4314                         // find the element that was clicked
4315                         for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
4316                                 if ((s.ftype == f_TabHeader) &&
4317                                                 (s.fid == event.GUIEvent.Caller->getID())) {
4318                                         s.send = true;
4319                                         acceptInput();
4320                                         s.send = false;
4321                                         return true;
4322                                 }
4323                         }
4324                 }
4325                 if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
4326                                 && isVisible()) {
4327                         if (!canTakeFocus(event.GUIEvent.Element)) {
4328                                 infostream<<"GUIFormSpecMenu: Not allowing focus change."
4329                                                 <<std::endl;
4330                                 // Returning true disables focus change
4331                                 return true;
4332                         }
4333                 }
4334                 if ((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) ||
4335                                 (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
4336                                 (event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) ||
4337                                 (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) {
4338                         unsigned int btn_id = event.GUIEvent.Caller->getID();
4339
4340                         if (btn_id == 257) {
4341                                 if (m_allowclose) {
4342                                         acceptInput(quit_mode_accept);
4343                                         quitMenu();
4344                                 } else {
4345                                         acceptInput();
4346                                         m_text_dst->gotText(L"ExitButton");
4347                                 }
4348                                 // quitMenu deallocates menu
4349                                 return true;
4350                         }
4351
4352                         // find the element that was clicked
4353                         for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
4354                                 // if its a button, set the send field so
4355                                 // lua knows which button was pressed
4356                                 if ((s.ftype == f_Button || s.ftype == f_CheckBox) &&
4357                                                 s.fid == event.GUIEvent.Caller->getID()) {
4358                                         s.send = true;
4359                                         if (s.is_exit) {
4360                                                 if (m_allowclose) {
4361                                                         acceptInput(quit_mode_accept);
4362                                                         quitMenu();
4363                                                 } else {
4364                                                         m_text_dst->gotText(L"ExitButton");
4365                                                 }
4366                                                 return true;
4367                                         }
4368
4369                                         acceptInput(quit_mode_no);
4370                                         s.send = false;
4371                                         return true;
4372
4373                                 } else if ((s.ftype == f_DropDown) &&
4374                                                 (s.fid == event.GUIEvent.Caller->getID())) {
4375                                         // only send the changed dropdown
4376                                         for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
4377                                                 if (s2.ftype == f_DropDown) {
4378                                                         s2.send = false;
4379                                                 }
4380                                         }
4381                                         s.send = true;
4382                                         acceptInput(quit_mode_no);
4383
4384                                         // revert configuration to make sure dropdowns are sent on
4385                                         // regular button click
4386                                         for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
4387                                                 if (s2.ftype == f_DropDown) {
4388                                                         s2.send = true;
4389                                                 }
4390                                         }
4391                                         return true;
4392                                 } else if ((s.ftype == f_ScrollBar) &&
4393                                                 (s.fid == event.GUIEvent.Caller->getID())) {
4394                                         s.fdefault = L"Changed";
4395                                         acceptInput(quit_mode_no);
4396                                         s.fdefault = L"";
4397                                 } else if ((s.ftype == f_Unknown) &&
4398                                                 (s.fid == event.GUIEvent.Caller->getID())) {
4399                                         s.send = true;
4400                                         acceptInput();
4401                                         s.send = false;
4402                                 }
4403                         }
4404                 }
4405
4406                 if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
4407                         if (event.GUIEvent.Caller->getID() > 257) {
4408                                 bool close_on_enter = true;
4409                                 for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
4410                                         if (s.ftype == f_Unknown &&
4411                                                         s.fid == event.GUIEvent.Caller->getID()) {
4412                                                 current_field_enter_pending = s.fname;
4413                                                 std::unordered_map<std::string, bool>::const_iterator it =
4414                                                         field_close_on_enter.find(s.fname);
4415                                                 if (it != field_close_on_enter.end())
4416                                                         close_on_enter = (*it).second;
4417
4418                                                 break;
4419                                         }
4420                                 }
4421
4422                                 if (m_allowclose && close_on_enter) {
4423                                         current_keys_pending.key_enter = true;
4424                                         acceptInput(quit_mode_accept);
4425                                         quitMenu();
4426                                 } else {
4427                                         current_keys_pending.key_enter = true;
4428                                         acceptInput();
4429                                 }
4430                                 // quitMenu deallocates menu
4431                                 return true;
4432                         }
4433                 }
4434
4435                 if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) {
4436                         int current_id = event.GUIEvent.Caller->getID();
4437                         if (current_id > 257) {
4438                                 // find the element that was clicked
4439                                 for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
4440                                         // if it's a table, set the send field
4441                                         // so lua knows which table was changed
4442                                         if ((s.ftype == f_Table) && (s.fid == current_id)) {
4443                                                 s.send = true;
4444                                                 acceptInput();
4445                                                 s.send=false;
4446                                         }
4447                                 }
4448                                 return true;
4449                         }
4450                 }
4451         }
4452
4453         return Parent ? Parent->OnEvent(event) : false;
4454 }
4455
4456 /**
4457  * get name of element by element id
4458  * @param id of element
4459  * @return name string or empty string
4460  */
4461 std::string GUIFormSpecMenu::getNameByID(s32 id)
4462 {
4463         for (FieldSpec &spec : m_fields) {
4464                 if (spec.fid == id)
4465                         return spec.fname;
4466         }
4467         return "";
4468 }
4469
4470
4471 const GUIFormSpecMenu::FieldSpec *GUIFormSpecMenu::getSpecByID(s32 id)
4472 {
4473         for (FieldSpec &spec : m_fields) {
4474                 if (spec.fid == id)
4475                         return &spec;
4476         }
4477         return nullptr;
4478 }
4479
4480 /**
4481  * get label of element by id
4482  * @param id of element
4483  * @return label string or empty string
4484  */
4485 std::wstring GUIFormSpecMenu::getLabelByID(s32 id)
4486 {
4487         for (FieldSpec &spec : m_fields) {
4488                 if (spec.fid == id)
4489                         return spec.flabel;
4490         }
4491         return L"";
4492 }
4493
4494 StyleSpec GUIFormSpecMenu::getStyleForElement(const std::string &type,
4495                 const std::string &name, const std::string &parent_type) {
4496         StyleSpec ret;
4497
4498         if (!parent_type.empty()) {
4499                 auto it = theme_by_type.find(parent_type);
4500                 if (it != theme_by_type.end()) {
4501                         ret |= it->second;
4502                 }
4503         }
4504
4505         auto it = theme_by_type.find(type);
4506         if (it != theme_by_type.end()) {
4507                 ret |= it->second;
4508         }
4509
4510         it = theme_by_name.find(name);
4511         if (it != theme_by_name.end()) {
4512                 ret |= it->second;
4513         }
4514
4515         return ret;
4516 }