Skip to content

Commit 967121a

Browse files
sapierkwolekr
sapier
authored andcommittedJul 2, 2013
Replace C++ mainmenu by formspec powered one
1 parent fe4ce03 commit 967121a

37 files changed

+8083
-3974
lines changed
 

‎builtin/gamemgr.lua

+309
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
--Minetest
2+
--Copyright (C) 2013 sapier
3+
--
4+
--This program is free software; you can redistribute it and/or modify
5+
--it under the terms of the GNU Lesser General Public License as published by
6+
--the Free Software Foundation; either version 2.1 of the License, or
7+
--(at your option) any later version.
8+
--
9+
--This program is distributed in the hope that it will be useful,
10+
--but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
--GNU Lesser General Public License for more details.
13+
--
14+
--You should have received a copy of the GNU Lesser General Public License along
15+
--with this program; if not, write to the Free Software Foundation, Inc.,
16+
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17+
18+
gamemgr = {}
19+
20+
--------------------------------------------------------------------------------
21+
function gamemgr.dialog_new_game()
22+
local retval =
23+
"label[2,2;Game Name]"..
24+
"field[4.5,2.4;6,0.5;te_game_name;;]" ..
25+
"button[5,4.2;2.6,0.5;new_game_confirm;Create]" ..
26+
"button[7.5,4.2;2.8,0.5;new_game_cancel;Cancel]"
27+
28+
return retval
29+
end
30+
31+
--------------------------------------------------------------------------------
32+
function gamemgr.handle_games_buttons(fields)
33+
if fields["gamelist"] ~= nil then
34+
local event = explode_textlist_event(fields["gamelist"])
35+
gamemgr.selected_game = event.index
36+
end
37+
38+
if fields["btn_game_mgr_edit_game"] ~= nil then
39+
return {
40+
is_dialog = true,
41+
show_buttons = false,
42+
current_tab = "dialog_edit_game"
43+
}
44+
end
45+
46+
if fields["btn_game_mgr_new_game"] ~= nil then
47+
return {
48+
is_dialog = true,
49+
show_buttons = false,
50+
current_tab = "dialog_new_game"
51+
}
52+
end
53+
54+
return nil
55+
end
56+
57+
--------------------------------------------------------------------------------
58+
function gamemgr.handle_new_game_buttons(fields)
59+
60+
if fields["new_game_confirm"] and
61+
fields["te_game_name"] ~= nil and
62+
fields["te_game_name"] ~= "" then
63+
local gamepath = engine.get_gamepath()
64+
65+
if gamepath ~= nil and
66+
gamepath ~= "" then
67+
local gamefolder = cleanup_path(fields["te_game_name"])
68+
69+
--TODO check for already existing first
70+
engine.create_dir(gamepath .. DIR_DELIM .. gamefolder)
71+
engine.create_dir(gamepath .. DIR_DELIM .. gamefolder .. DIR_DELIM .. "mods")
72+
engine.create_dir(gamepath .. DIR_DELIM .. gamefolder .. DIR_DELIM .. "menu")
73+
74+
local gameconf =
75+
io.open(gamepath .. DIR_DELIM .. gamefolder .. DIR_DELIM .. "game.conf","w")
76+
77+
if gameconf then
78+
gameconf:write("name = " .. fields["te_game_name"])
79+
gameconf:close()
80+
end
81+
end
82+
end
83+
84+
return {
85+
is_dialog = false,
86+
show_buttons = true,
87+
current_tab = engine.setting_get("main_menu_tab")
88+
}
89+
end
90+
91+
--------------------------------------------------------------------------------
92+
function gamemgr.handle_edit_game_buttons(fields)
93+
local current_game = gamemgr.get_game(gamemgr.selected_game)
94+
95+
if fields["btn_close_edit_game"] ~= nil or
96+
current_game == nil then
97+
return {
98+
is_dialog = false,
99+
show_buttons = true,
100+
current_tab = engine.setting_get("main_menu_tab")
101+
}
102+
end
103+
104+
if fields["btn_remove_mod_from_game"] ~= nil then
105+
gamemgr.delete_mod(current_game,engine.get_textlist_index("mods_current"))
106+
end
107+
108+
if fields["btn_add_mod_to_game"] ~= nil then
109+
local modindex = engine.get_textlist_index("mods_available")
110+
111+
if modindex > 0 and
112+
modindex <= #modmgr.global_mods then
113+
114+
local sourcepath =
115+
engine.get_modpath() .. DIR_DELIM .. modmgr.global_mods[modindex]
116+
117+
gamemgr.add_mod(current_game,sourcepath)
118+
end
119+
end
120+
121+
return nil
122+
end
123+
124+
--------------------------------------------------------------------------------
125+
function gamemgr.add_mod(gamespec,sourcepath)
126+
if gamespec.gamemods_path ~= nil and
127+
gamespec.gamemods_path ~= "" then
128+
129+
local modname = get_last_folder(sourcepath)
130+
131+
engine.copy_dir(sourcepath,gamespec.gamemods_path .. DIR_DELIM .. modname);
132+
end
133+
end
134+
135+
--------------------------------------------------------------------------------
136+
function gamemgr.delete_mod(gamespec,modindex)
137+
if gamespec.gamemods_path ~= nil and
138+
gamespec.gamemods_path ~= "" then
139+
local game_mods = {}
140+
get_mods(gamespec.gamemods_path,game_mods)
141+
142+
if modindex > 0 and
143+
#game_mods >= modindex then
144+
145+
local modname = game_mods[modindex]
146+
147+
if modname:find("<MODPACK>") ~= nil then
148+
modname = modname:sub(0,modname:find("<") -2)
149+
end
150+
151+
local modpath = gamespec.gamemods_path .. DIR_DELIM .. modname
152+
if modpath:sub(0,gamespec.gamemods_path:len()) == gamespec.gamemods_path then
153+
engine.delete_dir(modpath)
154+
end
155+
end
156+
end
157+
end
158+
159+
--------------------------------------------------------------------------------
160+
function gamemgr.get_game_mods(gamespec)
161+
162+
local retval = ""
163+
164+
if gamespec.gamemods_path ~= nil and
165+
gamespec.gamemods_path ~= "" then
166+
local game_mods = {}
167+
get_mods(gamespec.gamemods_path,game_mods)
168+
169+
for i=1,#game_mods,1 do
170+
if retval ~= "" then
171+
retval = retval..","
172+
end
173+
retval = retval .. game_mods[i]
174+
end
175+
end
176+
return retval
177+
end
178+
179+
--------------------------------------------------------------------------------
180+
function gamemgr.gettab(name)
181+
local retval = ""
182+
183+
if name == "dialog_edit_game" then
184+
retval = retval .. gamemgr.dialog_edit_game()
185+
end
186+
187+
if name == "dialog_new_game" then
188+
retval = retval .. gamemgr.dialog_new_game()
189+
end
190+
191+
if name == "game_mgr" then
192+
retval = retval .. gamemgr.tab()
193+
end
194+
195+
return retval
196+
end
197+
198+
--------------------------------------------------------------------------------
199+
function gamemgr.tab()
200+
if gamemgr.selected_game == nil then
201+
gamemgr.selected_game = 1
202+
end
203+
204+
local retval =
205+
"vertlabel[0,-0.25;GAMES]" ..
206+
"label[1,-0.25;Games:]" ..
207+
"textlist[1,0.25;4.5,4.4;gamelist;" ..
208+
gamemgr.gamelist() ..
209+
";" .. gamemgr.selected_game .. "]"
210+
211+
local current_game = gamemgr.get_game(gamemgr.selected_game)
212+
213+
if current_game ~= nil then
214+
if current_game.menuicon_path ~= nil and
215+
current_game.menuicon_path ~= "" then
216+
retval = retval ..
217+
"image[5.8,-0.25;2,2;" .. current_game.menuicon_path .. "]"
218+
end
219+
220+
retval = retval ..
221+
"field[8,-0.25;6,2;;" .. current_game.name .. ";]"..
222+
"label[6,1.4;Mods:]" ..
223+
"button[9.7,1.5;2,0.2;btn_game_mgr_edit_game;edit game]" ..
224+
"textlist[6,2;5.5,3.3;game_mgr_modlist;"
225+
.. gamemgr.get_game_mods(current_game) ..";0]" ..
226+
"button[1,4.75;3.2,0.5;btn_game_mgr_new_game;new game]"
227+
end
228+
return retval
229+
end
230+
231+
--------------------------------------------------------------------------------
232+
function gamemgr.dialog_edit_game()
233+
local current_game = gamemgr.get_game(gamemgr.selected_game)
234+
if current_game ~= nil then
235+
local retval =
236+
"vertlabel[0,-0.25;EDIT GAME]" ..
237+
"label[0,-0.25;" .. current_game.name .. "]" ..
238+
"button[11.55,-0.2;0.75,0.5;btn_close_edit_game;x]"
239+
240+
if current_game.menuicon_path ~= nil and
241+
current_game.menuicon_path ~= "" then
242+
retval = retval ..
243+
"image[5.25,0;2,2;" .. current_game.menuicon_path .. "]"
244+
end
245+
246+
retval = retval ..
247+
"textlist[0.5,0.5;4.5,4.3;mods_current;"
248+
.. gamemgr.get_game_mods(current_game) ..";0]"
249+
250+
251+
retval = retval ..
252+
"textlist[7,0.5;4.5,4.3;mods_available;"
253+
.. modmgr.get_mods_list() .. ";0]"
254+
255+
retval = retval ..
256+
"button[0.55,4.95;4.7,0.5;btn_remove_mod_from_game;Remove selected mod]"
257+
258+
retval = retval ..
259+
"button[7.05,4.95;4.7,0.5;btn_add_mod_to_game;<<-- Add mod]"
260+
261+
return retval
262+
end
263+
end
264+
265+
--------------------------------------------------------------------------------
266+
function gamemgr.handle_buttons(tab,fields)
267+
local retval = nil
268+
269+
if tab == "dialog_edit_game" then
270+
retval = gamemgr.handle_edit_game_buttons(fields)
271+
end
272+
273+
if tab == "dialog_new_game" then
274+
retval = gamemgr.handle_new_game_buttons(fields)
275+
end
276+
277+
if tab == "game_mgr" then
278+
retval = gamemgr.handle_games_buttons(fields)
279+
end
280+
281+
return retval
282+
end
283+
284+
--------------------------------------------------------------------------------
285+
function gamemgr.get_game(index)
286+
if index > 0 and index <= #gamemgr.games then
287+
return gamemgr.games[index]
288+
end
289+
290+
return nil
291+
end
292+
293+
--------------------------------------------------------------------------------
294+
function gamemgr.update_gamelist()
295+
gamemgr.games = engine.get_games()
296+
end
297+
298+
--------------------------------------------------------------------------------
299+
function gamemgr.gamelist()
300+
local retval = ""
301+
if #gamemgr.games > 0 then
302+
retval = retval .. gamemgr.games[1].id
303+
304+
for i=2,#gamemgr.games,1 do
305+
retval = retval .. "," .. gamemgr.games[i].name
306+
end
307+
end
308+
return retval
309+
end

