Add formspec table

This commit is contained in:
Kahrl 2013-08-23 12:24:11 +02:00 committed by ShadowNinja
parent 2b1eff7725
commit 8966c16ad2
13 changed files with 1798 additions and 246 deletions

@ -31,7 +31,7 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function gamemgr.handle_games_buttons(fields) function gamemgr.handle_games_buttons(fields)
if fields["gamelist"] ~= nil then if fields["gamelist"] ~= nil then
local event = explode_textlist_event(fields["gamelist"]) local event = engine.explode_textlist_event(fields["gamelist"])
gamemgr.selected_game = event.index gamemgr.selected_game = event.index
end end

@ -459,8 +459,8 @@ function tabbuilder.handle_multiplayer_buttons(fields)
end end
if fields["favourites"] ~= nil then if fields["favourites"] ~= nil then
local event = explode_textlist_event(fields["favourites"]) local event = engine.explode_textlist_event(fields["favourites"])
if event.typ == "DCL" then if event.type == "DCL" then
if event.index <= #menu.favorites then if event.index <= #menu.favorites then
gamedata.address = menu.favorites[event.index].address gamedata.address = menu.favorites[event.index].address
gamedata.port = menu.favorites[event.index].port gamedata.port = menu.favorites[event.index].port
@ -484,7 +484,7 @@ function tabbuilder.handle_multiplayer_buttons(fields)
end end
end end
if event.typ == "CHG" then if event.type == "CHG" then
if event.index <= #menu.favorites then if event.index <= #menu.favorites then
local address = menu.favorites[event.index].address local address = menu.favorites[event.index].address
local port = menu.favorites[event.index].port local port = menu.favorites[event.index].port
@ -586,12 +586,12 @@ function tabbuilder.handle_server_buttons(fields)
local world_doubleclick = false local world_doubleclick = false
if fields["srv_worlds"] ~= nil then if fields["srv_worlds"] ~= nil then
local event = explode_textlist_event(fields["srv_worlds"]) local event = engine.explode_textlist_event(fields["srv_worlds"])
if event.typ == "DCL" then if event.type == "DCL" then
world_doubleclick = true world_doubleclick = true
end end
if event.typ == "CHG" then if event.type == "CHG" then
engine.setting_set("mainmenu_last_selected_world", engine.setting_set("mainmenu_last_selected_world",
filterlist.get_raw_index(worldlist,engine.get_textlist_index("srv_worlds"))) filterlist.get_raw_index(worldlist,engine.get_textlist_index("srv_worlds")))
end end
@ -737,13 +737,13 @@ function tabbuilder.handle_singleplayer_buttons(fields)
local world_doubleclick = false local world_doubleclick = false
if fields["sp_worlds"] ~= nil then if fields["sp_worlds"] ~= nil then
local event = explode_textlist_event(fields["sp_worlds"]) local event = engine.explode_textlist_event(fields["sp_worlds"])
if event.typ == "DCL" then if event.type == "DCL" then
world_doubleclick = true world_doubleclick = true
end end
if event.typ == "CHG" then if event.type == "CHG" then
engine.setting_set("mainmenu_last_selected_world", engine.setting_set("mainmenu_last_selected_world",
filterlist.get_raw_index(worldlist,engine.get_textlist_index("sp_worlds"))) filterlist.get_raw_index(worldlist,engine.get_textlist_index("sp_worlds")))
end end
@ -813,8 +813,8 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function tabbuilder.handle_texture_pack_buttons(fields) function tabbuilder.handle_texture_pack_buttons(fields)
if fields["TPs"] ~= nil then if fields["TPs"] ~= nil then
local event = explode_textlist_event(fields["TPs"]) local event = engine.explode_textlist_event(fields["TPs"])
if event.typ == "CHG" or event.typ=="DCL" then if event.type == "CHG" or event.type == "DCL" then
local index = engine.get_textlist_index("TPs") local index = engine.get_textlist_index("TPs")
engine.setting_set("mainmenu_last_selected_TP", engine.setting_set("mainmenu_last_selected_TP",
index) index)

@ -115,26 +115,6 @@ function math.hypot(x, y)
return x * math.sqrt(1 + t * t) return x * math.sqrt(1 + t * t)
end end
--------------------------------------------------------------------------------
function explode_textlist_event(text)
local retval = {}
retval.typ = "INV"
local parts = text:split(":")
if #parts == 2 then
retval.typ = parts[1]:trim()
retval.index= tonumber(parts[2]:trim())
if type(retval.index) ~= "number" then
retval.typ = "INV"
end
end
return retval
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function get_last_folder(text,count) function get_last_folder(text,count)
local parts = text:split(DIR_DELIM) local parts = text:split(DIR_DELIM)
@ -368,6 +348,37 @@ if minetest then
end end
end end
--------------------------------------------------------------------------------
function tbl.explode_table_event(evt)
if evt ~= nil then
local parts = evt:split(":")
if #parts == 3 then
local t = parts[1]:trim()
local r = tonumber(parts[2]:trim())
local c = tonumber(parts[3]:trim())
if type(r) == "number" and type(c) == "number" and t ~= "INV" then
return {type=t, row=r, column=c}
end
end
end
return {type="INV", row=0, column=0}
end
--------------------------------------------------------------------------------
function tbl.explode_textlist_event(evt)
if evt ~= nil then
local parts = evt:split(":")
if #parts == 2 then
local t = parts[1]:trim()
local r = tonumber(parts[2]:trim())
if type(r) == "number" and t ~= "INV" then
return {type=t, index=r}
end
end
end
return {type="INV", index=0}
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- mainmenu only functions -- mainmenu only functions
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

