summaryrefslogtreecommitdiff
path: root/src/mapnode.cpp
blob: fbf0ac8c90d39c3313194641a8e4bc39eaea0829 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "irrlichttypes_extrabloated.h"
#include "mapnode.h"
#include "porting.h"
#include "main.h" // For g_settings
#include "nodedef.h"
#include "content_mapnode.h" // For mapnode_translate_*_internal
#include "serialization.h" // For ser_ver_supported
#include "util/serialize.h"
#include "log.h"
#include "util/numeric.h"
#include <string>
#include <sstream>

static const Rotation wallmounted_to_rot[] = {
	ROTATE_0, ROTATE_180, ROTATE_90, ROTATE_270
};

static const u8 rot_to_wallmounted[] = {
	2, 4, 3, 5
};


/*
	MapNode
*/

// Create directly from a nodename
// If name is unknown, sets CONTENT_IGNORE
MapNode::MapNode(INodeDefManager *ndef, const std::string &name,
		u8 a_param1, u8 a_param2)
{
	content_t id = CONTENT_IGNORE;
	ndef->getId(name, id);
	param0 = id;
	param1 = a_param1;
	param2 = a_param2;
}

void MapNode::setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr)
{
	// If node doesn't contain light data, ignore this
	if(nodemgr->get(*this).param_type != CPT_LIGHT)
		return;
	if(bank == LIGHTBANK_DAY)
	{
		param1 &= 0xf0;
		param1 |= a_light & 0x0f;
	}
	else if(bank == LIGHTBANK_NIGHT)
	{
		param1 &= 0x0f;
		param1 |= (a_light & 0x0f)<<4;
	}
	else
		assert(0);
}

u8 MapNode::getLight(enum LightBank bank, INodeDefManager *nodemgr) const
{
	// Select the brightest of [light source, propagated light]
	const ContentFeatures &f = nodemgr->get(*this);

	u8 light;
	if(f.param_type == CPT_LIGHT)
		light = bank == LIGHTBANK_DAY ? param1 & 0x0f : (param1 >> 4) & 0x0f;
	else
		light = 0;

	return MYMAX(f.light_source, light);
}

bool MapNode::getLightBanks(u8 &lightday, u8 &lightnight, INodeDefManager *nodemgr) const
{
	// Select the brightest of [light source, propagated light]
	const ContentFeatures &f = nodemgr->get(*this);
	if(f.param_type == CPT_LIGHT)
	{
		lightday = param1 & 0x0f;
		lightnight = (param1>>4)&0x0f;
	}
	else
	{
		lightday = 0;
		lightnight = 0;
	}
	if(f.light_source > lightday)
		lightday = f.light_source;
	if(f.light_source > lightnight)
		lightnight = f.light_source;
	return f.param_type == CPT_LIGHT || f.light_source != 0;
}

u8 MapNode::getFaceDir(INodeDefManager *nodemgr) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	if(f.param_type_2 == CPT2_FACEDIR)
		return getParam2() & 0x1F;
	return 0;
}

u8 MapNode::getWallMounted(INodeDefManager *nodemgr) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	if(f.param_type_2 == CPT2_WALLMOUNTED)
		return getParam2() & 0x07;
	return 0;
}

v3s16 MapNode::getWallMountedDir(INodeDefManager *nodemgr) const
{
	switch(getWallMounted(nodemgr))
	{
	case 0: default: return v3s16(0,1,0);
	case 1: return v3s16(0,-1,0);
	case 2: return v3s16(1,0,0);
	case 3: return v3s16(-1,0,0);
	case 4: return v3s16(0,0,1);
	case 5: return v3s16(0,0,-1);
	}
}

void MapNode::rotateAlongYAxis(INodeDefManager *nodemgr, Rotation rot) {
	ContentParamType2 cpt2 = nodemgr->get(*this).param_type_2;

	if (cpt2 == CPT2_FACEDIR) {
		u8 newrot = param2 & 3;
		param2 &= ~3;
		param2 |= (newrot + rot) & 3;
	} else if (cpt2 == CPT2_WALLMOUNTED) {
		u8 wmountface = (param2 & 7);
		if (wmountface <= 1)
			return;
			
		Rotation oldrot = wallmounted_to_rot[wmountface - 2];
		param2 &= ~7;
		param2 |= rot_to_wallmounted[(oldrot - rot) & 3];
	}
}

static std::vector<aabb3f> transformNodeBox(const MapNode &n,
		const NodeBox &nodebox, INodeDefManager *nodemgr)
{
	std::vector<aabb3f> boxes;
	if(nodebox.type == NODEBOX_FIXED || nodebox.type == NODEBOX_LEVELED)
	{
		const std::vector<aabb3f> &fixed = nodebox.fixed;
		int facedir = n.getFaceDir(nodemgr);
		u8 axisdir = facedir>>2;
		facedir&=0x03;
		for(std::vector<aabb3f>::const_iterator
				i = fixed.begin();
				i != fixed.end(); i++)
		{
			aabb3f box = *i;

			if (nodebox.type == NODEBOX_LEVELED) {
				box.MaxEdge.Y = -BS/2 + BS*((float)1/LEVELED_MAX) * n.getLevel(nodemgr);
			}

			switch (axisdir)
			{
			case 0:
				if(facedir == 1)
				{
					box.MinEdge.rotateXZBy(-90);
					box.MaxEdge.rotateXZBy(-90);
				}
				else if(facedir == 2)
				{
					box.MinEdge.rotateXZBy(180);
					box.MaxEdge.rotateXZBy(180);
				}
				else if(facedir == 3)
				{
					box.MinEdge.rotateXZBy(90);
					box.MaxEdge.rotateXZBy(90);
				}
				break;
			case 1: // z+
				box.MinEdge.rotateYZBy(90);
				box.MaxEdge.rotateYZBy(90);
				if(facedir == 1)
				{
					box.MinEdge.rotateXYBy(90);
					box.MaxEdge.rotateXYBy(90);
				}
				else if(facedir == 2)
				{
					box.MinEdge.rotateXYBy(180);
					box.MaxEdge.rotateXYBy(180);
				}
				else if(facedir == 3)
				{
					box.MinEdge.rotateXYBy(-90);
					box.MaxEdge.rotateXYBy(-90);
				}
				break;
			case 2: //z-
				box.MinEdge.rotateYZBy(-90);
				box.MaxEdge.rotateYZBy(-90);
				if(facedir == 1)
				{
					box.MinEdge.rotateXYBy(-90);
					box.MaxEdge.rotateXYBy(-90);
				}
				else if(facedir == 2)
				{
					box.MinEdge.rotateXYBy(180);
					box.MaxEdge.rotateXYBy(180);
				}
				else if(facedir == 3)
				{
					box.MinEdge.rotateXYBy(90);
					box.MaxEdge.rotateXYBy(90);
				}
				break;
			case 3:  //x+
				box.MinEdge.rotateXYBy(-90);
				box.MaxEdge.rotateXYBy(-90);
				if(facedir == 1)
				{
					box.MinEdge.rotateYZBy(90);
					box.MaxEdge.rotateYZBy(90);
				}
				else if(facedir == 2)
				{
					box.MinEdge.rotateYZBy(180);
					box.MaxEdge.rotateYZBy(180);
				}
				else if(facedir == 3)
				{
					box.MinEdge.rotateYZBy(-90);
					box.MaxEdge.rotateYZBy(-90);
				}
				break;
			case 4:  //x-
				box.MinEdge.rotateXYBy(90);
				box.MaxEdge.rotateXYBy(90);
				if(facedir == 1)
				{
					box.MinEdge.rotateYZBy(-90);
					box.MaxEdge.rotateYZBy(-90);
				}
				else if(facedir == 2)
				{
					box.MinEdge.rotateYZBy(180);
					box.MaxEdge.rotateYZBy(180);
				}
				else if(facedir == 3)
				{
					box.MinEdge.rotateYZBy(90);
					box.MaxEdge.rotateYZBy(90);
				}
				break;
			case 5:
				box.MinEdge.rotateXYBy(-180);
				box.MaxEdge.rotateXYBy(-180);
				if(facedir == 1)
				{
					box.MinEdge.rotateXZBy(90);
					box.MaxEdge.rotateXZBy(90);
				}
				else if(facedir == 2)
				{
					box.MinEdge.rotateXZBy(180);
					box.MaxEdge.rotateXZBy(180);
				}
				else if(facedir == 3)
				{
					box.MinEdge.rotateXZBy(-90);
					box.MaxEdge.rotateXZBy(-90);
				}
				break;
			default:
				break;
			}
			box.repair();
			boxes.push_back(box);
		}
	}
	else if(nodebox.type == NODEBOX_WALLMOUNTED)
	{
		v3s16 dir = n.getWallMountedDir(nodemgr);

		// top
		if(dir == v3s16(0,1,0))
		{
			boxes.push_back(nodebox.wall_top);
		}
		// bottom
		else if(dir == v3s16(0,-1,0))
		{
			boxes.push_back(nodebox.wall_bottom);
		}
		// side
		else
		{
			v3f vertices[2] =
			{
				nodebox.wall_side.MinEdge,
				nodebox.wall_side.MaxEdge
			};

			for(s32 i=0; i<2; i++)
			{
				if(dir == v3s16(-1,0,0))
					vertices[i].rotateXZBy(0);
				if(dir == v3s16(1,0,0))
					vertices[i].rotateXZBy(180);
				if(dir == v3s16(0,0,-1))
					vertices[i].rotateXZBy(90);
				if(dir == v3s16(0,0,1))
					vertices[i].rotateXZBy(-90);
			}

			aabb3f box = aabb3f(vertices[0]);
			box.addInternalPoint(vertices[1]);
			boxes.push_back(box);
		}
	}
	else // NODEBOX_REGULAR
	{
		boxes.push_back(aabb3f(-BS/2,-BS/2,-BS/2,BS/2,BS/2,BS/2));
	}
	return boxes;
}

std::vector<aabb3f> MapNode::getNodeBoxes(INodeDefManager *nodemgr) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	return transformNodeBox(*this, f.node_box, nodemgr);
}

std::vector<aabb3f> MapNode::getCollisionBoxes(INodeDefManager *nodemgr) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	if (f.collision_box.fixed.empty())
		return transformNodeBox(*this, f.node_box, nodemgr);
	else
		return transformNodeBox(*this, f.collision_box, nodemgr);
}

std::vector<aabb3f> MapNode::getSelectionBoxes(INodeDefManager *nodemgr) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	return transformNodeBox(*this, f.selection_box, nodemgr);
}

u8 MapNode::getMaxLevel(INodeDefManager *nodemgr) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	// todo: after update in all games leave only if (f.param_type_2 ==
	if( f.liquid_type == LIQUID_FLOWING || f.param_type_2 == CPT2_FLOWINGLIQUID)
		return LIQUID_LEVEL_MAX;
	if(f.leveled || f.param_type_2 == CPT2_LEVELED)
		return LEVELED_MAX;
	return 0;
}

u8 MapNode::getLevel(INodeDefManager *nodemgr) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	// todo: after update in all games leave only if (f.param_type_2 ==
	if(f.liquid_type == LIQUID_SOURCE)
		return LIQUID_LEVEL_SOURCE;
	if (f.param_type_2 == CPT2_FLOWINGLIQUID)
		return getParam2() & LIQUID_LEVEL_MASK;
	if(f.liquid_type == LIQUID_FLOWING) // can remove if all param_type_2 setted
		return getParam2() & LIQUID_LEVEL_MASK;
	if(f.leveled || f.param_type_2 == CPT2_LEVELED) {
		 u8 level = getParam2() & LEVELED_MASK;
		 if(level)
			return level;
		 if(f.leveled > LEVELED_MAX)
		 	return LEVELED_MAX;
		 return f.leveled; //default
	}
	return 0;
}

u8 MapNode::setLevel(INodeDefManager *nodemgr, s8 level)
{
	u8 rest = 0;
	if (level < 1) {
		setContent(CONTENT_AIR);
		return 0;
	}
	const ContentFeatures &f = nodemgr->get(*this);
	if (f.param_type_2 == CPT2_FLOWINGLIQUID
		|| f.liquid_type == LIQUID_FLOWING
		|| f.liquid_type == LIQUID_SOURCE) {
		if (level >= LIQUID_LEVEL_SOURCE) {
			rest = level - LIQUID_LEVEL_SOURCE;
			setContent(nodemgr->getId(f.liquid_alternative_source));
		} else {
			setContent(nodemgr->getId(f.liquid_alternative_flowing));
			setParam2(level & LIQUID_LEVEL_MASK);
		}
	} else if (f.leveled || f.param_type_2 == CPT2_LEVELED) {
		if (level > LEVELED_MAX) {
			rest = level - LEVELED_MAX;
			level = LEVELED_MAX;
		}
		setParam2(level & LEVELED_MASK);
	}
	return rest;
}

u8 MapNode::addLevel(INodeDefManager *nodemgr, s8 add)
{
	s8 level = getLevel(nodemgr);
	if (add == 0) level = 1;
	level += add;
	return setLevel(nodemgr, level);
}

void MapNode::freezeMelt(INodeDefManager *ndef) {
	u8 level_was_max = this->getMaxLevel(ndef);
	u8 level_was = this->getLevel(ndef);
	this->setContent(ndef->getId(ndef->get(*this).freezemelt));
	u8 level_now_max = this->getMaxLevel(ndef);
	if (level_was_max && level_was_max != level_now_max) {
		u8 want = (float)level_now_max / level_was_max * level_was;
		if (!want)
			want = 1;
		if (want != level_was)
			this->setLevel(ndef, want);
		//errorstream<<"was="<<(int)level_was<<"/"<<(int)level_was_max<<" nowm="<<(int)want<<"/"<<(int)level_now_max<< " => "<<(int)this->getLevel(ndef)<< std::endl;
	}
	if (this->getMaxLevel(ndef) && !this->getLevel(ndef))
		this->addLevel(ndef);
}