‎builtin/mainmenu.lua

+1,190
Large diffs are not rendered by default.

‎builtin/mainmenu_helper.lua

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
--------------------------------------------------------------------------------
2+
function dump(o, dumped)
3+
dumped = dumped or {}
4+
if type(o) == "number" then
5+
return tostring(o)
6+
elseif type(o) == "string" then
7+
return string.format("%q", o)
8+
elseif type(o) == "table" then
9+
if dumped[o] then
10+
return "<circular reference>"
11+
end
12+
dumped[o] = true
13+
local t = {}
14+
for k,v in pairs(o) do
15+
t[#t+1] = "" .. k .. " = " .. dump(v, dumped)
16+
end
17+
return "{" .. table.concat(t, ", ") .. "}"
18+
elseif type(o) == "boolean" then
19+
return tostring(o)
20+
elseif type(o) == "function" then
21+
return "<function>"
22+
elseif type(o) == "userdata" then
23+
return "<userdata>"
24+
elseif type(o) == "nil" then
25+
return "nil"
26+
else
27+
error("cannot dump a " .. type(o))
28+
return nil
29+
end
30+
end
31+
32+
--------------------------------------------------------------------------------
33+
function string:split(sep)
34+
local sep, fields = sep or ",", {}
35+
local pattern = string.format("([^%s]+)", sep)
36+
self:gsub(pattern, function(c) fields[#fields+1] = c end)
37+
return fields
38+
end
39+
40+
--------------------------------------------------------------------------------
41+
function string:trim()
42+
return (self:gsub("^%s*(.-)%s*$", "%1"))
43+
end
44+
45+
--------------------------------------------------------------------------------
46+
engine.get_game = function(index)
47+
local games = game.get_games()
48+
49+
if index > 0 and index <= #games then
50+
return games[index]
51+
end
52+
53+
return nil
54+
end
55+
56+
--------------------------------------------------------------------------------
57+
function fs_escape_string(text)
58+
if text ~= nil then
59+
while (text:find("\r\n") ~= nil) do
60+
local newtext = text:sub(1,text:find("\r\n")-1)
61+
newtext = newtext .. " " .. text:sub(text:find("\r\n")+3)
62+
63+
text = newtext
64+
end
65+
66+
while (text:find("\n") ~= nil) do
67+
local newtext = text:sub(1,text:find("\n")-1)
68+
newtext = newtext .. " " .. text:sub(text:find("\n")+1)
69+
70+
text = newtext
71+
end
72+
73+
while (text:find("\r") ~= nil) do
74+
local newtext = text:sub(1,text:find("\r")-1)
75+
newtext = newtext .. " " .. text:sub(text:find("\r")+1)
76+
77+
text = newtext
78+
end
79+
80+
text = text:gsub("%[","%[%[")
81+
text = text:gsub("]","]]")
82+
text = text:gsub(";"," ")
83+
end
84+
return text
85+
end
86+
87+
--------------------------------------------------------------------------------
88+
function explode_textlist_event(text)
89+
90+
local retval = {}
91+
retval.typ = "INV"
92+
93+
local parts = text:split(":")
94+
95+
if #parts == 2 then
96+
retval.typ = parts[1]:trim()
97+
retval.index= tonumber(parts[2]:trim())
98+
99+
if type(retval.index) ~= "number" then
100+
retval.typ = "INV"
101+
end
102+
end
103+
104+
return retval
105+
end

‎builtin/modmgr.lua

+881
Large diffs are not rendered by default.

‎builtin/modstore.lua

+275
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
--Minetest
2+
--Copyright (C) 2013 sapier
3+
--
4+
--This program is free software; you can redistribute it and/or modify
5+
--it under the terms of the GNU Lesser General Public License as published by
6+
--the Free Software Foundation; either version 2.1 of the License, or
7+
--(at your option) any later version.
8+
--
9+
--This program is distributed in the hope that it will be useful,
10+
--but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
--GNU Lesser General Public License for more details.
13+
--
14+
--You should have received a copy of the GNU Lesser General Public License along
15+
--with this program; if not, write to the Free Software Foundation, Inc.,
16+
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17+
18+
--------------------------------------------------------------------------------
19+
20+
--modstore implementation
21+
modstore = {}
22+
23+
--------------------------------------------------------------------------------
24+
function modstore.init()
25+
modstore.tabnames = {}
26+
27+
table.insert(modstore.tabnames,"dialog_modstore_unsorted")
28+
table.insert(modstore.tabnames,"dialog_modstore_search")
29+
30+
modstore.modsperpage = 5
31+
32+
modstore.basetexturedir = engine.get_gamepath() .. DIR_DELIM .. ".." ..
33+
DIR_DELIM .. "textures" .. DIR_DELIM .. "base" ..
34+
DIR_DELIM .. "pack" .. DIR_DELIM
35+
modstore.update_modlist()
36+
37+
modstore.current_list = nil
38+
39+
modstore.details_cache = {}
40+
end
41+
--------------------------------------------------------------------------------
42+
function modstore.nametoindex(name)
43+
44+
for i=1,#modstore.tabnames,1 do
45+
if modstore.tabnames[i] == name then
46+
return i
47+
end
48+
end
49+
50+
return 1
51+
end
52+
53+
--------------------------------------------------------------------------------
54+
function modstore.gettab(tabname)
55+
local retval = ""
56+
57+
local is_modstore_tab = false
58+
59+
if tabname == "dialog_modstore_unsorted" then
60+
retval = modstore.getmodlist(modstore.modlist_unsorted)
61+
is_modstore_tab = true
62+
end
63+
64+
if tabname == "dialog_modstore_search" then
65+
66+
67+
is_modstore_tab = true
68+
end
69+
70+
if is_modstore_tab then
71+
return modstore.tabheader(tabname) .. retval
72+
end
73+
74+
if tabname == "modstore_mod_installed" then
75+
return "size[6,2]label[0.25,0.25;Mod: " .. modstore.lastmodtitle ..
76+
" installed successfully]" ..
77+
"button[2.5,1.5;1,0.5;btn_confirm_mod_successfull;ok]"
78+
end
79+
80+
return ""
81+
end
82+
83+
--------------------------------------------------------------------------------
84+
function modstore.tabheader(tabname)
85+
local retval = "size[12,9.25]"
86+
retval = retval .. "tabheader[-0.3,-0.99;modstore_tab;" ..
87+
"Unsorted,Search;" ..
88+
modstore.nametoindex(tabname) .. ";true;false]"
89+
90+
return retval
91+
end
92+
93+
--------------------------------------------------------------------------------
94+
function modstore.handle_buttons(current_tab,fields)
95+
96+
modstore.lastmodtitle = ""
97+
98+
if fields["modstore_tab"] then
99+
local index = tonumber(fields["modstore_tab"])
100+
101+
if index > 0 and
102+
index <= #modstore.tabnames then
103+
return {
104+
current_tab = modstore.tabnames[index],
105+
is_dialog = true,
106+
show_buttons = false
107+
}
108+
end
109+
110+
modstore.modlist_page = 0
111+
end
112+
113+
if fields["btn_modstore_page_up"] then
114+
if modstore.current_list ~= nil and modstore.current_list.page > 0 then
115+
modstore.current_list.page = modstore.current_list.page - 1
116+
end
117+
end
118+
119+
if fields["btn_modstore_page_down"] then
120+
if modstore.current_list ~= nil and
121+
modstore.current_list.page <modstore.current_list.pagecount then
122+
modstore.current_list.page = modstore.current_list.page +1
123+
end
124+
end
125+
126+
if fields["btn_confirm_mod_successfull"] then
127+
return {
128+
current_tab = modstore.tabnames[1],
129+
is_dialog = true,
130+
show_buttons = false
131+
}
132+
end
133+
134+
for i=1, modstore.modsperpage, 1 do
135+
local installbtn = "btn_install_mod_" .. i
136+
137+
if fields[installbtn] then
138+
local modlistentry =
139+
modstore.current_list.page * modstore.modsperpage + i
140+
141+
local moddetails = modstore.get_details(modstore.current_list.data[modlistentry].id)
142+
143+
local fullurl = engine.setting_get("modstore_download_url") ..
144+
moddetails.download_url
145+
local modfilename = os.tempfolder() .. ".zip"
146+
print("Downloading mod from: " .. fullurl .. " to ".. modfilename)
147+
148+
if engine.download_file(fullurl,modfilename) then
149+
150+
modmgr.installmod(modfilename,moddetails.basename)
151+
152+
os.remove(modfilename)
153+
modstore.lastmodtitle = modstore.current_list.data[modlistentry].title
154+
155+
return {
156+
current_tab = "modstore_mod_installed",
157+
is_dialog = true,
158+
show_buttons = false
159+
}
160+
else
161+
gamedata.errormessage = "Unable to download " ..
162+
moddetails.download_url .. " (internet connection?)"
163+
end
164+
end
165+
end
166+
end
167+
168+
--------------------------------------------------------------------------------
169+
function modstore.update_modlist()
170+
modstore.modlist_unsorted = {}
171+
modstore.modlist_unsorted.data = engine.get_modstore_list()
172+
173+
if modstore.modlist_unsorted.data ~= nil then
174+
modstore.modlist_unsorted.pagecount =
175+
math.floor((#modstore.modlist_unsorted.data / modstore.modsperpage))
176+
else
177+
modstore.modlist_unsorted.data = {}
178+
modstore.modlist_unsorted.pagecount = 0
179+
end
180+
modstore.modlist_unsorted.page = 0
181+
end
182+
183+
--------------------------------------------------------------------------------
184+
function modstore.getmodlist(list)
185+
local retval = ""
186+
retval = retval .. "label[10,-0.4;Page " .. (list.page +1) ..
187+
" of " .. (list.pagecount +1) .. "]"
188+
189+
retval = retval .. "button[11.6,-0.1;0.5,0.5;btn_modstore_page_up;^]"
190+
retval = retval .. "box[11.6,0.35;0.28,8.6;BLK]"
191+
local scrollbarpos = 0.35 + (8.1/list.pagecount) * list.page
192+
retval = retval .. "box[11.6," ..scrollbarpos .. ";0.28,0.5;LIM]"
193+
retval = retval .. "button[11.6,9.0;0.5,0.5;btn_modstore_page_down;v]"
194+
195+
196+
if #list.data < (list.page * modstore.modsperpage) then
197+
return retval
198+
end
199+
200+
local endmod = (list.page * modstore.modsperpage) + modstore.modsperpage
201+
202+
if (endmod > #list.data) then
203+
endmod = #list.data
204+
end
205+
206+
for i=(list.page * modstore.modsperpage) +1, endmod, 1 do
207+
--getmoddetails
208+
local details = modstore.get_details(list.data[i].id)
209+
210+
if details ~= nil then
211+
local screenshot_ypos = (i-1 - (list.page * modstore.modsperpage))*1.9 +0.2
212+
213+
retval = retval .. "box[0," .. screenshot_ypos .. ";11.4,1.75;WHT]"
214+
215+
--screenshot
216+
if details.screenshot_url ~= nil and
217+
details.screenshot_url ~= "" then
218+
if list.data[i].texturename == nil then
219+
print("downloading screenshot: " .. details.screenshot_url)
220+
local filename = os.tempfolder()
221+
222+
if engine.download_file(details.screenshot_url,filename) then
223+
list.data[i].texturename = filename
224+
end
225+
end
226+
end
227+
228+
if list.data[i].texturename == nil then
229+
list.data[i].texturename = modstore.basetexturedir .. "no_screenshot.png"
230+
end
231+
232+
retval = retval .. "image[0,".. screenshot_ypos .. ";3,2;" ..
233+
list.data[i].texturename .. "]"
234+
235+
--title + author
236+
retval = retval .."label[2.75," .. screenshot_ypos .. ";" ..
237+
fs_escape_string(details.title) .. " (" .. details.author .. ")]"
238+
239+
--description
240+
local descriptiony = screenshot_ypos + 0.5
241+
retval = retval .. "textarea[3," .. descriptiony .. ";6.5,1.6;;" ..
242+
fs_escape_string(details.description) .. ";]"
243+
--rating
244+
local ratingy = screenshot_ypos + 0.6
245+
retval = retval .."label[10.1," .. ratingy .. ";Rating: " .. details.rating .."]"
246+
247+
--install button
248+
local buttony = screenshot_ypos + 1.2
249+
local buttonnumber = (i - (list.page * modstore.modsperpage))
250+
retval = retval .."button[9.6," .. buttony .. ";2,0.5;btn_install_mod_" .. buttonnumber .. ";"
251+
252+
if modmgr.mod_exists(details.basename) then
253+
retval = retval .. "re-Install]"
254+
else
255+
retval = retval .. "Install]"
256+
end
257+
end
258+
end
259+
260+
modstore.current_list = list
261+
262+
return retval
263+
end
264+
265+
--------------------------------------------------------------------------------
266+
function modstore.get_details(modid)
267+
268+
if modstore.details_cache[modid] ~= nil then
269+
return modstore.details_cache[modid]
270+
end
271+
272+
local retval = engine.get_modstore_details(tostring(modid))
273+
modstore.details_cache[modid] = retval
274+
return retval
275+
end

‎doc/lua_api.txt

+58
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,15 @@ background[<X>,<Y>;<W>,<H>;<texture name>]
883883
^ Position and size units are inventory slots
884884
^ Example for formspec 8x4 in 16x resolution: image shall be sized 8*16px x 4*16px
885885

886+
pwdfield[<X>,<Y>;<W>,<H>;<name>;<label>]
887+
^ Textual password style field; will be sent to server when a button is clicked
888+
^ x and y position the field relative to the top left of the menu
889+
^ w and h are the size of the field
890+
^ fields are a set height, but will be vertically centred on h
891+
^ Position and size units are inventory slots
892+
^ name is the name of the field as returned in fields to on_receive_fields
893+
^ label, if not blank, will be text printed on the top left above the field
894+
886895
field[<X>,<Y>;<W>,<H>;<name>;<label>;<default>]
887896
^ Textual field; will be sent to server when a button is clicked
888897
^ x and y position the field relative to the top left of the menu
@@ -910,6 +919,12 @@ label[<X>,<Y>;<label>]
910919
^ label is the text on the label
911920
^ Position and size units are inventory slots
912921

922+
vertlabel[<X>,<Y>;<label>]
923+
^ Textual label drawn verticaly
924+
^ x and y work as per field
925+
^ label is the text on the label
926+
^ Position and size units are inventory slots
927+
913928
button[<X>,<Y>;<W>,<H>;<name>;<label>]
914929
^ Clickable button. When clicked, fields will be sent.
915930
^ x, y and name work as per field
@@ -922,6 +937,13 @@ image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]
922937
^ image is the filename of an image
923938
^ Position and size units are inventory slots
924939

940+
image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>;<noclip>;<drawborder>]
941+
^ x, y, w, h, and name work as per button
942+
^ image is the filename of an image
943+
^ Position and size units are inventory slots
944+
^ noclip true meand imagebutton doesn't need to be within specified formsize
945+
^ drawborder draw button bodrer or not
946+
925947
item_image_button[<X>,<Y>;<W>,<H>;<item name>;<name>;<label>]
926948
^ x, y, w, h, name and label work as per button
927949
^ item name is the registered name of an item/node,
@@ -934,6 +956,42 @@ button_exit[<X>,<Y>;<W>,<H>;<name>;<label>]
934956
image_button_exit[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]
935957
^ When clicked, fields will be sent and the form will quit.
936958

959+
textlist[<X>,<Y>;<W>,<H>;<name>;<listelem 1>,<listelem 2>,...,<listelem n>]
960+
^Scrollabel itemlist showing arbitrary text elements
961+
^ x and y position the itemlist relative to the top left of the menu
962+
^ w and h are the size of the itemlist
963+
^ listelements can be prepended by #colorkey (see colorkeys),
964+
^ if you want a listelement to start with # write ##
965+
^ name fieldname sent to server on doubleclick value is current selected element
966+
967+
tabheader[<X>,<Y>;<name>;<caption 1>,<caption 2>;<current_tab>;<transparent>;<draw_border>]
968+
^ show a tabHEADER at specific position (ignores formsize)
969+
^ x and y position the itemlist relative to the top left of the menu
970+
^ name fieldname data is transfered to lua
971+
^ caption 1... name shown on top of tab
972+
^ current_tab index of selected tab 1...
973+
^ transparent (optional) show transparent
974+
^ draw_border (optional) draw border
975+
976+
box[<X>,<Y>;<W>,<H>;<colorkey>]
977+
^ simple colored semitransparent box
978+
^ x and y position the box relative to the top left of the menu
979+
^ w and h are the size of box
980+
^ colorkey (see colorkeys)
981+
982+
Available colorkeys:
983+
- YLW yellow
984+
- GRN green
985+
- LIM lime
986+
- ORN orange
987+
- RED red
988+
- BLU blue
989+
- CYN cyan
990+
- BLK black
991+
- BRN brown
992+
- WHT white
993+
- GRY grey
994+
937995
Inventory location:
938996

939997
- "context": Selected node metadata (deprecated: "current_name")

‎doc/menu_lua_api.txt

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
Minetest Lua Mainmenu API Reference 0.4.6
2+
========================================
3+
4+
Introduction
5+
-------------
6+
The main menu is defined as a formspec by Lua in builtin/mainmenu.lua
7+
Description of formspec language to show your menu is in lua_api.txt
8+
9+
Callbacks
10+
---------
11+
engine.buttonhandler(fields): called when a button is pressed.
12+
^ fields = {name1 = value1, name2 = value2, ...}
13+
engine.event_handler(event)
14+
^ event: "MenuQuit", "KeyEnter", "ExitButton" or "EditBoxEnter"
15+
16+
Gamedata
17+
--------
18+
The "gamedata" table is read when calling engine.start(). It should contain:
19+
{
20+
playername = <name>,
21+
password = <password>,
22+
address = <IP/adress>,
23+
port = <port>,
24+
selected_world = <index>, -- 0 for client mode
25+
singleplayer = <true/false>,
26+
}
27+
28+
Functions
29+
---------
30+
engine.start()
31+
engine.close()
32+
33+
Filesystem:
34+
engine.get_scriptdir()
35+
^ returns directory of script
36+
engine.get_modpath()
37+
^ returns path to global modpath
38+
engine.get_modstore_details(modid)
39+
^ modid numeric id of mod in modstore
40+
^ returns {
41+
id = <numeric id of mod in modstore>,
42+
title = <human readable title>,
43+
basename = <basename for mod>,
44+
description = <description of mod>,
45+
author = <author of mod>,
46+
download_url= <best match download url>,
47+
license = <short description of license>,
48+
rating = <float value of current rating>
49+
}
50+
engine.get_modstore_list()
51+
^ returns {
52+
[1] = {
53+
id = <numeric id of mod in modstore>,
54+
title = <human readable title>,
55+
basename = <basename for mod>
56+
}
57+
}
58+
engine.get_gamepath()
59+
^ returns path to global gamepath
60+
engine.get_dirlist(path,onlydirs)
61+
^ path to get subdirs from
62+
^ onlydirs should result contain only dirs?
63+
^ returns list of folders within path
64+
engine.create_dir(absolute_path)
65+
^ absolute_path to directory to create (needs to be absolute)
66+
^ returns true/false
67+
engine.delete_dir(absolute_path)
68+
^ absolute_path to directory to delete (needs to be absolute)
69+
^ returns true/false
70+
engine.copy_dir(source,destination,keep_soure)
71+
^ source folder
72+
^ destination folder
73+
^ keep_source DEFAULT true --> if set to false source is deleted after copying
74+
^ returns true/false
75+
engine.extract_zip(zipfile,destination) [unzip within path required]
76+
^ zipfile to extract
77+
^ destination folder to extract to
78+
^ returns true/false
79+
engine.download_file(url,target)
80+
^ url to download
81+
^ target to store to
82+
^ returns true/false
83+
engine.get_version()
84+
^ returns current minetest version
85+
86+
GUI:
87+
engine.update_formspec(formspec)
88+
- engine.set_background(type, texturepath)
89+
^ type: "background", "overlay", "header" or "footer"
90+
engine.set_clouds(<true/false>)
91+
engine.set_topleft_text(text)
92+
93+
Games:
94+
engine.get_game(index)
95+
^ returns {
96+
id = <id>,
97+
path = <full path to game>,
98+
gamemods_path = <path>,
99+
name = <name of game>,
100+
menuicon_path = <full path to menuicon>,
101+
DEPRECATED:
102+
addon_mods_paths = {[1] = <path>,},
103+
}
104+
engine.get_games() -> table of all games in upper format
105+
106+
Favorites:
107+
engine.get_favorites(location) -> list of favorites
108+
^ location: "local" or "online"
109+
^ returns {
110+
[1] = {
111+
clients = <number of clients/nil>,
112+
clients_max = <maximum number of clients/nil>,
113+
version = <server version/nil>,
114+
password = <true/nil>,
115+
creative = <true/nil>,
116+
damage = <true/nil>,
117+
pvp = <true/nil>,
118+
description = <server description/nil>,
119+
name = <server name/nil>,
120+
address = <address of server/nil>,
121+
port = <port>
122+
},
123+
}
124+
engine.delete_favorite(id, location) -> success
125+
126+
Settings:
127+
engine.setting_set(name, value)
128+
engine.setting_get(name) -> string or nil
129+
engine.setting_setbool(name, value)
130+
engine.setting_getbool(name) -> bool or nil
131+
132+
Worlds:
133+
engine.get_worlds() -> list of worlds
134+
^ returns {
135+
[1] = {
136+
path = <full path to world>,
137+
name = <name of world>,
138+
gameid = <gameid of world>,
139+
},
140+
}
141+
engine.create_world(worldname, gameid)
142+
engine.delete_world(index)
143+
144+
145+
UI:
146+
engine.get_textlist_index(textlistname) -> index
147+
engine.show_keys_menu()
148+
engine.file_open_dialog(formname,caption)
149+
^ shows a file open dialog
150+
^ formname is base name of dialog response returned in fields
151+
^ -if dialog was accepted "_accepted"
152+
^^ will be added to fieldname containing the path
153+
^ -if dialog was canceled "_cancelled"
154+
^ will be added to fieldname value is set to formname itself
155+
^ returns nil or selected file/folder
156+
157+
Helpers:
158+
dump(obj, dumped={})
159+
^ Return object serialized as a string
160+
string:split(separator)
161+
^ eg. string:split("a,b", ",") == {"a","b"}
162+
string:trim()
163+
^ eg. string.trim("\n \t\tfoo bar\t ") == "foo bar"

‎minetest.conf.example

+6
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,9 @@
373373
# Enable/disable running an IPv6 server. An IPv6 server may be restricted
374374
# to IPv6 clients, depending on system configuration.
375375
#ipv6_server = false
376+
377+
#main_menu_game_mgr = 0
378+
#main_menu_mod_mgr = 0
379+
#modstore_download_url = http://forum.minetest.net/media/
380+
#modstore_listmods_url = http://forum.minetest.net/mmdb/mods/
381+
#modstore_details_url = http://forum.minetest.net/mmdb/mod/*/

‎src/CMakeLists.txt

+6-5
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ set(common_SRCS
271271
staticobject.cpp
272272
serverlist.cpp
273273
pathfinder.cpp
274+
convert_json.cpp
274275
${SCRIPT_SRCS}
275276
${UTIL_SRCS}
276277
)
@@ -313,7 +314,6 @@ set(minetest_SRCS
313314
clientobject.cpp
314315
chat.cpp
315316
hud.cpp
316-
guiMainMenu.cpp
317317
guiKeyChangeMenu.cpp
318318
guiMessageMenu.cpp
319319
guiTextInputMenu.cpp
@@ -323,15 +323,16 @@ set(minetest_SRCS
323323
guiVolumeChange.cpp
324324
guiDeathScreen.cpp
325325
guiChatConsole.cpp
326-
guiCreateWorld.cpp
327-
guiConfigureWorld.cpp
328-
guiConfirmMenu.cpp
329326
client.cpp
330327
filecache.cpp
331328
tile.cpp
332329
shader.cpp
333330
game.cpp
334331
main.cpp
332+
guiEngine.cpp
333+
guiLuaApi.cpp
334+
guiFileSelectMenu.cpp
335+
convert_json.cpp
335336
)
336337

337338
if(USE_FREETYPE)
@@ -488,7 +489,7 @@ else()
488489
endif()
489490

490491
set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG ${RELEASE_WARNING_FLAGS} ${WARNING_FLAGS} ${OTHER_FLAGS} -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops")
491-
set(CMAKE_CXX_FLAGS_DEBUG "-g -O1 -Wall ${WARNING_FLAGS} ${OTHER_FLAGS}")
492+
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall ${WARNING_FLAGS} ${OTHER_FLAGS}")
492493

493494
if(USE_GPROF)
494495
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg")

‎src/convert_json.cpp

+367
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
/*
2+
Minetest
3+
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4+
5+
This program is free software; you can redistribute it and/or modify
6+
it under the terms of the GNU Lesser General Public License as published by
7+
the Free Software Foundation; either version 2.1 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public License along
16+
with this program; if not, write to the Free Software Foundation, Inc.,
17+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*/
19+
20+
#include <vector>
21+
#include <iostream>
22+
#include <sstream>
23+
24+
#include "convert_json.h"
25+
#include "mods.h"
26+
#include "config.h"
27+
#include "log.h"
28+
29+
#if USE_CURL
30+
#include <curl/curl.h>
31+
32+
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
33+
{
34+
((std::string*)userp)->append((char*)contents, size * nmemb);
35+
return size * nmemb;
36+
}
37+
38+
#endif
39+
40+
Json::Value fetchJsonValue(const std::string url,
41+
struct curl_slist *chunk) {
42+
#if USE_CURL
43+
std::string liststring;
44+
CURL *curl;
45+
46+
curl = curl_easy_init();
47+
if (curl)
48+
{
49+
CURLcode res;
50+
51+
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
52+
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
53+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
54+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &liststring);
55+
56+
if (chunk != 0)
57+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
58+
59+
60+
res = curl_easy_perform(curl);
61+
if (res != CURLE_OK)
62+
errorstream<<"Jsonreader: "<< url <<" not found (internet connection?)"<<std::endl;
63+
curl_easy_cleanup(curl);
64+
}
65+
66+
Json::Value root;
67+
Json::Reader reader;
68+
std::istringstream stream(liststring);
69+
if (!liststring.size()) {
70+
return Json::Value();
71+
}
72+
73+
if (!reader.parse( stream, root ) )
74+
{
75+
errorstream << "URL: " << url << std::endl;
76+
errorstream << "Failed to parse json data " << reader.getFormattedErrorMessages();
77+
errorstream << "data: \"" << liststring << "\"" << std::endl;
78+
return Json::Value();
79+
}
80+
81+
if (root.isArray()) {
82+
return root;
83+
}
84+
if ((root["list"].isArray())) {
85+
return root["list"];
86+
}
87+
else {
88+
return root;
89+
}
90+
#endif
91+
return Json::Value();
92+
}
93+
94+
std::vector<ModStoreMod> readModStoreList(Json::Value& modlist) {
95+
std::vector<ModStoreMod> retval;
96+
97+
if (modlist.isArray()) {
98+
for (unsigned int i = 0; i < modlist.size(); i++)
99+
{
100+
ModStoreMod toadd;
101+
toadd.valid = true;
102+
103+
//id
104+
if (modlist[i]["id"].asString().size()) {
105+
const char* id_raw = modlist[i]["id"].asString().c_str();
106+
char* endptr = 0;
107+
int numbervalue = strtol(id_raw,&endptr,10);
108+
109+
if ((*id_raw != 0) && (*endptr == 0)) {
110+
toadd.id = numbervalue;
111+
}
112+
}
113+
else {
114+
toadd.valid = false;
115+
}
116+
117+
//title
118+
if (modlist[i]["title"].asString().size()) {
119+
toadd.title = modlist[i]["title"].asString();
120+
}
121+
else {
122+
toadd.valid = false;
123+
}
124+
125+
//basename
126+
if (modlist[i]["basename"].asString().size()) {
127+
toadd.basename = modlist[i]["basename"].asString();
128+
}
129+
else {
130+
toadd.valid = false;
131+
}
132+
133+
//author
134+
135+
//rating
136+
137+
//version
138+
139+
if (toadd.valid) {
140+
retval.push_back(toadd);
141+
}
142+
}
143+
}
144+
return retval;
145+
}
146+
147+
ModStoreModDetails readModStoreModDetails(Json::Value& details) {
148+
149+
ModStoreModDetails retval;
150+
151+
retval.valid = true;
152+
153+
//version set
154+
if (details["version_set"].isArray()) {
155+
for (unsigned int i = 0; i < details["version_set"].size(); i++)
156+
{
157+
ModStoreVersionEntry toadd;
158+
159+
if (details["version_set"][i]["id"].asString().size()) {
160+
const char* id_raw = details["version_set"][i]["id"].asString().c_str();
161+
char* endptr = 0;
162+
int numbervalue = strtol(id_raw,&endptr,10);
163+
164+
if ((*id_raw != 0) && (*endptr == 0)) {
165+
toadd.id = numbervalue;
166+
}
167+
}
168+
else {
169+
retval.valid = false;
170+
}
171+
172+
//date
173+
if (details["version_set"][i]["date"].asString().size()) {
174+
toadd.date = details["version_set"][i]["date"].asString();
175+
}
176+
177+
//file
178+
if (details["version_set"][i]["file"].asString().size()) {
179+
toadd.file = details["version_set"][i]["file"].asString();
180+
}
181+
else {
182+
retval.valid = false;
183+
}
184+
185+
//approved
186+
187+
//mtversion
188+
189+
if( retval.valid ) {
190+
retval.versions.push_back(toadd);
191+
}
192+
else {
193+
break;
194+
}
195+
}
196+
}
197+
198+
if (retval.versions.size() < 1) {
199+
retval.valid = false;
200+
}
201+
202+
//categories
203+
if (details["categories"].isObject()) {
204+
for (unsigned int i = 0; i < details["categories"].size(); i++) {
205+
ModStoreCategoryInfo toadd;
206+
207+
if (details["categories"][i]["id"].asString().size()) {
208+
209+
const char* id_raw = details["categories"][i]["id"].asString().c_str();
210+
char* endptr = 0;
211+
int numbervalue = strtol(id_raw,&endptr,10);
212+
213+
if ((*id_raw != 0) && (*endptr == 0)) {
214+
toadd.id = numbervalue;
215+
}
216+
}
217+
else {
218+
retval.valid = false;
219+
}
220+
if (details["categories"][i]["title"].asString().size()) {
221+
toadd.name = details["categories"][i]["title"].asString();
222+
}
223+
else {
224+
retval.valid = false;
225+
}
226+
227+
if( retval.valid ) {
228+
retval.categories.push_back(toadd);
229+
}
230+
else {
231+
break;
232+
}
233+
}
234+
}
235+
236+
//author
237+
if (details["author"].isObject()) {
238+
if (details["author"]["id"].asString().size()) {
239+
240+
const char* id_raw = details["author"]["id"].asString().c_str();
241+
char* endptr = 0;
242+
int numbervalue = strtol(id_raw,&endptr,10);
243+
244+
if ((*id_raw != 0) && (*endptr == 0)) {
245+
retval.author.id = numbervalue;
246+
}
247+
else {
248+
retval.valid = false;
249+
}
250+
}
251+
else {
252+
retval.valid = false;
253+
}
254+
255+
if (details["author"]["username"].asString().size()) {
256+
retval.author.username = details["author"]["username"].asString();
257+
}
258+
else {
259+
retval.valid = false;
260+
}
261+
}
262+
else {
263+
retval.valid = false;
264+
}
265+
266+
//license
267+
if (details["license"].isObject()) {
268+
if (details["license"]["id"].asString().size()) {
269+
270+
const char* id_raw = details["license"]["id"].asString().c_str();
271+
char* endptr = 0;
272+
int numbervalue = strtol(id_raw,&endptr,10);
273+
274+
if ((*id_raw != 0) && (*endptr == 0)) {
275+
retval.license.id = numbervalue;
276+
}
277+
}
278+
else {
279+
retval.valid = false;
280+
}
281+
282+
if (details["license"]["short"].asString().size()) {
283+
retval.license.shortinfo = details["license"]["short"].asString();
284+
}
285+
else {
286+
retval.valid = false;
287+
}
288+
289+
if (details["license"]["link"].asString().size()) {
290+
retval.license.url = details["license"]["link"].asString();
291+
}
292+
293+
}
294+
295+
//id
296+
if (details["id"].asString().size()) {
297+
298+
const char* id_raw = details["id"].asString().c_str();
299+
char* endptr = 0;
300+
int numbervalue = strtol(id_raw,&endptr,10);
301+
302+
if ((*id_raw != 0) && (*endptr == 0)) {
303+
retval.id = numbervalue;
304+
}
305+
}
306+
else {
307+
retval.valid = false;
308+
}
309+
310+
//title
311+
if (details["title"].asString().size()) {
312+
retval.title = details["title"].asString();
313+
}
314+
else {
315+
retval.valid = false;
316+
}
317+
318+
//basename
319+
if (details["basename"].asString().size()) {
320+
retval.basename = details["basename"].asString();
321+
}
322+
else {
323+
retval.valid = false;
324+
}
325+
326+
//description
327+
if (details["desc"].asString().size()) {
328+
retval.description = details["desc"].asString();
329+
}
330+
331+
//repository
332+
if (details["replink"].asString().size()) {
333+
retval.repository = details["replink"].asString();
334+
}
335+
336+
//value
337+
if (details["rating"].asString().size()) {
338+
339+
const char* id_raw = details["rating"].asString().c_str();
340+
char* endptr = 0;
341+
float numbervalue = strtof(id_raw,&endptr);
342+
343+
if ((*id_raw != 0) && (*endptr == 0)) {
344+
retval.rating = numbervalue;
345+
}
346+
}
347+
else {
348+
retval.rating = 0.0;
349+
}
350+
351+
//depends
352+
if (details["depends"].isArray()) {
353+
//TODO
354+
}
355+
356+
//softdepends
357+
if (details["softdep"].isArray()) {
358+
//TODO
359+
}
360+
361+
//screenshot url
362+
if (details["screenshot_url"].asString().size()) {
363+
retval.screenshot_url = details["screenshot_url"].asString();
364+
}
365+
366+
return retval;
367+
}

‎src/guiConfirmMenu.h ‎src/convert_json.h

+13-35
Original file line numberDiff line numberDiff line change
@@ -17,40 +17,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
1717
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1818
*/
1919

20-
#ifndef GUICONFIRMMENU_HEADER
21-
#define GUICONFIRMMENU_HEADER
22-
23-
#include "irrlichttypes_extrabloated.h"
24-
#include "modalMenu.h"
25-
#include <string>
26-
27-
struct ConfirmDest
28-
{
29-
virtual void answer(bool answer) = 0;
30-
virtual ~ConfirmDest() {};
31-
};
32-
33-
class GUIConfirmMenu : public GUIModalMenu
34-
{
35-
public:
36-
GUIConfirmMenu(gui::IGUIEnvironment* env,
37-
gui::IGUIElement* parent, s32 id,
38-
IMenuManager *menumgr,
39-
ConfirmDest *dest,
40-
std::wstring message_text);
41-
~GUIConfirmMenu();
42-
43-
void removeChildren();
44-
// Remove and re-add (or reposition) stuff
45-
void regenerateGui(v2u32 screensize);
46-
void drawMenu();
47-
void acceptInput(bool answer);
48-
bool OnEvent(const SEvent& event);
49-
50-
private:
51-
ConfirmDest *m_dest;
52-
std::wstring m_message_text;
53-
};
20+
#ifndef __CONVERT_JSON_H__
21+
#define __CONVERT_JSON_H__
5422

55-
#endif
23+
#include "json/json.h"
24+
25+
struct ModStoreMod;
26+
struct ModStoreModDetails;
27+
28+
std::vector<ModStoreMod> readModStoreList(Json::Value& modlist);
29+
ModStoreModDetails readModStoreModDetails(Json::Value& details);
5630

31+
Json::Value fetchJsonValue(const std::string url,
32+
struct curl_slist *chunk);
33+
34+
#endif

‎src/defaultsettings.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,11 @@ void set_default_settings(Settings *settings)
256256
// IPv6
257257
settings->setDefault("enable_ipv6", "true");
258258
settings->setDefault("ipv6_server", "false");
259+
260+
settings->setDefault("modstore_download_url", "http://forum.minetest.net/media/");
261+
settings->setDefault("modstore_listmods_url", "http://forum.minetest.net/mmdb/mods/");
262+
settings->setDefault("modstore_details_url", "http://forum.minetest.net/mmdb/mod/*/");
263+
259264
}
260265

261266
void override_default_settings(Settings *settings, Settings *from)

‎src/filesys.cpp

+260-7
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
2020
#include "filesys.h"
2121
#include "strfnd.h"
2222
#include <iostream>
23+
#include <stdio.h>
2324
#include <string.h>
25+
#include <errno.h>
2426
#include "log.h"
2527

2628
namespace fs
@@ -30,11 +32,9 @@ namespace fs
3032

3133
#define _WIN32_WINNT 0x0501
3234
#include <windows.h>
33-
#include <stdio.h>
3435
#include <malloc.h>
3536
#include <tchar.h>
3637
#include <wchar.h>
37-
#include <stdio.h>
3838

3939
#define BUFSIZE MAX_PATH
4040

@@ -145,6 +145,11 @@ bool IsDir(std::string path)
145145
(attr & FILE_ATTRIBUTE_DIRECTORY));
146146
}
147147

148+
bool IsDirDelimiter(char c)
149+
{
150+
return c == '/' || c == '\\';
151+
}
152+
148153
bool RecursiveDelete(std::string path)
149154
{
150155
infostream<<"Recursively deleting \""<<path<<"\""<<std::endl;
@@ -207,11 +212,26 @@ bool DeleteSingleFileOrEmptyDirectory(std::string path)
207212
}
208213
}
209214

