2 --Copyright (C) 2015 PilzAdam
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.
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.
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.
18 local FILENAME = "settingtypes.txt"
20 local CHAR_CLASSES = {
22 VARIABLE = "[%w_%-%.]",
23 INTEGER = "[+-]?[%d]",
24 FLOAT = "[+-]?[%d%.]",
28 local function flags_to_table(flags)
29 return flags:gsub("%s+", ""):split(",", true) -- Remove all spaces and split
32 -- returns error message, or nil
33 local function parse_setting_line(settings, line, read_all, base_level, allow_secure)
35 local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$")
37 if settings.current_comment == "" then
38 settings.current_comment = comment
40 settings.current_comment = settings.current_comment .. "\n" .. comment
45 -- clear current_comment so only comments directly above a setting are bound to it
46 -- but keep a local reference to it for variables in the current line
47 local current_comment = settings.current_comment
48 settings.current_comment = ""
51 if line:match("^" .. CHAR_CLASSES.SPACE .. "*$") then
56 local stars, category = line:match("^%[([%*]*)([^%]]+)%]$")
58 table.insert(settings, {
60 level = stars:len() + base_level,
67 local first_part, name, readable_name, setting_type = line:match("^"
68 -- this first capture group matches the whole first part,
69 -- so we can later strip it from the rest of the line
71 .. "([" .. CHAR_CLASSES.VARIABLE .. "+)" -- variable name
72 .. CHAR_CLASSES.SPACE .. "*"
73 .. "%(([^%)]*)%)" -- readable name
74 .. CHAR_CLASSES.SPACE .. "*"
75 .. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type
76 .. CHAR_CLASSES.SPACE .. "*"
79 if not first_part then
83 if name:match("secure%.[.]*") and not allow_secure then
84 return "Tried to add \"secure.\" setting"
87 if readable_name == "" then
90 local remaining_line = line:sub(first_part:len() + 1)
92 if setting_type == "int" then
93 local default, min, max = remaining_line:match("^"
94 -- first int is required, the last 2 are optional
95 .. "(" .. CHAR_CLASSES.INTEGER .. "+)" .. CHAR_CLASSES.SPACE .. "*"
96 .. "(" .. CHAR_CLASSES.INTEGER .. "*)" .. CHAR_CLASSES.SPACE .. "*"
97 .. "(" .. CHAR_CLASSES.INTEGER .. "*)"
100 if not default or not tonumber(default) then
101 return "Invalid integer setting"
106 table.insert(settings, {
108 readable_name = readable_name,
113 comment = current_comment,
118 if setting_type == "string"
119 or setting_type == "key" or setting_type == "v3f" then
120 local default = remaining_line:match("^(.*)$")
123 return "Invalid string setting"
125 if setting_type == "key" and not read_all then
126 -- ignore key type if read_all is false
130 table.insert(settings, {
132 readable_name = readable_name,
135 comment = current_comment,
140 if setting_type == "noise_params_2d"
141 or setting_type == "noise_params_3d" then
142 local default = remaining_line:match("^(.*)$")
145 return "Invalid string setting"
151 for match in default:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
152 index = default:find("[+-]?[%d.-e]+", index) + match:len()
153 table.insert(values, match)
159 index = default:find("[^, ]", index)
162 flags = default:sub(index)
163 default = default:sub(1, index - 3) -- Make sure no flags in single-line format
165 table.insert(values, flags)
167 table.insert(settings, {
169 readable_name = readable_name,
182 persistence = values[8],
183 lacunarity = values[9],
187 comment = current_comment,
189 flags = flags_to_table("defaults,eased,absvalue")
194 if setting_type == "bool" then
195 if remaining_line ~= "false" and remaining_line ~= "true" then
196 return "Invalid boolean setting"
199 table.insert(settings, {
201 readable_name = readable_name,
203 default = remaining_line,
204 comment = current_comment,
209 if setting_type == "float" then
210 local default, min, max = remaining_line:match("^"
211 -- first float is required, the last 2 are optional
212 .. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "*"
213 .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "*"
214 .. "(" .. CHAR_CLASSES.FLOAT .. "*)"
217 if not default or not tonumber(default) then
218 return "Invalid float setting"
223 table.insert(settings, {
225 readable_name = readable_name,
230 comment = current_comment,
235 if setting_type == "enum" then
236 local default, values = remaining_line:match("^"
237 -- first value (default) may be empty (i.e. is optional)
238 .. "(" .. CHAR_CLASSES.VARIABLE .. "*)" .. CHAR_CLASSES.SPACE .. "*"
239 .. "(" .. CHAR_CLASSES.FLAGS .. "+)"
242 if not default or values == "" then
243 return "Invalid enum setting"
246 table.insert(settings, {
248 readable_name = readable_name,
251 values = values:split(",", true),
252 comment = current_comment,
257 if setting_type == "path" or setting_type == "filepath" then
258 local default = remaining_line:match("^(.*)$")
261 return "Invalid path setting"
264 table.insert(settings, {
266 readable_name = readable_name,
269 comment = current_comment,
274 if setting_type == "flags" then
275 local default, possible = remaining_line:match("^"
276 -- first value (default) may be empty (i.e. is optional)
277 -- this is implemented by making the last value optional, and
278 -- swapping them around if it turns out empty.
279 .. "(" .. CHAR_CLASSES.FLAGS .. "+)" .. CHAR_CLASSES.SPACE .. "*"
280 .. "(" .. CHAR_CLASSES.FLAGS .. "*)"
283 if not default or not possible then
284 return "Invalid flags setting"
287 if possible == "" then
292 table.insert(settings, {
294 readable_name = readable_name,
297 possible = flags_to_table(possible),
298 comment = current_comment,
303 return "Invalid setting type \"" .. setting_type .. "\""
306 local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure)
307 -- store this helper variable in the table so it's easier to pass to parse_setting_line()
308 result.current_comment = ""
310 local line = file:read("*line")
312 local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure)
314 core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"")
316 line = file:read("*line")
319 result.current_comment = nil
322 -- read_all: whether to ignore certain setting types for GUI or not
323 -- parse_mods: whether to parse settingtypes.txt in mods and games
324 local function parse_config_file(read_all, parse_mods)
328 local builtin_path = core.get_builtin_path() .. FILENAME
329 local file = io.open(builtin_path, "r")
331 core.log("error", "Can't load " .. FILENAME)
335 parse_single_file(file, builtin_path, read_all, settings, 0, true)
342 local games_category_initialized = false
344 local game = pkgmgr.get_game(index)
346 local path = game.path .. DIR_DELIM .. FILENAME
347 local file = io.open(path, "r")
349 if not games_category_initialized then
350 fgettext_ne("Games") -- not used, but needed for xgettext
351 table.insert(settings, {
356 games_category_initialized = true
359 table.insert(settings, {
365 parse_single_file(file, path, read_all, settings, 2, false)
371 game = pkgmgr.get_game(index)
375 local mods_category_initialized = false
377 get_mods(core.get_modpath(), mods)
378 for _, mod in ipairs(mods) do
379 local path = mod.path .. DIR_DELIM .. FILENAME
380 local file = io.open(path, "r")
382 if not mods_category_initialized then
383 fgettext_ne("Mods") -- not used, but needed for xgettext
384 table.insert(settings, {
389 mods_category_initialized = true
392 table.insert(settings, {
398 parse_single_file(file, path, read_all, settings, 2, false)
405 local clientmods_category_initialized = false
406 local clientmods = {}
407 get_mods(core.get_clientmodpath(), clientmods)
408 for _, clientmod in ipairs(clientmods) do
409 local path = clientmod.path .. DIR_DELIM .. FILENAME
410 local file = io.open(path, "r")
412 if not clientmods_category_initialized then
413 fgettext_ne("Clientmods") -- not used, but needed for xgettext
414 table.insert(settings, {
419 clientmods_category_initialized = true
422 table.insert(settings, {
423 name = clientmod.name,
428 parse_single_file(file, path, read_all, settings, 2, false)
438 local function filter_settings(settings, searchstring)
439 if not searchstring or searchstring == "" then
443 -- Setup the keyword list
445 for word in searchstring:lower():gmatch("%S+") do
446 table.insert(keywords, word)
450 local category_stack = {}
451 local current_level = 0
452 local best_setting = nil
453 for _, entry in pairs(settings) do
454 if entry.type == "category" then
455 -- Remove all settingless categories
456 while #category_stack > 0 and entry.level <= current_level do
457 table.remove(category_stack, #category_stack)
458 if #category_stack > 0 then
459 current_level = category_stack[#category_stack].level
465 -- Push category onto stack
466 category_stack[#category_stack + 1] = entry
467 current_level = entry.level
469 -- See if setting matches keywords
470 local setting_score = 0
471 for k = 1, #keywords do
472 local keyword = keywords[k]
474 if string.find(entry.name:lower(), keyword, 1, true) then
475 setting_score = setting_score + 1
478 if entry.readable_name and
479 string.find(fgettext(entry.readable_name):lower(), keyword, 1, true) then
480 setting_score = setting_score + 1
484 string.find(fgettext_ne(entry.comment):lower(), keyword, 1, true) then
485 setting_score = setting_score + 1
489 -- Add setting to results if match
490 if setting_score > 0 then
491 -- Add parent categories
492 for _, category in pairs(category_stack) do
493 result[#result + 1] = category
498 result[#result + 1] = entry
499 entry.score = setting_score
501 if not best_setting or
502 setting_score > result[best_setting].score then
503 best_setting = #result
508 return result, best_setting or -1
511 local full_settings = parse_config_file(false, true)
512 local search_string = ""
513 local settings = full_settings
514 local selected_setting = 1
516 local function get_current_value(setting)
517 local value = core.settings:get(setting.name)
519 value = setting.default
524 local function get_current_np_group(setting)
525 local value = core.settings:get_np_group(setting.name)
530 table.insert(t, value.offset)
531 table.insert(t, value.scale)
532 table.insert(t, value.spread.x)
533 table.insert(t, value.spread.y)
534 table.insert(t, value.spread.z)
535 table.insert(t, value.seed)
536 table.insert(t, value.octaves)
537 table.insert(t, value.persistence)
538 table.insert(t, value.lacunarity)
539 table.insert(t, value.flags)
544 local function get_current_np_group_as_string(setting)
545 local value = core.settings:get_np_group(setting.name)
550 t = value.offset .. ", " ..
551 value.scale .. ", (" ..
552 value.spread.x .. ", " ..
553 value.spread.y .. ", " ..
554 value.spread.z .. "), " ..
555 value.seed .. ", " ..
556 value.octaves .. ", " ..
557 value.persistence .. ", " ..
559 if value.flags ~= "" then
560 t = t .. ", " .. value.flags
566 local checkboxes = {} -- handle checkboxes events
568 local function create_change_setting_formspec(dialogdata)
569 local setting = settings[selected_setting]
570 -- Final formspec will be created at the end of this function
571 -- Default values below, may be changed depending on setting type
574 local description_height = 3
577 -- Setting-specific formspec elements
578 if setting.type == "bool" then
579 local selected_index = 1
580 if core.is_yes(get_current_value(setting)) then
583 formspec = "dropdown[3," .. height .. ";4,1;dd_setting_value;"
584 .. fgettext("Disabled") .. "," .. fgettext("Enabled") .. ";"
585 .. selected_index .. "]"
586 height = height + 1.25
588 elseif setting.type == "enum" then
589 local selected_index = 0
590 formspec = "dropdown[3," .. height .. ";4,1;dd_setting_value;"
591 for index, value in ipairs(setting.values) do
592 -- translating value is not possible, since it's the value
593 -- that we set the setting to
594 formspec = formspec .. core.formspec_escape(value) .. ","
595 if get_current_value(setting) == value then
596 selected_index = index
599 if #setting.values > 0 then
600 formspec = formspec:sub(1, -2) -- remove trailing comma
602 formspec = formspec .. ";" .. selected_index .. "]"
603 height = height + 1.25
605 elseif setting.type == "path" or setting.type == "filepath" then
606 local current_value = dialogdata.selected_path
607 if not current_value then
608 current_value = get_current_value(setting)
610 formspec = "field[0.28," .. height + 0.15 .. ";8,1;te_setting_value;;"
611 .. core.formspec_escape(current_value) .. "]"
612 .. "button[8," .. height - 0.15 .. ";2,1;btn_browser_"
613 .. setting.type .. ";" .. fgettext("Browse") .. "]"
614 height = height + 1.15
616 elseif setting.type == "noise_params_2d" or setting.type == "noise_params_3d" then
617 local t = get_current_np_group(setting)
619 if setting.type == "noise_params_2d" then
623 -- More space for 3x3 fields
624 description_height = description_height - 1.5
625 height = height - 1.5
628 local function add_field(x, name, label, value)
629 fields[#fields + 1] = ("field[%f,%f;3.3,1;%s;%s;%s]"):format(
630 x, height, name, label, core.formspec_escape(value or "")
634 height = height + 0.3
635 add_field(0.3, "te_offset", fgettext("Offset"), t[1])
636 add_field(3.6, "te_scale", fgettext("Scale"), t[2])
637 add_field(6.9, "te_seed", fgettext("Seed"), t[6])
638 height = height + 1.1
641 add_field(0.3, "te_spreadx", fgettext("X spread"), t[3])
642 if dimension == 3 then
643 add_field(3.6, "te_spready", fgettext("Y spread"), t[4])
645 fields[#fields + 1] = "label[4," .. height - 0.2 .. ";" ..
646 fgettext("2D Noise") .. "]"
648 add_field(6.9, "te_spreadz", fgettext("Z spread"), t[5])
649 height = height + 1.1
652 add_field(0.3, "te_octaves", fgettext("Octaves"), t[7])
653 add_field(3.6, "te_persist", fgettext("Persistance"), t[8])
654 add_field(6.9, "te_lacun", fgettext("Lacunarity"), t[9])
655 height = height + 1.1
658 local enabled_flags = flags_to_table(t[10])
660 for _, name in ipairs(enabled_flags) do
661 -- Index by name, to avoid iterating over all enabled_flags for every possible flag.
664 for _, name in ipairs(setting.flags) do
665 local checkbox_name = "cb_" .. name
666 local is_enabled = flags[name] == true -- to get false if nil
667 checkboxes[checkbox_name] = is_enabled
670 formspec = table.concat(fields)
671 .. "checkbox[0.5," .. height - 0.6 .. ";cb_defaults;"
672 --[[~ "defaults" is a noise parameter flag.
673 It describes the default processing options
674 for noise settings in main menu -> "All Settings". ]]
675 .. fgettext("defaults") .. ";" -- defaults
676 .. tostring(flags["defaults"] == true) .. "]" -- to get false if nil
677 .. "checkbox[5," .. height - 0.6 .. ";cb_eased;"
678 --[[~ "eased" is a noise parameter flag.
679 It is used to make the map smoother and
680 can be enabled in noise settings in
681 main menu -> "All Settings". ]]
682 .. fgettext("eased") .. ";" -- eased
683 .. tostring(flags["eased"] == true) .. "]"
684 .. "checkbox[5," .. height - 0.15 .. ";cb_absvalue;"
685 --[[~ "absvalue" is a noise parameter flag.
686 It is short for "absolute value".
687 It can be enabled in noise settings in
688 main menu -> "All Settings". ]]
689 .. fgettext("absvalue") .. ";" -- absvalue
690 .. tostring(flags["absvalue"] == true) .. "]"
693 elseif setting.type == "v3f" then
694 local val = get_current_value(setting)
696 for line in val:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
697 table.insert(v3f, line)
700 height = height + 0.3
702 .. "field[0.3," .. height .. ";3.3,1;te_x;"
703 .. fgettext("X") .. ";" -- X
704 .. core.formspec_escape(v3f[1] or "") .. "]"
705 .. "field[3.6," .. height .. ";3.3,1;te_y;"
706 .. fgettext("Y") .. ";" -- Y
707 .. core.formspec_escape(v3f[2] or "") .. "]"
708 .. "field[6.9," .. height .. ";3.3,1;te_z;"
709 .. fgettext("Z") .. ";" -- Z
710 .. core.formspec_escape(v3f[3] or "") .. "]"
711 height = height + 1.1
713 elseif setting.type == "flags" then
714 local current_flags = flags_to_table(get_current_value(setting))
716 for _, name in ipairs(current_flags) do
717 -- Index by name, to avoid iterating over all enabled_flags for every possible flag.
718 if name:sub(1, 2) == "no" then
719 flags[name:sub(3)] = false
724 local flags_count = #setting.possible / 2
725 local max_height = math.ceil(flags_count / 2) / 2
727 -- More space for flags
728 description_height = description_height - 1
731 local fields = {} -- To build formspec
733 for _, name in ipairs(setting.possible) do
734 if name:sub(1, 2) ~= "no" then
736 local y = height + j / 2 - 0.75
737 if j - 1 >= flags_count / 2 then -- 2nd column
742 local checkbox_name = "cb_" .. name
743 local is_enabled = flags[name] == true -- to get false if nil
744 checkboxes[checkbox_name] = is_enabled
746 fields[#fields + 1] = ("checkbox[%f,%f;%s;%s;%s]"):format(
747 x, y, checkbox_name, name, tostring(is_enabled)
751 formspec = table.concat(fields)
752 height = height + max_height + 0.25
755 -- TODO: fancy input for float, int
756 local text = get_current_value(setting)
757 if dialogdata.error_message and dialogdata.entered_text then
758 text = dialogdata.entered_text
760 formspec = "field[0.28," .. height + 0.15 .. ";" .. width .. ",1;te_setting_value;;"
761 .. core.formspec_escape(text) .. "]"
762 height = height + 1.15
765 -- Box good, textarea bad. Calculate textarea size from box.
766 local function create_textfield(size, label, text, bg_color)
771 h = size.h * 1.16 + 0.12
773 return ("box[%f,%f;%f,%f;%s]textarea[%f,%f;%f,%f;;%s;%s]"):format(
774 size.x, size.y, size.w, size.h, bg_color or "#000",
775 textarea.x, textarea.y, textarea.w, textarea.h,
776 core.formspec_escape(label), core.formspec_escape(text)
781 -- When there's an error: Shrink description textarea and add error below
782 if dialogdata.error_message then
785 y = description_height - 0.4,
789 formspec = formspec ..
790 create_textfield(error_box, "", dialogdata.error_message, "#600")
791 description_height = description_height - 0.75
794 -- Get description field
795 local description_box = {
799 h = description_height
802 local setting_name = setting.name
803 if setting.readable_name then
804 setting_name = fgettext_ne(setting.readable_name) ..
805 " (" .. setting.name .. ")"
809 if setting.comment == "" then
810 comment_text = fgettext_ne("(No description of setting given)")
812 comment_text = fgettext_ne(setting.comment)
816 "size[" .. width .. "," .. height + 0.25 .. ",true]" ..
817 create_textfield(description_box, setting_name, comment_text) ..
819 "button[" .. width / 2 - 2.5 .. "," .. height - 0.4 .. ";2.5,1;btn_done;" ..
820 fgettext("Save") .. "]" ..
821 "button[" .. width / 2 .. "," .. height - 0.4 .. ";2.5,1;btn_cancel;" ..
822 fgettext("Cancel") .. "]"
826 local function handle_change_setting_buttons(this, fields)
827 local setting = settings[selected_setting]
828 if fields["btn_done"] or fields["key_enter"] then
829 if setting.type == "bool" then
830 local new_value = fields["dd_setting_value"]
831 -- Note: new_value is the actual (translated) value shown in the dropdown
832 core.settings:set_bool(setting.name, new_value == fgettext("Enabled"))
834 elseif setting.type == "enum" then
835 local new_value = fields["dd_setting_value"]
836 core.settings:set(setting.name, new_value)
838 elseif setting.type == "int" then
839 local new_value = tonumber(fields["te_setting_value"])
840 if not new_value or math.floor(new_value) ~= new_value then
841 this.data.error_message = fgettext_ne("Please enter a valid integer.")
842 this.data.entered_text = fields["te_setting_value"]
843 core.update_formspec(this:get_formspec())
846 if setting.min and new_value < setting.min then
847 this.data.error_message = fgettext_ne("The value must be at least $1.", setting.min)
848 this.data.entered_text = fields["te_setting_value"]
849 core.update_formspec(this:get_formspec())
852 if setting.max and new_value > setting.max then
853 this.data.error_message = fgettext_ne("The value must not be larger than $1.", setting.max)
854 this.data.entered_text = fields["te_setting_value"]
855 core.update_formspec(this:get_formspec())
858 core.settings:set(setting.name, new_value)
860 elseif setting.type == "float" then
861 local new_value = tonumber(fields["te_setting_value"])
862 if not new_value then
863 this.data.error_message = fgettext_ne("Please enter a valid number.")
864 this.data.entered_text = fields["te_setting_value"]
865 core.update_formspec(this:get_formspec())
868 if setting.min and new_value < setting.min then
869 this.data.error_message = fgettext_ne("The value must be at least $1.", setting.min)
870 this.data.entered_text = fields["te_setting_value"]
871 core.update_formspec(this:get_formspec())
874 if setting.max and new_value > setting.max then
875 this.data.error_message = fgettext_ne("The value must not be larger than $1.", setting.max)
876 this.data.entered_text = fields["te_setting_value"]
877 core.update_formspec(this:get_formspec())
880 core.settings:set(setting.name, new_value)
882 elseif setting.type == "flags" then
884 for _, name in ipairs(setting.possible) do
885 if name:sub(1, 2) ~= "no" then
886 if checkboxes["cb_" .. name] then
887 table.insert(values, name)
889 table.insert(values, "no" .. name)
896 local new_value = table.concat(values, ", ")
897 core.settings:set(setting.name, new_value)
899 elseif setting.type == "noise_params_2d" or setting.type == "noise_params_3d" then
901 for _, name in ipairs(setting.flags) do
902 if checkboxes["cb_" .. name] then
903 table.insert(np_flags, name)
909 if setting.type == "noise_params_2d" then
910 fields["te_spready"] = fields["te_spreadz"]
913 offset = fields["te_offset"],
914 scale = fields["te_scale"],
916 x = fields["te_spreadx"],
917 y = fields["te_spready"],
918 z = fields["te_spreadz"]
920 seed = fields["te_seed"],
921 octaves = fields["te_octaves"],
922 persistence = fields["te_persist"],
923 lacunarity = fields["te_lacun"],
924 flags = table.concat(np_flags, ", ")
926 core.settings:set_np_group(setting.name, new_value)
928 elseif setting.type == "v3f" then
929 local new_value = "("
930 .. fields["te_x"] .. ", "
931 .. fields["te_y"] .. ", "
932 .. fields["te_z"] .. ")"
933 core.settings:set(setting.name, new_value)
936 local new_value = fields["te_setting_value"]
937 core.settings:set(setting.name, new_value)
939 core.settings:write()
944 if fields["btn_cancel"] then
949 if fields["btn_browser_path"] then
950 core.show_path_select_dialog("dlg_browse_path",
951 fgettext_ne("Select directory"), false)
954 if fields["btn_browser_filepath"] then
955 core.show_path_select_dialog("dlg_browse_path",
956 fgettext_ne("Select file"), true)
959 if fields["dlg_browse_path_accepted"] then
960 this.data.selected_path = fields["dlg_browse_path_accepted"]
961 core.update_formspec(this:get_formspec())
964 if setting.type == "flags"
965 or setting.type == "noise_params_2d"
966 or setting.type == "noise_params_3d" then
967 for name, value in pairs(fields) do
968 if name:sub(1, 3) == "cb_" then
969 checkboxes[name] = value == "true"
977 local function create_settings_formspec(tabview, _, tabdata)
978 local formspec = "size[12,5.4;true]" ..
979 "tablecolumns[color;tree;text,width=28;text]" ..
980 "tableoptions[background=#00000000;border=false]" ..
981 "field[0.3,0.1;10.2,1;search_string;;" .. core.formspec_escape(search_string) .. "]" ..
982 "field_close_on_enter[search_string;false]" ..
983 "button[10.2,-0.2;2,1;search;" .. fgettext("Search") .. "]" ..
984 "table[0,0.8;12,3.5;list_settings;"
986 local current_level = 0
987 for _, entry in ipairs(settings) do
989 if not core.settings:get_bool("main_menu_technical_settings") and entry.readable_name then
990 name = fgettext_ne(entry.readable_name)
995 if entry.type == "category" then
996 current_level = entry.level
997 formspec = formspec .. "#FFFF00," .. current_level .. "," .. fgettext(name) .. ",,"
999 elseif entry.type == "bool" then
1000 local value = get_current_value(entry)
1001 if core.is_yes(value) then
1002 value = fgettext("Enabled")
1004 value = fgettext("Disabled")
1006 formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
1009 elseif entry.type == "key" then --luacheck: ignore
1010 -- ignore key settings, since we have a special dialog for them
1012 elseif entry.type == "noise_params_2d" or entry.type == "noise_params_3d" then
1013 formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
1014 .. core.formspec_escape(get_current_np_group_as_string(entry)) .. ","
1017 formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
1018 .. core.formspec_escape(get_current_value(entry)) .. ","
1022 if #settings > 0 then
1023 formspec = formspec:sub(1, -2) -- remove trailing comma
1025 formspec = formspec .. ";" .. selected_setting .. "]" ..
1026 "button[0,4.9;4,1;btn_back;".. fgettext("< Back to Settings page") .. "]" ..
1027 "button[10,4.9;2,1;btn_edit;" .. fgettext("Edit") .. "]" ..
1028 "button[7,4.9;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" ..
1029 "checkbox[0,4.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";"
1030 .. dump(core.settings:get_bool("main_menu_technical_settings")) .. "]"
1035 local function handle_settings_buttons(this, fields, tabname, tabdata)
1036 local list_enter = false
1037 if fields["list_settings"] then
1038 selected_setting = core.get_table_index("list_settings")
1039 if core.explode_table_event(fields["list_settings"]).type == "DCL" then
1040 -- Directly toggle booleans
1041 local setting = settings[selected_setting]
1042 if setting and setting.type == "bool" then
1043 local current_value = get_current_value(setting)
1044 core.settings:set_bool(setting.name, not core.is_yes(current_value))
1045 core.settings:write()
1055 if fields.search or fields.key_enter_field == "search_string" then
1056 if search_string == fields.search_string then
1057 if selected_setting > 0 then
1058 -- Go to next result on enter press
1059 local i = selected_setting + 1
1060 local looped = false
1061 while i > #settings or settings[i].type == "category" do
1063 if i > #settings then
1064 -- Stop infinte looping
1072 selected_setting = i
1073 core.update_formspec(this:get_formspec())
1077 -- Search for setting
1078 search_string = fields.search_string
1079 settings, selected_setting = filter_settings(full_settings, search_string)
1080 core.update_formspec(this:get_formspec())
1085 if fields["btn_edit"] or list_enter then
1086 local setting = settings[selected_setting]
1087 if setting and setting.type ~= "category" then
1088 local edit_dialog = dialog_create("change_setting",
1089 create_change_setting_formspec, handle_change_setting_buttons)
1090 edit_dialog:set_parent(this)
1097 if fields["btn_restore"] then
1098 local setting = settings[selected_setting]
1099 if setting and setting.type ~= "category" then
1100 core.settings:remove(setting.name)
1101 core.settings:write()
1102 core.update_formspec(this:get_formspec())
1107 if fields["btn_back"] then
1112 if fields["cb_tech_settings"] then
1113 core.settings:set("main_menu_technical_settings", fields["cb_tech_settings"])
1114 core.settings:write()
1115 core.update_formspec(this:get_formspec())
1122 function create_adv_settings_dlg()
1123 local dlg = dialog_create("settings_advanced",
1124 create_settings_formspec,
1125 handle_settings_buttons,
1131 -- Uncomment to generate 'minetest.conf.example' and 'settings_translation_file.cpp'.
1132 -- For RUN_IN_PLACE the generated files may appear in the 'bin' folder.
1133 -- See comment and alternative line at the end of 'generate_from_settingtypes.lua'.
1135 --assert(loadfile(core.get_builtin_path().."mainmenu"..DIR_DELIM..
1136 -- "generate_from_settingtypes.lua"))(parse_config_file(true, false))