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