u32 MapNode::serializedLength(u8 version)
{
	if(!ser_ver_supported(version))
		throw VersionMismatchException("ERROR: MapNode format not supported");
		
	if(version == 0)
		return 1;
	else if(version <= 9)
		return 2;
	else if(version <= 23)
		return 3;
	else
		return 4;
}
void MapNode::serialize(u8 *dest, u8 version)
{
	if(!ser_ver_supported(version))
		throw VersionMismatchException("ERROR: MapNode format not supported");
	
	// Can't do this anymore; we have 16-bit dynamically allocated node IDs
	// in memory; conversion just won't work in this direction.
	if(version < 24)
		throw SerializationError("MapNode::serialize: serialization to "
				"version < 24 not possible");
		
	writeU16(dest+0, param0);
	writeU8(dest+2, param1);
	writeU8(dest+3, param2);
}
void MapNode::deSerialize(u8 *source, u8 version)
{
	if(!ser_ver_supported(version))
		throw VersionMismatchException("ERROR: MapNode format not supported");
		
	if(version <= 21)
	{
		deSerialize_pre22(source, version);
		return;
	}

	if(version >= 24){
		param0 = readU16(source+0);
		param1 = readU8(source+2);
		param2 = readU8(source+3);
	}else{
		param0 = readU8(source+0);
		param1 = readU8(source+1);
		param2 = readU8(source+2);
		if(param0 > 0x7F){
			param0 |= ((param2&0xF0)<<4);
			param2 &= 0x0F;
		}
	}
}
void MapNode::serializeBulk(std::ostream &os, int version,
		const MapNode *nodes, u32 nodecount,
		u8 content_width, u8 params_width, bool compressed)
{
	if(!ser_ver_supported(version))
		throw VersionMismatchException("ERROR: MapNode format not supported");

	assert(content_width == 2);
	assert(params_width == 2);

	// Can't do this anymore; we have 16-bit dynamically allocated node IDs
	// in memory; conversion just won't work in this direction.
	if(version < 24)
		throw SerializationError("MapNode::serializeBulk: serialization to "
				"version < 24 not possible");

	SharedBuffer<u8> databuf(nodecount * (content_width + params_width));

	// Serialize content
	for(u32 i=0; i<nodecount; i++)
		writeU16(&databuf[i*2], nodes[i].param0);

	// Serialize param1
	u32 start1 = content_width * nodecount;
	for(u32 i=0; i<nodecount; i++)
		writeU8(&databuf[start1 + i], nodes[i].param1);

	// Serialize param2
	u32 start2 = (content_width + 1) * nodecount;
	for(u32 i=0; i<nodecount; i++)
		writeU8(&databuf[start2 + i], nodes[i].param2);

	/*
		Compress data to output stream
	*/

	if(compressed)
	{
		compressZlib(databuf, os);
	}
	else
	{
		os.write((const char*) &databuf[0], databuf.getSize());
	}
}

// Deserialize bulk node data
void MapNode::deSerializeBulk(std::istream &is, int version,
		MapNode *nodes, u32 nodecount,
		u8 content_width, u8 params_width, bool compressed)
{
	if(!ser_ver_supported(version))
		throw VersionMismatchException("ERROR: MapNode format not supported");

	assert(version >= 22);
	assert(content_width == 1 || content_width == 2);
	assert(params_width == 2);

	// Uncompress or read data
	u32 len = nodecount * (content_width + params_width);
	SharedBuffer<u8> databuf(len);
	if(compressed)
	{
		std::ostringstream os(std::ios_base::binary);
		decompressZlib(is, os);
		std::string s = os.str();
		if(s.size() != len)
			throw SerializationError("deSerializeBulkNodes: "
					"decompress resulted in invalid size");
		memcpy(&databuf[0], s.c_str(), len);
	}
	else
	{
		is.read((char*) &databuf[0], len);
		if(is.eof() || is.fail())
			throw SerializationError("deSerializeBulkNodes: "
					"failed to read bulk node data");
	}

	// Deserialize content
	if(content_width == 1)
	{
		for(u32 i=0; i<nodecount; i++)
			nodes[i].param0 = readU8(&databuf[i]);
	}
	else if(content_width == 2)
	{
		for(u32 i=0; i<nodecount; i++)
			nodes[i].param0 = readU16(&databuf[i*2]);
	}

	// Deserialize param1
	u32 start1 = content_width * nodecount;
	for(u32 i=0; i<nodecount; i++)
		nodes[i].param1 = readU8(&databuf[start1 + i]);

	// Deserialize param2
	u32 start2 = (content_width + 1) * nodecount;
	if(content_width == 1)
	{
		for(u32 i=0; i<nodecount; i++) {
			nodes[i].param2 = readU8(&databuf[start2 + i]);
			if(nodes[i].param0 > 0x7F){
				nodes[i].param0 <<= 4;
				nodes[i].param0 |= (nodes[i].param2&0xF0)>>4;
				nodes[i].param2 &= 0x0F;
			}
		}
	}
	else if(content_width == 2)
	{
		for(u32 i=0; i<nodecount; i++)
			nodes[i].param2 = readU8(&databuf[start2 + i]);
	}
}

/*
	Legacy serialization
*/
void MapNode::deSerialize_pre22(u8 *source, u8 version)
{
	if(version <= 1)
	{
		param0 = source[0];
	}
	else if(version <= 9)
	{
		param0 = source[0];
		param1 = source[1];
	}
	else
	{
		param0 = source[0];
		param1 = source[1];
		param2 = source[2];
		if(param0 > 0x7f){
			param0 <<= 4;
			param0 |= (param2&0xf0)>>4;
			param2 &= 0x0f;
		}
	}
	
	// Convert special values from old version to new
	if(version <= 19)
	{
		// In these versions, CONTENT_IGNORE and CONTENT_AIR
		// are 255 and 254
		// Version 19 is fucked up with sometimes the old values and sometimes not
		if(param0 == 255)
			param0 = CONTENT_IGNORE;
		else if(param0 == 254)
			param0 = CONTENT_AIR;
	}

	// Translate to our known version
	*this = mapnode_translate_to_internal(*this, version);
}
2961'>2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/


#include <cstdlib>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <limits>
#include "guiFormSpecMenu.h"
#include "guiTable.h"
#include "constants.h"
#include "gamedef.h"
#include "keycode.h"
#include "strfnd.h"
#include <IGUICheckBox.h>
#include <IGUIEditBox.h>
#include <IGUIButton.h>
#include <IGUIStaticText.h>
#include <IGUIFont.h>
#include <IGUITabControl.h>
#include <IGUIComboBox.h>
#include "log.h"
#include "client/tile.h" // ITextureSource
#include "hud.h" // drawItemStack
#include "filesys.h"
#include "gettime.h"
#include "gettext.h"
#include "scripting_game.h"
#include "porting.h"
#include "main.h"
#include "settings.h"
#include "client.h"
#include "fontengine.h"
#include "util/hex.h"
#include "util/numeric.h"
#include "util/string.h" // for parseColorString()

#define MY_CHECKPOS(a,b)													\
	if (v_pos.size() != 2) {												\
		errorstream<< "Invalid pos for element " << a << "specified: \""	\
			<< parts[b] << "\"" << std::endl;								\
			return;															\
	}

#define MY_CHECKGEOM(a,b)													\
	if (v_geom.size() != 2) {												\
		errorstream<< "Invalid pos for element " << a << "specified: \""	\
			<< parts[b] << "\"" << std::endl;								\
			return;															\
	}
/*
	GUIFormSpecMenu
*/
static unsigned int font_line_height(gui::IGUIFont *font)
{
	return font->getDimension(L"Ay").Height + font->getKerningHeight();
}

GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev,
		gui::IGUIElement* parent, s32 id, IMenuManager *menumgr,
		InventoryManager *invmgr, IGameDef *gamedef,
		ISimpleTextureSource *tsrc, IFormSource* fsrc, TextDest* tdst,
		Client* client) :
	GUIModalMenu(dev->getGUIEnvironment(), parent, id, menumgr),
	m_device(dev),
	m_invmgr(invmgr),
	m_gamedef(gamedef),
	m_tsrc(tsrc),
	m_client(client),
	m_selected_item(NULL),
	m_selected_amount(0),
	m_selected_dragging(false),
	m_tooltip_element(NULL),
	m_hovered_time(0),
	m_old_tooltip_id(-1),
	m_rmouse_auto_place(false),
	m_allowclose(true),
	m_lock(false),
	m_form_src(fsrc),
	m_text_dst(tdst),
	m_formspec_version(0),
	m_focused_element(L""),
	m_font(NULL)
#ifdef __ANDROID__
	,m_JavaDialogFieldName(L"")
#endif
{
	current_keys_pending.key_down = false;
	current_keys_pending.key_up = false;
	current_keys_pending.key_enter = false;
	current_keys_pending.key_escape = false;

	m_doubleclickdetect[0].time = 0;
	m_doubleclickdetect[1].time = 0;

	m_doubleclickdetect[0].pos = v2s32(0, 0);
	m_doubleclickdetect[1].pos = v2s32(0, 0);

	m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay");
}

GUIFormSpecMenu::~GUIFormSpecMenu()
{
	removeChildren();

	for (u32 i = 0; i < m_tables.size(); ++i) {
		GUITable *table = m_tables[i].second;
		table->drop();
	}

	delete m_selected_item;

	if (m_form_src != NULL) {
		delete m_form_src;
	}
	if (m_text_dst != NULL) {
		delete m_text_dst;
	}
}

void GUIFormSpecMenu::removeChildren()
{
	const core::list<gui::IGUIElement*> &children = getChildren();

	while(!children.empty()) {
		(*children.getLast())->remove();
	}

	if(m_tooltip_element) {
		m_tooltip_element->remove();
		m_tooltip_element->drop();
		m_tooltip_element = NULL;
	}

}

void GUIFormSpecMenu::setInitialFocus()
{
	// Set initial focus according to following order of precedence:
	// 1. first empty editbox
	// 2. first editbox
	// 3. first table
	// 4. last button
	// 5. first focusable (not statictext, not tabheader)
	// 6. first child element

	core::list<gui::IGUIElement*> children = getChildren();

	// in case "children" contains any NULL elements, remove them
	for (core::list<gui::IGUIElement*>::Iterator it = children.begin();
			it != children.end();) {
		if (*it)
			++it;
		else
			it = children.erase(it);
	}

	// 1. first empty editbox
	for (core::list<gui::IGUIElement*>::Iterator it = children.begin();
			it != children.end(); ++it) {
		if ((*it)->getType() == gui::EGUIET_EDIT_BOX
				&& (*it)->getText()[0] == 0) {
			Environment->setFocus(*it);
			return;
		}
	}

	// 2. first editbox
	for (core::list<gui::IGUIElement*>::Iterator it = children.begin();
			it != children.end(); ++it) {
		if ((*it)->getType() == gui::EGUIET_EDIT_BOX) {
			Environment->setFocus(*it);
			return;
		}
	}

	// 3. first table
	for (core::list<gui::IGUIElement*>::Iterator it = children.begin();
			it != children.end(); ++it) {
		if ((*it)->getTypeName() == std::string("GUITable")) {
			Environment->setFocus(*it);
			return;
		}
	}

	// 4. last button
	for (core::list<gui::IGUIElement*>::Iterator it = children.getLast();
			it != children.end(); --it) {
		if ((*it)->getType() == gui::EGUIET_BUTTON) {
			Environment->setFocus(*it);
			return;
		}
	}

	// 5. first focusable (not statictext, not tabheader)
	for (core::list<gui::IGUIElement*>::Iterator it = children.begin();
			it != children.end(); ++it) {
		if ((*it)->getType() != gui::EGUIET_STATIC_TEXT &&
				(*it)->getType() != gui::EGUIET_TAB_CONTROL) {
			Environment->setFocus(*it);
			return;
		}
	}

	// 6. first child element
	if (children.empty())
		Environment->setFocus(this);
	else
		Environment->setFocus(*(children.begin()));
}

GUITable* GUIFormSpecMenu::getTable(std::wstring tablename)
{
	for (u32 i = 0; i < m_tables.size(); ++i) {
		if (tablename == m_tables[i].first.fname)
			return m_tables[i].second;
	}
	return 0;
}

std::vector<std::string> split(const std::string &s, char delim) {
	std::vector<std::string> tokens;

	std::string current = "";
	bool last_was_escape = false;
	for(unsigned int i=0; i < s.size(); i++) {
		if (last_was_escape) {
			current += '\\';
			current += s.c_str()[i];
			last_was_escape = false;
		}
		else {
			if (s.c_str()[i] == delim) {
				tokens.push_back(current);
				current = "";
				last_was_escape = false;
			}
			else if (s.c_str()[i] == '\\'){
				last_was_escape = true;
			}
			else {
				current += s.c_str()[i];
				last_was_escape = false;
			}
		}
	}
	//push last element
	tokens.push_back(current);

	return tokens;
}

