dlg_settings_advanced.lua 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106
  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. --[[~ "defaults" is a noise parameter flag.
  563. It describes the default processing options
  564. for noise settings in main menu -> "All Settings". ]]
  565. .. fgettext("defaults") .. ";" -- defaults
  566. .. tostring(flags["defaults"] == true) .. "]" -- to get false if nil
  567. .. "checkbox[5," .. height - 0.6 .. ";cb_eased;"
  568. --[[~ "eased" is a noise parameter flag.
  569. It is used to make the map smoother and
  570. can be enabled in noise settings in
  571. main menu -> "All Settings". ]]
  572. .. fgettext("eased") .. ";" -- eased
  573. .. tostring(flags["eased"] == true) .. "]"
  574. .. "checkbox[5," .. height - 0.15 .. ";cb_absvalue;"
  575. --[[~ "absvalue" is a noise parameter flag.
  576. It is short for "absolute value".
  577. It can be enabled in noise settings in
  578. main menu -> "All Settings". ]]
  579. .. fgettext("absvalue") .. ";" -- absvalue
  580. .. tostring(flags["absvalue"] == true) .. "]"
  581. height = height + 1
  582. elseif setting.type == "v3f" then
  583. local val = get_current_value(setting)
  584. local v3f = {}
  585. for line in val:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
  586. table.insert(v3f, line)
  587. end
  588. height = height + 0.3
  589. formspec = formspec
  590. .. "field[0.3," .. height .. ";3.3,1;te_x;"
  591. .. fgettext("X") .. ";" -- X
  592. .. core.formspec_escape(v3f[1] or "") .. "]"
  593. .. "field[3.6," .. height .. ";3.3,1;te_y;"
  594. .. fgettext("Y") .. ";" -- Y
  595. .. core.formspec_escape(v3f[2] or "") .. "]"
  596. .. "field[6.9," .. height .. ";3.3,1;te_z;"
  597. .. fgettext("Z") .. ";" -- Z
  598. .. core.formspec_escape(v3f[3] or "") .. "]"
  599. height = height + 1.1
  600. elseif setting.type == "flags" then
  601. local current_flags = flags_to_table(get_current_value(setting))
  602. local flags = {}
  603. for _, name in ipairs(current_flags) do
  604. -- Index by name, to avoid iterating over all enabled_flags for every possible flag.
  605. if name:sub(1, 2) == "no" then
  606. flags[name:sub(3)] = false
  607. else
  608. flags[name] = true
  609. end
  610. end
  611. local flags_count = #setting.possible / 2
  612. local max_height = math.ceil(flags_count / 2) / 2
  613. -- More space for flags
  614. description_height = description_height - 1
  615. height = height - 1
  616. local fields = {} -- To build formspec
  617. local j = 1
  618. for _, name in ipairs(setting.possible) do
  619. if name:sub(1, 2) ~= "no" then
  620. local x = 0.5
  621. local y = height + j / 2 - 0.75
  622. if j - 1 >= flags_count / 2 then -- 2nd column
  623. x = 5
  624. y = y - max_height
  625. end
  626. j = j + 1;
  627. local checkbox_name = "cb_" .. name
  628. local is_enabled = flags[name] == true -- to get false if nil
  629. checkboxes[checkbox_name] = is_enabled
  630. fields[#fields + 1] = ("checkbox[%f,%f;%s;%s;%s]"):format(
  631. x, y, checkbox_name, name, tostring(is_enabled)
  632. )
  633. end
  634. end
  635. formspec = table.concat(fields)
  636. height = height + max_height + 0.25
  637. else
  638. -- TODO: fancy input for float, int
  639. local text = get_current_value(setting)
  640. if dialogdata.error_message and dialogdata.entered_text then
  641. text = dialogdata.entered_text
  642. end
  643. formspec = "field[0.28," .. height + 0.15 .. ";" .. width .. ",1;te_setting_value;;"
  644. .. core.formspec_escape(text) .. "]"
  645. height = height + 1.15
  646. end
  647. -- Box good, textarea bad. Calculate textarea size from box.
  648. local function create_textfield(size, label, text, bg_color)
  649. local textarea = {
  650. x = size.x + 0.3,
  651. y = size.y,
  652. w = size.w + 0.25,
  653. h = size.h * 1.16 + 0.12
  654. }
  655. return ("box[%f,%f;%f,%f;%s]textarea[%f,%f;%f,%f;;%s;%s]"):format(
  656. size.x, size.y, size.w, size.h, bg_color or "#000",
  657. textarea.x, textarea.y, textarea.w, textarea.h,
  658. core.formspec_escape(label), core.formspec_escape(text)
  659. )
  660. end
  661. -- When there's an error: Shrink description textarea and add error below
  662. if dialogdata.error_message then
  663. local error_box = {
  664. x = 0,
  665. y = description_height - 0.4,
  666. w = width - 0.25,
  667. h = 0.5
  668. }
  669. formspec = formspec ..
  670. create_textfield(error_box, "", dialogdata.error_message, "#600")
  671. description_height = description_height - 0.75
  672. end
  673. -- Get description field
  674. local description_box = {
  675. x = 0,
  676. y = 0.2,
  677. w = width - 0.25,
  678. h = description_height
  679. }
  680. local setting_name = setting.name
  681. if setting.readable_name then
  682. setting_name = fgettext_ne(setting.readable_name) ..
  683. " (" .. setting.name .. ")"
  684. end
  685. local comment_text
  686. if setting.comment == "" then
  687. comment_text = fgettext_ne("(No description of setting given)")
  688. else
  689. comment_text = fgettext_ne(setting.comment)
  690. end
  691. return (
  692. "size[" .. width .. "," .. height + 0.25 .. ",true]" ..
  693. create_textfield(description_box, setting_name, comment_text) ..
  694. formspec ..
  695. "button[" .. width / 2 - 2.5 .. "," .. height - 0.4 .. ";2.5,1;btn_done;" ..
  696. fgettext("Save") .. "]" ..
  697. "button[" .. width / 2 .. "," .. height - 0.4 .. ";2.5,1;btn_cancel;" ..
  698. fgettext("Cancel") .. "]"
  699. )
  700. end
  701. local function handle_change_setting_buttons(this, fields)
  702. local setting = settings[selected_setting]
  703. if fields["btn_done"] or fields["key_enter"] then
  704. if setting.type == "bool" then
  705. local new_value = fields["dd_setting_value"]
  706. -- Note: new_value is the actual (translated) value shown in the dropdown
  707. core.settings:set_bool(setting.name, new_value == fgettext("Enabled"))
  708. elseif setting.type == "enum" then
  709. local new_value = fields["dd_setting_value"]
  710. core.settings:set(setting.name, new_value)
  711. elseif setting.type == "int" then
  712. local new_value = tonumber(fields["te_setting_value"])
  713. if not new_value or math.floor(new_value) ~= new_value then
  714. this.data.error_message = fgettext_ne("Please enter a valid integer.")
  715. this.data.entered_text = fields["te_setting_value"]
  716. core.update_formspec(this:get_formspec())
  717. return true
  718. end
  719. if setting.min and new_value < setting.min then
  720. this.data.error_message = fgettext_ne("The value must be at least $1.", setting.min)
  721. this.data.entered_text = fields["te_setting_value"]
  722. core.update_formspec(this:get_formspec())
  723. return true
  724. end
  725. if setting.max and new_value > setting.max then
  726. this.data.error_message = fgettext_ne("The value must not be larger than $1.", setting.max)
  727. this.data.entered_text = fields["te_setting_value"]
  728. core.update_formspec(this:get_formspec())
  729. return true
  730. end
  731. core.settings:set(setting.name, new_value)
  732. elseif setting.type == "float" then
  733. local new_value = tonumber(fields["te_setting_value"])
  734. if not new_value then
  735. this.data.error_message = fgettext_ne("Please enter a valid number.")
  736. this.data.entered_text = fields["te_setting_value"]
  737. core.update_formspec(this:get_formspec())
  738. return true
  739. end
  740. if setting.min and new_value < setting.min then
  741. this.data.error_message = fgettext_ne("The value must be at least $1.", setting.min)
  742. this.data.entered_text = fields["te_setting_value"]
  743. core.update_formspec(this:get_formspec())
  744. return true
  745. end
  746. if setting.max and new_value > setting.max then
  747. this.data.error_message = fgettext_ne("The value must not be larger than $1.", setting.max)
  748. this.data.entered_text = fields["te_setting_value"]
  749. core.update_formspec(this:get_formspec())
  750. return true
  751. end
  752. core.settings:set(setting.name, new_value)
  753. elseif setting.type == "flags" then
  754. local values = {}
  755. for _, name in ipairs(setting.possible) do
  756. if name:sub(1, 2) ~= "no" then
  757. if checkboxes["cb_" .. name] then
  758. table.insert(values, name)
  759. else
  760. table.insert(values, "no" .. name)
  761. end
  762. end
  763. end
  764. checkboxes = {}
  765. local new_value = table.concat(values, ", ")
  766. core.settings:set(setting.name, new_value)
  767. elseif setting.type == "noise_params_2d" or setting.type == "noise_params_3d" then
  768. local np_flags = {}
  769. for _, name in ipairs(setting.flags) do
  770. if checkboxes["cb_" .. name] then
  771. table.insert(np_flags, name)
  772. end
  773. end
  774. checkboxes = {}
  775. if setting.type == "noise_params_2d" then
  776. fields["te_spready"] = fields["te_spreadz"]
  777. end
  778. local new_value = {
  779. offset = fields["te_offset"],
  780. scale = fields["te_scale"],
  781. spread = {
  782. x = fields["te_spreadx"],
  783. y = fields["te_spready"],
  784. z = fields["te_spreadz"]
  785. },
  786. seed = fields["te_seed"],
  787. octaves = fields["te_octaves"],
  788. persistence = fields["te_persist"],
  789. lacunarity = fields["te_lacun"],
  790. flags = table.concat(np_flags, ", ")
  791. }
  792. core.settings:set_np_group(setting.name, new_value)
  793. elseif setting.type == "v3f" then
  794. local new_value = "("
  795. .. fields["te_x"] .. ", "
  796. .. fields["te_y"] .. ", "
  797. .. fields["te_z"] .. ")"
  798. core.settings:set(setting.name, new_value)
  799. else
  800. local new_value = fields["te_setting_value"]
  801. core.settings:set(setting.name, new_value)
  802. end
  803. core.settings:write()
  804. this:delete()
  805. return true
  806. end
  807. if fields["btn_cancel"] then
  808. this:delete()
  809. return true
  810. end
  811. if fields["btn_browser_path"] then
  812. core.show_path_select_dialog("dlg_browse_path",
  813. fgettext_ne("Select directory"), false)
  814. end
  815. if fields["btn_browser_filepath"] then
  816. core.show_path_select_dialog("dlg_browse_path",
  817. fgettext_ne("Select file"), true)
  818. end
  819. if fields["dlg_browse_path_accepted"] then
  820. this.data.selected_path = fields["dlg_browse_path_accepted"]
  821. core.update_formspec(this:get_formspec())
  822. end
  823. if setting.type == "flags"
  824. or setting.type == "noise_params_2d"
  825. or setting.type == "noise_params_3d" then
  826. for name, value in pairs(fields) do
  827. if name:sub(1, 3) == "cb_" then
  828. checkboxes[name] = value == "true"
  829. end
  830. end
  831. end
  832. return false
  833. end
  834. local function create_settings_formspec(tabview, _, tabdata)
  835. local formspec = "size[12,5.4;true]" ..
  836. "tablecolumns[color;tree;text,width=28;text]" ..
  837. "tableoptions[background=#00000000;border=false]" ..
  838. "field[0.3,0.1;10.2,1;search_string;;" .. core.formspec_escape(search_string) .. "]" ..
  839. "field_close_on_enter[search_string;false]" ..
  840. "button[10.2,-0.2;2,1;search;" .. fgettext("Search") .. "]" ..
  841. "table[0,0.8;12,3.5;list_settings;"
  842. local current_level = 0
  843. for _, entry in ipairs(settings) do
  844. local name
  845. if not core.settings:get_bool("main_menu_technical_settings") and entry.readable_name then
  846. name = fgettext_ne(entry.readable_name)
  847. else
  848. name = entry.name
  849. end
  850. if entry.type == "category" then
  851. current_level = entry.level
  852. formspec = formspec .. "#FFFF00," .. current_level .. "," .. fgettext(name) .. ",,"
  853. elseif entry.type == "bool" then
  854. local value = get_current_value(entry)
  855. if core.is_yes(value) then
  856. value = fgettext("Enabled")
  857. else
  858. value = fgettext("Disabled")
  859. end
  860. formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
  861. .. value .. ","
  862. elseif entry.type == "key" then --luacheck: ignore
  863. -- ignore key settings, since we have a special dialog for them
  864. elseif entry.type == "noise_params_2d" or entry.type == "noise_params_3d" then
  865. formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
  866. .. core.formspec_escape(get_current_np_group_as_string(entry)) .. ","
  867. else
  868. formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
  869. .. core.formspec_escape(get_current_value(entry)) .. ","
  870. end
  871. end
  872. if #settings > 0 then
  873. formspec = formspec:sub(1, -2) -- remove trailing comma
  874. end
  875. formspec = formspec .. ";" .. selected_setting .. "]" ..
  876. "button[0,4.9;4,1;btn_back;".. fgettext("< Back to Settings page") .. "]" ..
  877. "button[10,4.9;2,1;btn_edit;" .. fgettext("Edit") .. "]" ..
  878. "button[7,4.9;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" ..
  879. "checkbox[0,4.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";"
  880. .. dump(core.settings:get_bool("main_menu_technical_settings")) .. "]"
  881. return formspec
  882. end
  883. local function handle_settings_buttons(this, fields, tabname, tabdata)
  884. local list_enter = false
  885. if fields["list_settings"] then
  886. selected_setting = core.get_table_index("list_settings")
  887. if core.explode_table_event(fields["list_settings"]).type == "DCL" then
  888. -- Directly toggle booleans
  889. local setting = settings[selected_setting]
  890. if setting and setting.type == "bool" then
  891. local current_value = get_current_value(setting)
  892. core.settings:set_bool(setting.name, not core.is_yes(current_value))
  893. core.settings:write()
  894. return true
  895. else
  896. list_enter = true
  897. end
  898. else
  899. return true
  900. end
  901. end
  902. if fields.search or fields.key_enter_field == "search_string" then
  903. if search_string == fields.search_string then
  904. if selected_setting > 0 then
  905. -- Go to next result on enter press
  906. local i = selected_setting + 1
  907. local looped = false
  908. while i > #settings or settings[i].type == "category" do
  909. i = i + 1
  910. if i > #settings then
  911. -- Stop infinte looping
  912. if looped then
  913. return false
  914. end
  915. i = 1
  916. looped = true
  917. end
  918. end
  919. selected_setting = i
  920. core.update_formspec(this:get_formspec())
  921. return true
  922. end
  923. else
  924. -- Search for setting
  925. search_string = fields.search_string
  926. settings, selected_setting = filter_settings(full_settings, search_string)
  927. core.update_formspec(this:get_formspec())
  928. end
  929. return true
  930. end
  931. if fields["btn_edit"] or list_enter then
  932. local setting = settings[selected_setting]
  933. if setting and setting.type ~= "category" then
  934. local edit_dialog = dialog_create("change_setting",
  935. create_change_setting_formspec, handle_change_setting_buttons)
  936. edit_dialog:set_parent(this)
  937. this:hide()
  938. edit_dialog:show()
  939. end
  940. return true
  941. end
  942. if fields["btn_restore"] then
  943. local setting = settings[selected_setting]
  944. if setting and setting.type ~= "category" then
  945. core.settings:remove(setting.name)
  946. core.settings:write()
  947. core.update_formspec(this:get_formspec())
  948. end
  949. return true
  950. end
  951. if fields["btn_back"] then
  952. this:delete()
  953. return true
  954. end
  955. if fields["cb_tech_settings"] then
  956. core.settings:set("main_menu_technical_settings", fields["cb_tech_settings"])
  957. core.settings:write()
  958. core.update_formspec(this:get_formspec())
  959. return true
  960. end
  961. return false
  962. end
  963. function create_adv_settings_dlg()
  964. local dlg = dialog_create("settings_advanced",
  965. create_settings_formspec,
  966. handle_settings_buttons,
  967. nil)
  968. return dlg
  969. end
  970. -- Uncomment to generate 'minetest.conf.example' and 'settings_translation_file.cpp'.
  971. -- For RUN_IN_PLACE the generated files may appear in the 'bin' folder.
  972. -- See comment and alternative line at the end of 'generate_from_settingtypes.lua'.
  973. --assert(loadfile(core.get_builtin_path().."mainmenu"..DIR_DELIM..
  974. -- "generate_from_settingtypes.lua"))(parse_config_file(true, false))