#include "Preferences.hpp"
#include "OptionsGroup.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
#include "MsgDialog.hpp"
#include "I18N.hpp"
#include "format.hpp"
#include "libslic3r/AppConfig.hpp"
#include <wx/notebook.h>
#include "Notebook.hpp"
#include "ButtonsDescription.hpp"
#include "OG_CustomCtrl.hpp"
#include "GLCanvas3D.hpp"
#include "ConfigWizard.hpp"

#include <boost/dll/runtime_symbol_info.hpp>

#ifdef WIN32
#include <wx/msw/registry.h>
#endif // WIN32
#ifdef __linux__
#include "DesktopIntegrationDialog.hpp"
#endif //__linux__

namespace Slic3r {

	static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values& enum_keys_map)
	{
		t_config_enum_names names;
		int cnt = 0;
		for (const auto& kvp : enum_keys_map)
			cnt = std::max(cnt, kvp.second);
		cnt += 1;
		names.assign(cnt, "");
		for (const auto& kvp : enum_keys_map)
			names[kvp.second] = kvp.first;
		return names;
	}

#define CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NAME) \
    static t_config_enum_names s_keys_names_##NAME = enum_names_from_keys_map(s_keys_map_##NAME); \
    template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values() { return s_keys_map_##NAME; } \
    template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names() { return s_keys_names_##NAME; }



	static const t_config_enum_values s_keys_map_NotifyReleaseMode = {
		{"all",         NotifyReleaseAll},
		{"release",     NotifyReleaseOnly},
		{"none",        NotifyReleaseNone},
	};

	CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NotifyReleaseMode)

namespace GUI {

PreferencesDialog::PreferencesDialog(wxWindow* parent) :
    DPIDialog(parent, wxID_ANY, _L("Preferences"), wxDefaultPosition, 
              wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
{
#ifdef __WXOSX__
    isOSX = true;
#endif
	build();

	m_highlighter.set_timer_owner(this, 0);
}

static void update_color(wxColourPickerCtrl* color_pckr, const wxColour& color) 
{
	if (color_pckr->GetColour() != color) {
		color_pckr->SetColour(color);
		wxPostEvent(color_pckr, wxCommandEvent(wxEVT_COLOURPICKER_CHANGED));
	}
}

void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::string()*/, const std::string& tab_name/*= std::string()*/)
{
	int selected_tab = 0;
	for ( ; selected_tab < int(tabs->GetPageCount()); selected_tab++)
		if (tabs->GetPageText(selected_tab) == _(tab_name))
			break;
	if (selected_tab < int(tabs->GetPageCount()))
		tabs->SetSelection(selected_tab);

	if (!highlight_opt_key.empty())
		init_highlighter(highlight_opt_key);

	// cache input values for custom toolbar size
	m_custom_toolbar_size		= atoi(get_app_config()->get("custom_toolbar_size").c_str());
	m_use_custom_toolbar_size	= get_app_config()->get_bool("use_custom_toolbar_size");

	// set Field for notify_release to its value
	if (m_optgroup_gui && m_optgroup_gui->get_field("notify_release") != nullptr) {
		boost::any val = s_keys_map_NotifyReleaseMode.at(wxGetApp().app_config->get("notify_release"));
		m_optgroup_gui->get_field("notify_release")->set_value(val, false);
	}
	

	if (wxGetApp().is_editor()) {
		auto app_config = get_app_config();

		downloader->set_path_name(app_config->get("url_downloader_dest"));
		downloader->allow(!app_config->has("downloader_url_registered") || app_config->get_bool("downloader_url_registered"));

		for (const std::string& opt_key : {"suppress_hyperlinks", "downloader_url_registered"})
			m_optgroup_other->set_value(opt_key, app_config->get_bool(opt_key));

		// update colors for color pickers of the labels
		update_color(m_sys_colour, wxGetApp().get_label_clr_sys());
		update_color(m_mod_colour, wxGetApp().get_label_clr_modified());

		// update color pickers for mode palette
		const auto palette = wxGetApp().get_mode_palette(); 
		std::vector<wxColourPickerCtrl*> color_pickres = {m_mode_simple, m_mode_advanced, m_mode_expert};
		for (size_t mode = 0; mode < color_pickres.size(); ++mode)
			update_color(color_pickres[mode], palette[mode]);
	}

	this->ShowModal();
}

static std::shared_ptr<ConfigOptionsGroup>create_options_tab(const wxString& title, wxBookCtrlBase* tabs)
{
	wxPanel* tab = new wxPanel(tabs, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
	tabs->AddPage(tab, _(title));
	tab->SetFont(wxGetApp().normal_font());

	wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
	sizer->SetSizeHints(tab);
	tab->SetSizer(sizer);

	std::shared_ptr<ConfigOptionsGroup> optgroup = std::make_shared<ConfigOptionsGroup>(tab);
	optgroup->label_width = 40;
	optgroup->set_config_category_and_type(title, int(Preset::TYPE_PREFERENCES));
	return optgroup;
}

static void activate_options_tab(std::shared_ptr<ConfigOptionsGroup> optgroup)
{
	optgroup->activate([](){}, wxALIGN_RIGHT);
	optgroup->update_visibility(comSimple);
	wxBoxSizer* sizer = static_cast<wxBoxSizer*>(static_cast<wxPanel*>(optgroup->parent())->GetSizer());
	sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10);

	// apply sercher
	wxGetApp().sidebar().get_searcher().append_preferences_options(optgroup->get_lines());
}

static void append_bool_option( std::shared_ptr<ConfigOptionsGroup> optgroup,
								const std::string& opt_key,
								const std::string& label,
								const std::string& tooltip,
								bool def_val,
								ConfigOptionMode mode = comSimple)
{
	ConfigOptionDef def = {opt_key, coBool};
	def.label = label;
	def.tooltip = tooltip;
	def.mode = mode;
	def.set_default_value(new ConfigOptionBool{ def_val });
	Option option(def, opt_key);
	optgroup->append_single_option_line(option);

	// fill data to the Search Dialog
	wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences"));
}

template<typename EnumType>
static void append_enum_option( std::shared_ptr<ConfigOptionsGroup> optgroup,
								const std::string& opt_key,
								const std::string& label,
								const std::string& tooltip,
								const ConfigOption* def_val,
								std::initializer_list<std::pair<std::string_view, std::string_view>> enum_values,
								ConfigOptionMode mode = comSimple)
{
	ConfigOptionDef def = {opt_key, coEnum };
	def.label = label;
	def.tooltip = tooltip;
	def.mode = mode;
	def.set_enum<EnumType>(enum_values);

	def.set_default_value(def_val);
	Option option(def, opt_key);
	optgroup->append_single_option_line(option);

	// fill data to the Search Dialog
	wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences"));
}

static void append_preferences_option_to_searcher(std::shared_ptr<ConfigOptionsGroup> optgroup,
												const std::string& opt_key,
												const wxString& label)
{
	// fill data to the Search Dialog
	wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences"));
	// apply sercher
	wxGetApp().sidebar().get_searcher().append_preferences_option(Line(opt_key, label, ""));
}

void PreferencesDialog::build()
{
#ifdef _WIN32
	wxGetApp().UpdateDarkUI(this);
#else
	SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif
	const wxFont& font = wxGetApp().normal_font();
	SetFont(font);

	auto app_config = get_app_config();

#ifdef _MSW_DARK_MODE
	tabs = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNB_DEFAULT);
#else
    tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL  |wxNB_NOPAGETHEME | wxNB_DEFAULT );
	tabs->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif

	// Add "General" tab
	m_optgroup_general = create_options_tab(L("General"), tabs);
	m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
		if (auto it = m_values.find(opt_key); it != m_values.end()) {
			m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
			return;
		}
		if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset" || opt_key == "default_action_on_new_project")
			m_values[opt_key] = boost::any_cast<bool>(value) ? "none" : "discard";
		else if (opt_key == "default_action_on_dirty_project")
			m_values[opt_key] = boost::any_cast<bool>(value) ? "" : "0";
		else
		    m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
	};

	bool is_editor = wxGetApp().is_editor();

	if (is_editor) {
		append_bool_option(m_optgroup_general, "remember_output_path", 
			L("Remember output directory"),
			L("If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files."),
			app_config->has("remember_output_path") ? app_config->get_bool("remember_output_path") : true);

		append_bool_option(m_optgroup_general, "autocenter", 
			L("Auto-center parts"),
			L("If this is enabled, Slic3r will auto-center objects around the print bed center."),
			app_config->get_bool("autocenter"));

		append_bool_option(m_optgroup_general, "background_processing", 
			L("Background processing"),
			L("If this is enabled, Slic3r will pre-process objects as soon "
				"as they\'re loaded in order to save time when exporting G-code."),
			app_config->get_bool("background_processing"));

		append_bool_option(m_optgroup_general, "alert_when_supports_needed", 
			L("Alert when supports needed"),
			L("If this is enabled, Slic3r will raise alerts when it detects "
				"issues in the sliced object, that can be resolved with supports (and brim). "
				"Examples of such issues are floating object parts, unsupported extrusions and low bed adhesion."),
			app_config->get_bool("alert_when_supports_needed"));


		m_optgroup_general->append_separator();

		// Please keep in sync with ConfigWizard
		append_bool_option(m_optgroup_general, "export_sources_full_pathnames",
			L("Export sources full pathnames to 3mf and amf"),
			L("If enabled, allows the Reload from disk command to automatically find and load the files when invoked."),
			app_config->get_bool("export_sources_full_pathnames"));

#ifdef _WIN32
		// Please keep in sync with ConfigWizard
		append_bool_option(m_optgroup_general, "associate_3mf",
			L("Associate .3mf files to QIDISlicer"),
			L("If enabled, sets QIDISlicer as default application to open .3mf files."),
			app_config->get_bool("associate_3mf"));

		append_bool_option(m_optgroup_general, "associate_stl",
			L("Associate .stl files to QIDISlicer"),
			L("If enabled, sets QIDISlicer as default application to open .stl files."),
			app_config->get_bool("associate_stl"));
#endif // _WIN32

		m_optgroup_general->append_separator();

		// Please keep in sync with ConfigWizard
		append_bool_option(m_optgroup_general, "preset_update",
			L("Update built-in Presets automatically"),
			L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded "
			  "into a separate temporary location. When a new preset version becomes available it is offered at application startup."),
			app_config->get_bool("preset_update"));

		append_bool_option(m_optgroup_general, "no_defaults",
			L("Suppress \" - default - \" presets"),
			L("Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available."),
			app_config->get_bool("no_defaults"));

		append_bool_option(m_optgroup_general, "no_templates",
			L("Suppress \" Template \" filament presets"),
			L("Suppress \" Template \" filament presets in configuration wizard and sidebar visibility."),
			app_config->get_bool("no_templates"));

		append_bool_option(m_optgroup_general, "show_incompatible_presets",
			L("Show incompatible print and filament presets"),
			L("When checked, the print and filament presets are shown in the preset editor "
			"even if they are marked as incompatible with the active printer"),
			app_config->get_bool("show_incompatible_presets"));

		m_optgroup_general->append_separator();

		append_bool_option(m_optgroup_general, "show_drop_project_dialog",
			L("Show load project dialog"),
			L("When checked, whenever dragging and dropping a project file on the application or open it from a browser, "
			  "shows a dialog asking to select the action to take on the file to load."),
			app_config->get_bool("show_drop_project_dialog"));

		append_bool_option(m_optgroup_general, "single_instance",
#if __APPLE__
			L("Allow just a single QIDISlicer instance"),
			L("On OSX there is always only one instance of app running by default. However it is allowed to run multiple instances "
			  "of same app from the command line. In such case this settings will allow only one instance."),
#else
			L("Allow just a single QIDISlicer instance"),
			L("If this is enabled, when starting QIDISlicer and another instance of the same QIDISlicer is already running, that instance will be reactivated instead."),
#endif
		app_config->has("single_instance") ? app_config->get_bool("single_instance") : false );

		m_optgroup_general->append_separator();

		append_bool_option(m_optgroup_general, "default_action_on_dirty_project",
			L("Ask for unsaved changes in project"),
			L("Always ask for unsaved changes in project, when: \n"
						"- Closing QIDISlicer,\n"
						"- Loading or creating a new project"),
			app_config->get("default_action_on_dirty_project").empty());

		m_optgroup_general->append_separator();

		append_bool_option(m_optgroup_general, "default_action_on_close_application",
			L("Ask to save unsaved changes in presets when closing the application or when loading a new project"),
			L("Always ask for unsaved changes in presets, when: \n"
						"- Closing QIDISlicer while some presets are modified,\n"
						"- Loading a new project while some presets are modified"),
			app_config->get("default_action_on_close_application") == "none");

		append_bool_option(m_optgroup_general, "default_action_on_select_preset",
			L("Ask for unsaved changes in presets when selecting new preset"),
			L("Always ask for unsaved changes in presets when selecting new preset or resetting a preset"),
			app_config->get("default_action_on_select_preset") == "none");

		append_bool_option(m_optgroup_general, "default_action_on_new_project",
			L("Ask for unsaved changes in presets when creating new project"),
			L("Always ask for unsaved changes in presets when creating new project"),
			app_config->get("default_action_on_new_project") == "none");
	}