215+
std::string TempPath()
216+
{
217+
DWORD bufsize = GetTempPath(0, "");
218+
if(bufsize == 0){
219+
errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl;
220+
return "";
221+
}
222+
std::vector<char> buf(bufsize);
223+
DWORD len = GetTempPath(bufsize, &buf[0]);
224+
if(len == 0 || len > bufsize){
225+
errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl;
226+
return "";
227+
}
228+
return std::string(buf.begin(), buf.begin() + len);
229+
}
230+
210231
#else // POSIX
211232

212233
#include <sys/types.h>
213234
#include <dirent.h>
214-
#include <errno.h>
215235
#include <sys/stat.h>
216236
#include <sys/wait.h>
217237
#include <unistd.h>
@@ -301,6 +321,11 @@ bool IsDir(std::string path)
301321
return ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
302322
}
303323

324+
bool IsDirDelimiter(char c)
325+
{
326+
return c == '/';
327+
}
328+
304329
bool RecursiveDelete(std::string path)
305330
{
306331
/*
@@ -364,6 +389,20 @@ bool DeleteSingleFileOrEmptyDirectory(std::string path)
364389
}
365390
}
366391

392+
std::string TempPath()
393+
{
394+
/*
395+
Should the environment variables TMPDIR, TMP and TEMP
396+
and the macro P_tmpdir (if defined by stdio.h) be checked
397+
before falling back on /tmp?
398+
399+
Probably not, because this function is intended to be
400+
compatible with lua's os.tmpname which under the default
401+
configuration hardcodes mkstemp("/tmp/lua_XXXXXX").
402+
*/
403+
return std::string(DIR_DELIM) + "tmp";
404+
}
405+
367406
#endif
368407

