Formspec textlist: Black Irrlicht magic to detect fake doubleclicks
[oweals/minetest.git] / src / 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 <sstream>
25 #include <limits>
26 #include "guiFormSpecMenu.h"
27 #include "constants.h"
28 #include "gamedef.h"
29 #include "keycode.h"
30 #include "strfnd.h"
31 #include <IGUICheckBox.h>
32 #include <IGUIEditBox.h>
33 #include <IGUIButton.h>
34 #include <IGUIStaticText.h>
35 #include <IGUIFont.h>
36 #include <IGUIListBox.h>
37 #include <IGUITabControl.h>
38 #include <IGUIScrollBar.h>
39 #include <IGUIComboBox.h>
40 #include "log.h"
41 #include "tile.h" // ITextureSource
42 #include "hud.h" // drawItemStack
43 #include "util/string.h"
44 #include "util/numeric.h"
45 #include "filesys.h"
46 #include "gettime.h"
47
48 #include "gettext.h"
49
50
51 #define MY_CHECKPOS(a,b)                                                                                                        \
52         if (v_pos.size() != 2) {                                                                                                \
53                 errorstream<< "Invalid pos for element " << a << "specified: \""        \
54                         << parts[b] << "\"" << std::endl;                                                               \
55                         return;                                                                                                                 \
56         }
57
58 #define MY_CHECKGEOM(a,b)                                                                                                       \
59         if (v_geom.size() != 2) {                                                                                               \
60                 errorstream<< "Invalid pos for element " << a << "specified: \""        \
61                         << parts[b] << "\"" << std::endl;                                                               \
62                         return;                                                                                                                 \
63         }
64
65
66 /*
67         GUIFormSpecMenu
68 */
69
70 GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev,
71                 gui::IGUIElement* parent, s32 id,
72                 IMenuManager *menumgr,
73                 InventoryManager *invmgr,
74                 IGameDef *gamedef
75 ):
76         GUIModalMenu(dev->getGUIEnvironment(), parent, id, menumgr),
77         m_device(dev),
78         m_invmgr(invmgr),
79         m_gamedef(gamedef),
80         m_form_src(NULL),
81         m_text_dst(NULL),
82         m_selected_item(NULL),
83         m_selected_amount(0),
84         m_selected_dragging(false),
85         m_listbox_click_fname(),
86         m_listbox_click_index(-1),
87         m_listbox_click_time(0),
88         m_listbox_doubleclick(false),
89         m_tooltip_element(NULL),
90         m_allowclose(true),
91         m_use_gettext(false),
92         m_lock(false)
93 {
94         current_keys_pending.key_down = false;
95         current_keys_pending.key_up = false;
96         current_keys_pending.key_enter = false;
97         current_keys_pending.key_escape = false;
98
99 }
100
101 GUIFormSpecMenu::~GUIFormSpecMenu()
102 {
103         removeChildren();
104
105         delete m_selected_item;
106         delete m_form_src;
107         delete m_text_dst;
108 }
109
110 void GUIFormSpecMenu::removeChildren()
111 {
112         const core::list<gui::IGUIElement*> &children = getChildren();
113         core::list<gui::IGUIElement*> children_copy;
114         for(core::list<gui::IGUIElement*>::ConstIterator
115                         i = children.begin(); i != children.end(); i++)
116         {
117                 children_copy.push_back(*i);
118         }
119         for(core::list<gui::IGUIElement*>::Iterator
120                         i = children_copy.begin();
121                         i != children_copy.end(); i++)
122         {
123                 (*i)->remove();
124         }
125         /*{
126                 gui::IGUIElement *e = getElementFromId(256);
127                 if(e != NULL)
128                         e->remove();
129         }*/
130         if(m_tooltip_element)
131         {
132                 m_tooltip_element->remove();
133                 m_tooltip_element = NULL;
134         }
135 }
136
137 int GUIFormSpecMenu::getListboxIndex(std::string listboxname) {
138
139         std::wstring wlistboxname = narrow_to_wide(listboxname.c_str());
140
141         for(unsigned int i=0; i < m_listboxes.size(); i++) {
142
143                 std::wstring name(m_listboxes[i].first.fname.c_str());
144                 if ( name == wlistboxname) {
145                         return m_listboxes[i].second->getSelected();
146                 }
147         }
148         return -1;
149 }
150
151 bool GUIFormSpecMenu::checkListboxClick(std::wstring wlistboxname,
152                 int eventtype)
153 {
154         // WARNING: BLACK IRRLICHT MAGIC
155         // Used to fix Irrlicht's subpar reporting of single clicks and double
156         // clicks in listboxes (gui::EGET_LISTBOX_CHANGED,
157         // gui::EGET_LISTBOX_SELECTED_AGAIN):
158         // 1. IGUIListBox::setSelected() is counted as a click.
159         //    Including the initial setSelected() done by parseTextList().
160         // 2. Clicking on a the selected item and then dragging for less
161         //    than 500ms is counted as a doubleclick, no matter when the
162         //    item was previously selected (e.g. more than 500ms ago)
163
164         // So when Irrlicht reports a doubleclick, we need to check
165         // for ourselves if really was a doubleclick. Or just a fake.
166
167         for(unsigned int i=0; i < m_listboxes.size(); i++) {
168                 std::wstring name(m_listboxes[i].first.fname.c_str());
169                 int selected = m_listboxes[i].second->getSelected();
170                 if (name == wlistboxname && selected >= 0) {
171                         u32 now = getTimeMs();
172                         bool doubleclick =
173                                 (eventtype == gui::EGET_LISTBOX_SELECTED_AGAIN)
174                                 && (name == m_listbox_click_fname)
175                                 && (selected == m_listbox_click_index)
176                                 && (m_listbox_click_time >= now - 500);
177                         m_listbox_click_fname = name;
178                         m_listbox_click_index = selected;
179                         m_listbox_click_time = now;
180                         m_listbox_doubleclick = doubleclick;
181                         return true;
182                 }
183         }
184         return false;
185 }
186
187 std::vector<std::string> split(const std::string &s, char delim) {
188         std::vector<std::string> tokens;
189
190         std::string current = "";
191         bool last_was_escape = false;
192         for(unsigned int i=0; i < s.size(); i++) {
193                 if (last_was_escape) {
194                         current += '\\';
195                         current += s.c_str()[i];
196                         last_was_escape = false;
197                 }
198                 else {
199                         if (s.c_str()[i] == delim) {
200                                 tokens.push_back(current);
201                                 current = "";
202                                 last_was_escape = false;
203                         }
204                         else if (s.c_str()[i] == '\\'){
205                                 last_was_escape = true;
206                         }
207                         else {
208                                 current += s.c_str()[i];
209                                 last_was_escape = false;
210                         }
211                 }
212         }
213         //push last element
214         tokens.push_back(current);
215
216         return tokens;
217 }
218
219 void GUIFormSpecMenu::parseSize(parserData* data,std::string element) {
220         std::vector<std::string> parts = split(element,',');
221
222         if (parts.size() == 2) {
223                 v2f invsize;
224
225                 if (parts[1].find(';') != std::string::npos)
226                         parts[1] = parts[1].substr(0,parts[1].find(';'));
227
228                 invsize.X = stof(parts[0]);
229                 invsize.Y = stof(parts[1]);
230
231                 infostream<<"Form size ("<<invsize.X<<","<<invsize.Y<<")"<<std::endl;
232
233                 if (m_lock) {
234                         v2u32 current_screensize = m_device->getVideoDriver()->getScreenSize();
235                         v2u32 delta = current_screensize - m_lockscreensize;
236
237                         if (current_screensize.Y > m_lockscreensize.Y)
238                                 delta.Y /= 2;
239                         else
240                                 delta.Y = 0;
241
242                         if (current_screensize.X > m_lockscreensize.X)
243                                 delta.X /= 2;
244                         else
245                                 delta.X = 0;
246
247                         offset = v2s32(delta.X,delta.Y);
248
249                         data->screensize = m_lockscreensize;
250                 }
251                 else {
252                         offset = v2s32(0,0);
253                 }
254
255                 padding = v2s32(data->screensize.Y/40, data->screensize.Y/40);
256                 spacing = v2s32(data->screensize.Y/12, data->screensize.Y/13);
257                 imgsize = v2s32(data->screensize.Y/15, data->screensize.Y/15);
258                 data->size = v2s32(
259                         padding.X*2+spacing.X*(invsize.X-1.0)+imgsize.X,
260                         padding.Y*2+spacing.Y*(invsize.Y-1.0)+imgsize.Y + (data->helptext_h-5)
261                 );
262                 data->rect = core::rect<s32>(
263                                 data->screensize.X/2 - data->size.X/2 + offset.X,
264                                 data->screensize.Y/2 - data->size.Y/2 + offset.Y,
265                                 data->screensize.X/2 + data->size.X/2 + offset.X,
266                                 data->screensize.Y/2 + data->size.Y/2 + offset.Y
267                 );
268
269                 DesiredRect = data->rect;
270                 recalculateAbsolutePosition(false);
271                 data->basepos = getBasePos();
272                 data->bp_set = 2;
273                 return;
274         }
275         errorstream<< "Invalid size element (" << parts.size() << "): '" << element << "'"  << std::endl;
276 }
277
278 void GUIFormSpecMenu::parseList(parserData* data,std::string element) {
279
280         if (m_gamedef == 0) {
281                 errorstream<<"WARNING: invalid use of 'list' with m_gamedef==0"<<std::endl;
282                 return;
283         }
284
285         std::vector<std::string> parts = split(element,';');
286
287         if ((parts.size() == 4) || (parts.size() == 5)) {
288                 std::string location = parts[0];
289                 std::string listname = parts[1];
290                 std::vector<std::string> v_pos  = split(parts[2],',');
291                 std::vector<std::string> v_geom = split(parts[3],',');
292                 std::string startindex = "";
293                 if (parts.size() == 5)
294                         startindex = parts[4];
295
296                 MY_CHECKPOS("list",2);
297                 MY_CHECKGEOM("list",3);
298
299                 InventoryLocation loc;
300
301                 if(location == "context" || location == "current_name")
302                         loc = m_current_inventory_location;
303                 else
304                         loc.deSerialize(location);
305
306                 v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
307                 pos.X += stof(v_pos[0]) * (float)spacing.X;
308                 pos.Y += stof(v_pos[1]) * (float)spacing.Y;
309
310                 v2s32 geom;
311                 geom.X = stoi(v_geom[0]);
312                 geom.Y = stoi(v_geom[1]);
313                 infostream<<"list inv="<<location<<", listname="<<listname
314                                 <<", pos=("<<pos.X<<","<<pos.Y<<")"
315                                 <<", geom=("<<geom.X<<","<<geom.Y<<")"
316                                 <<std::endl;
317
318                 s32 start_i = 0;
319                 if(startindex != "")
320                         start_i = stoi(startindex);
321                 if(data->bp_set != 2)
322                         errorstream<<"WARNING: invalid use of list without a size[] element"<<std::endl;
323                 m_inventorylists.push_back(ListDrawSpec(loc, listname, pos, geom, start_i));
324                 return;
325         }
326         errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'"  << std::endl;
327 }
328
329 void GUIFormSpecMenu::parseCheckbox(parserData* data,std::string element) {
330         std::vector<std::string> parts = split(element,';');
331
332         if ((parts.size() == 3) || (parts.size() == 4)) {
333                 std::vector<std::string> v_pos = split(parts[0],',');
334                 std::string name = parts[1];
335                 std::string label = parts[2];
336                 std::string selected = "";
337
338                 if (parts.size() == 4)
339                         selected = parts[3];
340
341                 MY_CHECKPOS("checkbox",0);
342
343                 v2s32 pos = padding;
344                 pos.X += stof(v_pos[0]) * (float) spacing.X;
345                 pos.Y += stof(v_pos[1]) * (float) spacing.Y;
346
347                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y+((imgsize.Y/2)-15), pos.X+300, pos.Y+((imgsize.Y/2)+15));
348
349                 bool fselected = false;
350
351                 if (selected == "true")
352                         fselected = true;
353
354                 std::wstring wlabel = narrow_to_wide(label.c_str());
355
356                 if (m_use_gettext)
357                         wlabel = wstrgettext(label);
358
359                 FieldSpec spec = FieldSpec(
360                                 narrow_to_wide(name.c_str()),
361                                 L"",
362                                 wlabel,
363                                 258+m_fields.size()
364                         );
365
366                 spec.ftype = f_CheckBox;
367                 spec.flabel = wlabel; //Needed for displaying text on MSVC
368                 gui::IGUICheckBox* e = Environment->addCheckBox(fselected, rect, this,
369                                         spec.fid, spec.flabel.c_str());
370                 m_checkboxes.push_back(std::pair<FieldSpec,gui::IGUICheckBox*>(spec,e));
371                 m_fields.push_back(spec);
372                 return;
373         }
374         errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'"  << std::endl;
375 }
376
377 void GUIFormSpecMenu::parseImage(parserData* data,std::string element) {
378         std::vector<std::string> parts = split(element,';');
379
380         if (parts.size() == 3) {
381                 std::vector<std::string> v_pos = split(parts[0],',');
382                 std::vector<std::string> v_geom = split(parts[1],',');
383                 std::string name = parts[2];
384
385                 MY_CHECKPOS("image",0);
386                 MY_CHECKGEOM("image",1);
387
388                 v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
389                 pos.X += stof(v_pos[0]) * (float) spacing.X;
390                 pos.Y += stof(v_pos[1]) * (float) spacing.Y;
391
392                 v2s32 geom;
393                 geom.X = stoi(v_geom[0]) * (float)imgsize.X;
394                 geom.Y = stoi(v_geom[1]) * (float)imgsize.Y;
395
396                 infostream<<"image name="<<name
397                                 <<", pos=("<<pos.X<<","<<pos.Y<<")"
398                                 <<", geom=("<<geom.X<<","<<geom.Y<<")"
399                                 <<std::endl;
400                 if(data->bp_set != 2)
401                         errorstream<<"WARNING: invalid use of image without a size[] element"<<std::endl;
402                 m_images.push_back(ImageDrawSpec(name, pos, geom));
403                 return;
404         }
405
406         if (parts.size() == 2) {
407                 std::vector<std::string> v_pos = split(parts[0],',');
408                 std::string name = parts[1];
409
410                 MY_CHECKPOS("image",0);
411
412                 v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
413                 pos.X += stof(v_pos[0]) * (float) spacing.X;
414                 pos.Y += stof(v_pos[1]) * (float) spacing.Y;
415
416                 std::cout<<"image name="<<name
417                                 <<", pos=("<<pos.X<<","<<pos.Y<<")"
418                                 <<std::endl;
419                 if(data->bp_set != 2)
420                         errorstream<<"WARNING: invalid use of image without a size[] element"<<std::endl;
421                 m_images.push_back(ImageDrawSpec(name, pos));
422                 return;
423         }
424         errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'"  << std::endl;
425 }
426
427 void GUIFormSpecMenu::parseItemImage(parserData* data,std::string element) {
428         std::vector<std::string> parts = split(element,';');
429
430         if (parts.size() == 3) {
431                 std::vector<std::string> v_pos = split(parts[0],',');
432                 std::vector<std::string> v_geom = split(parts[1],',');
433                 std::string name = parts[2];
434
435                 MY_CHECKPOS("itemimage",0);
436                 MY_CHECKGEOM("itemimage",1);
437
438                 v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
439                 pos.X += stof(v_pos[0]) * (float) spacing.X;
440                 pos.Y += stof(v_pos[1]) * (float) spacing.Y;
441
442                 v2s32 geom;
443                 geom.X = stoi(v_geom[0]) * (float)imgsize.X;
444                 geom.Y = stoi(v_geom[1]) * (float)imgsize.Y;
445
446                 infostream<<"item name="<<name
447                                 <<", pos=("<<pos.X<<","<<pos.Y<<")"
448                                 <<", geom=("<<geom.X<<","<<geom.Y<<")"
449                                 <<std::endl;
450                 if(data->bp_set != 2)
451                         errorstream<<"WARNING: invalid use of item_image without a size[] element"<<std::endl;
452                 m_itemimages.push_back(ImageDrawSpec(name, pos, geom));
453                 return;
454         }
455         errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'"  << std::endl;
456 }
457
458 void GUIFormSpecMenu::parseButton(parserData* data,std::string element,std::string type) {
459         std::vector<std::string> parts = split(element,';');
460
461         if (parts.size() == 4) {
462                 std::vector<std::string> v_pos = split(parts[0],',');
463                 std::vector<std::string> v_geom = split(parts[1],',');
464                 std::string name = parts[2];
465                 std::string label = parts[3];
466
467                 MY_CHECKPOS("button",0);
468                 MY_CHECKGEOM("button",1);
469
470                 v2s32 pos = padding;
471                 pos.X += stof(v_pos[0]) * (float)spacing.X;
472                 pos.Y += stof(v_pos[1]) * (float)spacing.Y;
473
474                 v2s32 geom;
475                 geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
476                 pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
477
478                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y-15, pos.X+geom.X, pos.Y+15);
479
480                 if(data->bp_set != 2)
481                         errorstream<<"WARNING: invalid use of button without a size[] element"<<std::endl;
482
483                 label = unescape_string(label);
484
485                 std::wstring wlabel = narrow_to_wide(label.c_str());
486
487                 if (m_use_gettext)
488                         wlabel = wstrgettext(label);
489
490                 FieldSpec spec = FieldSpec(
491                         narrow_to_wide(name.c_str()),
492                         wlabel,
493                         L"",
494                         258+m_fields.size()
495                 );
496                 spec.ftype = f_Button;
497                 if(type == "button_exit")
498                         spec.is_exit = true;
499
500                 Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());
501                 m_fields.push_back(spec);
502                 return;
503         }
504         errorstream<< "Invalid button element(" << parts.size() << "): '" << element << "'"  << std::endl;
505 }
506
507 void GUIFormSpecMenu::parseBackground(parserData* data,std::string element) {
508         std::vector<std::string> parts = split(element,';');
509
510         if (parts.size() == 3) {
511                 std::vector<std::string> v_pos = split(parts[0],',');
512                 std::vector<std::string> v_geom = split(parts[1],',');
513                 std::string name = parts[2];
514
515                 MY_CHECKPOS("background",0);
516                 MY_CHECKGEOM("background",1);
517
518                 v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
519                 pos.X += stof(v_pos[0]) * (float)spacing.X - ((float)spacing.X-(float)imgsize.X)/2;
520                 pos.Y += stof(v_pos[1]) * (float)spacing.Y - ((float)spacing.Y-(float)imgsize.Y)/2;
521
522                 v2s32 geom;
523                 geom.X = stof(v_geom[0]) * (float)spacing.X;
524                 geom.Y = stof(v_geom[1]) * (float)spacing.Y;
525
526                 infostream<<"image name="<<name
527                                 <<", pos=("<<pos.X<<","<<pos.Y<<")"
528                                 <<", geom=("<<geom.X<<","<<geom.Y<<")"
529                                 <<std::endl;
530                 if(data->bp_set != 2)
531                         errorstream<<"WARNING: invalid use of background without a size[] element"<<std::endl;
532                 m_backgrounds.push_back(ImageDrawSpec(name, pos, geom));
533                 return;
534         }
535         errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'"  << std::endl;
536 }
537
538 void GUIFormSpecMenu::parseTextList(parserData* data,std::string element) {
539         std::vector<std::string> parts = split(element,';');
540
541         if ((parts.size() == 5) || (parts.size() == 6)) {
542                 std::vector<std::string> v_pos = split(parts[0],',');
543                 std::vector<std::string> v_geom = split(parts[1],',');
544                 std::string name = parts[2];
545                 std::vector<std::string> items = split(parts[3],',');
546                 std::string str_initial_selection = "";
547                 std::string str_transparent = "false";
548
549                 if (parts.size() >= 5)
550                         str_initial_selection = parts[4];
551
552                 if (parts.size() >= 6)
553                         str_transparent = parts[5];
554
555                 MY_CHECKPOS("textlist",0);
556                 MY_CHECKGEOM("textlist",1);
557
558                 v2s32 pos = padding;
559                 pos.X += stof(v_pos[0]) * (float)spacing.X;
560                 pos.Y += stof(v_pos[1]) * (float)spacing.Y;
561
562                 v2s32 geom;
563                 geom.X = stof(v_geom[0]) * (float)spacing.X;
564                 geom.Y = stof(v_geom[1]) * (float)spacing.Y;
565
566
567                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
568
569                 std::wstring fname_w = narrow_to_wide(name.c_str());
570
571                 FieldSpec spec = FieldSpec(
572                         fname_w,
573                         L"",
574                         L"",
575                         258+m_fields.size()
576                 );
577
578                 spec.ftype = f_ListBox;
579
580                 //now really show list
581                 gui::IGUIListBox *e = Environment->addListBox(rect, this,spec.fid);
582
583                 //don't reset if we already have a user specified selection
584                 if (data->listbox_selections.find(fname_w) == data->listbox_selections.end()) {
585                         e->setAutoScrollEnabled(false);
586                 }
587
588                 if (str_transparent == "false")
589                         e->setDrawBackground(true);
590
591                 for (unsigned int i=0; i < items.size(); i++) {
592                         if (items[i].c_str()[0] == '#') {
593                                 if (items[i].c_str()[1] == '#') {
594                                         e->addItem(narrow_to_wide(unescape_string(items[i])).c_str() +1);
595                                 }
596                                 else {
597                                         std::string color = items[i].substr(1,6);
598                                         std::wstring toadd =
599                                                 narrow_to_wide(unescape_string(items[i]).c_str() + 7);
600
601
602                                         e->addItem(toadd.c_str());
603
604                                         irr::video::SColor toset;
605
606                                         if (parseColor(color, toset))
607                                                 e->setItemOverrideColor(i,toset);
608                                 }
609                         }
610                         else {
611                                 e->addItem(narrow_to_wide(unescape_string(items[i])).c_str());
612                         }
613                 }
614
615                 if (data->listbox_selections.find(fname_w) != data->listbox_selections.end()) {
616                         e->setSelected(data->listbox_selections[fname_w]);
617                 }
618
619                 if (str_initial_selection != "")
620                         e->setSelected(stoi(str_initial_selection.c_str())-1);
621
622                 m_listboxes.push_back(std::pair<FieldSpec,gui::IGUIListBox*>(spec,e));
623                 m_fields.push_back(spec);
624                 return;
625         }
626         errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'"  << std::endl;
627 }
628
629
630 void GUIFormSpecMenu::parseDropDown(parserData* data,std::string element) {
631         std::vector<std::string> parts = split(element,';');
632
633         if (parts.size() == 5) {
634                 std::vector<std::string> v_pos = split(parts[0],',');
635                 std::string name = parts[2];
636                 std::vector<std::string> items = split(parts[3],',');
637                 std::string str_initial_selection = "";
638                 str_initial_selection = parts[4];
639
640                 MY_CHECKPOS("dropdown",0);
641
642                 v2s32 pos = padding;
643                 pos.X += stof(v_pos[0]) * (float)spacing.X;
644                 pos.Y += stof(v_pos[1]) * (float)spacing.Y;
645
646                 s32 width = stof(parts[1]) * (float)spacing.Y;
647
648                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+width, pos.Y+30);
649
650                 std::wstring fname_w = narrow_to_wide(name.c_str());
651
652                 FieldSpec spec = FieldSpec(
653                         fname_w,
654                         L"",
655                         L"",
656                         258+m_fields.size()
657                 );
658
659                 spec.ftype = f_DropDown;
660                 spec.send = true;
661
662                 //now really show list
663                 gui::IGUIComboBox *e = Environment->addComboBox(rect, this,spec.fid);
664
665                 //don't reset if we already have a user specified selection
666                 //if (data->combobox_selections.find(fname_w) == data->listbox_selections.end()) {
667                 //      e->setAutoScrollEnabled(false);
668                 //}
669
670                 for (unsigned int i=0; i < items.size(); i++) {
671                         e->addItem(narrow_to_wide(items[i]).c_str());
672                 }
673
674                 if (str_initial_selection != "")
675                         e->setSelected(stoi(str_initial_selection.c_str())-1);
676
677                 //if (data->listbox_selections.find(fname_w) != data->listbox_selections.end()) {
678                 //      e->setSelected(data->listbox_selections[fname_w]);
679                 //}
680
681                 //m_listboxes.push_back(std::pair<FieldSpec,gui::IGUIListBox*>(spec,e));
682                 m_fields.push_back(spec);
683                 return;
684         }
685         errorstream << "Invalid dropdown element(" << parts.size() << "): '"
686                                 << element << "'"  << std::endl;
687 }
688
689 void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element) {
690         std::vector<std::string> parts = split(element,';');
691
692         if (parts.size() == 4) {
693                 std::vector<std::string> v_pos = split(parts[0],',');
694                 std::vector<std::string> v_geom = split(parts[1],',');
695                 std::string name = parts[2];
696                 std::string label = parts[3];
697
698                 MY_CHECKPOS("pwdfield",0);
699                 MY_CHECKGEOM("pwdfield",1);
700
701                 v2s32 pos;
702                 pos.X += stof(v_pos[0]) * (float)spacing.X;
703                 pos.Y += stof(v_pos[1]) * (float)spacing.Y;
704
705                 v2s32 geom;
706                 geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
707
708                 pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
709                 pos.Y -= 15;
710                 geom.Y = 30;
711
712                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
713
714                 label = unescape_string(label);
715
716                 std::wstring wlabel = narrow_to_wide(label.c_str());
717
718                 if (m_use_gettext) {
719                         if (label.length() > 1)
720                                 wlabel = wstrgettext(label);
721                         else
722                                 wlabel = L"";
723                 }
724
725                 FieldSpec spec = FieldSpec(
726                         narrow_to_wide(name.c_str()),
727                         wlabel,
728                         L"",
729                         258+m_fields.size()
730                         );
731
732                 spec.send = true;
733                 gui::IGUIEditBox * e = Environment->addEditBox(0, rect, true, this, spec.fid);
734                 Environment->setFocus(e);
735
736                 if (label.length() > 1)
737                 {
738                         rect.UpperLeftCorner.Y -= 15;
739                         rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 15;
740                         Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0);
741                 }
742
743                 e->setPasswordBox(true,L'*');
744
745                 irr::SEvent evt;
746                 evt.EventType            = EET_KEY_INPUT_EVENT;
747                 evt.KeyInput.Key         = KEY_END;
748                 evt.KeyInput.Char        = 0;
749                 evt.KeyInput.Control     = 0;
750                 evt.KeyInput.Shift       = 0;
751                 evt.KeyInput.PressedDown = true;
752                 e->OnEvent(evt);
753                 m_fields.push_back(spec);
754                 return;
755         }
756         errorstream<< "Invalid pwdfield element(" << parts.size() << "): '" << element << "'"  << std::endl;
757 }
758
759 void GUIFormSpecMenu::parseSimpleField(parserData* data,std::vector<std::string> &parts) {
760         std::string name = parts[0];
761         std::string label = parts[1];
762         std::string default_val = parts[2];
763
764         core::rect<s32> rect;
765
766         if(!data->bp_set)
767         {
768                 rect = core::rect<s32>(
769                         data->screensize.X/2 - 580/2,
770                         data->screensize.Y/2 - 300/2,
771                         data->screensize.X/2 + 580/2,
772                         data->screensize.Y/2 + 300/2
773                 );
774                 DesiredRect = rect;
775                 recalculateAbsolutePosition(false);
776                 data->basepos = getBasePos();
777                 data->bp_set = 1;
778         }
779         else if(data->bp_set == 2)
780                 errorstream<<"WARNING: invalid use of unpositioned \"field\" in inventory"<<std::endl;
781
782         v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
783         pos.Y = ((m_fields.size()+2)*60);
784         v2s32 size = DesiredRect.getSize();
785
786         rect = core::rect<s32>(size.X/2-150, pos.Y, (size.X/2-150)+300, pos.Y+30);
787
788
789         if(m_form_src)
790                 default_val = m_form_src->resolveText(default_val);
791
792         default_val = unescape_string(default_val);
793         label = unescape_string(label);
794
795         std::wstring wlabel = narrow_to_wide(label.c_str());
796
797         if (m_use_gettext) {
798                 if (label.length() > 1)
799                         wlabel = wstrgettext(label);
800                 else
801                         wlabel = L"";
802         }
803
804         FieldSpec spec = FieldSpec(
805                 narrow_to_wide(name.c_str()),
806                 wlabel,
807                 narrow_to_wide(default_val.c_str()),
808                 258+m_fields.size()
809         );
810
811         if (name == "")
812         {
813                 // spec field id to 0, this stops submit searching for a value that isn't there
814                 Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid);
815         }
816         else
817         {
818                 spec.send = true;
819                 gui::IGUIEditBox *e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid);
820                 Environment->setFocus(e);
821
822                 irr::SEvent evt;
823                 evt.EventType            = EET_KEY_INPUT_EVENT;
824                 evt.KeyInput.Key         = KEY_END;
825                 evt.KeyInput.Char        = 0;
826                 evt.KeyInput.Control     = 0;
827                 evt.KeyInput.Shift       = 0;
828                 evt.KeyInput.PressedDown = true;
829                 e->OnEvent(evt);
830
831                 if (label.length() > 1)
832                 {
833                         rect.UpperLeftCorner.Y -= 15;
834                         rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 15;
835                         Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0);
836                 }
837         }
838
839         m_fields.push_back(spec);
840 }
841
842 void GUIFormSpecMenu::parseTextArea(parserData* data,std::vector<std::string>& parts,std::string type) {
843
844         std::vector<std::string> v_pos = split(parts[0],',');
845         std::vector<std::string> v_geom = split(parts[1],',');
846         std::string name = parts[2];
847         std::string label = parts[3];
848         std::string default_val = parts[4];
849
850         MY_CHECKPOS(type,0);
851         MY_CHECKGEOM(type,1);
852
853         v2s32 pos;
854         pos.X = stof(v_pos[0]) * (float) spacing.X;
855         pos.Y = stof(v_pos[1]) * (float) spacing.Y;
856
857         v2s32 geom;
858
859         geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
860
861         if (type == "textarea")
862         {
863                 geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y);
864                 pos.Y += 15;
865         }
866         else
867         {
868                 pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
869                 pos.Y -= 15;
870                 geom.Y = 30;
871         }
872
873         core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
874
875         if(data->bp_set != 2)
876                 errorstream<<"WARNING: invalid use of positioned "<<type<<" without a size[] element"<<std::endl;
877
878         if(m_form_src)
879                 default_val = m_form_src->resolveText(default_val);
880
881
882         default_val = unescape_string(default_val);
883         label = unescape_string(label);
884
885         std::wstring wlabel = narrow_to_wide(label.c_str());
886
887         if (m_use_gettext) {
888                 if (label.length() > 1)
889                         wlabel = wstrgettext(label);
890                 else
891                         wlabel = L"";
892         }
893
894         FieldSpec spec = FieldSpec(
895                 narrow_to_wide(name.c_str()),
896                 wlabel,
897                 narrow_to_wide(default_val.c_str()),
898                 258+m_fields.size()
899         );
900
901         if (name == "")
902         {
903                 // spec field id to 0, this stops submit searching for a value that isn't there
904                 Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid);
905         }
906         else
907         {
908                 spec.send = true;
909                 gui::IGUIEditBox *e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid);
910                 Environment->setFocus(e);
911
912                 if (type == "textarea")
913                 {
914                         e->setMultiLine(true);
915                         e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT);
916                 } else {
917                         irr::SEvent evt;
918                         evt.EventType            = EET_KEY_INPUT_EVENT;
919                         evt.KeyInput.Key         = KEY_END;
920                         evt.KeyInput.Char        = 0;
921                         evt.KeyInput.Control     = 0;
922                         evt.KeyInput.Shift       = 0;
923                         evt.KeyInput.PressedDown = true;
924                         e->OnEvent(evt);
925                 }
926
927                 if (label.length() > 1)
928                 {
929                         rect.UpperLeftCorner.Y -= 15;
930                         rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 15;
931                         Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0);
932                 }
933         }
934         m_fields.push_back(spec);
935 }
936
937 void GUIFormSpecMenu::parseField(parserData* data,std::string element,std::string type) {
938         std::vector<std::string> parts = split(element,';');
939
940         if (parts.size() == 3) {
941                 parseSimpleField(data,parts);
942                 return;
943         }
944
945         if (parts.size() == 5) {
946                 parseTextArea(data,parts,type);
947                 return;
948         }
949         errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'"  << std::endl;
950 }
951
952 void GUIFormSpecMenu::parseLabel(parserData* data,std::string element) {
953         std::vector<std::string> parts = split(element,';');
954
955         if (parts.size() == 2) {
956                 std::vector<std::string> v_pos = split(parts[0],',');
957                 std::string text = parts[1];
958
959                 MY_CHECKPOS("label",0);
960
961                 v2s32 pos = padding;
962                 pos.X += stof(v_pos[0]) * (float)spacing.X;
963                 pos.Y += stof(v_pos[1]) * (float)spacing.Y;
964
965                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y+((imgsize.Y/2)-15), pos.X+300, pos.Y+((imgsize.Y/2)+15));
966
967                 if(data->bp_set != 2)
968                         errorstream<<"WARNING: invalid use of label without a size[] element"<<std::endl;
969
970                 text = unescape_string(text);
971
972                 std::wstring wlabel = narrow_to_wide(text.c_str());
973
974                 if (m_use_gettext)
975                         wlabel = wstrgettext(text);
976
977                 FieldSpec spec = FieldSpec(
978                         L"",
979                         wlabel,
980                         L"",
981                         258+m_fields.size()
982                 );
983                 Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid);
984                 m_fields.push_back(spec);
985                 return;
986         }
987         errorstream<< "Invalid label element(" << parts.size() << "): '" << element << "'"  << std::endl;
988 }
989
990 void GUIFormSpecMenu::parseVertLabel(parserData* data,std::string element) {
991         std::vector<std::string> parts = split(element,';');
992
993         if (parts.size() == 2) {
994                 std::vector<std::string> v_pos = split(parts[0],',');
995                 std::string text = parts[1];
996
997                 MY_CHECKPOS("vertlabel",1);
998
999                 v2s32 pos = padding;
1000                 pos.X += stof(v_pos[0]) * (float)spacing.X;
1001                 pos.Y += stof(v_pos[1]) * (float)spacing.Y;
1002
1003                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y+((imgsize.Y/2)-15), pos.X+15, pos.Y+300);
1004
1005                 if(data->bp_set != 2)
1006                         errorstream<<"WARNING: invalid use of label without a size[] element"<<std::endl;
1007
1008                 text = unescape_string(text);
1009                 std::string label = "";
1010
1011                 if (m_use_gettext) {
1012                         const char* toset = gettext(text.c_str());
1013
1014                         text = std::string(toset);
1015                 }
1016
1017                 for (unsigned int i=0; i < text.length(); i++) {
1018                         label += text.c_str()[i];
1019                         label += "\n";
1020                 }
1021
1022                 FieldSpec spec = FieldSpec(
1023                         L"",
1024                         narrow_to_wide(label.c_str()),
1025                         L"",
1026                         258+m_fields.size()
1027                 );
1028                 gui::IGUIStaticText *t =
1029                                 Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid);
1030                 t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
1031                 m_fields.push_back(spec);
1032                 return;
1033         }
1034         errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'"  << std::endl;
1035 }
1036
1037 void GUIFormSpecMenu::parseImageButton(parserData* data,std::string element,std::string type) {
1038         std::vector<std::string> parts = split(element,';');
1039
1040         if ((parts.size() == 5) || (parts.size() == 7) || (parts.size() == 8)) {
1041                 std::vector<std::string> v_pos = split(parts[0],',');
1042                 std::vector<std::string> v_geom = split(parts[1],',');
1043                 std::string image_name = parts[2];
1044                 std::string name = parts[3];
1045                 std::string label = parts[4];
1046
1047                 MY_CHECKPOS("imagebutton",0);
1048                 MY_CHECKGEOM("imagebutton",1);
1049
1050                 v2s32 pos = padding;
1051                 pos.X += stof(v_pos[0]) * (float)spacing.X;
1052                 pos.Y += stof(v_pos[1]) * (float)spacing.Y;
1053                 v2s32 geom;
1054                 geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
1055                 geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
1056
1057                 bool noclip = false;
1058                 bool drawborder = true;
1059
1060                 if ((parts.size() >= 7)) {
1061                         if (parts[5] == "true")
1062                                 noclip = true;
1063
1064                         if (parts[6] == "false")
1065                                 drawborder = false;
1066                 }
1067                 
1068                 std::string pressed_image_name = "";
1069                 
1070                 if ((parts.size() == 8)) {
1071                         pressed_image_name = parts[7];
1072                 }
1073
1074                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
1075
1076                 if(data->bp_set != 2)
1077                         errorstream<<"WARNING: invalid use of item_image_button without a size[] element"<<std::endl;
1078
1079                 label = unescape_string(label);
1080
1081                 std::wstring wlabel = narrow_to_wide(label.c_str());
1082
1083                 if (m_use_gettext)
1084                         wlabel = wstrgettext(label);
1085
1086                 FieldSpec spec = FieldSpec(
1087                         narrow_to_wide(name.c_str()),
1088                         wlabel,
1089                         narrow_to_wide(image_name.c_str()),
1090                         258+m_fields.size()
1091                 );
1092                 spec.ftype = f_Button;
1093                 if(type == "image_button_exit")
1094                         spec.is_exit = true;
1095
1096                 video::ITexture *texture = 0;
1097                 video::ITexture *pressed_texture = 0;
1098                 //if there's no gamedef specified try to get direct
1099                 //TODO check for possible texture leak
1100                 if (m_gamedef != 0) {
1101                         texture = m_gamedef->tsrc()->getTexture(image_name);
1102                         if ((parts.size() == 8)) {
1103                                 pressed_texture = m_gamedef->tsrc()->getTexture(pressed_image_name);
1104                         }
1105                 } else {
1106                         if (fs::PathExists(image_name)) {
1107                                 texture = Environment->getVideoDriver()->getTexture(image_name.c_str());
1108                                 m_Textures.push_back(texture);
1109                         }
1110                         if (fs::PathExists(pressed_image_name)) {
1111                                 pressed_texture = Environment->getVideoDriver()->getTexture(pressed_image_name.c_str());
1112                                 m_Textures.push_back(pressed_texture);
1113                         }
1114                 }
1115                 if (parts.size() < 8)
1116                         pressed_texture = texture;
1117
1118                 gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());
1119                 e->setUseAlphaChannel(true);
1120                 e->setImage(texture);
1121                 e->setPressedImage(pressed_texture);
1122                 e->setScaleImage(true);
1123                 e->setNotClipped(noclip);
1124                 e->setDrawBorder(drawborder);
1125
1126                 m_fields.push_back(spec);
1127                 return;
1128         }
1129
1130         errorstream<< "Invalid imagebutton element(" << parts.size() << "): '" << element << "'"  << std::endl;
1131 }
1132
1133 void GUIFormSpecMenu::parseTabHeader(parserData* data,std::string element) {
1134         std::vector<std::string> parts = split(element,';');
1135
1136         if ((parts.size() == 4) || (parts.size() == 6)) {
1137                 std::vector<std::string> v_pos = split(parts[0],',');
1138                 std::string name = parts[1];
1139                 std::vector<std::string> buttons = split(parts[2],',');
1140                 std::string str_index = parts[3];
1141                 bool show_background = true;
1142                 bool show_border = true;
1143                 int tab_index = stoi(str_index) -1;
1144
1145                 MY_CHECKPOS("tabheader",0);
1146
1147                 if (parts.size() == 6) {
1148                         if (parts[4] == "true")
1149                                 show_background = false;
1150                         if (parts[5] == "false")
1151                                 show_border = false;
1152                 }
1153
1154                 FieldSpec spec = FieldSpec(
1155                         narrow_to_wide(name.c_str()),
1156                         L"",
1157                         L"",
1158                         258+m_fields.size()
1159                 );
1160
1161                 spec.ftype = f_TabHeader;
1162
1163                 v2s32 pos = padding;
1164                 pos.X += stof(v_pos[0]) * (float)spacing.X;
1165                 pos.Y += stof(v_pos[1]) * (float)spacing.Y;
1166                 v2s32 geom;
1167                 geom.X = data->screensize.Y;
1168                 geom.Y = 30;
1169
1170                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
1171
1172                 gui::IGUITabControl *e = Environment->addTabControl(rect,this,show_background,show_border,spec.fid);
1173
1174                 e->setNotClipped(true);
1175
1176                 for (unsigned int i=0; i< buttons.size(); i++) {
1177                         wchar_t* wbutton = 0;
1178
1179                         if (m_use_gettext)
1180                                 wbutton = wgettext(buttons[i].c_str());
1181                         else
1182                                 wbutton = (wchar_t*) narrow_to_wide(buttons[i].c_str()).c_str();
1183
1184                         e->addTab(wbutton,-1);
1185
1186                         if (m_use_gettext)
1187                                 delete[] wbutton;
1188                 }
1189
1190                 if ((tab_index >= 0) &&
1191                                 (buttons.size() < INT_MAX) &&
1192                                 (tab_index < (int) buttons.size()))
1193                         e->setActiveTab(tab_index);
1194
1195                 m_fields.push_back(spec);
1196                 return;
1197         }
1198         errorstream<< "Invalid TabHeader element(" << parts.size() << "): '" << element << "'"  << std::endl;
1199 }
1200
1201 void GUIFormSpecMenu::parseItemImageButton(parserData* data,std::string element) {
1202
1203         if (m_gamedef == 0) {
1204                 errorstream<<"WARNING: invalid use of item_image_button with m_gamedef==0"<<std::endl;
1205                 return;
1206         }
1207
1208         std::vector<std::string> parts = split(element,';');
1209
1210         if (parts.size() == 5) {
1211                 std::vector<std::string> v_pos = split(parts[0],',');
1212                 std::vector<std::string> v_geom = split(parts[1],',');
1213                 std::string item_name = parts[2];
1214                 std::string name = parts[3];
1215                 std::string label = parts[4];
1216
1217                 MY_CHECKPOS("itemimagebutton",0);
1218                 MY_CHECKGEOM("itemimagebutton",1);
1219
1220                 v2s32 pos = padding;
1221                 pos.X += stof(v_pos[0]) * (float)spacing.X;
1222                 pos.Y += stof(v_pos[1]) * (float)spacing.Y;
1223                 v2s32 geom;
1224                 geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
1225                 geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
1226
1227                 core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
1228
1229                 if(data->bp_set != 2)
1230                         errorstream<<"WARNING: invalid use of item_image_button without a size[] element"<<std::endl;
1231
1232                 IItemDefManager *idef = m_gamedef->idef();
1233                 ItemStack item;
1234                 item.deSerialize(item_name, idef);
1235                 video::ITexture *texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef);
1236                 std::string tooltip = item.getDefinition(idef).description;
1237
1238                 label = unescape_string(label);
1239                 FieldSpec spec = FieldSpec(
1240                         narrow_to_wide(name.c_str()),
1241                         narrow_to_wide(label.c_str()),
1242                         narrow_to_wide(item_name.c_str()),
1243                         258+m_fields.size()
1244                 );
1245
1246                 gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());
1247                 e->setUseAlphaChannel(true);
1248                 e->setImage(texture);
1249                 e->setPressedImage(texture);
1250                 e->setScaleImage(true);
1251                 spec.ftype = f_Button;
1252                 rect+=data->basepos-padding;
1253                 spec.rect=rect;
1254                 if (tooltip!="")
1255                         spec.tooltip=tooltip;
1256                 m_fields.push_back(spec);
1257                 return;
1258         }
1259         errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'"  << std::endl;
1260 }
1261
1262 void GUIFormSpecMenu::parseBox(parserData* data,std::string element) {
1263         std::vector<std::string> parts = split(element,';');
1264
1265         if (parts.size() == 3) {
1266                 std::vector<std::string> v_pos = split(parts[0],',');
1267                 std::vector<std::string> v_geom = split(parts[1],',');
1268                 std::string color_str = parts[2];
1269
1270                 MY_CHECKPOS("box",0);
1271                 MY_CHECKGEOM("box",1);
1272
1273                 v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
1274                 pos.X += stof(v_pos[0]) * (float) spacing.X;
1275                 pos.Y += stof(v_pos[1]) * (float) spacing.Y;
1276
1277                 v2s32 geom;
1278                 geom.X = stof(v_geom[0]) * (float)spacing.X;
1279                 geom.Y = stof(v_geom[1]) * (float)spacing.Y;
1280
1281                 irr::video::SColor color;
1282
1283                 if (parseColor(color_str, color)) {
1284                         BoxDrawSpec spec(pos,geom,color);
1285
1286                         m_boxes.push_back(spec);
1287                 }
1288                 else {
1289                         errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'  INVALID COLOR"  << std::endl;
1290                 }
1291                 return;
1292         }
1293         errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'"  << std::endl;
1294 }
1295
1296 void GUIFormSpecMenu::parseElement(parserData* data,std::string element) {
1297
1298         //some prechecks
1299         if (element == "")
1300                 return;
1301
1302         std::vector<std::string> parts = split(element,'[');
1303
1304         // ugly workaround to keep compatibility
1305         if (parts.size() > 2) {
1306                 if (trim(parts[0]) == "image") {
1307                         for (unsigned int i=2;i< parts.size(); i++) {
1308                                 parts[1] += "[" + parts[i];
1309                         }
1310                 }
1311                 else { return; }
1312         }
1313
1314         if (parts.size() < 2) {
1315                 return;
1316         }
1317
1318         std::string type = trim(parts[0]);
1319         std::string description = trim(parts[1]);
1320
1321         if ((type == "size") || (type == "invsize")){
1322                 parseSize(data,description);
1323                 return;
1324         }
1325
1326         if (type == "list") {
1327                 parseList(data,description);
1328                 return;
1329         }
1330
1331         if (type == "checkbox") {
1332                 parseCheckbox(data,description);
1333                 return;
1334         }
1335
1336         if (type == "image") {
1337                 parseImage(data,description);
1338                 return;
1339         }
1340
1341         if (type == "item_image") {
1342                 parseItemImage(data,description);
1343                 return;
1344         }
1345
1346         if ((type == "button") || (type == "button_exit")) {
1347                 parseButton(data,description,type);
1348                 return;
1349         }
1350
1351         if (type == "background") {
1352                 parseBackground(data,description);
1353                 return;
1354         }
1355
1356         if (type == "textlist"){
1357                 parseTextList(data,description);
1358                 return;
1359         }
1360
1361         if (type == "dropdown"){
1362                 parseDropDown(data,description);
1363                 return;
1364         }
1365
1366         if (type == "pwdfield") {
1367                 parsePwdField(data,description);
1368                 return;
1369         }
1370
1371         if ((type == "field") || (type == "textarea")){
1372                 parseField(data,description,type);
1373                 return;
1374         }
1375
1376         if (type == "label") {
1377                 parseLabel(data,description);
1378                 return;
1379         }
1380
1381         if (type == "vertlabel") {
1382                 parseVertLabel(data,description);
1383                 return;
1384         }
1385
1386         if (type == "item_image_button") {
1387                 parseItemImageButton(data,description);
1388                 return;
1389         }
1390
1391         if ((type == "image_button") || (type == "image_button_exit")) {
1392                 parseImageButton(data,description,type);
1393                 return;
1394         }
1395
1396         if (type == "tabheader") {
1397                 parseTabHeader(data,description);
1398                 return;
1399         }
1400
1401         if (type == "box") {
1402                 parseBox(data,description);
1403                 return;
1404         }
1405
1406         // Ignore others
1407         infostream
1408                 << "Unknown DrawSpec: type="<<type<<", data=\""<<description<<"\""
1409                 <<std::endl;
1410 }
1411
1412
1413
1414 void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
1415 {
1416         parserData mydata;
1417
1418         //preserve listboxes
1419         for (unsigned int i = 0; i < m_listboxes.size(); i++) {
1420                 int selection = m_listboxes[i].second->getSelected();
1421                 if (selection != -1) {
1422                         std::wstring listboxname = m_listboxes[i].first.fname;
1423                         mydata.listbox_selections[listboxname] = selection;
1424                 }
1425         }
1426
1427         // Remove children
1428         removeChildren();
1429
1430         mydata.size= v2s32(100,100);
1431         mydata.helptext_h = 15;
1432         mydata.screensize = screensize;
1433
1434         // Base position of contents of form
1435         mydata.basepos = getBasePos();
1436
1437         // State of basepos, 0 = not set, 1= set by formspec, 2 = set by size[] element
1438         // Used to adjust form size automatically if needed
1439         // A proceed button is added if there is no size[] element
1440         mydata.bp_set = 0;
1441
1442         
1443         /* Convert m_init_draw_spec to m_inventorylists */
1444         
1445         m_inventorylists.clear();
1446         m_images.clear();
1447         m_backgrounds.clear();
1448         m_itemimages.clear();
1449         m_listboxes.clear();
1450         m_checkboxes.clear();
1451         m_fields.clear();
1452         m_boxes.clear();
1453
1454
1455         std::vector<std::string> elements = split(m_formspec_string,']');
1456
1457         for (unsigned int i=0;i< elements.size();i++) {
1458                 parseElement(&mydata,elements[i]);
1459         }
1460
1461         // If there's inventory, put the usage string at the bottom
1462         if (m_inventorylists.size())
1463         {
1464                 changeCtype("");
1465                 core::rect<s32> rect(0, 0, mydata.size.X-padding.X*2, mydata.helptext_h);
1466                 rect = rect + v2s32((mydata.size.X/2 - mydata.rect.getWidth()/2) +5,
1467                                 mydata.size.Y-5-mydata.helptext_h);
1468                 const wchar_t *text = wgettext("Left click: Move all items, Right click: Move single item");
1469                 Environment->addStaticText(text, rect, false, true, this, 256);
1470                 delete[] text;
1471                 changeCtype("C");
1472         }
1473         // If there's fields, add a Proceed button
1474         if (m_fields.size() && mydata.bp_set != 2)
1475         {
1476                 // if the size wasn't set by an invsize[] or size[] adjust it now to fit all the fields
1477                 mydata.rect = core::rect<s32>(
1478                                 mydata.screensize.X/2 - 580/2,
1479                                 mydata.screensize.Y/2 - 300/2,
1480                                 mydata.screensize.X/2 + 580/2,
1481                                 mydata.screensize.Y/2 + 240/2+(m_fields.size()*60)
1482                 );
1483                 DesiredRect = mydata.rect;
1484                 recalculateAbsolutePosition(false);
1485                 mydata.basepos = getBasePos();
1486
1487                 changeCtype("");
1488                 {
1489                         v2s32 pos = mydata.basepos;
1490                         pos.Y = ((m_fields.size()+2)*60);
1491
1492                         v2s32 size = DesiredRect.getSize();
1493                         mydata.rect = core::rect<s32>(size.X/2-70, pos.Y, (size.X/2-70)+140, pos.Y+30);
1494                         wchar_t* text = wgettext("Proceed");
1495                         Environment->addButton(mydata.rect, this, 257, text);
1496                         delete[] text;
1497                 }
1498                 changeCtype("C");
1499         }
1500         // Add tooltip
1501         {
1502                 // Note: parent != this so that the tooltip isn't clipped by the menu rectangle
1503                 m_tooltip_element = Environment->addStaticText(L"",core::rect<s32>(0,0,110,18));
1504                 m_tooltip_element->enableOverrideColor(true);
1505                 m_tooltip_element->setBackgroundColor(video::SColor(255,110,130,60));
1506                 m_tooltip_element->setDrawBackground(true);
1507                 m_tooltip_element->setDrawBorder(true);
1508                 m_tooltip_element->setOverrideColor(video::SColor(255,255,255,255));
1509                 m_tooltip_element->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
1510                 m_tooltip_element->setWordWrap(false);
1511         }
1512 }
1513
1514 GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
1515 {
1516         core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
1517         
1518         for(u32 i=0; i<m_inventorylists.size(); i++)
1519         {
1520                 const ListDrawSpec &s = m_inventorylists[i];
1521
1522                 for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
1523                 {
1524                         s32 item_i = i + s.start_item_i;
1525                         s32 x = (i%s.geom.X) * spacing.X;
1526                         s32 y = (i/s.geom.X) * spacing.Y;
1527                         v2s32 p0(x,y);
1528                         core::rect<s32> rect = imgrect + s.pos + p0;
1529                         if(rect.isPointInside(p))
1530                         {
1531                                 return ItemSpec(s.inventoryloc, s.listname, item_i);
1532                         }
1533                 }
1534         }
1535
1536         return ItemSpec(InventoryLocation(), "", -1);
1537 }
1538
1539 void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase)
1540 {
1541         video::IVideoDriver* driver = Environment->getVideoDriver();
1542
1543         // Get font
1544         gui::IGUIFont *font = NULL;
1545         gui::IGUISkin* skin = Environment->getSkin();
1546         if (skin)
1547                 font = skin->getFont();
1548         
1549         Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
1550         if(!inv){
1551                 infostream<<"GUIFormSpecMenu::drawList(): WARNING: "
1552                                 <<"The inventory location "
1553                                 <<"\""<<s.inventoryloc.dump()<<"\" doesn't exist"
1554                                 <<std::endl;
1555                 return;
1556         }
1557         InventoryList *ilist = inv->getList(s.listname);
1558         if(!ilist){
1559                 infostream<<"GUIFormSpecMenu::drawList(): WARNING: "
1560                                 <<"The inventory list \""<<s.listname<<"\" @ \""
1561                                 <<s.inventoryloc.dump()<<"\" doesn't exist"
1562                                 <<std::endl;
1563                 return;
1564         }
1565         
1566         core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
1567         
1568         for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
1569         {
1570                 s32 item_i = i + s.start_item_i;
1571                 if(item_i >= (s32) ilist->getSize())
1572                         break;
1573                 s32 x = (i%s.geom.X) * spacing.X;
1574                 s32 y = (i/s.geom.X) * spacing.Y;
1575                 v2s32 p(x,y);
1576                 core::rect<s32> rect = imgrect + s.pos + p;
1577                 ItemStack item;
1578                 if(ilist)
1579                         item = ilist->getItem(item_i);
1580
1581                 bool selected = m_selected_item
1582                         && m_invmgr->getInventory(m_selected_item->inventoryloc) == inv
1583                         && m_selected_item->listname == s.listname
1584                         && m_selected_item->i == item_i;
1585                 bool hovering = rect.isPointInside(m_pointer);
1586
1587                 if(phase == 0)
1588                 {
1589                         if(hovering && m_selected_item)
1590                         {
1591                                 video::SColor bgcolor(255,192,192,192);
1592                                 driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect);
1593                         }
1594                         else
1595                         {
1596                                 video::SColor bgcolor(255,128,128,128);
1597                                 driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect);
1598                         }
1599                 }
1600
1601                 if(phase == 1)
1602                 {
1603                         // Draw item stack
1604                         if(selected)
1605                         {
1606                                 item.takeItem(m_selected_amount);
1607                         }
1608                         if(!item.empty())
1609                         {
1610                                 drawItemStack(driver, font, item,
1611                                                 rect, &AbsoluteClippingRect, m_gamedef);
1612                         }
1613
1614                         // Draw tooltip
1615                         std::string tooltip_text = "";
1616                         if(hovering && !m_selected_item)
1617                                 tooltip_text = item.getDefinition(m_gamedef->idef()).description;
1618                         if(tooltip_text != "")
1619                         {
1620                                 m_tooltip_element->setVisible(true);
1621                                 this->bringToFront(m_tooltip_element);
1622                                 m_tooltip_element->setText(narrow_to_wide(tooltip_text).c_str());
1623                                 s32 tooltip_x = m_pointer.X + 15;
1624                                 s32 tooltip_y = m_pointer.Y + 15;
1625                                 s32 tooltip_width = m_tooltip_element->getTextWidth() + 15;
1626                                 s32 tooltip_height = m_tooltip_element->getTextHeight() + 5;
1627                                 m_tooltip_element->setRelativePosition(core::rect<s32>(
1628                                                 core::position2d<s32>(tooltip_x, tooltip_y),
1629                                                 core::dimension2d<s32>(tooltip_width, tooltip_height)));
1630                         }
1631                 }
1632         }
1633 }
1634
1635 void GUIFormSpecMenu::drawSelectedItem()
1636 {
1637         if(!m_selected_item)
1638                 return;
1639
1640         video::IVideoDriver* driver = Environment->getVideoDriver();
1641
1642         // Get font
1643         gui::IGUIFont *font = NULL;
1644         gui::IGUISkin* skin = Environment->getSkin();
1645         if (skin)
1646                 font = skin->getFont();
1647         
1648         Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
1649         assert(inv);
1650         InventoryList *list = inv->getList(m_selected_item->listname);
1651         assert(list);
1652         ItemStack stack = list->getItem(m_selected_item->i);
1653         stack.count = m_selected_amount;
1654
1655         core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
1656         core::rect<s32> rect = imgrect + (m_pointer - imgrect.getCenter());
1657         drawItemStack(driver, font, stack, rect, NULL, m_gamedef);
1658 }
1659
1660 void GUIFormSpecMenu::drawMenu()
1661 {
1662         if(m_form_src){
1663                 std::string newform = m_form_src->getForm();
1664                 if(newform != m_formspec_string){
1665                         m_formspec_string = newform;
1666                         regenerateGui(m_screensize_old);
1667                 }
1668         }
1669
1670         m_pointer = m_device->getCursorControl()->getPosition();
1671
1672         updateSelectedItem();
1673
1674         gui::IGUISkin* skin = Environment->getSkin();
1675         if (!skin)
1676                 return;
1677         video::IVideoDriver* driver = Environment->getVideoDriver();
1678         
1679         video::SColor bgcolor(140,0,0,0);
1680         driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
1681
1682         m_tooltip_element->setVisible(false);
1683
1684         /*
1685                 Draw backgrounds
1686         */
1687         for(u32 i=0; i<m_backgrounds.size(); i++)
1688         {
1689                 const ImageDrawSpec &spec = m_backgrounds[i];
1690                 video::ITexture *texture = 0;
1691
1692                 if (m_gamedef != 0)
1693                         texture = m_gamedef->tsrc()->getTexture(spec.name);
1694                 else
1695                 {
1696                         texture = driver->getTexture(spec.name.c_str());
1697                         m_Textures.push_back(texture);
1698                 }
1699
1700                 if (texture != 0) {
1701                         // Image size on screen
1702                         core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
1703                         // Image rectangle on screen
1704                         core::rect<s32> rect = imgrect + spec.pos;
1705                         const video::SColor color(255,255,255,255);
1706                         const video::SColor colors[] = {color,color,color,color};
1707                         driver->draw2DImage(texture, rect,
1708                                 core::rect<s32>(core::position2d<s32>(0,0),
1709                                                 core::dimension2di(texture->getOriginalSize())),
1710                                 NULL/*&AbsoluteClippingRect*/, colors, true);
1711                 }
1712                 else {
1713                         errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl;
1714                         errorstream << "\t" << spec.name << std::endl;
1715                 }
1716         }
1717         
1718         /*
1719                 Draw Boxes
1720         */
1721         for(u32 i=0; i<m_boxes.size(); i++)
1722         {
1723                 const BoxDrawSpec &spec = m_boxes[i];
1724
1725                 irr::video::SColor todraw = spec.color;
1726
1727                 todraw.setAlpha(140);
1728
1729                 core::rect<s32> rect(spec.pos.X,spec.pos.Y,
1730                                                         spec.pos.X + spec.geom.X,spec.pos.Y + spec.geom.Y);
1731
1732                 driver->draw2DRectangle(todraw, rect, 0);
1733         }
1734         /*
1735                 Draw images
1736         */
1737         for(u32 i=0; i<m_images.size(); i++)
1738         {
1739                 const ImageDrawSpec &spec = m_images[i];
1740                 video::ITexture *texture = 0;
1741
1742                 if (m_gamedef != 0)
1743                         texture = m_gamedef->tsrc()->getTexture(spec.name);
1744                 else
1745                 {
1746                         texture = driver->getTexture(spec.name.c_str());
1747                         m_Textures.push_back(texture);
1748                 }
1749                 if (texture != 0) {
1750                         const core::dimension2d<u32>& img_origsize = texture->getOriginalSize();
1751                         // Image size on screen
1752                         core::rect<s32> imgrect;
1753
1754                         if (spec.scale)
1755                                 imgrect = core::rect<s32>(0,0,spec.geom.X, spec.geom.Y);
1756                         else {
1757
1758                                 imgrect = core::rect<s32>(0,0,img_origsize.Width,img_origsize.Height);
1759                         }
1760                         // Image rectangle on screen
1761                         core::rect<s32> rect = imgrect + spec.pos;
1762                         const video::SColor color(255,255,255,255);
1763                         const video::SColor colors[] = {color,color,color,color};
1764                         driver->draw2DImage(texture, rect,
1765                                 core::rect<s32>(core::position2d<s32>(0,0),img_origsize),
1766                                 NULL/*&AbsoluteClippingRect*/, colors, true);
1767                 }
1768                 else {
1769                         errorstream << "GUIFormSpecMenu::drawMenu() Draw images unable to load texture:" << std::endl;
1770                         errorstream << "\t" << spec.name << std::endl;
1771                 }
1772         }
1773         
1774         /*
1775                 Draw item images
1776         */
1777         for(u32 i=0; i<m_itemimages.size(); i++)
1778         {
1779                 if (m_gamedef == 0)
1780                         break;
1781
1782                 const ImageDrawSpec &spec = m_itemimages[i];
1783                 IItemDefManager *idef = m_gamedef->idef();
1784                 ItemStack item;
1785                 item.deSerialize(spec.name, idef);
1786                 video::ITexture *texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef);         
1787                 // Image size on screen
1788                 core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
1789                 // Image rectangle on screen
1790                 core::rect<s32> rect = imgrect + spec.pos;
1791                 const video::SColor color(255,255,255,255);
1792                 const video::SColor colors[] = {color,color,color,color};
1793                 driver->draw2DImage(texture, rect,
1794                         core::rect<s32>(core::position2d<s32>(0,0),
1795                                         core::dimension2di(texture->getOriginalSize())),
1796                         NULL/*&AbsoluteClippingRect*/, colors, true);
1797         }
1798         
1799         /*
1800                 Draw items
1801                 Phase 0: Item slot rectangles
1802                 Phase 1: Item images; prepare tooltip
1803                 If backgrounds used, do not draw Item slot rectangles
1804         */
1805         int start_phase=0;
1806         if (m_backgrounds.size() > 0) start_phase=1;
1807         for(int phase=start_phase; phase<=1; phase++)
1808         for(u32 i=0; i<m_inventorylists.size(); i++)
1809         {
1810                 drawList(m_inventorylists[i], phase);
1811         }
1812
1813         /*
1814                 Call base class
1815         */
1816         gui::IGUIElement::draw();
1817         
1818         /*
1819                 Draw fields/buttons tooltips
1820         */
1821         for(u32 i=0; i<m_fields.size(); i++)
1822         {
1823                 const FieldSpec &spec = m_fields[i];
1824                 if (spec.tooltip != "")
1825                 {
1826                         core::rect<s32> rect = spec.rect;
1827                         if (rect.isPointInside(m_pointer)) 
1828                         {
1829                                 m_tooltip_element->setVisible(true);
1830                                 this->bringToFront(m_tooltip_element);
1831                                 m_tooltip_element->setText(narrow_to_wide(spec.tooltip).c_str());
1832                                 s32 tooltip_x = m_pointer.X + 15;
1833                                 s32 tooltip_y = m_pointer.Y + 15;
1834                                 s32 tooltip_width = m_tooltip_element->getTextWidth() + 15;
1835                                 s32 tooltip_height = m_tooltip_element->getTextHeight() + 5;
1836                                 m_tooltip_element->setRelativePosition(core::rect<s32>(
1837                                 core::position2d<s32>(tooltip_x, tooltip_y),
1838                                 core::dimension2d<s32>(tooltip_width, tooltip_height)));
1839                         }
1840                 }
1841         }
1842         
1843         /*
1844                 Draw dragged item stack
1845         */
1846         drawSelectedItem();
1847 }
1848
1849 void GUIFormSpecMenu::updateSelectedItem()
1850 {
1851         // If the selected stack has become empty for some reason, deselect it.
1852         // If the selected stack has become inaccessible, deselect it.
1853         // If the selected stack has become smaller, adjust m_selected_amount.
1854         ItemStack selected = verifySelectedItem();
1855
1856         // WARNING: BLACK MAGIC
1857         // See if there is a stack suited for our current guess.
1858         // If such stack does not exist, clear the guess.
1859         if(m_selected_content_guess.name != "" &&
1860                         selected.name == m_selected_content_guess.name &&
1861                         selected.count == m_selected_content_guess.count){
1862                 // Selected item fits the guess. Skip the black magic.
1863         }
1864         else if(m_selected_content_guess.name != ""){
1865                 bool found = false;
1866                 for(u32 i=0; i<m_inventorylists.size() && !found; i++){
1867                         const ListDrawSpec &s = m_inventorylists[i];
1868                         Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
1869                         if(!inv)
1870                                 continue;
1871                         InventoryList *list = inv->getList(s.listname);
1872                         if(!list)
1873                                 continue;
1874                         for(s32 i=0; i<s.geom.X*s.geom.Y && !found; i++){
1875                                 u32 item_i = i + s.start_item_i;
1876                                 if(item_i >= list->getSize())
1877                                         continue;
1878                                 ItemStack stack = list->getItem(item_i);
1879                                 if(stack.name == m_selected_content_guess.name &&
1880                                                 stack.count == m_selected_content_guess.count){
1881                                         found = true;
1882                                         infostream<<"Client: Changing selected content guess to "
1883                                                         <<s.inventoryloc.dump()<<" "<<s.listname
1884                                                         <<" "<<item_i<<std::endl;
1885                                         delete m_selected_item;
1886                                         m_selected_item = new ItemSpec(s.inventoryloc, s.listname, item_i);
1887                                         m_selected_amount = stack.count;
1888                                 }
1889                         }
1890                 }
1891                 if(!found){
1892                         infostream<<"Client: Discarding selected content guess: "
1893                                         <<m_selected_content_guess.getItemString()<<std::endl;
1894                         m_selected_content_guess.name = "";
1895                 }
1896         }
1897
1898         // If craftresult is nonempty and nothing else is selected, select it now.
1899         if(!m_selected_item)
1900         {
1901                 for(u32 i=0; i<m_inventorylists.size(); i++)
1902                 {
1903                         const ListDrawSpec &s = m_inventorylists[i];
1904                         if(s.listname == "craftpreview")
1905                         {
1906                                 Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
1907                                 InventoryList *list = inv->getList("craftresult");
1908                                 if(list && list->getSize() >= 1 && !list->getItem(0).empty())
1909                                 {
1910                                         m_selected_item = new ItemSpec;
1911                                         m_selected_item->inventoryloc = s.inventoryloc;
1912                                         m_selected_item->listname = "craftresult";
1913                                         m_selected_item->i = 0;
1914                                         m_selected_amount = 0;
1915                                         m_selected_dragging = false;
1916                                         break;
1917                                 }
1918                         }
1919                 }
1920         }
1921
1922         // If craftresult is selected, keep the whole stack selected
1923         if(m_selected_item && m_selected_item->listname == "craftresult")
1924         {
1925                 m_selected_amount = verifySelectedItem().count;
1926         }
1927 }
1928
1929 ItemStack GUIFormSpecMenu::verifySelectedItem()
1930 {
1931         // If the selected stack has become empty for some reason, deselect it.
1932         // If the selected stack has become inaccessible, deselect it.
1933         // If the selected stack has become smaller, adjust m_selected_amount.
1934         // Return the selected stack.
1935
1936         if(m_selected_item)
1937         {
1938                 if(m_selected_item->isValid())
1939                 {
1940                         Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
1941                         if(inv)
1942                         {
1943                                 InventoryList *list = inv->getList(m_selected_item->listname);
1944                                 if(list && (u32) m_selected_item->i < list->getSize())
1945                                 {
1946                                         ItemStack stack = list->getItem(m_selected_item->i);
1947                                         if(m_selected_amount > stack.count)
1948                                                 m_selected_amount = stack.count;
1949                                         if(!stack.empty())
1950                                                 return stack;
1951                                 }
1952                         }
1953                 }
1954
1955                 // selection was not valid
1956                 delete m_selected_item;
1957                 m_selected_item = NULL;
1958                 m_selected_amount = 0;
1959                 m_selected_dragging = false;
1960         }
1961         return ItemStack();
1962 }
1963
1964 void GUIFormSpecMenu::acceptInput()
1965 {
1966         if(m_text_dst)
1967         {
1968                 std::map<std::string, std::string> fields;
1969
1970                 if (current_keys_pending.key_down) {
1971                         fields["key_down"] = "true";
1972                         current_keys_pending.key_down = false;
1973                 }
1974
1975                 if (current_keys_pending.key_up) {
1976                         fields["key_up"] = "true";
1977                         current_keys_pending.key_up = false;
1978                 }
1979
1980                 if (current_keys_pending.key_enter) {
1981                         fields["key_enter"] = "true";
1982                         current_keys_pending.key_enter = false;
1983                 }
1984
1985                 if (current_keys_pending.key_escape) {
1986                         fields["key_escape"] = "true";
1987                         current_keys_pending.key_escape = false;
1988                 }
1989
1990                 for(u32 i=0; i<m_fields.size(); i++)
1991                 {
1992                         const FieldSpec &s = m_fields[i];
1993                         if(s.send) 
1994                         {
1995                                 if(s.ftype == f_Button)
1996                                 {
1997                                         fields[wide_to_narrow(s.fname.c_str())] = wide_to_narrow(s.flabel.c_str());
1998                                 }
1999                                 else if(s.ftype == f_ListBox) {
2000                                         std::stringstream ss;
2001
2002                                         if (m_listbox_doubleclick) {
2003                                                 ss << "DCL:";
2004                                         }
2005                                         else {
2006                                                 ss << "CHG:";
2007                                         }
2008                                         ss << (getListboxIndex(wide_to_narrow(s.fname.c_str()))+1);
2009                                         fields[wide_to_narrow(s.fname.c_str())] = ss.str();
2010                                 }
2011                                 else if(s.ftype == f_DropDown) {
2012                                         // no dynamic cast possible due to some distributions shipped
2013                                         // without rtti support in irrlicht
2014                                         IGUIElement * element = getElementFromId(s.fid);
2015                                         gui::IGUIComboBox *e = NULL;
2016                                         if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) {
2017                                                 e = static_cast<gui::IGUIComboBox*>(element);
2018                                         }
2019                                         fields[wide_to_narrow(s.fname.c_str())] =
2020                                                         wide_to_narrow(e->getItem(e->getSelected()));
2021                                 }
2022                                 else if (s.ftype == f_TabHeader) {
2023                                         // no dynamic cast possible due to some distributions shipped
2024                                         // without rtti support in irrlicht
2025                                         IGUIElement * element = getElementFromId(s.fid);
2026                                         gui::IGUITabControl *e = NULL;
2027                                         if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) {
2028                                                 e = static_cast<gui::IGUITabControl*>(element);
2029                                         }
2030
2031                                         if (e != 0) {
2032                                                 std::stringstream ss;
2033                                                 ss << (e->getActiveTab() +1);
2034                                                 fields[wide_to_narrow(s.fname.c_str())] = ss.str();
2035                                         }
2036                                 }
2037                                 else if (s.ftype == f_CheckBox) {
2038                                         // no dynamic cast possible due to some distributions shipped
2039                                         // without rtti support in irrlicht
2040                                         IGUIElement * element = getElementFromId(s.fid);
2041                                         gui::IGUICheckBox *e = NULL;
2042                                         if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) {
2043                                                 e = static_cast<gui::IGUICheckBox*>(element);
2044                                         }
2045
2046                                         if (e != 0) {
2047                                                 if (e->isChecked())
2048                                                         fields[wide_to_narrow(s.fname.c_str())] = "true";
2049                                                 else
2050                                                         fields[wide_to_narrow(s.fname.c_str())] = "false";
2051                                         }
2052                                 }
2053                                 else
2054                                 {
2055                                         IGUIElement* e = getElementFromId(s.fid);
2056                                         if(e != NULL)
2057                                         {
2058                                                 fields[wide_to_narrow(s.fname.c_str())] = wide_to_narrow(e->getText());
2059                                         }
2060                                 }
2061                         }
2062                 }
2063
2064                 m_text_dst->gotText(fields);
2065         }
2066 }
2067
2068 bool GUIFormSpecMenu::OnEvent(const SEvent& event)
2069 {
2070         if(event.EventType==EET_KEY_INPUT_EVENT)
2071         {
2072                 KeyPress kp(event.KeyInput);
2073                 if (event.KeyInput.PressedDown && (kp == EscapeKey ||
2074                         kp == getKeySetting("keymap_inventory")))
2075                 {
2076                         if (m_allowclose)
2077                                 quitMenu();
2078                         else
2079                                 m_text_dst->gotText(narrow_to_wide("MenuQuit"));
2080                         return true;
2081                 }
2082                 if (event.KeyInput.PressedDown &&
2083                         (event.KeyInput.Key==KEY_RETURN ||
2084                          event.KeyInput.Key==KEY_UP ||
2085                          event.KeyInput.Key==KEY_DOWN)
2086                         ) {
2087
2088
2089                         switch (event.KeyInput.Key) {
2090                                 case KEY_RETURN:
2091                                         if (m_allowclose) {
2092                                                 acceptInput();
2093                                                 quitMenu();
2094                                         }
2095                                         else
2096                                                 current_keys_pending.key_enter = true;
2097                                         break;
2098                                 case KEY_UP:
2099                                         current_keys_pending.key_up = true;
2100                                         break;
2101                                 case KEY_DOWN:
2102                                         current_keys_pending.key_down = true;
2103                                         break;
2104                                 break;
2105                                 default:
2106                                         //can't happen at all!
2107                                         assert("reached a source line that can't ever been reached" == 0);
2108                                         break;
2109                         }
2110                         acceptInput();
2111                         return true;
2112                 }
2113
2114         }
2115         if(event.EventType==EET_MOUSE_INPUT_EVENT
2116                         && event.MouseInput.Event != EMIE_MOUSE_MOVED)
2117         {
2118                 // Mouse event other than movement
2119
2120                 // Get selected item and hovered/clicked item (s)
2121
2122                 updateSelectedItem();
2123                 ItemSpec s = getItemAtPos(m_pointer);
2124
2125                 Inventory *inv_selected = NULL;
2126                 Inventory *inv_s = NULL;
2127
2128                 if(m_selected_item)
2129                 {
2130                         inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc);
2131                         assert(inv_selected);
2132                         assert(inv_selected->getList(m_selected_item->listname) != NULL);
2133                 }
2134
2135                 u32 s_count = 0;
2136
2137                 if(s.isValid())
2138                 do{ // breakable
2139                         inv_s = m_invmgr->getInventory(s.inventoryloc);
2140
2141                         if(!inv_s){
2142                                 errorstream<<"InventoryMenu: The selected inventory location "
2143                                                 <<"\""<<s.inventoryloc.dump()<<"\" doesn't exist"
2144                                                 <<std::endl;
2145                                 s.i = -1;  // make it invalid again
2146                                 break;
2147                         }
2148
2149                         InventoryList *list = inv_s->getList(s.listname);
2150                         if(list == NULL){
2151                                 verbosestream<<"InventoryMenu: The selected inventory list \""
2152                                                 <<s.listname<<"\" does not exist"<<std::endl;
2153                                 s.i = -1;  // make it invalid again
2154                                 break;
2155                         }
2156
2157                         if((u32)s.i >= list->getSize()){
2158                                 infostream<<"InventoryMenu: The selected inventory list \""
2159                                                 <<s.listname<<"\" is too small (i="<<s.i<<", size="
2160                                                 <<list->getSize()<<")"<<std::endl;
2161                                 s.i = -1;  // make it invalid again
2162                                 break;
2163                         }
2164
2165                         s_count = list->getItem(s.i).count;
2166                 }while(0);
2167
2168                 bool identical = (m_selected_item != NULL) && s.isValid() &&
2169                         (inv_selected == inv_s) &&
2170                         (m_selected_item->listname == s.listname) &&
2171                         (m_selected_item->i == s.i);
2172
2173                 // buttons: 0 = left, 1 = right, 2 = middle
2174                 // up/down: 0 = down (press), 1 = up (release), 2 = unknown event
2175                 int button = 0;
2176                 int updown = 2;
2177                 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
2178                         { button = 0; updown = 0; }
2179                 else if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
2180                         { button = 1; updown = 0; }
2181                 else if(event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN)
2182                         { button = 2; updown = 0; }
2183                 else if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
2184                         { button = 0; updown = 1; }
2185                 else if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
2186                         { button = 1; updown = 1; }
2187                 else if(event.MouseInput.Event == EMIE_MMOUSE_LEFT_UP)
2188                         { button = 2; updown = 1; }
2189
2190                 // Set this number to a positive value to generate a move action
2191                 // from m_selected_item to s.
2192                 u32 move_amount = 0;
2193
2194                 // Set this number to a positive value to generate a drop action
2195                 // from m_selected_item.
2196                 u32 drop_amount = 0;
2197
2198                 // Set this number to a positive value to generate a craft action at s.
2199                 u32 craft_amount = 0;
2200
2201                 if(updown == 0)
2202                 {
2203                         // Some mouse button has been pressed
2204
2205                         //infostream<<"Mouse button "<<button<<" pressed at p=("
2206                         //      <<p.X<<","<<p.Y<<")"<<std::endl;
2207
2208                         m_selected_dragging = false;
2209
2210                         if(s.isValid() && s.listname == "craftpreview")
2211                         {
2212                                 // Craft preview has been clicked: craft
2213                                 craft_amount = (button == 2 ? 10 : 1);
2214                         }
2215                         else if(m_selected_item == NULL)
2216                         {
2217                                 if(s_count != 0)
2218                                 {
2219                                         // Non-empty stack has been clicked: select it
2220                                         m_selected_item = new ItemSpec(s);
2221
2222                                         if(button == 1)  // right
2223                                                 m_selected_amount = (s_count + 1) / 2;
2224                                         else if(button == 2)  // middle
2225                                                 m_selected_amount = MYMIN(s_count, 10);
2226                                         else  // left
2227                                                 m_selected_amount = s_count;
2228
2229                                         m_selected_dragging = true;
2230                                 }
2231                         }
2232                         else  // m_selected_item != NULL
2233                         {
2234                                 assert(m_selected_amount >= 1);
2235
2236                                 if(s.isValid())
2237                                 {
2238                                         // Clicked a slot: move
2239                                         if(button == 1)  // right
2240                                                 move_amount = 1;
2241                                         else if(button == 2)  // middle
2242                                                 move_amount = MYMIN(m_selected_amount, 10);
2243                                         else  // left
2244                                                 move_amount = m_selected_amount;
2245
2246                                         if(identical)
2247                                         {
2248                                                 if(move_amount >= m_selected_amount)
2249                                                         m_selected_amount = 0;
2250                                                 else
2251                                                         m_selected_amount -= move_amount;
2252                                                 move_amount = 0;
2253                                         }
2254                                 }
2255                                 else if(getAbsoluteClippingRect().isPointInside(m_pointer))
2256                                 {
2257                                         // Clicked somewhere else: deselect
2258                                         m_selected_amount = 0;
2259                                 }
2260                                 else
2261                                 {
2262                                         // Clicked outside of the window: drop
2263                                         if(button == 1)  // right
2264                                                 drop_amount = 1;
2265                                         else if(button == 2)  // middle
2266                                                 drop_amount = MYMIN(m_selected_amount, 10);
2267                                         else  // left
2268                                                 drop_amount = m_selected_amount;
2269                                 }
2270                         }
2271                 }
2272                 else if(updown == 1)
2273                 {
2274                         // Some mouse button has been released
2275
2276                         //infostream<<"Mouse button "<<button<<" released at p=("
2277                         //      <<p.X<<","<<p.Y<<")"<<std::endl;
2278
2279                         if(m_selected_item != NULL && m_selected_dragging && s.isValid())
2280                         {
2281                                 if(!identical)
2282                                 {
2283                                         // Dragged to different slot: move all selected
2284                                         move_amount = m_selected_amount;
2285                                 }
2286                         }
2287                         else if(m_selected_item != NULL && m_selected_dragging &&
2288                                 !(getAbsoluteClippingRect().isPointInside(m_pointer)))
2289                         {
2290                                 // Dragged outside of window: drop all selected
2291                                 drop_amount = m_selected_amount;
2292                         }
2293
2294                         m_selected_dragging = false;
2295                 }
2296
2297                 // Possibly send inventory action to server
2298                 if(move_amount > 0)
2299                 {
2300                         // Send IACTION_MOVE
2301
2302                         assert(m_selected_item && m_selected_item->isValid());
2303                         assert(s.isValid());
2304
2305                         assert(inv_selected && inv_s);
2306                         InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
2307                         InventoryList *list_to = inv_s->getList(s.listname);
2308                         assert(list_from && list_to);
2309                         ItemStack stack_from = list_from->getItem(m_selected_item->i);
2310                         ItemStack stack_to = list_to->getItem(s.i);
2311
2312                         // Check how many items can be moved
2313                         move_amount = stack_from.count = MYMIN(move_amount, stack_from.count);
2314                         ItemStack leftover = stack_to.addItem(stack_from, m_gamedef->idef());
2315                         // If source stack cannot be added to destination stack at all,
2316                         // they are swapped
2317                         if(leftover.count == stack_from.count && leftover.name == stack_from.name)
2318                         {
2319                                 m_selected_amount = stack_to.count;
2320                                 // In case the server doesn't directly swap them but instead
2321                                 // moves stack_to somewhere else, set this
2322                                 m_selected_content_guess = stack_to;
2323                                 m_selected_content_guess_inventory = s.inventoryloc;
2324                         }
2325                         // Source stack goes fully into destination stack
2326                         else if(leftover.empty())
2327                         {
2328                                 m_selected_amount -= move_amount;
2329                                 m_selected_content_guess = ItemStack(); // Clear
2330                         }
2331                         // Source stack goes partly into destination stack
2332                         else
2333                         {
2334                                 move_amount -= leftover.count;
2335                                 m_selected_amount -= move_amount;
2336                                 m_selected_content_guess = ItemStack(); // Clear
2337                         }
2338
2339                         infostream<<"Handing IACTION_MOVE to manager"<<std::endl;
2340                         IMoveAction *a = new IMoveAction();
2341                         a->count = move_amount;
2342                         a->from_inv = m_selected_item->inventoryloc;
2343                         a->from_list = m_selected_item->listname;
2344                         a->from_i = m_selected_item->i;
2345                         a->to_inv = s.inventoryloc;
2346                         a->to_list = s.listname;
2347                         a->to_i = s.i;
2348                         m_invmgr->inventoryAction(a);
2349                 }
2350                 else if(drop_amount > 0)
2351                 {
2352                         m_selected_content_guess = ItemStack(); // Clear
2353
2354                         // Send IACTION_DROP
2355
2356                         assert(m_selected_item && m_selected_item->isValid());
2357                         assert(inv_selected);
2358                         InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
2359                         assert(list_from);
2360                         ItemStack stack_from = list_from->getItem(m_selected_item->i);
2361
2362                         // Check how many items can be dropped
2363                         drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count);
2364                         assert(drop_amount > 0 && drop_amount <= m_selected_amount);
2365                         m_selected_amount -= drop_amount;
2366
2367                         infostream<<"Handing IACTION_DROP to manager"<<std::endl;
2368                         IDropAction *a = new IDropAction();
2369                         a->count = drop_amount;
2370                         a->from_inv = m_selected_item->inventoryloc;
2371                         a->from_list = m_selected_item->listname;
2372                         a->from_i = m_selected_item->i;
2373                         m_invmgr->inventoryAction(a);
2374                 }
2375                 else if(craft_amount > 0)
2376                 {
2377                         m_selected_content_guess = ItemStack(); // Clear
2378
2379                         // Send IACTION_CRAFT
2380
2381                         assert(s.isValid());
2382                         assert(inv_s);
2383
2384                         infostream<<"Handing IACTION_CRAFT to manager"<<std::endl;
2385                         ICraftAction *a = new ICraftAction();
2386                         a->count = craft_amount;
2387                         a->craft_inv = s.inventoryloc;
2388                         m_invmgr->inventoryAction(a);
2389                 }
2390
2391                 // If m_selected_amount has been decreased to zero, deselect
2392                 if(m_selected_amount == 0)
2393                 {
2394                         delete m_selected_item;
2395                         m_selected_item = NULL;
2396                         m_selected_amount = 0;
2397                         m_selected_dragging = false;
2398                         m_selected_content_guess = ItemStack();
2399                 }
2400         }
2401         if(event.EventType==EET_GUI_EVENT)
2402         {
2403
2404                 if(event.GUIEvent.EventType==gui::EGET_TAB_CHANGED
2405                                                 && isVisible())
2406                 {
2407                         // find the element that was clicked
2408                         for(u32 i=0; i<m_fields.size(); i++)
2409                         {
2410                                 FieldSpec &s = m_fields[i];
2411                                 // if its a button, set the send field so
2412                                 // lua knows which button was pressed
2413                                 if ((s.ftype == f_TabHeader) && (s.fid == event.GUIEvent.Caller->getID()))
2414                                 {
2415                                         s.send = true;
2416                                         acceptInput();
2417                                         s.send = false;
2418                                         // Restore focus to the full form
2419                                         Environment->setFocus(this);
2420                                         return true;
2421                                 }
2422                         }
2423                 }
2424                 if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST
2425                                 && isVisible())
2426                 {
2427                         if(!canTakeFocus(event.GUIEvent.Element))
2428                         {
2429                                 infostream<<"GUIFormSpecMenu: Not allowing focus change."
2430                                                 <<std::endl;
2431                                 // Returning true disables focus change
2432                                 return true;
2433                         }
2434                 }
2435                 if((event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED) ||
2436                                 (event.GUIEvent.EventType==gui::EGET_CHECKBOX_CHANGED))
2437                 {
2438                         unsigned int btn_id = event.GUIEvent.Caller->getID();
2439
2440                         if (btn_id == 257) {
2441                                 acceptInput();
2442                                 if (m_allowclose)
2443                                         quitMenu();
2444                                 else
2445                                         m_text_dst->gotText(narrow_to_wide("ExitButton"));
2446                                 // quitMenu deallocates menu
2447                                 return true;
2448                         }
2449
2450                         // find the element that was clicked
2451                         for(u32 i=0; i<m_fields.size(); i++)
2452                         {
2453                                 FieldSpec &s = m_fields[i];
2454                                 // if its a button, set the send field so 
2455                                 // lua knows which button was pressed
2456                                 if (((s.ftype == f_Button) || (s.ftype == f_CheckBox)) &&
2457                                                 (s.fid == event.GUIEvent.Caller->getID()))
2458                                 {
2459                                         s.send = true;
2460                                         acceptInput();
2461                                         if(s.is_exit){
2462                                                 if (m_allowclose)
2463                                                         quitMenu();
2464                                                 else
2465                                                         m_text_dst->gotText(narrow_to_wide("ExitButton"));
2466                                                 return true;
2467                                         }else{
2468                                                 s.send = false;
2469                                                 // Restore focus to the full form
2470                                                 Environment->setFocus(this);
2471                                                 return true;
2472                                         }
2473                                 }
2474                         }
2475                 }
2476                 if(event.GUIEvent.EventType==gui::EGET_EDITBOX_ENTER)
2477                 {
2478                         if(event.GUIEvent.Caller->getID() > 257)
2479                         {
2480
2481                                 if (m_allowclose) {
2482                                         acceptInput();
2483                                         quitMenu();
2484                                 }
2485                                 else {
2486                                         current_keys_pending.key_enter = true;
2487                                         acceptInput();
2488                                 }
2489                                 // quitMenu deallocates menu
2490                                 return true;
2491                         }
2492                 }
2493
2494                 if((event.GUIEvent.EventType==gui::EGET_LISTBOX_SELECTED_AGAIN) ||
2495                         (event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED))
2496                 {
2497                         int current_id = event.GUIEvent.Caller->getID();
2498                         if(current_id > 257)
2499                         {
2500                                 // find the element that was clicked
2501                                 for(u32 i=0; i<m_fields.size(); i++)
2502                                 {
2503                                         FieldSpec &s = m_fields[i];
2504                                         // if its a listbox, set the send field so
2505                                         // lua knows which listbox was changed
2506                                         // checkListboxClick() is black magic
2507                                         // for properly handling double clicks
2508                                         if ((s.ftype == f_ListBox) && (s.fid == current_id)
2509                                                         && checkListboxClick(s.fname,
2510                                                                 event.GUIEvent.EventType))
2511                                         {
2512                                                 s.send = true;
2513                                                 acceptInput();
2514                                                 s.send=false;
2515                                                 // Restore focus to the full form
2516                                                 Environment->setFocus(this);
2517                                         }
2518                                 }
2519                                 return true;
2520                         }
2521                 }
2522         }
2523
2524         return Parent ? Parent->OnEvent(event) : false;
2525 }
2526
2527 bool GUIFormSpecMenu::parseColor(std::string color, irr::video::SColor& outcolor) {
2528         outcolor = irr::video::SColor(0,0,0,0);
2529
2530         if (!string_allowed(color, "0123456789abcdefABCDEF"))
2531                 return false;
2532
2533         u32 color_value;
2534         std::istringstream iss(color);
2535         iss >> std::hex >> color_value;
2536         
2537         outcolor = irr::video::SColor(color_value);
2538
2539         outcolor.setAlpha(255);
2540         return true;
2541 }