#ifdef _WIN32
	else {
		append_bool_option(m_optgroup_general, "associate_gcode",
			L("Associate .gcode files to QIDISlicer G-code Viewer"),
			L("If enabled, sets QIDISlicer G-code Viewer as default application to open .gcode files."),
			app_config->get_bool("associate_gcode"));
	}
#endif // _WIN32

#if __APPLE__
	append_bool_option(m_optgroup_general, "use_retina_opengl",
		L("Use Retina resolution for the 3D scene"),
		L("If enabled, the 3D scene will be rendered in Retina resolution. "
	      "If you are experiencing 3D performance problems, disabling this option may help."),
		app_config->get_bool("use_retina_opengl"));
#endif

	m_optgroup_general->append_separator();

    // Show/Hide splash screen
	append_bool_option(m_optgroup_general, "show_splash_screen",
		L("Show splash screen"),
		L("Show splash screen"),
		app_config->get_bool("show_splash_screen"));

	append_bool_option(m_optgroup_general, "restore_win_position",
		L("Restore window position on start"),
		L("If enabled, QIDISlicer will be open at the position it was closed"),
		app_config->get_bool("restore_win_position"));

    // Clear Undo / Redo stack on new project
	append_bool_option(m_optgroup_general, "clear_undo_redo_stack_on_new_project",
		L("Clear Undo / Redo stack on new project"),
		L("Clear Undo / Redo stack on new project or when an existing project is loaded."),
		app_config->get_bool("clear_undo_redo_stack_on_new_project"));