369408
void GetRecursiveSubPaths(std::string path, std::vector<std::string> &dst)
@@ -414,22 +453,236 @@ bool RecursiveDeleteContent(std::string path)
414453
bool CreateAllDirs(std::string path)
415454
{
416455

417-
size_t pos;
418456
std::vector<std::string> tocreate;
419457
std::string basepath = path;
420458
while(!PathExists(basepath))
421459
{
422460
tocreate.push_back(basepath);
423-
pos = basepath.rfind(DIR_DELIM_C);
424-
if(pos == std::string::npos)
461+
basepath = RemoveLastPathComponent(basepath);
462+
if(basepath.empty())
425463
break;
426-
basepath = basepath.substr(0,pos);
427464
}
428465
for(int i=tocreate.size()-1;i>=0;i--)
429466
if(!CreateDir(tocreate[i]))
430467
return false;
431468
return true;
432469
}
433470

471+
bool CopyFileContents(std::string source, std::string target)
472+
{
473+
FILE *sourcefile = fopen(source.c_str(), "rb");
474+
if(sourcefile == NULL){
475+
errorstream<<source<<": can't open for reading: "
476+
<<strerror(errno)<<std::endl;
477+
return false;
478+
}
479+
480+
FILE *targetfile = fopen(target.c_str(), "wb");
481+
if(targetfile == NULL){
482+
errorstream<<target<<": can't open for writing: "
483+
<<strerror(errno)<<std::endl;
484+
fclose(sourcefile);
485+
return false;
486+
}
487+
488+
size_t total = 0;
489+
bool retval = true;
490+
bool done = false;
491+
char readbuffer[BUFSIZ];
492+
while(!done){
493+
size_t readbytes = fread(readbuffer, 1,
494+
sizeof(readbuffer), sourcefile);
495+
total += readbytes;
496+
if(ferror(sourcefile)){
497+
errorstream<<source<<": IO error: "
498+
<<strerror(errno)<<std::endl;
499+
retval = false;
500+
done = true;
501+
}
502+
if(readbytes > 0){
503+
fwrite(readbuffer, 1, readbytes, targetfile);
504+
}
505+
if(feof(sourcefile) || ferror(sourcefile)){
506+
// flush destination file to catch write errors
507+
// (e.g. disk full)
508+
fflush(targetfile);
509+
done = true;
510+
}
511+
if(ferror(targetfile)){
512+
errorstream<<target<<": IO error: "
513+
<<strerror(errno)<<std::endl;
514+
retval = false;
515+
done = true;
516+
}
517+
}
518+
infostream<<"copied "<<total<<" bytes from "
519+
<<source<<" to "<<target<<std::endl;
520+
fclose(sourcefile);
521+
fclose(targetfile);
522+
return retval;
523+
}
524+
525+
bool CopyDir(std::string source, std::string target)
526+
{
527+
if(PathExists(source)){
528+
if(!PathExists(target)){
529+
fs::CreateAllDirs(target);
530+
}
531+
bool retval = true;
532+
std::vector<DirListNode> content = fs::GetDirListing(source);
533+
534+
for(unsigned int i=0; i < content.size(); i++){
535+
std::string sourcechild = source + DIR_DELIM + content[i].name;
536+
std::string targetchild = target + DIR_DELIM + content[i].name;
537+
if(content[i].dir){
538+
if(!fs::CopyDir(sourcechild, targetchild)){
539+
retval = false;
540+
}
541+
}
542+
else {
543+
if(!fs::CopyFileContents(sourcechild, targetchild)){
544+
retval = false;
545+
}
546+
}
547+
}
548+
return retval;
549+
}
550+
else {
551+
return false;
552+
}
553+
}
554+
555+
bool PathStartsWith(std::string path, std::string prefix)
556+
{
557+
size_t pathsize = path.size();
558+
size_t pathpos = 0;
559+
size_t prefixsize = prefix.size();
560+
size_t prefixpos = 0;
561+
for(;;){
562+
bool delim1 = pathpos == pathsize
563+
|| IsDirDelimiter(path[pathpos]);
564+
bool delim2 = prefixpos == prefixsize
565+
|| IsDirDelimiter(prefix[prefixpos]);
566+
567+
if(delim1 != delim2)
568+
return false;
569+
570+
if(delim1){
571+
while(pathpos < pathsize &&
572+
IsDirDelimiter(path[pathpos]))
573+
++pathpos;
574+
while(prefixpos < prefixsize &&
575+
IsDirDelimiter(prefix[prefixpos]))
576+
++prefixpos;
577+
if(prefixpos == prefixsize)
578+
return true;
579+
if(pathpos == pathsize)
580+
return false;
581+
}
582+
else{
583+
size_t len = 0;
584+
do{
585+
char pathchar = path[pathpos+len];
586+
char prefixchar = prefix[prefixpos+len];
587+
if(FILESYS_CASE_INSENSITIVE){
588+
pathchar = tolower(pathchar);
589+
prefixchar = tolower(prefixchar);
590+
}
591+
if(pathchar != prefixchar)
592+
return false;
593+
++len;
594+
} while(pathpos+len < pathsize
595+
&& !IsDirDelimiter(path[pathpos+len])
596+
&& prefixpos+len < prefixsize
597+
&& !IsDirDelimiter(
598+
prefix[prefixsize+len]));
599+
pathpos += len;
600+
prefixpos += len;
601+
}
602+
}
603+
}
604+
605+
std::string RemoveLastPathComponent(std::string path,
606+
std::string *removed, int count)
607+
{
608+
if(removed)
609+
*removed = "";
610+
611+
size_t remaining = path.size();
612+
613+
for(int i = 0; i < count; ++i){
614+
// strip a dir delimiter
615+
while(remaining != 0 && IsDirDelimiter(path[remaining-1]))
616+
remaining--;
617+
// strip a path component
618+
size_t component_end = remaining;
619+
while(remaining != 0 && !IsDirDelimiter(path[remaining-1]))
620+
remaining--;
621+
size_t component_start = remaining;
622+
// strip a dir delimiter
623+
while(remaining != 0 && IsDirDelimiter(path[remaining-1]))
624+
remaining--;
625+
if(removed){
626+
std::string component = path.substr(component_start,
627+
component_end - component_start);
628+
if(i)
629+
*removed = component + DIR_DELIM + *removed;
630+
else
631+
*removed = component;
632+
}
633+
}
634+
return path.substr(0, remaining);
635+
}
636+
637+
std::string RemoveRelativePathComponents(std::string path)
638+
{
639+
size_t pos = path.size();
640+
size_t dotdot_count = 0;
641+
while(pos != 0){
642+
size_t component_with_delim_end = pos;
643+
// skip a dir delimiter
644+
while(pos != 0 && IsDirDelimiter(path[pos-1]))
645+
pos--;
646+
// strip a path component
647+
size_t component_end = pos;
648+
while(pos != 0 && !IsDirDelimiter(path[pos-1]))
649+
pos--;
650+
size_t component_start = pos;
651+
652+
std::string component = path.substr(component_start,
653+
component_end - component_start);
654+
bool remove_this_component = false;
655+
if(component == "."){
656+
remove_this_component = true;
657+
}
658+
else if(component == ".."){
659+
remove_this_component = true;
660+
dotdot_count += 1;
661+
}
662+
else if(dotdot_count != 0){
663+
remove_this_component = true;
664+
dotdot_count -= 1;
665+
}
666+
667+
if(remove_this_component){
668+
while(pos != 0 && IsDirDelimiter(path[pos-1]))
669+
pos--;
670+
path = path.substr(0, pos) + DIR_DELIM +
671+
path.substr(component_with_delim_end,
672+
std::string::npos);
673+
pos++;
674+
}
675+
}
676+
677+
if(dotdot_count > 0)
678+
return "";
679+
680+
// remove trailing dir delimiters
681+
pos = path.size();
682+
while(pos != 0 && IsDirDelimiter(path[pos-1]))
683+
pos--;
684+
return path.substr(0, pos);
685+
}
686+
434687
} // namespace fs
435688

