/*
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 "tile.h" // ITextureSource
#include "hud.h" // drawItemStack
#include "hex.h"
#include "util/string.h"
#include "util/numeric.h"
#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 "util/string.h" // for parseColorString()
#include "fontengine.h"
#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;
}
|