#if defined(_WIN32) || defined(__APPLE__)
	append_bool_option(m_optgroup_general, "use_legacy_3DConnexion",
		L("Enable support for legacy 3DConnexion devices"),
		L("If enabled, the legacy 3DConnexion devices settings dialog is available by pressing CTRL+M"),
		app_config->get_bool("use_legacy_3DConnexion"));
#endif // _WIN32 || __APPLE__

	activate_options_tab(m_optgroup_general);

	// Add "Camera" tab
	m_optgroup_camera = create_options_tab(L("Camera"), tabs);
	m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
		if (auto it = m_values.find(opt_key);it != m_values.end()) {
			m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
			return;
		}
		m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
	};

	append_bool_option(m_optgroup_camera, "use_perspective_camera",
		L("Use perspective camera"),
		L("If enabled, use perspective camera. If not enabled, use orthographic camera."),
		app_config->get_bool("use_perspective_camera"));

	append_bool_option(m_optgroup_camera, "use_free_camera",
		L("Use free camera"),
		L("If enabled, use free camera. If not enabled, use constrained camera."),
		app_config->get_bool("use_free_camera"));

	append_bool_option(m_optgroup_camera, "reverse_mouse_wheel_zoom",
		L("Reverse direction of zoom with mouse wheel"),
		L("If enabled, reverses the direction of zoom with mouse wheel"),
		app_config->get_bool("reverse_mouse_wheel_zoom"));

	activate_options_tab(m_optgroup_camera);

	// Add "GUI" tab
	m_optgroup_gui = create_options_tab(L("GUI"), tabs);
	m_optgroup_gui->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
		if (opt_key == "notify_release") {
			int val_int = boost::any_cast<int>(value);
			for (const auto& item : s_keys_map_NotifyReleaseMode) {
				if (item.second == val_int) {
					m_values[opt_key] = item.first;
					return;
				}
			}
		}
		if (opt_key == "use_custom_toolbar_size") {
			m_icon_size_sizer->ShowItems(boost::any_cast<bool>(value));
			refresh_og(m_optgroup_gui);
			get_app_config()->set("use_custom_toolbar_size", boost::any_cast<bool>(value) ? "1" : "0");
			wxGetApp().plater()->get_current_canvas3D()->render();
			return;
		}
		if (opt_key == "tabs_as_menu") {
			bool disable_new_layout = boost::any_cast<bool>(value);
			m_rb_new_settings_layout_mode->Show(!disable_new_layout);
			if (disable_new_layout && m_rb_new_settings_layout_mode->GetValue()) {
				m_rb_new_settings_layout_mode->SetValue(false);
				m_rb_old_settings_layout_mode->SetValue(true);
			}
			refresh_og(m_optgroup_gui);
		}

		if (auto it = m_values.find(opt_key); it != m_values.end()) {
			m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
			return;
		}