‎src/filesys.h

+31-2
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
2626

2727
#ifdef _WIN32 // WINDOWS
2828
#define DIR_DELIM "\\"
29-
#define DIR_DELIM_C '\\'
29+
#define FILESYS_CASE_INSENSITIVE 1
3030
#else // POSIX
3131
#define DIR_DELIM "/"
32-
#define DIR_DELIM_C '/'
32+
#define FILESYS_CASE_INSENSITIVE 0
3333
#endif
3434

3535
namespace fs
@@ -49,12 +49,17 @@ bool PathExists(std::string path);
4949

5050
bool IsDir(std::string path);
5151

52+
bool IsDirDelimiter(char c);
53+
5254
// Only pass full paths to this one. True on success.
5355
// NOTE: The WIN32 version returns always true.
5456
bool RecursiveDelete(std::string path);
5557

5658
bool DeleteSingleFileOrEmptyDirectory(std::string path);
5759

60+
// Returns path to temp directory, can return "" on error
61+
std::string TempPath();
62+
5863
/* Multiplatform */
5964

6065
// The path itself not included
@@ -69,6 +74,30 @@ bool RecursiveDeleteContent(std::string path);
6974
// Create all directories on the given path that don't already exist.
7075
bool CreateAllDirs(std::string path);
7176