void GUIFormSpecMenu::parseSize(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,',');

	if (((parts.size() == 2) || parts.size() == 3) ||
		((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		if (parts[1].find(';') != std::string::npos)
			parts[1] = parts[1].substr(0,parts[1].find(';'));

		data->invsize.X = MYMAX(0, stof(parts[0]));
		data->invsize.Y = MYMAX(0, stof(parts[1]));

		lockSize(false);
		if (parts.size() == 3) {
			if (parts[2] == "true") {
				lockSize(true,v2u32(800,600));
			}
		}

		data->explicit_size = true;
		return;
	}
	errorstream<< "Invalid size element (" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseList(parserData* data,std::string element)
{
	if (m_gamedef == 0) {
		errorstream<<"WARNING: invalid use of 'list' with m_gamedef==0"<<std::endl;
		return;
	}

	std::vector<std::string> parts = split(element,';');

	if (((parts.size() == 4) || (parts.size() == 5)) ||
		((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::string location = parts[0];
		std::string listname = parts[1];
		std::vector<std::string> v_pos  = split(parts[2],',');
		std::vector<std::string> v_geom = split(parts[3],',');
		std::string startindex = "";
		if (parts.size() == 5)
			startindex = parts[4];

		MY_CHECKPOS("list",2);
		MY_CHECKGEOM("list",3);

		InventoryLocation loc;

		if(location == "context" || location == "current_name")
			loc = m_current_inventory_location;
		else
			loc.deSerialize(location);

		v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
		pos.X += stof(v_pos[0]) * (float)spacing.X;
		pos.Y += stof(v_pos[1]) * (float)spacing.Y;

		v2s32 geom;
		geom.X = stoi(v_geom[0]);
		geom.Y = stoi(v_geom[1]);

		s32 start_i = 0;
		if(startindex != "")
			start_i = stoi(startindex);

		if (geom.X < 0 || geom.Y < 0 || start_i < 0) {
			errorstream<< "Invalid list element: '" << element << "'"  << std::endl;
			return;
		}

		if(!data->explicit_size)
			errorstream<<"WARNING: invalid use of list without a size[] element"<<std::endl;
		m_inventorylists.push_back(ListDrawSpec(loc, listname, pos, geom, start_i));
		return;
	}
	errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseCheckbox(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if (((parts.size() >= 3) && (parts.size() <= 4)) ||
		((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::string name = parts[1];
		std::string label = parts[2];
		std::string selected = "";

		if (parts.size() >= 4)
			selected = parts[3];

		MY_CHECKPOS("checkbox",0);

		v2s32 pos = padding;
		pos.X += stof(v_pos[0]) * (float) spacing.X;
		pos.Y += stof(v_pos[1]) * (float) spacing.Y;

		bool fselected = false;

		if (selected == "true")
			fselected = true;

		std::wstring wlabel = narrow_to_wide(label);

		core::rect<s32> rect = core::rect<s32>(
				pos.X, pos.Y + ((imgsize.Y/2) - m_btn_height),
				pos.X + m_font->getDimension(wlabel.c_str()).Width + 25, // text size + size of checkbox
				pos.Y + ((imgsize.Y/2) + m_btn_height));

		FieldSpec spec(
				narrow_to_wide(name),
				wlabel, //Needed for displaying text on MSVC
				wlabel,
				258+m_fields.size()
			);

		spec.ftype = f_CheckBox;

		gui::IGUICheckBox* e = Environment->addCheckBox(fselected, rect, this,
					spec.fid, spec.flabel.c_str());

		if (spec.fname == data->focused_fieldname) {
			Environment->setFocus(e);
		}

		m_checkboxes.push_back(std::pair<FieldSpec,gui::IGUICheckBox*>(spec,e));
		m_fields.push_back(spec);
		return;
	}
	errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseScrollBar(parserData* data, std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if (parts.size() >= 5) {
		std::vector<std::string> v_pos = split(parts[0],',');
		std::vector<std::string> v_dim = split(parts[1],',');
		std::string name = parts[2];
		std::string value = parts[4];

		MY_CHECKPOS("scrollbar",0);

		v2s32 pos = padding;
		pos.X += stof(v_pos[0]) * (float) spacing.X;
		pos.Y += stof(v_pos[1]) * (float) spacing.Y;

		if (v_dim.size() != 2) {
			errorstream<< "Invalid size for element " << "scrollbar"
				<< "specified: \"" << parts[1] << "\"" << std::endl;
			return;
		}

		v2s32 dim;
		dim.X = stof(v_dim[0]) * (float) spacing.X;
		dim.Y = stof(v_dim[1]) * (float) spacing.Y;

		core::rect<s32> rect =
				core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y);

		FieldSpec spec(
				narrow_to_wide(name),
				L"",
				L"",
				258+m_fields.size()
			);

		bool is_horizontal = true;

		if (parts[2] == "vertical")
			is_horizontal = false;

		spec.ftype = f_ScrollBar;
		spec.send  = true;
		gui::IGUIScrollBar* e =
				Environment->addScrollBar(is_horizontal,rect,this,spec.fid);

		e->setMax(1000);
		e->setMin(0);
		e->setPos(stoi(parts[4]));
		e->setSmallStep(10);
		e->setLargeStep(100);

		m_scrollbars.push_back(std::pair<FieldSpec,gui::IGUIScrollBar*>(spec,e));
		m_fields.push_back(spec);
		return;
	}
	errorstream<< "Invalid scrollbar element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseImage(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if ((parts.size() == 3) ||
		((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::vector<std::string> v_geom = split(parts[1],',');
		std::string name = unescape_string(parts[2]);

		MY_CHECKPOS("image",0);
		MY_CHECKGEOM("image",1);

		v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
		pos.X += stof(v_pos[0]) * (float) spacing.X;
		pos.Y += stof(v_pos[1]) * (float) spacing.Y;

		v2s32 geom;
		geom.X = stof(v_geom[0]) * (float)imgsize.X;
		geom.Y = stof(v_geom[1]) * (float)imgsize.Y;

		if(!data->explicit_size)
			errorstream<<"WARNING: invalid use of image without a size[] element"<<std::endl;
		m_images.push_back(ImageDrawSpec(name, pos, geom));
		return;
	}

	if (parts.size() == 2) {
		std::vector<std::string> v_pos = split(parts[0],',');
		std::string name = unescape_string(parts[1]);

		MY_CHECKPOS("image",0);

		v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
		pos.X += stof(v_pos[0]) * (float) spacing.X;
		pos.Y += stof(v_pos[1]) * (float) spacing.Y;

		if(!data->explicit_size)
			errorstream<<"WARNING: invalid use of image without a size[] element"<<std::endl;
		m_images.push_back(ImageDrawSpec(name, pos));
		return;
	}
	errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseItemImage(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if ((parts.size() == 3) ||
		((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::vector<std::string> v_geom = split(parts[1],',');
		std::string name = parts[2];

		MY_CHECKPOS("itemimage",0);
		MY_CHECKGEOM("itemimage",1);

		v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
		pos.X += stof(v_pos[0]) * (float) spacing.X;
		pos.Y += stof(v_pos[1]) * (float) spacing.Y;

		v2s32 geom;
		geom.X = stof(v_geom[0]) * (float)imgsize.X;
		geom.Y = stof(v_geom[1]) * (float)imgsize.Y;

		if(!data->explicit_size)
			errorstream<<"WARNING: invalid use of item_image without a size[] element"<<std::endl;
		m_itemimages.push_back(ImageDrawSpec(name, pos, geom));
		return;
	}
	errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseButton(parserData* data,std::string element,
		std::string type)
{
	std::vector<std::string> parts = split(element,';');

	if ((parts.size() == 4) ||
		((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::vector<std::string> v_geom = split(parts[1],',');
		std::string name = parts[2];
		std::string label = parts[3];

		MY_CHECKPOS("button",0);
		MY_CHECKGEOM("button",1);

		v2s32 pos = padding;
		pos.X += stof(v_pos[0]) * (float)spacing.X;
		pos.Y += stof(v_pos[1]) * (float)spacing.Y;

		v2s32 geom;
		geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
		pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;

		core::rect<s32> rect =
				core::rect<s32>(pos.X, pos.Y - m_btn_height,
						pos.X + geom.X, pos.Y + m_btn_height);

		if(!data->explicit_size)
			errorstream<<"WARNING: invalid use of button without a size[] element"<<std::endl;

		label = unescape_string(label);

		std::wstring wlabel = narrow_to_wide(label);

		FieldSpec spec(
			narrow_to_wide(name),
			wlabel,
			L"",
			258+m_fields.size()
		);
		spec.ftype = f_Button;
		if(type == "button_exit")
			spec.is_exit = true;
		gui::IGUIButton* e = Environment->addButton(rect, this, spec.fid,
				spec.flabel.c_str());

		if (spec.fname == data->focused_fieldname) {
			Environment->setFocus(e);
		}

		m_fields.push_back(spec);
		return;
	}
	errorstream<< "Invalid button element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseBackground(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if (((parts.size() == 3) || (parts.size() == 4)) ||
		((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::vector<std::string> v_geom = split(parts[1],',');
		std::string name = unescape_string(parts[2]);

		MY_CHECKPOS("background",0);
		MY_CHECKGEOM("background",1);

		v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
		pos.X += stof(v_pos[0]) * (float)spacing.X - ((float)spacing.X-(float)imgsize.X)/2;
		pos.Y += stof(v_pos[1]) * (float)spacing.Y - ((float)spacing.Y-(float)imgsize.Y)/2;

		v2s32 geom;
		geom.X = stof(v_geom[0]) * (float)spacing.X;
		geom.Y = stof(v_geom[1]) * (float)spacing.Y;

		if (parts.size() == 4) {
			m_clipbackground = is_yes(parts[3]);
			if (m_clipbackground) {
				pos.X = stoi(v_pos[0]); //acts as offset
				pos.Y = stoi(v_pos[1]); //acts as offset
			}
		}

		if(!data->explicit_size)
			errorstream<<"WARNING: invalid use of background without a size[] element"<<std::endl;
		m_backgrounds.push_back(ImageDrawSpec(name, pos, geom));
		return;
	}
	errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseTableOptions(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	data->table_options.clear();
	for (size_t i = 0; i < parts.size(); ++i) {
		// Parse table option
		std::string opt = unescape_string(parts[i]);
		data->table_options.push_back(GUITable::splitOption(opt));
	}
}

void GUIFormSpecMenu::parseTableColumns(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	data->table_columns.clear();
	for (size_t i = 0; i < parts.size(); ++i) {
		std::vector<std::string> col_parts = split(parts[i],',');
		GUITable::TableColumn column;
		// Parse column type
		if (!col_parts.empty())
			column.type = col_parts[0];
		// Parse column options
		for (size_t j = 1; j < col_parts.size(); ++j) {
			std::string opt = unescape_string(col_parts[j]);
			column.options.push_back(GUITable::splitOption(opt));
		}
		data->table_columns.push_back(column);
	}
}

void GUIFormSpecMenu::parseTable(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if (((parts.size() == 4) || (parts.size() == 5)) ||
		((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::vector<std::string> v_geom = split(parts[1],',');
		std::string name = parts[2];
		std::vector<std::string> items = split(parts[3],',');
		std::string str_initial_selection = "";
		std::string str_transparent = "false";

		if (parts.size() >= 5)
			str_initial_selection = parts[4];

		MY_CHECKPOS("table",0);
		MY_CHECKGEOM("table",1);

		v2s32 pos = padding;
		pos.X += stof(v_pos[0]) * (float)spacing.X;
		pos.Y += stof(v_pos[1]) * (float)spacing.Y;

		v2s32 geom;
		geom.X = stof(v_geom[0]) * (float)spacing.X;
		geom.Y = stof(v_geom[1]) * (float)spacing.Y;


		core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);

		std::wstring fname_w = narrow_to_wide(name);

		FieldSpec spec(
			fname_w,
			L"",
			L"",
			258+m_fields.size()
		);

		spec.ftype = f_Table;

		for (unsigned int i = 0; i < items.size(); ++i) {
			items[i] = unescape_string(items[i]);
		}

		//now really show table
		GUITable *e = new GUITable(Environment, this, spec.fid, rect,
				m_tsrc);

		if (spec.fname == data->focused_fieldname) {
			Environment->setFocus(e);
		}

		e->setTable(data->table_options, data->table_columns, items);

		if (data->table_dyndata.find(fname_w) != data->table_dyndata.end()) {
			e->setDynamicData(data->table_dyndata[fname_w]);
		}

		if ((str_initial_selection != "") &&
				(str_initial_selection != "0"))
			e->setSelected(stoi(str_initial_selection.c_str()));

		m_tables.push_back(std::pair<FieldSpec,GUITable*>(spec, e));
		m_fields.push_back(spec);
		return;
	}
	errorstream<< "Invalid table element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseTextList(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if (((parts.size() == 4) || (parts.size() == 5) || (parts.size() == 6)) ||
		((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::vector<std::string> v_geom = split(parts[1],',');
		std::string name = parts[2];
		std::vector<std::string> items = split(parts[3],',');
		std::string str_initial_selection = "";
		std::string str_transparent = "false";

		if (parts.size() >= 5)
			str_initial_selection = parts[4];

		if (parts.size() >= 6)
			str_transparent = parts[5];

		MY_CHECKPOS("textlist",0);
		MY_CHECKGEOM("textlist",1);

		v2s32 pos = padding;
		pos.X += stof(v_pos[0]) * (float)spacing.X;
		pos.Y += stof(v_pos[1]) * (float)spacing.Y;

		v2s32 geom;
		geom.X = stof(v_geom[0]) * (float)spacing.X;
		geom.Y = stof(v_geom[1]) * (float)spacing.Y;


		core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);

		std::wstring fname_w = narrow_to_wide(name);

		FieldSpec spec(
			fname_w,
			L"",
			L"",
			258+m_fields.size()
		);

		spec.ftype = f_Table;

		for (unsigned int i = 0; i < items.size(); ++i) {
			items[i] = unescape_string(items[i]);
		}

		//now really show list
		GUITable *e = new GUITable(Environment, this, spec.fid, rect,
				m_tsrc);

		if (spec.fname == data->focused_fieldname) {
			Environment->setFocus(e);
		}

		e->setTextList(items, is_yes(str_transparent));

		if (data->table_dyndata.find(fname_w) != data->table_dyndata.end()) {
			e->setDynamicData(data->table_dyndata[fname_w]);
		}

		if ((str_initial_selection != "") &&
				(str_initial_selection != "0"))
			e->setSelected(stoi(str_initial_selection.c_str()));

		m_tables.push_back(std::pair<FieldSpec,GUITable*>(spec, e));
		m_fields.push_back(spec);
		return;
	}
	errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'"  << std::endl;
}


void GUIFormSpecMenu::parseDropDown(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if ((parts.size() == 5) ||
		((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::string name = parts[2];
		std::vector<std::string> items = split(parts[3],',');
		std::string str_initial_selection = "";
		str_initial_selection = parts[4];

		MY_CHECKPOS("dropdown",0);

		v2s32 pos = padding;
		pos.X += stof(v_pos[0]) * (float)spacing.X;
		pos.Y += stof(v_pos[1]) * (float)spacing.Y;

		s32 width = stof(parts[1]) * (float)spacing.Y;

		core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y,
				pos.X + width, pos.Y + (m_btn_height * 2));

		std::wstring fname_w = narrow_to_wide(name);

		FieldSpec spec(
			fname_w,
			L"",
			L"",
			258+m_fields.size()
		);

		spec.ftype = f_DropDown;
		spec.send = true;

		//now really show list
		gui::IGUIComboBox *e = Environment->addComboBox(rect, this,spec.fid);

		if (spec.fname == data->focused_fieldname) {
			Environment->setFocus(e);
		}

		for (unsigned int i=0; i < items.size(); i++) {
			e->addItem(narrow_to_wide(items[i]).c_str());
		}

		if (str_initial_selection != "")
			e->setSelected(stoi(str_initial_selection.c_str())-1);

		m_fields.push_back(spec);
		return;
	}
	errorstream << "Invalid dropdown element(" << parts.size() << "): '"
				<< element << "'"  << std::endl;
}

void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if ((parts.size() == 4) ||
		((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::vector<std::string> v_geom = split(parts[1],',');
		std::string name = parts[2];
		std::string label = parts[3];

		MY_CHECKPOS("pwdfield",0);
		MY_CHECKGEOM("pwdfield",1);

		v2s32 pos;
		pos.X += stof(v_pos[0]) * (float)spacing.X;
		pos.Y += stof(v_pos[1]) * (float)spacing.Y;

		v2s32 geom;
		geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);

		pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
		pos.Y -= m_btn_height;
		geom.Y = m_btn_height*2;

		core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);

		label = unescape_string(label);

		std::wstring wlabel = narrow_to_wide(label);

		FieldSpec spec(
			narrow_to_wide(name),
			wlabel,
			L"",
			258+m_fields.size()
			);

		spec.send = true;
		gui::IGUIEditBox * e = Environment->addEditBox(0, rect, true, this, spec.fid);

		if (spec.fname == data->focused_fieldname) {
			Environment->setFocus(e);
		}

		if (label.length() >= 1)
		{
			int font_height = g_fontengine->getTextHeight();
			rect.UpperLeftCorner.Y -= font_height;
			rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
			Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0);
		}

		e->setPasswordBox(true,L'*');

		irr::SEvent evt;
		evt.EventType            = EET_KEY_INPUT_EVENT;
		evt.KeyInput.Key         = KEY_END;
		evt.KeyInput.Char        = 0;
		evt.KeyInput.Control     = 0;
		evt.KeyInput.Shift       = 0;
		evt.KeyInput.PressedDown = true;
		e->OnEvent(evt);
		m_fields.push_back(spec);
		return;
	}
	errorstream<< "Invalid pwdfield element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseSimpleField(parserData* data,
		std::vector<std::string> &parts)
{
	std::string name = parts[0];
	std::string label = parts[1];
	std::string default_val = parts[2];

	core::rect<s32> rect;

	if(data->explicit_size)
		errorstream<<"WARNING: invalid use of unpositioned \"field\" in inventory"<<std::endl;

	v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
	pos.Y = ((m_fields.size()+2)*60);
	v2s32 size = DesiredRect.getSize();

	rect = core::rect<s32>(size.X / 2 - 150, pos.Y,
			(size.X / 2 - 150) + 300, pos.Y + (m_btn_height*2));


	if(m_form_src)
		default_val = m_form_src->resolveText(default_val);

	default_val = unescape_string(default_val);
	label = unescape_string(label);

	std::wstring wlabel = narrow_to_wide(label);

	FieldSpec spec(
		narrow_to_wide(name),
		wlabel,
		narrow_to_wide(default_val),
		258+m_fields.size()
	);

	if (name == "")
	{
		// spec field id to 0, this stops submit searching for a value that isn't there
		Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid);
	}
	else
	{
		spec.send = true;
		gui::IGUIEditBox *e =
			Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid);

		if (spec.fname == data->focused_fieldname) {
			Environment->setFocus(e);
		}

		irr::SEvent evt;
		evt.EventType            = EET_KEY_INPUT_EVENT;
		evt.KeyInput.Key         = KEY_END;
		evt.KeyInput.Char        = 0;
		evt.KeyInput.Control     = 0;
		evt.KeyInput.Shift       = 0;
		evt.KeyInput.PressedDown = true;
		e->OnEvent(evt);

		if (label.length() >= 1)
		{
			int font_height = g_fontengine->getTextHeight();
			rect.UpperLeftCorner.Y -= font_height;
			rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
			Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0);
		}
	}

	m_fields.push_back(spec);
}

void GUIFormSpecMenu::parseTextArea(parserData* data,
		std::vector<std::string>& parts,std::string type)
{

	std::vector<std::string> v_pos = split(parts[0],',');
	std::vector<std::string> v_geom = split(parts[1],',');
	std::string name = parts[2];
	std::string label = parts[3];
	std::string default_val = parts[4];

	MY_CHECKPOS(type,0);
	MY_CHECKGEOM(type,1);

	v2s32 pos;
	pos.X = stof(v_pos[0]) * (float) spacing.X;
	pos.Y = stof(v_pos[1]) * (float) spacing.Y;

	v2s32 geom;

	geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);

	if (type == "textarea")
	{
		geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y);
		pos.Y += m_btn_height;
	}
	else
	{
		pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
		pos.Y -= m_btn_height;
		geom.Y = m_btn_height*2;
	}

	core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);

	if(!data->explicit_size)
		errorstream<<"WARNING: invalid use of positioned "<<type<<" without a size[] element"<<std::endl;

	if(m_form_src)
		default_val = m_form_src->resolveText(default_val);


	default_val = unescape_string(default_val);
	label = unescape_string(label);

	std::wstring wlabel = narrow_to_wide(label);

	FieldSpec spec(
		narrow_to_wide(name),
		wlabel,
		narrow_to_wide(default_val),
		258+m_fields.size()
	);

	if (name == "")
	{
		// spec field id to 0, this stops submit searching for a value that isn't there
		Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid);
	}
	else
	{
		spec.send = true;
		gui::IGUIEditBox *e =
			Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid);

		if (spec.fname == data->focused_fieldname) {
			Environment->setFocus(e);
		}

		if (type == "textarea")
		{
			e->setMultiLine(true);
			e->setWordWrap(true);
			e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT);
		} else {
			irr::SEvent evt;
			evt.EventType            = EET_KEY_INPUT_EVENT;
			evt.KeyInput.Key         = KEY_END;
			evt.KeyInput.Char        = 0;
			evt.KeyInput.Control     = 0;
			evt.KeyInput.Shift       = 0;
			evt.KeyInput.PressedDown = true;
			e->OnEvent(evt);
		}

		if (label.length() >= 1)
		{
			int font_height = g_fontengine->getTextHeight();
			rect.UpperLeftCorner.Y -= font_height;
			rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
			Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0);
		}
	}
	m_fields.push_back(spec);
}

void GUIFormSpecMenu::parseField(parserData* data,std::string element,
		std::string type)
{
	std::vector<std::string> parts = split(element,';');

	if (parts.size() == 3 || parts.size() == 4) {
		parseSimpleField(data,parts);
		return;
	}

	if ((parts.size() == 5) ||
		((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		parseTextArea(data,parts,type);
		return;
	}
	errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseLabel(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if ((parts.size() == 2) ||
		((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::string text = parts[1];

		MY_CHECKPOS("label",0);

		v2s32 pos = padding;
		pos.X += stof(v_pos[0]) * (float)spacing.X;
		pos.Y += (stof(v_pos[1]) + 7.0/30.0) * (float)spacing.Y;

		if(!data->explicit_size)
			errorstream<<"WARNING: invalid use of label without a size[] element"<<std::endl;

		text = unescape_string(text);
		std::vector<std::string> lines = split(text, '\n');

		for (unsigned int i = 0; i != lines.size(); i++) {
			// Lines are spaced at the nominal distance of
			// 2/5 inventory slot, even if the font doesn't
			// quite match that.  This provides consistent
			// form layout, at the expense of sometimes
			// having sub-optimal spacing for the font.
			// We multiply by 2 and then divide by 5, rather
			// than multiply by 0.4, to get exact results
			// in the integer cases: 0.4 is not exactly
			// representable in binary floating point.
			s32 posy = pos.Y + ((float)i) * spacing.Y * 2.0 / 5.0;
			std::wstring wlabel = narrow_to_wide(lines[i]);
			core::rect<s32> rect = core::rect<s32>(
				pos.X, posy - m_btn_height,
				pos.X + m_font->getDimension(wlabel.c_str()).Width,
				posy + m_btn_height);
			FieldSpec spec(
				L"",
				wlabel,
				L"",
				258+m_fields.size()
			);
			gui::IGUIStaticText *e =
				Environment->addStaticText(spec.flabel.c_str(),
					rect, false, false, this, spec.fid);
			e->setTextAlignment(gui::EGUIA_UPPERLEFT,
						gui::EGUIA_CENTER);
			m_fields.push_back(spec);
		}

		return;
	}
	errorstream<< "Invalid label element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseVertLabel(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if ((parts.size() == 2) ||
		((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::wstring text = narrow_to_wide(unescape_string(parts[1]));

		MY_CHECKPOS("vertlabel",1);

		v2s32 pos = padding;
		pos.X += stof(v_pos[0]) * (float)spacing.X;
		pos.Y += stof(v_pos[1]) * (float)spacing.Y;

		core::rect<s32> rect = core::rect<s32>(
				pos.X, pos.Y+((imgsize.Y/2)- m_btn_height),
				pos.X+15, pos.Y +
					font_line_height(m_font)
					* (text.length()+1)
					+((imgsize.Y/2)- m_btn_height));
		//actually text.length() would be correct but adding +1 avoids to break all mods

		if(!data->explicit_size)
			errorstream<<"WARNING: invalid use of label without a size[] element"<<std::endl;

		std::wstring label = L"";

		for (unsigned int i=0; i < text.length(); i++) {
			label += text[i];
			label += L"\n";
		}

		FieldSpec spec(
			L"",
			label,
			L"",
			258+m_fields.size()
		);
		gui::IGUIStaticText *t =
				Environment->addStaticText(spec.flabel.c_str(), rect, false, false, this, spec.fid);
		t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
		m_fields.push_back(spec);
		return;
	}
	errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseImageButton(parserData* data,std::string element,
		std::string type)
{
	std::vector<std::string> parts = split(element,';');

	if ((((parts.size() >= 5) && (parts.size() <= 8)) && (parts.size() != 6)) ||
		((parts.size() > 8) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::vector<std::string> v_geom = split(parts[1],',');
		std::string image_name = parts[2];
		std::string name = parts[3];
		std::string label = parts[4];

		MY_CHECKPOS("imagebutton",0);
		MY_CHECKGEOM("imagebutton",1);

		v2s32 pos = padding;
		pos.X += stof(v_pos[0]) * (float)spacing.X;
		pos.Y += stof(v_pos[1]) * (float)spacing.Y;
		v2s32 geom;
		geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
		geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y);

		bool noclip     = false;
		bool drawborder = true;
		std::string pressed_image_name = "";

		if (parts.size() >= 7) {
			if (parts[5] == "true")
				noclip = true;
			if (parts[6] == "false")
				drawborder = false;
		}

		if (parts.size() >= 8) {
			pressed_image_name = parts[7];
		}

		core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);

		if(!data->explicit_size)
			errorstream<<"WARNING: invalid use of image_button without a size[] element"<<std::endl;

		image_name = unescape_string(image_name);
		pressed_image_name = unescape_string(pressed_image_name);
		label = unescape_string(label);

		std::wstring wlabel = narrow_to_wide(label);

		FieldSpec spec(
			narrow_to_wide(name),
			wlabel,
			narrow_to_wide(image_name),
			258+m_fields.size()
		);
		spec.ftype = f_Button;
		if(type == "image_button_exit")
			spec.is_exit = true;

		video::ITexture *texture = 0;
		video::ITexture *pressed_texture = 0;
		texture = m_tsrc->getTexture(image_name);
		if (pressed_image_name != "")
			pressed_texture = m_tsrc->getTexture(pressed_image_name);
		else
			pressed_texture = texture;

		gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());

		if (spec.fname == data->focused_fieldname) {
			Environment->setFocus(e);
		}

		e->setUseAlphaChannel(true);
		e->setImage(texture);
		e->setPressedImage(pressed_texture);
		e->setScaleImage(true);
		e->setNotClipped(noclip);
		e->setDrawBorder(drawborder);

		m_fields.push_back(spec);
		return;
	}

	errorstream<< "Invalid imagebutton element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseTabHeader(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if (((parts.size() == 4) || (parts.size() == 6)) ||
		((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::string name = parts[1];
		std::vector<std::string> buttons = split(parts[2],',');
		std::string str_index = parts[3];
		bool show_background = true;
		bool show_border = true;
		int tab_index = stoi(str_index) -1;

		MY_CHECKPOS("tabheader",0);

		if (parts.size() == 6) {
			if (parts[4] == "true")
				show_background = false;
			if (parts[5] == "false")
				show_border = false;
		}

		FieldSpec spec(
			narrow_to_wide(name),
			L"",
			L"",
			258+m_fields.size()
		);

		spec.ftype = f_TabHeader;

		v2s32 pos(0,0);
		pos.X += stof(v_pos[0]) * (float)spacing.X;
		pos.Y += stof(v_pos[1]) * (float)spacing.Y - m_btn_height * 2;
		v2s32 geom;
		geom.X = DesiredRect.getWidth();
		geom.Y = m_btn_height*2;

		core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
				pos.Y+geom.Y);

		gui::IGUITabControl *e = Environment->addTabControl(rect, this,
				show_background, show_border, spec.fid);
		e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT,
				irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT);
		e->setTabHeight(m_btn_height*2);

		if (spec.fname == data->focused_fieldname) {
			Environment->setFocus(e);
		}

		e->setNotClipped(true);

		for (unsigned int i=0; i< buttons.size(); i++) {
			e->addTab(narrow_to_wide(buttons[i]).c_str(), -1);
		}

		if ((tab_index >= 0) &&
				(buttons.size() < INT_MAX) &&
				(tab_index < (int) buttons.size()))
			e->setActiveTab(tab_index);

		m_fields.push_back(spec);
		return;
	}
	errorstream << "Invalid TabHeader element(" << parts.size() << "): '"
			<< element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseItemImageButton(parserData* data,std::string element)
{

	if (m_gamedef == 0) {
		errorstream <<
				"WARNING: invalid use of item_image_button with m_gamedef==0"
				<< std::endl;
		return;
	}

	std::vector<std::string> parts = split(element,';');

	if ((parts.size() == 5) ||
		((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::vector<std::string> v_geom = split(parts[1],',');
		std::string item_name = parts[2];
		std::string name = parts[3];
		std::string label = parts[4];

		MY_CHECKPOS("itemimagebutton",0);
		MY_CHECKGEOM("itemimagebutton",1);

		v2s32 pos = padding;
		pos.X += stof(v_pos[0]) * (float)spacing.X;
		pos.Y += stof(v_pos[1]) * (float)spacing.Y;
		v2s32 geom;
		geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
		geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y);

		core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);

		if(!data->explicit_size)
			errorstream<<"WARNING: invalid use of item_image_button without a size[] element"<<std::endl;

		IItemDefManager *idef = m_gamedef->idef();
		ItemStack item;
		item.deSerialize(item_name, idef);
		video::ITexture *texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef);

		m_tooltips[narrow_to_wide(name)] =
			TooltipSpec (item.getDefinition(idef).description,
						m_default_tooltip_bgcolor,
						m_default_tooltip_color);

		label = unescape_string(label);
		FieldSpec spec(
			narrow_to_wide(name),
			narrow_to_wide(label),
			narrow_to_wide(item_name),
			258+m_fields.size()
		);

		gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());

		if (spec.fname == data->focused_fieldname) {
			Environment->setFocus(e);
		}

		e->setUseAlphaChannel(true);
		e->setImage(texture);
		e->setPressedImage(texture);
		e->setScaleImage(true);
		spec.ftype = f_Button;
		rect+=data->basepos-padding;
		spec.rect=rect;
		m_fields.push_back(spec);
		return;
	}
	errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseBox(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if ((parts.size() == 3) ||
		((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		std::vector<std::string> v_pos = split(parts[0],',');
		std::vector<std::string> v_geom = split(parts[1],',');

		MY_CHECKPOS("box",0);
		MY_CHECKGEOM("box",1);

		v2s32 pos = padding + AbsoluteRect.UpperLeftCorner;
		pos.X += stof(v_pos[0]) * (float) spacing.X;
		pos.Y += stof(v_pos[1]) * (float) spacing.Y;

		v2s32 geom;
		geom.X = stof(v_geom[0]) * (float)spacing.X;
		geom.Y = stof(v_geom[1]) * (float)spacing.Y;

		video::SColor tmp_color;

		if (parseColorString(parts[2], tmp_color, false)) {
			BoxDrawSpec spec(pos, geom, tmp_color);

			m_boxes.push_back(spec);
		}
		else {
			errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'  INVALID COLOR"  << std::endl;
		}
		return;
	}
	errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseBackgroundColor(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if (((parts.size() == 1) || (parts.size() == 2)) ||
		((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		parseColorString(parts[0],m_bgcolor,false);

		if (parts.size() == 2) {
			std::string fullscreen = parts[1];
			m_bgfullscreen = is_yes(fullscreen);
		}
		return;
	}
	errorstream<< "Invalid bgcolor element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseListColors(parserData* data,std::string element)
{
	std::vector<std::string> parts = split(element,';');

	if (((parts.size() == 2) || (parts.size() == 3) || (parts.size() == 5)) ||
		((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
	{
		parseColorString(parts[0], m_slotbg_n, false);
		parseColorString(parts[1], m_slotbg_h, false);

		if (parts.size() >= 3) {
			if (parseColorString(parts[2], m_slotbordercolor, false)) {
				m_slotborder = true;
			}
		}
		if (parts.size() == 5) {
			video::SColor tmp_color;

			if (parseColorString(parts[3], tmp_color, false))
				m_default_tooltip_bgcolor = tmp_color;
			if (parseColorString(parts[4], tmp_color, false))
				m_default_tooltip_color = tmp_color;
		}
		return;
	}
	errorstream<< "Invalid listcolors element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

void GUIFormSpecMenu::parseTooltip(parserData* data, std::string element)
{
	std::vector<std::string> parts = split(element,';');
	if (parts.size() == 2) {
		std::string name = parts[0];
		m_tooltips[narrow_to_wide(name)] = TooltipSpec (parts[1], m_default_tooltip_bgcolor, m_default_tooltip_color);
		return;
	} else if (parts.size() == 4) {
		std::string name = parts[0];
		video::SColor tmp_color1, tmp_color2;
		if ( parseColorString(parts[2], tmp_color1, false) && parseColorString(parts[3], tmp_color2, false) ) {
			m_tooltips[narrow_to_wide(name)] = TooltipSpec (parts[1], tmp_color1, tmp_color2);
			return;
		}
	}
	errorstream<< "Invalid tooltip element(" << parts.size() << "): '" << element << "'"  << std::endl;
}

bool GUIFormSpecMenu::parseVersionDirect(std::string data)
{
	//some prechecks
	if (data == "")
		return false;

	std::vector<std::string> parts = split(data,'[');

	if (parts.size() < 2) {
		return false;
	}

	if (parts[0] != "formspec_version") {
		return false;
	}

	if (is_number(parts[1])) {
		m_formspec_version = mystoi(parts[1]);
		return true;
	}

	return false;
}

bool GUIFormSpecMenu::parseSizeDirect(parserData* data, std::string element)
{
	if (element == "")
		return false;

	std::vector<std::string> parts = split(element,'[');

	if (parts.size() < 2)
		return false;

	std::string type = trim(parts[0]);
	std::string description = trim(parts[1]);

	if (type != "size" && type != "invsize")
		return false;

	if (type == "invsize")
		log_deprecated("Deprecated formspec element \"invsize\" is used");

	parseSize(data, description);

	return true;
}

void GUIFormSpecMenu::parseElement(parserData* data, std::string element)
{
	//some prechecks
	if (element == "")
		return;

	std::vector<std::string> parts = split(element,'[');

	// ugly workaround to keep compatibility
	if (parts.size() > 2) {
		if (trim(parts[0]) == "image") {
			for (unsigned int i=2;i< parts.size(); i++) {
				parts[1] += "[" + parts[i];
			}
		}
		else { return; }
	}

	if (parts.size() < 2) {
		return;
	}

	std::string type = trim(parts[0]);
	std::string description = trim(parts[1]);

	if (type == "list") {
		parseList(data,description);
		return;
	}

	if (type == "checkbox") {
		parseCheckbox(data,description);
		return;
	}

	if (type == "image") {
		parseImage(data,description);
		return;
	}

	if (type == "item_image") {
		parseItemImage(data,description);
		return;
	}

	if ((type == "button") || (type == "button_exit")) {
		parseButton(data,description,type);
		return;
	}

	if (type == "background") {
		parseBackground(data,description);
		return;
	}

	if (type == "tableoptions"){
		parseTableOptions(data,description);
		return;
	}

	if (type == "tablecolumns"){
		parseTableColumns(data,description);
		return;
	}

	if (type == "table"){
		parseTable(data,description);
		return;
	}

	if (type == "textlist"){
		parseTextList(data,description);
		return;
	}

	if (type == "dropdown"){
		parseDropDown(data,description);
		return;
	}

	if (type == "pwdfield") {
		parsePwdField(data,description);
		return;
	}

	if ((type == "field") || (type == "textarea")){
		parseField(data,description,type);
		return;
	}

	if (type == "label") {
		parseLabel(data,description);
		return;
	}

	if (type == "vertlabel") {
		parseVertLabel(data,description);
		return;
	}

	if (type == "item_image_button") {
		parseItemImageButton(data,description);
		return;
	}

	if ((type == "image_button") || (type == "image_button_exit")) {
		parseImageButton(data,description,type);
		return;
	}

	if (type == "tabheader") {
		parseTabHeader(data,description);
		return;
	}

	if (type == "box") {
		parseBox(data,description);
		return;
	}

	if (type == "bgcolor") {
		parseBackgroundColor(data,description);
		return;
	}

	if (type == "listcolors") {
		parseListColors(data,description);
		return;
	}

	if (type == "tooltip") {
		parseTooltip(data,description);
		return;
	}

	if (type == "scrollbar") {
		parseScrollBar(data, description);
		return;
	}

	// Ignore others
	infostream
		<< "Unknown DrawSpec: type="<<type<<", data=\""<<description<<"\""
		<<std::endl;
}

void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
{
	/* useless to regenerate without a screensize */
	if ((screensize.X <= 0) || (screensize.Y <= 0)) {
		return;
	}

	parserData mydata;

	//preserve tables
	for (u32 i = 0; i < m_tables.size(); ++i) {
		std::wstring tablename = m_tables[i].first.fname;
		GUITable *table = m_tables[i].second;
		mydata.table_dyndata[tablename] = table->getDynamicData();
	}

	//set focus
	if (!m_focused_element.empty())
		mydata.focused_fieldname = m_focused_element;

	//preserve focus
	gui::IGUIElement *focused_element = Environment->getFocus();
	if (focused_element && focused_element->getParent() == this) {
		s32 focused_id = focused_element->getID();
		if (focused_id > 257) {
			for (u32 i=0; i<m_fields.size(); i++) {
				if (m_fields[i].fid == focused_id) {
					mydata.focused_fieldname =
						m_fields[i].fname;
					break;
				}
			}
		}
	}

	// Remove children
	removeChildren();

	for (u32 i = 0; i < m_tables.size(); ++i) {
		GUITable *table = m_tables[i].second;
		table->drop();
	}

	mydata.size= v2s32(100,100);
	mydata.screensize = screensize;

	// Base position of contents of form
	mydata.basepos = getBasePos();

	/* Convert m_init_draw_spec to m_inventorylists */

	m_inventorylists.clear();
	m_images.clear();
	m_backgrounds.clear();
	m_itemimages.clear();
	m_tables.clear();
	m_checkboxes.clear();
	m_scrollbars.clear();
	m_fields.clear();
	m_boxes.clear();
	m_tooltips.clear();

	// Set default values (fits old formspec values)
	m_bgcolor = video::SColor(140,0,0,0);
	m_bgfullscreen = false;

	m_slotbg_n = video::SColor(255,128,128,128);
	m_slotbg_h = video::SColor(255,192,192,192);

	m_default_tooltip_bgcolor = video::SColor(255,110,130,60);
	m_default_tooltip_color = video::SColor(255,255,255,255);

	m_slotbordercolor = video::SColor(200,0,0,0);
	m_slotborder = false;

	m_clipbackground = false;
	// Add tooltip
	{
		assert(m_tooltip_element == NULL);
		// Note: parent != this so that the tooltip isn't clipped by the menu rectangle
		m_tooltip_element = Environment->addStaticText(L"",core::rect<s32>(0,0,110,18));
		m_tooltip_element->enableOverrideColor(true);
		m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor);
		m_tooltip_element->setDrawBackground(true);
		m_tooltip_element->setDrawBorder(true);
		m_tooltip_element->setOverrideColor(m_default_tooltip_color);
		m_tooltip_element->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
		m_tooltip_element->setWordWrap(false);
		//we're not parent so no autograb for this one!
		m_tooltip_element->grab();
	}

	std::vector<std::string> elements = split(m_formspec_string,']');
	unsigned int i = 0;

	/* try to read version from first element only */
	if (elements.size() >= 1) {
		if ( parseVersionDirect(elements[0]) ) {
			i++;
		}
	}

	/* we need size first in order to calculate image scale */
	mydata.explicit_size = false;
	for (; i< elements.size(); i++) {
		if (!parseSizeDirect(&mydata, elements[i])) {
			break;
		}
	}

	if (mydata.explicit_size) {
		// compute scaling for specified form size
		if (m_lock) {
			v2u32 current_screensize = m_device->getVideoDriver()->getScreenSize();
			v2u32 delta = current_screensize - m_lockscreensize;

			if (current_screensize.Y > m_lockscreensize.Y)
				delta.Y /= 2;
			else
				delta.Y = 0;

			if (current_screensize.X > m_lockscreensize.X)
				delta.X /= 2;
			else
				delta.X = 0;

			offset = v2s32(delta.X,delta.Y);

			mydata.screensize = m_lockscreensize;
		} else {
			offset = v2s32(0,0);
		}

		double gui_scaling = g_settings->getFloat("gui_scaling");
		double screen_dpi = porting::getDisplayDensity() * 96;

		double use_imgsize;
		if (m_lock) {
			// In fixed-size mode, inventory image size
			// is 0.53 inch multiplied by the gui_scaling
			// config parameter.  This magic size is chosen
			// to make the main menu (15.5 inventory images
			// wide, including border) just fit into the
			// default window (800 pixels wide) at 96 DPI
			// and default scaling (1.00).
			use_imgsize = 0.5555 * screen_dpi * gui_scaling;
		} else {
			// In variable-size mode, we prefer to make the
			// inventory image size 1/15 of screen height,
			// multiplied by the gui_scaling config parameter.
			// If the preferred size won't fit the whole
			// form on the screen, either horizontally or
			// vertically, then we scale it down to fit.
			// (The magic numbers in the computation of what
			// fits arise from the scaling factors in the
			// following stanza, including the form border,
			// help text space, and 0.1 inventory slot spare.)
			// However, a minimum size is also set, that
			// the image size can't be less than 0.3 inch
			// multiplied by gui_scaling, even if this means
			// the form doesn't fit the screen.
			double prefer_imgsize = mydata.screensize.Y / 15 *
							gui_scaling;
			double fitx_imgsize = mydata.screensize.X /
				((5.0/4.0) * (0.5 + mydata.invsize.X));
			double fity_imgsize = mydata.screensize.Y /
				((15.0/13.0) * (0.85 * mydata.invsize.Y));
			double screen_dpi = porting::getDisplayDensity() * 96;
			double min_imgsize = 0.3 * screen_dpi * gui_scaling;
			use_imgsize = MYMAX(min_imgsize, MYMIN(prefer_imgsize,
				MYMIN(fitx_imgsize, fity_imgsize)));
		}

		// Everything else is scaled in proportion to the
		// inventory image size.  The inventory slot spacing
		// is 5/4 image size horizontally and 15/13 image size
		// vertically.	The padding around the form (incorporating
		// the border of the outer inventory slots) is 3/8
		// image size.	Font height (baseline to baseline)
		// is 2/5 vertical inventory slot spacing, and button
		// half-height is 7/8 of font height.
		imgsize = v2s32(use_imgsize, use_imgsize);
		spacing = v2s32(use_imgsize*5.0/4, use_imgsize*15.0/13);
		padding = v2s32(use_imgsize*3.0/8, use_imgsize*3.0/8);
		m_btn_height = use_imgsize*15.0/13 * 0.35;

		m_font = g_fontengine->getFont();

		mydata.size = v2s32(
			padding.X*2+spacing.X*(mydata.invsize.X-1.0)+imgsize.X,
			padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0
		);
		DesiredRect = mydata.rect = core::rect<s32>(
				mydata.screensize.X/2 - mydata.size.X/2 + offset.X,
				mydata.screensize.Y/2 - mydata.size.Y/2 + offset.Y,
				mydata.screensize.X/2 + mydata.size.X/2 + offset.X,
				mydata.screensize.Y/2 + mydata.size.Y/2 + offset.Y
		);
	} else {
		// Non-size[] form must consist only of text fields and
		// implicit "Proceed" button.  Use default font, and
		// temporary form size which will be recalculated below.
		m_font = g_fontengine->getFont();
		m_btn_height = font_line_height(m_font) * 0.875;
		DesiredRect = core::rect<s32>(
			mydata.screensize.X/2 - 580/2,
			mydata.screensize.Y/2 - 300/2,
			mydata.screensize.X/2 + 580/2,
			mydata.screensize.Y/2 + 300/2
		);
	}
	recalculateAbsolutePosition(false);
	mydata.basepos = getBasePos();
	m_tooltip_element->setOverrideFont(m_font);

	gui::IGUISkin* skin = Environment->getSkin();
	assert(skin != NULL);
	gui::IGUIFont *old_font = skin->getFont();
	skin->setFont(m_font);

	for (; i< elements.size(); i++) {
		parseElement(&mydata, elements[i]);
	}

	// If there are fields without explicit size[], add a "Proceed"
	// button and adjust size to fit all the fields.
	if (m_fields.size() && !mydata.explicit_size) {
		mydata.rect = core::rect<s32>(
				mydata.screensize.X/2 - 580/2,
				mydata.screensize.Y/2 - 300/2,
				mydata.screensize.X/2 + 580/2,
				mydata.screensize.Y/2 + 240/2+(m_fields.size()*60)
		);
		DesiredRect = mydata.rect;
		recalculateAbsolutePosition(false);
		mydata.basepos = getBasePos();

		{
			v2s32 pos = mydata.basepos;
			pos.Y = ((m_fields.size()+2)*60);

			v2s32 size = DesiredRect.getSize();
			mydata.rect =
					core::rect<s32>(size.X/2-70, pos.Y,
							(size.X/2-70)+140, pos.Y + (m_btn_height*2));
			const wchar_t *text = wgettext("Proceed");
			Environment->addButton(mydata.rect, this, 257, text);
			delete[] text;
		}

	}

	//set initial focus if parser didn't set it
	focused_element = Environment->getFocus();
	if (!focused_element
			|| !isMyChild(focused_element)
			|| focused_element->getType() == gui::EGUIET_TAB_CONTROL)
		setInitialFocus();

	skin->setFont(old_font);
}

#ifdef __ANDROID__
bool GUIFormSpecMenu::getAndroidUIInput()
{
	/* no dialog shown */
	if (m_JavaDialogFieldName == L"") {
		return false;
	}

	/* still waiting */
	if (porting::getInputDialogState() == -1) {
		return true;
	}

	std::wstring fieldname = m_JavaDialogFieldName;
	m_JavaDialogFieldName = L"";

	/* no value abort dialog processing */
	if (porting::getInputDialogState() != 0) {
		return false;
	}

	for(std::vector<FieldSpec>::iterator iter =  m_fields.begin();
			iter != m_fields.end(); iter++) {

		if (iter->fname != fieldname) {
			continue;
		}
		IGUIElement* tochange = getElementFromId(iter->fid);

		if (tochange == 0) {
			return false;
		}

		if (tochange->getType() != irr::gui::EGUIET_EDIT_BOX) {
			return false;
		}

		std::string text = porting::getInputDialogValue();

		((gui::IGUIEditBox*) tochange)->
			setText(narrow_to_wide(text).c_str());
	}
	return false;
}
#endif

GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
{
	core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);

	for(u32 i=0; i<m_inventorylists.size(); i++)
	{
		const ListDrawSpec &s = m_inventorylists[i];

		for(s32 i=0; i<s.geom.X*s.geom.Y; i++) {
			s32 item_i = i + s.start_item_i;
			s32 x = (i%s.geom.X) * spacing.X;
			s32 y = (i/s.geom.X) * spacing.Y;
			v2s32 p0(x,y);
			core::rect<s32> rect = imgrect + s.pos + p0;
			if(rect.isPointInside(p))
			{
				return ItemSpec(s.inventoryloc, s.listname, item_i);
			}
		}
	}

	return ItemSpec(InventoryLocation(), "", -1);
}

void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase)
{
	video::IVideoDriver* driver = Environment->getVideoDriver();

	Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
	if(!inv){
		infostream<<"GUIFormSpecMenu::drawList(): WARNING: "
				<<"The inventory location "
				<<"\""<<s.inventoryloc.dump()<<"\" doesn't exist"
				<<std::endl;
		return;
	}
	InventoryList *ilist = inv->getList(s.listname);
	if(!ilist){
		infostream<<"GUIFormSpecMenu::drawList(): WARNING: "
				<<"The inventory list \""<<s.listname<<"\" @ \""
				<<s.inventoryloc.dump()<<"\" doesn't exist"
				<<std::endl;
		return;
	}

	core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);

	for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
	{
		s32 item_i = i + s.start_item_i;
		if(item_i >= (s32) ilist->getSize())
			break;
		s32 x = (i%s.geom.X) * spacing.X;
		s32 y = (i/s.geom.X) * spacing.Y;
		v2s32 p(x,y);
		core::rect<s32> rect = imgrect + s.pos + p;
		ItemStack item;
		if(ilist)
			item = ilist->getItem(item_i);

		bool selected = m_selected_item
			&& m_invmgr->getInventory(m_selected_item->inventoryloc) == inv
			&& m_selected_item->listname == s.listname
			&& m_selected_item->i == item_i;
		bool hovering = rect.isPointInside(m_pointer);

		if(phase == 0)
		{
			if(hovering)
				driver->draw2DRectangle(m_slotbg_h, rect, &AbsoluteClippingRect);
			else
				driver->draw2DRectangle(m_slotbg_n, rect, &AbsoluteClippingRect);
		}

		//Draw inv slot borders
		if (m_slotborder) {
			s32 x1 = rect.UpperLeftCorner.X;
			s32 y1 = rect.UpperLeftCorner.Y;
			s32 x2 = rect.LowerRightCorner.X;
			s32 y2 = rect.LowerRightCorner.Y;
			s32 border = 1;
			driver->draw2DRectangle(m_slotbordercolor,
				core::rect<s32>(v2s32(x1 - border, y1 - border),
								v2s32(x2 + border, y1)), NULL);
			driver->draw2DRectangle(m_slotbordercolor,
				core::rect<s32>(v2s32(x1 - border, y2),
								v2s32(x2 + border, y2 + border)), NULL);
			driver->draw2DRectangle(m_slotbordercolor,
				core::rect<s32>(v2s32(x1 - border, y1),
								v2s32(x1, y2)), NULL);
			driver->draw2DRectangle(m_slotbordercolor,
				core::rect<s32>(v2s32(x2, y1),
								v2s32(x2 + border, y2)), NULL);
		}

		if(phase == 1)
		{
			// Draw item stack
			if(selected)
			{
				item.takeItem(m_selected_amount);
			}
			if(!item.empty())
			{
				drawItemStack(driver, m_font, item,
						rect, &AbsoluteClippingRect, m_gamedef);
			}

			// Draw tooltip
			std::string tooltip_text = "";
			if (hovering && !m_selected_item)
				tooltip_text = item.getDefinition(m_gamedef->idef()).description;
			if (tooltip_text != "") {
				std::vector<std::string> tt_rows = str_split(tooltip_text, '\n');
				m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor);
				m_tooltip_element->setOverrideColor(m_default_tooltip_color);
				m_tooltip_element->setVisible(true);
				this->bringToFront(m_tooltip_element);
				m_tooltip_element->setText(narrow_to_wide(tooltip_text).c_str());
				s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
				s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5;
				v2u32 screenSize = driver->getScreenSize();
				int tooltip_offset_x = m_btn_height;
				int tooltip_offset_y = m_btn_height;
#ifdef __ANDROID__
				tooltip_offset_x *= 3;
				tooltip_offset_y  = 0;
				if (m_pointer.X > (s32)screenSize.X / 2)
					tooltip_offset_x = (tooltip_offset_x + tooltip_width) * -1;
#endif
				s32 tooltip_x = m_pointer.X + tooltip_offset_x;
				s32 tooltip_y = m_pointer.Y + tooltip_offset_y;
				if (tooltip_x + tooltip_width > (s32)screenSize.X)
					tooltip_x = (s32)screenSize.X - tooltip_width  - m_btn_height;
				if (tooltip_y + tooltip_height > (s32)screenSize.Y)
					tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height;
				m_tooltip_element->setRelativePosition(core::rect<s32>(
						core::position2d<s32>(tooltip_x, tooltip_y),
						core::dimension2d<s32>(tooltip_width, tooltip_height)));
			}
		}
	}
}

void GUIFormSpecMenu::drawSelectedItem()
{
	if(!m_selected_item)
		return;

	video::IVideoDriver* driver = Environment->getVideoDriver();

	Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
	assert(inv);
	InventoryList *list = inv->getList(m_selected_item->listname);
	assert(list);
	ItemStack stack = list->getItem(m_selected_item->i);
	stack.count = m_selected_amount;

	core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
	core::rect<s32> rect = imgrect + (m_pointer - imgrect.getCenter());
	drawItemStack(driver, m_font, stack, rect, NULL, m_gamedef);
}

void GUIFormSpecMenu::drawMenu()
{
	if(m_form_src){
		std::string newform = m_form_src->getForm();
		if(newform != m_formspec_string){
			m_formspec_string = newform;
			regenerateGui(m_screensize_old);
		}
	}

	gui::IGUISkin* skin = Environment->getSkin();
	assert(skin != NULL);
	gui::IGUIFont *old_font = skin->getFont();
	skin->setFont(m_font);

	updateSelectedItem();

	video::IVideoDriver* driver = Environment->getVideoDriver();

	v2u32 screenSize = driver->getScreenSize();
	core::rect<s32> allbg(0, 0, screenSize.X ,	screenSize.Y);
	if (m_bgfullscreen)
		driver->draw2DRectangle(m_bgcolor, allbg, &allbg);
	else
		driver->draw2DRectangle(m_bgcolor, AbsoluteRect, &AbsoluteClippingRect);

	m_tooltip_element->setVisible(false);

	/*
		Draw backgrounds
	*/
	for(u32 i=0; i<m_backgrounds.size(); i++)
	{
		const ImageDrawSpec &spec = m_backgrounds[i];
		video::ITexture *texture = m_tsrc->getTexture(spec.name);

		if (texture != 0) {
			// Image size on screen
			core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
			// Image rectangle on screen
			core::rect<s32> rect = imgrect + spec.pos;

			if (m_clipbackground) {
				core::dimension2d<s32> absrec_size = AbsoluteRect.getSize();
				rect = core::rect<s32>(AbsoluteRect.UpperLeftCorner.X - spec.pos.X,
									AbsoluteRect.UpperLeftCorner.Y - spec.pos.Y,
									AbsoluteRect.UpperLeftCorner.X + absrec_size.Width + spec.pos.X,
									AbsoluteRect.UpperLeftCorner.Y + absrec_size.Height + spec.pos.Y);
			}

			const video::SColor color(255,255,255,255);
			const video::SColor colors[] = {color,color,color,color};
			driver->draw2DImage(texture, rect,
				core::rect<s32>(core::position2d<s32>(0,0),
						core::dimension2di(texture->getOriginalSize())),
				NULL/*&AbsoluteClippingRect*/, colors, true);
		}
		else {
			errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl;
			errorstream << "\t" << spec.name << std::endl;
		}
	}

	/*
		Draw Boxes
	*/
	for(u32 i=0; i<m_boxes.size(); i++)
	{
		const BoxDrawSpec &spec = m_boxes[i];

		irr::video::SColor todraw = spec.color;

		todraw.setAlpha(140);

		core::rect<s32> rect(spec.pos.X,spec.pos.Y,
							spec.pos.X + spec.geom.X,spec.pos.Y + spec.geom.Y);

		driver->draw2DRectangle(todraw, rect, 0);
	}
	/*
		Draw images
	*/
	for(u32 i=0; i<m_images.size(); i++)
	{
		const ImageDrawSpec &spec = m_images[i];
		video::ITexture *texture = m_tsrc->getTexture(spec.name);

		if (texture != 0) {
			const core::dimension2d<u32>& img_origsize = texture->getOriginalSize();
			// Image size on screen
			core::rect<s32> imgrect;

			if (spec.scale)
				imgrect = core::rect<s32>(0,0,spec.geom.X, spec.geom.Y);
			else {

				imgrect = core::rect<s32>(0,0,img_origsize.Width,img_origsize.Height);
			}
			// Image rectangle on screen
			core::rect<s32> rect = imgrect + spec.pos;
			const video::SColor color(255,255,255,255);
			const video::SColor colors[] = {color,color,color,color};
			driver->draw2DImage(texture, rect,
				core::rect<s32>(core::position2d<s32>(0,0),img_origsize),
				NULL/*&AbsoluteClippingRect*/, colors, true);
		}
		else {
			errorstream << "GUIFormSpecMenu::drawMenu() Draw images unable to load texture:" << std::endl;
			errorstream << "\t" << spec.name << std::endl;
		}
	}

	/*
		Draw item images
	*/
	for(u32 i=0; i<m_itemimages.size(); i++)
	{
		if (m_gamedef == 0)
			break;

		const ImageDrawSpec &spec = m_itemimages[i];
		IItemDefManager *idef = m_gamedef->idef();
		ItemStack item;
		item.deSerialize(spec.name, idef);
		video::ITexture *texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef);
		// Image size on screen
		core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
		// Image rectangle on screen
		core::rect<s32> rect = imgrect + spec.pos;
		const video::SColor color(255,255,255,255);
		const video::SColor colors[] = {color,color,color,color};
		driver->draw2DImage(texture, rect,
			core::rect<s32>(core::position2d<s32>(0,0),
					core::dimension2di(texture->getOriginalSize())),
			NULL/*&AbsoluteClippingRect*/, colors, true);
	}

	/*
		Draw items
		Phase 0: Item slot rectangles
		Phase 1: Item images; prepare tooltip
	*/
	int start_phase=0;
	for(int phase=start_phase; phase<=1; phase++)
	for(u32 i=0; i<m_inventorylists.size(); i++)
	{
		drawList(m_inventorylists[i], phase);
	}

	/*
		Call base class
	*/
	gui::IGUIElement::draw();

/* TODO find way to show tooltips on touchscreen */
#ifndef HAVE_TOUCHSCREENGUI
	m_pointer = m_device->getCursorControl()->getPosition();
#endif

	/*
		Draw fields/buttons tooltips
	*/
	gui::IGUIElement *hovered =
			Environment->getRootGUIElement()->getElementFromPoint(m_pointer);

	if (hovered != NULL) {
		s32 id = hovered->getID();

		u32 delta = 0;
		if (id == -1) {
			m_old_tooltip_id = id;
			m_old_tooltip = "";
		} else {
			if (id == m_old_tooltip_id) {
				delta = porting::getDeltaMs(m_hovered_time, getTimeMs());
			} else {
				m_hovered_time = getTimeMs();
				m_old_tooltip_id = id;
			}
		}

		if (id != -1 && delta >= m_tooltip_show_delay) {
			for(std::vector<FieldSpec>::iterator iter =  m_fields.begin();
					iter != m_fields.end(); iter++) {
				if ( (iter->fid == id) && (m_tooltips[iter->fname].tooltip != "") ){
					if (m_old_tooltip != m_tooltips[iter->fname].tooltip) {
						m_old_tooltip = m_tooltips[iter->fname].tooltip;
						m_tooltip_element->setText(narrow_to_wide(m_tooltips[iter->fname].tooltip).c_str());
						std::vector<std::string> tt_rows = str_split(m_tooltips[iter->fname].tooltip, '\n');
						s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
						s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5;
						int tooltip_offset_x = m_btn_height;
						int tooltip_offset_y = m_btn_height;
#ifdef __ANDROID__
						tooltip_offset_x *= 3;
						tooltip_offset_y  = 0;
						if (m_pointer.X > (s32)screenSize.X / 2)
							tooltip_offset_x = (tooltip_offset_x + tooltip_width) * -1;
#endif
						s32 tooltip_x = m_pointer.X + tooltip_offset_x;
						s32 tooltip_y = m_pointer.Y + tooltip_offset_y;
						if (tooltip_x + tooltip_width > (s32)screenSize.X)
							tooltip_x = (s32)screenSize.X - tooltip_width  - m_btn_height;
						if (tooltip_y + tooltip_height > (s32)screenSize.Y)
							tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height;
						m_tooltip_element->setRelativePosition(core::rect<s32>(
						core::position2d<s32>(tooltip_x, tooltip_y),
						core::dimension2d<s32>(tooltip_width, tooltip_height)));
					}
					m_tooltip_element->setBackgroundColor(m_tooltips[iter->fname].bgcolor);
					m_tooltip_element->setOverrideColor(m_tooltips[iter->fname].color);
					m_tooltip_element->setVisible(true);
					this->bringToFront(m_tooltip_element);
					break;
				}
			}
		}
	}

	/*
		Draw dragged item stack
	*/
	drawSelectedItem();

	skin->setFont(old_font);
}

void GUIFormSpecMenu::updateSelectedItem()
{
	// If the selected stack has become empty for some reason, deselect it.
	// If the selected stack has become inaccessible, deselect it.
	// If the selected stack has become smaller, adjust m_selected_amount.
	ItemStack selected = verifySelectedItem();

	// WARNING: BLACK MAGIC
	// See if there is a stack suited for our current guess.
	// If such stack does not exist, clear the guess.
	if(m_selected_content_guess.name != "" &&
			selected.name == m_selected_content_guess.name &&
			selected.count == m_selected_content_guess.count){
		// Selected item fits the guess. Skip the black magic.
	}
	else if(m_selected_content_guess.name != ""){
		bool found = false;
		for(u32 i=0; i<m_inventorylists.size() && !found; i++){
			const ListDrawSpec &s = m_inventorylists[i];
			Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
			if(!inv)
				continue;
			InventoryList *list = inv->getList(s.listname);
			if(!list)
				continue;
			for(s32 i=0; i<s.geom.X*s.geom.Y && !found; i++){
				u32 item_i = i + s.start_item_i;
				if(item_i >= list->getSize())
					continue;
				ItemStack stack = list->getItem(item_i);
				if(stack.name == m_selected_content_guess.name &&
						stack.count == m_selected_content_guess.count){
					found = true;
					infostream<<"Client: Changing selected content guess to "
							<<s.inventoryloc.dump()<<" "<<s.listname
							<<" "<<item_i<<std::endl;
					delete m_selected_item;
					m_selected_item = new ItemSpec(s.inventoryloc, s.listname, item_i);
					m_selected_amount = stack.count;
				}
			}
		}
		if(!found){
			infostream<<"Client: Discarding selected content guess: "
					<<m_selected_content_guess.getItemString()<<std::endl;
			m_selected_content_guess.name = "";
		}
	}

	// If craftresult is nonempty and nothing else is selected, select it now.
	if(!m_selected_item)
	{
		for(u32 i=0; i<m_inventorylists.size(); i++)
		{
			const ListDrawSpec &s = m_inventorylists[i];
			if(s.listname == "craftpreview")
			{
				Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
				InventoryList *list = inv->getList("craftresult");
				if(list && list->getSize() >= 1 && !list->getItem(0).empty())
				{
					m_selected_item = new ItemSpec;
					m_selected_item->inventoryloc = s.inventoryloc;
					m_selected_item->listname = "craftresult";
					m_selected_item->i = 0;
					m_selected_amount = 0;
					m_selected_dragging = false;
					break;
				}
			}
		}
	}

	// If craftresult is selected, keep the whole stack selected
	if(m_selected_item && m_selected_item->listname == "craftresult")
	{
		m_selected_amount = verifySelectedItem().count;
	}
}

ItemStack GUIFormSpecMenu::verifySelectedItem()
{
	// If the selected stack has become empty for some reason, deselect it.
	// If the selected stack has become inaccessible, deselect it.
	// If the selected stack has become smaller, adjust m_selected_amount.
	// Return the selected stack.

	if(m_selected_item)
	{
		if(m_selected_item->isValid())
		{
			Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
			if(inv)
			{
				InventoryList *list = inv->getList(m_selected_item->listname);
				if(list && (u32) m_selected_item->i < list->getSize())
				{
					ItemStack stack = list->getItem(m_selected_item->i);
					if(m_selected_amount > stack.count)
						m_selected_amount = stack.count;
					if(!stack.empty())
						return stack;
				}
			}
		}

		// selection was not valid
		delete m_selected_item;
		m_selected_item = NULL;
		m_selected_amount = 0;
		m_selected_dragging = false;
	}
	return ItemStack();
}

void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
{
	if(m_text_dst)
	{
		std::map<std::string, std::string> fields;

		if (quitmode == quit_mode_accept) {
			fields["quit"] = "true";
		}

		if (quitmode == quit_mode_cancel) {
			fields["quit"] = "true";
			m_text_dst->gotText(fields);
			return;
		}

		if (current_keys_pending.key_down) {
			fields["key_down"] = "true";
			current_keys_pending.key_down = false;
		}

		if (current_keys_pending.key_up) {
			fields["key_up"] = "true";
			current_keys_pending.key_up = false;
		}

		if (current_keys_pending.key_enter) {
			fields["key_enter"] = "true";
			current_keys_pending.key_enter = false;
		}

		if (current_keys_pending.key_escape) {
			fields["key_escape"] = "true";
			current_keys_pending.key_escape = false;
		}

		for(unsigned int i=0; i<m_fields.size(); i++) {
			const FieldSpec &s = m_fields[i];
			if(s.send) {
				std::string name  = wide_to_narrow(s.fname);
				if(s.ftype == f_Button) {
					fields[name] = wide_to_narrow(s.flabel);
				}
				else if(s.ftype == f_Table) {
					GUITable *table = getTable(s.fname);
					if (table) {
						fields[name] = table->checkEvent();
					}
				}
				else if(s.ftype == f_DropDown) {
					// no dynamic cast possible due to some distributions shipped
					// without rtti support in irrlicht
					IGUIElement * element = getElementFromId(s.fid);
					gui::IGUIComboBox *e = NULL;
					if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) {
						e = static_cast<gui::IGUIComboBox*>(element);
					}
					s32 selected = e->getSelected();
					if (selected >= 0) {
						fields[name] =
							wide_to_narrow(e->getItem(selected));
					}
				}
				else if (s.ftype == f_TabHeader) {
					// no dynamic cast possible due to some distributions shipped
					// without rtti support in irrlicht
					IGUIElement * element = getElementFromId(s.fid);
					gui::IGUITabControl *e = NULL;
					if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) {
						e = static_cast<gui::IGUITabControl*>(element);
					}

					if (e != 0) {
						std::stringstream ss;
						ss << (e->getActiveTab() +1);
						fields[name] = ss.str();
					}
				}
				else if (s.ftype == f_CheckBox) {
					// no dynamic cast possible due to some distributions shipped
					// without rtti support in irrlicht
					IGUIElement * element = getElementFromId(s.fid);
					gui::IGUICheckBox *e = NULL;
					if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) {
						e = static_cast<gui::IGUICheckBox*>(element);
					}

					if (e != 0) {
						if (e->isChecked())
							fields[name] = "true";
						else
							fields[name] = "false";
					}
				}
				else if (s.ftype == f_ScrollBar) {
					// no dynamic cast possible due to some distributions shipped
					// without rtti support in irrlicht
					IGUIElement * element = getElementFromId(s.fid);
					gui::IGUIScrollBar *e = NULL;
					if ((element) && (element->getType() == gui::EGUIET_SCROLL_BAR)) {
						e = static_cast<gui::IGUIScrollBar*>(element);
					}

					if (e != 0) {
						std::stringstream os;
						os << e->getPos();
						if (s.fdefault == L"Changed")
							fields[name] = "CHG:" + os.str();
						else
							fields[name] = "VAL:" + os.str();
 					}
				}
				else
				{
					IGUIElement* e = getElementFromId(s.fid);
					if(e != NULL) {
						fields[name] = wide_to_narrow(e->getText());
					}
				}
			}
		}

		m_text_dst->gotText(fields);
	}
}

static bool isChild(gui::IGUIElement * tocheck, gui::IGUIElement * parent)
{
	while(tocheck != NULL) {
		if (tocheck == parent) {
			return true;
		}
		tocheck = tocheck->getParent();
	}
	return false;
}

bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
{
	// The IGUITabControl renders visually using the skin's selected
	// font, which we override for the duration of form drawing,
	// but computes tab hotspots based on how it would have rendered
	// using the font that is selected at the time of button release.
	// To make these two consistent, temporarily override the skin's
	// font while the IGUITabControl is processing the event.
	if (event.EventType == EET_MOUSE_INPUT_EVENT &&
			event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
		s32 x = event.MouseInput.X;
		s32 y = event.MouseInput.Y;
		gui::IGUIElement *hovered =
			Environment->getRootGUIElement()->getElementFromPoint(
				core::position2d<s32>(x, y));
		if (hovered && isMyChild(hovered) &&
				hovered->getType() == gui::EGUIET_TAB_CONTROL) {
			gui::IGUISkin* skin = Environment->getSkin();
			assert(skin != NULL);
			gui::IGUIFont *old_font = skin->getFont();
			skin->setFont(m_font);
			bool retval = hovered->OnEvent(event);
			skin->setFont(old_font);
			return retval;
		}
	}

	// Fix Esc/Return key being eaten by checkboxen and tables
	if(event.EventType==EET_KEY_INPUT_EVENT) {
		KeyPress kp(event.KeyInput);
		if (kp == EscapeKey || kp == CancelKey
				|| kp == getKeySetting("keymap_inventory")
				|| event.KeyInput.Key==KEY_RETURN) {
			gui::IGUIElement *focused = Environment->getFocus();
			if (focused && isMyChild(focused) &&
					(focused->getType() == gui::EGUIET_LIST_BOX ||
					 focused->getType() == gui::EGUIET_CHECK_BOX)) {
				OnEvent(event);
				return true;
			}
		}
	}
	// Mouse wheel events: send to hovered element instead of focused
	if(event.EventType==EET_MOUSE_INPUT_EVENT
			&& event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
		s32 x = event.MouseInput.X;
		s32 y = event.MouseInput.Y;
		gui::IGUIElement *hovered =
			Environment->getRootGUIElement()->getElementFromPoint(
				core::position2d<s32>(x, y));
		if (hovered && isMyChild(hovered)) {
			hovered->OnEvent(event);
			return true;
		}
	}

	if (event.EventType == EET_MOUSE_INPUT_EVENT) {
		s32 x = event.MouseInput.X;
		s32 y = event.MouseInput.Y;
		gui::IGUIElement *hovered =
			Environment->getRootGUIElement()->getElementFromPoint(
				core::position2d<s32>(x, y));
		if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
			m_old_tooltip_id = -1;
			m_old_tooltip = "";
		}
		if (!isChild(hovered,this)) {
			if (DoubleClickDetection(event)) {
				return true;
			}
		}
	}

	#ifdef __ANDROID__
	// display software keyboard when clicking edit boxes
	if (event.EventType == EET_MOUSE_INPUT_EVENT
			&& event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
		gui::IGUIElement *hovered =
			Environment->getRootGUIElement()->getElementFromPoint(
				core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));
		if ((hovered) && (hovered->getType() == irr::gui::EGUIET_EDIT_BOX)) {
			bool retval = hovered->OnEvent(event);
			if (retval) {
				Environment->setFocus(hovered);
			}
			m_JavaDialogFieldName = getNameByID(hovered->getID());
			std::string message   = gettext("Enter ");
			std::string label     = wide_to_narrow(getLabelByID(hovered->getID()));
			if (label == "") {
				label = "text";
			}
			message += gettext(label) + ":";

			/* single line text input */
			int type = 2;

			/* multi line text input */
			if (((gui::IGUIEditBox*) hovered)->isMultiLineEnabled()) {
				type = 1;
			}

			/* passwords are always single line */
			if (((gui::IGUIEditBox*) hovered)->isPasswordBox()) {
				type = 3;
			}

			porting::showInputDialog(gettext("ok"), "",
					wide_to_narrow(((gui::IGUIEditBox*) hovered)->getText()),
					type);
			return retval;
		}
	}

	if (event.EventType == EET_TOUCH_INPUT_EVENT)
	{
		SEvent translated;
		memset(&translated, 0, sizeof(SEvent));
		translated.EventType   = EET_MOUSE_INPUT_EVENT;
		gui::IGUIElement* root = Environment->getRootGUIElement();

		if (!root) {
			errorstream
			<< "GUIFormSpecMenu::preprocessEvent unable to get root element"
			<< std::endl;
			return false;
		}
		gui::IGUIElement* hovered = root->getElementFromPoint(
			core::position2d<s32>(
					event.TouchInput.X,
					event.TouchInput.Y));

		translated.MouseInput.X = event.TouchInput.X;
		translated.MouseInput.Y = event.TouchInput.Y;
		translated.MouseInput.Control = false;

		bool dont_send_event = false;

		if (event.TouchInput.touchedCount == 1) {
			switch (event.TouchInput.Event) {
				case ETIE_PRESSED_DOWN:
					m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y);
					translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
					translated.MouseInput.ButtonStates = EMBSM_LEFT;
					m_down_pos = m_pointer;
					break;
				case ETIE_MOVED:
					m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y);
					translated.MouseInput.Event = EMIE_MOUSE_MOVED;
					translated.MouseInput.ButtonStates = EMBSM_LEFT;
					break;
				case ETIE_LEFT_UP:
					translated.MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
					translated.MouseInput.ButtonStates = 0;
					hovered = root->getElementFromPoint(m_down_pos);
					/* we don't have a valid pointer element use last
					 * known pointer pos */
					translated.MouseInput.X = m_pointer.X;
					translated.MouseInput.Y = m_pointer.Y;

					/* reset down pos */
					m_down_pos = v2s32(0,0);
					break;
				default:
					dont_send_event = true;
					//this is not supposed to happen
					errorstream
					<< "GUIFormSpecMenu::preprocessEvent unexpected usecase Event="
					<< event.TouchInput.Event << std::endl;
			}
		} else if ( (event.TouchInput.touchedCount == 2) &&
				(event.TouchInput.Event == ETIE_PRESSED_DOWN) ) {
			hovered = root->getElementFromPoint(m_down_pos);

			translated.MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
			translated.MouseInput.ButtonStates = EMBSM_LEFT | EMBSM_RIGHT;
			translated.MouseInput.X = m_pointer.X;
			translated.MouseInput.Y = m_pointer.Y;

			if (hovered) {
				hovered->OnEvent(translated);
			}

			translated.MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
			translated.MouseInput.ButtonStates = EMBSM_LEFT;


			if (hovered) {
				hovered->OnEvent(translated);
			}
			dont_send_event = true;
		}
		/* ignore unhandled 2 touch events ... accidental moving for example */
		else if (event.TouchInput.touchedCount == 2) {
			dont_send_event = true;
		}
		else if (event.TouchInput.touchedCount > 2) {
			errorstream
			<< "GUIFormSpecMenu::preprocessEvent to many multitouch events "
			<< event.TouchInput.touchedCount << " ignoring them" << std::endl;
		}

		if (dont_send_event) {
			return true;
		}

		/* check if translated event needs to be preprocessed again */
		if (preprocessEvent(translated)) {
			return true;
		}
		if (hovered) {
			grab();
			bool retval = hovered->OnEvent(translated);

			if (event.TouchInput.Event == ETIE_LEFT_UP) {
				/* reset pointer */
				m_pointer = v2s32(0,0);
			}
			drop();
			return retval;
		}
	}
	#endif

	return false;
}

/******************************************************************************/
bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event)
{
	if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
		m_doubleclickdetect[0].pos  = m_doubleclickdetect[1].pos;
		m_doubleclickdetect[0].time = m_doubleclickdetect[1].time;

		m_doubleclickdetect[1].pos  = m_pointer;
		m_doubleclickdetect[1].time = getTimeMs();
	}
	else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
		u32 delta = porting::getDeltaMs(m_doubleclickdetect[0].time, getTimeMs());
		if (delta > 400) {
			return false;
		}

		double squaredistance =
				m_doubleclickdetect[0].pos
				.getDistanceFromSQ(m_doubleclickdetect[1].pos);

		if (squaredistance > (30*30)) {
			return false;
		}

		SEvent* translated = new SEvent();
		assert(translated != 0);
		//translate doubleclick to escape
		memset(translated, 0, sizeof(SEvent));
		translated->EventType = irr::EET_KEY_INPUT_EVENT;
		translated->KeyInput.Key         = KEY_ESCAPE;
		translated->KeyInput.Control     = false;
		translated->KeyInput.Shift       = false;
		translated->KeyInput.PressedDown = true;
		translated->KeyInput.Char        = 0;
		OnEvent(*translated);

		// no need to send the key up event as we're already deleted
		// and no one else did notice this event
		delete translated;
		return true;
	}
	return false;
}

bool GUIFormSpecMenu::OnEvent(const SEvent& event)
{
	if(event.EventType==EET_KEY_INPUT_EVENT) {
		KeyPress kp(event.KeyInput);
		if (event.KeyInput.PressedDown && ( (kp == EscapeKey) ||
			(kp == getKeySetting("keymap_inventory")) || (kp == CancelKey))) {
			if (m_allowclose) {
				doPause = false;
				acceptInput(quit_mode_cancel);
				quitMenu();
			} else {
				m_text_dst->gotText(narrow_to_wide("MenuQuit"));
			}
			return true;
		} else if (m_client != NULL && event.KeyInput.PressedDown &&
			(kp == getKeySetting("keymap_screenshot"))) {
				m_client->makeScreenshot(m_device);
		}
		if (event.KeyInput.PressedDown &&
			(event.KeyInput.Key==KEY_RETURN ||
			 event.KeyInput.Key==KEY_UP ||
			 event.KeyInput.Key==KEY_DOWN)
			) {
			switch (event.KeyInput.Key) {
				case KEY_RETURN:
					current_keys_pending.key_enter = true;
					break;
				case KEY_UP:
					current_keys_pending.key_up = true;
					break;
				case KEY_DOWN:
					current_keys_pending.key_down = true;
					break;
				break;
				default:
					//can't happen at all!
					assert("reached a source line that can't ever been reached" == 0);
					break;
			}
			if (current_keys_pending.key_enter && m_allowclose) {
				acceptInput(quit_mode_accept);
				quitMenu();
			} else {
				acceptInput();
			}
			return true;
		}

	}

	/* Mouse event other than movement, or crossing the border of inventory
	  field while holding right mouse button
	 */
	if (event.EventType == EET_MOUSE_INPUT_EVENT &&
			(event.MouseInput.Event != EMIE_MOUSE_MOVED ||
			 (event.MouseInput.Event == EMIE_MOUSE_MOVED &&
			  event.MouseInput.isRightPressed() &&
			  getItemAtPos(m_pointer).i != getItemAtPos(m_old_pointer).i))) {

		// Get selected item and hovered/clicked item (s)

		m_old_tooltip_id = -1;
		updateSelectedItem();
		ItemSpec s = getItemAtPos(m_pointer);

		Inventory *inv_selected = NULL;
		Inventory *inv_s = NULL;

		if(m_selected_item) {
			inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc);
			assert(inv_selected);
			assert(inv_selected->getList(m_selected_item->listname) != NULL);
		}

		u32 s_count = 0;

		if(s.isValid())
		do { // breakable
			inv_s = m_invmgr->getInventory(s.inventoryloc);

			if(!inv_s) {
				errorstream<<"InventoryMenu: The selected inventory location "
						<<"\""<<s.inventoryloc.dump()<<"\" doesn't exist"
						<<std::endl;
				s.i = -1;  // make it invalid again
				break;
			}

			InventoryList *list = inv_s->getList(s.listname);
			if(list == NULL) {
				verbosestream<<"InventoryMenu: The selected inventory list \""
						<<s.listname<<"\" does not exist"<<std::endl;
				s.i = -1;  // make it invalid again
				break;
			}

			if((u32)s.i >= list->getSize()) {
				infostream<<"InventoryMenu: The selected inventory list \""
						<<s.listname<<"\" is too small (i="<<s.i<<", size="
						<<list->getSize()<<")"<<std::endl;
				s.i = -1;  // make it invalid again
				break;
			}

			s_count = list->getItem(s.i).count;
		} while(0);

		bool identical = (m_selected_item != NULL) && s.isValid() &&
			(inv_selected == inv_s) &&
			(m_selected_item->listname == s.listname) &&
			(m_selected_item->i == s.i);

		// buttons: 0 = left, 1 = right, 2 = middle
		// up/down: 0 = down (press), 1 = up (release), 2 = unknown event, -1 movement
		int button = 0;
		int updown = 2;
		if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
			{ button = 0; updown = 0; }
		else if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
			{ button = 1; updown = 0; }
		else if(event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN)
			{ button = 2; updown = 0; }
		else if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
			{ button = 0; updown = 1; }
		else if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
			{ button = 1; updown = 1; }
		else if(event.MouseInput.Event == EMIE_MMOUSE_LEFT_UP)
			{ button = 2; updown = 1; }
		else if(event.MouseInput.Event == EMIE_MOUSE_MOVED)
			{ updown = -1;}

		// Set this number to a positive value to generate a move action
		// from m_selected_item to s.
		u32 move_amount = 0;

		// Set this number to a positive value to generate a drop action
		// from m_selected_item.
		u32 drop_amount = 0;

		// Set this number to a positive value to generate a craft action at s.
		u32 craft_amount = 0;

		if(updown == 0) {
			// Some mouse button has been pressed

			//infostream<<"Mouse button "<<button<<" pressed at p=("
			//	<<p.X<<","<<p.Y<<")"<<std::endl;

			m_selected_dragging = false;

			if(s.isValid() && s.listname == "craftpreview") {
				// Craft preview has been clicked: craft
				craft_amount = (button == 2 ? 10 : 1);
			}
			else if(m_selected_item == NULL) {
				if(s_count != 0) {
					// Non-empty stack has been clicked: select it
					m_selected_item = new ItemSpec(s);

					if(button == 1)  // right
						m_selected_amount = (s_count + 1) / 2;
					else if(button == 2)  // middle
						m_selected_amount = MYMIN(s_count, 10);
					else  // left
						m_selected_amount = s_count;

					m_selected_dragging = true;
					m_rmouse_auto_place = false;
				}
			}
			else { // m_selected_item != NULL
				assert(m_selected_amount >= 1);

				if(s.isValid()) {
					// Clicked a slot: move
					if(button == 1)  // right
						move_amount = 1;
					else if(button == 2)  // middle
						move_amount = MYMIN(m_selected_amount, 10);
					else  // left
						move_amount = m_selected_amount;

					if(identical) {
						if(move_amount >= m_selected_amount)
							m_selected_amount = 0;
						else
							m_selected_amount -= move_amount;
						move_amount = 0;
					}
				}
				else if (!getAbsoluteClippingRect().isPointInside(m_pointer)) {
					// Clicked outside of the window: drop
					if(button == 1)  // right
						drop_amount = 1;
					else if(button == 2)  // middle
						drop_amount = MYMIN(m_selected_amount, 10);
					else  // left
						drop_amount = m_selected_amount;
				}
			}
		}
		else if(updown == 1) {
			// Some mouse button has been released

			//infostream<<"Mouse button "<<button<<" released at p=("
			//	<<p.X<<","<<p.Y<<")"<<std::endl;

			if(m_selected_item != NULL && m_selected_dragging && s.isValid()) {
				if(!identical) {
					// Dragged to different slot: move all selected
					move_amount = m_selected_amount;
				}
			}
			else if(m_selected_item != NULL && m_selected_dragging &&
				!(getAbsoluteClippingRect().isPointInside(m_pointer))) {
				// Dragged outside of window: drop all selected
				drop_amount = m_selected_amount;
			}

			m_selected_dragging = false;
			// Keep count of how many times right mouse button has been
			// clicked. One click is drag without dropping. Click + release
			// + click changes to drop one item when moved mode
			if(button == 1 && m_selected_item != NULL)
				m_rmouse_auto_place = !m_rmouse_auto_place;
		}
		else if(updown == -1) {
			// Mouse has been moved and rmb is down and mouse pointer just
			// entered a new inventory field (checked in the entry-if, this
			// is the only action here that is generated by mouse movement)
			if(m_selected_item != NULL && s.isValid()){
				// Move 1 item
				// TODO: middle mouse to move 10 items might be handy
				if (m_rmouse_auto_place) {
					// Only move an item if the destination slot is empty
					// or contains the same item type as what is going to be
					// moved
					InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
					InventoryList *list_to = inv_s->getList(s.listname);
					assert(list_from && list_to);
					ItemStack stack_from = list_from->getItem(m_selected_item->i);
					ItemStack stack_to = list_to->getItem(s.i);
					if (stack_to.empty() || stack_to.name == stack_from.name)
						move_amount = 1;
				}
			}
		}

		// Possibly send inventory action to server
		if(move_amount > 0)
		{
			// Send IACTION_MOVE

			assert(m_selected_item && m_selected_item->isValid());
			assert(s.isValid());

			assert(inv_selected && inv_s);
			InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
			InventoryList *list_to = inv_s->getList(s.listname);
			assert(list_from && list_to);
			ItemStack stack_from = list_from->getItem(m_selected_item->i);
			ItemStack stack_to = list_to->getItem(s.i);

			// Check how many items can be moved
			move_amount = stack_from.count = MYMIN(move_amount, stack_from.count);
			ItemStack leftover = stack_to.addItem(stack_from, m_gamedef->idef());
			// If source stack cannot be added to destination stack at all,
			// they are swapped
			if ((leftover.count == stack_from.count) &&
					(leftover.name == stack_from.name)) {
				m_selected_amount = stack_to.count;
				// In case the server doesn't directly swap them but instead
				// moves stack_to somewhere else, set this
				m_selected_content_guess = stack_to;
				m_selected_content_guess_inventory = s.inventoryloc;
			}
			// Source stack goes fully into destination stack
			else if(leftover.empty()) {
				m_selected_amount -= move_amount;
				m_selected_content_guess = ItemStack(); // Clear
			}
			// Source stack goes partly into destination stack
			else {
				move_amount -= leftover.count;
				m_selected_amount -= move_amount;
				m_selected_content_guess = ItemStack(); // Clear
			}

			infostream<<"Handing IACTION_MOVE to manager"<<std::endl;
			IMoveAction *a = new IMoveAction();
			a->count = move_amount;
			a->from_inv = m_selected_item->inventoryloc;
			a->from_list = m_selected_item->listname;
			a->from_i = m_selected_item->i;
			a->to_inv = s.inventoryloc;
			a->to_list = s.listname;
			a->to_i = s.i;
			m_invmgr->inventoryAction(a);
		}
		else if(drop_amount > 0) {
			m_selected_content_guess = ItemStack(); // Clear

			// Send IACTION_DROP

			assert(m_selected_item && m_selected_item->isValid());
			assert(inv_selected);
			InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
			assert(list_from);
			ItemStack stack_from = list_from->getItem(m_selected_item->i);

			// Check how many items can be dropped
			drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count);
			assert(drop_amount > 0 && drop_amount <= m_selected_amount);
			m_selected_amount -= drop_amount;

			infostream<<"Handing IACTION_DROP to manager"<<std::endl;
			IDropAction *a = new IDropAction();
			a->count = drop_amount;
			a->from_inv = m_selected_item->inventoryloc;
			a->from_list = m_selected_item->listname;
			a->from_i = m_selected_item->i;
			m_invmgr->inventoryAction(a);
		}
		else if(craft_amount > 0) {
			m_selected_content_guess = ItemStack(); // Clear

			// Send IACTION_CRAFT

			assert(s.isValid());
			assert(inv_s);

			infostream<<"Handing IACTION_CRAFT to manager"<<std::endl;
			ICraftAction *a = new ICraftAction();
			a->count = craft_amount;
			a->craft_inv = s.inventoryloc;
			m_invmgr->inventoryAction(a);
		}

		// If m_selected_amount has been decreased to zero, deselect
		if(m_selected_amount == 0) {
			delete m_selected_item;
			m_selected_item = NULL;
			m_selected_amount = 0;
			m_selected_dragging = false;
			m_selected_content_guess = ItemStack();
		}
		m_old_pointer = m_pointer;
	}
	if(event.EventType==EET_GUI_EVENT) {

		if(event.GUIEvent.EventType==gui::EGET_TAB_CHANGED
				&& isVisible()) {
			// find the element that was clicked
			for(unsigned int i=0; i<m_fields.size(); i++) {
				FieldSpec &s = m_fields[i];
				if ((s.ftype == f_TabHeader) &&
						(s.fid == event.GUIEvent.Caller->getID())) {
					s.send = true;
					acceptInput();
					s.send = false;
					return true;
				}
			}
		}
		if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST
				&& isVisible()) {
			if(!canTakeFocus(event.GUIEvent.Element)) {
				infostream<<"GUIFormSpecMenu: Not allowing focus change."
						<<std::endl;
				// Returning true disables focus change
				return true;
			}
		}
		if((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) ||
				(event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
				(event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) ||
				(event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) {
			unsigned int btn_id = event.GUIEvent.Caller->getID();

			if (btn_id == 257) {
				if (m_allowclose) {
					acceptInput(quit_mode_accept);
					quitMenu();
				} else {
					acceptInput();
					m_text_dst->gotText(narrow_to_wide("ExitButton"));
				}
				// quitMenu deallocates menu
				return true;
			}

			// find the element that was clicked
			for(u32 i=0; i<m_fields.size(); i++) {
				FieldSpec &s = m_fields[i];
				// if its a button, set the send field so
				// lua knows which button was pressed
				if (((s.ftype == f_Button) || (s.ftype == f_CheckBox)) &&
						(s.fid == event.GUIEvent.Caller->getID())) {
					s.send = true;
					if(s.is_exit) {
						if (m_allowclose) {
							acceptInput(quit_mode_accept);
							quitMenu();
						} else {
							m_text_dst->gotText(narrow_to_wide("ExitButton"));
						}
						return true;
					} else {
						acceptInput(quit_mode_no);
						s.send = false;
						return true;
					}
				}
				else if ((s.ftype == f_DropDown) &&
						(s.fid == event.GUIEvent.Caller->getID())) {
					// only send the changed dropdown
					for(u32 i=0; i<m_fields.size(); i++) {
						FieldSpec &s2 = m_fields[i];
						if (s2.ftype == f_DropDown) {
							s2.send = false;
						}
					}
					s.send = true;
					acceptInput(quit_mode_no);

					// revert configuration to make sure dropdowns are sent on
					// regular button click
					for(u32 i=0; i<m_fields.size(); i++) {
						FieldSpec &s2 = m_fields[i];
						if (s2.ftype == f_DropDown) {
							s2.send = true;
						}
					}
					return true;
				}
				else if ((s.ftype == f_ScrollBar) &&
					(s.fid == event.GUIEvent.Caller->getID()))
				{
					s.fdefault = L"Changed";
					acceptInput(quit_mode_no);
					s.fdefault = L"";
				}
			}
		}

		if(event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
			if(event.GUIEvent.Caller->getID() > 257) {

				if (m_allowclose) {
					acceptInput(quit_mode_accept);
					quitMenu();
				} else {
					current_keys_pending.key_enter = true;
					acceptInput();
				}
				// quitMenu deallocates menu
				return true;
			}
		}

		if(event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) {
			int current_id = event.GUIEvent.Caller->getID();
			if(current_id > 257) {
				// find the element that was clicked
				for(u32 i=0; i<m_fields.size(); i++) {
					FieldSpec &s = m_fields[i];
					// if it's a table, set the send field
					// so lua knows which table was changed
					if ((s.ftype == f_Table) && (s.fid == current_id)) {
						s.send = true;
						acceptInput();
						s.send=false;
					}
				}
				return true;
			}
		}
	}

	return Parent ? Parent->OnEvent(event) : false;
}

/**
 * get name of element by element id
 * @param id of element
 * @return name string or empty string
 */
std::wstring GUIFormSpecMenu::getNameByID(s32 id)
{
	for(std::vector<FieldSpec>::iterator iter =  m_fields.begin();
				iter != m_fields.end(); iter++) {
		if (iter->fid == id) {
			return iter->fname;
		}
	}
	return L"";
}

/**
 * get label of element by id
 * @param id of element
 * @return label string or empty string
 */
std::wstring GUIFormSpecMenu::getLabelByID(s32 id)
{
	for(std::vector<FieldSpec>::iterator iter =  m_fields.begin();
				iter != m_fields.end(); iter++) {
		if (iter->fid == id) {
			return iter->flabel;
		}
	}
	return L"";
}