/*		if (opt_key == "suppress_hyperlinks")
			m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "";
		else*/
			m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
	};

	append_bool_option(m_optgroup_gui, "seq_top_layer_only",
		L("Sequential slider applied only to top layer"),
		L("If enabled, changes made using the sequential slider, in preview, apply only to gcode top layer. "
		  "If disabled, changes made using the sequential slider, in preview, apply to the whole gcode."),
		app_config->get_bool("seq_top_layer_only"));

	if (is_editor) {
		append_bool_option(m_optgroup_gui, "show_collapse_button",
			L("Show sidebar collapse/expand button"),
			L("If enabled, the button for the collapse sidebar will be appeared in top right corner of the 3D Scene"),
			app_config->get_bool("show_collapse_button"));
/*
		append_bool_option(m_optgroup_gui, "suppress_hyperlinks",
			L("Suppress to open hyperlink in browser"),
			L("If enabled, QIDISlicer will not open a hyperlinks in your browser."),
			//L("If enabled, the descriptions of configuration parameters in settings tabs wouldn't work as hyperlinks. "
			//  "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."),
			app_config->get_bool("suppress_hyperlinks"));
*/
		append_bool_option(m_optgroup_gui, "color_mapinulation_panel",
			L("Use colors for axes values in Manipulation panel"),
			L("If enabled, the axes names and axes values will be colorized according to the axes colors. "
			  "If disabled, old UI will be used."),
			app_config->get_bool("color_mapinulation_panel"));

		append_bool_option(m_optgroup_gui, "order_volumes",
			L("Order object volumes by types"),
			L("If enabled, volumes will be always ordered inside the object. Correct order is Model Part, Negative Volume, Modifier, Support Blocker and Support Enforcer. "
			  "If disabled, you can reorder Model Parts, Negative Volumes and Modifiers. But one of the model parts have to be on the first place."),
			app_config->get_bool("order_volumes"));

		append_bool_option(m_optgroup_gui, "non_manifold_edges",
			L("Show non-manifold edges"),
			L("If enabled, shows non-manifold edges."),
			app_config->get_bool("non_manifold_edges"));

		append_bool_option(m_optgroup_gui, "allow_auto_color_change",
			L("Allow automatically color change"),
			L("If enabled, related notification will be shown, when sliced object looks like a logo or a sign."),
			app_config->get_bool("allow_auto_color_change"));

#ifdef _MSW_DARK_MODE
		append_bool_option(m_optgroup_gui, "tabs_as_menu",
			L("Set settings tabs as menu items (experimental)"),
			L("If enabled, Settings Tabs will be placed as menu items. If disabled, old UI will be used."),
			app_config->get_bool("tabs_as_menu"));
#endif

		m_optgroup_gui->append_separator();

		append_bool_option(m_optgroup_gui, "show_hints",
			L("Show \"Tip of the day\" notification after start"),
			L("If enabled, useful hints are displayed at startup."),
			app_config->get_bool("show_hints"));

		append_enum_option<NotifyReleaseMode>(m_optgroup_gui, "notify_release",
			L("Notify about new releases"),
			L("You will be notified about new release after startup acordingly: All = Regular release and alpha / beta releases. Release only = regular release."),
			new ConfigOptionEnum<NotifyReleaseMode>(static_cast<NotifyReleaseMode>(s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")))),
			{ { "all", L("All") },
			  { "release", L("Release only") },
			  { "none", L("None") }
			});

		m_optgroup_gui->append_separator();

		append_bool_option(m_optgroup_gui, "use_custom_toolbar_size",
			L("Use custom size for toolbar icons"),
			L("If enabled, you can change size of toolbar icons manually."),
			app_config->get_bool("use_custom_toolbar_size"));
	}

	activate_options_tab(m_optgroup_gui);

	if (is_editor) {
		// set Field for notify_release to its value to activate the object
		boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release"));
		m_optgroup_gui->get_field("notify_release")->set_value(val, false);

		create_icon_size_slider();
		m_icon_size_sizer->ShowItems(app_config->get_bool("use_custom_toolbar_size"));

		create_settings_mode_widget();
		create_settings_text_color_widget();
		create_settings_mode_color_widget();

		m_optgroup_other = create_options_tab(_L("Other"), tabs);
		m_optgroup_other->m_on_change = [this](t_config_option_key opt_key, boost::any value) {

			if (auto it = m_values.find(opt_key); it != m_values.end() && opt_key != "url_downloader_dest") {
				m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
				return;
			}

			if (opt_key == "suppress_hyperlinks")
				m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "";
			else
				m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
		};


		append_bool_option(m_optgroup_other, "suppress_hyperlinks",
			L("Suppress to open hyperlink in browser"),
			L("If enabled, QIDISlicer will not open a hyperlinks in your browser."),
			//L("If enabled, the descriptions of configuration parameters in settings tabs wouldn't work as hyperlinks. "
			//  "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."),
			app_config->get_bool("suppress_hyperlinks"));
		
		append_bool_option(m_optgroup_other, "downloader_url_registered",
			L("Allow downloads from Printables.com"),
			L("If enabled, QIDISlicer will be allowed to download from Printables.com"),
			app_config->get_bool("downloader_url_registered"));

		activate_options_tab(m_optgroup_other);

		create_downloader_path_sizer();
//		create_settings_font_widget();

#if ENABLE_ENVIRONMENT_MAP
		// Add "Render" tab
		m_optgroup_render = create_options_tab(L("Render"), tabs);
		m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
			if (auto it = m_values.find(opt_key); it != m_values.end()) {
				m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
				return;
			}
			m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
		};

		append_bool_option(m_optgroup_render, "use_environment_map",
			L("Use environment map"),
			L("If enabled, renders object using the environment map."),
			app_config->get_bool("use_environment_map"));

		activate_options_tab(m_optgroup_render);
#endif // ENABLE_ENVIRONMENT_MAP

#ifdef _WIN32
		// Add "Dark Mode" tab
		m_optgroup_dark_mode = create_options_tab(_L("Dark mode (experimental)"), tabs);
		m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
			if (auto it = m_values.find(opt_key); it != m_values.end()) {
				m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
				return;
			}
			m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
		};

		append_bool_option(m_optgroup_dark_mode, "dark_color_mode",
			L("Enable dark mode"),
			L("If enabled, UI will use Dark mode colors. If disabled, old UI will be used."),
			app_config->get_bool("dark_color_mode"));

		if (wxPlatformInfo::Get().GetOSMajorVersion() >= 10) // Use system menu just for Window newer then Windows 10
															 // Use menu with ownerdrawn items by default on systems older then Windows 10
		{
		append_bool_option(m_optgroup_dark_mode, "sys_menu_enabled",
			L("Use system menu for application"),
			L("If enabled, application will use the standard Windows system menu,\n"
			"but on some combination of display scales it can look ugly. If disabled, old UI will be used."),
			app_config->get_bool("sys_menu_enabled"));
		}

		activate_options_tab(m_optgroup_dark_mode);
#endif //_WIN32
	}

	// update alignment of the controls for all tabs
	update_ctrls_alignment();

	auto sizer = new wxBoxSizer(wxVERTICAL);
	sizer->Add(tabs, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5);

	auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
	this->Bind(wxEVT_BUTTON, &PreferencesDialog::accept, this, wxID_OK);
	this->Bind(wxEVT_BUTTON, &PreferencesDialog::revert, this, wxID_CANCEL);

	for (int id : {wxID_OK, wxID_CANCEL})
		wxGetApp().UpdateDarkUI(static_cast<wxButton*>(FindWindowById(id, this)));

	sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM | wxTOP, 10);

	SetSizer(sizer);
	sizer->SetSizeHints(this);
	this->CenterOnParent();
}

