dlg_settings_advanced.lua 31 KB

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