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