std::vector<ConfigOptionsGroup*> PreferencesDialog::optgroups()
{
	std::vector<ConfigOptionsGroup*> out;
	out.reserve(4);
	for (ConfigOptionsGroup* opt : { m_optgroup_general.get(), m_optgroup_camera.get(), m_optgroup_gui.get(), m_optgroup_other.get()
#ifdef _WIN32
		, m_optgroup_dark_mode.get()
#endif // _WIN32
#if ENABLE_ENVIRONMENT_MAP
		, m_optgroup_render.get()
#endif // ENABLE_ENVIRONMENT_MAP
	})
		if (opt)
			out.emplace_back(opt);
	return out;
}

void PreferencesDialog::update_ctrls_alignment()
{
	int max_ctrl_width{ 0 };
	for (ConfigOptionsGroup* og : this->optgroups())
		if (int max = og->custom_ctrl->get_max_win_width();
			max_ctrl_width < max)
			max_ctrl_width = max;
	if (max_ctrl_width)
		for (ConfigOptionsGroup* og : this->optgroups())
			og->custom_ctrl->set_max_win_width(max_ctrl_width);
}

void PreferencesDialog::accept(wxEvent&)
{
	if(wxGetApp().is_editor()) {
		if (const auto it = m_values.find("downloader_url_registered"); it != m_values.end())
			downloader->allow(it->second == "1");
		if (!downloader->on_finish())
			return;
#ifdef __linux__
		if( downloader->get_perform_registration_linux()) 
			DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif // __linux__
	}

	std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled", "font_size" };

	for (const std::string& option : options_to_recreate_GUI) {
		if (m_values.find(option) != m_values.end()) {
			wxString title = wxGetApp().is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME);
			title += " - " + _L("Changes for the critical options");
			MessageDialog dialog(nullptr,
				_L("Changing some options will trigger application restart.\n"
				   "You will lose the content of the plater.") + "\n\n" +
				_L("Do you want to proceed?"),
				title,
				wxICON_QUESTION | wxYES | wxNO);
			if (dialog.ShowModal() == wxID_YES) {
				m_recreate_GUI = true;
			}
			else {
				for (const std::string& option : options_to_recreate_GUI)
					m_values.erase(option);
			}
			break;
		}
	}

	auto app_config = get_app_config();

	m_seq_top_layer_only_changed = false;
	if (auto it = m_values.find("seq_top_layer_only"); it != m_values.end())
		m_seq_top_layer_only_changed = app_config->get("seq_top_layer_only") != it->second;

	m_settings_layout_changed = false;
	for (const std::string& key : { "old_settings_layout_mode",
								    "new_settings_layout_mode",
								    "dlg_settings_layout_mode" })
	{
	    auto it = m_values.find(key);
	    if (it != m_values.end() && app_config->get(key) != it->second) {
			m_settings_layout_changed = true;
			break;
	    }
	}