77+
// Copy a regular file
78+
bool CopyFileContents(std::string source, std::string target);
79+
80+
// Copy directory and all subdirectories
81+
// Omits files and subdirectories that start with a period
82+
bool CopyDir(std::string source, std::string target);
83+
84+
// Check if one path is prefix of another
85+
// For example, "/tmp" is a prefix of "/tmp" and "/tmp/file" but not "/tmp2"
86+
// Ignores case differences and '/' vs. '\\' on Windows
87+
bool PathStartsWith(std::string path, std::string prefix);
88+
89+
// Remove last path component and the dir delimiter before and/or after it,
90+
// returns "" if there is only one path component.
91+
// removed: If non-NULL, receives the removed component(s).
92+
// count: Number of components to remove
93+
std::string RemoveLastPathComponent(std::string path,
94+
std::string *removed = NULL, int count = 1);
95+
96+
// Remove "." and ".." path components and for every ".." removed, remove
97+
// the last normal path component before it. Unlike AbsolutePath,
98+
// this does not resolve symlinks and check for existence of directories.
99+
std::string RemoveRelativePathComponents(std::string path);
100+
72101
}//fs
73102

74103
#endif

‎src/game.cpp

-27
Original file line numberDiff line numberDiff line change
@@ -208,33 +208,6 @@ class PlayerInventoryFormSource: public IFormSource
208208
Client *m_client;
209209
};
210210

