dlg_settings_advanced.lua 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007
  1. --Minetest
  2. --Copyright (C) 2015 PilzAdam
  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. local FILENAME = "settingtypes.txt"
  18. local CHAR_CLASSES = {
  19. SPACE = "[%s]",
  20. VARIABLE = "[%w_%-%.]",
  21. INTEGER = "[+-]?[%d]",
  22. FLOAT = "[+-]?[%d%.]",
  23. FLAGS = "[%w_%-%.,]",
  24. }
  25. local function flags_to_table(flags)
  26. return flags:gsub("%s+", ""):split(",", true) -- Remove all spaces and split
  27. end
  28. -- returns error message, or nil
  29. local function parse_setting_line(settings, line, read_all, base_level, allow_secure)
  30. -- comment
  31. local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$")
  32. if comment then
  33. if settings.current_comment == "" then
  34. settings.current_comment = comment
  35. else
  36. settings.current_comment = settings.current_comment .. "\n" .. comment
  37. end
  38. return
  39. end
  40. -- clear current_comment so only comments directly above a setting are bound to it
  41. -- but keep a local reference to it for variables in the current line
  42. local current_comment = settings.current_comment
  43. settings.current_comment = ""
  44. -- empty lines
  45. if line:match("^" .. CHAR_CLASSES.SPACE .. "*$") then
  46. return
  47. end
  48. -- category
  49. local stars, category = line:match("^%[([%*]*)([^%]]+)%]$")
  50. if category then
  51. table.insert(settings, {
  52. name = category,
  53. level = stars:len() + base_level,
  54. type = "category",
  55. })
  56. return
  57. end
  58. -- settings
  59. local first_part, name, readable_name, setting_type = line:match("^"
  60. -- this first capture group matches the whole first part,
  61. -- so we can later strip it from the rest of the line
  62. .. "("
  63. .. "([" .. CHAR_CLASSES.VARIABLE .. "+)" -- variable name
  64. .. CHAR_CLASSES.SPACE .. "*"
  65. .. "%(([^%)]*)%)" -- readable name
  66. .. CHAR_CLASSES.SPACE .. "*"
  67. .. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type
  68. .. CHAR_CLASSES.SPACE .. "*"
  69. .. ")")
  70. if not first_part then
  71. return "Invalid line"
  72. end
  73. if name:match("secure%.[.]*") and not allow_secure then
  74. return "Tried to add \"secure.\" setting"
  75. end
  76. if readable_name == "" then
  77. readable_name = nil
  78. end
  79. local remaining_line = line:sub(first_part:len() + 1)
  80. if setting_type == "int" then
  81. local default, min, max = remaining_line:match("^"
  82. -- first int is required, the last 2 are optional
  83. .. "(" .. CHAR_CLASSES.INTEGER .. "+)" .. CHAR_CLASSES.SPACE .. "*"
  84. .. "(" .. CHAR_CLASSES.INTEGER .. "*)" .. CHAR_CLASSES.SPACE .. "*"
  85. .. "(" .. CHAR_CLASSES.INTEGER .. "*)"
  86. .. "$")
  87. if not default or not tonumber(default) then
  88. return "Invalid integer setting"
  89. end
  90. min = tonumber(min)
  91. max = tonumber(max)
  92. table.insert(settings, {
  93. name = name,
  94. readable_name = readable_name,
  95. type = "int",
  96. default = default,
  97. min = min,
  98. max = max,
  99. comment = current_comment,
  100. })
  101. return
  102. end
  103. if setting_type == "string"
  104. or setting_type == "key" or setting_type == "v3f" then
  105. local default = remaining_line:match("^(.*)$")
  106. if not default then
  107. return "Invalid string setting"
  108. end
  109. if setting_type == "key" and not read_all then
  110. -- ignore key type if read_all is false
  111. return
  112. end
  113. table.insert(settings, {
  114. name = name,
  115. readable_name = readable_name,
  116. type = setting_type,
  117. default = default,
  118. comment = current_comment,
  119. })
  120. return
  121. end
  122. if setting_type == "noise_params_2d"
  123. or setting_type == "noise_params_3d" then
  124. local default = remaining_line:match("^(.*)$")
  125. if not default then
  126. return "Invalid string setting"
  127. end
  128. local values = {}
  129. local ti = 1
  130. local index = 1
  131. for line in default:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
  132. index = default:find("[+-]?[%d.-e]+", index) + line:len()
  133. table.insert(values, line)
  134. ti = ti + 1
  135. if ti > 9 then
  136. break
  137. end
  138. end
  139. index = default:find("[^, ]", index)
  140. local flags = ""
  141. if index then
  142. flags = default:sub(index)
  143. default = default:sub(1, index - 3) -- Make sure no flags in single-line format
  144. end
  145. table.insert(values, flags)
  146. table.insert(settings, {
  147. name = name,
  148. readable_name = readable_name,
  149. type = setting_type,
  150. default = default,
  151. default_table = {
  152. offset = values[1],
  153. scale = values[2],
  154. spread = {
  155. x = values[3],
  156. y = values[4],
  157. z = values[5]
  158. },
  159. seed = values[6],
  160. octaves = values[7],
  161. persistence = values[8],
  162. lacunarity = values[9],
  163. flags = values[10]
  164. },
  165. values = values,
  166. comment = current_comment,
  167. noise_params = true,
  168. flags = flags_to_table("defaults,eased,absvalue")
  169. })
  170. return
  171. end
  172. if setting_type == "bool" then
  173. if remaining_line ~= "false" and remaining_line ~= "true" then
  174. return "Invalid boolean setting"
  175. end
  176. table.insert(settings, {
  177. name = name,
  178. readable_name = readable_name,
  179. type = "bool",
  180. default = remaining_line,
  181. comment = current_comment,
  182. })
  183. return
  184. end
  185. if setting_type == "float" then
  186. local default, min, max = remaining_line:match("^"
  187. -- first float is required, the last 2 are optional
  188. .. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "*"
  189. .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "*"
  190. .. "(" .. CHAR_CLASSES.FLOAT .. "*)"
  191. .."$")
  192. if not default or not tonumber(default) then
  193. return "Invalid float setting"
  194. end
  195. min = tonumber(min)
  196. max = tonumber(max)
  197. table.insert(settings, {
  198. name = name,
  199. readable_name = readable_name,
  200. type = "float",
  201. default = default,
  202. min = min,
  203. max = max,
  204. comment = current_comment,
  205. })
  206. return
  207. end
  208. if setting_type == "enum" then
  209. local default, values = remaining_line:match("^"
  210. -- first value (default) may be empty (i.e. is optional)
  211. .. "(" .. CHAR_CLASSES.VARIABLE .. "*)" .. CHAR_CLASSES.SPACE .. "*"
  212. .. "(" .. CHAR_CLASSES.FLAGS .. "+)"
  213. .. "$")
  214. if not default or values == "" then
  215. return "Invalid enum setting"
  216. end
  217. table.insert(settings, {
  218. name = name,
  219. readable_name = readable_name,
  220. type = "enum",
  221. default = default,
  222. values = values:split(",", true),
  223. comment = current_comment,
  224. })
  225. return
  226. end
  227. if setting_type == "path" or setting_type == "filepath" then
  228. local default = remaining_line:match("^(.*)$")
  229. if not default then
  230. return "Invalid path setting"
  231. end
  232. table.insert(settings, {
  233. name = name,
  234. readable_name = readable_name,
  235. type = setting_type,
  236. default = default,
  237. comment = current_comment,
  238. })
  239. return
  240. end
  241. if setting_type == "flags" then
  242. local default, possible = remaining_line:match("^"
  243. -- first value (default) may be empty (i.e. is optional)
  244. -- this is implemented by making the last value optional, and
  245. -- swapping them around if it turns out empty.
  246. .. "(" .. CHAR_CLASSES.FLAGS .. "+)" .. CHAR_CLASSES.SPACE .. "*"
  247. .. "(" .. CHAR_CLASSES.FLAGS .. "*)"
  248. .. "$")
  249. if not default or not possible then
  250. return "Invalid flags setting"
  251. end
  252. if possible == "" then
  253. possible = default
  254. default = ""
  255. end
  256. table.insert(settings, {
  257. name = name,
  258. readable_name = readable_name,
  259. type = "flags",
  260. default = default,
  261. possible = flags_to_table(possible),
  262. comment = current_comment,
  263. })
  264. return
  265. end
  266. return "Invalid setting type \"" .. setting_type .. "\""
  267. end
  268. local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure)
  269. -- store this helper variable in the table so it's easier to pass to parse_setting_line()
  270. result.current_comment = ""
  271. local line = file:read("*line")
  272. while line do
  273. local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure)
  274. if error_msg then
  275. core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"")
  276. end
  277. line = file:read("*line")
  278. end
  279. result.current_comment = nil
  280. end
  281. -- read_all: whether to ignore certain setting types for GUI or not
  282. -- parse_mods: whether to parse settingtypes.txt in mods and games
  283. local function parse_config_file(read_all, parse_mods)
  284. local builtin_path = core.get_builtin_path() .. FILENAME
  285. local file = io.open(builtin_path, "r")
  286. local settings = {}
  287. if not file then
  288. core.log("error", "Can't load " .. FILENAME)
  289. return settings
  290. end
  291. parse_single_file(file, builtin_path, read_all, settings, 0, true)
  292. file:close()
  293. if parse_mods then
  294. -- Parse games
  295. local games_category_initialized = false
  296. local index = 1
  297. local game = gamemgr.get_game(index)
  298. while game do
  299. local path = game.path .. DIR_DELIM .. FILENAME
  300. local file = io.open(path, "r")
  301. if file then
  302. if not games_category_initialized then
  303. local translation = fgettext_ne("Games"), -- not used, but needed for xgettext
  304. table.insert(settings, {
  305. name = "Games",
  306. level = 0,
  307. type = "category",
  308. })
  309. games_category_initialized = true
  310. end
  311. table.insert(settings, {
  312. name = game.name,
  313. level = 1,
  314. type = "category",
  315. })
  316. parse_single_file(file, path, read_all, settings, 2, false)
  317. file:close()
  318. end
  319. index = index + 1
  320. game = gamemgr.get_game(index)
  321. end
  322. -- Parse mods
  323. local mods_category_initialized = false
  324. local mods = {}
  325. get_mods(core.get_modpath(), mods)
  326. for _, mod in ipairs(mods) do
  327. local path = mod.path .. DIR_DELIM .. FILENAME
  328. local file = io.open(path, "r")
  329. if file then
  330. if not mods_category_initialized then
  331. local translation = fgettext_ne("Mods"), -- not used, but needed for xgettext
  332. table.insert(settings, {
  333. name = "Mods",
  334. level = 0,
  335. type = "category",
  336. })
  337. mods_category_initialized = true
  338. end
  339. table.insert(settings, {
  340. name = mod.name,
  341. level = 1,
  342. type = "category",
  343. })
  344. parse_single_file(file, path, read_all, settings, 2, false)
  345. file:close()
  346. end
  347. end
  348. end
  349. return settings
  350. end
  351. local function filter_settings(settings, searchstring)
  352. if not searchstring or searchstring == "" then
  353. return settings, -1
  354. end
  355. -- Setup the keyword list
  356. local keywords = {}
  357. for word in searchstring:lower():gmatch("%S+") do
  358. table.insert(keywords, word)
  359. end
  360. local result = {}
  361. local category_stack = {}
  362. local current_level = 0
  363. local best_setting = nil
  364. for _, entry in pairs(settings) do
  365. if entry.type == "category" then
  366. -- Remove all settingless categories
  367. while #category_stack > 0 and entry.level <= current_level do
  368. table.remove(category_stack, #category_stack)
  369. if #category_stack > 0 then
  370. current_level = category_stack[#category_stack].level
  371. else
  372. current_level = 0
  373. end
  374. end
  375. -- Push category onto stack
  376. category_stack[#category_stack + 1] = entry
  377. current_level = entry.level
  378. else
  379. -- See if setting matches keywords
  380. local setting_score = 0
  381. for k = 1, #keywords do
  382. local keyword = keywords[k]
  383. if string.find(entry.name:lower(), keyword, 1, true) then
  384. setting_score = setting_score + 1
  385. end
  386. if entry.readable_name and
  387. string.find(fgettext(entry.readable_name):lower(), keyword, 1, true) then
  388. setting_score = setting_score + 1
  389. end
  390. if entry.comment and
  391. string.find(fgettext_ne(entry.comment):lower(), keyword, 1, true) then
  392. setting_score = setting_score + 1
  393. end
  394. end
  395. -- Add setting to results if match
  396. if setting_score > 0 then
  397. -- Add parent categories
  398. for _, category in pairs(category_stack) do
  399. result[#result + 1] = category
  400. end
  401. category_stack = {}
  402. -- Add setting
  403. result[#result + 1] = entry
  404. entry.score = setting_score
  405. if not best_setting or
  406. setting_score > result[best_setting].score then
  407. best_setting = #result
  408. end
  409. end
  410. end
  411. end
  412. return result, best_setting or -1
  413. end
  414. local full_settings = parse_config_file(false, true)
  415. local search_string = ""
  416. local settings = full_settings
  417. local selected_setting = 1
  418. local function get_current_value(setting)
  419. local value = core.settings:get(setting.name)
  420. if value == nil then
  421. value = setting.default
  422. end
  423. return value
  424. end
  425. local function get_current_np_group(setting)
  426. local value = core.settings:get_np_group(setting.name)
  427. local t = {}
  428. if value == nil then
  429. t = setting.values
  430. else
  431. table.insert(t, value.offset)
  432. table.insert(t, value.scale)
  433. table.insert(t, value.spread.x)
  434. table.insert(t, value.spread.y)
  435. table.insert(t, value.spread.z)
  436. table.insert(t, value.seed)
  437. table.insert(t, value.octaves)
  438. table.insert(t, value.persistence)
  439. table.insert(t, value.lacunarity)
  440. table.insert(t, value.flags)
  441. end
  442. return t
  443. end
  444. local function get_current_np_group_as_string(setting)
  445. local value = core.settings:get_np_group(setting.name)
  446. local t
  447. if value == nil then
  448. t = setting.default
  449. else
  450. t = value.offset .. ", " ..
  451. value.scale .. ", (" ..
  452. value.spread.x .. ", " ..
  453. value.spread.y .. ", " ..
  454. value.spread.z .. "), " ..
  455. value.seed .. ", " ..
  456. value.octaves .. ", " ..
  457. value.persistence .. ", " ..
  458. value.lacunarity .. ", " ..
  459. value.flags
  460. end
  461. return t
  462. end
  463. local checkboxes = {} -- handle checkboxes events
  464. local function create_change_setting_formspec(dialogdata)
  465. local setting = settings[selected_setting]
  466. local height = 5.2
  467. if setting.type == "noise_params_2d" or setting.type == "noise_params_3d" then
  468. -- Three flags, checkboxes on 2 columns, with a vertical space of 1/2 unit
  469. height = 8.7
  470. elseif setting.type == "flags" then
  471. -- Checkboxes on 2 columns, with a vertical space of 1/2 unit
  472. height = 5.2 + math.ceil(#setting.possible / 2) / 2
  473. end
  474. local formspec = "size[10," .. height .. ",true]" ..
  475. "button[5," .. height - 0.7 .. ";2,1;btn_done;" .. fgettext("Save") .. "]" ..
  476. "button[3," .. height - 0.7 .. ";2,1;btn_cancel;" .. fgettext("Cancel") .. "]" ..
  477. "tablecolumns[color;text]" ..
  478. "tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
  479. "table[0,0;10,3;info;"
  480. if setting.readable_name then
  481. formspec = formspec .. "#FFFF00," .. fgettext(setting.readable_name)
  482. .. " (" .. core.formspec_escape(setting.name) .. "),"
  483. else
  484. formspec = formspec .. "#FFFF00," .. core.formspec_escape(setting.name) .. ","
  485. end
  486. formspec = formspec .. ",,"
  487. local comment_text = ""
  488. if setting.comment == "" then
  489. comment_text = fgettext_ne("(No description of setting given)")
  490. else
  491. comment_text = fgettext_ne(setting.comment)
  492. end
  493. for _, comment_line in ipairs(comment_text:split("\n", true)) do
  494. formspec = formspec .. "," .. core.formspec_escape(comment_line) .. ","
  495. end
  496. formspec = formspec:sub(1, -2) -- remove trailing comma
  497. formspec = formspec .. ";1]"
  498. if setting.type == "bool" then
  499. local selected_index
  500. if core.is_yes(get_current_value(setting)) then
  501. selected_index = 2
  502. else
  503. selected_index = 1
  504. end
  505. formspec = formspec .. "dropdown[0.5,3.5;3,1;dd_setting_value;"
  506. .. fgettext("Disabled") .. "," .. fgettext("Enabled") .. ";"
  507. .. selected_index .. "]"
  508. elseif setting.type == "enum" then
  509. local selected_index = 0
  510. formspec = formspec .. "dropdown[0.5,3.5;3,1;dd_setting_value;"
  511. for index, value in ipairs(setting.values) do
  512. -- translating value is not possible, since it's the value
  513. -- that we set the setting to
  514. formspec = formspec .. core.formspec_escape(value) .. ","
  515. if get_current_value(setting) == value then
  516. selected_index = index
  517. end
  518. end
  519. if #setting.values > 0 then
  520. formspec = formspec:sub(1, -2) -- remove trailing comma
  521. end
  522. formspec = formspec .. ";" .. selected_index .. "]"
  523. elseif setting.type == "path" or setting.type == "filepath" then
  524. local current_value = dialogdata.selected_path
  525. if not current_value then
  526. current_value = get_current_value(setting)
  527. end
  528. formspec = formspec .. "field[0.5,4;7.5,1;te_setting_value;;"
  529. .. core.formspec_escape(current_value) .. "]"
  530. .. "button[8,3.75;2,1;btn_browser_" .. setting.type .. ";" .. fgettext("Browse") .. "]"
  531. elseif setting.type == "noise_params_2d" or setting.type == "noise_params_3d" then
  532. local t = get_current_np_group(setting)
  533. local dimension = 3
  534. if setting.type == "noise_params_2d" then
  535. dimension = 2
  536. end
  537. formspec = formspec
  538. .. "label[0,2.5;(" .. dimension .. "D Noise)]"
  539. .. "field[0.5,4;3.3,1;te_offset;Offset;" -- Offset
  540. .. core.formspec_escape(t[1] or "") .. "]"
  541. .. "field[3.8,4;3.3,1;te_scale;Scale;" -- Scale
  542. .. core.formspec_escape(t[2] or "") .. "]"
  543. .. "field[7.1,4;3.3,1;te_seed;Seed;" -- Seed
  544. .. core.formspec_escape(t[6] or "") .. "]"
  545. .. "label[0.5,4.7;Spread]" -- Spread
  546. .. "field[2.0,5;2.8,1;te_spreadx;X;"
  547. .. core.formspec_escape(t[3] or "") .. "]"
  548. .. "field[4.8,5;2.8,1;te_spready;Y;"
  549. .. core.formspec_escape(t[4] or "") .. "]"
  550. .. "field[7.6,5;2.8,1;te_spreadz;Z;"
  551. .. core.formspec_escape(t[5] or "") .. "]"
  552. .. "field[0.5,6;3.3,1;te_octaves;Octaves;" -- Octaves
  553. .. core.formspec_escape(t[7] or "") .. "]"
  554. .. "field[3.8,6;3.3,1;te_persist;Persistance;" -- Persistance
  555. .. core.formspec_escape(t[8] or "") .. "]"
  556. .. "field[7.1,6;3.3,1;te_lacun;Lacunarity;" -- Lacunarity
  557. .. core.formspec_escape(t[9] or "") .. "]"
  558. local enabled_flags = flags_to_table(t[10])
  559. local flags = {}
  560. for _, name in ipairs(enabled_flags) do
  561. -- Index by name, to avoid iterating over all enabled_flags for every possible flag.
  562. flags[name] = true
  563. end
  564. -- Flags
  565. formspec = formspec
  566. .. "checkbox[0.5,6.5;cb_defaults;defaults;" -- defaults
  567. .. tostring(flags["defaults"] == true) .. "]" -- to get false if nil
  568. .. "checkbox[5,6.5;cb_eased;eased;" -- eased
  569. .. tostring(flags["eased"] == true) .. "]"
  570. .. "checkbox[5,7.0;cb_absvalue;absvalue;" -- absvalue
  571. .. tostring(flags["absvalue"] == true) .. "]"
  572. elseif setting.type == "v3f" then
  573. local val = get_current_value(setting)
  574. local v3f = {}
  575. for line in val:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
  576. table.insert(v3f, line)
  577. end
  578. formspec = formspec
  579. .. "field[0.5,4;3.3,1;te_x;X;" -- X
  580. .. core.formspec_escape(v3f[1] or "") .. "]"
  581. .. "field[3.8,4;3.3,1;te_y;Y;" -- Y
  582. .. core.formspec_escape(v3f[2] or "") .. "]"
  583. .. "field[7.1,4;3.3,1;te_z;Z;" -- Z
  584. .. core.formspec_escape(v3f[3] or "") .. "]"
  585. elseif setting.type == "flags" then
  586. local enabled_flags = flags_to_table(get_current_value(setting))
  587. local flags = {}
  588. for _, name in ipairs(enabled_flags) do
  589. -- Index by name, to avoid iterating over all enabled_flags for every possible flag.
  590. flags[name] = true
  591. end
  592. local flags_count = #setting.possible
  593. for i, name in ipairs(setting.possible) do
  594. local x = 0.5
  595. local y = 3.5 + i / 2
  596. if i - 1 >= flags_count / 2 then -- 2nd column
  597. x = 5
  598. y = y - flags_count / 4
  599. end
  600. local checkbox_name = "cb_" .. name
  601. local is_enabled = flags[name] == true -- to get false if nil
  602. checkboxes[checkbox_name] = is_enabled
  603. formspec = formspec .. "checkbox["
  604. .. x .. "," .. y
  605. .. ";" .. checkbox_name .. ";"
  606. .. name .. ";" .. tostring(is_enabled) .. "]"
  607. end
  608. else
  609. -- TODO: fancy input for float, int
  610. local width = 10
  611. local text = get_current_value(setting)
  612. if dialogdata.error_message then
  613. formspec = formspec .. "tablecolumns[color;text]" ..
  614. "tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
  615. "table[5,3.9;5,0.6;error_message;#FF0000,"
  616. .. core.formspec_escape(dialogdata.error_message) .. ";0]"
  617. width = 5
  618. if dialogdata.entered_text then
  619. text = dialogdata.entered_text
  620. end
  621. end
  622. formspec = formspec .. "field[0.5,4;" .. width .. ",1;te_setting_value;;"
  623. .. core.formspec_escape(text) .. "]"
  624. end
  625. return formspec
  626. end
  627. local function handle_change_setting_buttons(this, fields)
  628. local setting = settings[selected_setting]
  629. if fields["btn_done"] or fields["key_enter"] then
  630. if setting.type == "bool" then
  631. local new_value = fields["dd_setting_value"]
  632. -- Note: new_value is the actual (translated) value shown in the dropdown
  633. core.settings:set_bool(setting.name, new_value == fgettext("Enabled"))
  634. elseif setting.type == "enum" then
  635. local new_value = fields["dd_setting_value"]
  636. core.settings:set(setting.name, new_value)
  637. elseif setting.type == "int" then
  638. local new_value = tonumber(fields["te_setting_value"])
  639. if not new_value or math.floor(new_value) ~= new_value then
  640. this.data.error_message = fgettext_ne("Please enter a valid integer.")
  641. this.data.entered_text = fields["te_setting_value"]
  642. core.update_formspec(this:get_formspec())
  643. return true
  644. end
  645. if setting.min and new_value < setting.min then
  646. this.data.error_message = fgettext_ne("The value must be at least $1.", setting.min)
  647. this.data.entered_text = fields["te_setting_value"]
  648. core.update_formspec(this:get_formspec())
  649. return true
  650. end
  651. if setting.max and new_value > setting.max then
  652. this.data.error_message = fgettext_ne("The value must not be larger than $1.", setting.max)
  653. this.data.entered_text = fields["te_setting_value"]
  654. core.update_formspec(this:get_formspec())
  655. return true
  656. end
  657. core.settings:set(setting.name, new_value)
  658. elseif setting.type == "float" then
  659. local new_value = tonumber(fields["te_setting_value"])
  660. if not new_value then
  661. this.data.error_message = fgettext_ne("Please enter a valid number.")
  662. this.data.entered_text = fields["te_setting_value"]
  663. core.update_formspec(this:get_formspec())
  664. return true
  665. end
  666. core.settings:set(setting.name, new_value)
  667. elseif setting.type == "flags" then
  668. local values = {}
  669. for _, name in ipairs(setting.possible) do
  670. if checkboxes["cb_" .. name] then
  671. table.insert(values, name)
  672. end
  673. end
  674. checkboxes = {}
  675. local new_value = table.concat(values, ", ")
  676. core.settings:set(setting.name, new_value)
  677. elseif setting.type == "noise_params_2d" or setting.type == "noise_params_3d" then
  678. local np_flags = {}
  679. for _, name in ipairs(setting.flags) do
  680. if checkboxes["cb_" .. name] then
  681. table.insert(np_flags, name)
  682. end
  683. end
  684. checkboxes = {}
  685. local new_value = {
  686. offset = fields["te_offset"],
  687. scale = fields["te_scale"],
  688. spread = {
  689. x = fields["te_spreadx"],
  690. y = fields["te_spready"],
  691. z = fields["te_spreadz"]
  692. },
  693. seed = fields["te_seed"],
  694. octaves = fields["te_octaves"],
  695. persistence = fields["te_persist"],
  696. lacunarity = fields["te_lacun"],
  697. flags = table.concat(np_flags, ", ")
  698. }
  699. core.settings:set_np_group(setting.name, new_value)
  700. elseif setting.type == "v3f" then
  701. local new_value = "("
  702. .. fields["te_x"] .. ", "
  703. .. fields["te_y"] .. ", "
  704. .. fields["te_z"] .. ")"
  705. core.settings:set(setting.name, new_value)
  706. else
  707. local new_value = fields["te_setting_value"]
  708. core.settings:set(setting.name, new_value)
  709. end
  710. core.settings:write()
  711. this:delete()
  712. return true
  713. end
  714. if fields["btn_cancel"] then
  715. this:delete()
  716. return true
  717. end
  718. if fields["btn_browser_path"] then
  719. core.show_path_select_dialog("dlg_browse_path",
  720. fgettext_ne("Select directory"), false)
  721. end
  722. if fields["btn_browser_filepath"] then
  723. core.show_path_select_dialog("dlg_browse_path",
  724. fgettext_ne("Select file"), true)
  725. end
  726. if fields["dlg_browse_path_accepted"] then
  727. this.data.selected_path = fields["dlg_browse_path_accepted"]
  728. core.update_formspec(this:get_formspec())
  729. end
  730. if setting.type == "flags"
  731. or setting.type == "noise_params_2d"
  732. or setting.type == "noise_params_3d" then
  733. for name, value in pairs(fields) do
  734. if name:sub(1, 3) == "cb_" then
  735. checkboxes[name] = value == "true"
  736. end
  737. end
  738. end
  739. return false
  740. end
  741. local function create_settings_formspec(tabview, name, tabdata)
  742. local formspec = "size[12,6.5;true]" ..
  743. "tablecolumns[color;tree;text,width=32;text]" ..
  744. "tableoptions[background=#00000000;border=false]" ..
  745. "field[0.3,0.1;10.2,1;search_string;;" .. core.formspec_escape(search_string) .. "]" ..
  746. "field_close_on_enter[search_string;false]" ..
  747. "button[10.2,-0.2;2,1;search;" .. fgettext("Search") .. "]" ..
  748. "table[0,0.8;12,4.5;list_settings;"
  749. local current_level = 0
  750. for _, entry in ipairs(settings) do
  751. local name
  752. if not core.settings:get_bool("main_menu_technical_settings") and entry.readable_name then
  753. name = fgettext_ne(entry.readable_name)
  754. else
  755. name = entry.name
  756. end
  757. if entry.type == "category" then
  758. current_level = entry.level
  759. formspec = formspec .. "#FFFF00," .. current_level .. "," .. fgettext(name) .. ",,"
  760. elseif entry.type == "bool" then
  761. local value = get_current_value(entry)
  762. if core.is_yes(value) then
  763. value = fgettext("Enabled")
  764. else
  765. value = fgettext("Disabled")
  766. end
  767. formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
  768. .. value .. ","
  769. elseif entry.type == "key" then
  770. -- ignore key settings, since we have a special dialog for them
  771. elseif entry.type == "noise_params_2d" or entry.type == "noise_params_3d" then
  772. formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
  773. .. core.formspec_escape(get_current_np_group_as_string(entry)) .. ","
  774. else
  775. formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
  776. .. core.formspec_escape(get_current_value(entry)) .. ","
  777. end
  778. end
  779. if #settings > 0 then
  780. formspec = formspec:sub(1, -2) -- remove trailing comma
  781. end
  782. formspec = formspec .. ";" .. selected_setting .. "]" ..
  783. "button[0,6;4,1;btn_back;".. fgettext("< Back to Settings page") .. "]" ..
  784. "button[10,6;2,1;btn_edit;" .. fgettext("Edit") .. "]" ..
  785. "button[7,6;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" ..
  786. "checkbox[0,5.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";"
  787. .. dump(core.settings:get_bool("main_menu_technical_settings")) .. "]"
  788. return formspec
  789. end
  790. local function handle_settings_buttons(this, fields, tabname, tabdata)
  791. local list_enter = false
  792. if fields["list_settings"] then
  793. selected_setting = core.get_table_index("list_settings")
  794. if core.explode_table_event(fields["list_settings"]).type == "DCL" then
  795. -- Directly toggle booleans
  796. local setting = settings[selected_setting]
  797. if setting and setting.type == "bool" then
  798. local current_value = get_current_value(setting)
  799. core.settings:set_bool(setting.name, not core.is_yes(current_value))
  800. core.settings:write()
  801. return true
  802. else
  803. list_enter = true
  804. end
  805. else
  806. return true
  807. end
  808. end
  809. if fields.search or fields.key_enter_field == "search_string" then
  810. if search_string == fields.search_string then
  811. if selected_setting > 0 then
  812. -- Go to next result on enter press
  813. local i = selected_setting + 1
  814. local looped = false
  815. while i > #settings or settings[i].type == "category" do
  816. i = i + 1
  817. if i > #settings then
  818. -- Stop infinte looping
  819. if looped then
  820. return false
  821. end
  822. i = 1
  823. looped = true
  824. end
  825. end
  826. selected_setting = i
  827. core.update_formspec(this:get_formspec())
  828. return true
  829. end
  830. else
  831. -- Search for setting
  832. search_string = fields.search_string
  833. settings, selected_setting = filter_settings(full_settings, search_string)
  834. core.update_formspec(this:get_formspec())
  835. end
  836. return true
  837. end
  838. if fields["btn_edit"] or list_enter then
  839. local setting = settings[selected_setting]
  840. if setting and setting.type ~= "category" then
  841. local edit_dialog = dialog_create("change_setting", create_change_setting_formspec,
  842. handle_change_setting_buttons)
  843. edit_dialog:set_parent(this)
  844. this:hide()
  845. edit_dialog:show()
  846. end
  847. return true
  848. end
  849. if fields["btn_restore"] then
  850. local setting = settings[selected_setting]
  851. if setting and setting.type ~= "category" then
  852. if setting.type == "noise_params_2d"
  853. or setting.type == "noise_params_3d" then
  854. core.settings:set_np_group(setting.name, setting.default_table)
  855. else
  856. core.settings:set(setting.name, setting.default)
  857. end
  858. core.settings:write()
  859. core.update_formspec(this:get_formspec())
  860. end
  861. return true
  862. end
  863. if fields["btn_back"] then
  864. this:delete()
  865. return true
  866. end
  867. if fields["cb_tech_settings"] then
  868. core.settings:set("main_menu_technical_settings", fields["cb_tech_settings"])
  869. core.settings:write()
  870. core.update_formspec(this:get_formspec())
  871. return true
  872. end
  873. return false
  874. end
  875. function create_adv_settings_dlg()
  876. local dlg = dialog_create("settings_advanced",
  877. create_settings_formspec,
  878. handle_settings_buttons,
  879. nil)
  880. return dlg
  881. end
  882. -- Uncomment to generate minetest.conf.example and settings_translation_file.cpp
  883. -- For RUN_IN_PLACE the generated files may appear in the bin folder
  884. --assert(loadfile(core.get_builtin_path().."mainmenu"..DIR_DELIM.."generate_from_settingtypes.lua"))(parse_config_file(true, false))