#if 0 //#ifdef _WIN32 // #ysDarkMSW - Allow it when we deside to support the sustem colors for application
	if (m_values.find("always_dark_color_mode") != m_values.end())
		wxGetApp().force_sys_colors_update();
#endif

	for (std::map<std::string, std::string>::iterator it = m_values.begin(); it != m_values.end(); ++it)
		app_config->set(it->first, it->second);

	if (wxGetApp().is_editor()) {
		wxGetApp().set_label_clr_sys(m_sys_colour->GetColour());
		wxGetApp().set_label_clr_modified(m_mod_colour->GetColour());
		wxGetApp().set_mode_palette(m_mode_palette);
	}

	EndModal(wxID_OK);

#ifdef _WIN32
	if (m_values.find("dark_color_mode") != m_values.end())
		wxGetApp().force_colors_update();
#ifdef _MSW_DARK_MODE
	if (m_values.find("sys_menu_enabled") != m_values.end())
		wxGetApp().force_menu_update();
#endif //_MSW_DARK_MODE
#endif // _WIN32

	if (m_values.find("no_templates") != m_values.end())
		wxGetApp().plater()->force_filament_cb_update();

	wxGetApp().update_ui_from_settings();
	clear_cache();
}

void PreferencesDialog::revert(wxEvent&)
{
	auto app_config = get_app_config();

	if (m_custom_toolbar_size != atoi(app_config->get("custom_toolbar_size").c_str())) {
		app_config->set("custom_toolbar_size", (boost::format("%d") % m_custom_toolbar_size).str());
		m_icon_size_slider->SetValue(m_custom_toolbar_size);
	}
	if (m_use_custom_toolbar_size != (get_app_config()->get_bool("use_custom_toolbar_size"))) {
		app_config->set("use_custom_toolbar_size", m_use_custom_toolbar_size ? "1" : "0");

		m_optgroup_gui->set_value("use_custom_toolbar_size", m_use_custom_toolbar_size);
		m_icon_size_sizer->ShowItems(m_use_custom_toolbar_size);
		refresh_og(m_optgroup_gui);
	}

	for (auto value : m_values) {
		const std::string& key = value.first;

		if (key == "default_action_on_dirty_project") {
			m_optgroup_general->set_value(key, app_config->get(key).empty());
			continue;
		}
		if (key == "default_action_on_close_application" || key == "default_action_on_select_preset" || key == "default_action_on_new_project") {
			m_optgroup_general->set_value(key, app_config->get(key) == "none");
			continue;
		}
		if (key == "notify_release") {
			m_optgroup_gui->set_value(key, s_keys_map_NotifyReleaseMode.at(app_config->get(key)));
			continue;
		}
		if (key == "old_settings_layout_mode") {
			m_rb_old_settings_layout_mode->SetValue(app_config->get_bool(key));
			m_settings_layout_changed = false;
			continue;
		}
		if (key == "new_settings_layout_mode") {
			m_rb_new_settings_layout_mode->SetValue(app_config->get_bool(key));
			m_settings_layout_changed = false;
			continue;
		}
		if (key == "dlg_settings_layout_mode") {
			m_rb_dlg_settings_layout_mode->SetValue(app_config->get_bool(key));
			m_settings_layout_changed = false;
			continue;
		}

		for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui, m_optgroup_other
#ifdef _WIN32
			, m_optgroup_dark_mode
#endif // _WIN32
#if ENABLE_ENVIRONMENT_MAP
			, m_optgroup_render
#endif // ENABLE_ENVIRONMENT_MAP
			}) {
			if (opt_group->set_value(key, app_config->get_bool(key)))
				break;
		}
		if (key == "tabs_as_menu") {
			m_rb_new_settings_layout_mode->Show(!app_config->get_bool(key));
			refresh_og(m_optgroup_gui);
			continue;
		}
	}

	clear_cache();
	EndModal(wxID_CANCEL);
}

void PreferencesDialog::msw_rescale()
{
	for (ConfigOptionsGroup* og : this->optgroups())
		og->msw_rescale();

	update_ctrls_alignment();

    msw_buttons_rescale(this, em_unit(), { wxID_OK, wxID_CANCEL });

    layout();
}

void PreferencesDialog::on_sys_color_changed()
{
#ifdef _WIN32
	wxGetApp().UpdateDlgDarkUI(this);
#endif
}

void PreferencesDialog::layout()
{
    const int em = em_unit();

    SetMinSize(wxSize(47 * em, 28 * em));
    Fit();

    Refresh();
}

void PreferencesDialog::clear_cache()
{
	m_values.clear();
	m_custom_toolbar_size = -1;
}

