dlg_settings_advanced.lua 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083
  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 match in default:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
  132. index = default:find("[+-]?[%d.-e]+", index) + match:len()
  133. table.insert(values, match)
  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 settings = {}
  285. do
  286. local builtin_path = core.get_builtin_path() .. FILENAME
  287. local file = io.open(builtin_path, "r")
  288. if not file then
  289. core.log("error", "Can't load " .. FILENAME)
  290. return settings
  291. end
  292. parse_single_file(file, builtin_path, read_all, settings, 0, true)
  293. file:close()
  294. end
  295. if parse_mods then
  296. -- Parse games
  297. local games_category_initialized = false
  298. local index = 1
  299. local game = pkgmgr.get_game(index)
  300. while game do
  301. local path = game.path .. DIR_DELIM .. FILENAME
  302. local file = io.open(path, "r")
  303. if file then
  304. if not games_category_initialized then
  305. fgettext_ne("Games") -- not used, but needed for xgettext
  306. table.insert(settings, {
  307. name = "Games",
  308. level = 0,
  309. type = "category",
  310. })
  311. games_category_initialized = true
  312. end
  313. table.insert(settings, {
  314. name = game.name,
  315. level = 1,
  316. type = "category",
  317. })
  318. parse_single_file(file, path, read_all, settings, 2, false)
  319. file:close()
  320. end
  321. index = index + 1
  322. game = pkgmgr.get_game(index)
  323. end
  324. -- Parse mods
  325. local mods_category_initialized = false
  326. local mods = {}
  327. get_mods(core.get_modpath(), mods)
  328. for _, mod in ipairs(mods) do
  329. local path = mod.path .. DIR_DELIM .. FILENAME
  330. local file = io.open(path, "r")
  331. if file then
  332. if not mods_category_initialized then
  333. fgettext_ne("Mods") -- not used, but needed for xgettext
  334. table.insert(settings, {
  335. name = "Mods",
  336. level = 0,
  337. type = "category",
  338. })
  339. mods_category_initialized = true
  340. end
  341. table.insert(settings, {
  342. name = mod.name,
  343. level = 1,
  344. type = "category",
  345. })
  346. parse_single_file(file, path, read_all, settings, 2, false)
  347. file:close()
  348. end
  349. end
  350. end
  351. return settings
  352. end
  353. local function filter_settings(settings, searchstring)
  354. if not searchstring or searchstring == "" then
  355. return settings, -1
  356. end
  357. -- Setup the keyword list
  358. local keywords = {}
  359. for word in searchstring:lower():gmatch("%S+") do
  360. table.insert(keywords, word)
  361. end
  362. local result = {}
  363. local category_stack = {}
  364. local current_level = 0
  365. local best_setting = nil
  366. for _, entry in pairs(settings) do
  367. if entry.type == "category" then
  368. -- Remove all settingless categories
  369. while #category_stack > 0 and entry.level <= current_level do
  370. table.remove(category_stack, #category_stack)
  371. if #category_stack > 0 then
  372. current_level = category_stack[#category_stack].level
  373. else
  374. current_level = 0
  375. end
  376. end
  377. -- Push category onto stack
  378. category_stack[#category_stack + 1] = entry
  379. current_level = entry.level
  380. else
  381. -- See if setting matches keywords
  382. local setting_score = 0
  383. for k = 1, #keywords do
  384. local keyword = keywords[k]
  385. if string.find(entry.name:lower(), keyword, 1, true) then
  386. setting_score = setting_score + 1
  387. end
  388. if entry.readable_name and
  389. string.find(fgettext(entry.readable_name):lower(), keyword, 1, true) then
  390. setting_score = setting_score + 1
  391. end
  392. if entry.comment and
  393. string.find(fgettext_ne(entry.comment):lower(), keyword, 1, true) then
  394. setting_score = setting_score + 1
  395. end
  396. end
  397. -- Add setting to results if match
  398. if setting_score > 0 then
  399. -- Add parent categories
  400. for _, category in pairs(category_stack) do
  401. result[#result + 1] = category
  402. end
  403. category_stack = {}
  404. -- Add setting
  405. result[#result + 1] = entry
  406. entry.score = setting_score
  407. if not best_setting or
  408. setting_score > result[best_setting].score then
  409. best_setting = #result
  410. end
  411. end
  412. end
  413. end
  414. return result, best_setting or -1
  415. end
  416. local full_settings = parse_config_file(false, true)
  417. local search_string = ""
  418. local settings = full_settings
  419. local selected_setting = 1
  420. local function get_current_value(setting)
  421. local value = core.settings:get(setting.name)
  422. if value == nil then
  423. value = setting.default
  424. end
  425. return value
  426. end
  427. local function get_current_np_group(setting)
  428. local value = core.settings:get_np_group(setting.name)
  429. local t = {}
  430. if value == nil then
  431. t = setting.values
  432. else
  433. table.insert(t, value.offset)
  434. table.insert(t, value.scale)
  435. table.insert(t, value.spread.x)
  436. table.insert(t, value.spread.y)
  437. table.insert(t, value.spread.z)
  438. table.insert(t, value.seed)
  439. table.insert(t, value.octaves)
  440. table.insert(t, value.persistence)
  441. table.insert(t, value.lacunarity)
  442. table.insert(t, value.flags)
  443. end
  444. return t
  445. end
  446. local function get_current_np_group_as_string(setting)
  447. local value = core.settings:get_np_group(setting.name)
  448. local t
  449. if value == nil then
  450. t = setting.default
  451. else
  452. t = value.offset .. ", " ..
  453. value.scale .. ", (" ..
  454. value.spread.x .. ", " ..
  455. value.spread.y .. ", " ..
  456. value.spread.z .. "), " ..
  457. value.seed .. ", " ..
  458. value.octaves .. ", " ..
  459. value.persistence .. ", " ..
  460. value.lacunarity
  461. if value.flags ~= "" then
  462. t = t .. ", " .. value.flags
  463. end
  464. end
  465. return t
  466. end
  467. local checkboxes = {} -- handle checkboxes events
  468. local function create_change_setting_formspec(dialogdata)
  469. local setting = settings[selected_setting]
  470. -- Final formspec will be created at the end of this function
  471. -- Default values below, may be changed depending on setting type
  472. local width = 10
  473. local height = 3.5
  474. local description_height = 3
  475. local formspec = ""
  476. -- Setting-specific formspec elements
  477. if setting.type == "bool" then
  478. local selected_index = 1
  479. if core.is_yes(get_current_value(setting)) then
  480. selected_index = 2
  481. end
  482. formspec = "dropdown[3," .. height .. ";4,1;dd_setting_value;"
  483. .. fgettext("Disabled") .. "," .. fgettext("Enabled") .. ";"
  484. .. selected_index .. "]"
  485. height = height + 1.25
  486. elseif setting.type == "enum" then
  487. local selected_index = 0
  488. formspec = "dropdown[3," .. height .. ";4,1;dd_setting_value;"
  489. for index, value in ipairs(setting.values) do
  490. -- translating value is not possible, since it's the value
  491. -- that we set the setting to
  492. formspec = formspec .. core.formspec_escape(value) .. ","
  493. if get_current_value(setting) == value then
  494. selected_index = index
  495. end
  496. end
  497. if #setting.values > 0 then
  498. formspec = formspec:sub(1, -2) -- remove trailing comma
  499. end
  500. formspec = formspec .. ";" .. selected_index .. "]"
  501. height = height + 1.25
  502. elseif setting.type == "path" or setting.type == "filepath" then
  503. local current_value = dialogdata.selected_path
  504. if not current_value then
  505. current_value = get_current_value(setting)
  506. end
  507. formspec = "field[0.28," .. height + 0.15 .. ";8,1;te_setting_value;;"
  508. .. core.formspec_escape(current_value) .. "]"
  509. .. "button[8," .. height - 0.15 .. ";2,1;btn_browser_"
  510. .. setting.type .. ";" .. fgettext("Browse") .. "]"
  511. height = height + 1.15
  512. elseif setting.type == "noise_params_2d" or setting.type == "noise_params_3d" then
  513. local t = get_current_np_group(setting)
  514. local dimension = 3
  515. if setting.type == "noise_params_2d" then
  516. dimension = 2
  517. end
  518. -- More space for 3x3 fields
  519. description_height = description_height - 1.5
  520. height = height - 1.5
  521. local fields = {}
  522. local function add_field(x, name, label, value)
  523. fields[#fields + 1] = ("field[%f,%f;3.3,1;%s;%s;%s]"):format(
  524. x, height, name, label, core.formspec_escape(value or "")
  525. )
  526. end
  527. -- First row
  528. height = height + 0.3
  529. add_field(0.3, "te_offset", fgettext("Offset"), t[1])
  530. add_field(3.6, "te_scale", fgettext("Scale"), t[2])
  531. add_field(6.9, "te_seed", fgettext("Seed"), t[6])
  532. height = height + 1.1
  533. -- Second row
  534. add_field(0.3, "te_spreadx", fgettext("X spread"), t[3])
  535. if dimension == 3 then
  536. add_field(3.6, "te_spready", fgettext("Y spread"), t[4])
  537. else
  538. fields[#fields + 1] = "label[4," .. height - 0.2 .. ";" ..
  539. fgettext("2D Noise") .. "]"
  540. end
  541. add_field(6.9, "te_spreadz", fgettext("Z spread"), t[5])
  542. height = height + 1.1
  543. -- Third row
  544. add_field(0.3, "te_octaves", fgettext("Octaves"), t[7])
  545. add_field(3.6, "te_persist", fgettext("Persistance"), t[8])
  546. add_field(6.9, "te_lacun", fgettext("Lacunarity"), t[9])
  547. height = height + 1.1
  548. local enabled_flags = flags_to_table(t[10])
  549. local flags = {}
  550. for _, name in ipairs(enabled_flags) do
  551. -- Index by name, to avoid iterating over all enabled_flags for every possible flag.
  552. flags[name] = true
  553. end
  554. for _, name in ipairs(setting.flags) do
  555. local checkbox_name = "cb_" .. name
  556. local is_enabled = flags[name] == true -- to get false if nil
  557. checkboxes[checkbox_name] = is_enabled
  558. end
  559. -- Flags
  560. formspec = table.concat(fields)
  561. .. "checkbox[0.5," .. height - 0.6 .. ";cb_defaults;"
  562. .. fgettext("defaults") .. ";" -- defaults
  563. .. tostring(flags["defaults"] == true) .. "]" -- to get false if nil
  564. .. "checkbox[5," .. height - 0.6 .. ";cb_eased;"
  565. .. fgettext("eased") .. ";" -- eased
  566. .. tostring(flags["eased"] == true) .. "]"
  567. .. "checkbox[5," .. height - 0.15 .. ";cb_absvalue;"
  568. .. fgettext("absvalue") .. ";" -- absvalue
  569. .. tostring(flags["absvalue"] == true) .. "]"
  570. height = height + 1
  571. elseif setting.type == "v3f" then
  572. local val = get_current_value(setting)
  573. local v3f = {}
  574. for line in val:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
  575. table.insert(v3f, line)
  576. end
  577. height = height + 0.3
  578. formspec = formspec
  579. .. "field[0.3," .. height .. ";3.3,1;te_x;"
  580. .. fgettext("X") .. ";" -- X
  581. .. core.formspec_escape(v3f[1] or "") .. "]"
  582. .. "field[3.6," .. height .. ";3.3,1;te_y;"
  583. .. fgettext("Y") .. ";" -- Y
  584. .. core.formspec_escape(v3f[2] or "") .. "]"
  585. .. "field[6.9," .. height .. ";3.3,1;te_z;"
  586. .. fgettext("Z") .. ";" -- Z
  587. .. core.formspec_escape(v3f[3] or "") .. "]"
  588. height = height + 1.1
  589. elseif setting.type == "flags" then
  590. local enabled_flags = flags_to_table(get_current_value(setting))
  591. local flags = {}
  592. for _, name in ipairs(enabled_flags) do
  593. -- Index by name, to avoid iterating over all enabled_flags for every possible flag.
  594. flags[name] = true
  595. end
  596. local flags_count = #setting.possible
  597. local max_height = flags_count / 4
  598. -- More space for flags
  599. description_height = description_height - 1
  600. height = height - 1
  601. local fields = {} -- To build formspec
  602. for i, name in ipairs(setting.possible) do
  603. local x = 0.5
  604. local y = height + i / 2 - 0.75
  605. if i - 1 >= flags_count / 2 then -- 2nd column
  606. x = 5
  607. y = y - max_height
  608. end
  609. local checkbox_name = "cb_" .. name
  610. local is_enabled = flags[name] == true -- to get false if nil
  611. checkboxes[checkbox_name] = is_enabled
  612. fields[#fields + 1] = ("checkbox[%f,%f;%s;%s;%s]"):format(
  613. x, y, checkbox_name, name, tostring(is_enabled)
  614. )
  615. end
  616. formspec = table.concat(fields)
  617. height = height + max_height + 0.25
  618. else
  619. -- TODO: fancy input for float, int
  620. local text = get_current_value(setting)
  621. if dialogdata.error_message and dialogdata.entered_text then
  622. text = dialogdata.entered_text
  623. end
  624. formspec = "field[0.28," .. height + 0.15 .. ";" .. width .. ",1;te_setting_value;;"
  625. .. core.formspec_escape(text) .. "]"
  626. height = height + 1.15
  627. end
  628. -- Box good, textarea bad. Calculate textarea size from box.
  629. local function create_textfield(size, label, text, bg_color)
  630. local textarea = {
  631. x = size.x + 0.3,
  632. y = size.y,
  633. w = size.w + 0.25,
  634. h = size.h * 1.16 + 0.12
  635. }
  636. return ("box[%f,%f;%f,%f;%s]textarea[%f,%f;%f,%f;;%s;%s]"):format(
  637. size.x, size.y, size.w, size.h, bg_color or "#000",
  638. textarea.x, textarea.y, textarea.w, textarea.h,
  639. core.formspec_escape(label), core.formspec_escape(text)
  640. )
  641. end
  642. -- When there's an error: Shrink description textarea and add error below
  643. if dialogdata.error_message then
  644. local error_box = {
  645. x = 0,
  646. y = description_height - 0.4,
  647. w = width - 0.25,
  648. h = 0.5
  649. }
  650. formspec = formspec ..
  651. create_textfield(error_box, "", dialogdata.error_message, "#600")
  652. description_height = description_height - 0.75
  653. end
  654. -- Get description field
  655. local description_box = {
  656. x = 0,
  657. y = 0.2,
  658. w = width - 0.25,
  659. h = description_height
  660. }
  661. local setting_name = setting.name
  662. if setting.readable_name then
  663. setting_name = fgettext_ne(setting.readable_name) ..
  664. " (" .. setting.name .. ")"
  665. end
  666. local comment_text
  667. if setting.comment == "" then
  668. comment_text = fgettext_ne("(No description of setting given)")
  669. else
  670. comment_text = fgettext_ne(setting.comment)
  671. end
  672. return (
  673. "size[" .. width .. "," .. height + 0.25 .. ",true]" ..
  674. create_textfield(description_box, setting_name, comment_text) ..
  675. formspec ..
  676. "button[" .. width / 2 - 2.5 .. "," .. height - 0.4 .. ";2.5,1;btn_done;" ..
  677. fgettext("Save") .. "]" ..
  678. "button[" .. width / 2 .. "," .. height - 0.4 .. ";2.5,1;btn_cancel;" ..
  679. fgettext("Cancel") .. "]"
  680. )
  681. end
  682. local function handle_change_setting_buttons(this, fields)
  683. local setting = settings[selected_setting]
  684. if fields["btn_done"] or fields["key_enter"] then
  685. if setting.type == "bool" then
  686. local new_value = fields["dd_setting_value"]
  687. -- Note: new_value is the actual (translated) value shown in the dropdown
  688. core.settings:set_bool(setting.name, new_value == fgettext("Enabled"))
  689. elseif setting.type == "enum" then
  690. local new_value = fields["dd_setting_value"]
  691. core.settings:set(setting.name, new_value)
  692. elseif setting.type == "int" then
  693. local new_value = tonumber(fields["te_setting_value"])
  694. if not new_value or math.floor(new_value) ~= new_value then
  695. this.data.error_message = fgettext_ne("Please enter a valid integer.")
  696. this.data.entered_text = fields["te_setting_value"]
  697. core.update_formspec(this:get_formspec())
  698. return true
  699. end
  700. if setting.min and new_value < setting.min then
  701. this.data.error_message = fgettext_ne("The value must be at least $1.", setting.min)
  702. this.data.entered_text = fields["te_setting_value"]
  703. core.update_formspec(this:get_formspec())
  704. return true
  705. end
  706. if setting.max and new_value > setting.max then
  707. this.data.error_message = fgettext_ne("The value must not be larger than $1.", setting.max)
  708. this.data.entered_text = fields["te_setting_value"]
  709. core.update_formspec(this:get_formspec())
  710. return true
  711. end
  712. core.settings:set(setting.name, new_value)
  713. elseif setting.type == "float" then
  714. local new_value = tonumber(fields["te_setting_value"])
  715. if not new_value then
  716. this.data.error_message = fgettext_ne("Please enter a valid number.")
  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 == "flags" then
  735. local values = {}
  736. for _, name in ipairs(setting.possible) do
  737. if checkboxes["cb_" .. name] then
  738. table.insert(values, name)
  739. end
  740. end
  741. checkboxes = {}
  742. local new_value = table.concat(values, ", ")
  743. core.settings:set(setting.name, new_value)
  744. elseif setting.type == "noise_params_2d" or setting.type == "noise_params_3d" then
  745. local np_flags = {}
  746. for _, name in ipairs(setting.flags) do
  747. if checkboxes["cb_" .. name] then
  748. table.insert(np_flags, name)
  749. end
  750. end
  751. checkboxes = {}
  752. if setting.type == "noise_params_2d" then
  753. fields["te_spready"] = fields["te_spreadz"]
  754. end
  755. local new_value = {
  756. offset = fields["te_offset"],
  757. scale = fields["te_scale"],
  758. spread = {
  759. x = fields["te_spreadx"],
  760. y = fields["te_spready"],
  761. z = fields["te_spreadz"]
  762. },
  763. seed = fields["te_seed"],
  764. octaves = fields["te_octaves"],
  765. persistence = fields["te_persist"],
  766. lacunarity = fields["te_lacun"],
  767. flags = table.concat(np_flags, ", ")
  768. }
  769. core.settings:set_np_group(setting.name, new_value)
  770. elseif setting.type == "v3f" then
  771. local new_value = "("
  772. .. fields["te_x"] .. ", "
  773. .. fields["te_y"] .. ", "
  774. .. fields["te_z"] .. ")"
  775. core.settings:set(setting.name, new_value)
  776. else
  777. local new_value = fields["te_setting_value"]
  778. core.settings:set(setting.name, new_value)
  779. end
  780. core.settings:write()
  781. this:delete()
  782. return true
  783. end
  784. if fields["btn_cancel"] then
  785. this:delete()
  786. return true
  787. end
  788. if fields["btn_browser_path"] then
  789. core.show_path_select_dialog("dlg_browse_path",
  790. fgettext_ne("Select directory"), false)
  791. end
  792. if fields["btn_browser_filepath"] then
  793. core.show_path_select_dialog("dlg_browse_path",
  794. fgettext_ne("Select file"), true)
  795. end
  796. if fields["dlg_browse_path_accepted"] then
  797. this.data.selected_path = fields["dlg_browse_path_accepted"]
  798. core.update_formspec(this:get_formspec())
  799. end
  800. if setting.type == "flags"
  801. or setting.type == "noise_params_2d"
  802. or setting.type == "noise_params_3d" then
  803. for name, value in pairs(fields) do
  804. if name:sub(1, 3) == "cb_" then
  805. checkboxes[name] = value == "true"
  806. end
  807. end
  808. end
  809. return false
  810. end
  811. local function create_settings_formspec(tabview, _, tabdata)
  812. local formspec = "size[12,5.4;true]" ..
  813. "tablecolumns[color;tree;text,width=28;text]" ..
  814. "tableoptions[background=#00000000;border=false]" ..
  815. "field[0.3,0.1;10.2,1;search_string;;" .. core.formspec_escape(search_string) .. "]" ..
  816. "field_close_on_enter[search_string;false]" ..
  817. "button[10.2,-0.2;2,1;search;" .. fgettext("Search") .. "]" ..
  818. "table[0,0.8;12,3.5;list_settings;"
  819. local current_level = 0
  820. for _, entry in ipairs(settings) do
  821. local name
  822. if not core.settings:get_bool("main_menu_technical_settings") and entry.readable_name then
  823. name = fgettext_ne(entry.readable_name)
  824. else
  825. name = entry.name
  826. end
  827. if entry.type == "category" then
  828. current_level = entry.level
  829. formspec = formspec .. "#FFFF00," .. current_level .. "," .. fgettext(name) .. ",,"
  830. elseif entry.type == "bool" then
  831. local value = get_current_value(entry)
  832. if core.is_yes(value) then
  833. value = fgettext("Enabled")
  834. else
  835. value = fgettext("Disabled")
  836. end
  837. formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
  838. .. value .. ","
  839. elseif entry.type == "key" then --luacheck: ignore
  840. -- ignore key settings, since we have a special dialog for them
  841. elseif entry.type == "noise_params_2d" or entry.type == "noise_params_3d" then
  842. formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
  843. .. core.formspec_escape(get_current_np_group_as_string(entry)) .. ","
  844. else
  845. formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
  846. .. core.formspec_escape(get_current_value(entry)) .. ","
  847. end
  848. end
  849. if #settings > 0 then
  850. formspec = formspec:sub(1, -2) -- remove trailing comma
  851. end
  852. formspec = formspec .. ";" .. selected_setting .. "]" ..
  853. "button[0,4.9;4,1;btn_back;".. fgettext("< Back to Settings page") .. "]" ..
  854. "button[10,4.9;2,1;btn_edit;" .. fgettext("Edit") .. "]" ..
  855. "button[7,4.9;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" ..
  856. "checkbox[0,4.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";"
  857. .. dump(core.settings:get_bool("main_menu_technical_settings")) .. "]"
  858. return formspec
  859. end
  860. local function handle_settings_buttons(this, fields, tabname, tabdata)
  861. local list_enter = false
  862. if fields["list_settings"] then
  863. selected_setting = core.get_table_index("list_settings")
  864. if core.explode_table_event(fields["list_settings"]).type == "DCL" then
  865. -- Directly toggle booleans
  866. local setting = settings[selected_setting]
  867. if setting and setting.type == "bool" then
  868. local current_value = get_current_value(setting)
  869. core.settings:set_bool(setting.name, not core.is_yes(current_value))
  870. core.settings:write()
  871. return true
  872. else
  873. list_enter = true
  874. end
  875. else
  876. return true
  877. end
  878. end
  879. if fields.search or fields.key_enter_field == "search_string" then
  880. if search_string == fields.search_string then
  881. if selected_setting > 0 then
  882. -- Go to next result on enter press
  883. local i = selected_setting + 1
  884. local looped = false
  885. while i > #settings or settings[i].type == "category" do
  886. i = i + 1
  887. if i > #settings then
  888. -- Stop infinte looping
  889. if looped then
  890. return false
  891. end
  892. i = 1
  893. looped = true
  894. end
  895. end
  896. selected_setting = i
  897. core.update_formspec(this:get_formspec())
  898. return true
  899. end
  900. else
  901. -- Search for setting
  902. search_string = fields.search_string
  903. settings, selected_setting = filter_settings(full_settings, search_string)
  904. core.update_formspec(this:get_formspec())
  905. end
  906. return true
  907. end
  908. if fields["btn_edit"] or list_enter then
  909. local setting = settings[selected_setting]
  910. if setting and setting.type ~= "category" then
  911. local edit_dialog = dialog_create("change_setting",
  912. create_change_setting_formspec, handle_change_setting_buttons)
  913. edit_dialog:set_parent(this)
  914. this:hide()
  915. edit_dialog:show()
  916. end
  917. return true
  918. end
  919. if fields["btn_restore"] then
  920. local setting = settings[selected_setting]
  921. if setting and setting.type ~= "category" then
  922. core.settings:remove(setting.name)
  923. core.settings:write()
  924. core.update_formspec(this:get_formspec())
  925. end
  926. return true
  927. end
  928. if fields["btn_back"] then
  929. this:delete()
  930. return true
  931. end
  932. if fields["cb_tech_settings"] then
  933. core.settings:set("main_menu_technical_settings", fields["cb_tech_settings"])
  934. core.settings:write()
  935. core.update_formspec(this:get_formspec())
  936. return true
  937. end
  938. return false
  939. end
  940. function create_adv_settings_dlg()
  941. local dlg = dialog_create("settings_advanced",
  942. create_settings_formspec,
  943. handle_settings_buttons,
  944. nil)
  945. return dlg
  946. end
  947. -- Uncomment to generate 'minetest.conf.example' and 'settings_translation_file.cpp'.
  948. -- For RUN_IN_PLACE the generated files may appear in the 'bin' folder.
  949. -- See comment and alternative line at the end of 'generate_from_settingtypes.lua'.
  950. --assert(loadfile(core.get_builtin_path().."mainmenu"..DIR_DELIM..
  951. -- "generate_from_settingtypes.lua"))(parse_config_file(true, false))