211-
class FormspecFormSource: public IFormSource
212-
{
213-
public:
214-
FormspecFormSource(std::string formspec,FormspecFormSource** game_formspec)
215-
{
216-
m_formspec = formspec;
217-
m_game_formspec = game_formspec;
218-
}
219-
220-
~FormspecFormSource()
221-
{
222-
*m_game_formspec = 0;
223-
}
224-
225-
void setForm(std::string formspec) {
226-
m_formspec = formspec;
227-
}
228-
229-
std::string getForm()
230-
{
231-
return m_formspec;
232-
}
233-
234-
std::string m_formspec;
235-
FormspecFormSource** m_game_formspec;
236-
};
237-
238211
/*
239212
Check if a node is pointable
240213
*/

‎src/guiConfigureWorld.cpp

-693
This file was deleted.

‎src/guiConfigureWorld.h

-107
This file was deleted.

‎src/guiConfirmMenu.cpp

-204
This file was deleted.

‎src/guiCreateWorld.cpp

-280
This file was deleted.

‎src/guiCreateWorld.h

-64
This file was deleted.

‎src/guiEngine.cpp

+570
Large diffs are not rendered by default.

‎src/guiEngine.h

+260
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/*
2+
Minetest
3+
Copyright (C) 2013 sapier
4+
5+
This program is free software; you can redistribute it and/or modify
6+
it under the terms of the GNU Lesser General Public License as published by
7+
the Free Software Foundation; either version 2.1 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public License along
16+
with this program; if not, write to the Free Software Foundation, Inc.,
17+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*/
19+
20+
#ifndef GUI_ENGINE_H_
21+
#define GUI_ENGINE_H_
22+
23+
/******************************************************************************/
24+
/* Includes */
25+
/******************************************************************************/
26+
#include "irrlichttypes.h"
27+
#include "modalMenu.h"
28+
#include "clouds.h"
29+
#include "guiLuaApi.h"
30+
#include "guiFormSpecMenu.h"
31+
32+
/******************************************************************************/
33+
/* Typedefs and macros */
34+
/******************************************************************************/
35+
#define MAX_MENUBAR_BTN_COUNT 10
36+
#define MAX_MENUBAR_BTN_ID 256
37+
#define MIN_MENUBAR_BTN_ID (MAX_MENUBAR_BTN_ID - MAX_MENUBAR_BTN_COUNT)
38+
39+
/** texture layer ids */
40+
typedef enum {
41+
TEX_LAYER_BACKGROUND = 0,
42+
TEX_LAYER_OVERLAY,
43+
TEX_LAYER_HEADER,
44+
TEX_LAYER_FOOTER,
45+
TEX_LAYER_MAX
46+
} texture_layer;
47+
48+
/******************************************************************************/
49+
/* forward declarations */
50+
/******************************************************************************/
51+
class GUIEngine;
52+
struct MainMenuData;
53+
54+
/******************************************************************************/
55+
/* declarations */
56+
/******************************************************************************/
57+
58+
/** GUIEngine specific implementation of TextDest used within guiFormSpecMenu */
59+
class TextDestGuiEngine : public TextDest
60+
{
61+
public:
62+
/**
63+
* default constructor
64+
* @param engine the engine data is transmitted for further processing
65+
*/
66+
TextDestGuiEngine(GUIEngine* engine);
67+
/**
68+
* receive fields transmitted by guiFormSpecMenu
69+
* @param fields map containing formspec field elements currently active
70+
*/
71+
void gotText(std::map<std::string, std::string> fields);
72+
73+
/**
74+
* receive text/events transmitted by guiFormSpecMenu
75+
* @param text textual representation of event
76+
*/
77+
void gotText(std::wstring text);
78+
private:
79+
/** target to transmit data to */
80+
GUIEngine* m_engine;
81+
};
82+
83+
84+
/** implementation of main menu based uppon formspecs */
85+
class GUIEngine {
86+
public:
87+
/** TextDestGuiEngine needs to transfer data to engine */
88+
friend class TextDestGuiEngine;
89+
/** guiLuaApi is used to initialize contained stack */
90+
friend class guiLuaApi;
91+
92+
/**
93+
* default constructor
94+
* @param dev device to draw at
95+
* @param parent parent gui element
96+
* @param menumgr manager to add menus to
97+
* @param smgr scene manager to add scene elements to
98+
* @param data struct to transfer data to main game handling
99+
*/
100+
GUIEngine( irr::IrrlichtDevice* dev,
101+
gui::IGUIElement* parent,
102+
IMenuManager *menumgr,
103+
scene::ISceneManager* smgr,
104+
MainMenuData* data);
105+
106+
/** default destructor */
107+
virtual ~GUIEngine();
108+
109+
protected:
110+
/**
111+
* process field data recieved from formspec
112+
* @param fields data in field format
113+
*/
114+
void handleButtons(std::map<std::string, std::string> fields);
115+
/**
116+
* process events received from formspec
117+
* @param text events in textual form
118+
*/
119+
void handleEvent(std::string text);
120+
121+
/**
122+
* return dir of current menuscript
123+
*/
124+
std::string getScriptDir() {
125+
return m_scriptdir;
126+
}
127+
128+
private:
129+
130+
/* run main menu loop */
131+
void run();
132+
133+
/** handler to limit frame rate within main menu */
134+
void limitFrameRate();
135+
136+
/** device to draw at */
137+
irr::IrrlichtDevice* m_device;
138+
/** parent gui element */
139+
gui::IGUIElement* m_parent;
140+
/** manager to add menus to */
141+
IMenuManager* m_menumanager;
142+
/** scene manager to add scene elements to */
143+
scene::ISceneManager* m_smgr;
144+
/** pointer to data beeing transfered back to main game handling */
145+
MainMenuData* m_data;
146+
147+
/** representation of form source to be used in mainmenu formspec */
148+
FormspecFormSource* m_formspecgui;
149+
/** formspec input receiver */
150+
TextDestGuiEngine* m_buttonhandler;
151+
/** the formspec menu */
152+
GUIFormSpecMenu* m_menu;
153+
154+
/** variable used to abort menu and return back to main game handling */
155+
bool m_startgame;
156+
157+
/**
158+
* initialize lua stack
159+
* @param L stack to initialize
160+
*/
161+
void initalize_api(lua_State * L);
162+
163+
/**
164+
* run a lua script
165+
* @param script full path to script to run
166+
*/
167+
bool runScript(std::string script);
168+
169+
/**
170+
* script error handler to process errors within lua
171+
*/
172+
void scriptError(const char *fmt, ...);
173+
174+
/** lua stack */
175+
lua_State* m_engineluastack;
176+
/** lua internal stack number of error handler*/
177+
int m_luaerrorhandler;
178+
179+
/** script basefolder */
180+
std::string m_scriptdir;
181+
182+
/**
183+
* draw background layer
184+
* @param driver to use for drawing
185+
*/
186+
void drawBackground(video::IVideoDriver* driver);
187+
/**
188+
* draw overlay layer
189+
* @param driver to use for drawing
190+
*/
191+
void drawOverlay(video::IVideoDriver* driver);
192+
/**
193+
* draw header layer
194+
* @param driver to use for drawing
195+
*/
196+
void drawHeader(video::IVideoDriver* driver);
197+
/**
198+
* draw footer layer
199+
* @param driver to use for drawing
200+
*/
201+
void drawFooter(video::IVideoDriver* driver);
202+
203+
/**
204+
* load a texture for a specified layer
205+
* @param layer draw layer to specify texture
206+
* @param texturepath full path of texture to load
207+
*/
208+
bool setTexture(texture_layer layer,std::string texturepath);
209+
210+
/**
211+
* download a file using curl
212+
* @param url url to download
213+
* @param target file to store to
214+
*/
215+
bool downloadFile(std::string url,std::string target);
216+
217+
/** array containing pointers to current specified texture layers */
218+
video::ITexture* m_textures[TEX_LAYER_MAX];
219+
220+
/** draw version string in topleft corner */
221+
void drawVersion();
222+
223+
/**
224+
* specify text to be appended to version string
225+
* @param text to set
226+
*/
227+
void setTopleftText(std::string append);
228+
229+
/** pointer to gui element shown at topleft corner */
230+
irr::gui::IGUIStaticText* m_irr_toplefttext;
231+
232+
/** initialize cloud subsystem */
233+
void cloudInit();
234+
/** do preprocessing for cloud subsystem */
235+
void cloudPreProcess();
236+
/** do postprocessing for cloud subsystem */
237+
void cloudPostProcess();
238+
239+
/** internam data required for drawing clouds */
240+
struct clouddata {
241+
/** delta time since last cloud processing */
242+
f32 dtime;
243+
/** absolute time of last cloud processing */
244+
u32 lasttime;
245+
/** pointer to cloud class */
246+
Clouds* clouds;
247+
/** camera required for drawing clouds */
248+
scene::ICameraSceneNode* camera;
249+
};
250+
251+
/** is drawing of clouds enabled atm */
252+
bool m_clouds_enabled;
253+
/** data used to draw clouds */
254+
clouddata m_cloud;
255+
256+
};
257+
258+
259+
260+
#endif /* GUI_ENGINE_H_ */

