]> git.lizzy.rs Git - dragonfireclient.git/blob - src/guiFormSpecMenu.cpp
Formspec button_exit[] and image_button_exit[]
[dragonfireclient.git] / src / guiFormSpecMenu.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2010 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 "guiFormSpecMenu.h"
22 #include "constants.h"
23 #include "gamedef.h"
24 #include "keycode.h"
25 #include "strfnd.h"
26 #include <IGUICheckBox.h>
27 #include <IGUIEditBox.h>
28 #include <IGUIButton.h>
29 #include <IGUIStaticText.h>
30 #include <IGUIFont.h>
31 #include "log.h"
32 #include "tile.h" // ITextureSource
33 #include "util/string.h"
34 #include "util/numeric.h"
35
36 #include "gettext.h"
37
38 void drawItemStack(video::IVideoDriver *driver,
39                 gui::IGUIFont *font,
40                 const ItemStack &item,
41                 const core::rect<s32> &rect,
42                 const core::rect<s32> *clip,
43                 IGameDef *gamedef)
44 {
45         if(item.empty())
46                 return;
47         
48         const ItemDefinition &def = item.getDefinition(gamedef->idef());
49         video::ITexture *texture = def.inventory_texture;
50
51         // Draw the inventory texture
52         if(texture != NULL)
53         {
54                 const video::SColor color(255,255,255,255);
55                 const video::SColor colors[] = {color,color,color,color};
56                 driver->draw2DImage(texture, rect,
57                         core::rect<s32>(core::position2d<s32>(0,0),
58                         core::dimension2di(texture->getOriginalSize())),
59                         clip, colors, true);
60         }
61
62         if(def.type == ITEM_TOOL && item.wear != 0)
63         {
64                 // Draw a progressbar
65                 float barheight = rect.getHeight()/16;
66                 float barpad_x = rect.getWidth()/16;
67                 float barpad_y = rect.getHeight()/16;
68                 core::rect<s32> progressrect(
69                         rect.UpperLeftCorner.X + barpad_x,
70                         rect.LowerRightCorner.Y - barpad_y - barheight,
71                         rect.LowerRightCorner.X - barpad_x,
72                         rect.LowerRightCorner.Y - barpad_y);
73
74                 // Shrink progressrect by amount of tool damage
75                 float wear = item.wear / 65535.0;
76                 int progressmid =
77                         wear * progressrect.UpperLeftCorner.X +
78                         (1-wear) * progressrect.LowerRightCorner.X;
79
80                 // Compute progressbar color
81                 //   wear = 0.0: green
82                 //   wear = 0.5: yellow
83                 //   wear = 1.0: red
84                 video::SColor color(255,255,255,255);
85                 int wear_i = MYMIN(floor(wear * 600), 511);
86                 wear_i = MYMIN(wear_i + 10, 511);
87                 if(wear_i <= 255)
88                         color.set(255, wear_i, 255, 0);
89                 else
90                         color.set(255, 255, 511-wear_i, 0);
91
92                 core::rect<s32> progressrect2 = progressrect;
93                 progressrect2.LowerRightCorner.X = progressmid;
94                 driver->draw2DRectangle(color, progressrect2, clip);
95
96                 color = video::SColor(255,0,0,0);
97                 progressrect2 = progressrect;
98                 progressrect2.UpperLeftCorner.X = progressmid;
99                 driver->draw2DRectangle(color, progressrect2, clip);
100         }
101
102         if(font != NULL && item.count >= 2)
103         {
104                 // Get the item count as a string
105                 std::string text = itos(item.count);
106                 v2u32 dim = font->getDimension(narrow_to_wide(text).c_str());
107                 v2s32 sdim(dim.X,dim.Y);
108
109                 core::rect<s32> rect2(
110                         /*rect.UpperLeftCorner,
111                         core::dimension2d<u32>(rect.getWidth(), 15)*/
112                         rect.LowerRightCorner - sdim,
113                         sdim
114                 );
115
116                 video::SColor bgcolor(128,0,0,0);
117                 driver->draw2DRectangle(bgcolor, rect2, clip);
118
119                 video::SColor color(255,255,255,255);
120                 font->draw(text.c_str(), rect2, color, false, false, clip);
121         }
122 }
123
124 /*
125         GUIFormSpecMenu
126 */
127
128 GUIFormSpecMenu::GUIFormSpecMenu(gui::IGUIEnvironment* env,
129                 gui::IGUIElement* parent, s32 id,
130                 IMenuManager *menumgr,
131                 InventoryManager *invmgr,
132                 IGameDef *gamedef
133 ):
134         GUIModalMenu(env, parent, id, menumgr),
135         m_invmgr(invmgr),
136         m_gamedef(gamedef),
137         m_form_src(NULL),
138         m_text_dst(NULL),
139         m_selected_item(NULL),
140         m_selected_amount(0),
141         m_selected_dragging(false),
142         m_tooltip_element(NULL)
143 {
144 }
145
146 GUIFormSpecMenu::~GUIFormSpecMenu()
147 {
148         removeChildren();
149
150         delete m_selected_item;
151         delete m_form_src;
152         delete m_text_dst;
153 }
154
155 void GUIFormSpecMenu::removeChildren()
156 {
157         const core::list<gui::IGUIElement*> &children = getChildren();
158         core::list<gui::IGUIElement*> children_copy;
159         for(core::list<gui::IGUIElement*>::ConstIterator
160                         i = children.begin(); i != children.end(); i++)
161         {
162                 children_copy.push_back(*i);
163         }
164         for(core::list<gui::IGUIElement*>::Iterator
165                         i = children_copy.begin();
166                         i != children_copy.end(); i++)
167         {
168                 (*i)->remove();
169         }
170         /*{
171                 gui::IGUIElement *e = getElementFromId(256);
172                 if(e != NULL)
173                         e->remove();
174         }*/
175         if(m_tooltip_element)
176         {
177                 m_tooltip_element->remove();
178                 m_tooltip_element = NULL;
179         }
180 }
181
182 void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
183 {
184         // Remove children
185         removeChildren();
186         
187         v2s32 size(100,100);
188         s32 helptext_h = 15;
189         core::rect<s32> rect;
190
191         // Base position of contents of form
192         v2s32 basepos = getBasePos();
193         // State of basepos, 0 = not set, 1= set by formspec, 2 = set by size[] element
194         // Used to adjust form size automatically if needed
195         // A proceed button is added if there is no size[] element
196         int bp_set = 0;
197         
198         /* Convert m_init_draw_spec to m_inventorylists */
199         
200         m_inventorylists.clear();
201         m_images.clear();
202         m_fields.clear();
203
204         Strfnd f(m_formspec_string);
205         while(f.atend() == false)
206         {
207                 std::string type = trim(f.next("["));
208                 if(type == "invsize" || type == "size")
209                 {
210                         v2f invsize;
211                         invsize.X = stof(f.next(","));
212                         if(type == "size")
213                         {
214                                 invsize.Y = stof(f.next("]"));
215                         }
216                         else{
217                                 invsize.Y = stof(f.next(";"));
218                                 errorstream<<"WARNING: invsize is deprecated, use size"<<std::endl;
219                                 f.next("]");
220                         }
221                         infostream<<"size ("<<invsize.X<<","<<invsize.Y<<")"<<std::endl;
222
223                         padding = v2s32(screensize.Y/40, screensize.Y/40);
224                         spacing = v2s32(screensize.Y/12, screensize.Y/13);
225                         imgsize = v2s32(screensize.Y/15, screensize.Y/15);
226                         size = v2s32(
227                                 padding.X*2+spacing.X*(invsize.X-1.0)+imgsize.X,
228                                 padding.Y*2+spacing.Y*(invsize.Y-1.0)+imgsize.Y + (helptext_h-5)
229                         );
230                         rect = core::rect<s32>(
231                                         screensize.X/2 - size.X/2,
232                                         screensize.Y/2 - size.Y/2,
233                                         screensize.X/2 + size.X/2,
234                                         screensize.Y/2 + size.Y/2
235                         );
236                         DesiredRect = rect;
237                         recalculateAbsolutePosition(false);
238                         basepos = getBasePos();
239                         bp_set = 2;
240                 }
241                 else if(type == "list")
242                 {
243                         std::string name = f.next(";");
244                         InventoryLocation loc;
245                         if(name == "context" || name == "current_name")
246                                 loc = m_current_inventory_location;
247                         else
248                                 loc.deSerialize(name);
249                         std::string listname = f.next(";");
250                         v2s32 pos = basepos;
251                         pos.X += stof(f.next(",")) * (float)spacing.X;
252                         pos.Y += stof(f.next(";")) * (float)spacing.Y;
253                         v2s32 geom;
254                         geom.X = stoi(f.next(","));
255                         geom.Y = stoi(f.next(";"));
256                         infostream<<"list inv="<<name<<", listname="<<listname
257                                         <<", pos=("<<pos.X<<","<<pos.Y<<")"
258                                         <<", geom=("<<geom.X<<","<<geom.Y<<")"
259                                         <<std::endl;
260                         f.next("]");
261                         if(bp_set != 2)
262                                 errorstream<<"WARNING: invalid use of button without a size[] element"<<std::endl;
263                         m_inventorylists.push_back(ListDrawSpec(loc, listname, pos, geom));
264                 }
265                 else if(type == "image")
266                 {
267                         v2s32 pos = basepos;
268                         pos.X += stof(f.next(",")) * (float)spacing.X;
269                         pos.Y += stof(f.next(";")) * (float)spacing.Y;
270                         v2s32 geom;
271                         geom.X = stof(f.next(",")) * (float)imgsize.X;
272                         geom.Y = stof(f.next(";")) * (float)imgsize.Y;
273                         std::string name = f.next("]");
274                         infostream<<"image name="<<name
275                                         <<", pos=("<<pos.X<<","<<pos.Y<<")"
276                                         <<", geom=("<<geom.X<<","<<geom.Y<<")"
277                                         <<std::endl;
278                         if(bp_set != 2)
279                                 errorstream<<"WARNING: invalid use of button without a size[] element"<<std::endl;
280                         m_images.push_back(ImageDrawSpec(name, pos, geom));
281                 }
282                 else if(type == "field")
283                 {
284                         std::string fname = f.next(";");
285                         std::string flabel = f.next(";");
286                         
287                         if(fname.find(",") == std::string::npos && flabel.find(",") == std::string::npos)
288                         {
289                                 if(!bp_set)
290                                 {
291                                         rect = core::rect<s32>(
292                                                 screensize.X/2 - 580/2,
293                                                 screensize.Y/2 - 300/2,
294                                                 screensize.X/2 + 580/2,
295                                                 screensize.Y/2 + 300/2
296                                         );
297                                         DesiredRect = rect;
298                                         recalculateAbsolutePosition(false);
299                                         basepos = getBasePos();
300                                         bp_set = 1;
301                                 }
302                                 else if(bp_set == 2)
303                                         errorstream<<"WARNING: invalid use of unpositioned field in inventory"<<std::endl;
304
305                                 v2s32 pos = basepos;
306                                 pos.Y = ((m_fields.size()+2)*60);
307                                 v2s32 size = DesiredRect.getSize();
308                                 rect = core::rect<s32>(size.X/2-150, pos.Y, (size.X/2-150)+300, pos.Y+30);
309                         }
310                         else
311                         {
312                                 v2s32 pos;
313                                 pos.X = stof(fname.substr(0,fname.find(","))) * (float)spacing.X;
314                                 pos.Y = stof(fname.substr(fname.find(",")+1)) * (float)spacing.Y;
315                                 v2s32 geom;
316                                 geom.X = (stof(flabel.substr(0,flabel.find(","))) * (float)spacing.X)-(spacing.X-imgsize.X);
317                                 pos.Y += (stof(flabel.substr(flabel.find(",")+1)) * (float)imgsize.Y)/2;
318
319                                 rect = core::rect<s32>(pos.X, pos.Y-15, pos.X+geom.X, pos.Y+15);
320                                 
321                                 fname = f.next(";");
322                                 flabel = f.next(";");
323                                 if(bp_set != 2)
324                                         errorstream<<"WARNING: invalid use of positioned field without a size[] element"<<std::endl;
325                                 
326                         }
327
328                         std::string odefault = f.next("]");
329                         std::string fdefault;
330                         
331                         // fdefault may contain a variable reference, which
332                         // needs to be resolved from the node metadata
333                         if(m_form_src)
334                                 fdefault = m_form_src->resolveText(odefault);
335                         else
336                                 fdefault = odefault;
337
338                         FieldSpec spec = FieldSpec(
339                                 narrow_to_wide(fname.c_str()),
340                                 narrow_to_wide(flabel.c_str()),
341                                 narrow_to_wide(fdefault.c_str()),
342                                 258+m_fields.size()
343                         );
344                         
345                         // three cases: field and no label, label and no field, label and field
346                         if (flabel == "") 
347                         {
348                                 spec.send = true;
349                                 gui::IGUIElement *e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid);
350                                 Environment->setFocus(e);
351
352                                 irr::SEvent evt;
353                                 evt.EventType = EET_KEY_INPUT_EVENT;
354                                 evt.KeyInput.Key = KEY_END;
355                                 evt.KeyInput.PressedDown = true;
356                                 e->OnEvent(evt);
357                         }
358                         else if (fname == "")
359                         {
360                                 // set spec field id to 0, this stops submit searching for a value that isn't there
361                                 Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid);
362                         }
363                         else
364                         {
365                                 spec.send = true;
366                                 gui::IGUIElement *e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid);
367                                 Environment->setFocus(e);
368                                 rect.UpperLeftCorner.Y -= 15;
369                                 rect.LowerRightCorner.Y -= 15;
370                                 Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0);
371
372                                 irr::SEvent evt;
373                                 evt.EventType = EET_KEY_INPUT_EVENT;
374                                 evt.KeyInput.Key = KEY_END;
375                                 evt.KeyInput.PressedDown = true;
376                                 e->OnEvent(evt);
377                         }
378                         
379                         m_fields.push_back(spec);
380                 }
381                 else if(type == "label")
382                 {
383                         v2s32 pos;
384                         pos.X = stof(f.next(",")) * (float)spacing.X;
385                         pos.Y = stof(f.next(";")) * (float)spacing.Y;
386
387                         rect = core::rect<s32>(pos.X, pos.Y+((imgsize.Y/2)-15), pos.X+300, pos.Y+((imgsize.Y/2)+15));
388                         
389                         std::string flabel = f.next("]");
390                         if(bp_set != 2)
391                                 errorstream<<"WARNING: invalid use of label without a size[] element"<<std::endl;
392
393                         FieldSpec spec = FieldSpec(
394                                 narrow_to_wide(""),
395                                 narrow_to_wide(flabel.c_str()),
396                                 narrow_to_wide(""),
397                                 258+m_fields.size()
398                         );
399                         Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid);
400                         m_fields.push_back(spec);
401                 }
402                 else if(type == "button" || type == "button_exit")
403                 {
404                         v2s32 pos;
405                         pos.X = stof(f.next(",")) * (float)spacing.X;
406                         pos.Y = stof(f.next(";")) * (float)spacing.Y;
407                         v2s32 geom;
408                         geom.X = (stof(f.next(",")) * (float)spacing.X)-(spacing.X-imgsize.X);
409                         pos.Y += (stof(f.next(";")) * (float)imgsize.Y)/2;
410
411                         rect = core::rect<s32>(pos.X, pos.Y-15, pos.X+geom.X, pos.Y+15);
412                         
413                         std::string fname = f.next(";");
414                         std::string flabel = f.next("]");
415                         if(bp_set != 2)
416                                 errorstream<<"WARNING: invalid use of button without a size[] element"<<std::endl;
417
418                         FieldSpec spec = FieldSpec(
419                                 narrow_to_wide(fname.c_str()),
420                                 narrow_to_wide(flabel.c_str()),
421                                 narrow_to_wide(""),
422                                 258+m_fields.size()
423                         );
424                         spec.is_button = true;
425                         if(type == "button_exit")
426                                 spec.is_exit = true;
427                         Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());
428                         m_fields.push_back(spec);
429                 }
430                 else if(type == "image_button" || type == "image_button_exit")
431                 {
432                         v2s32 pos;
433                         pos.X = stof(f.next(",")) * (float)spacing.X;
434                         pos.Y = stof(f.next(";")) * (float)spacing.Y;
435                         v2s32 geom;
436                         geom.X = (stof(f.next(",")) * (float)spacing.X)-(spacing.X-imgsize.X);
437                         geom.Y = (stof(f.next(";")) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
438
439                         rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
440                         
441                         std::string fimage = f.next(";");
442                         std::string fname = f.next(";");
443                         std::string flabel = f.next("]");
444                         if(bp_set != 2)
445                                 errorstream<<"WARNING: invalid use of image_button without a size[] element"<<std::endl;
446
447                         FieldSpec spec = FieldSpec(
448                                 narrow_to_wide(fname.c_str()),
449                                 narrow_to_wide(flabel.c_str()),
450                                 narrow_to_wide(fimage.c_str()),
451                                 258+m_fields.size()
452                         );
453                         spec.is_button = true;
454                         if(type == "image_button_exit")
455                                 spec.is_exit = true;
456                         
457                         video::ITexture *texture = m_gamedef->tsrc()->getTextureRaw(fimage);
458                         gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());
459                         e->setImage(texture);
460                         e->setPressedImage(texture);
461                         e->setScaleImage(true);
462                         
463                         m_fields.push_back(spec);
464                 }
465                 else
466                 {
467                         // Ignore others
468                         std::string ts = f.next("]");
469                         infostream<<"Unknown DrawSpec: type="<<type<<", data=\""<<ts<<"\""
470                                         <<std::endl;
471                 }
472         }
473
474         // If there's inventory, put the usage string at the bottom
475         if (m_inventorylists.size())
476         {
477                 changeCtype("");
478                 core::rect<s32> rect(0, 0, size.X-padding.X*2, helptext_h);
479                 rect = rect + v2s32(size.X/2 - rect.getWidth()/2,
480                                 size.Y-rect.getHeight()-5);
481                 const wchar_t *text = wgettext("Left click: Move all items, Right click: Move single item");
482                 Environment->addStaticText(text, rect, false, true, this, 256);
483                 changeCtype("C");
484         }
485         // If there's fields, add a Proceed button
486         if (m_fields.size() && bp_set != 2) 
487         {
488                 // if the size wasn't set by an invsize[] or size[] adjust it now to fit all the fields
489                 rect = core::rect<s32>(
490                         screensize.X/2 - 580/2,
491                         screensize.Y/2 - 300/2,
492                         screensize.X/2 + 580/2,
493                         screensize.Y/2 + 240/2+(m_fields.size()*60)
494                 );
495                 DesiredRect = rect;
496                 recalculateAbsolutePosition(false);
497                 basepos = getBasePos();
498
499                 changeCtype("");
500                 {
501                         v2s32 pos = basepos;
502                         pos.Y = ((m_fields.size()+2)*60);
503
504                         v2s32 size = DesiredRect.getSize();
505                         rect = core::rect<s32>(size.X/2-70, pos.Y, (size.X/2-70)+140, pos.Y+30);
506                         Environment->addButton(rect, this, 257, wgettext("Proceed"));
507                 }
508                 changeCtype("C");
509         }
510         // Add tooltip
511         {
512                 // Note: parent != this so that the tooltip isn't clipped by the menu rectangle
513                 m_tooltip_element = Environment->addStaticText(L"",core::rect<s32>(0,0,110,18));
514                 m_tooltip_element->enableOverrideColor(true);
515                 m_tooltip_element->setBackgroundColor(video::SColor(255,110,130,60));
516                 m_tooltip_element->setDrawBackground(true);
517                 m_tooltip_element->setDrawBorder(true);
518                 m_tooltip_element->setOverrideColor(video::SColor(255,255,255,255));
519                 m_tooltip_element->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
520                 m_tooltip_element->setWordWrap(false);
521         }
522 }
523
524 GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
525 {
526         core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
527         
528         for(u32 i=0; i<m_inventorylists.size(); i++)
529         {
530                 const ListDrawSpec &s = m_inventorylists[i];
531
532                 for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
533                 {
534                         s32 x = (i%s.geom.X) * spacing.X;
535                         s32 y = (i/s.geom.X) * spacing.Y;
536                         v2s32 p0(x,y);
537                         core::rect<s32> rect = imgrect + s.pos + p0;
538                         if(rect.isPointInside(p))
539                         {
540                                 return ItemSpec(s.inventoryloc, s.listname, i);
541                         }
542                 }
543         }
544
545         return ItemSpec(InventoryLocation(), "", -1);
546 }
547
548 void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase)
549 {
550         video::IVideoDriver* driver = Environment->getVideoDriver();
551
552         // Get font
553         gui::IGUIFont *font = NULL;
554         gui::IGUISkin* skin = Environment->getSkin();
555         if (skin)
556                 font = skin->getFont();
557         
558         Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
559         if(!inv){
560                 infostream<<"GUIFormSpecMenu::drawList(): WARNING: "
561                                 <<"The inventory location "
562                                 <<"\""<<s.inventoryloc.dump()<<"\" doesn't exist"
563                                 <<std::endl;
564                 return;
565         }
566         InventoryList *ilist = inv->getList(s.listname);
567         if(!ilist){
568                 infostream<<"GUIFormSpecMenu::drawList(): WARNING: "
569                                 <<"The inventory list \""<<s.listname<<"\" @ \""
570                                 <<s.inventoryloc.dump()<<"\" doesn't exist"
571                                 <<std::endl;
572                 return;
573         }
574         
575         core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
576         
577         for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
578         {
579                 s32 x = (i%s.geom.X) * spacing.X;
580                 s32 y = (i/s.geom.X) * spacing.Y;
581                 v2s32 p(x,y);
582                 core::rect<s32> rect = imgrect + s.pos + p;
583                 ItemStack item;
584                 if(ilist)
585                         item = ilist->getItem(i);
586
587                 bool selected = m_selected_item
588                         && m_invmgr->getInventory(m_selected_item->inventoryloc) == inv
589                         && m_selected_item->listname == s.listname
590                         && m_selected_item->i == i;
591                 bool hovering = rect.isPointInside(m_pointer);
592
593                 if(phase == 0)
594                 {
595                         if(hovering && m_selected_item)
596                         {
597                                 video::SColor bgcolor(255,192,192,192);
598                                 driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect);
599                         }
600                         else
601                         {
602                                 video::SColor bgcolor(255,128,128,128);
603                                 driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect);
604                         }
605                 }
606
607                 if(phase == 1)
608                 {
609                         // Draw item stack
610                         if(selected)
611                         {
612                                 item.takeItem(m_selected_amount);
613                         }
614                         if(!item.empty())
615                         {
616                                 drawItemStack(driver, font, item,
617                                                 rect, &AbsoluteClippingRect, m_gamedef);
618                         }
619
620                         // Draw tooltip
621                         std::string tooltip_text = "";
622                         if(hovering && !m_selected_item)
623                                 tooltip_text = item.getDefinition(m_gamedef->idef()).description;
624                         if(tooltip_text != "")
625                         {
626                                 m_tooltip_element->setVisible(true);
627                                 this->bringToFront(m_tooltip_element);
628                                 m_tooltip_element->setText(narrow_to_wide(tooltip_text).c_str());
629                                 s32 tooltip_x = m_pointer.X + 15;
630                                 s32 tooltip_y = m_pointer.Y + 15;
631                                 s32 tooltip_width = m_tooltip_element->getTextWidth() + 15;
632                                 s32 tooltip_height = m_tooltip_element->getTextHeight() + 5;
633                                 m_tooltip_element->setRelativePosition(core::rect<s32>(
634                                                 core::position2d<s32>(tooltip_x, tooltip_y),
635                                                 core::dimension2d<s32>(tooltip_width, tooltip_height)));
636                         }
637                 }
638         }
639 }
640
641 void GUIFormSpecMenu::drawSelectedItem()
642 {
643         if(!m_selected_item)
644                 return;
645
646         video::IVideoDriver* driver = Environment->getVideoDriver();
647
648         // Get font
649         gui::IGUIFont *font = NULL;
650         gui::IGUISkin* skin = Environment->getSkin();
651         if (skin)
652                 font = skin->getFont();
653         
654         Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
655         assert(inv);
656         InventoryList *list = inv->getList(m_selected_item->listname);
657         assert(list);
658         ItemStack stack = list->getItem(m_selected_item->i);
659         stack.count = m_selected_amount;
660
661         core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
662         core::rect<s32> rect = imgrect + (m_pointer - imgrect.getCenter());
663         drawItemStack(driver, font, stack, rect, NULL, m_gamedef);
664 }
665
666 void GUIFormSpecMenu::drawMenu()
667 {
668         if(m_form_src){
669                 std::string newform = m_form_src->getForm();
670                 if(newform != m_formspec_string){
671                         m_formspec_string = newform;
672                         regenerateGui(m_screensize_old);
673                 }
674         }
675
676         updateSelectedItem();
677
678         gui::IGUISkin* skin = Environment->getSkin();
679         if (!skin)
680                 return;
681         video::IVideoDriver* driver = Environment->getVideoDriver();
682         
683         video::SColor bgcolor(140,0,0,0);
684         driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
685
686         m_tooltip_element->setVisible(false);
687
688         /*
689                 Draw items
690                 Phase 0: Item slot rectangles
691                 Phase 1: Item images; prepare tooltip
692         */
693         
694         for(int phase=0; phase<=1; phase++)
695         for(u32 i=0; i<m_inventorylists.size(); i++)
696         {
697                 drawList(m_inventorylists[i], phase);
698         }
699
700         for(u32 i=0; i<m_images.size(); i++)
701         {
702                 const ImageDrawSpec &spec = m_images[i];
703                 video::ITexture *texture =
704                                 m_gamedef->tsrc()->getTextureRaw(spec.name);
705                 // Image size on screen
706                 core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
707                 // Image rectangle on screen
708                 core::rect<s32> rect = imgrect + spec.pos;
709                 const video::SColor color(255,255,255,255);
710                 const video::SColor colors[] = {color,color,color,color};
711                 driver->draw2DImage(texture, rect,
712                         core::rect<s32>(core::position2d<s32>(0,0),
713                                         core::dimension2di(texture->getOriginalSize())),
714                         NULL/*&AbsoluteClippingRect*/, colors, true);
715         }
716
717         /*
718                 Draw dragged item stack
719         */
720         drawSelectedItem();
721
722         /*
723                 Call base class
724         */
725         gui::IGUIElement::draw();
726 }
727
728 void GUIFormSpecMenu::updateSelectedItem()
729 {
730         // If the selected stack has become empty for some reason, deselect it.
731         // If the selected stack has become smaller, adjust m_selected_amount.
732         if(m_selected_item)
733         {
734                 bool selection_valid = false;
735                 if(m_selected_item->isValid())
736                 {
737                         Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
738                         if(inv)
739                         {
740                                 InventoryList *list = inv->getList(m_selected_item->listname);
741                                 if(list && (u32) m_selected_item->i < list->getSize())
742                                 {
743                                         ItemStack stack = list->getItem(m_selected_item->i);
744                                         if(m_selected_amount > stack.count)
745                                                 m_selected_amount = stack.count;
746                                         if(!stack.empty())
747                                                 selection_valid = true;
748                                 }
749                         }
750                 }
751                 if(!selection_valid)
752                 {
753                         delete m_selected_item;
754                         m_selected_item = NULL;
755                         m_selected_amount = 0;
756                         m_selected_dragging = false;
757                 }
758         }
759
760         // If craftresult is nonempty and nothing else is selected, select it now.
761         if(!m_selected_item)
762         {
763                 for(u32 i=0; i<m_inventorylists.size(); i++)
764                 {
765                         const ListDrawSpec &s = m_inventorylists[i];
766                         if(s.listname == "craftpreview")
767                         {
768                                 Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
769                                 InventoryList *list = inv->getList("craftresult");
770                                 if(list && list->getSize() >= 1 && !list->getItem(0).empty())
771                                 {
772                                         m_selected_item = new ItemSpec;
773                                         m_selected_item->inventoryloc = s.inventoryloc;
774                                         m_selected_item->listname = "craftresult";
775                                         m_selected_item->i = 0;
776                                         m_selected_amount = 0;
777                                         m_selected_dragging = false;
778                                         break;
779                                 }
780                         }
781                 }
782         }
783
784         // If craftresult is selected, keep the whole stack selected
785         if(m_selected_item && m_selected_item->listname == "craftresult")
786         {
787                 Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
788                 assert(inv);
789                 InventoryList *list = inv->getList(m_selected_item->listname);
790                 assert(list);
791                 m_selected_amount = list->getItem(m_selected_item->i).count;
792         }
793 }
794
795 void GUIFormSpecMenu::acceptInput()
796 {
797         if(m_text_dst)
798         {
799                 std::map<std::string, std::string> fields;
800                 gui::IGUIElement *e;
801                 for(u32 i=0; i<m_fields.size(); i++)
802                 {
803                         const FieldSpec &s = m_fields[i];
804                         if(s.send) 
805                         {
806                                 if(s.is_button)
807                                 {
808                                         fields[wide_to_narrow(s.fname.c_str())] = wide_to_narrow(s.flabel.c_str());
809                                 }
810                                 else
811                                 {
812                                         e = getElementFromId(s.fid);
813                                         if(e != NULL)
814                                         {
815                                                 fields[wide_to_narrow(s.fname.c_str())] = wide_to_narrow(e->getText());
816                                         }
817                                 }
818                         }
819                 }
820                 m_text_dst->gotText(fields);
821         }
822 }
823
824 bool GUIFormSpecMenu::OnEvent(const SEvent& event)
825 {
826         if(event.EventType==EET_KEY_INPUT_EVENT)
827         {
828                 KeyPress kp(event.KeyInput);
829                 if (event.KeyInput.PressedDown && (kp == EscapeKey ||
830                         kp == getKeySetting("keymap_inventory")))
831                 {
832                         quitMenu();
833                         return true;
834                 }
835                 if(event.KeyInput.Key==KEY_RETURN && event.KeyInput.PressedDown)
836                 {
837                         acceptInput();
838                         quitMenu();
839                         return true;
840                 }
841         }
842         if(event.EventType==EET_MOUSE_INPUT_EVENT
843                         && event.MouseInput.Event == EMIE_MOUSE_MOVED)
844         {
845                 // Mouse moved
846                 m_pointer = v2s32(event.MouseInput.X, event.MouseInput.Y);
847         }
848         if(event.EventType==EET_MOUSE_INPUT_EVENT
849                         && event.MouseInput.Event != EMIE_MOUSE_MOVED)
850         {
851                 // Mouse event other than movement
852
853                 v2s32 p(event.MouseInput.X, event.MouseInput.Y);
854                 m_pointer = p;
855
856                 // Get selected item and hovered/clicked item (s)
857
858                 updateSelectedItem();
859                 ItemSpec s = getItemAtPos(p);
860
861                 Inventory *inv_selected = NULL;
862                 Inventory *inv_s = NULL;
863
864                 if(m_selected_item)
865                 {
866                         inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc);
867                         assert(inv_selected);
868                         assert(inv_selected->getList(m_selected_item->listname) != NULL);
869                 }
870
871                 u32 s_count = 0;
872
873                 if(s.isValid())
874                 do{ // breakable
875                         inv_s = m_invmgr->getInventory(s.inventoryloc);
876
877                         if(!inv_s){
878                                 errorstream<<"InventoryMenu: The selected inventory location "
879                                                 <<"\""<<s.inventoryloc.dump()<<"\" doesn't exist"
880                                                 <<std::endl;
881                                 s.i = -1;  // make it invalid again
882                                 break;
883                         }
884
885                         InventoryList *list = inv_s->getList(s.listname);
886                         if(list == NULL){
887                                 errorstream<<"InventoryMenu: The selected inventory list \""
888                                                 <<s.listname<<"\" does not exist"<<std::endl;
889                                 s.i = -1;  // make it invalid again
890                                 break;
891                         }
892
893                         if((u32)s.i >= list->getSize()){
894                                 errorstream<<"InventoryMenu: The selected inventory list \""
895                                                 <<s.listname<<"\" is too small (i="<<s.i<<", size="
896                                                 <<list->getSize()<<")"<<std::endl;
897                                 s.i = -1;  // make it invalid again
898                                 break;
899                         }
900
901                         s_count = list->getItem(s.i).count;
902                 }while(0);
903
904                 bool identical = (m_selected_item != NULL) && s.isValid() &&
905                         (inv_selected == inv_s) &&
906                         (m_selected_item->listname == s.listname) &&
907                         (m_selected_item->i == s.i);
908
909                 // buttons: 0 = left, 1 = right, 2 = middle
910                 // up/down: 0 = down (press), 1 = up (release), 2 = unknown event
911                 int button = 0;
912                 int updown = 2;
913                 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
914                         { button = 0; updown = 0; }
915                 else if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
916                         { button = 1; updown = 0; }
917                 else if(event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN)
918                         { button = 2; updown = 0; }
919                 else if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
920                         { button = 0; updown = 1; }
921                 else if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
922                         { button = 1; updown = 1; }
923                 else if(event.MouseInput.Event == EMIE_MMOUSE_LEFT_UP)
924                         { button = 2; updown = 1; }
925
926                 // Set this number to a positive value to generate a move action
927                 // from m_selected_item to s.
928                 u32 move_amount = 0;
929
930                 // Set this number to a positive value to generate a drop action
931                 // from m_selected_item.
932                 u32 drop_amount = 0;
933
934                 // Set this number to a positive value to generate a craft action at s.
935                 u32 craft_amount = 0;
936
937                 if(updown == 0)
938                 {
939                         // Some mouse button has been pressed
940
941                         //infostream<<"Mouse button "<<button<<" pressed at p=("
942                         //      <<p.X<<","<<p.Y<<")"<<std::endl;
943
944                         m_selected_dragging = false;
945
946                         if(s.isValid() && s.listname == "craftpreview")
947                         {
948                                 // Craft preview has been clicked: craft
949                                 craft_amount = (button == 2 ? 10 : 1);
950                         }
951                         else if(m_selected_item == NULL)
952                         {
953                                 if(s_count != 0)
954                                 {
955                                         // Non-empty stack has been clicked: select it
956                                         m_selected_item = new ItemSpec(s);
957
958                                         if(button == 1)  // right
959                                                 m_selected_amount = (s_count + 1) / 2;
960                                         else if(button == 2)  // middle
961                                                 m_selected_amount = MYMIN(s_count, 10);
962                                         else  // left
963                                                 m_selected_amount = s_count;
964
965                                         m_selected_dragging = true;
966                                 }
967                         }
968                         else  // m_selected_item != NULL
969                         {
970                                 assert(m_selected_amount >= 1);
971
972                                 if(s.isValid())
973                                 {
974                                         // Clicked a slot: move
975                                         if(button == 1)  // right
976                                                 move_amount = 1;
977                                         else if(button == 2)  // middle
978                                                 move_amount = MYMIN(m_selected_amount, 10);
979                                         else  // left
980                                                 move_amount = m_selected_amount;
981
982                                         if(identical)
983                                         {
984                                                 if(move_amount >= m_selected_amount)
985                                                         m_selected_amount = 0;
986                                                 else
987                                                         m_selected_amount -= move_amount;
988                                                 move_amount = 0;
989                                         }
990                                 }
991                                 else if(getAbsoluteClippingRect().isPointInside(m_pointer))
992                                 {
993                                         // Clicked somewhere else: deselect
994                                         m_selected_amount = 0;
995                                 }
996                                 else
997                                 {
998                                         // Clicked outside of the window: drop
999                                         if(button == 1)  // right
1000                                                 drop_amount = 1;
1001                                         else if(button == 2)  // middle
1002                                                 drop_amount = MYMIN(m_selected_amount, 10);
1003                                         else  // left
1004                                                 drop_amount = m_selected_amount;
1005                                 }
1006                         }
1007                 }
1008                 else if(updown == 1)
1009                 {
1010                         // Some mouse button has been released
1011
1012                         //infostream<<"Mouse button "<<button<<" released at p=("
1013                         //      <<p.X<<","<<p.Y<<")"<<std::endl;
1014
1015                         if(m_selected_item != NULL && m_selected_dragging && s.isValid())
1016                         {
1017                                 if(!identical)
1018                                 {
1019                                         // Dragged to different slot: move all selected
1020                                         move_amount = m_selected_amount;
1021                                 }
1022                         }
1023                         else if(m_selected_item != NULL && m_selected_dragging &&
1024                                 !(getAbsoluteClippingRect().isPointInside(m_pointer)))
1025                         {
1026                                 // Dragged outside of window: drop all selected
1027                                 drop_amount = m_selected_amount;
1028                         }
1029
1030                         m_selected_dragging = false;
1031                 }
1032
1033                 // Possibly send inventory action to server
1034                 if(move_amount > 0)
1035                 {
1036                         // Send IACTION_MOVE
1037
1038                         assert(m_selected_item && m_selected_item->isValid());
1039                         assert(s.isValid());
1040
1041                         assert(inv_selected && inv_s);
1042                         InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
1043                         InventoryList *list_to = inv_s->getList(s.listname);
1044                         assert(list_from && list_to);
1045                         ItemStack stack_from = list_from->getItem(m_selected_item->i);
1046                         ItemStack stack_to = list_to->getItem(s.i);
1047
1048                         // Check how many items can be moved
1049                         move_amount = stack_from.count = MYMIN(move_amount, stack_from.count);
1050                         ItemStack leftover = stack_to.addItem(stack_from, m_gamedef->idef());
1051                         if(leftover.count == stack_from.count)
1052                         {
1053                                 // Swap the stacks
1054                                 m_selected_amount -= stack_to.count;
1055                         }
1056                         else if(leftover.empty())
1057                         {
1058                                 // Item fits
1059                                 m_selected_amount -= move_amount;
1060                         }
1061                         else
1062                         {
1063                                 // Item only fits partially
1064                                 move_amount -= leftover.count;
1065                                 m_selected_amount -= move_amount;
1066                         }
1067
1068                         infostream<<"Handing IACTION_MOVE to manager"<<std::endl;
1069                         IMoveAction *a = new IMoveAction();
1070                         a->count = move_amount;
1071                         a->from_inv = m_selected_item->inventoryloc;
1072                         a->from_list = m_selected_item->listname;
1073                         a->from_i = m_selected_item->i;
1074                         a->to_inv = s.inventoryloc;
1075                         a->to_list = s.listname;
1076                         a->to_i = s.i;
1077                         m_invmgr->inventoryAction(a);
1078                 }
1079                 else if(drop_amount > 0)
1080                 {
1081                         // Send IACTION_DROP
1082
1083                         assert(m_selected_item && m_selected_item->isValid());
1084                         assert(inv_selected);
1085                         InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
1086                         assert(list_from);
1087                         ItemStack stack_from = list_from->getItem(m_selected_item->i);
1088
1089                         // Check how many items can be dropped
1090                         drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count);
1091                         assert(drop_amount > 0 && drop_amount <= m_selected_amount);
1092                         m_selected_amount -= drop_amount;
1093
1094                         infostream<<"Handing IACTION_DROP to manager"<<std::endl;
1095                         IDropAction *a = new IDropAction();
1096                         a->count = drop_amount;
1097                         a->from_inv = m_selected_item->inventoryloc;
1098                         a->from_list = m_selected_item->listname;
1099                         a->from_i = m_selected_item->i;
1100                         m_invmgr->inventoryAction(a);
1101                 }
1102                 else if(craft_amount > 0)
1103                 {
1104                         // Send IACTION_CRAFT
1105
1106                         assert(s.isValid());
1107                         assert(inv_s);
1108
1109                         infostream<<"Handing IACTION_CRAFT to manager"<<std::endl;
1110                         ICraftAction *a = new ICraftAction();
1111                         a->count = craft_amount;
1112                         a->craft_inv = s.inventoryloc;
1113                         m_invmgr->inventoryAction(a);
1114                 }
1115
1116                 // If m_selected_amount has been decreased to zero, deselect
1117                 if(m_selected_amount == 0)
1118                 {
1119                         delete m_selected_item;
1120                         m_selected_item = NULL;
1121                         m_selected_amount = 0;
1122                         m_selected_dragging = false;
1123                 }
1124         }
1125         if(event.EventType==EET_GUI_EVENT)
1126         {
1127                 if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST
1128                                 && isVisible())
1129                 {
1130                         if(!canTakeFocus(event.GUIEvent.Element))
1131                         {
1132                                 infostream<<"GUIFormSpecMenu: Not allowing focus change."
1133                                                 <<std::endl;
1134                                 // Returning true disables focus change
1135                                 return true;
1136                         }
1137                 }
1138                 if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED)
1139                 {
1140                         switch(event.GUIEvent.Caller->getID())
1141                         {
1142                         case 257:
1143                                 acceptInput();
1144                                 quitMenu();
1145                                 // quitMenu deallocates menu
1146                                 return true;
1147                         }
1148                         // find the element that was clicked
1149                         for(u32 i=0; i<m_fields.size(); i++)
1150                         {
1151                                 FieldSpec &s = m_fields[i];
1152                                 // if its a button, set the send field so 
1153                                 // lua knows which button was pressed
1154                                 if (s.is_button && s.fid == event.GUIEvent.Caller->getID())
1155                                 {
1156                                         s.send = true;
1157                                         acceptInput();
1158                                         if(s.is_exit){
1159                                                 quitMenu();
1160                                                 return true;
1161                                         }else{
1162                                                 s.send = false;
1163                                                 return true;
1164                                         }
1165                                 }
1166                         }
1167                 }
1168                 if(event.GUIEvent.EventType==gui::EGET_EDITBOX_ENTER)
1169                 {
1170                         if(event.GUIEvent.Caller->getID() > 257)
1171                         {
1172                                 acceptInput();
1173                                 quitMenu();
1174                                 // quitMenu deallocates menu
1175                                 return true;
1176                         }
1177                 }
1178         }
1179
1180         return Parent ? Parent->OnEvent(event) : false;
1181 }
1182