void PreferencesDialog::refresh_og(std::shared_ptr<ConfigOptionsGroup> og)
{
	og->parent()->Layout();
	tabs->Layout();
	this->layout();
}

void PreferencesDialog::create_icon_size_slider()
{
    const auto app_config = get_app_config();

    const int em = em_unit();

    m_icon_size_sizer = new wxBoxSizer(wxHORIZONTAL);

	wxWindow* parent = m_optgroup_gui->parent();
	wxGetApp().UpdateDarkUI(parent);

    if (isOSX)
        // For correct rendering of the slider and value label under OSX
        // we should use system default background
        parent->SetBackgroundStyle(wxBG_STYLE_ERASE);

    auto label = new wxStaticText(parent, wxID_ANY, _L("Icon size in a respect to the default size") + " (%) :");

    m_icon_size_sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL| wxRIGHT | (isOSX ? 0 : wxLEFT), em);

    const int def_val = atoi(app_config->get("custom_toolbar_size").c_str());

    long style = wxSL_HORIZONTAL;
    if (!isOSX)
        style |= wxSL_LABELS | wxSL_AUTOTICKS;

    m_icon_size_slider = new wxSlider(parent, wxID_ANY, def_val, 30, 100, 
                               wxDefaultPosition, wxDefaultSize, style);

    m_icon_size_slider->SetTickFreq(10);
    m_icon_size_slider->SetPageSize(10);
    m_icon_size_slider->SetToolTip(_L("Select toolbar icon size in respect to the default one."));

    m_icon_size_sizer->Add(m_icon_size_slider, 1, wxEXPAND);

    wxStaticText* val_label{ nullptr };
    if (isOSX) {
        val_label = new wxStaticText(parent, wxID_ANY, wxString::Format("%d", def_val));
        m_icon_size_sizer->Add(val_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, em);
    }

    m_icon_size_slider->Bind(wxEVT_SLIDER, ([this, val_label, app_config](wxCommandEvent e) {
        auto val = m_icon_size_slider->GetValue();

		app_config->set("custom_toolbar_size", (boost::format("%d") % val).str());
		wxGetApp().plater()->get_current_canvas3D()->render();

        if (val_label)
            val_label->SetLabelText(wxString::Format("%d", val));
    }), m_icon_size_slider->GetId());

    for (wxWindow* win : std::vector<wxWindow*>{ m_icon_size_slider, label, val_label }) {
        if (!win) continue;         
        win->SetFont(wxGetApp().normal_font());

        if (isOSX) continue; // under OSX we use wxBG_STYLE_ERASE
        win->SetBackgroundStyle(wxBG_STYLE_PAINT);
    }

	m_optgroup_gui->sizer->Add(m_icon_size_sizer, 0, wxEXPAND | wxALL, em);
}

void PreferencesDialog::create_settings_mode_widget()
{
	wxWindow* parent = m_optgroup_gui->parent();

	wxString title = L("Layout Options");
    wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _(title));
	wxGetApp().UpdateDarkUI(stb);
	if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT);
	stb->SetFont(wxGetApp().normal_font());

	wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL);

	auto app_config = get_app_config();
	std::vector<wxString> choices = {	_L("Old regular layout with the tab bar"),
										_L("New layout, access via settings button in the top menu"),
										_L("Settings in non-modal window") };
	int id = -1;
	auto add_radio = [this, parent, stb_sizer, choices](wxRadioButton** rb, int id, bool select) {
		*rb = new wxRadioButton(parent, wxID_ANY, choices[id], wxDefaultPosition, wxDefaultSize, id == 0 ? wxRB_GROUP : 0);
		stb_sizer->Add(*rb);
		(*rb)->SetValue(select);
		(*rb)->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) {
			m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0";
			m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0";
			m_values["dlg_settings_layout_mode"] = (id == 2) ? "1" : "0";
		});
	};

	add_radio(&m_rb_old_settings_layout_mode, ++id, app_config->get_bool("old_settings_layout_mode"));
	add_radio(&m_rb_new_settings_layout_mode, ++id, app_config->get_bool("new_settings_layout_mode"));
	add_radio(&m_rb_dlg_settings_layout_mode, ++id, app_config->get_bool("dlg_settings_layout_mode"));

#ifdef _MSW_DARK_MODE
	if (app_config->get_bool("tabs_as_menu")) {
		m_rb_new_settings_layout_mode->Hide();
		if (m_rb_new_settings_layout_mode->GetValue()) {
			m_rb_new_settings_layout_mode->SetValue(false);
			m_rb_old_settings_layout_mode->SetValue(true);
		}
	}
#endif

	std::string opt_key = "settings_layout_mode";
	m_blinkers[opt_key] = new BlinkingBitmap(parent);

	auto sizer = new wxBoxSizer(wxHORIZONTAL);
	sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2);
	sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL);
	m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit());

	append_preferences_option_to_searcher(m_optgroup_gui, opt_key, title);
}

void PreferencesDialog::create_settings_text_color_widget()
{
	wxWindow* parent = m_optgroup_gui->parent();

	wxString title = L("Text colors");
	wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _(title));
	wxGetApp().UpdateDarkUI(stb);
	if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT);

	std::string opt_key = "text_colors";
	m_blinkers[opt_key] = new BlinkingBitmap(parent);

	wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL);
	GUI_Descriptions::FillSizerWithTextColorDescriptions(stb_sizer, parent, &m_sys_colour, &m_mod_colour);

	auto sizer = new wxBoxSizer(wxHORIZONTAL);
	sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2);
	sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL);

	m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit());

	append_preferences_option_to_searcher(m_optgroup_gui, opt_key, title);
}