@ -572,7 +572,7 @@ function modmgr.handle_modmgr_buttons(fields)
} }
if fields["modlist"] ~= nil then if fields["modlist"] ~= nil then
local event = explode_textlist_event(fields["modlist"]) local event = engine.explode_textlist_event(fields["modlist"])
modmgr.selected_mod = event.index modmgr.selected_mod = event.index
end end
@ -693,10 +693,10 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function modmgr.handle_configure_world_buttons(fields) function modmgr.handle_configure_world_buttons(fields)
if fields["world_config_modlist"] ~= nil then if fields["world_config_modlist"] ~= nil then
local event = explode_textlist_event(fields["world_config_modlist"]) local event = engine.explode_textlist_event(fields["world_config_modlist"])
modmgr.world_config_selected_mod = event.index modmgr.world_config_selected_mod = event.index
if event.typ == "DCL" then if event.type == "DCL" then
modmgr.world_config_enable_mod(nil) modmgr.world_config_enable_mod(nil)
end end
end end

@ -1011,6 +1011,7 @@ textlist[<X>,<Y>;<W>,<H>;<name>;<listelem 1>,<listelem 2>,...,<listelem n>;<sele
^ if you want a listelement to start with # write ## ^ if you want a listelement to start with # write ##
^ index to be selected within textlist ^ index to be selected within textlist
^ true/false draw transparent background ^ true/false draw transparent background
^ see also minetest.explode_textlist_event (main menu: engine.explode_textlist_event)
tabheader[<X>,<Y>;<name>;<caption 1>,<caption 2>,...,<caption n>;<current_tab>;<transparent>;<draw_border>] tabheader[<X>,<Y>;<name>;<caption 1>,<caption 2>,...,<caption n>;<current_tab>;<transparent>;<draw_border>]
^ show a tabHEADER at specific position (ignores formsize) ^ show a tabHEADER at specific position (ignores formsize)
@ -1043,6 +1044,57 @@ checkbox[<X>,<Y>;<name>;<label>;<selected>]
^ label to be shown left of checkbox ^ label to be shown left of checkbox
^ selected (optional) true/false ^ selected (optional) true/false
table[<X>,<Y>;<W>,<H>;<name>;<cell 1>,<cell 2>,...,<cell n>;<selected idx>]
^ show scrollable table using options defined by the previous tableoptions[]
^ displays cells as defined by the previous tablecolumns[]
^ x and y position the itemlist relative to the top left of the menu
^ w and h are the size of the itemlist
^ name fieldname sent to server on row select or doubleclick
^ cell 1...n cell contents given in row-major order
^ selected idx: index of row to be selected within table (first row = 1)
^ see also minetest.explode_table_event (main menu: engine.explode_table_event)
tableoptions[<opt 1>;<opt 2>;...]
^ sets options for table[]:
^ color=#RRGGBB
^^ default text color (HEX-Color), defaults to #FFFFFF
^ background=#RRGGBB
^^ table background color (HEX-Color), defaults to #000000
^ border=<true/false>
^^ should the table be drawn with a border? (default true)
^ highlight=#RRGGBB
^^ highlight background color (HEX-Color), defaults to #466432
^ highlight_text=#RRGGBB
^^ highlight text color (HEX-Color), defaults to #FFFFFF
^ opendepth=<value>
^^ all subtrees up to depth < value are open (default value = 0)
^^ only useful when there is a column of type "tree"
tablecolumns[<type 1>,<opt 1a>,<opt 1b>,...;<type 2>,<opt 2a>,<opt 2b>;...]
^ sets columns for table[]:
^ types: text, image, color, indent, tree
^^ text: show cell contents as text
^^ image: cell contents are an image index, use column options to define images
^^ color: cell contents are a HEX-Color and define color of following cell
^^ indent: cell contents are a number and define indentation of following cell
^^ tree: same as indent, but user can open and close subtrees (treeview-like)
^ column options:
^^ align=<value> for "text" and "image": content alignment within cells
^^ available values: left (default), center, right, inline
^^ width=<value> for "text" and "image": minimum width in em (default 0)
^^ for "indent" and "tree": indent width in em (default 1.5)
^^ padding=<value> padding left of the column, in em (default 0.5)
^^ exception: defaults to 0 for indent columns
^^ tooltip=<value> tooltip text (default empty)
^ "image" column options:
^^ 0=<value> sets image for image index 0
^^ 1=<value> sets image for image index 1
^^ 2=<value> sets image for image index 2
^^ and so on; defined indices need not be contiguous
^^ empty or non-numeric cells are treated as 0
^ "color" column options:
^^ span=<value> number of following columns to affect (default infinite)
Note: do NOT use a element name starting with "key_" those names are reserved to Note: do NOT use a element name starting with "key_" those names are reserved to
pass key press events to formspec! pass key press events to formspec!
@ -1346,11 +1398,21 @@ minetest.get_inventory(location) -> InvRef
minetest.create_detached_inventory(name, callbacks) -> InvRef minetest.create_detached_inventory(name, callbacks) -> InvRef
^ callbacks: See "Detached inventory callbacks" ^ callbacks: See "Detached inventory callbacks"
^ Creates a detached inventory. If it already exists, it is cleared. ^ Creates a detached inventory. If it already exists, it is cleared.
Formspec:
minetest.show_formspec(playername, formname, formspec) minetest.show_formspec(playername, formname, formspec)
^ playername: name of player to show formspec ^ playername: name of player to show formspec
^ formname: name passed to on_player_receive_fields callbacks ^ formname: name passed to on_player_receive_fields callbacks
^ should follow "modname:<whatever>" naming convention ^ should follow "modname:<whatever>" naming convention
^ formspec: formspec to display ^ formspec: formspec to display
minetest.formspec_escape(string) -> string
^ escapes characters [ ] \ , ; that can not be used in formspecs
minetest.explode_table_event(string) -> table
^ returns e.g. {type="CHG", row=1, column=2}
^ type: "INV" (no row selected), "CHG" (selected) or "DCL" (double-click)
minetest.explode_textlist_event(string) -> table
^ returns e.g. {type="CHG", index=1}
^ type: "INV" (no row selected), "CHG" (selected) or "DCL" (double-click)
Item handling: Item handling:
minetest.inventorycube(img1, img2, img3) minetest.inventorycube(img1, img2, img3)

@ -89,12 +89,33 @@ engine.sound_play(spec, looped) -> handle
^ looped = bool ^ looped = bool
engine.sound_stop(handle) engine.sound_stop(handle)
GUI: Formspec:
engine.update_formspec(formspec) engine.update_formspec(formspec)
- engine.set_background(type, texturepath) engine.get_table_index(tablename) -> index
^ can also handle textlists
engine.formspec_escape(string) -> string
^ escapes characters [ ] \ , ; that can not be used in formspecs
engine.explode_table_event(string) -> table
^ returns e.g. {type="CHG", row=1, column=2}
^ type: "INV" (no row selected), "CHG" (selected) or "DCL" (double-click)
engine.explode_textlist_event(string) -> table
^ returns e.g. {type="CHG", index=1}
^ type: "INV" (no row selected), "CHG" (selected) or "DCL" (double-click)
GUI:
engine.set_background(type, texturepath)
^ type: "background", "overlay", "header" or "footer" ^ type: "background", "overlay", "header" or "footer"
engine.set_clouds(<true/false>) engine.set_clouds(<true/false>)
engine.set_topleft_text(text) engine.set_topleft_text(text)
engine.show_keys_menu()
engine.file_open_dialog(formname,caption)
^ shows a file open dialog
^ formname is base name of dialog response returned in fields
^ -if dialog was accepted "_accepted"
^^ will be added to fieldname containing the path
^ -if dialog was canceled "_cancelled"
^ will be added to fieldname value is set to formname itself
^ returns nil or selected file/folder
Games: Games:
engine.get_game(index) engine.get_game(index)
@ -155,22 +176,7 @@ engine.get_worlds() -> list of worlds (possible in async calls)
engine.create_world(worldname, gameid) engine.create_world(worldname, gameid)
engine.delete_world(index) engine.delete_world(index)
UI:
engine.get_textlist_index(textlistname) -> index
engine.show_keys_menu()
engine.file_open_dialog(formname,caption)
^ shows a file open dialog
^ formname is base name of dialog response returned in fields
^ -if dialog was accepted "_accepted"
^^ will be added to fieldname containing the path
^ -if dialog was canceled "_cancelled"
^ will be added to fieldname value is set to formname itself
^ returns nil or selected file/folder
Helpers: Helpers:
engine.formspec_escape(string) -> string
^ escapes characters [ ] \ , ; that can not be used in formspecs
engine.gettext(string) -> string engine.gettext(string) -> string
^ look up the translation of a string in the gettext message catalog ^ look up the translation of a string in the gettext message catalog
fgettext(string, ...) -> string fgettext(string, ...) -> string

@ -374,6 +374,7 @@ set(minetest_SRCS
guiMessageMenu.cpp guiMessageMenu.cpp
guiTextInputMenu.cpp guiTextInputMenu.cpp
guiFormSpecMenu.cpp guiFormSpecMenu.cpp
guiTable.cpp
guiPauseMenu.cpp guiPauseMenu.cpp
guiPasswordChange.cpp guiPasswordChange.cpp
guiVolumeChange.cpp guiVolumeChange.cpp

@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <sstream> #include <sstream>
#include <limits> #include <limits>
#include "guiFormSpecMenu.h" #include "guiFormSpecMenu.h"
#include "guiTable.h"
#include "constants.h" #include "constants.h"
#include "gamedef.h" #include "gamedef.h"
#include "keycode.h" #include "keycode.h"
@ -33,9 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <IGUIButton.h> #include <IGUIButton.h>
#include <IGUIStaticText.h> #include <IGUIStaticText.h>
#include <IGUIFont.h> #include <IGUIFont.h>
#include <IGUIListBox.h>
#include <IGUITabControl.h> #include <IGUITabControl.h>
#include <IGUIScrollBar.h>
#include <IGUIComboBox.h> #include <IGUIComboBox.h>
#include "log.h" #include "log.h"
#include "tile.h" // ITextureSource #include "tile.h" // ITextureSource
@ -83,10 +82,6 @@ GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev,
m_selected_item(NULL), m_selected_item(NULL),
m_selected_amount(0), m_selected_amount(0),
m_selected_dragging(false), m_selected_dragging(false),
m_listbox_click_fname(),
m_listbox_click_index(-1),
m_listbox_click_time(0),
m_listbox_doubleclick(false),
m_tooltip_element(NULL), m_tooltip_element(NULL),
m_allowclose(true), m_allowclose(true),
m_lock(false) m_lock(false)
@ -142,7 +137,7 @@ void GUIFormSpecMenu::setInitialFocus()
// Set initial focus according to following order of precedence: // Set initial focus according to following order of precedence:
// 1. first empty editbox // 1. first empty editbox
// 2. first editbox // 2. first editbox
// 3. first listbox // 3. first table
// 4. last button // 4. last button
// 5. first focusable (not statictext, not tabheader) // 5. first focusable (not statictext, not tabheader)
// 6. first child element // 6. first child element
@ -177,10 +172,10 @@ void GUIFormSpecMenu::setInitialFocus()
} }
} }
// 3. first listbox // 3. first table
for (core::list<gui::IGUIElement*>::Iterator it = children.begin(); for (core::list<gui::IGUIElement*>::Iterator it = children.begin();
it != children.end(); ++it) { it != children.end(); ++it) {
if ((*it)->getType() == gui::EGUIET_LIST_BOX) { if ((*it)->getTypeName() == std::string("GUITable")) {
Environment->setFocus(*it); Environment->setFocus(*it);
return; return;
} }
@ -212,86 +207,13 @@ void GUIFormSpecMenu::setInitialFocus()
Environment->setFocus(*(children.begin())); Environment->setFocus(*(children.begin()));
} }
int GUIFormSpecMenu::getListboxIndex(std::string listboxname) { GUITable* GUIFormSpecMenu::getTable(std::wstring tablename)
std::wstring wlistboxname = narrow_to_wide(listboxname.c_str());
for(unsigned int i=0; i < m_listboxes.size(); i++) {
std::wstring name(m_listboxes[i].first.fname.c_str());
if ( name == wlistboxname) {
return m_listboxes[i].second->getSelected();
}
}
return -1;
}
bool GUIFormSpecMenu::checkListboxClick(std::wstring wlistboxname,
int eventtype)
{ {
// WARNING: BLACK IRRLICHT MAGIC for (u32 i = 0; i < m_tables.size(); ++i) {
// Used to fix Irrlicht's subpar reporting of single clicks and double if (tablename == m_tables[i].first.fname)
// clicks in listboxes (gui::EGET_LISTBOX_CHANGED, return m_tables[i].second;
// gui::EGET_LISTBOX_SELECTED_AGAIN):
// 1. IGUIListBox::setSelected() is counted as a click.
// Including the initial setSelected() done by parseTextList().
// 2. Clicking on a the selected item and then dragging for less
// than 500ms is counted as a doubleclick, no matter when the
// item was previously selected (e.g. more than 500ms ago)
// So when Irrlicht reports a doubleclick, we need to check
// for ourselves if really was a doubleclick. Or just a fake.
for(unsigned int i=0; i < m_listboxes.size(); i++) {
std::wstring name(m_listboxes[i].first.fname.c_str());
int selected = m_listboxes[i].second->getSelected();
if (name == wlistboxname && selected >= 0) {
u32 now = getTimeMs();
bool doubleclick =
(eventtype == gui::EGET_LISTBOX_SELECTED_AGAIN)
&& (name == m_listbox_click_fname)
&& (selected == m_listbox_click_index)
&& (m_listbox_click_time >= now - 500);
m_listbox_click_fname = name;
m_listbox_click_index = selected;
m_listbox_click_time = now;
m_listbox_doubleclick = doubleclick;
return true;
} }
} return 0;
return false;
}
gui::IGUIScrollBar* GUIFormSpecMenu::getListboxScrollbar(
gui::IGUIListBox *listbox)
{
// WARNING: BLACK IRRLICHT MAGIC
// Ordinarily, due to how formspecs work (recreating the entire GUI
// when something changes), when you select an item in a textlist
// with more items than fit in the visible area, the newly selected
// item is scrolled to the bottom of the visible area. This is
// annoying and breaks GUI designs that use double clicks.
// This function helps fixing this problem by giving direct access
// to a listbox's scrollbar. This works because CGUIListBox doesn't
// cache the scrollbar position anywhere.
// If this stops working in a future irrlicht version, consider
// maintaining a local copy of irr::gui::CGUIListBox, possibly also
// fixing the other reasons why black irrlicht magic is needed.
core::list<gui::IGUIElement*> children = listbox->getChildren();
for(core::list<gui::IGUIElement*>::Iterator it = children.begin();
it != children.end(); ++it) {
gui::IGUIElement* child = *it;
if (child && child->getType() == gui::EGUIET_SCROLL_BAR) {
return static_cast<gui::IGUIScrollBar*>(child);
}
}
verbosestream<<"getListboxScrollbar: WARNING: "
<<"listbox has no scrollbar"<<std::endl;
return NULL;
} }
std::vector<std::string> split(const std::string &s, char delim) { std::vector<std::string> split(const std::string &s, char delim) {
@ -643,10 +565,109 @@ void GUIFormSpecMenu::parseBackground(parserData* data,std::string element) {
errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'" << std::endl; 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)) {
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.c_str());
FieldSpec spec = FieldSpec(
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);
e->drop(); // IGUIElement maintains the remaining reference
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) { void GUIFormSpecMenu::parseTextList(parserData* data,std::string element) {
std::vector<std::string> parts = split(element,';'); std::vector<std::string> parts = split(element,';');
if ((parts.size() == 5) || (parts.size() == 6)) { if ((parts.size() == 4) || (parts.size() == 5) || (parts.size() == 6)) {
std::vector<std::string> v_pos = split(parts[0],','); std::vector<std::string> v_pos = split(parts[0],',');
std::vector<std::string> v_geom = split(parts[1],','); std::vector<std::string> v_geom = split(parts[1],',');
std::string name = parts[2]; std::string name = parts[2];
@ -683,63 +704,32 @@ void GUIFormSpecMenu::parseTextList(parserData* data,std::string element) {
258+m_fields.size() 258+m_fields.size()
); );
spec.ftype = f_ListBox; spec.ftype = f_Table;
for (unsigned int i = 0; i < items.size(); ++i) {
items[i] = unescape_string(items[i]);
}
//now really show list //now really show list
gui::IGUIListBox *e = Environment->addListBox(rect, this,spec.fid); GUITable *e = new GUITable(Environment, this, spec.fid, rect,
m_tsrc);
e->drop(); // IGUIElement maintains the remaining reference
if (spec.fname == data->focused_fieldname) { if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e); Environment->setFocus(e);
} }
if (str_transparent == "false") e->setTextList(items, is_yes(str_transparent));
e->setDrawBackground(true);
for (unsigned int i=0; i < items.size(); i++) { if (data->table_dyndata.find(fname_w) != data->table_dyndata.end()) {
if (items[i].c_str()[0] == '#') { e->setDynamicData(data->table_dyndata[fname_w]);
if (items[i].c_str()[1] == '#') {
e->addItem(narrow_to_wide(unescape_string(items[i])).c_str() +1);
}
else {
std::string color = items[i].substr(0,7);
std::wstring toadd =
narrow_to_wide(unescape_string(items[i]).c_str() + 7);
e->addItem(toadd.c_str());
video::SColor tmp_color;
if (parseColor(color, tmp_color, false))
e->setItemOverrideColor(i,tmp_color);
}
}
else {
e->addItem(narrow_to_wide(unescape_string(items[i])).c_str());
}
}
if (data->listbox_selections.find(fname_w) != data->listbox_selections.end()) {
e->setSelected(data->listbox_selections[fname_w]);
}
if (data->listbox_scroll.find(fname_w) != data->listbox_scroll.end()) {
gui::IGUIScrollBar *scrollbar = getListboxScrollbar(e);
if (scrollbar) {
scrollbar->setPos(data->listbox_scroll[fname_w]);
}
}
else {
gui::IGUIScrollBar *scrollbar = getListboxScrollbar(e);
if (scrollbar) {
scrollbar->setPos(0);
}
} }
if ((str_initial_selection != "") && if ((str_initial_selection != "") &&
(str_initial_selection != "0")) (str_initial_selection != "0"))
e->setSelected(stoi(str_initial_selection.c_str())-1); e->setSelected(stoi(str_initial_selection.c_str()));
m_listboxes.push_back(std::pair<FieldSpec,gui::IGUIListBox*>(spec,e)); m_tables.push_back(std::pair<FieldSpec,GUITable*>(spec, e));
m_fields.push_back(spec); m_fields.push_back(spec);
return; return;
} }
@ -1478,6 +1468,21 @@ void GUIFormSpecMenu::parseElement(parserData* data,std::string element) {
return; 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"){ if (type == "textlist"){
parseTextList(data,description); parseTextList(data,description);
return; return;
@ -1550,20 +1555,11 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
{ {
parserData mydata; parserData mydata;
//preserve listboxes //preserve tables
for (unsigned int i = 0; i < m_listboxes.size(); i++) { for (u32 i = 0; i < m_tables.size(); ++i) {
std::wstring listboxname = m_listboxes[i].first.fname; std::wstring tablename = m_tables[i].first.fname;
gui::IGUIListBox *listbox = m_listboxes[i].second; GUITable *table = m_tables[i].second;
mydata.table_dyndata[tablename] = table->getDynamicData();
int selection = listbox->getSelected();
if (selection != -1) {
mydata.listbox_selections[listboxname] = selection;
}
gui::IGUIScrollBar *scrollbar = getListboxScrollbar(listbox);
if (scrollbar) {
mydata.listbox_scroll[listboxname] = scrollbar->getPos();
}
} }
//preserve focus //preserve focus
@ -1603,7 +1599,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
m_images.clear(); m_images.clear();
m_backgrounds.clear(); m_backgrounds.clear();
m_itemimages.clear(); m_itemimages.clear();
m_listboxes.clear(); m_tables.clear();
m_checkboxes.clear(); m_checkboxes.clear();
m_fields.clear(); m_fields.clear();
m_boxes.clear(); m_boxes.clear();
@ -2175,17 +2171,12 @@ void GUIFormSpecMenu::acceptInput(bool quit=false)
{ {
fields[wide_to_narrow(s.fname.c_str())] = wide_to_narrow(s.flabel.c_str()); fields[wide_to_narrow(s.fname.c_str())] = wide_to_narrow(s.flabel.c_str());
} }
else if(s.ftype == f_ListBox) { else if(s.ftype == f_Table) {
std::stringstream ss; GUITable *table = getTable(s.fname);
if (table) {
if (m_listbox_doubleclick) { fields[wide_to_narrow(s.fname.c_str())]
ss << "DCL:"; = table->checkEvent();
} }
else {
ss << "CHG:";
}
ss << (getListboxIndex(wide_to_narrow(s.fname.c_str()))+1);
fields[wide_to_narrow(s.fname.c_str())] = ss.str();
} }
else if(s.ftype == f_DropDown) { else if(s.ftype == f_DropDown) {
// no dynamic cast possible due to some distributions shipped // no dynamic cast possible due to some distributions shipped
@ -2249,7 +2240,7 @@ void GUIFormSpecMenu::acceptInput(bool quit=false)
bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
{ {
// Fix Esc/Return key being eaten by checkboxen and listboxen // Fix Esc/Return key being eaten by checkboxen and tables
if(event.EventType==EET_KEY_INPUT_EVENT) if(event.EventType==EET_KEY_INPUT_EVENT)
{ {
KeyPress kp(event.KeyInput); KeyPress kp(event.KeyInput);
@ -2706,8 +2697,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} }
} }
if((event.GUIEvent.EventType==gui::EGET_LISTBOX_SELECTED_AGAIN) || if(event.GUIEvent.EventType==gui::EGET_TABLE_CHANGED)
(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED))
{ {
int current_id = event.GUIEvent.Caller->getID(); int current_id = event.GUIEvent.Caller->getID();
if(current_id > 257) if(current_id > 257)
@ -2716,13 +2706,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
for(u32 i=0; i<m_fields.size(); i++) for(u32 i=0; i<m_fields.size(); i++)
{ {
FieldSpec &s = m_fields[i]; FieldSpec &s = m_fields[i];
// if its a listbox, set the send field so // if it's a table, set the send field
// lua knows which listbox was changed // so lua knows which table was changed
// checkListboxClick() is black magic if ((s.ftype == f_Table) && (s.fid == current_id))
// for properly handling double clicks
if ((s.ftype == f_ListBox) && (s.fid == current_id)
&& checkListboxClick(s.fname,
event.GUIEvent.EventType))
{ {
s.send = true; s.send = true;
acceptInput(); acceptInput();
@ -2737,7 +2723,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
return Parent ? Parent->OnEvent(event) : false; return Parent ? Parent->OnEvent(event) : false;
} }
bool GUIFormSpecMenu::parseColor(std::string &value, video::SColor &color, bool quiet) bool GUIFormSpecMenu::parseColor(const std::string &value, video::SColor &color, bool quiet)
{ {
const char *hexpattern = NULL; const char *hexpattern = NULL;
if (value[0] == '#') { if (value[0] == '#') {

@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "inventory.h" #include "inventory.h"
#include "inventorymanager.h" #include "inventorymanager.h"
#include "modalMenu.h" #include "modalMenu.h"
#include "guiTable.h"
class IGameDef; class IGameDef;
class InventoryManager; class InventoryManager;
@ -34,7 +35,7 @@ class ISimpleTextureSource;
typedef enum { typedef enum {
f_Button, f_Button,
f_ListBox, f_Table,
f_TabHeader, f_TabHeader,
f_CheckBox, f_CheckBox,
f_DropDown, f_DropDown,
@ -231,7 +232,10 @@ public:
bool preprocessEvent(const SEvent& event); bool preprocessEvent(const SEvent& event);
bool OnEvent(const SEvent& event); bool OnEvent(const SEvent& event);
int getListboxIndex(std::string listboxname); GUITable* getTable(std::wstring tablename);
static bool parseColor(const std::string &value,
video::SColor &color, bool quiet);
protected: protected:
v2s32 getBasePos() const v2s32 getBasePos() const
@ -260,7 +264,7 @@ protected:
std::vector<ImageDrawSpec> m_itemimages; std::vector<ImageDrawSpec> m_itemimages;
std::vector<BoxDrawSpec> m_boxes; std::vector<BoxDrawSpec> m_boxes;
std::vector<FieldSpec> m_fields; std::vector<FieldSpec> m_fields;
std::vector<std::pair<FieldSpec,gui::IGUIListBox*> > m_listboxes; std::vector<std::pair<FieldSpec,GUITable*> > m_tables;
std::vector<std::pair<FieldSpec,gui::IGUICheckBox*> > m_checkboxes; std::vector<std::pair<FieldSpec,gui::IGUICheckBox*> > m_checkboxes;
ItemSpec *m_selected_item; ItemSpec *m_selected_item;
@ -273,12 +277,6 @@ protected:
ItemStack m_selected_content_guess; ItemStack m_selected_content_guess;
InventoryLocation m_selected_content_guess_inventory; InventoryLocation m_selected_content_guess_inventory;
// WARNING: BLACK IRRLICHT MAGIC, see checkListboxClick()
std::wstring m_listbox_click_fname;
int m_listbox_click_index;
u32 m_listbox_click_time;
bool m_listbox_doubleclick;
v2s32 m_pointer; v2s32 m_pointer;
gui::IGUIStaticText *m_tooltip_element; gui::IGUIStaticText *m_tooltip_element;
@ -302,8 +300,10 @@ private:
int bp_set; int bp_set;
v2u32 screensize; v2u32 screensize;
std::wstring focused_fieldname; std::wstring focused_fieldname;
std::map<std::wstring,int> listbox_selections; GUITable::TableOptions table_options;
std::map<std::wstring,int> listbox_scroll; GUITable::TableColumns table_columns;
// used to restore table selection/scroll/treeview state
std::map<std::wstring,GUITable::DynamicData> table_dyndata;
} parserData; } parserData;
typedef struct { typedef struct {
@ -315,12 +315,6 @@ private:
fs_key_pendig current_keys_pending; fs_key_pendig current_keys_pending;
// Determine whether listbox click was double click
// (Using some black Irrlicht magic)
bool checkListboxClick(std::wstring wlistboxname, int eventtype);
gui::IGUIScrollBar* getListboxScrollbar(gui::IGUIListBox *listbox);
void parseElement(parserData* data,std::string element); void parseElement(parserData* data,std::string element);
void parseSize(parserData* data,std::string element); void parseSize(parserData* data,std::string element);
@ -330,6 +324,9 @@ private:
void parseItemImage(parserData* data,std::string element); void parseItemImage(parserData* data,std::string element);
void parseButton(parserData* data,std::string element,std::string typ); void parseButton(parserData* data,std::string element,std::string typ);
void parseBackground(parserData* data,std::string element); void parseBackground(parserData* data,std::string element);
void parseTableOptions(parserData* data,std::string element);
void parseTableColumns(parserData* data,std::string element);
void parseTable(parserData* data,std::string element);
void parseTextList(parserData* data,std::string element); void parseTextList(parserData* data,std::string element);
void parseDropDown(parserData* data,std::string element); void parseDropDown(parserData* data,std::string element);
void parsePwdField(parserData* data,std::string element); void parsePwdField(parserData* data,std::string element);
@ -344,8 +341,6 @@ private:
void parseBox(parserData* data,std::string element); void parseBox(parserData* data,std::string element);
void parseBackgroundColor(parserData* data,std::string element); void parseBackgroundColor(parserData* data,std::string element);
void parseListColors(parserData* data,std::string element); void parseListColors(parserData* data,std::string element);
bool parseColor(std::string &value, video::SColor &color, bool quiet);
}; };
class FormspecFormSource: public IFormSource class FormspecFormSource: public IFormSource

1212
src/guiTable.cpp Normal file

File diff suppressed because it is too large Load Diff

269
src/guiTable.h Normal file

@ -0,0 +1,269 @@
/*
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.
*/
#ifndef GUITABLE_HEADER
#define GUITABLE_HEADER
#include <map>
#include <set>
#include <string>
#include <vector>
#include <iostream>
#include "irrlichttypes_extrabloated.h"
class ISimpleTextureSource;
/*
A table GUI element for GUIFormSpecMenu.
Sends a EGET_TABLE_CHANGED event to the parent when
an item is selected or double-clicked.
Call checkEvent() to get info.
Credits: The interface and implementation of this class are (very)
loosely based on the Irrlicht classes CGUITable and CGUIListBox.
CGUITable and CGUIListBox are licensed under the Irrlicht license;
they are Copyright (C) 2002-2012 Nikolaus Gebhardt
*/
class GUITable : public gui::IGUIElement
{
public:
/*
Stores dynamic data that should be preserved
when updating a formspec
*/
struct DynamicData
{
s32 selected;
s32 scrollpos;
s32 keynav_time;
core::stringw keynav_buffer;
std::set<s32> opened_trees;
DynamicData()
{
selected = 0;
scrollpos = 0;
keynav_time = 0;
}
};
/*
An option of the form <name>=<value>
*/
struct Option
{
std::string name;
std::string value;
Option(const std::string &name_, const std::string &value_)
{
name = name_;
value = value_;
}
};
/*
A list of options that concern the entire table
*/
typedef std::vector<Option> TableOptions;
/*
A column with options
*/
struct TableColumn
{
std::string type;
std::vector<Option> options;
};
typedef std::vector<TableColumn> TableColumns;
GUITable(gui::IGUIEnvironment *env,
gui::IGUIElement *parent, s32 id,
core::rect<s32> rectangle,
ISimpleTextureSource *tsrc);
virtual ~GUITable();
/* Split a string of the form "name=value" into name and value */
static Option splitOption(const std::string &str);
/* Set textlist-like options, columns and data */
void setTextList(const std::vector<std::string> &content,
bool transparent);
/* Set generic table options, columns and content */
// Adds empty strings to end of content if there is an incomplete row
void setTable(const TableOptions &options,
const TableColumns &columns,
std::vector<std::string> &content);
/* Clear the table */
void clear();
/* Get info about last event (string such as "CHG:1:2") */
// Call this after EGET_TABLE_CHANGED
std::string checkEvent();
/* Get index of currently selected row (first=1; 0 if none selected) */
s32 getSelected() const;
/* Set currently selected row (first=1; 0 if none selected) */
// If given index is not visible at the moment, select its parent
// Autoscroll to make the selected row fully visible
void setSelected(s32 index);
/* Get selection, scroll position and opened (sub)trees */
DynamicData getDynamicData() const;
/* Set selection, scroll position and opened (sub)trees */
void setDynamicData(const DynamicData &dyndata);
/* Returns "GUITable" */
virtual const c8* getTypeName() const;
/* Must be called when position or size changes */
virtual void updateAbsolutePosition();
/* Irrlicht draw method */
virtual void draw();
/* Irrlicht event handler */
virtual bool OnEvent(const SEvent &event);
protected:
enum ColumnType {
COLUMN_TYPE_TEXT,
COLUMN_TYPE_IMAGE,
COLUMN_TYPE_COLOR,
COLUMN_TYPE_INDENT,
COLUMN_TYPE_TREE,
};
struct Cell {
s32 xmin;
s32 xmax;
s32 xpos;
ColumnType content_type;
s32 content_index;
s32 tooltip_index;
video::SColor color;
bool color_defined;
s32 reported_column;
};
struct Row {
Cell *cells;
s32 cellcount;
s32 indent;
// visible_index >= 0: is index of row in m_visible_rows
// visible_index == -1: parent open but other ancestor closed
// visible_index == -2: parent closed
s32 visible_index;
};
// Texture source
ISimpleTextureSource *m_tsrc;
// Table content (including hidden rows)
std::vector<Row> m_rows;
// Table content (only visible; indices into m_rows)
std::vector<s32> m_visible_rows;
bool m_is_textlist;
bool m_has_tree_column;
// Selection status
s32 m_selected; // index of row (1...n), or 0 if none selected
s32 m_sel_column;
bool m_sel_doubleclick;
// Keyboard navigation stuff
s32 m_keynav_time;
core::stringw m_keynav_buffer;
// Drawing and geometry information
bool m_border;
video::SColor m_color;
video::SColor m_background;
video::SColor m_highlight;
video::SColor m_highlight_text;
s32 m_rowheight;
gui::IGUIFont *m_font;
gui::IGUIScrollBar *m_scrollbar;
// Allocated strings and images
std::vector<core::stringw> m_strings;
std::vector<video::ITexture*> m_images;
std::map<std::string, s32> m_alloc_strings;
std::map<std::string, s32> m_alloc_images;
s32 allocString(const std::string &text);
s32 allocImage(const std::string &imagename);
void allocationComplete();
// Helper for draw() that draws a single cell
void drawCell(const Cell *cell, video::SColor color,
const core::rect<s32> &rowrect,
const core::rect<s32> &client_clip);
// Returns the i-th visible row (NULL if i is invalid)
const Row *getRow(s32 i) const;
// Key navigation helper
bool doesRowStartWith(const Row *row, const core::stringw &str) const;
// Returns the row at a given screen Y coordinate
// Returns index i such that m_rows[i] is valid (or -1 on error)
s32 getRowAt(s32 y, bool &really_hovering) const;
// Returns the cell at a given screen X coordinate within m_rows[row_i]
// Returns index j such that m_rows[row_i].cells[j] is valid
// (or -1 on error)
s32 getCellAt(s32 x, s32 row_i) const;
// Make the selected row fully visible
void autoScroll();
// Should be called when m_rowcount or m_rowheight changes
void updateScrollBar();
// Sends EET_GUI_EVENT / EGET_TABLE_CHANGED to parent
void sendTableEvent(s32 column, bool doubleclick);
// Functions that help deal with hidden rows
// The following functions take raw row indices (hidden rows not skipped)
void getOpenedTrees(std::set<s32> &opened_trees) const;
void setOpenedTrees(const std::set<s32> &opened_trees);
void openTree(s32 to_open);
void closeTree(s32 to_close);
// The following function takes a visible row index (hidden rows skipped)
// dir: -1 = left (close), 0 = auto (toggle), 1 = right (open)
void toggleVisibleTree(s32 row_i, int dir, bool move_selection);
// Aligns cell content in column according to alignment specification
// align = 0: left aligned, 1: centered, 2: right aligned, 3: inline
static void alignContent(Cell *cell, s32 xmax, s32 content_width,
s32 align);
};
#endif

@ -183,18 +183,25 @@ int ModApiMainMenu::l_set_clouds(lua_State *L)
/******************************************************************************/ /******************************************************************************/
int ModApiMainMenu::l_get_textlist_index(lua_State *L) int ModApiMainMenu::l_get_textlist_index(lua_State *L)
{
// get_table_index accepts both tables and textlists
return l_get_table_index(L);
}
/******************************************************************************/
int ModApiMainMenu::l_get_table_index(lua_State *L)
{ {
GUIEngine* engine = getGuiEngine(L); GUIEngine* engine = getGuiEngine(L);
assert(engine != 0); assert(engine != 0);
std::string listboxname(luaL_checkstring(L, 1)); std::wstring tablename(narrow_to_wide(luaL_checkstring(L, 1)));
GUITable *table = engine->m_menu->getTable(tablename);
int selection = engine->m_menu->getListboxIndex(listboxname); s32 selection = table ? table->getSelected() : 0;
if (selection >= 0)
selection++;
if (selection >= 1)
lua_pushinteger(L, selection); lua_pushinteger(L, selection);
else
lua_pushnil(L);
return 1; return 1;
} }
@ -1026,6 +1033,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(update_formspec); API_FCT(update_formspec);
API_FCT(set_clouds); API_FCT(set_clouds);
API_FCT(get_textlist_index); API_FCT(get_textlist_index);
API_FCT(get_table_index);
API_FCT(get_worlds); API_FCT(get_worlds);
API_FCT(get_games); API_FCT(get_games);
API_FCT(start); API_FCT(start);

@ -97,6 +97,8 @@ private:
static int l_get_textlist_index(lua_State *L); static int l_get_textlist_index(lua_State *L);
static int l_get_table_index(lua_State *L);
static int l_set_background(lua_State *L); static int l_set_background(lua_State *L);
static int l_update_formspec(lua_State *L); static int l_update_formspec(lua_State *L);