‎src/guiFileSelectMenu.cpp

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
Minetest
3+
Copyright (C) 2013 sapier
4+
5+
This program is free software; you can redistribute it and/or modify
6+
it under the terms of the GNU Lesser General Public License as published by
7+
the Free Software Foundation; either version 2.1 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public License along
16+
with this program; if not, write to the Free Software Foundation, Inc.,
17+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*/
19+
20+
#include "guiFileSelectMenu.h"
21+
#include "util/string.h"
22+
#include <locale.h>
23+
24+
GUIFileSelectMenu::GUIFileSelectMenu(gui::IGUIEnvironment* env,
25+
gui::IGUIElement* parent, s32 id, IMenuManager *menumgr,
26+
std::string title, std::string formname) :
27+
GUIModalMenu(env, parent, id, menumgr)
28+
{
29+
m_title = narrow_to_wide(title);
30+
m_parent = parent;
31+
m_formname = formname;
32+
m_text_dst = 0;
33+
m_accepted = false;
34+
m_previous_locale = setlocale(LC_ALL,0);
35+
}
36+
37+
GUIFileSelectMenu::~GUIFileSelectMenu()
38+
{
39+
removeChildren();
40+
setlocale(LC_ALL,m_previous_locale.c_str());
41+
}
42+
43+
void GUIFileSelectMenu::removeChildren()
44+
{
45+
const core::list<gui::IGUIElement*> &children = getChildren();
46+
core::list<gui::IGUIElement*> children_copy;
47+
for (core::list<gui::IGUIElement*>::ConstIterator i = children.begin(); i
48+
!= children.end(); i++)
49+
{
50+
children_copy.push_back(*i);
51+
}
52+
for (core::list<gui::IGUIElement*>::Iterator i = children_copy.begin(); i
53+
!= children_copy.end(); i++)
54+
{
55+
(*i)->remove();
56+
}
57+
}
58+
59+
void GUIFileSelectMenu::regenerateGui(v2u32 screensize)
60+
{
61+
removeChildren();
62+
m_fileOpenDialog = 0;
63+
64+
core::dimension2du size(600,400);
65+
core::rect < s32 > rect(0,0,screensize.X,screensize.Y);
66+
67+
DesiredRect = rect;
68+
recalculateAbsolutePosition(false);
69+
70+
m_fileOpenDialog =
71+
Environment->addFileOpenDialog(m_title.c_str(),false,this,-1);
72+
73+
core::position2di pos = core::position2di(screensize.X/2 - size.Width/2,screensize.Y/2 -size.Height/2);
74+
m_fileOpenDialog->setRelativePosition(pos);
75+
m_fileOpenDialog->setMinSize(size);
76+
}
77+
78+
void GUIFileSelectMenu::drawMenu()
79+
{
80+
gui::IGUISkin* skin = Environment->getSkin();
81+
if (!skin)
82+
return;
83+
84+
gui::IGUIElement::draw();
85+
}
86+
87+
void GUIFileSelectMenu::acceptInput() {
88+
if ((m_text_dst != 0) && (this->m_formname != "")){
89+
std::map<std::string, std::string> fields;
90+
91+
if (m_accepted)
92+
fields[m_formname + "_accepted"] = wide_to_narrow(m_fileOpenDialog->getFileName());
93+
else
94+
fields[m_formname + "_canceled"] = m_formname;
95+
96+
this->m_text_dst->gotText(fields);
97+
}
98+
}
99+
100+
bool GUIFileSelectMenu::OnEvent(const SEvent& event)
101+
{
102+
103+
if (event.EventType == irr::EET_GUI_EVENT) {
104+
105+
int callerId = event.GUIEvent.Caller->getID();
106+
if (callerId >= 0) {
107+
std::cout << "CallerId:" << callerId << std::endl;
108+
}
109+
110+
switch (event.GUIEvent.EventType) {
111+
case gui::EGET_ELEMENT_CLOSED:
112+
case gui::EGET_FILE_CHOOSE_DIALOG_CANCELLED:
113+
m_accepted=false;
114+
acceptInput();
115+
quitMenu();
116+
return true;
117+
break;
118+
119+
case gui::EGET_DIRECTORY_SELECTED:
120+
case gui::EGET_FILE_SELECTED:
121+
m_accepted=true;
122+
acceptInput();
123+
quitMenu();
124+
return true;
125+
break;
126+
127+
default:
128+
//ignore this event
129+
break;
130+
}
131+
}
132+
return Parent ? Parent->OnEvent(event) : false;
133+
}

‎src/guiFileSelectMenu.h

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
Minetest
3+
Copyright (C) 2013 sapier
4+
5+
This program is free software; you can redistribute it and/or modify
6+
it under the terms of the GNU Lesser General Public License as published by
7+
the Free Software Foundation; either version 2.1 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public License along
16+
with this program; if not, write to the Free Software Foundation, Inc.,
17+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*/
19+
20+
#ifndef GUIFILESELECTMENU_H_
21+
#define GUIFILESELECTMENU_H_
22+
23+
#include <string>
24+
25+
#include "modalMenu.h"
26+
#include "IGUIFileOpenDialog.h"
27+
#include "guiFormSpecMenu.h" //required because of TextDest only !!!
28+
29+
30+
class GUIFileSelectMenu: public GUIModalMenu
31+
{
32+
public:
33+
GUIFileSelectMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent,
34+
s32 id, IMenuManager *menumgr,
35+
std::string title,
36+
std::string formid);
37+
~GUIFileSelectMenu();
38+
39+
void removeChildren();
40+
41+
/*
42+
Remove and re-add (or reposition) stuff
43+
*/
44+
void regenerateGui(v2u32 screensize);
45+
46+
void drawMenu();
47+
48+
bool OnEvent(const SEvent& event);
49+
50+
bool isRunning() {
51+
return m_running;
52+
}
53+
54+
void setTextDest(TextDest * dest) {
55+
m_text_dst = dest;
56+
}
57+
58+
private:
59+
void acceptInput();
60+
61+
std::wstring m_title;
62+
bool m_accepted;
63+
gui::IGUIElement* m_parent;
64+
65+
std::string m_selectedPath;
66+
67+
gui::IGUIFileOpenDialog* m_fileOpenDialog;
68+
69+
std::string m_previous_locale;
70+
71+
bool m_running;
72+
73+
TextDest *m_text_dst;
74+
75+
std::string m_formname;
76+
};
77+
78+
79+
80+
#endif /* GUIFILESELECTMENU_H_ */

0 commit comments

Comments
 (0)
Please sign in to comment.