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