void PreferencesDialog::create_settings_mode_color_widget()
{
	wxWindow* parent = m_optgroup_gui->parent();

	wxString title = L("Mode markers");
	wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _(title));
	wxGetApp().UpdateDarkUI(stb);
	if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT);

	std::string opt_key = "mode_markers";
	m_blinkers[opt_key] = new BlinkingBitmap(parent);

	wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL);

    // Mode color markers description
	m_mode_palette = wxGetApp().get_mode_palette();
	GUI_Descriptions::FillSizerWithModeColorDescriptions(stb_sizer, parent, { &m_mode_simple, &m_mode_advanced, &m_mode_expert }, m_mode_palette);

	auto sizer = new wxBoxSizer(wxHORIZONTAL);
	sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2);
	sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL);

	m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit());

	append_preferences_option_to_searcher(m_optgroup_gui, opt_key, title);
}

void PreferencesDialog::create_settings_font_widget()
{
	wxWindow* parent = m_optgroup_other->parent();
	wxGetApp().UpdateDarkUI(parent);

	const wxString title = L("Application font size");
	wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _(title));
	if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT);

	const std::string opt_key = "font_size";
	m_blinkers[opt_key] = new BlinkingBitmap(parent);

	wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxHORIZONTAL);

	wxStaticText* font_example = new wxStaticText(parent, wxID_ANY, "Application text");
    int val = wxGetApp().normal_font().GetPointSize();
	wxSpinCtrl* size_sc = new wxSpinCtrl(parent, wxID_ANY, format_wxstr("%1%", val), wxDefaultPosition, wxSize(15*em_unit(), -1), wxTE_PROCESS_ENTER | wxSP_ARROW_KEYS
#ifdef _WIN32
		| wxBORDER_SIMPLE
#endif 
	, 8, 20);
	wxGetApp().UpdateDarkUI(size_sc);

	auto apply_font = [this, font_example, opt_key](const int val, const wxFont& font) {
		font_example->SetFont(font);
		m_values[opt_key] = format("%1%", val);
		refresh_og(m_optgroup_other);
	};

	auto change_value = [size_sc, apply_font](wxCommandEvent& evt) {
		const int val = size_sc->GetValue();
		wxFont font = wxGetApp().normal_font();
		font.SetPointSize(val);

		apply_font(val, font);
	};
    size_sc->Bind(wxEVT_SPINCTRL, change_value);
	size_sc->Bind(wxEVT_TEXT_ENTER, change_value);

	auto revert_btn = new ScalableButton(parent, wxID_ANY, "undo");
	revert_btn->SetToolTip(_L("Revert font to default"));
	revert_btn->Bind(wxEVT_BUTTON, [size_sc, apply_font](wxEvent& event) {
		wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
		const int val = font.GetPointSize();
	    size_sc->SetValue(val);
		apply_font(val, font);
	});
	parent->Bind(wxEVT_UPDATE_UI, [size_sc](wxUpdateUIEvent& evt) {
		const int def_size = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).GetPointSize();
		evt.Enable(def_size != size_sc->GetValue());
	}, revert_btn->GetId());

    stb_sizer->Add(new wxStaticText(parent, wxID_ANY, _L("Font size") + ":"), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, em_unit());
    stb_sizer->Add(size_sc, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, em_unit());
    stb_sizer->Add(revert_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, em_unit());
	wxBoxSizer* font_sizer = new wxBoxSizer(wxVERTICAL);
	font_sizer->Add(font_example, 1, wxALIGN_CENTER_HORIZONTAL);
    stb_sizer->Add(font_sizer, 1, wxALIGN_CENTER_VERTICAL);

	auto sizer = new wxBoxSizer(wxHORIZONTAL);
	sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2);
	sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL);

	m_optgroup_other->sizer->Add(sizer, 1, wxEXPAND | wxTOP, em_unit());

	append_preferences_option_to_searcher(m_optgroup_other, opt_key, title);
}

void PreferencesDialog::create_downloader_path_sizer()
{
	wxWindow* parent = m_optgroup_other->parent();

	wxString title = L("Download path");
	std::string opt_key = "url_downloader_dest";
	m_blinkers[opt_key] = new BlinkingBitmap(parent);

	downloader = new DownloaderUtils::Worker(parent);

	auto sizer = new wxBoxSizer(wxHORIZONTAL);
	sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2);
	sizer->Add(downloader, 1, wxALIGN_CENTER_VERTICAL);

	m_optgroup_other->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit());

	append_preferences_option_to_searcher(m_optgroup_other, opt_key, title);
}

void PreferencesDialog::init_highlighter(const t_config_option_key& opt_key)
{
	if (m_blinkers.find(opt_key) != m_blinkers.end())
		if (BlinkingBitmap* blinker = m_blinkers.at(opt_key); blinker) {
			m_highlighter.init(blinker);
			return;
		}

	for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui, m_optgroup_other
#ifdef _WIN32
		, m_optgroup_dark_mode
#endif // _WIN32
#if ENABLE_ENVIRONMENT_MAP
		, m_optgroup_render
#endif // ENABLE_ENVIRONMENT_MAP
		}) {
		std::pair<OG_CustomCtrl*, bool*> ctrl = opt_group->get_custom_ctrl_with_blinking_ptr(opt_key, -1);
		if (ctrl.first && ctrl.second) {
			m_highlighter.init(ctrl);
			break;
		}
	}
}

} // GUI
} // Slic3r
