diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index a89a7c8b0..934d1b978 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -81,6 +81,8 @@
 #define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1)
 // Enable gizmo grabbers to share common models
 #define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1)
+// Disable association to 3mf and stl files if the application is run on Windows 8 or later
+#define ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER (1 && ENABLE_2_5_0_ALPHA1)
 
 
 #endif // _prusaslicer_technologies_h_
diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp
index 86768dfc5..b9dcc1a4f 100644
--- a/src/slic3r/GUI/ConfigWizard.cpp
+++ b/src/slic3r/GUI/ConfigWizard.cpp
@@ -1,3075 +1,3093 @@
-// FIXME: extract absolute units -> em
-
-#include "ConfigWizard_private.hpp"
-
-#include <algorithm>
-#include <numeric>
-#include <utility>
-#include <unordered_map>
-#include <stdexcept>
-#include <boost/format.hpp>
-#include <boost/log/trivial.hpp>
-#include <boost/algorithm/string/predicate.hpp>
-#include <boost/nowide/convert.hpp>
-
-#include <wx/settings.h>
-#include <wx/stattext.h>
-#include <wx/textctrl.h>
-#include <wx/dcclient.h>
-#include <wx/statbmp.h>
-#include <wx/checkbox.h>
-#include <wx/statline.h>
-#include <wx/dataview.h>
-#include <wx/notebook.h>
-#include <wx/listbook.h>
-#include <wx/display.h>
-#include <wx/filefn.h>
-#include <wx/wupdlock.h>
-#include <wx/debug.h>
-
-#ifdef _MSW_DARK_MODE
-#include <wx/msw/dark_mode.h>
-#endif // _MSW_DARK_MODE
-
-#include "libslic3r/Platform.hpp"
-#include "libslic3r/Utils.hpp"
-#include "libslic3r/Config.hpp"
-#include "libslic3r/libslic3r.h"
-#include "libslic3r/Model.hpp"
-#include "libslic3r/Color.hpp"
-#include "GUI.hpp"
-#include "GUI_App.hpp"
-#include "GUI_Utils.hpp"
-#include "GUI_ObjectManipulation.hpp"
-#include "Field.hpp"
-#include "DesktopIntegrationDialog.hpp"
-#include "slic3r/Config/Snapshot.hpp"
-#include "slic3r/Utils/PresetUpdater.hpp"
-#include "format.hpp"
-#include "MsgDialog.hpp"
-#include "UnsavedChangesDialog.hpp"
-
-#if defined(__linux__) && defined(__WXGTK3__)
-#define wxLinux_gtk3 true
-#else
-#define wxLinux_gtk3 false
-#endif //defined(__linux__) && defined(__WXGTK3__)
-
-namespace Slic3r {
-namespace GUI {
-
-
-using Config::Snapshot;
-using Config::SnapshotDB;
-
-
-// Configuration data structures extensions needed for the wizard
-
-bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle)
-{
-    this->preset_bundle = std::make_unique<PresetBundle>();
-    this->is_in_resources = ais_in_resources;
-    this->is_prusa_bundle = ais_prusa_bundle;
-
-    std::string path_string = source_path.string();
-    // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
-    auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(
-        path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
-    UNUSED(config_substitutions);
-    // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
-    assert(config_substitutions.empty());
-    auto first_vendor = preset_bundle->vendors.begin();
-    if (first_vendor == preset_bundle->vendors.end()) {
-        BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
-        return false;
-    }
-    if (presets_loaded == 0) {
-        BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string;
-        return false;
-    } 
-
-    BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded;
-    this->vendor_profile = &first_vendor->second;
-    return true;
-}
-
-Bundle::Bundle(Bundle &&other)
-    : preset_bundle(std::move(other.preset_bundle))
-    , vendor_profile(other.vendor_profile)
-    , is_in_resources(other.is_in_resources)
-    , is_prusa_bundle(other.is_prusa_bundle)
-{
-    other.vendor_profile = nullptr;
-}
-
-BundleMap BundleMap::load()
-{
-    BundleMap res;
-
-    const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred();
-    const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
-
-    auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
-    auto prusa_bundle_rsrc = false;
-    if (! boost::filesystem::exists(prusa_bundle_path)) {
-        prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
-        prusa_bundle_rsrc = true;
-    }
-    {
-        Bundle prusa_bundle;
-        if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true))
-            res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle)); 
-    }
-
-    // Load the other bundles in the datadir/vendor directory
-    // and then additionally from resources/profiles.
-    bool is_in_resources = false;
-    for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) {
-        for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) {
-            if (Slic3r::is_ini_file(dir_entry)) {
-                std::string id = dir_entry.path().stem().string();  // stem() = filename() without the trailing ".ini" part
-
-                // Don't load this bundle if we've already loaded it.
-                if (res.find(id) != res.end()) { continue; }
-
-                Bundle bundle;
-                if (bundle.load(dir_entry.path(), is_in_resources))
-                    res.emplace(std::move(id), std::move(bundle));
-            }
-        }
-
-        is_in_resources = true;
-    }
-
-    return res;
-}
-
-Bundle& BundleMap::prusa_bundle()
-{
-    auto it = find(PresetBundle::PRUSA_BUNDLE);
-    if (it == end()) {
-        throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded");
-    }
-
-    return it->second;
-}
-
-const Bundle& BundleMap::prusa_bundle() const
-{
-    return const_cast<BundleMap*>(this)->prusa_bundle();
-}
-
-
-// Printer model picker GUI control
-
-struct PrinterPickerEvent : public wxEvent
-{
-    std::string vendor_id;
-    std::string model_id;
-    std::string variant_name;
-    bool enable;
-
-    PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable)
-        : wxEvent(winid, eventType)
-        , vendor_id(std::move(vendor_id))
-        , model_id(std::move(model_id))
-        , variant_name(std::move(variant_name))
-        , enable(enable)
-    {}
-
-    virtual wxEvent *Clone() const
-    {
-        return new PrinterPickerEvent(*this);
-    }
-};
-
-wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent);
-
-const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png";
-
-PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter)
-    : wxPanel(parent)
-    , vendor_id(vendor.id)
-    , width(0)
-{
-    wxGetApp().UpdateDarkUI(this);
-    const auto &models = vendor.models;
-
-    auto *sizer = new wxBoxSizer(wxVERTICAL);
-
-    const auto font_title = GetFont().MakeBold().Scaled(1.3f);
-    const auto font_name = GetFont().MakeBold();
-    const auto font_alt_nozzle = GetFont().Scaled(0.9f);
-
-    // wxGrid appends widgets by rows, but we need to construct them in columns.
-    // These vectors are used to hold the elements so that they can be appended in the right order.
-    std::vector<wxStaticText*> titles;
-    std::vector<wxStaticBitmap*> bitmaps;
-    std::vector<wxPanel*> variants_panels;
-
-    int max_row_width = 0;
-    int current_row_width = 0;
-
-    bool is_variants = false;
-
-    for (const auto &model : models) {
-        if (! filter(model)) { continue; }
-
-        wxBitmap bitmap;
-        int bitmap_width = 0;
-        auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool {
-            if (wxFileExists(bitmap_file)) {
-                bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG);
-                bitmap_width = bitmap.GetWidth();
-                return true;
-            }
-            return false;
-        };
-        if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) {
-            if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) {
-                BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead")
-                    % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png")
-                    % vendor.id
-                    % model.id;
-                load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width);
-            }
-        }
-        auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
-        title->SetFont(font_name);
-        const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width);
-        title->Wrap(wrap_width);
-
-        current_row_width += wrap_width;
-        if (titles.size() % max_cols == max_cols - 1) {
-            max_row_width = std::max(max_row_width, current_row_width);
-            current_row_width = 0;
-        }
-
-        titles.push_back(title);
-
-        auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap);
-        bitmaps.push_back(bitmap_widget);
-
-        auto *variants_panel = new wxPanel(this);
-        wxGetApp().UpdateDarkUI(variants_panel);
-        auto *variants_sizer = new wxBoxSizer(wxVERTICAL);
-        variants_panel->SetSizer(variants_sizer);
-        const auto model_id = model.id;
-
-        for (size_t i = 0; i < model.variants.size(); i++) {
-            const auto &variant = model.variants[i];
-
-            const auto label = model.technology == ptFFF
-                ? from_u8((boost::format("%1% %2% %3%") % variant.name % _utf8(L("mm")) % _utf8(L("nozzle"))).str())
-                : from_u8(model.name);
-
-            if (i == 1) {
-                auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _L("Alternate nozzles:"));
-                alt_label->SetFont(font_alt_nozzle);
-                variants_sizer->Add(alt_label, 0, wxBOTTOM, 3);
-                is_variants = true;
-            }
-
-            auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name);
-            i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox);
-
-            const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name);
-            cbox->SetValue(enabled);
-
-            variants_sizer->Add(cbox, 0, wxBOTTOM, 3);
-
-            cbox->Bind(wxEVT_CHECKBOX, [this, cbox](wxCommandEvent &event) {
-                on_checkbox(cbox, event.IsChecked());
-            });
-        }
-
-        variants_panels.push_back(variants_panel);
-    }
-
-    width = std::max(max_row_width, current_row_width);
-
-    const size_t cols = std::min(max_cols, titles.size());
-
-    auto *printer_grid = new wxFlexGridSizer(cols, 0, 20);
-    printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL);
-
-    if (titles.size() > 0) {
-        const size_t odd_items = titles.size() % cols;
-
-        for (size_t i = 0; i < titles.size() - odd_items; i += cols) {
-            for (size_t j = i; j < i + cols; j++) { printer_grid->Add(bitmaps[j], 0, wxBOTTOM, 20); }
-            for (size_t j = i; j < i + cols; j++) { printer_grid->Add(titles[j], 0, wxBOTTOM, 3); }
-            for (size_t j = i; j < i + cols; j++) { printer_grid->Add(variants_panels[j]); }
-
-            // Add separator space to multiliners
-            if (titles.size() > cols) {
-                for (size_t j = i; j < i + cols; j++) { printer_grid->Add(1, 30); }
-            }
-        }
-        if (odd_items > 0) {
-            const size_t rem = titles.size() - odd_items;
-
-            for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(bitmaps[i], 0, wxBOTTOM, 20); }
-            for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); }
-            for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(titles[i], 0, wxBOTTOM, 3); }
-            for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); }
-            for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(variants_panels[i]); }
-        }
-    }
-
-    auto *title_sizer = new wxBoxSizer(wxHORIZONTAL);
-    if (! title.IsEmpty()) {
-        auto *title_widget = new wxStaticText(this, wxID_ANY, title);
-        title_widget->SetFont(font_title);
-        title_sizer->Add(title_widget);
-    }
-    title_sizer->AddStretchSpacer();
-
-    if (titles.size() > 1 || is_variants) {
-        // It only makes sense to add the All / None buttons if there's multiple printers
-        // All Standard button is added when there are more variants for at least one printer
-        auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _L("All standard") : _L("Standard"));
-        auto *sel_all = new wxButton(this, wxID_ANY, _L("All"));
-        auto *sel_none = new wxButton(this, wxID_ANY, _L("None"));
-        if (is_variants) 
-            sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& event) { this->select_all(true, false); });
-        sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, true); });
-        sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); });
-        if (is_variants) 
-            title_sizer->Add(sel_all_std, 0, wxRIGHT, BTN_SPACING);
-        title_sizer->Add(sel_all, 0, wxRIGHT, BTN_SPACING);
-        title_sizer->Add(sel_none);
-
-        wxGetApp().UpdateDarkUI(sel_all_std);
-        wxGetApp().UpdateDarkUI(sel_all);
-        wxGetApp().UpdateDarkUI(sel_none);
-
-        // fill button indexes used later for buttons rescaling
-        if (is_variants)
-            m_button_indexes = { sel_all_std->GetId(), sel_all->GetId(), sel_none->GetId() };
-        else {
-            sel_all_std->Destroy();
-            m_button_indexes = { sel_all->GetId(), sel_none->GetId() };
-        }
-    }
-
-    sizer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, BTN_SPACING);
-    sizer->Add(printer_grid);
-
-    SetSizer(sizer);
-}
-
-PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig)
-    : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; })
-{}
-
-void PrinterPicker::select_all(bool select, bool alternates)
-{
-    for (const auto &cb : cboxes) {
-        if (cb->GetValue() != select) {
-            cb->SetValue(select);
-            on_checkbox(cb, select);
-        }
-    }
-
-    if (! select) { alternates = false; }
-
-    for (const auto &cb : cboxes_alt) {
-        if (cb->GetValue() != alternates) {
-            cb->SetValue(alternates);
-            on_checkbox(cb, alternates);
-        }
-    }
-}
-
-void PrinterPicker::select_one(size_t i, bool select)
-{
-    if (i < cboxes.size() && cboxes[i]->GetValue() != select) {
-        cboxes[i]->SetValue(select);
-        on_checkbox(cboxes[i], select);
-    }
-}
-
-bool PrinterPicker::any_selected() const
-{
-    for (const auto &cb : cboxes) {
-        if (cb->GetValue()) { return true; }
-    }
-
-    for (const auto &cb : cboxes_alt) {
-        if (cb->GetValue()) { return true; }
-    }
-
-    return false;
-}
-
-std::set<std::string> PrinterPicker::get_selected_models() const 
-{
-    std::set<std::string> ret_set;
-
-    for (const auto& cb : cboxes)
-        if (cb->GetValue())
-            ret_set.emplace(cb->model);
-
-    for (const auto& cb : cboxes_alt)
-        if (cb->GetValue())
-            ret_set.emplace(cb->model);
-
-    return ret_set;
-}
-
-void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked)
-{
-    PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked);
-    AddPendingEvent(evt);
-}
-
-
-// Wizard page base
-
-ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent)
-    : wxPanel(parent->p->hscroll)
-    , parent(parent)
-    , shortname(std::move(shortname))
-    , indent(indent)
-{
-    wxGetApp().UpdateDarkUI(this);
-
-    auto *sizer = new wxBoxSizer(wxVERTICAL);
-
-    auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
-    const auto font = GetFont().MakeBold().Scaled(1.5);
-    text->SetFont(font);
-    sizer->Add(text, 0, wxALIGN_LEFT, 0);
-    sizer->AddSpacer(10);
-
-    content = new wxBoxSizer(wxVERTICAL);
-    sizer->Add(content, 1, wxEXPAND);
-
-    SetSizer(sizer);
-
-    // There is strange layout on Linux with GTK3, 
-    // see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861
-    // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages 
-    if (!wxLinux_gtk3)
-        this->Hide();
-
-    Bind(wxEVT_SIZE, [this](wxSizeEvent &event) {
-        this->Layout();
-        event.Skip();
-    });
-}
-
-ConfigWizardPage::~ConfigWizardPage() {}
-
-wxStaticText* ConfigWizardPage::append_text(wxString text)
-{
-    auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
-    widget->Wrap(WRAP_WIDTH);
-    widget->SetMinSize(wxSize(WRAP_WIDTH, -1));
-    append(widget);
-    return widget;
-}
-
-void ConfigWizardPage::append_spacer(int space)
-{
-    // FIXME: scaling
-    content->AddSpacer(space);
-}
-
-// Wizard pages
-
-PageWelcome::PageWelcome(ConfigWizard *parent)
-    : ConfigWizardPage(parent, from_u8((boost::format(
-#ifdef __APPLE__
-            _utf8(L("Welcome to the %s Configuration Assistant"))
-#else
-            _utf8(L("Welcome to the %s Configuration Wizard"))
-#endif
-            ) % SLIC3R_APP_NAME).str()), _L("Welcome"))
-    , welcome_text(append_text(from_u8((boost::format(
-        _utf8(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")))
-        % SLIC3R_APP_NAME
-        % _utf8(ConfigWizard::name())).str())
-    ))
-    , cbox_reset(append(
-        new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)"))
-    ))
-    , cbox_integrate(append(
-        new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system)."))
-    ))
-{
-    welcome_text->Hide();
-    cbox_reset->Hide();
-    cbox_integrate->Hide();    
-}
-
-void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
-{
-    const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY;
-    welcome_text->Show(data_empty);
-    cbox_reset->Show(!data_empty);
-#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
-    if (!DesktopIntegrationDialog::is_integrated())
-        cbox_integrate->Show(true);
-    else
-        cbox_integrate->Hide();
-#else
-    cbox_integrate->Hide();
-#endif
-}
-
-
-PagePrinters::PagePrinters(ConfigWizard *parent,
-    wxString title,
-    wxString shortname,
-    const VendorProfile &vendor,
-    unsigned indent,
-    Technology technology)
-    : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent)
-    , technology(technology)
-    , install(false)   // only used for 3rd party vendors
-{
-    enum {
-        COL_SIZE = 200,
-    };
-
-    AppConfig *appconfig = &this->wizard_p()->appconfig_new;
-
-    const auto families = vendor.families();
-    for (const auto &family : families) {
-        const auto filter = [&](const VendorProfile::PrinterModel &model) {
-            return ((model.technology == ptFFF && technology & T_FFF)
-                    || (model.technology == ptSLA && technology & T_SLA))
-                && model.family == family;
-        };
-
-        if (std::find_if(vendor.models.begin(), vendor.models.end(), filter) == vendor.models.end()) {
-            continue;
-        }
-
-        const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str());
-        auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter);
-
-        picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) {
-            appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
-            wizard_p()->on_printer_pick(this, evt);
-        });
-
-        append(new StaticLine(this));
-
-        append(picker);
-        printer_pickers.push_back(picker);
-        has_printers = true;
-    }
-
-}
-
-void PagePrinters::select_all(bool select, bool alternates)
-{
-    for (auto picker : printer_pickers) {
-        picker->select_all(select, alternates);
-    }
-}
-
-int PagePrinters::get_width() const
-{
-    return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0,
-        [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); });
-}
-
-bool PagePrinters::any_selected() const
-{
-    for (const auto *picker : printer_pickers) {
-        if (picker->any_selected()) { return true; }
-    }
-
-    return false;
-}
-
-std::set<std::string> PagePrinters::get_selected_models()
-{
-    std::set<std::string> ret_set;
-
-    for (const auto *picker : printer_pickers)
-    {
-        std::set<std::string> tmp_models = picker->get_selected_models();
-        ret_set.insert(tmp_models.begin(), tmp_models.end());
-    }
-
-    return ret_set;
-}
-
-void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason)
-{
-    if (is_primary_printer_page
-        && (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY)
-        && printer_pickers.size() > 0 
-        && printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) {
-        printer_pickers[0]->select_one(0, true);
-    }
-}
-
-
-const std::string PageMaterials::EMPTY;
-
-PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name)
-    : ConfigWizardPage(parent, std::move(title), std::move(shortname))
-    , materials(materials)
-	, list_printer(new  StringList(this, wxLB_MULTIPLE))
-    , list_type(new StringList(this))
-    , list_vendor(new StringList(this))
-    , list_profile(new PresetList(this))
-{
-    append_spacer(VERTICAL_SPACING);
-
-    const int em = parent->em_unit();
-    const int list_h = 30*em;
-
-
-	list_printer->SetMinSize(wxSize(23*em, list_h));
-    list_type->SetMinSize(wxSize(13*em, list_h));
-    list_vendor->SetMinSize(wxSize(13*em, list_h));
-    list_profile->SetMinSize(wxSize(23*em, list_h));
-
-
-
-    grid = new wxFlexGridSizer(4, em/2, em);
-    grid->AddGrowableCol(3, 1);
-    grid->AddGrowableRow(1, 1);
-
-	grid->Add(new wxStaticText(this, wxID_ANY, _L("Printer:")));
-    grid->Add(new wxStaticText(this, wxID_ANY, list1name));
-    grid->Add(new wxStaticText(this, wxID_ANY, _L("Vendor:")));
-    grid->Add(new wxStaticText(this, wxID_ANY, _L("Profile:")));
-
-	grid->Add(list_printer, 0, wxEXPAND);
-    grid->Add(list_type, 0, wxEXPAND);
-    grid->Add(list_vendor, 0, wxEXPAND);
-    grid->Add(list_profile, 1, wxEXPAND);
-
-    auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL);
-    auto *sel_all = new wxButton(this, wxID_ANY, _L("All"));
-    auto *sel_none = new wxButton(this, wxID_ANY, _L("None"));
-    btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2);
-    btn_sizer->Add(sel_none);
-
-    wxGetApp().UpdateDarkUI(list_printer);
-    wxGetApp().UpdateDarkUI(list_type);
-    wxGetApp().UpdateDarkUI(list_vendor);
-    wxGetApp().UpdateDarkUI(sel_all);
-    wxGetApp().UpdateDarkUI(sel_none);
-
-    grid->Add(new wxBoxSizer(wxHORIZONTAL));
-    grid->Add(new wxBoxSizer(wxHORIZONTAL));
-    grid->Add(new wxBoxSizer(wxHORIZONTAL));
-    grid->Add(btn_sizer, 0, wxALIGN_RIGHT);
-
-    append(grid, 1, wxEXPAND);
-
-    append_spacer(VERTICAL_SPACING);
-
-    html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition,
-        wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO);
-    append(html_window, 0, wxEXPAND);
-
-	list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) {
-		update_lists(list_type->GetSelection(), list_vendor->GetSelection(), evt.GetInt());
-		});
-    list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
-        update_lists(list_type->GetSelection(), list_vendor->GetSelection());
-    });
-    list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
-        update_lists(list_type->GetSelection(), list_vendor->GetSelection());
-    });
-
-    list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); });
-    list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); });
-
-    sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); });
-    sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); });
-    /*
-    Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();});
-
-    list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); });
-    list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); });
-    list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); });
-    */
-    reload_presets();
-    set_compatible_printers_html_window(std::vector<std::string>(), false);
-}
-void PageMaterials::on_paint()
-{
-}
-void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt)
-{
-    const wxClientDC dc(list_profile);
-    const wxPoint pos = evt.GetLogicalPosition(dc);
-    int item = list_profile->HitTest(pos);
-    on_material_hovered(item);
-}
-void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt)
-{}
-void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt)
-{
-    on_material_hovered(-1);
-}
-void PageMaterials::reload_presets()
-{
-    clear();
-
-	list_printer->append(_L("(All)"), &EMPTY);
-    //list_printer->SetLabelMarkup("<b>bald</b>");
-	for (const Preset* printer : materials->printers) {
-		list_printer->append(printer->name, &printer->name);
-	}
-    sort_list_data(list_printer, true, false);
-    if (list_printer->GetCount() > 0) {
-        list_printer->SetSelection(0);
-        sel_printers_prev.Clear();
-        sel_type_prev = wxNOT_FOUND;
-        sel_vendor_prev = wxNOT_FOUND;
-        update_lists(0, 0, 0);
-    }
-
-    presets_loaded = true;
-}
-
-void PageMaterials::set_compatible_printers_html_window(const std::vector<std::string>& printer_names, bool all_printers)
-{
-    const auto bgr_clr = 
-#if defined(__APPLE__)
-        html_window->GetParent()->GetBackgroundColour();
-#else 
-#if defined(_WIN32)
-        wxGetApp().get_window_default_clr();
-#else
-        wxSystemSettings::GetColour(wxSYS_COLOUR_MENU);
-#endif
-#endif
-    const auto text_clr = wxGetApp().get_label_clr_default();
-    const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
-    const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
-    wxString first_line = format_wxstr(_L("%1% marked with <b>*</b> are <b>not</b> compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials"));
-    wxString text;
-    if (all_printers) {
-        wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material"));
-        text = wxString::Format(
-            "<html>"
-            "<style>"
-            "table{border-spacing: 1px;}"
-            "</style>"
-            "<body bgcolor= %s>"
-            "<font color=%s>"
-            "<font size=\"3\">"
-            "%s<br /><br />%s"
-            "</font>"
-            "</font>"
-            "</body>"
-            "</html>"
-            , bgr_clr_str
-            , text_clr_str
-            , first_line
-            , second_line
-            );
-    } else {
-        wxString second_line;
-        if (!printer_names.empty())
-            second_line = (materials->technology == T_FFF ?
-                          _L("Only the following installed printers are compatible with the selected filaments") :
-                          _L("Only the following installed printers are compatible with the selected SLA materials")) + ":";
-        text = wxString::Format(
-            "<html>"
-            "<style>"
-            "table{border-spacing: 1px;}"
-            "</style>"
-            "<body bgcolor= %s>"
-            "<font color=%s>"
-            "<font size=\"3\">"
-            "%s<br /><br />%s"
-            "<table>"
-            "<tr>"
-            , bgr_clr_str
-            , text_clr_str
-            , first_line
-            , second_line);
-        for (size_t i = 0; i < printer_names.size(); ++i)
-        {
-            text += wxString::Format("<td>%s</td>", boost::nowide::widen(printer_names[i]));
-            if (i % 3 == 2) {
-                text += wxString::Format(
-                    "</tr>"
-                    "<tr>");
-            }
-        }
-        text += wxString::Format(
-            "</tr>"
-            "</table>"
-            "</font>"
-            "</font>"
-            "</body>"
-            "</html>"
-        );
-    }
-   
-    wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this));
-    const int fs = font.GetPointSize();
-    int size[] = { fs,fs,fs,fs,fs,fs,fs };
-    html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
-    html_window->SetPage(text);
-}
-
-void PageMaterials::clear_compatible_printers_label()
-{
-    set_compatible_printers_html_window(std::vector<std::string>(), false);
-}
-
-void PageMaterials::on_material_hovered(int sel_material)
-{
-
-}
-
-void PageMaterials::on_material_highlighted(int sel_material)
-{
-    if (sel_material == last_hovered_item)
-        return;
-    if (sel_material == -1) {
-        clear_compatible_printers_label();
-        return;
-    }
-    last_hovered_item = sel_material;
-    std::vector<std::string> tabs;
-    tabs.push_back(std::string());
-    tabs.push_back(std::string());
-    tabs.push_back(std::string());
-    //selected material string
-    std::string material_name = list_profile->get_data(sel_material);
-    // get material preset
-    const std::vector<const Preset*> matching_materials = materials->get_presets_by_alias(material_name);
-    if (matching_materials.empty())
-    {
-        clear_compatible_printers_label();
-        return;
-    }
-    //find matching printers
-    std::vector<std::string> names;
-    for (const Preset* printer : materials->printers) {
-        for (const Preset* material : matching_materials) {
-            if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) {
-                names.push_back(printer->name);
-                break;
-            }
-        }
-    }
-    set_compatible_printers_html_window(names, names.size() == materials->printers.size());
-}
-
-void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected_printer/* = -1*/)
-{
-	wxWindowUpdateLocker freeze_guard(this);
-	(void)freeze_guard;
-
-	wxArrayInt sel_printers;
-	int sel_printers_count = list_printer->GetSelections(sel_printers);
-
-    // Does our wxWidgets version support operator== for wxArrayInt ?
-    // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614
-#if wxCHECK_VERSION(3, 1, 1)
-    if (sel_printers != sel_printers_prev) {
-#else
-    auto are_equal = [](const wxArrayInt& arr_first, const wxArrayInt& arr_second) {
-        if (arr_first.GetCount() != arr_second.GetCount())
-            return false;
-        for (size_t i = 0; i < arr_first.GetCount(); i++)
-            if (arr_first[i] != arr_second[i])
-                return false;
-        return true;
-    };
-    if (!are_equal(sel_printers, sel_printers_prev)) {
-#endif
-
-        // Refresh type list
-		list_type->Clear();
-		list_type->append(_L("(All)"), &EMPTY);
-		if (sel_printers_count > 0) {
-            // If all is selected with other printers
-            // unselect "all" or all printers depending on last value
-            if (sel_printers[0] == 0 && sel_printers_count > 1) {
-                if (last_selected_printer == 0) {
-                    list_printer->SetSelection(wxNOT_FOUND);
-                    list_printer->SetSelection(0);
-                } else {
-                    list_printer->SetSelection(0, false);
-                    sel_printers_count = list_printer->GetSelections(sel_printers);
-                }
-            }
-			if (sel_printers[0] != 0) {
-                for (int i = 0; i < sel_printers_count; i++) {
-					const std::string& printer_name = list_printer->get_data(sel_printers[i]);
-					const Preset* printer = nullptr;
-					for (const Preset* it : materials->printers) {
-						if (it->name == printer_name) {
-							printer = it;
-							break;
-						}
-					}
-					materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) {
-						const std::string& type = this->materials->get_type(p);
-						if (list_type->find(type) == wxNOT_FOUND) {
-							list_type->append(type, &type);
-						}
-						});
-				}
-			} else {
-                //clear selection except "ALL"
-                list_printer->SetSelection(wxNOT_FOUND);
-                list_printer->SetSelection(0);
-                sel_printers_count = list_printer->GetSelections(sel_printers);
-
-				materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) {
-					const std::string& type = this->materials->get_type(p);
-					if (list_type->find(type) == wxNOT_FOUND) {
-						list_type->append(type, &type);
-					}
-					});
-			}
-            sort_list_data(list_type, true, true);
-		}
-
-		sel_printers_prev = sel_printers;
-		sel_type = 0;
-		sel_type_prev = wxNOT_FOUND;
-		list_type->SetSelection(sel_type);
-		list_profile->Clear();
-	}
-	
-	if (sel_type != sel_type_prev) {
-		// Refresh vendor list
-
-		// XXX: The vendor list is created with quadratic complexity here,
-		// but the number of vendors is going to be very small this shouldn't be a problem.
-
-		list_vendor->Clear();
-		list_vendor->append(_L("(All)"), &EMPTY);
-		if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) {
-			const std::string& type = list_type->get_data(sel_type);
-			// find printer preset
-            for (int i = 0; i < sel_printers_count; i++) {
-				const std::string& printer_name = list_printer->get_data(sel_printers[i]);
-				const Preset* printer = nullptr;
-				for (const Preset* it : materials->printers) {
-					if (it->name == printer_name) {
-						printer = it;
-						break;
-					}
-				}
-				materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) {
-					const std::string& vendor = this->materials->get_vendor(p);
-					if (list_vendor->find(vendor) == wxNOT_FOUND) {
-						list_vendor->append(vendor, &vendor);
-					}
-					});
-			}
-            sort_list_data(list_vendor, true, false);
-		}
-
-		sel_type_prev = sel_type;
-		sel_vendor = 0;
-		sel_vendor_prev = wxNOT_FOUND;
-		list_vendor->SetSelection(sel_vendor);
-		list_profile->Clear();
-	}
-         
-	if (sel_vendor != sel_vendor_prev) {
-		// Refresh material list
-
-		list_profile->Clear();
-        clear_compatible_printers_label();
-		if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) {
-			const std::string& type = list_type->get_data(sel_type);
-			const std::string& vendor = list_vendor->get_data(sel_vendor);
-			// finst printer preset
-            std::vector<ProfilePrintData> to_list;
-            for (int i = 0; i < sel_printers_count; i++) {
-				const std::string& printer_name = list_printer->get_data(sel_printers[i]);
-				const Preset* printer = nullptr;
-				for (const Preset* it : materials->printers) {
-					if (it->name == printer_name) {
-						printer = it;
-						break;
-					}
-				}
-
-				materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) {
-					const std::string& section = materials->appconfig_section();
-                    bool checked = wizard_p()->appconfig_new.has(section, p->name);
-                    bool was_checked = false;
-
-                    int cur_i = list_profile->find(p->alias);
-                    if (cur_i == wxNOT_FOUND) {
-                        cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias);
-                        to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked);
-                    }
-                    else {
-                        was_checked = list_profile->IsChecked(cur_i);
-                        to_list[cur_i].checked = checked || was_checked;
-                    }
-                    list_profile->Check(cur_i, checked || was_checked);
-
-					/* Update preset selection in config.
-					 * If one preset from aliases bundle is selected,
-					 * than mark all presets with this aliases as selected
-					 * */
-					if (checked && !was_checked)
-						wizard_p()->update_presets_in_config(section, p->alias, true);
-					else if (!checked && was_checked)
-						wizard_p()->appconfig_new.set(section, p->name, "1");
-					});
-			}
-            sort_list_data(list_profile, to_list);
-		}
-
-		sel_vendor_prev = sel_vendor;
-	}
-    wxGetApp().UpdateDarkUI(list_profile);
-}
-
-void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering)
-{
-// get data from list
-// sort data
-// first should be <all>
-// then prusa profiles
-// then the rest
-// in alphabetical order
-    
-    std::vector<std::reference_wrapper<const std::string>> prusa_profiles;
-    std::vector<std::reference_wrapper<const std::string>> other_profiles;
-    for (int i = 0 ; i < list->size(); ++i) {
-        const std::string& data = list->get_data(i);
-        if (data == EMPTY) // do not sort <all> item
-            continue;
-        if (!material_type_ordering && data.find("Prusa") != std::string::npos)
-            prusa_profiles.push_back(data);
-        else 
-            other_profiles.push_back(data);
-    }
-    if(material_type_ordering) {
-        
-        const ConfigOptionDef* def = print_config_def.get("filament_type");
-        std::vector<std::string>enum_values = def->enum_values;
-        size_t end_of_sorted = 0;
-        for (size_t vals = 0; vals < enum_values.size(); vals++) {
-            for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++)
-            {
-                // find instead compare because PET vs PETG
-                if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) {
-                    //swap
-                    if(profs != end_of_sorted) {
-                        std::reference_wrapper<const std::string> aux = other_profiles[end_of_sorted];
-                        other_profiles[end_of_sorted] = other_profiles[profs];
-                        other_profiles[profs] = aux;
-                    }
-                    end_of_sorted++;
-                    break;
-                }
-            }
-        }
-    } else {
-        std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper<const std::string> a, std::reference_wrapper<const std::string> b) {
-            return a.get() < b.get();
-            });
-        std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper<const std::string> a, std::reference_wrapper<const std::string> b) {
-            return a.get() < b.get();
-            });
-    }
-    
-    list->Clear();
-    if (add_All_item)
-        list->append(_L("(All)"), &EMPTY);
-    for (const auto& item : prusa_profiles)
-        list->append(item, &const_cast<std::string&>(item.get()));
-    for (const auto& item : other_profiles)
-        list->append(item, &const_cast<std::string&>(item.get()));
-}     
-
-void PageMaterials::sort_list_data(PresetList* list, const std::vector<ProfilePrintData>& data)
-{
-    // sort data
-    // then prusa profiles
-    // then the rest
-    // in alphabetical order
-    std::vector<ProfilePrintData> prusa_profiles;
-    std::vector<ProfilePrintData> other_profiles;
-    //for (int i = 0; i < data.size(); ++i) {
-    for (const auto& item : data) {
-        const std::string& name = item.name;
-        if (name.find("Prusa") != std::string::npos)
-            prusa_profiles.emplace_back(item);
-        else
-            other_profiles.emplace_back(item);
-    }
-    std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) {
-        return a.name.get() < b.name.get();
-        });
-    std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) {
-        return a.name.get() < b.name.get();
-        });
-    list->Clear();
-    for (size_t i = 0; i < prusa_profiles.size(); ++i) {
-        list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast<std::string&>(prusa_profiles[i].name.get()));
-        list->Check(i, prusa_profiles[i].checked);
-    }
-    for (size_t i = 0; i < other_profiles.size(); ++i) {
-        list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast<std::string&>(other_profiles[i].name.get()));
-        list->Check(i + prusa_profiles.size(), other_profiles[i].checked);
-    }
-}
-
-void PageMaterials::select_material(int i)
-{
-    const bool checked = list_profile->IsChecked(i);
-
-    const std::string& alias_key = list_profile->get_data(i);
-    wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked);
-}
-
-void PageMaterials::select_all(bool select)
-{
-    wxWindowUpdateLocker freeze_guard(this);
-    (void)freeze_guard;
-
-    for (unsigned i = 0; i < list_profile->GetCount(); i++) {
-        const bool current = list_profile->IsChecked(i);
-        if (current != select) {
-            list_profile->Check(i, select);
-            select_material(i);
-        }
-    }
-}
-
-void PageMaterials::clear()
-{
-	list_printer->Clear();
-    list_type->Clear();
-    list_vendor->Clear();
-    list_profile->Clear();
-	sel_printers_prev.Clear();
-    sel_type_prev = wxNOT_FOUND;
-    sel_vendor_prev = wxNOT_FOUND;
-    presets_loaded = false;
-}
-
-void PageMaterials::on_activate()
-{
-    if (! presets_loaded) {
-        wizard_p()->update_materials(materials->technology);
-        reload_presets();
-    }
-    first_paint = true;
-}
-
-
-const char *PageCustom::default_profile_name = "My Settings";
-
-PageCustom::PageCustom(ConfigWizard *parent)
-    : ConfigWizardPage(parent, _L("Custom Printer Setup"), _L("Custom Printer"))
-{
-    cb_custom = new wxCheckBox(this, wxID_ANY, _L("Define a custom printer profile"));
-    tc_profile_name = new wxTextCtrl(this, wxID_ANY, default_profile_name);
-    auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:"));
-
-    wxGetApp().UpdateDarkUI(tc_profile_name);
-
-    tc_profile_name->Enable(false);
-    tc_profile_name->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent &evt) {
-        if (tc_profile_name->GetValue().IsEmpty()) {
-            if (profile_name_prev.IsEmpty()) { tc_profile_name->SetValue(default_profile_name); }
-            else { tc_profile_name->SetValue(profile_name_prev); }
-        } else {
-            profile_name_prev = tc_profile_name->GetValue();
-        }
-        evt.Skip();
-    });
-
-    cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) {
-        tc_profile_name->Enable(custom_wanted());
-        wizard_p()->on_custom_setup(custom_wanted());
-		
-    });
-
-    append(cb_custom);
-    append(label);
-    append(tc_profile_name);
-}
-
-PageUpdate::PageUpdate(ConfigWizard *parent)
-    : ConfigWizardPage(parent, _L("Automatic updates"), _L("Updates"))
-    , version_check(true)
-    , preset_update(true)
-{
-    const AppConfig *app_config = wxGetApp().app_config;
-    auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
-    boldfont.SetWeight(wxFONTWEIGHT_BOLD);
-
-    auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates"));
-    box_slic3r->SetValue(app_config->get("notify_release") != "none");
-    append(box_slic3r);
-    append_text(wxString::Format(_L(
-        "If enabled, %s checks for new application versions online. When a new version becomes available, "
-         "a notification is displayed at the next application startup (never during program usage). "
-         "This is only a notification mechanisms, no automatic installation is done."), SLIC3R_APP_NAME));
-
-    append_spacer(VERTICAL_SPACING);
-
-    auto *box_presets = new wxCheckBox(this, wxID_ANY, _L("Update built-in Presets automatically"));
-    box_presets->SetValue(app_config->get("preset_update") == "1");
-    append(box_presets);
-    append_text(wxString::Format(_L(
-        "If enabled, %s 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."), SLIC3R_APP_NAME));
-    const auto text_bold = _L("Updates are never applied without user's consent and never overwrite user's customized settings.");
-    auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold);
-    label_bold->SetFont(boldfont);
-    label_bold->Wrap(WRAP_WIDTH);
-    append(label_bold);
-    append_text(_L("Additionally a backup snapshot of the whole configuration is created before an update is applied."));
-
-    box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); });
-    box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
-}
-
-PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent)
-    : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk"))
-    , full_pathnames(false)
-{
-    auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _L("Export full pathnames of models and parts sources into 3mf and amf files"));
-    box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1");
-    append(box_pathnames);
-    append_text(_L(
-        "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n"
-        "If not enabled, the Reload from disk command will ask to select each file using an open file dialog."
-    ));
-
-    box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); });
-}
-
-#ifdef _WIN32
-PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent)
-    : ConfigWizardPage(parent, _L("Files association"), _L("Files association"))
-{
-    cb_3mf = new wxCheckBox(this, wxID_ANY, _L("Associate .3mf files to PrusaSlicer"));
-    cb_stl = new wxCheckBox(this, wxID_ANY, _L("Associate .stl files to PrusaSlicer"));
-//    cb_gcode = new wxCheckBox(this, wxID_ANY, _L("Associate .gcode files to PrusaSlicer G-code Viewer"));
-
-    append(cb_3mf);
-    append(cb_stl);
-//    append(cb_gcode);
-}
-#endif // _WIN32
-
-PageMode::PageMode(ConfigWizard *parent)
-    : ConfigWizardPage(parent, _L("View mode"), _L("View mode"))
-{
-    append_text(_L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n"
-        "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. "
-        "The other two offer progressively more sophisticated fine-tuning, "
-        "they are suitable for advanced and expert users, respectively."));
-
-    radio_simple = new wxRadioButton(this, wxID_ANY, _L("Simple mode"));
-    radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode"));
-    radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode"));
-
-    std::string mode { "simple" };
-    wxGetApp().app_config->get("", "view_mode", mode);
-
-    if (mode == "advanced") { radio_advanced->SetValue(true); }
-    else if (mode == "expert") { radio_expert->SetValue(true); }
-    else { radio_simple->SetValue(true); }
-
-    append(radio_simple);
-    append(radio_advanced);
-    append(radio_expert);
-
-    append_text("\n" + _L("The size of the object can be specified in inches"));
-    check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches"));
-    check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1");
-    append(check_inch);
-
-    on_activate();
-}
-
-void PageMode::serialize_mode(AppConfig *app_config) const
-{
-    std::string mode = "";
-
-    if (radio_simple->GetValue()) { mode = "simple"; }
-    if (radio_advanced->GetValue()) { mode = "advanced"; }
-    if (radio_expert->GetValue()) { mode = "expert"; }
-
-    app_config->set("view_mode", mode);
-    app_config->set("use_inches", check_inch->GetValue() ? "1" : "0");
-}
-
-PageVendors::PageVendors(ConfigWizard *parent)
-    : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors"))
-{
-    const AppConfig &appconfig = this->wizard_p()->appconfig_new;
-
-    append_text(wxString::Format(_L("Pick another vendor supported by %s"), SLIC3R_APP_NAME) + ":");
-
-    auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
-    boldfont.SetWeight(wxFONTWEIGHT_BOLD);
-
-    for (const auto &pair : wizard_p()->bundles) {
-        const VendorProfile *vendor = pair.second.vendor_profile;
-        if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
-
-        auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name);
-        cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) {
-            wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked());
-        });
-
-        const auto &vendors = appconfig.vendors();
-        const bool enabled = vendors.find(pair.first) != vendors.end();
-        if (enabled) {
-            cbox->SetValue(true);
-
-            auto pages = wizard_p()->pages_3rdparty.find(vendor->id);
-            wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created");
-
-            for (PagePrinters* page : { pages->second.first, pages->second.second })
-                if (page) page->install = true;
-        }
-
-        append(cbox);
-    }
-}
-
-PageFirmware::PageFirmware(ConfigWizard *parent)
-    : ConfigWizardPage(parent, _L("Firmware Type"), _L("Firmware"), 1)
-    , gcode_opt(*print_config_def.get("gcode_flavor"))
-    , gcode_picker(nullptr)
-{
-    append_text(_L("Choose the type of firmware used by your printer."));
-    append_text(_(gcode_opt.tooltip));
-
-    wxArrayString choices;
-    choices.Alloc(gcode_opt.enum_labels.size());
-    for (const auto &label : gcode_opt.enum_labels) {
-        choices.Add(label);
-    }
-
-    gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices);
-    wxGetApp().UpdateDarkUI(gcode_picker);
-    const auto &enum_values = gcode_opt.enum_values;
-    auto needle = enum_values.cend();
-    if (gcode_opt.default_value) {
-        needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize());
-    }
-    if (needle != enum_values.cend()) {
-        gcode_picker->SetSelection(needle - enum_values.cbegin());
-    } else {
-        gcode_picker->SetSelection(0);
-    }
-
-    append(gcode_picker);
-}
-
-void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
-{
-    auto sel = gcode_picker->GetSelection();
-    if (sel >= 0 && (size_t)sel < gcode_opt.enum_labels.size()) {
-        auto *opt = new ConfigOptionEnum<GCodeFlavor>(static_cast<GCodeFlavor>(sel));
-        config.set_key_value("gcode_flavor", opt);
-    }
-}
-
-PageBedShape::PageBedShape(ConfigWizard *parent)
-    : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1)
-    , shape_panel(new BedShapePanel(this))
-{
-    append_text(_L("Set the shape of your printer's bed."));
-
-    shape_panel->build_panel(*wizard_p()->custom_config->option<ConfigOptionPoints>("bed_shape"),
-        *wizard_p()->custom_config->option<ConfigOptionString>("bed_custom_texture"),
-        *wizard_p()->custom_config->option<ConfigOptionString>("bed_custom_model"));
-
-    append(shape_panel);
-}
-
-void PageBedShape::apply_custom_config(DynamicPrintConfig &config)
-{
-    const std::vector<Vec2d>& points = shape_panel->get_shape();
-    const std::string& custom_texture = shape_panel->get_custom_texture();
-    const std::string& custom_model = shape_panel->get_custom_model();
-    config.set_key_value("bed_shape", new ConfigOptionPoints(points));
-    config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture));
-    config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model));
-}
-
-static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) 
-{
-    e.Skip();
-    wxString str = ctrl->GetValue();
-
-    const char dec_sep = is_decimal_separator_point() ? '.' : ',';
-    const char dec_sep_alt = dec_sep == '.' ? ',' : '.';
-    // Replace the first incorrect separator in decimal number.
-    bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0;
-
-    double val = 0.0;
-    if (!str.ToDouble(&val)) {
-        if (val == 0.0)
-            val = def_value;
-        ctrl->SetValue(double_to_string(val));
-        show_error(nullptr, _L("Invalid numeric input."));
-        ctrl->SetFocus();
-    }
-    else if (was_replaced)
-        ctrl->SetValue(double_to_string(val));
-}
-
-class DiamTextCtrl : public wxTextCtrl
-{
-public:
-    DiamTextCtrl(wxWindow* parent)
-    {
-#ifdef _WIN32
-        long style = wxBORDER_SIMPLE;
-#else
-        long style = 0;
-#endif
-        Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord), style);
-        wxGetApp().UpdateDarkUI(this);
-    }
-    ~DiamTextCtrl() {}
-};
-
-PageDiameters::PageDiameters(ConfigWizard *parent)
-    : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1)
-    , diam_nozzle(new DiamTextCtrl(this))
-    , diam_filam (new DiamTextCtrl(this))
-{
-    auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value<ConfigOptionFloats>();
-    wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5);
-    diam_nozzle->SetValue(value);
-
-    auto *default_filam = print_config_def.get("filament_diameter")->get_default_value<ConfigOptionFloats>();
-    value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0);
-    diam_filam->SetValue(value);
-
-    diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId());
-    diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId());
-
-    append_text(_L("Enter the diameter of your printer's hot end nozzle."));
-
-    auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5);
-    auto *text_nozzle = new wxStaticText(this, wxID_ANY, _L("Nozzle Diameter:"));
-    auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm"));
-    sizer_nozzle->AddGrowableCol(0, 1);
-    sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
-    sizer_nozzle->Add(diam_nozzle);
-    sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
-    append(sizer_nozzle);
-
-    append_spacer(VERTICAL_SPACING);
-
-    append_text(_L("Enter the diameter of your filament."));
-    append_text(_L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average."));
-
-    auto *sizer_filam = new wxFlexGridSizer(3, 5, 5);
-    auto *text_filam = new wxStaticText(this, wxID_ANY, _L("Filament Diameter:"));
-    auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm"));
-    sizer_filam->AddGrowableCol(0, 1);
-    sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL);
-    sizer_filam->Add(diam_filam);
-    sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL);
-    append(sizer_filam);
-}
-
-void PageDiameters::apply_custom_config(DynamicPrintConfig &config)
-{
-    double val = 0.0;
-    diam_nozzle->GetValue().ToDouble(&val);
-    auto *opt_nozzle = new ConfigOptionFloats(1, val);
-    config.set_key_value("nozzle_diameter", opt_nozzle);
-
-    val = 0.0;
-    diam_filam->GetValue().ToDouble(&val);
-    auto * opt_filam = new ConfigOptionFloats(1, val);
-    config.set_key_value("filament_diameter", opt_filam);
-
-    auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) {
-        char buf[64]; // locales don't matter here (sprintf/atof)
-        sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4);
-        config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false));
-    };
-
-    set_extrusion_width("support_material_extrusion_width",   0.35);
-    set_extrusion_width("top_infill_extrusion_width",		  0.40);
-    set_extrusion_width("first_layer_extrusion_width",		  0.42);
-
-    set_extrusion_width("extrusion_width",					  0.45);
-    set_extrusion_width("perimeter_extrusion_width",		  0.45);
-    set_extrusion_width("external_perimeter_extrusion_width", 0.45);
-    set_extrusion_width("infill_extrusion_width",			  0.45);
-    set_extrusion_width("solid_infill_extrusion_width",       0.45);
-}
-
-class SpinCtrlDouble: public wxSpinCtrlDouble
-{
-public:
-    SpinCtrlDouble(wxWindow* parent)
-    {
-#ifdef _WIN32
-        long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE;
-#else
-        long style = wxSP_ARROW_KEYS;
-#endif
-        Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, style);
-#ifdef _WIN32
-        wxGetApp().UpdateDarkUI(this->GetText());
-#endif
-        this->Refresh();
-    }
-    ~SpinCtrlDouble() {}
-};
-
-PageTemperatures::PageTemperatures(ConfigWizard *parent)
-    : ConfigWizardPage(parent, _L("Nozzle and Bed Temperatures"), _L("Temperatures"), 1)
-    , spin_extr(new SpinCtrlDouble(this))
-    , spin_bed (new SpinCtrlDouble(this))
-{
-    spin_extr->SetIncrement(5.0);
-    const auto &def_extr = *print_config_def.get("temperature");
-    spin_extr->SetRange(def_extr.min, def_extr.max);
-    auto *default_extr = def_extr.get_default_value<ConfigOptionInts>();
-    spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200);
-
-    spin_bed->SetIncrement(5.0);
-    const auto &def_bed = *print_config_def.get("bed_temperature");
-    spin_bed->SetRange(def_bed.min, def_bed.max);
-    auto *default_bed = def_bed.get_default_value<ConfigOptionInts>();
-    spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0);
-
-    append_text(_L("Enter the temperature needed for extruding your filament."));
-    append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS."));
-
-    auto *sizer_extr = new wxFlexGridSizer(3, 5, 5);
-    auto *text_extr = new wxStaticText(this, wxID_ANY, _L("Extrusion Temperature:"));
-    auto *unit_extr = new wxStaticText(this, wxID_ANY, _L("°C"));
-    sizer_extr->AddGrowableCol(0, 1);
-    sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL);
-    sizer_extr->Add(spin_extr);
-    sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL);
-    append(sizer_extr);
-
-    append_spacer(VERTICAL_SPACING);
-
-    append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed."));
-    append_text(_L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed."));
-
-    auto *sizer_bed = new wxFlexGridSizer(3, 5, 5);
-    auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:"));
-    auto *unit_bed = new wxStaticText(this, wxID_ANY, _L("°C"));
-    sizer_bed->AddGrowableCol(0, 1);
-    sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL);
-    sizer_bed->Add(spin_bed);
-    sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL);
-    append(sizer_bed);
-}
-
-void PageTemperatures::apply_custom_config(DynamicPrintConfig &config)
-{
-    auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue());
-    config.set_key_value("temperature", opt_extr);
-    auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue());
-    config.set_key_value("first_layer_temperature", opt_extr1st);
-    auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue());
-    config.set_key_value("bed_temperature", opt_bed);
-    auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue());
-    config.set_key_value("first_layer_bed_temperature", opt_bed1st);
-}
-
-
-// Index
-
-ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent)
-    : wxPanel(parent)
-    , bg(ScalableBitmap(parent, "PrusaSlicer_192px_transparent.png", 192))
-    , bullet_black(ScalableBitmap(parent, "bullet_black.png"))
-    , bullet_blue(ScalableBitmap(parent, "bullet_blue.png"))
-    , bullet_white(ScalableBitmap(parent, "bullet_white.png"))
-    , item_active(NO_ITEM)
-    , item_hover(NO_ITEM)
-    , last_page((size_t)-1)
-{
-#ifndef __WXOSX__ 
-    SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
-#endif //__WXOSX__
-    SetMinSize(bg.bmp().GetSize());
-
-    const wxSize size = GetTextExtent("m");
-    em_w = size.x;
-    em_h = size.y;
-
-    Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this);
-    Bind(wxEVT_SIZE, [this](wxEvent& e) { e.Skip(); Refresh(); });
-    Bind(wxEVT_MOTION, &ConfigWizardIndex::on_mouse_move, this);
-
-    Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &evt) {
-        if (item_hover != -1) {
-            item_hover = -1;
-            Refresh();
-        }
-        evt.Skip();
-    });
-
-    Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &evt) {
-        if (item_hover >= 0) { go_to(item_hover); }
-    });
-}
-
-wxDECLARE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
-
-void ConfigWizardIndex::add_page(ConfigWizardPage *page)
-{
-    last_page = items.size();
-    items.emplace_back(Item { page->shortname, page->indent, page });
-    Refresh();
-}
-
-void ConfigWizardIndex::add_label(wxString label, unsigned indent)
-{
-    items.emplace_back(Item { std::move(label), indent, nullptr });
-    Refresh();
-}
-
-ConfigWizardPage* ConfigWizardIndex::active_page() const
-{
-    if (item_active >= items.size()) { return nullptr; }
-
-    return items[item_active].page;
-}
-
-void ConfigWizardIndex::go_prev()
-{
-    // Search for a preceiding item that is a page (not a label, ie. page != nullptr)
-
-    if (item_active == NO_ITEM) { return; }
-
-    for (size_t i = item_active; i > 0; i--) {
-        if (items[i - 1].page != nullptr) {
-            go_to(i - 1);
-            return;
-        }
-    }
-}
-
-void ConfigWizardIndex::go_next()
-{
-    // Search for a next item that is a page (not a label, ie. page != nullptr)
-
-    if (item_active == NO_ITEM) { return; }
-
-    for (size_t i = item_active + 1; i < items.size(); i++) {
-        if (items[i].page != nullptr) {
-            go_to(i);
-            return;
-        }
-    }
-}
-
-// This one actually performs the go-to op
-void ConfigWizardIndex::go_to(size_t i)
-{
-    if (i != item_active
-        && i < items.size()
-        && items[i].page != nullptr) {
-        auto *new_active = items[i].page;
-        auto *former_active = active_page();
-        if (former_active != nullptr) {
-            former_active->Hide();
-        }
-
-        item_active = i;
-        new_active->Show();
-
-        wxCommandEvent evt(EVT_INDEX_PAGE, GetId());
-        AddPendingEvent(evt);
-
-        Refresh();
-
-        new_active->on_activate();
-    }
-}
-
-void ConfigWizardIndex::go_to(const ConfigWizardPage *page)
-{
-    if (page == nullptr) { return; }
-
-    for (size_t i = 0; i < items.size(); i++) {
-        if (items[i].page == page) {
-            go_to(i);
-            return;
-        }
-    }
-}
-
-void ConfigWizardIndex::clear()
-{
-    auto *former_active = active_page();
-    if (former_active != nullptr) { former_active->Hide(); }
-
-    items.clear();
-    item_active = NO_ITEM;
-}
-
-void ConfigWizardIndex::on_paint(wxPaintEvent & evt)
-{
-    const auto size = GetClientSize();
-    if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; }
-   
-    wxPaintDC dc(this);
-    
-    const auto bullet_w = bullet_black.bmp().GetSize().GetWidth();
-    const auto bullet_h = bullet_black.bmp().GetSize().GetHeight();
-    const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0;
-    const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0;
-    const int yinc = item_height();
-   
-    int index_width = 0;
-
-    unsigned y = 0;
-    for (size_t i = 0; i < items.size(); i++) {
-        const Item& item = items[i];
-        unsigned x = em_w/2 + item.indent * em_w;
-
-        if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) {
-            dc.DrawBitmap(bullet_blue.bmp(), x, y + yoff_icon, false);
-        }
-        else if (i < item_active)  { dc.DrawBitmap(bullet_black.bmp(), x, y + yoff_icon, false); }
-        else if (i > item_active)  { dc.DrawBitmap(bullet_white.bmp(), x, y + yoff_icon, false); }
-
-        x += + bullet_w + em_w/2;
-        const auto text_size = dc.GetTextExtent(item.label);
-        dc.SetTextForeground(wxGetApp().get_label_clr_default());
-        dc.DrawText(item.label, x, y + yoff_text);
-
-        y += yinc;
-        index_width = std::max(index_width, (int)x + text_size.x);
-    }
-    
-    //draw logo
-    if (int y = size.y - bg.GetBmpHeight(); y>=0) {
-        dc.DrawBitmap(bg.bmp(), 0, y, false);
-        index_width = std::max(index_width, bg.GetBmpWidth() + em_w / 2);
-    }
-
-    if (GetMinSize().x < index_width) {
-        CallAfter([this, index_width]() {
-            SetMinSize(wxSize(index_width, GetMinSize().y));
-            Refresh();
-        });
-    }
-}
-
-void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt)
-{
-    const wxClientDC dc(this);
-    const wxPoint pos = evt.GetLogicalPosition(dc);
-
-    const ssize_t item_hover_new = pos.y / item_height();
-
-    if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) {
-        item_hover = item_hover_new;
-        Refresh();
-    }
-
-    evt.Skip();
-}
-
-void ConfigWizardIndex::msw_rescale()
-{
-    const wxSize size = GetTextExtent("m");
-    em_w = size.x;
-    em_h = size.y;
-
-    bg.msw_rescale();
-    SetMinSize(bg.bmp().GetSize());
-
-    bullet_black.msw_rescale();
-    bullet_blue.msw_rescale();
-    bullet_white.msw_rescale();
-    Refresh();
-}
-
-
-// Materials
-
-const std::string Materials::UNKNOWN = "(Unknown)";
-
-void Materials::push(const Preset *preset)
-{
-    presets.emplace_back(preset);
-    types.insert(technology & T_FFF
-        ? Materials::get_filament_type(preset)
-        : Materials::get_material_type(preset));
-}
-
-void  Materials::add_printer(const Preset* preset)
-{
-	printers.insert(preset);
-}
-
-void Materials::clear()
-{
-    presets.clear();
-    types.clear();
-	printers.clear();
-    compatibility_counter.clear();
-}
-
-const std::string& Materials::appconfig_section() const
-{
-    return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
-}
-
-const std::string& Materials::get_type(const Preset *preset) const
-{
-    return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset);
-}
-
-const std::string& Materials::get_vendor(const Preset *preset) const
-{
-    return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset);
-}
-
-const std::string& Materials::get_filament_type(const Preset *preset)
-{
-    const auto *opt = preset->config.opt<ConfigOptionStrings>("filament_type");
-    if (opt != nullptr && opt->values.size() > 0) {
-        return opt->values[0];
-    } else {
-        return UNKNOWN;
-    }
-}
-
-const std::string& Materials::get_filament_vendor(const Preset *preset)
-{
-    const auto *opt = preset->config.opt<ConfigOptionString>("filament_vendor");
-    return opt != nullptr ? opt->value : UNKNOWN;
-}
-
-const std::string& Materials::get_material_type(const Preset *preset)
-{
-    const auto *opt = preset->config.opt<ConfigOptionString>("material_type");
-    if (opt != nullptr) {
-        return opt->value;
-    } else {
-        return UNKNOWN;
-    }
-}
-
-const std::string& Materials::get_material_vendor(const Preset *preset)
-{
-    const auto *opt = preset->config.opt<ConfigOptionString>("material_vendor");
-    return opt != nullptr ? opt->value : UNKNOWN;
-}
-
-// priv
-
-static const std::unordered_map<std::string, std::pair<std::string, std::string>> legacy_preset_map {{
-    { "Original Prusa i3 MK2.ini",                           std::make_pair("MK2S", "0.4") },
-    { "Original Prusa i3 MK2 MM Single Mode.ini",            std::make_pair("MK2SMM", "0.4") },
-    { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
-    { "Original Prusa i3 MK2 MultiMaterial.ini",             std::make_pair("MK2SMM", "0.4") },
-    { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini",  std::make_pair("MK2SMM", "0.6") },
-    { "Original Prusa i3 MK2 0.25 nozzle.ini",               std::make_pair("MK2S", "0.25") },
-    { "Original Prusa i3 MK2 0.6 nozzle.ini",                std::make_pair("MK2S", "0.6") },
-    { "Original Prusa i3 MK3.ini",                           std::make_pair("MK3",  "0.4") },
-}};
-
-void ConfigWizard::priv::load_pages()
-{
-    wxWindowUpdateLocker freeze_guard(q);
-    (void)freeze_guard;
-
-    const ConfigWizardPage *former_active = index->active_page();
-
-    index->clear();
-
-    index->add_page(page_welcome);
-
-    // Printers
-    if (!only_sla_mode)
-        index->add_page(page_fff);
-    index->add_page(page_msla);
-    if (!only_sla_mode) {
-        index->add_page(page_vendors);
-        for (const auto &pages : pages_3rdparty) {
-            for ( PagePrinters* page : { pages.second.first, pages.second.second })
-                if (page && page->install)
-                    index->add_page(page);
-        }
-
-        index->add_page(page_custom);
-        if (page_custom->custom_wanted()) {
-            index->add_page(page_firmware);
-            index->add_page(page_bed);
-            index->add_page(page_diams);
-            index->add_page(page_temps);
-        }
-   
-    // Filaments & Materials
-        if (any_fff_selected) { index->add_page(page_filaments); }
-    }
-    if (any_sla_selected) { index->add_page(page_sla_materials); }
-
-    // there should to be selected at least one printer
-    btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected);
-
-    index->add_page(page_update);
-    index->add_page(page_reload_from_disk);
-#ifdef _WIN32
-    index->add_page(page_files_association);
-#endif // _WIN32
-    index->add_page(page_mode);
-
-    index->go_to(former_active);   // Will restore the active item/page if possible
-
-    q->Layout();
-// This Refresh() is needed to avoid ugly artifacts after printer selection, when no one vendor was selected from the very beginnig
-    q->Refresh();
-}
-
-void ConfigWizard::priv::init_dialog_size()
-{
-    // Clamp the Wizard size based on screen dimensions
-
-    const auto idx = wxDisplay::GetFromWindow(q);
-    wxDisplay display(idx != wxNOT_FOUND ? idx : 0u);
-
-    const auto disp_rect = display.GetClientArea();
-    wxRect window_rect(
-        disp_rect.x + disp_rect.width / 20,
-        disp_rect.y + disp_rect.height / 20,
-        9*disp_rect.width / 10,
-        9*disp_rect.height / 10);
-
-    const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), (only_sla_mode ? page_msla->get_width() : page_fff->get_width()) + 30 * em());    // XXX: magic constant, I found no better solution
-    if (width_hint < window_rect.width) {
-        window_rect.x += (window_rect.width - width_hint) / 2;
-        window_rect.width = width_hint;
-    }
-
-    q->SetSize(window_rect);
-}
-
-void ConfigWizard::priv::load_vendors()
-{
-    bundles = BundleMap::load();
-
-    // Load up the set of vendors / models / variants the user has had enabled up till now
-    AppConfig *app_config = wxGetApp().app_config;
-    if (! app_config->legacy_datadir()) {
-        appconfig_new.set_vendors(*app_config);
-    } else {
-        // In case of legacy datadir, try to guess the preference based on the printer preset files that are present
-        const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer";
-        for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir))
-            if (Slic3r::is_ini_file(dir_entry)) {
-                auto needle = legacy_preset_map.find(dir_entry.path().filename().string());
-                if (needle == legacy_preset_map.end()) { continue; }
-
-                const auto &model = needle->second.first;
-                const auto &variant = needle->second.second;
-                appconfig_new.set_variant("PrusaResearch", model, variant, true);
-            }
-    }
-
-    // Initialize the is_visible flag in printer Presets
-    for (auto &pair : bundles) {
-        pair.second.preset_bundle->load_installed_printers(appconfig_new);
-    }
-
-    // Copy installed filaments and SLA material names from app_config to appconfig_new
-    // while resolving current names of profiles, which were renamed in the meantime.
-    for (PrinterTechnology technology : { ptFFF, ptSLA }) {
-    	const std::string &section_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
-		std::map<std::string, std::string> section_new;
-		if (app_config->has_section(section_name)) {
-			const std::map<std::string, std::string> &section_old = app_config->get_section(section_name);
-            for (const auto& material_name_and_installed : section_old)
-				if (material_name_and_installed.second == "1") {
-					// Material is installed. Resolve it in bundles.
-                    size_t num_found = 0;
-					const std::string &material_name = material_name_and_installed.first;
-				    for (auto &bundle : bundles) {
-				    	const PresetCollection &materials = bundle.second.preset_bundle->materials(technology);
-				    	const Preset           *preset    = materials.find_preset(material_name);
-				    	if (preset == nullptr) {
-				    		// Not found. Maybe the material preset is there, bu it was was renamed?
-							const std::string *new_name = materials.get_preset_name_renamed(material_name);
-							if (new_name != nullptr)
-								preset = materials.find_preset(*new_name);
-				    	}
-                        if (preset != nullptr) {
-                            // Materal preset was found, mark it as installed.
-                            section_new[preset->name] = "1";
-                            ++ num_found;
-                        }
-				    }
-                    if (num_found == 0)
-            	        BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name;
-                    else if (num_found > 1)
-            	        BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found;
-                }
-		}
-        appconfig_new.set_section(section_name, section_new);
-    };
-}
-
-void ConfigWizard::priv::add_page(ConfigWizardPage *page)
-{
-    const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0;
-    hscroll_sizer->Add(page, proportion, wxEXPAND);
-    all_pages.push_back(page);
-}
-
-void ConfigWizard::priv::enable_next(bool enable)
-{
-    btn_next->Enable(enable);
-    btn_finish->Enable(enable);
-}
-
-void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page)
-{
-    switch (start_page) {
-        case ConfigWizard::SP_PRINTERS: 
-            index->go_to(page_fff); 
-            btn_next->SetFocus();
-            break;
-        case ConfigWizard::SP_FILAMENTS:
-            index->go_to(page_filaments);
-            btn_finish->SetFocus();
-            break;
-        case ConfigWizard::SP_MATERIALS:
-            index->go_to(page_sla_materials);
-            btn_finish->SetFocus();
-            break;
-        default:
-            index->go_to(page_welcome);
-            btn_next->SetFocus();
-            break;
-    }
-}
-
-void ConfigWizard::priv::create_3rdparty_pages()
-{
-    for (const auto &pair : bundles) {
-        const VendorProfile *vendor = pair.second.vendor_profile;
-        if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
-
-        bool is_fff_technology = false;
-        bool is_sla_technology = false;
-
-        for (auto& model: vendor->models)
-        {
-            if (!is_fff_technology && model.technology == ptFFF)
-                 is_fff_technology = true;
-            if (!is_sla_technology && model.technology == ptSLA)
-                 is_sla_technology = true;
-        }
-
-        PagePrinters* pageFFF = nullptr;
-        PagePrinters* pageSLA = nullptr;
-
-        if (is_fff_technology) {
-            pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name+" FFF", *vendor, 1, T_FFF);
-            add_page(pageFFF);
-        }
-
-        if (is_sla_technology) {
-            pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name+" MSLA", *vendor, 1, T_SLA);
-            add_page(pageSLA);
-        }
-
-        pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}});
-    }
-}
-
-void ConfigWizard::priv::set_run_reason(RunReason run_reason)
-{
-    this->run_reason = run_reason;
-    for (auto &page : all_pages) {
-        page->set_run_reason(run_reason);
-    }
-}
-
-void ConfigWizard::priv::update_materials(Technology technology)
-{
-    if (any_fff_selected && (technology & T_FFF)) {
-        filaments.clear();
-        aliases_fff.clear();
-        // Iterate filaments in all bundles
-        for (const auto &pair : bundles) {
-            for (const auto &filament : pair.second.preset_bundle->filaments) {
-                // Check if filament is already added
-                if (filaments.containts(&filament))
-					continue;
-                // Iterate printers in all bundles
-                for (const auto &printer : pair.second.preset_bundle->printers) {
-					if (!printer.is_visible || printer.printer_technology() != ptFFF)
-						continue;
-                    // Filter out inapplicable printers
-					if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) {
-						if (!filaments.containts(&filament)) {
-							filaments.push(&filament);
-							if (!filament.alias.empty())
-								aliases_fff[filament.alias].insert(filament.name); 
-						} 
-						filaments.add_printer(&printer);
-                    }
-				}
-				
-            }
-        }
-        // count compatible printers
-        for (const auto& preset : filaments.presets) {
-
-            const auto filter = [preset](const std::pair<std::string, size_t> element) {
-                return preset->alias == element.first;
-            };
-            if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) {
-                continue;
-            }
-            std::vector<size_t> idx_with_same_alias;
-            for (size_t i = 0; i < filaments.presets.size(); ++i) {
-                if (preset->alias == filaments.presets[i]->alias)
-                    idx_with_same_alias.push_back(i);
-            }
-            size_t counter = 0;
-            for (const auto& printer : filaments.printers) {
-                if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF)
-                    continue;
-                bool compatible = false;
-                // Test otrher materials with same alias
-                for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
-                    const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]);
-                    const Preset& prntr = *printer;
-                    if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) {
-                        compatible = true;
-                        break;
-                    }
-                }
-                if (compatible)
-                    counter++;
-            }
-            filaments.compatibility_counter.emplace_back(preset->alias, counter);
-        }
-    }
-
-    if (any_sla_selected && (technology & T_SLA)) {
-        sla_materials.clear();
-        aliases_sla.clear();
-
-        // Iterate SLA materials in all bundles
-        for (const auto &pair : bundles) {
-            for (const auto &material : pair.second.preset_bundle->sla_materials) {
-                // Check if material is already added
-                if (sla_materials.containts(&material))
-                	continue;
-                // Iterate printers in all bundles
-				// For now, we only allow the profiles to be compatible with another profiles inside the same bundle.
-                for (const auto& printer : pair.second.preset_bundle->printers) {
-                    if(!printer.is_visible || printer.printer_technology() != ptSLA)
-                        continue;
-                    // Filter out inapplicable printers
-                    if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
-                        // Check if material is already added
-                        if(!sla_materials.containts(&material)) {
-                            sla_materials.push(&material);
-                            if (!material.alias.empty())
-                                aliases_sla[material.alias].insert(material.name);
-                        }
-                        sla_materials.add_printer(&printer);
-                    }
-                }
-            }
-        }
-        // count compatible printers        
-        for (const auto& preset : sla_materials.presets) {
-            
-            const auto filter = [preset](const std::pair<std::string, size_t> element) {
-                return preset->alias == element.first;
-            };
-            if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) {
-                continue;
-            }
-            std::vector<size_t> idx_with_same_alias;
-            for (size_t i = 0; i < sla_materials.presets.size(); ++i) {
-                if(preset->alias == sla_materials.presets[i]->alias)
-                    idx_with_same_alias.push_back(i);
-            }
-            size_t counter = 0;
-            for (const auto& printer : sla_materials.printers) {
-                if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA)
-                    continue;
-                bool compatible = false;
-                // Test otrher materials with same alias
-                for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
-                    const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]);
-                    const Preset& prntr = *printer;
-                    if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) {
-                        compatible = true;
-                        break;
-                    }
-                }
-                if (compatible)
-                    counter++;
-            }
-            sla_materials.compatibility_counter.emplace_back(preset->alias, counter);
-        }
-    }
-}
-
-void ConfigWizard::priv::on_custom_setup(const bool custom_wanted)
-{
-	custom_printer_selected = custom_wanted;
-    load_pages();
-}
-
-void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt)
-{
-    if (check_sla_selected() != any_sla_selected ||
-        check_fff_selected() != any_fff_selected) {
-        any_fff_selected = check_fff_selected();
-        any_sla_selected = check_sla_selected();
-
-        load_pages();
-    }
-
-    // Update the is_visible flag on relevant printer profiles
-    for (auto &pair : bundles) {
-        if (pair.first != evt.vendor_id) { continue; }
-
-        for (auto &preset : pair.second.preset_bundle->printers) {
-            if (preset.config.opt_string("printer_model") == evt.model_id
-                && preset.config.opt_string("printer_variant") == evt.variant_name) {
-                preset.is_visible = evt.enable;
-            }
-        }
-
-        // When a printer model is picked, but there is no material installed compatible with this printer model,
-        // install default materials for selected printer model silently.
-		check_and_install_missing_materials(page->technology, evt.model_id);
-    }
-
-    if (page->technology & T_FFF) {
-        page_filaments->clear();
-    } else if (page->technology & T_SLA) {
-        page_sla_materials->clear();
-    }
-}
-
-void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology)
-{
-    PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
-    for (const std::string& material : printer_model.default_materials)
-        appconfig_new.set(page_materials->materials->appconfig_section(), material, "1");
-}
-
-void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set<const VendorProfile::PrinterModel*> &printer_models)
-{
-    PageMaterials     *page_materials    = technology & T_FFF ? page_filaments : page_sla_materials;
-    const std::string &appconfig_section = page_materials->materials->appconfig_section();
-    
-    // Following block was unnecessary. Its enough to iterate printer_models once. Not for every vendor printer page. 
-    // Filament is selected on same page for all printers of same technology.
-    /*
-    auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models, technology](PagePrinters *page_printers, Technology technology)
-    {
-        const std::string vendor_id = page_printers->get_vendor_id();
-        for (auto& pair : bundles)
-            if (pair.first == vendor_id)
-                for (const VendorProfile::PrinterModel *printer_model : printer_models)
-                    for (const std::string &material : printer_model->default_materials)
-                        appconfig_new.set(appconfig_section, material, "1");
-    };
-
-    PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla;
-    select_default_materials_for_printer_page(page_printers, technology);
-
-    for (const auto& printer : pages_3rdparty)
-    {
-        page_printers = technology & T_FFF ? printer.second.first : printer.second.second;
-        if (page_printers)
-            select_default_materials_for_printer_page(page_printers, technology);
-    }
-    */
-
-    // Iterate printer_models and select default materials. If none available -> msg to user.
-    std::vector<const VendorProfile::PrinterModel*> models_without_default;
-    for (const VendorProfile::PrinterModel* printer_model : printer_models) {
-        if (printer_model->default_materials.empty()) {
-            models_without_default.emplace_back(printer_model);
-        } else {
-            for (const std::string& material : printer_model->default_materials)
-                appconfig_new.set(appconfig_section, material, "1");
-        }
-    }
-
-    if (!models_without_default.empty()) {
-        std::string printer_names = "\n\n";
-        for (const VendorProfile::PrinterModel* printer_model : models_without_default) {
-            printer_names += printer_model->name + "\n";
-        }
-        printer_names += "\n\n";
-        std::string message = (technology & T_FFF ?
-            GUI::format(_L("Following printer profiles has no default filament: %1%Please select one manually."), printer_names) :
-            GUI::format(_L("Following printer profiles has no default material: %1%Please select one manually."), printer_names));
-        MessageDialog msg(q, message, _L("Notice"), wxOK);
-        msg.ShowModal();
-    }
-
-    update_materials(technology);
-    ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets();
-}
-
-void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install)
-{
-    auto it = pages_3rdparty.find(vendor->id);
-    wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile");
-
-    for (PagePrinters* page : { it->second.first, it->second.second }) 
-        if (page) {
-            if (page->install && !install)
-                page->select_all(false);
-            page->install = install;
-            // if some 3rd vendor is selected, select first printer for them
-            if (install)
-                page->printer_pickers[0]->select_one(0, true);
-            page->Layout();
-        }
-
-    load_pages();
-}
-
-bool ConfigWizard::priv::on_bnt_finish()
-{
-    wxBusyCursor wait;
-    /* When Filaments or Sla Materials pages are activated, 
-     * materials for this pages are automaticaly updated and presets are reloaded.
-     * 
-     * But, if _Finish_ button was clicked without activation of those pages 
-     * (for example, just some printers were added/deleted), 
-     * than last changes wouldn't be updated for filaments/materials.
-     * SO, do that before close of Wizard
-     */
-    update_materials(T_ANY);
-    if (any_fff_selected)
-        page_filaments->reload_presets();
-    if (any_sla_selected)
-        page_sla_materials->reload_presets();
-
-	// theres no need to check that filament is selected if we have only custom printer
-    if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true;
-    // check, that there is selected at least one filament/material
-    return check_and_install_missing_materials(T_ANY);
-}
-
-// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed
-// for each Printer preset of each Printer Model installed.
-//
-// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently.
-// Otherwise the user is quieried whether to install the missing default materials or not.
-// 
-// Return true if the tested Printer Models already had materials installed.
-// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these
-// respective Printer Models or not.
-bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id)
-{
-	// Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle,
-	// which is compatible with it.
-    const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string &section)
-    {
-		const std::map<std::string, std::string> &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map<std::string, std::string>();
-    	std::set<const VendorProfile::PrinterModel*> printer_models_without_material;
-        for (const auto &pair : bundles) {
-        	const PresetCollection &materials = pair.second.preset_bundle->materials(technology);
-        	for (const auto &printer : pair.second.preset_bundle->printers) {
-                if (printer.is_visible && printer.printer_technology() == technology) {
-	            	const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer);
-	            	assert(printer_model != nullptr);
-	            	if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) &&
-	            		printer_models_without_material.find(printer_model) == printer_models_without_material.end()) {
-                    	bool has_material = false;
-                        for (const auto& preset : appconfig_presets) {
-			            	if (preset.second == "1") {
-			            		const Preset *material = materials.find_preset(preset.first, false);
-			            		if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
-				                	has_material = true;
-				                    break;
-				                }
-			                }
-			            }
-			            if (! has_material)
-			            	printer_models_without_material.insert(printer_model);
-			        }
-                }
-            }
-        }
-        assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id);
-        return printer_models_without_material;
-    };
-
-    const auto ask_and_select_default_materials = [this](const wxString &message, const std::set<const VendorProfile::PrinterModel*> &printer_models, Technology technology)
-    {
-        //wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO);
-        MessageDialog msg(q, message, _L("Notice"), wxYES_NO);
-        if (msg.ShowModal() == wxID_YES)
-            select_default_materials_for_printer_models(technology, printer_models);
-    };
-
-    const auto printer_model_list = [](const std::set<const VendorProfile::PrinterModel*> &printer_models) -> wxString {
-    	wxString out;
-    	for (const VendorProfile::PrinterModel *printer_model : printer_models) {
-            wxString name = from_u8(printer_model->name);
-    		out += "\t\t";
-    		out += name;
-    		out += "\n";
-    	}
-    	return out;
-    };
-
-    if (any_fff_selected && (technology & T_FFF)) {
-    	std::set<const VendorProfile::PrinterModel*> printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS);
-    	if (! printer_models_without_material.empty()) {
-			if (only_for_model_id.empty())
-				ask_and_select_default_materials(
-					_L("The following FFF printer models have no filament selected:") +
-					"\n\n\t" +
-					printer_model_list(printer_models_without_material) +
-					"\n\n\t" +
-					_L("Do you want to select default filaments for these FFF printer models?"),
-					printer_models_without_material,
-					T_FFF);
-			else
-				select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF);
-			return false;
-		}
-    }
-
-    if (any_sla_selected && (technology & T_SLA)) {
-    	std::set<const VendorProfile::PrinterModel*> printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS);
-    	if (! printer_models_without_material.empty()) {
-	        if (only_for_model_id.empty())
-	            ask_and_select_default_materials(
-					_L("The following SLA printer models have no materials selected:") +
-	            	"\n\n\t" +
-				   	printer_model_list(printer_models_without_material) +
-					"\n\n\t" +
-					_L("Do you want to select default SLA materials for these printer models?"),
-					printer_models_without_material,
-	            	T_SLA);
-	        else
-				select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA);
-	        return false;
-	    }
-    }
-
-    return true;
-}
-
-static std::set<std::string> get_new_added_presets(const std::map<std::string, std::string>& old_data, const std::map<std::string, std::string>& new_data) 
-{
-    auto get_aliases = [](const std::map<std::string, std::string>& data) {
-        std::set<std::string> old_aliases;
-        for (auto item : data) {
-            const std::string& name = item.first;
-            size_t pos = name.find("@");
-            old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1));
-        }
-        return old_aliases;
-    };
-
-    std::set<std::string> old_aliases = get_aliases(old_data);
-    std::set<std::string> new_aliases = get_aliases(new_data);
-    std::set<std::string> diff;
-    std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin()));
-
-    return diff;
-}
-
-static std::string get_first_added_preset(const std::map<std::string, std::string>& old_data, const std::map<std::string, std::string>& new_data)
-{
-    std::set<std::string> diff = get_new_added_presets(old_data, new_data);
-    if (diff.empty())
-        return std::string();
-    return *diff.begin();
-}
-
-bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes)
-{
-    wxString header, caption = _L("Configuration is edited in ConfigWizard");
-    const auto enabled_vendors = appconfig_new.vendors();
-    const auto enabled_vendors_old = app_config->vendors();
-
-    bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model());
-    PrinterTechnology preferred_pt = ptAny;
-    auto get_preferred_printer_technology = [enabled_vendors, enabled_vendors_old, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) {
-        const auto config = enabled_vendors.find(bundle_name);
-        PrinterTechnology pt = ptAny;
-        if (config != enabled_vendors.end()) {
-            for (const auto& model : bundle.vendor_profile->models) {
-                if (const auto model_it = config->second.find(model.id);
-                    model_it != config->second.end() && model_it->second.size() > 0) {
-                    pt = model.technology;
-                    const auto config_old = enabled_vendors_old.find(bundle_name);
-                    if (config_old == enabled_vendors_old.end() || config_old->second.find(model.id) == config_old->second.end()) {
-                        // if preferred printer model has SLA printer technology it's important to check the model for multi-part state
-                        if (pt == ptSLA && suppress_sla_printer)
-                            continue;
-                        return pt;
-                    }
-
-                    if (const auto model_it_old = config_old->second.find(model.id);
-                        model_it_old == config_old->second.end() || model_it_old->second != model_it->second) {
-                        // if preferred printer model has SLA printer technology it's important to check the model for multi-part state
-                        if (pt == ptSLA && suppress_sla_printer)
-                            continue;
-                        return pt;
-                    }
-                }
-            }
-        }
-        return pt;
-    };
-    // Prusa printers are considered first, then 3rd party.
-    if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle());
-        preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) {
-        for (const auto& bundle : bundles) {
-            if (bundle.second.is_prusa_bundle) { continue; }
-            if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny)
-                continue;
-            else if (preferred_pt == ptAny)
-                preferred_pt = pt;
-            if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)))
-                break;
-        }
-    }
-
-    if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption))
-        return false;
-
-    bool check_unsaved_preset_changes = page_welcome->reset_user_profile();
-    if (check_unsaved_preset_changes)
-        header = _L("All user presets will be deleted.");
-    int act_btns = UnsavedChangesDialog::ActionButtons::KEEP;
-    if (!check_unsaved_preset_changes)
-        act_btns |= UnsavedChangesDialog::ActionButtons::SAVE;
-
-    // Install bundles from resources if needed:
-    std::vector<std::string> install_bundles;
-    for (const auto &pair : bundles) {
-        if (! pair.second.is_in_resources) { continue; }
-
-        if (pair.second.is_prusa_bundle) {
-            // Always install Prusa bundle, because it has a lot of filaments/materials
-            // likely to be referenced by other profiles.
-            install_bundles.emplace_back(pair.first);
-            continue;
-        }
-
-        const auto vendor = enabled_vendors.find(pair.first);
-        if (vendor == enabled_vendors.end()) { continue; }
-
-        size_t size_sum = 0;
-        for (const auto &model : vendor->second) { size_sum += model.second.size(); }
-
-        if (size_sum > 0) {
-            // This vendor needs to be installed
-            install_bundles.emplace_back(pair.first);
-        }
-    }
-    if (!check_unsaved_preset_changes)
-        if ((check_unsaved_preset_changes = install_bundles.size() > 0))
-            header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size());
-
-#ifdef __linux__
-    // Desktop integration on Linux
-    if (page_welcome->integrate_desktop()) 
-        DesktopIntegrationDialog::perform_desktop_integration();
-#endif
-
-    // Decide whether to create snapshot based on run_reason and the reset profile checkbox
-    bool snapshot = true;
-    Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE;
-    switch (run_reason) {
-        case ConfigWizard::RR_DATA_EMPTY:
-            snapshot = false;
-            break;
-        case ConfigWizard::RR_DATA_LEGACY:
-            snapshot = true;
-            break;
-        case ConfigWizard::RR_DATA_INCOMPAT:
-            // In this case snapshot has already been taken by
-            // PresetUpdater with the appropriate reason
-            snapshot = false;
-            break;
-        case ConfigWizard::RR_USER:
-            snapshot = page_welcome->reset_user_profile();
-            snapshot_reason = Snapshot::SNAPSHOT_USER;
-            break;
-    }
-
-    if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Do you want to continue changing the configuration?")))
-        return false;
-
-    if (check_unsaved_preset_changes &&
-        !wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
-        return false;
-
-    if (install_bundles.size() > 0) {
-        // Install bundles from resources.
-        // Don't create snapshot - we've already done that above if applicable.
-        if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
-            return false;
-    } else {
-        BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
-    }
-
-    if (page_welcome->reset_user_profile()) {
-        BOOST_LOG_TRIVIAL(info) << "Resetting user profiles...";
-        preset_bundle->reset(true);
-    }
-
-    std::string preferred_model;
-    std::string preferred_variant;
-    auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) {
-        const auto config = enabled_vendors.find(bundle_name);
-        if (config == enabled_vendors.end())
-            return std::string();
-        for (const auto& model : bundle.vendor_profile->models) {
-            if (const auto model_it = config->second.find(model.id);
-                model_it != config->second.end() && model_it->second.size() > 0 &&
-                preferred_pt == model.technology) {
-                variant = *model_it->second.begin();
-                const auto config_old = enabled_vendors_old.find(bundle_name);
-                if (config_old == enabled_vendors_old.end())
-                    return model.id;
-                const auto model_it_old = config_old->second.find(model.id);
-                if (model_it_old == config_old->second.end())
-                    return model.id;
-                else if (model_it_old->second != model_it->second) {
-                    for (const auto& var : model_it->second)
-                        if (model_it_old->second.find(var) == model_it_old->second.end()) {
-                            variant = var;
-                            return model.id;
-                        }
-                }
-            }
-        }
-        if (!variant.empty())
-            variant.clear();
-        return std::string();
-    };
-    // Prusa printers are considered first, then 3rd party.
-    if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant);
-        preferred_model.empty()) {
-        for (const auto& bundle : bundles) {
-            if (bundle.second.is_prusa_bundle) { continue; }
-            if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant);
-                !preferred_model.empty())
-                    break;
-        }
-    }
-
-    // if unsaved changes was not cheched till this moment
-    if (!check_unsaved_preset_changes) {
-        if ((check_unsaved_preset_changes = !preferred_model.empty())) {
-            header = _L("A new Printer was installed and it will be activated.");
-            if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
-                return false;
-        }
-        else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) {
-            header = _L("Some Printers were uninstalled.");
-            if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
-                return false;
-        }
-    }
-
-    std::string first_added_filament, first_added_sla_material;
-    auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) {
-        if (appconfig_new.has_section(section_name)) {
-            // get first of new added preset names
-            const std::map<std::string, std::string>& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map<std::string, std::string>();
-            first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name));
-        }
-    };
-    get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament);
-    get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material);
-
-    // if unsaved changes was not cheched till this moment
-    if (!check_unsaved_preset_changes) {
-        if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) {
-            header = !first_added_filament.empty() ? 
-                     _L("A new filament was installed and it will be activated.") :
-                     _L("A new SLA material was installed and it will be activated.");
-            if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
-                return false;
-        }
-        else {
-            auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) {
-                return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map<std::string, std::string>()) != appconfig_new.get_section(section_name);
-            };
-            bool is_filaments_changed     = changed(AppConfig::SECTION_FILAMENTS);
-            bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS);
-            if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) {
-                header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled.");
-                if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
-                    return false;
-            }
-        }
-    }
-
-    // apply materials in app_config
-    for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS})
-        app_config->set_section(section_name, appconfig_new.get_section(section_name));
-
-    app_config->set_vendors(appconfig_new);
-
-    app_config->set("notify_release", page_update->version_check ? "all" : "none");
-    app_config->set("preset_update", page_update->preset_update ? "1" : "0");
-    app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
-
-#ifdef _WIN32
-    app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0");
-    app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0");
-//    app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0");
-
-    if (wxGetApp().is_editor()) {
-        if (page_files_association->associate_3mf())
-            wxGetApp().associate_3mf_files();
-        if (page_files_association->associate_stl())
-            wxGetApp().associate_stl_files();
-    }
-//    else {
-//        if (page_files_association->associate_gcode())
-//            wxGetApp().associate_gcode_files();
-//    }
-
-#endif // _WIN32
-
-    page_mode->serialize_mode(app_config);
-
-    if (check_unsaved_preset_changes)
-        preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, 
-                                    {preferred_model, preferred_variant, first_added_filament, first_added_sla_material});
-
-    if (!only_sla_mode && page_custom->custom_wanted()) {
-        // if unsaved changes was not cheched till this moment
-        if (!check_unsaved_preset_changes && 
-            !wxGetApp().check_and_keep_current_preset_changes(caption, _L("Custom printer was installed and it will be activated."), act_btns, &apply_keeped_changes))
-            return false;
-
-        page_firmware->apply_custom_config(*custom_config);
-        page_bed->apply_custom_config(*custom_config);
-        page_diams->apply_custom_config(*custom_config);
-        page_temps->apply_custom_config(*custom_config);
-
-        const std::string profile_name = page_custom->profile_name();
-        preset_bundle->load_config_from_wizard(profile_name, *custom_config);
-    }
-
-    // Update the selections from the compatibilty.
-    preset_bundle->export_selections(*app_config);
-
-    return true;
-}
-void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add)
-{
-    const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla;
-
-    auto update = [this, add](const std::string& s, const std::string& key) {
-    	assert(! s.empty());
-        if (add)
-            appconfig_new.set(s, key, "1");
-        else
-            appconfig_new.erase(s, key); 
-    };
-
-    // add or delete presets had a same alias 
-    auto it = aliases.find(alias_key);
-    if (it != aliases.end())
-        for (const std::string& name : it->second)
-            update(section, name);
-}
-
-bool ConfigWizard::priv::check_fff_selected()
-{
-    bool ret = page_fff->any_selected();
-    for (const auto& printer: pages_3rdparty)
-        if (printer.second.first)               // FFF page
-            ret |= printer.second.first->any_selected();
-    return ret;
-}
-
-bool ConfigWizard::priv::check_sla_selected()
-{
-    bool ret = page_msla->any_selected();
-    for (const auto& printer: pages_3rdparty)
-        if (printer.second.second)               // SLA page
-            ret |= printer.second.second->any_selected();
-    return ret;
-}
-
-
-// Public
-
-ConfigWizard::ConfigWizard(wxWindow *parent)
-    : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
-    , p(new priv(this))
-{
-    this->SetFont(wxGetApp().normal_font());
-
-    p->load_vendors();
-    p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({
-        "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature",
-    }));
-
-    p->index = new ConfigWizardIndex(this);
-
-    auto *vsizer = new wxBoxSizer(wxVERTICAL);
-    auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
-    auto* hline = new StaticLine(this);
-    p->btnsizer = new wxBoxSizer(wxHORIZONTAL);
-
-    // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard  without scrolling.
-    // Later, we compare that to the size of the current screen and set minimum width based on that (see below).
-    p->hscroll = new wxScrolledWindow(this);
-    p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL);
-    p->hscroll->SetSizer(p->hscroll_sizer);
-
-    topsizer->Add(p->index, 0, wxEXPAND);
-    topsizer->AddSpacer(INDEX_MARGIN);
-    topsizer->Add(p->hscroll, 1, wxEXPAND);
-
-    p->btn_sel_all = new wxButton(this, wxID_ANY, _L("Select all standard printers"));
-    p->btnsizer->Add(p->btn_sel_all);
-
-    p->btn_prev = new wxButton(this, wxID_ANY, _L("< &Back"));
-    p->btn_next = new wxButton(this, wxID_ANY, _L("&Next >"));
-    p->btn_finish = new wxButton(this, wxID_APPLY, _L("&Finish"));
-    p->btn_cancel = new wxButton(this, wxID_CANCEL, _L("Cancel"));   // Note: The label needs to be present, otherwise we get accelerator bugs on Mac
-    p->btnsizer->AddStretchSpacer();
-    p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING);
-    p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING);
-    p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING);
-    p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING);
-
-    wxGetApp().UpdateDarkUI(p->btn_sel_all);
-    wxGetApp().UpdateDarkUI(p->btn_prev);
-    wxGetApp().UpdateDarkUI(p->btn_next);
-    wxGetApp().UpdateDarkUI(p->btn_finish);
-    wxGetApp().UpdateDarkUI(p->btn_cancel);
-
-    const auto prusa_it = p->bundles.find("PrusaResearch");
-    wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found");
-    const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile;
-
-    p->add_page(p->page_welcome = new PageWelcome(this));
-
-    
-    p->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF);
-    p->only_sla_mode = !p->page_fff->has_printers;
-    if (!p->only_sla_mode) {
-        p->add_page(p->page_fff);
-        p->page_fff->is_primary_printer_page = true;
-    }
-  
-
-    p->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA);
-    p->add_page(p->page_msla);
-    if (p->only_sla_mode) {
-        p->page_msla->is_primary_printer_page = true;
-    }
-
-    if (!p->only_sla_mode) {
-	    // Pages for 3rd party vendors
-	    p->create_3rdparty_pages();   // Needs to be done _before_ creating PageVendors
-	    p->add_page(p->page_vendors = new PageVendors(this));
-	    p->add_page(p->page_custom = new PageCustom(this));
-        p->custom_printer_selected = p->page_custom->custom_wanted();
-    }
-
-    p->any_sla_selected = p->check_sla_selected();
-    p->any_fff_selected = ! p->only_sla_mode && p->check_fff_selected();
-
-    p->update_materials(T_ANY);
-    if (!p->only_sla_mode)
-        p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
-            _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") ));
-
-    p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials,
-        _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") ));
-
-    
-    p->add_page(p->page_update   = new PageUpdate(this));
-    p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this));
-#ifdef _WIN32
-    p->add_page(p->page_files_association = new PageFilesAssociation(this));
-#endif // _WIN32
-    p->add_page(p->page_mode     = new PageMode(this));
-    p->add_page(p->page_firmware = new PageFirmware(this));
-    p->add_page(p->page_bed      = new PageBedShape(this));
-    p->add_page(p->page_diams    = new PageDiameters(this));
-    p->add_page(p->page_temps    = new PageTemperatures(this));
-
-    p->load_pages();
-    p->index->go_to(size_t{0});
-
-    vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
-    vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING);
-    vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN);
-
-    SetSizer(vsizer);
-    SetSizerAndFit(vsizer);
-
-    // We can now enable scrolling on hscroll
-    p->hscroll->SetScrollRate(30, 30);
-
-    on_window_geometry(this, [this]() {
-        p->init_dialog_size();
-    });
-
-    p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); });
-
-    p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
-    {
-        // check, that there is selected at least one filament/material
-        ConfigWizardPage* active_page = this->p->index->active_page();
-        if (// Leaving the filaments or SLA materials page and 
-        	(active_page == p->page_filaments || active_page == p->page_sla_materials) && 
-        	// some Printer models had no filament or SLA material selected.
-        	! p->check_and_install_missing_materials(dynamic_cast<PageMaterials*>(active_page)->materials->technology))
-        	// In that case don't leave the page and the function above queried the user whether to install default materials.
-            return;
-        this->p->index->go_next();
-    });
-
-    p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
-    {
-        if (p->on_bnt_finish())
-            this->EndModal(wxID_OK);
-    });
-
-    p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) {
-        p->any_sla_selected = true;
-        p->load_pages();
-        p->page_fff->select_all(true, false);
-        p->page_msla->select_all(true, false);
-        p->index->go_to(p->page_mode);
-    });
-
-    p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) {
-        const bool is_last = p->index->active_is_last();
-        p->btn_next->Show(! is_last);
-        if (is_last)
-            p->btn_finish->SetFocus();
-
-        Layout();
-    });
-
-    if (wxLinux_gtk3)
-        this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) {
-            ConfigWizardPage* active_page = p->index->active_page();
-            if (!active_page)
-                return;
-            for (auto page : p->all_pages)
-                if (page != active_page)
-                    page->Hide();
-            // update best size for the dialog after hiding of the non-active pages
-            vsizer->SetSizeHints(this);
-            // set initial dialog size
-            p->init_dialog_size();
-        });
-}
-
-ConfigWizard::~ConfigWizard() {}
-
-bool ConfigWizard::run(RunReason reason, StartPage start_page)
-{
-    BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page;
-
-    GUI_App &app = wxGetApp();
-
-    p->set_run_reason(reason);
-    p->set_start_page(start_page);
-
-    if (ShowModal() == wxID_OK) {
-        bool apply_keeped_changes = false;
-        if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes))
-            return false;
-
-        if (apply_keeped_changes)
-            app.apply_keeped_preset_modifications();
-
-        app.app_config->set_legacy_datadir(false);
-        app.update_mode();
-        app.obj_manipul()->update_ui_from_settings();
-        BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied";
-        return true;
-    } else {
-        BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled";
-        return false;
-    }
-}
-
-const wxString& ConfigWizard::name(const bool from_menu/* = false*/)
-{
-    // A different naming convention is used for the Wizard on Windows & GTK vs. OSX.
-    // Note: Don't call _() macro here.
-    //       This function just return the current name according to the OS.
-    //       Translation is implemented inside GUI_App::add_config_menu()
-#if __APPLE__
-    static const wxString config_wizard_name =  L("Configuration Assistant");
-    static const wxString config_wizard_name_menu = L("Configuration &Assistant");
-#else
-    static const wxString config_wizard_name = L("Configuration Wizard");
-    static const wxString config_wizard_name_menu = L("Configuration &Wizard");
-#endif
-    return from_menu ? config_wizard_name_menu : config_wizard_name;
-}
-
-void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect)
-{
-    p->index->msw_rescale();
-
-    const int em = em_unit();
-
-    msw_buttons_rescale(this, em, { wxID_APPLY, 
-                                    wxID_CANCEL,
-                                    p->btn_sel_all->GetId(),
-                                    p->btn_next->GetId(),
-                                    p->btn_prev->GetId() });
-
-    for (auto printer_picker: p->page_fff->printer_pickers)
-        msw_buttons_rescale(this, em, printer_picker->get_button_indexes());
-
-    p->init_dialog_size();
-
-    Refresh();
-}
-
-void ConfigWizard::on_sys_color_changed()
-{
-    wxGetApp().UpdateDlgDarkUI(this);
-    Refresh();
-}
-
-}
-}
+// FIXME: extract absolute units -> em
+
+#include "ConfigWizard_private.hpp"
+
+#include <algorithm>
+#include <numeric>
+#include <utility>
+#include <unordered_map>
+#include <stdexcept>
+#include <boost/format.hpp>
+#include <boost/log/trivial.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/nowide/convert.hpp>
+
+#include <wx/settings.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/dcclient.h>
+#include <wx/statbmp.h>
+#include <wx/checkbox.h>
+#include <wx/statline.h>
+#include <wx/dataview.h>
+#include <wx/notebook.h>
+#include <wx/listbook.h>
+#include <wx/display.h>
+#include <wx/filefn.h>
+#include <wx/wupdlock.h>
+#include <wx/debug.h>
+
+#ifdef _MSW_DARK_MODE
+#include <wx/msw/dark_mode.h>
+#endif // _MSW_DARK_MODE
+
+#include "libslic3r/Platform.hpp"
+#include "libslic3r/Utils.hpp"
+#include "libslic3r/Config.hpp"
+#include "libslic3r/libslic3r.h"
+#include "libslic3r/Model.hpp"
+#include "libslic3r/Color.hpp"
+#include "GUI.hpp"
+#include "GUI_App.hpp"
+#include "GUI_Utils.hpp"
+#include "GUI_ObjectManipulation.hpp"
+#include "Field.hpp"
+#include "DesktopIntegrationDialog.hpp"
+#include "slic3r/Config/Snapshot.hpp"
+#include "slic3r/Utils/PresetUpdater.hpp"
+#include "format.hpp"
+#include "MsgDialog.hpp"
+#include "UnsavedChangesDialog.hpp"
+
+#if defined(__linux__) && defined(__WXGTK3__)
+#define wxLinux_gtk3 true
+#else
+#define wxLinux_gtk3 false
+#endif //defined(__linux__) && defined(__WXGTK3__)
+
+namespace Slic3r {
+namespace GUI {
+
+
+using Config::Snapshot;
+using Config::SnapshotDB;
+
+
+// Configuration data structures extensions needed for the wizard
+
+bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle)
+{
+    this->preset_bundle = std::make_unique<PresetBundle>();
+    this->is_in_resources = ais_in_resources;
+    this->is_prusa_bundle = ais_prusa_bundle;
+
+    std::string path_string = source_path.string();
+    // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
+    auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(
+        path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
+    UNUSED(config_substitutions);
+    // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
+    assert(config_substitutions.empty());
+    auto first_vendor = preset_bundle->vendors.begin();
+    if (first_vendor == preset_bundle->vendors.end()) {
+        BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
+        return false;
+    }
+    if (presets_loaded == 0) {
+        BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string;
+        return false;
+    } 
+
+    BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded;
+    this->vendor_profile = &first_vendor->second;
+    return true;
+}
+
+Bundle::Bundle(Bundle &&other)
+    : preset_bundle(std::move(other.preset_bundle))
+    , vendor_profile(other.vendor_profile)
+    , is_in_resources(other.is_in_resources)
+    , is_prusa_bundle(other.is_prusa_bundle)
+{
+    other.vendor_profile = nullptr;
+}
+
+BundleMap BundleMap::load()
+{
+    BundleMap res;
+
+    const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred();
+    const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
+
+    auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
+    auto prusa_bundle_rsrc = false;
+    if (! boost::filesystem::exists(prusa_bundle_path)) {
+        prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
+        prusa_bundle_rsrc = true;
+    }
+    {
+        Bundle prusa_bundle;
+        if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true))
+            res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle)); 
+    }
+
+    // Load the other bundles in the datadir/vendor directory
+    // and then additionally from resources/profiles.
+    bool is_in_resources = false;
+    for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) {
+        for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) {
+            if (Slic3r::is_ini_file(dir_entry)) {
+                std::string id = dir_entry.path().stem().string();  // stem() = filename() without the trailing ".ini" part
+
+                // Don't load this bundle if we've already loaded it.
+                if (res.find(id) != res.end()) { continue; }
+
+                Bundle bundle;
+                if (bundle.load(dir_entry.path(), is_in_resources))
+                    res.emplace(std::move(id), std::move(bundle));
+            }
+        }
+
+        is_in_resources = true;
+    }
+
+    return res;
+}
+
+Bundle& BundleMap::prusa_bundle()
+{
+    auto it = find(PresetBundle::PRUSA_BUNDLE);
+    if (it == end()) {
+        throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded");
+    }
+
+    return it->second;
+}
+
+const Bundle& BundleMap::prusa_bundle() const
+{
+    return const_cast<BundleMap*>(this)->prusa_bundle();
+}
+
+
+// Printer model picker GUI control
+
+struct PrinterPickerEvent : public wxEvent
+{
+    std::string vendor_id;
+    std::string model_id;
+    std::string variant_name;
+    bool enable;
+
+    PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable)
+        : wxEvent(winid, eventType)
+        , vendor_id(std::move(vendor_id))
+        , model_id(std::move(model_id))
+        , variant_name(std::move(variant_name))
+        , enable(enable)
+    {}
+
+    virtual wxEvent *Clone() const
+    {
+        return new PrinterPickerEvent(*this);
+    }
+};
+
+wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent);
+
+const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png";
+
+PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter)
+    : wxPanel(parent)
+    , vendor_id(vendor.id)
+    , width(0)
+{
+    wxGetApp().UpdateDarkUI(this);
+    const auto &models = vendor.models;
+
+    auto *sizer = new wxBoxSizer(wxVERTICAL);
+
+    const auto font_title = GetFont().MakeBold().Scaled(1.3f);
+    const auto font_name = GetFont().MakeBold();
+    const auto font_alt_nozzle = GetFont().Scaled(0.9f);
+
+    // wxGrid appends widgets by rows, but we need to construct them in columns.
+    // These vectors are used to hold the elements so that they can be appended in the right order.
+    std::vector<wxStaticText*> titles;
+    std::vector<wxStaticBitmap*> bitmaps;
+    std::vector<wxPanel*> variants_panels;
+
+    int max_row_width = 0;
+    int current_row_width = 0;
+
+    bool is_variants = false;
+
+    for (const auto &model : models) {
+        if (! filter(model)) { continue; }
+
+        wxBitmap bitmap;
+        int bitmap_width = 0;
+        auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool {
+            if (wxFileExists(bitmap_file)) {
+                bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG);
+                bitmap_width = bitmap.GetWidth();
+                return true;
+            }
+            return false;
+        };
+        if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) {
+            if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) {
+                BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead")
+                    % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png")
+                    % vendor.id
+                    % model.id;
+                load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width);
+            }
+        }
+        auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
+        title->SetFont(font_name);
+        const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width);
+        title->Wrap(wrap_width);
+
+        current_row_width += wrap_width;
+        if (titles.size() % max_cols == max_cols - 1) {
+            max_row_width = std::max(max_row_width, current_row_width);
+            current_row_width = 0;
+        }
+
+        titles.push_back(title);
+
+        auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap);
+        bitmaps.push_back(bitmap_widget);
+
+        auto *variants_panel = new wxPanel(this);
+        wxGetApp().UpdateDarkUI(variants_panel);
+        auto *variants_sizer = new wxBoxSizer(wxVERTICAL);
+        variants_panel->SetSizer(variants_sizer);
+        const auto model_id = model.id;
+
+        for (size_t i = 0; i < model.variants.size(); i++) {
+            const auto &variant = model.variants[i];
+
+            const auto label = model.technology == ptFFF
+                ? from_u8((boost::format("%1% %2% %3%") % variant.name % _utf8(L("mm")) % _utf8(L("nozzle"))).str())
+                : from_u8(model.name);
+
+            if (i == 1) {
+                auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _L("Alternate nozzles:"));
+                alt_label->SetFont(font_alt_nozzle);
+                variants_sizer->Add(alt_label, 0, wxBOTTOM, 3);
+                is_variants = true;
+            }
+
+            auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name);
+            i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox);
+
+            const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name);
+            cbox->SetValue(enabled);
+
+            variants_sizer->Add(cbox, 0, wxBOTTOM, 3);
+
+            cbox->Bind(wxEVT_CHECKBOX, [this, cbox](wxCommandEvent &event) {
+                on_checkbox(cbox, event.IsChecked());
+            });
+        }
+
+        variants_panels.push_back(variants_panel);
+    }
+
+    width = std::max(max_row_width, current_row_width);
+
+    const size_t cols = std::min(max_cols, titles.size());
+
+    auto *printer_grid = new wxFlexGridSizer(cols, 0, 20);
+    printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL);
+
+    if (titles.size() > 0) {
+        const size_t odd_items = titles.size() % cols;
+
+        for (size_t i = 0; i < titles.size() - odd_items; i += cols) {
+            for (size_t j = i; j < i + cols; j++) { printer_grid->Add(bitmaps[j], 0, wxBOTTOM, 20); }
+            for (size_t j = i; j < i + cols; j++) { printer_grid->Add(titles[j], 0, wxBOTTOM, 3); }
+            for (size_t j = i; j < i + cols; j++) { printer_grid->Add(variants_panels[j]); }
+
+            // Add separator space to multiliners
+            if (titles.size() > cols) {
+                for (size_t j = i; j < i + cols; j++) { printer_grid->Add(1, 30); }
+            }
+        }
+        if (odd_items > 0) {
+            const size_t rem = titles.size() - odd_items;
+
+            for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(bitmaps[i], 0, wxBOTTOM, 20); }
+            for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); }
+            for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(titles[i], 0, wxBOTTOM, 3); }
+            for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); }
+            for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(variants_panels[i]); }
+        }
+    }
+
+    auto *title_sizer = new wxBoxSizer(wxHORIZONTAL);
+    if (! title.IsEmpty()) {
+        auto *title_widget = new wxStaticText(this, wxID_ANY, title);
+        title_widget->SetFont(font_title);
+        title_sizer->Add(title_widget);
+    }
+    title_sizer->AddStretchSpacer();
+
+    if (titles.size() > 1 || is_variants) {
+        // It only makes sense to add the All / None buttons if there's multiple printers
+        // All Standard button is added when there are more variants for at least one printer
+        auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _L("All standard") : _L("Standard"));
+        auto *sel_all = new wxButton(this, wxID_ANY, _L("All"));
+        auto *sel_none = new wxButton(this, wxID_ANY, _L("None"));
+        if (is_variants) 
+            sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& event) { this->select_all(true, false); });
+        sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, true); });
+        sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); });
+        if (is_variants) 
+            title_sizer->Add(sel_all_std, 0, wxRIGHT, BTN_SPACING);
+        title_sizer->Add(sel_all, 0, wxRIGHT, BTN_SPACING);
+        title_sizer->Add(sel_none);
+
+        wxGetApp().UpdateDarkUI(sel_all_std);
+        wxGetApp().UpdateDarkUI(sel_all);
+        wxGetApp().UpdateDarkUI(sel_none);
+
+        // fill button indexes used later for buttons rescaling
+        if (is_variants)
+            m_button_indexes = { sel_all_std->GetId(), sel_all->GetId(), sel_none->GetId() };
+        else {
+            sel_all_std->Destroy();
+            m_button_indexes = { sel_all->GetId(), sel_none->GetId() };
+        }
+    }
+
+    sizer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, BTN_SPACING);
+    sizer->Add(printer_grid);
+
+    SetSizer(sizer);
+}
+
+PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig)
+    : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; })
+{}
+
+void PrinterPicker::select_all(bool select, bool alternates)
+{
+    for (const auto &cb : cboxes) {
+        if (cb->GetValue() != select) {
+            cb->SetValue(select);
+            on_checkbox(cb, select);
+        }
+    }
+
+    if (! select) { alternates = false; }
+
+    for (const auto &cb : cboxes_alt) {
+        if (cb->GetValue() != alternates) {
+            cb->SetValue(alternates);
+            on_checkbox(cb, alternates);
+        }
+    }
+}
+
+void PrinterPicker::select_one(size_t i, bool select)
+{
+    if (i < cboxes.size() && cboxes[i]->GetValue() != select) {
+        cboxes[i]->SetValue(select);
+        on_checkbox(cboxes[i], select);
+    }
+}
+
+bool PrinterPicker::any_selected() const
+{
+    for (const auto &cb : cboxes) {
+        if (cb->GetValue()) { return true; }
+    }
+
+    for (const auto &cb : cboxes_alt) {
+        if (cb->GetValue()) { return true; }
+    }
+
+    return false;
+}
+
+std::set<std::string> PrinterPicker::get_selected_models() const 
+{
+    std::set<std::string> ret_set;
+
+    for (const auto& cb : cboxes)
+        if (cb->GetValue())
+            ret_set.emplace(cb->model);
+
+    for (const auto& cb : cboxes_alt)
+        if (cb->GetValue())
+            ret_set.emplace(cb->model);
+
+    return ret_set;
+}
+
+void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked)
+{
+    PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked);
+    AddPendingEvent(evt);
+}
+
+
+// Wizard page base
+
+ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent)
+    : wxPanel(parent->p->hscroll)
+    , parent(parent)
+    , shortname(std::move(shortname))
+    , indent(indent)
+{
+    wxGetApp().UpdateDarkUI(this);
+
+    auto *sizer = new wxBoxSizer(wxVERTICAL);
+
+    auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
+    const auto font = GetFont().MakeBold().Scaled(1.5);
+    text->SetFont(font);
+    sizer->Add(text, 0, wxALIGN_LEFT, 0);
+    sizer->AddSpacer(10);
+
+    content = new wxBoxSizer(wxVERTICAL);
+    sizer->Add(content, 1, wxEXPAND);
+
+    SetSizer(sizer);
+
+    // There is strange layout on Linux with GTK3, 
+    // see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861
+    // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages 
+    if (!wxLinux_gtk3)
+        this->Hide();
+
+    Bind(wxEVT_SIZE, [this](wxSizeEvent &event) {
+        this->Layout();
+        event.Skip();
+    });
+}
+
+ConfigWizardPage::~ConfigWizardPage() {}
+
+wxStaticText* ConfigWizardPage::append_text(wxString text)
+{
+    auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
+    widget->Wrap(WRAP_WIDTH);
+    widget->SetMinSize(wxSize(WRAP_WIDTH, -1));
+    append(widget);
+    return widget;
+}
+
+void ConfigWizardPage::append_spacer(int space)
+{
+    // FIXME: scaling
+    content->AddSpacer(space);
+}
+
+// Wizard pages
+
+PageWelcome::PageWelcome(ConfigWizard *parent)
+    : ConfigWizardPage(parent, from_u8((boost::format(
+#ifdef __APPLE__
+            _utf8(L("Welcome to the %s Configuration Assistant"))
+#else
+            _utf8(L("Welcome to the %s Configuration Wizard"))
+#endif
+            ) % SLIC3R_APP_NAME).str()), _L("Welcome"))
+    , welcome_text(append_text(from_u8((boost::format(
+        _utf8(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")))
+        % SLIC3R_APP_NAME
+        % _utf8(ConfigWizard::name())).str())
+    ))
+    , cbox_reset(append(
+        new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)"))
+    ))
+    , cbox_integrate(append(
+        new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system)."))
+    ))
+{
+    welcome_text->Hide();
+    cbox_reset->Hide();
+    cbox_integrate->Hide();    
+}
+
+void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
+{
+    const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY;
+    welcome_text->Show(data_empty);
+    cbox_reset->Show(!data_empty);
+#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
+    if (!DesktopIntegrationDialog::is_integrated())
+        cbox_integrate->Show(true);
+    else
+        cbox_integrate->Hide();
+#else
+    cbox_integrate->Hide();
+#endif
+}
+
+
+PagePrinters::PagePrinters(ConfigWizard *parent,
+    wxString title,
+    wxString shortname,
+    const VendorProfile &vendor,
+    unsigned indent,
+    Technology technology)
+    : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent)
+    , technology(technology)
+    , install(false)   // only used for 3rd party vendors
+{
+    enum {
+        COL_SIZE = 200,
+    };
+
+    AppConfig *appconfig = &this->wizard_p()->appconfig_new;
+
+    const auto families = vendor.families();
+    for (const auto &family : families) {
+        const auto filter = [&](const VendorProfile::PrinterModel &model) {
+            return ((model.technology == ptFFF && technology & T_FFF)
+                    || (model.technology == ptSLA && technology & T_SLA))
+                && model.family == family;
+        };
+
+        if (std::find_if(vendor.models.begin(), vendor.models.end(), filter) == vendor.models.end()) {
+            continue;
+        }
+
+        const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str());
+        auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter);
+
+        picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) {
+            appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
+            wizard_p()->on_printer_pick(this, evt);
+        });
+
+        append(new StaticLine(this));
+
+        append(picker);
+        printer_pickers.push_back(picker);
+        has_printers = true;
+    }
+
+}
+
+void PagePrinters::select_all(bool select, bool alternates)
+{
+    for (auto picker : printer_pickers) {
+        picker->select_all(select, alternates);
+    }
+}
+
+int PagePrinters::get_width() const
+{
+    return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0,
+        [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); });
+}
+
+bool PagePrinters::any_selected() const
+{
+    for (const auto *picker : printer_pickers) {
+        if (picker->any_selected()) { return true; }
+    }
+
+    return false;
+}
+
+std::set<std::string> PagePrinters::get_selected_models()
+{
+    std::set<std::string> ret_set;
+
+    for (const auto *picker : printer_pickers)
+    {
+        std::set<std::string> tmp_models = picker->get_selected_models();
+        ret_set.insert(tmp_models.begin(), tmp_models.end());
+    }
+
+    return ret_set;
+}
+
+void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason)
+{
+    if (is_primary_printer_page
+        && (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY)
+        && printer_pickers.size() > 0 
+        && printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) {
+        printer_pickers[0]->select_one(0, true);
+    }
+}
+
+
+const std::string PageMaterials::EMPTY;
+
+PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name)
+    : ConfigWizardPage(parent, std::move(title), std::move(shortname))
+    , materials(materials)
+	, list_printer(new  StringList(this, wxLB_MULTIPLE))
+    , list_type(new StringList(this))
+    , list_vendor(new StringList(this))
+    , list_profile(new PresetList(this))
+{
+    append_spacer(VERTICAL_SPACING);
+
+    const int em = parent->em_unit();
+    const int list_h = 30*em;
+
+
+	list_printer->SetMinSize(wxSize(23*em, list_h));
+    list_type->SetMinSize(wxSize(13*em, list_h));
+    list_vendor->SetMinSize(wxSize(13*em, list_h));
+    list_profile->SetMinSize(wxSize(23*em, list_h));
+
+
+
+    grid = new wxFlexGridSizer(4, em/2, em);
+    grid->AddGrowableCol(3, 1);
+    grid->AddGrowableRow(1, 1);
+
+	grid->Add(new wxStaticText(this, wxID_ANY, _L("Printer:")));
+    grid->Add(new wxStaticText(this, wxID_ANY, list1name));
+    grid->Add(new wxStaticText(this, wxID_ANY, _L("Vendor:")));
+    grid->Add(new wxStaticText(this, wxID_ANY, _L("Profile:")));
+
+	grid->Add(list_printer, 0, wxEXPAND);
+    grid->Add(list_type, 0, wxEXPAND);
+    grid->Add(list_vendor, 0, wxEXPAND);
+    grid->Add(list_profile, 1, wxEXPAND);
+
+    auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL);
+    auto *sel_all = new wxButton(this, wxID_ANY, _L("All"));
+    auto *sel_none = new wxButton(this, wxID_ANY, _L("None"));
+    btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2);
+    btn_sizer->Add(sel_none);
+
+    wxGetApp().UpdateDarkUI(list_printer);
+    wxGetApp().UpdateDarkUI(list_type);
+    wxGetApp().UpdateDarkUI(list_vendor);
+    wxGetApp().UpdateDarkUI(sel_all);
+    wxGetApp().UpdateDarkUI(sel_none);
+
+    grid->Add(new wxBoxSizer(wxHORIZONTAL));
+    grid->Add(new wxBoxSizer(wxHORIZONTAL));
+    grid->Add(new wxBoxSizer(wxHORIZONTAL));
+    grid->Add(btn_sizer, 0, wxALIGN_RIGHT);
+
+    append(grid, 1, wxEXPAND);
+
+    append_spacer(VERTICAL_SPACING);
+
+    html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition,
+        wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO);
+    append(html_window, 0, wxEXPAND);
+
+	list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) {
+		update_lists(list_type->GetSelection(), list_vendor->GetSelection(), evt.GetInt());
+		});
+    list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
+        update_lists(list_type->GetSelection(), list_vendor->GetSelection());
+    });
+    list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
+        update_lists(list_type->GetSelection(), list_vendor->GetSelection());
+    });
+
+    list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); });
+    list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); });
+
+    sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); });
+    sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); });
+    /*
+    Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();});
+
+    list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); });
+    list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); });
+    list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); });
+    */
+    reload_presets();
+    set_compatible_printers_html_window(std::vector<std::string>(), false);
+}
+void PageMaterials::on_paint()
+{
+}
+void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt)
+{
+    const wxClientDC dc(list_profile);
+    const wxPoint pos = evt.GetLogicalPosition(dc);
+    int item = list_profile->HitTest(pos);
+    on_material_hovered(item);
+}
+void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt)
+{}
+void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt)
+{
+    on_material_hovered(-1);
+}
+void PageMaterials::reload_presets()
+{
+    clear();
+
+	list_printer->append(_L("(All)"), &EMPTY);
+    //list_printer->SetLabelMarkup("<b>bald</b>");
+	for (const Preset* printer : materials->printers) {
+		list_printer->append(printer->name, &printer->name);
+	}
+    sort_list_data(list_printer, true, false);
+    if (list_printer->GetCount() > 0) {
+        list_printer->SetSelection(0);
+        sel_printers_prev.Clear();
+        sel_type_prev = wxNOT_FOUND;
+        sel_vendor_prev = wxNOT_FOUND;
+        update_lists(0, 0, 0);
+    }
+
+    presets_loaded = true;
+}
+
+void PageMaterials::set_compatible_printers_html_window(const std::vector<std::string>& printer_names, bool all_printers)
+{
+    const auto bgr_clr = 
+#if defined(__APPLE__)
+        html_window->GetParent()->GetBackgroundColour();
+#else 
+#if defined(_WIN32)
+        wxGetApp().get_window_default_clr();
+#else
+        wxSystemSettings::GetColour(wxSYS_COLOUR_MENU);
+#endif
+#endif
+    const auto text_clr = wxGetApp().get_label_clr_default();
+    const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
+    const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
+    wxString first_line = format_wxstr(_L("%1% marked with <b>*</b> are <b>not</b> compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials"));
+    wxString text;
+    if (all_printers) {
+        wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material"));
+        text = wxString::Format(
+            "<html>"
+            "<style>"
+            "table{border-spacing: 1px;}"
+            "</style>"
+            "<body bgcolor= %s>"
+            "<font color=%s>"
+            "<font size=\"3\">"
+            "%s<br /><br />%s"
+            "</font>"
+            "</font>"
+            "</body>"
+            "</html>"
+            , bgr_clr_str
+            , text_clr_str
+            , first_line
+            , second_line
+            );
+    } else {
+        wxString second_line;
+        if (!printer_names.empty())
+            second_line = (materials->technology == T_FFF ?
+                          _L("Only the following installed printers are compatible with the selected filaments") :
+                          _L("Only the following installed printers are compatible with the selected SLA materials")) + ":";
+        text = wxString::Format(
+            "<html>"
+            "<style>"
+            "table{border-spacing: 1px;}"
+            "</style>"
+            "<body bgcolor= %s>"
+            "<font color=%s>"
+            "<font size=\"3\">"
+            "%s<br /><br />%s"
+            "<table>"
+            "<tr>"
+            , bgr_clr_str
+            , text_clr_str
+            , first_line
+            , second_line);
+        for (size_t i = 0; i < printer_names.size(); ++i)
+        {
+            text += wxString::Format("<td>%s</td>", boost::nowide::widen(printer_names[i]));
+            if (i % 3 == 2) {
+                text += wxString::Format(
+                    "</tr>"
+                    "<tr>");
+            }
+        }
+        text += wxString::Format(
+            "</tr>"
+            "</table>"
+            "</font>"
+            "</font>"
+            "</body>"
+            "</html>"
+        );
+    }
+   
+    wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this));
+    const int fs = font.GetPointSize();
+    int size[] = { fs,fs,fs,fs,fs,fs,fs };
+    html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
+    html_window->SetPage(text);
+}
+
+void PageMaterials::clear_compatible_printers_label()
+{
+    set_compatible_printers_html_window(std::vector<std::string>(), false);
+}
+
+void PageMaterials::on_material_hovered(int sel_material)
+{
+
+}
+
+void PageMaterials::on_material_highlighted(int sel_material)
+{
+    if (sel_material == last_hovered_item)
+        return;
+    if (sel_material == -1) {
+        clear_compatible_printers_label();
+        return;
+    }
+    last_hovered_item = sel_material;
+    std::vector<std::string> tabs;
+    tabs.push_back(std::string());
+    tabs.push_back(std::string());
+    tabs.push_back(std::string());
+    //selected material string
+    std::string material_name = list_profile->get_data(sel_material);
+    // get material preset
+    const std::vector<const Preset*> matching_materials = materials->get_presets_by_alias(material_name);
+    if (matching_materials.empty())
+    {
+        clear_compatible_printers_label();
+        return;
+    }
+    //find matching printers
+    std::vector<std::string> names;
+    for (const Preset* printer : materials->printers) {
+        for (const Preset* material : matching_materials) {
+            if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) {
+                names.push_back(printer->name);
+                break;
+            }
+        }
+    }
+    set_compatible_printers_html_window(names, names.size() == materials->printers.size());
+}
+
+void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected_printer/* = -1*/)
+{
+	wxWindowUpdateLocker freeze_guard(this);
+	(void)freeze_guard;
+
+	wxArrayInt sel_printers;
+	int sel_printers_count = list_printer->GetSelections(sel_printers);
+
+    // Does our wxWidgets version support operator== for wxArrayInt ?
+    // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614
+#if wxCHECK_VERSION(3, 1, 1)
+    if (sel_printers != sel_printers_prev) {
+#else
+    auto are_equal = [](const wxArrayInt& arr_first, const wxArrayInt& arr_second) {
+        if (arr_first.GetCount() != arr_second.GetCount())
+            return false;
+        for (size_t i = 0; i < arr_first.GetCount(); i++)
+            if (arr_first[i] != arr_second[i])
+                return false;
+        return true;
+    };
+    if (!are_equal(sel_printers, sel_printers_prev)) {
+#endif
+
+        // Refresh type list
+		list_type->Clear();
+		list_type->append(_L("(All)"), &EMPTY);
+		if (sel_printers_count > 0) {
+            // If all is selected with other printers
+            // unselect "all" or all printers depending on last value
+            if (sel_printers[0] == 0 && sel_printers_count > 1) {
+                if (last_selected_printer == 0) {
+                    list_printer->SetSelection(wxNOT_FOUND);
+                    list_printer->SetSelection(0);
+                } else {
+                    list_printer->SetSelection(0, false);
+                    sel_printers_count = list_printer->GetSelections(sel_printers);
+                }
+            }
+			if (sel_printers[0] != 0) {
+                for (int i = 0; i < sel_printers_count; i++) {
+					const std::string& printer_name = list_printer->get_data(sel_printers[i]);
+					const Preset* printer = nullptr;
+					for (const Preset* it : materials->printers) {
+						if (it->name == printer_name) {
+							printer = it;
+							break;
+						}
+					}
+					materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) {
+						const std::string& type = this->materials->get_type(p);
+						if (list_type->find(type) == wxNOT_FOUND) {
+							list_type->append(type, &type);
+						}
+						});
+				}
+			} else {
+                //clear selection except "ALL"
+                list_printer->SetSelection(wxNOT_FOUND);
+                list_printer->SetSelection(0);
+                sel_printers_count = list_printer->GetSelections(sel_printers);
+
+				materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) {
+					const std::string& type = this->materials->get_type(p);
+					if (list_type->find(type) == wxNOT_FOUND) {
+						list_type->append(type, &type);
+					}
+					});
+			}
+            sort_list_data(list_type, true, true);
+		}
+
+		sel_printers_prev = sel_printers;
+		sel_type = 0;
+		sel_type_prev = wxNOT_FOUND;
+		list_type->SetSelection(sel_type);
+		list_profile->Clear();
+	}
+	
+	if (sel_type != sel_type_prev) {
+		// Refresh vendor list
+
+		// XXX: The vendor list is created with quadratic complexity here,
+		// but the number of vendors is going to be very small this shouldn't be a problem.
+
+		list_vendor->Clear();
+		list_vendor->append(_L("(All)"), &EMPTY);
+		if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) {
+			const std::string& type = list_type->get_data(sel_type);
+			// find printer preset
+            for (int i = 0; i < sel_printers_count; i++) {
+				const std::string& printer_name = list_printer->get_data(sel_printers[i]);
+				const Preset* printer = nullptr;
+				for (const Preset* it : materials->printers) {
+					if (it->name == printer_name) {
+						printer = it;
+						break;
+					}
+				}
+				materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) {
+					const std::string& vendor = this->materials->get_vendor(p);
+					if (list_vendor->find(vendor) == wxNOT_FOUND) {
+						list_vendor->append(vendor, &vendor);
+					}
+					});
+			}
+            sort_list_data(list_vendor, true, false);
+		}
+
+		sel_type_prev = sel_type;
+		sel_vendor = 0;
+		sel_vendor_prev = wxNOT_FOUND;
+		list_vendor->SetSelection(sel_vendor);
+		list_profile->Clear();
+	}
+         
+	if (sel_vendor != sel_vendor_prev) {
+		// Refresh material list
+
+		list_profile->Clear();
+        clear_compatible_printers_label();
+		if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) {
+			const std::string& type = list_type->get_data(sel_type);
+			const std::string& vendor = list_vendor->get_data(sel_vendor);
+			// finst printer preset
+            std::vector<ProfilePrintData> to_list;
+            for (int i = 0; i < sel_printers_count; i++) {
+				const std::string& printer_name = list_printer->get_data(sel_printers[i]);
+				const Preset* printer = nullptr;
+				for (const Preset* it : materials->printers) {
+					if (it->name == printer_name) {
+						printer = it;
+						break;
+					}
+				}
+
+				materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) {
+					const std::string& section = materials->appconfig_section();
+                    bool checked = wizard_p()->appconfig_new.has(section, p->name);
+                    bool was_checked = false;
+
+                    int cur_i = list_profile->find(p->alias);
+                    if (cur_i == wxNOT_FOUND) {
+                        cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias);
+                        to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked);
+                    }
+                    else {
+                        was_checked = list_profile->IsChecked(cur_i);
+                        to_list[cur_i].checked = checked || was_checked;
+                    }
+                    list_profile->Check(cur_i, checked || was_checked);
+
+					/* Update preset selection in config.
+					 * If one preset from aliases bundle is selected,
+					 * than mark all presets with this aliases as selected
+					 * */
+					if (checked && !was_checked)
+						wizard_p()->update_presets_in_config(section, p->alias, true);
+					else if (!checked && was_checked)
+						wizard_p()->appconfig_new.set(section, p->name, "1");
+					});
+			}
+            sort_list_data(list_profile, to_list);
+		}
+
+		sel_vendor_prev = sel_vendor;
+	}
+    wxGetApp().UpdateDarkUI(list_profile);
+}
+
+void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering)
+{
+// get data from list
+// sort data
+// first should be <all>
+// then prusa profiles
+// then the rest
+// in alphabetical order
+    
+    std::vector<std::reference_wrapper<const std::string>> prusa_profiles;
+    std::vector<std::reference_wrapper<const std::string>> other_profiles;
+    for (int i = 0 ; i < list->size(); ++i) {
+        const std::string& data = list->get_data(i);
+        if (data == EMPTY) // do not sort <all> item
+            continue;
+        if (!material_type_ordering && data.find("Prusa") != std::string::npos)
+            prusa_profiles.push_back(data);
+        else 
+            other_profiles.push_back(data);
+    }
+    if(material_type_ordering) {
+        
+        const ConfigOptionDef* def = print_config_def.get("filament_type");
+        std::vector<std::string>enum_values = def->enum_values;
+        size_t end_of_sorted = 0;
+        for (size_t vals = 0; vals < enum_values.size(); vals++) {
+            for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++)
+            {
+                // find instead compare because PET vs PETG
+                if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) {
+                    //swap
+                    if(profs != end_of_sorted) {
+                        std::reference_wrapper<const std::string> aux = other_profiles[end_of_sorted];
+                        other_profiles[end_of_sorted] = other_profiles[profs];
+                        other_profiles[profs] = aux;
+                    }
+                    end_of_sorted++;
+                    break;
+                }
+            }
+        }
+    } else {
+        std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper<const std::string> a, std::reference_wrapper<const std::string> b) {
+            return a.get() < b.get();
+            });
+        std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper<const std::string> a, std::reference_wrapper<const std::string> b) {
+            return a.get() < b.get();
+            });
+    }
+    
+    list->Clear();
+    if (add_All_item)
+        list->append(_L("(All)"), &EMPTY);
+    for (const auto& item : prusa_profiles)
+        list->append(item, &const_cast<std::string&>(item.get()));
+    for (const auto& item : other_profiles)
+        list->append(item, &const_cast<std::string&>(item.get()));
+}     
+
+void PageMaterials::sort_list_data(PresetList* list, const std::vector<ProfilePrintData>& data)
+{
+    // sort data
+    // then prusa profiles
+    // then the rest
+    // in alphabetical order
+    std::vector<ProfilePrintData> prusa_profiles;
+    std::vector<ProfilePrintData> other_profiles;
+    //for (int i = 0; i < data.size(); ++i) {
+    for (const auto& item : data) {
+        const std::string& name = item.name;
+        if (name.find("Prusa") != std::string::npos)
+            prusa_profiles.emplace_back(item);
+        else
+            other_profiles.emplace_back(item);
+    }
+    std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) {
+        return a.name.get() < b.name.get();
+        });
+    std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) {
+        return a.name.get() < b.name.get();
+        });
+    list->Clear();
+    for (size_t i = 0; i < prusa_profiles.size(); ++i) {
+        list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast<std::string&>(prusa_profiles[i].name.get()));
+        list->Check(i, prusa_profiles[i].checked);
+    }
+    for (size_t i = 0; i < other_profiles.size(); ++i) {
+        list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast<std::string&>(other_profiles[i].name.get()));
+        list->Check(i + prusa_profiles.size(), other_profiles[i].checked);
+    }
+}
+
+void PageMaterials::select_material(int i)
+{
+    const bool checked = list_profile->IsChecked(i);
+
+    const std::string& alias_key = list_profile->get_data(i);
+    wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked);
+}
+
+void PageMaterials::select_all(bool select)
+{
+    wxWindowUpdateLocker freeze_guard(this);
+    (void)freeze_guard;
+
+    for (unsigned i = 0; i < list_profile->GetCount(); i++) {
+        const bool current = list_profile->IsChecked(i);
+        if (current != select) {
+            list_profile->Check(i, select);
+            select_material(i);
+        }
+    }
+}
+
+void PageMaterials::clear()
+{
+	list_printer->Clear();
+    list_type->Clear();
+    list_vendor->Clear();
+    list_profile->Clear();
+	sel_printers_prev.Clear();
+    sel_type_prev = wxNOT_FOUND;
+    sel_vendor_prev = wxNOT_FOUND;
+    presets_loaded = false;
+}
+
+void PageMaterials::on_activate()
+{
+    if (! presets_loaded) {
+        wizard_p()->update_materials(materials->technology);
+        reload_presets();
+    }
+    first_paint = true;
+}
+
+
+const char *PageCustom::default_profile_name = "My Settings";
+
+PageCustom::PageCustom(ConfigWizard *parent)
+    : ConfigWizardPage(parent, _L("Custom Printer Setup"), _L("Custom Printer"))
+{
+    cb_custom = new wxCheckBox(this, wxID_ANY, _L("Define a custom printer profile"));
+    tc_profile_name = new wxTextCtrl(this, wxID_ANY, default_profile_name);
+    auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:"));
+
+    wxGetApp().UpdateDarkUI(tc_profile_name);
+
+    tc_profile_name->Enable(false);
+    tc_profile_name->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent &evt) {
+        if (tc_profile_name->GetValue().IsEmpty()) {
+            if (profile_name_prev.IsEmpty()) { tc_profile_name->SetValue(default_profile_name); }
+            else { tc_profile_name->SetValue(profile_name_prev); }
+        } else {
+            profile_name_prev = tc_profile_name->GetValue();
+        }
+        evt.Skip();
+    });
+
+    cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) {
+        tc_profile_name->Enable(custom_wanted());
+        wizard_p()->on_custom_setup(custom_wanted());
+		
+    });
+
+    append(cb_custom);
+    append(label);
+    append(tc_profile_name);
+}
+
+PageUpdate::PageUpdate(ConfigWizard *parent)
+    : ConfigWizardPage(parent, _L("Automatic updates"), _L("Updates"))
+    , version_check(true)
+    , preset_update(true)
+{
+    const AppConfig *app_config = wxGetApp().app_config;
+    auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
+    boldfont.SetWeight(wxFONTWEIGHT_BOLD);
+
+    auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates"));
+    box_slic3r->SetValue(app_config->get("notify_release") != "none");
+    append(box_slic3r);
+    append_text(wxString::Format(_L(
+        "If enabled, %s checks for new application versions online. When a new version becomes available, "
+         "a notification is displayed at the next application startup (never during program usage). "
+         "This is only a notification mechanisms, no automatic installation is done."), SLIC3R_APP_NAME));
+
+    append_spacer(VERTICAL_SPACING);
+
+    auto *box_presets = new wxCheckBox(this, wxID_ANY, _L("Update built-in Presets automatically"));
+    box_presets->SetValue(app_config->get("preset_update") == "1");
+    append(box_presets);
+    append_text(wxString::Format(_L(
+        "If enabled, %s 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."), SLIC3R_APP_NAME));
+    const auto text_bold = _L("Updates are never applied without user's consent and never overwrite user's customized settings.");
+    auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold);
+    label_bold->SetFont(boldfont);
+    label_bold->Wrap(WRAP_WIDTH);
+    append(label_bold);
+    append_text(_L("Additionally a backup snapshot of the whole configuration is created before an update is applied."));
+
+    box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); });
+    box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
+}
+
+PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent)
+    : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk"))
+    , full_pathnames(false)
+{
+    auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _L("Export full pathnames of models and parts sources into 3mf and amf files"));
+    box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1");
+    append(box_pathnames);
+    append_text(_L(
+        "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n"
+        "If not enabled, the Reload from disk command will ask to select each file using an open file dialog."
+    ));
+
+    box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); });
+}
+
+#ifdef _WIN32
+PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent)
+    : ConfigWizardPage(parent, _L("Files association"), _L("Files association"))
+{
+    cb_3mf = new wxCheckBox(this, wxID_ANY, _L("Associate .3mf files to PrusaSlicer"));
+    cb_stl = new wxCheckBox(this, wxID_ANY, _L("Associate .stl files to PrusaSlicer"));
+//    cb_gcode = new wxCheckBox(this, wxID_ANY, _L("Associate .gcode files to PrusaSlicer G-code Viewer"));
+
+    append(cb_3mf);
+    append(cb_stl);
+//    append(cb_gcode);
+}
+#endif // _WIN32
+
+PageMode::PageMode(ConfigWizard *parent)
+    : ConfigWizardPage(parent, _L("View mode"), _L("View mode"))
+{
+    append_text(_L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n"
+        "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. "
+        "The other two offer progressively more sophisticated fine-tuning, "
+        "they are suitable for advanced and expert users, respectively."));
+
+    radio_simple = new wxRadioButton(this, wxID_ANY, _L("Simple mode"));
+    radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode"));
+    radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode"));
+
+    std::string mode { "simple" };
+    wxGetApp().app_config->get("", "view_mode", mode);
+
+    if (mode == "advanced") { radio_advanced->SetValue(true); }
+    else if (mode == "expert") { radio_expert->SetValue(true); }
+    else { radio_simple->SetValue(true); }
+
+    append(radio_simple);
+    append(radio_advanced);
+    append(radio_expert);
+
+    append_text("\n" + _L("The size of the object can be specified in inches"));
+    check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches"));
+    check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1");
+    append(check_inch);
+
+    on_activate();
+}
+
+void PageMode::serialize_mode(AppConfig *app_config) const
+{
+    std::string mode = "";
+
+    if (radio_simple->GetValue()) { mode = "simple"; }
+    if (radio_advanced->GetValue()) { mode = "advanced"; }
+    if (radio_expert->GetValue()) { mode = "expert"; }
+
+    app_config->set("view_mode", mode);
+    app_config->set("use_inches", check_inch->GetValue() ? "1" : "0");
+}
+
+PageVendors::PageVendors(ConfigWizard *parent)
+    : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors"))
+{
+    const AppConfig &appconfig = this->wizard_p()->appconfig_new;
+
+    append_text(wxString::Format(_L("Pick another vendor supported by %s"), SLIC3R_APP_NAME) + ":");
+
+    auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
+    boldfont.SetWeight(wxFONTWEIGHT_BOLD);
+
+    for (const auto &pair : wizard_p()->bundles) {
+        const VendorProfile *vendor = pair.second.vendor_profile;
+        if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
+
+        auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name);
+        cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) {
+            wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked());
+        });
+
+        const auto &vendors = appconfig.vendors();
+        const bool enabled = vendors.find(pair.first) != vendors.end();
+        if (enabled) {
+            cbox->SetValue(true);
+
+            auto pages = wizard_p()->pages_3rdparty.find(vendor->id);
+            wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created");
+
+            for (PagePrinters* page : { pages->second.first, pages->second.second })
+                if (page) page->install = true;
+        }
+
+        append(cbox);
+    }
+}
+
+PageFirmware::PageFirmware(ConfigWizard *parent)
+    : ConfigWizardPage(parent, _L("Firmware Type"), _L("Firmware"), 1)
+    , gcode_opt(*print_config_def.get("gcode_flavor"))
+    , gcode_picker(nullptr)
+{
+    append_text(_L("Choose the type of firmware used by your printer."));
+    append_text(_(gcode_opt.tooltip));
+
+    wxArrayString choices;
+    choices.Alloc(gcode_opt.enum_labels.size());
+    for (const auto &label : gcode_opt.enum_labels) {
+        choices.Add(label);
+    }
+
+    gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices);
+    wxGetApp().UpdateDarkUI(gcode_picker);
+    const auto &enum_values = gcode_opt.enum_values;
+    auto needle = enum_values.cend();
+    if (gcode_opt.default_value) {
+        needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize());
+    }
+    if (needle != enum_values.cend()) {
+        gcode_picker->SetSelection(needle - enum_values.cbegin());
+    } else {
+        gcode_picker->SetSelection(0);
+    }
+
+    append(gcode_picker);
+}
+
+void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
+{
+    auto sel = gcode_picker->GetSelection();
+    if (sel >= 0 && (size_t)sel < gcode_opt.enum_labels.size()) {
+        auto *opt = new ConfigOptionEnum<GCodeFlavor>(static_cast<GCodeFlavor>(sel));
+        config.set_key_value("gcode_flavor", opt);
+    }
+}
+
+PageBedShape::PageBedShape(ConfigWizard *parent)
+    : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1)
+    , shape_panel(new BedShapePanel(this))
+{
+    append_text(_L("Set the shape of your printer's bed."));
+
+    shape_panel->build_panel(*wizard_p()->custom_config->option<ConfigOptionPoints>("bed_shape"),
+        *wizard_p()->custom_config->option<ConfigOptionString>("bed_custom_texture"),
+        *wizard_p()->custom_config->option<ConfigOptionString>("bed_custom_model"));
+
+    append(shape_panel);
+}
+
+void PageBedShape::apply_custom_config(DynamicPrintConfig &config)
+{
+    const std::vector<Vec2d>& points = shape_panel->get_shape();
+    const std::string& custom_texture = shape_panel->get_custom_texture();
+    const std::string& custom_model = shape_panel->get_custom_model();
+    config.set_key_value("bed_shape", new ConfigOptionPoints(points));
+    config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture));
+    config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model));
+}
+
+static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) 
+{
+    e.Skip();
+    wxString str = ctrl->GetValue();
+
+    const char dec_sep = is_decimal_separator_point() ? '.' : ',';
+    const char dec_sep_alt = dec_sep == '.' ? ',' : '.';
+    // Replace the first incorrect separator in decimal number.
+    bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0;
+
+    double val = 0.0;
+    if (!str.ToDouble(&val)) {
+        if (val == 0.0)
+            val = def_value;
+        ctrl->SetValue(double_to_string(val));
+        show_error(nullptr, _L("Invalid numeric input."));
+        ctrl->SetFocus();
+    }
+    else if (was_replaced)
+        ctrl->SetValue(double_to_string(val));
+}
+
+class DiamTextCtrl : public wxTextCtrl
+{
+public:
+    DiamTextCtrl(wxWindow* parent)
+    {
+#ifdef _WIN32
+        long style = wxBORDER_SIMPLE;
+#else
+        long style = 0;
+#endif
+        Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord), style);
+        wxGetApp().UpdateDarkUI(this);
+    }
+    ~DiamTextCtrl() {}
+};
+
+PageDiameters::PageDiameters(ConfigWizard *parent)
+    : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1)
+    , diam_nozzle(new DiamTextCtrl(this))
+    , diam_filam (new DiamTextCtrl(this))
+{
+    auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value<ConfigOptionFloats>();
+    wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5);
+    diam_nozzle->SetValue(value);
+
+    auto *default_filam = print_config_def.get("filament_diameter")->get_default_value<ConfigOptionFloats>();
+    value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0);
+    diam_filam->SetValue(value);
+
+    diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId());
+    diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId());
+
+    append_text(_L("Enter the diameter of your printer's hot end nozzle."));
+
+    auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5);
+    auto *text_nozzle = new wxStaticText(this, wxID_ANY, _L("Nozzle Diameter:"));
+    auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm"));
+    sizer_nozzle->AddGrowableCol(0, 1);
+    sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
+    sizer_nozzle->Add(diam_nozzle);
+    sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
+    append(sizer_nozzle);
+
+    append_spacer(VERTICAL_SPACING);
+
+    append_text(_L("Enter the diameter of your filament."));
+    append_text(_L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average."));
+
+    auto *sizer_filam = new wxFlexGridSizer(3, 5, 5);
+    auto *text_filam = new wxStaticText(this, wxID_ANY, _L("Filament Diameter:"));
+    auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm"));
+    sizer_filam->AddGrowableCol(0, 1);
+    sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL);
+    sizer_filam->Add(diam_filam);
+    sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL);
+    append(sizer_filam);
+}
+
+void PageDiameters::apply_custom_config(DynamicPrintConfig &config)
+{
+    double val = 0.0;
+    diam_nozzle->GetValue().ToDouble(&val);
+    auto *opt_nozzle = new ConfigOptionFloats(1, val);
+    config.set_key_value("nozzle_diameter", opt_nozzle);
+
+    val = 0.0;
+    diam_filam->GetValue().ToDouble(&val);
+    auto * opt_filam = new ConfigOptionFloats(1, val);
+    config.set_key_value("filament_diameter", opt_filam);
+
+    auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) {
+        char buf[64]; // locales don't matter here (sprintf/atof)
+        sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4);
+        config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false));
+    };
+
+    set_extrusion_width("support_material_extrusion_width",   0.35);
+    set_extrusion_width("top_infill_extrusion_width",		  0.40);
+    set_extrusion_width("first_layer_extrusion_width",		  0.42);
+
+    set_extrusion_width("extrusion_width",					  0.45);
+    set_extrusion_width("perimeter_extrusion_width",		  0.45);
+    set_extrusion_width("external_perimeter_extrusion_width", 0.45);
+    set_extrusion_width("infill_extrusion_width",			  0.45);
+    set_extrusion_width("solid_infill_extrusion_width",       0.45);
+}
+
+class SpinCtrlDouble: public wxSpinCtrlDouble
+{
+public:
+    SpinCtrlDouble(wxWindow* parent)
+    {
+#ifdef _WIN32
+        long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE;
+#else
+        long style = wxSP_ARROW_KEYS;
+#endif
+        Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, style);
+#ifdef _WIN32
+        wxGetApp().UpdateDarkUI(this->GetText());
+#endif
+        this->Refresh();
+    }
+    ~SpinCtrlDouble() {}
+};
+
+PageTemperatures::PageTemperatures(ConfigWizard *parent)
+    : ConfigWizardPage(parent, _L("Nozzle and Bed Temperatures"), _L("Temperatures"), 1)
+    , spin_extr(new SpinCtrlDouble(this))
+    , spin_bed (new SpinCtrlDouble(this))
+{
+    spin_extr->SetIncrement(5.0);
+    const auto &def_extr = *print_config_def.get("temperature");
+    spin_extr->SetRange(def_extr.min, def_extr.max);
+    auto *default_extr = def_extr.get_default_value<ConfigOptionInts>();
+    spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200);
+
+    spin_bed->SetIncrement(5.0);
+    const auto &def_bed = *print_config_def.get("bed_temperature");
+    spin_bed->SetRange(def_bed.min, def_bed.max);
+    auto *default_bed = def_bed.get_default_value<ConfigOptionInts>();
+    spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0);
+
+    append_text(_L("Enter the temperature needed for extruding your filament."));
+    append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS."));
+
+    auto *sizer_extr = new wxFlexGridSizer(3, 5, 5);
+    auto *text_extr = new wxStaticText(this, wxID_ANY, _L("Extrusion Temperature:"));
+    auto *unit_extr = new wxStaticText(this, wxID_ANY, _L("°C"));
+    sizer_extr->AddGrowableCol(0, 1);
+    sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL);
+    sizer_extr->Add(spin_extr);
+    sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL);
+    append(sizer_extr);
+
+    append_spacer(VERTICAL_SPACING);
+
+    append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed."));
+    append_text(_L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed."));
+
+    auto *sizer_bed = new wxFlexGridSizer(3, 5, 5);
+    auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:"));
+    auto *unit_bed = new wxStaticText(this, wxID_ANY, _L("°C"));
+    sizer_bed->AddGrowableCol(0, 1);
+    sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL);
+    sizer_bed->Add(spin_bed);
+    sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL);
+    append(sizer_bed);
+}
+
+void PageTemperatures::apply_custom_config(DynamicPrintConfig &config)
+{
+    auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue());
+    config.set_key_value("temperature", opt_extr);
+    auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue());
+    config.set_key_value("first_layer_temperature", opt_extr1st);
+    auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue());
+    config.set_key_value("bed_temperature", opt_bed);
+    auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue());
+    config.set_key_value("first_layer_bed_temperature", opt_bed1st);
+}
+
+
+// Index
+
+ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent)
+    : wxPanel(parent)
+    , bg(ScalableBitmap(parent, "PrusaSlicer_192px_transparent.png", 192))
+    , bullet_black(ScalableBitmap(parent, "bullet_black.png"))
+    , bullet_blue(ScalableBitmap(parent, "bullet_blue.png"))
+    , bullet_white(ScalableBitmap(parent, "bullet_white.png"))
+    , item_active(NO_ITEM)
+    , item_hover(NO_ITEM)
+    , last_page((size_t)-1)
+{
+#ifndef __WXOSX__ 
+    SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
+#endif //__WXOSX__
+    SetMinSize(bg.bmp().GetSize());
+
+    const wxSize size = GetTextExtent("m");
+    em_w = size.x;
+    em_h = size.y;
+
+    Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this);
+    Bind(wxEVT_SIZE, [this](wxEvent& e) { e.Skip(); Refresh(); });
+    Bind(wxEVT_MOTION, &ConfigWizardIndex::on_mouse_move, this);
+
+    Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &evt) {
+        if (item_hover != -1) {
+            item_hover = -1;
+            Refresh();
+        }
+        evt.Skip();
+    });
+
+    Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &evt) {
+        if (item_hover >= 0) { go_to(item_hover); }
+    });
+}
+
+wxDECLARE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
+
+void ConfigWizardIndex::add_page(ConfigWizardPage *page)
+{
+    last_page = items.size();
+    items.emplace_back(Item { page->shortname, page->indent, page });
+    Refresh();
+}
+
+void ConfigWizardIndex::add_label(wxString label, unsigned indent)
+{
+    items.emplace_back(Item { std::move(label), indent, nullptr });
+    Refresh();
+}
+
+ConfigWizardPage* ConfigWizardIndex::active_page() const
+{
+    if (item_active >= items.size()) { return nullptr; }
+
+    return items[item_active].page;
+}
+
+void ConfigWizardIndex::go_prev()
+{
+    // Search for a preceiding item that is a page (not a label, ie. page != nullptr)
+
+    if (item_active == NO_ITEM) { return; }
+
+    for (size_t i = item_active; i > 0; i--) {
+        if (items[i - 1].page != nullptr) {
+            go_to(i - 1);
+            return;
+        }
+    }
+}
+
+void ConfigWizardIndex::go_next()
+{
+    // Search for a next item that is a page (not a label, ie. page != nullptr)
+
+    if (item_active == NO_ITEM) { return; }
+
+    for (size_t i = item_active + 1; i < items.size(); i++) {
+        if (items[i].page != nullptr) {
+            go_to(i);
+            return;
+        }
+    }
+}
+
+// This one actually performs the go-to op
+void ConfigWizardIndex::go_to(size_t i)
+{
+    if (i != item_active
+        && i < items.size()
+        && items[i].page != nullptr) {
+        auto *new_active = items[i].page;
+        auto *former_active = active_page();
+        if (former_active != nullptr) {
+            former_active->Hide();
+        }
+
+        item_active = i;
+        new_active->Show();
+
+        wxCommandEvent evt(EVT_INDEX_PAGE, GetId());
+        AddPendingEvent(evt);
+
+        Refresh();
+
+        new_active->on_activate();
+    }
+}
+
+void ConfigWizardIndex::go_to(const ConfigWizardPage *page)
+{
+    if (page == nullptr) { return; }
+
+    for (size_t i = 0; i < items.size(); i++) {
+        if (items[i].page == page) {
+            go_to(i);
+            return;
+        }
+    }
+}
+
+void ConfigWizardIndex::clear()
+{
+    auto *former_active = active_page();
+    if (former_active != nullptr) { former_active->Hide(); }
+
+    items.clear();
+    item_active = NO_ITEM;
+}
+
+void ConfigWizardIndex::on_paint(wxPaintEvent & evt)
+{
+    const auto size = GetClientSize();
+    if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; }
+   
+    wxPaintDC dc(this);
+    
+    const auto bullet_w = bullet_black.bmp().GetSize().GetWidth();
+    const auto bullet_h = bullet_black.bmp().GetSize().GetHeight();
+    const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0;
+    const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0;
+    const int yinc = item_height();
+   
+    int index_width = 0;
+
+    unsigned y = 0;
+    for (size_t i = 0; i < items.size(); i++) {
+        const Item& item = items[i];
+        unsigned x = em_w/2 + item.indent * em_w;
+
+        if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) {
+            dc.DrawBitmap(bullet_blue.bmp(), x, y + yoff_icon, false);
+        }
+        else if (i < item_active)  { dc.DrawBitmap(bullet_black.bmp(), x, y + yoff_icon, false); }
+        else if (i > item_active)  { dc.DrawBitmap(bullet_white.bmp(), x, y + yoff_icon, false); }
+
+        x += + bullet_w + em_w/2;
+        const auto text_size = dc.GetTextExtent(item.label);
+        dc.SetTextForeground(wxGetApp().get_label_clr_default());
+        dc.DrawText(item.label, x, y + yoff_text);
+
+        y += yinc;
+        index_width = std::max(index_width, (int)x + text_size.x);
+    }
+    
+    //draw logo
+    if (int y = size.y - bg.GetBmpHeight(); y>=0) {
+        dc.DrawBitmap(bg.bmp(), 0, y, false);
+        index_width = std::max(index_width, bg.GetBmpWidth() + em_w / 2);
+    }
+
+    if (GetMinSize().x < index_width) {
+        CallAfter([this, index_width]() {
+            SetMinSize(wxSize(index_width, GetMinSize().y));
+            Refresh();
+        });
+    }
+}
+
+void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt)
+{
+    const wxClientDC dc(this);
+    const wxPoint pos = evt.GetLogicalPosition(dc);
+
+    const ssize_t item_hover_new = pos.y / item_height();
+
+    if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) {
+        item_hover = item_hover_new;
+        Refresh();
+    }
+
+    evt.Skip();
+}
+
+void ConfigWizardIndex::msw_rescale()
+{
+    const wxSize size = GetTextExtent("m");
+    em_w = size.x;
+    em_h = size.y;
+
+    bg.msw_rescale();
+    SetMinSize(bg.bmp().GetSize());
+
+    bullet_black.msw_rescale();
+    bullet_blue.msw_rescale();
+    bullet_white.msw_rescale();
+    Refresh();
+}
+
+
+// Materials
+
+const std::string Materials::UNKNOWN = "(Unknown)";
+
+void Materials::push(const Preset *preset)
+{
+    presets.emplace_back(preset);
+    types.insert(technology & T_FFF
+        ? Materials::get_filament_type(preset)
+        : Materials::get_material_type(preset));
+}
+
+void  Materials::add_printer(const Preset* preset)
+{
+	printers.insert(preset);
+}
+
+void Materials::clear()
+{
+    presets.clear();
+    types.clear();
+	printers.clear();
+    compatibility_counter.clear();
+}
+
+const std::string& Materials::appconfig_section() const
+{
+    return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
+}
+
+const std::string& Materials::get_type(const Preset *preset) const
+{
+    return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset);
+}
+
+const std::string& Materials::get_vendor(const Preset *preset) const
+{
+    return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset);
+}
+
+const std::string& Materials::get_filament_type(const Preset *preset)
+{
+    const auto *opt = preset->config.opt<ConfigOptionStrings>("filament_type");
+    if (opt != nullptr && opt->values.size() > 0) {
+        return opt->values[0];
+    } else {
+        return UNKNOWN;
+    }
+}
+
+const std::string& Materials::get_filament_vendor(const Preset *preset)
+{
+    const auto *opt = preset->config.opt<ConfigOptionString>("filament_vendor");
+    return opt != nullptr ? opt->value : UNKNOWN;
+}
+
+const std::string& Materials::get_material_type(const Preset *preset)
+{
+    const auto *opt = preset->config.opt<ConfigOptionString>("material_type");
+    if (opt != nullptr) {
+        return opt->value;
+    } else {
+        return UNKNOWN;
+    }
+}
+
+const std::string& Materials::get_material_vendor(const Preset *preset)
+{
+    const auto *opt = preset->config.opt<ConfigOptionString>("material_vendor");
+    return opt != nullptr ? opt->value : UNKNOWN;
+}
+
+// priv
+
+static const std::unordered_map<std::string, std::pair<std::string, std::string>> legacy_preset_map {{
+    { "Original Prusa i3 MK2.ini",                           std::make_pair("MK2S", "0.4") },
+    { "Original Prusa i3 MK2 MM Single Mode.ini",            std::make_pair("MK2SMM", "0.4") },
+    { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
+    { "Original Prusa i3 MK2 MultiMaterial.ini",             std::make_pair("MK2SMM", "0.4") },
+    { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini",  std::make_pair("MK2SMM", "0.6") },
+    { "Original Prusa i3 MK2 0.25 nozzle.ini",               std::make_pair("MK2S", "0.25") },
+    { "Original Prusa i3 MK2 0.6 nozzle.ini",                std::make_pair("MK2S", "0.6") },
+    { "Original Prusa i3 MK3.ini",                           std::make_pair("MK3",  "0.4") },
+}};
+
+void ConfigWizard::priv::load_pages()
+{
+    wxWindowUpdateLocker freeze_guard(q);
+    (void)freeze_guard;
+
+    const ConfigWizardPage *former_active = index->active_page();
+
+    index->clear();
+
+    index->add_page(page_welcome);
+
+    // Printers
+    if (!only_sla_mode)
+        index->add_page(page_fff);
+    index->add_page(page_msla);
+    if (!only_sla_mode) {
+        index->add_page(page_vendors);
+        for (const auto &pages : pages_3rdparty) {
+            for ( PagePrinters* page : { pages.second.first, pages.second.second })
+                if (page && page->install)
+                    index->add_page(page);
+        }
+
+        index->add_page(page_custom);
+        if (page_custom->custom_wanted()) {
+            index->add_page(page_firmware);
+            index->add_page(page_bed);
+            index->add_page(page_diams);
+            index->add_page(page_temps);
+        }
+   
+    // Filaments & Materials
+        if (any_fff_selected) { index->add_page(page_filaments); }
+    }
+    if (any_sla_selected) { index->add_page(page_sla_materials); }
+
+    // there should to be selected at least one printer
+    btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected);
+
+    index->add_page(page_update);
+    index->add_page(page_reload_from_disk);
+#ifdef _WIN32
+#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+    if (page_files_association != nullptr)
+#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+        index->add_page(page_files_association);
+#endif // _WIN32
+    index->add_page(page_mode);
+
+    index->go_to(former_active);   // Will restore the active item/page if possible
+
+    q->Layout();
+// This Refresh() is needed to avoid ugly artifacts after printer selection, when no one vendor was selected from the very beginnig
+    q->Refresh();
+}
+
+void ConfigWizard::priv::init_dialog_size()
+{
+    // Clamp the Wizard size based on screen dimensions
+
+    const auto idx = wxDisplay::GetFromWindow(q);
+    wxDisplay display(idx != wxNOT_FOUND ? idx : 0u);
+
+    const auto disp_rect = display.GetClientArea();
+    wxRect window_rect(
+        disp_rect.x + disp_rect.width / 20,
+        disp_rect.y + disp_rect.height / 20,
+        9*disp_rect.width / 10,
+        9*disp_rect.height / 10);
+
+    const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), (only_sla_mode ? page_msla->get_width() : page_fff->get_width()) + 30 * em());    // XXX: magic constant, I found no better solution
+    if (width_hint < window_rect.width) {
+        window_rect.x += (window_rect.width - width_hint) / 2;
+        window_rect.width = width_hint;
+    }
+
+    q->SetSize(window_rect);
+}
+
+void ConfigWizard::priv::load_vendors()
+{
+    bundles = BundleMap::load();
+
+    // Load up the set of vendors / models / variants the user has had enabled up till now
+    AppConfig *app_config = wxGetApp().app_config;
+    if (! app_config->legacy_datadir()) {
+        appconfig_new.set_vendors(*app_config);
+    } else {
+        // In case of legacy datadir, try to guess the preference based on the printer preset files that are present
+        const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer";
+        for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir))
+            if (Slic3r::is_ini_file(dir_entry)) {
+                auto needle = legacy_preset_map.find(dir_entry.path().filename().string());
+                if (needle == legacy_preset_map.end()) { continue; }
+
+                const auto &model = needle->second.first;
+                const auto &variant = needle->second.second;
+                appconfig_new.set_variant("PrusaResearch", model, variant, true);
+            }
+    }
+
+    // Initialize the is_visible flag in printer Presets
+    for (auto &pair : bundles) {
+        pair.second.preset_bundle->load_installed_printers(appconfig_new);
+    }
+
+    // Copy installed filaments and SLA material names from app_config to appconfig_new
+    // while resolving current names of profiles, which were renamed in the meantime.
+    for (PrinterTechnology technology : { ptFFF, ptSLA }) {
+    	const std::string &section_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
+		std::map<std::string, std::string> section_new;
+		if (app_config->has_section(section_name)) {
+			const std::map<std::string, std::string> &section_old = app_config->get_section(section_name);
+            for (const auto& material_name_and_installed : section_old)
+				if (material_name_and_installed.second == "1") {
+					// Material is installed. Resolve it in bundles.
+                    size_t num_found = 0;
+					const std::string &material_name = material_name_and_installed.first;
+				    for (auto &bundle : bundles) {
+				    	const PresetCollection &materials = bundle.second.preset_bundle->materials(technology);
+				    	const Preset           *preset    = materials.find_preset(material_name);
+				    	if (preset == nullptr) {
+				    		// Not found. Maybe the material preset is there, bu it was was renamed?
+							const std::string *new_name = materials.get_preset_name_renamed(material_name);
+							if (new_name != nullptr)
+								preset = materials.find_preset(*new_name);
+				    	}
+                        if (preset != nullptr) {
+                            // Materal preset was found, mark it as installed.
+                            section_new[preset->name] = "1";
+                            ++ num_found;
+                        }
+				    }
+                    if (num_found == 0)
+            	        BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name;
+                    else if (num_found > 1)
+            	        BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found;
+                }
+		}
+        appconfig_new.set_section(section_name, section_new);
+    };
+}
+
+void ConfigWizard::priv::add_page(ConfigWizardPage *page)
+{
+    const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0;
+    hscroll_sizer->Add(page, proportion, wxEXPAND);
+    all_pages.push_back(page);
+}
+
+void ConfigWizard::priv::enable_next(bool enable)
+{
+    btn_next->Enable(enable);
+    btn_finish->Enable(enable);
+}
+
+void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page)
+{
+    switch (start_page) {
+        case ConfigWizard::SP_PRINTERS: 
+            index->go_to(page_fff); 
+            btn_next->SetFocus();
+            break;
+        case ConfigWizard::SP_FILAMENTS:
+            index->go_to(page_filaments);
+            btn_finish->SetFocus();
+            break;
+        case ConfigWizard::SP_MATERIALS:
+            index->go_to(page_sla_materials);
+            btn_finish->SetFocus();
+            break;
+        default:
+            index->go_to(page_welcome);
+            btn_next->SetFocus();
+            break;
+    }
+}
+
+void ConfigWizard::priv::create_3rdparty_pages()
+{
+    for (const auto &pair : bundles) {
+        const VendorProfile *vendor = pair.second.vendor_profile;
+        if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
+
+        bool is_fff_technology = false;
+        bool is_sla_technology = false;
+
+        for (auto& model: vendor->models)
+        {
+            if (!is_fff_technology && model.technology == ptFFF)
+                 is_fff_technology = true;
+            if (!is_sla_technology && model.technology == ptSLA)
+                 is_sla_technology = true;
+        }
+
+        PagePrinters* pageFFF = nullptr;
+        PagePrinters* pageSLA = nullptr;
+
+        if (is_fff_technology) {
+            pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name+" FFF", *vendor, 1, T_FFF);
+            add_page(pageFFF);
+        }
+
+        if (is_sla_technology) {
+            pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name+" MSLA", *vendor, 1, T_SLA);
+            add_page(pageSLA);
+        }
+
+        pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}});
+    }
+}
+
+void ConfigWizard::priv::set_run_reason(RunReason run_reason)
+{
+    this->run_reason = run_reason;
+    for (auto &page : all_pages) {
+        page->set_run_reason(run_reason);
+    }
+}
+
+void ConfigWizard::priv::update_materials(Technology technology)
+{
+    if (any_fff_selected && (technology & T_FFF)) {
+        filaments.clear();
+        aliases_fff.clear();
+        // Iterate filaments in all bundles
+        for (const auto &pair : bundles) {
+            for (const auto &filament : pair.second.preset_bundle->filaments) {
+                // Check if filament is already added
+                if (filaments.containts(&filament))
+					continue;
+                // Iterate printers in all bundles
+                for (const auto &printer : pair.second.preset_bundle->printers) {
+					if (!printer.is_visible || printer.printer_technology() != ptFFF)
+						continue;
+                    // Filter out inapplicable printers
+					if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) {
+						if (!filaments.containts(&filament)) {
+							filaments.push(&filament);
+							if (!filament.alias.empty())
+								aliases_fff[filament.alias].insert(filament.name); 
+						} 
+						filaments.add_printer(&printer);
+                    }
+				}
+				
+            }
+        }
+        // count compatible printers
+        for (const auto& preset : filaments.presets) {
+
+            const auto filter = [preset](const std::pair<std::string, size_t> element) {
+                return preset->alias == element.first;
+            };
+            if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) {
+                continue;
+            }
+            std::vector<size_t> idx_with_same_alias;
+            for (size_t i = 0; i < filaments.presets.size(); ++i) {
+                if (preset->alias == filaments.presets[i]->alias)
+                    idx_with_same_alias.push_back(i);
+            }
+            size_t counter = 0;
+            for (const auto& printer : filaments.printers) {
+                if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF)
+                    continue;
+                bool compatible = false;
+                // Test otrher materials with same alias
+                for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
+                    const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]);
+                    const Preset& prntr = *printer;
+                    if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) {
+                        compatible = true;
+                        break;
+                    }
+                }
+                if (compatible)
+                    counter++;
+            }
+            filaments.compatibility_counter.emplace_back(preset->alias, counter);
+        }
+    }
+
+    if (any_sla_selected && (technology & T_SLA)) {
+        sla_materials.clear();
+        aliases_sla.clear();
+
+        // Iterate SLA materials in all bundles
+        for (const auto &pair : bundles) {
+            for (const auto &material : pair.second.preset_bundle->sla_materials) {
+                // Check if material is already added
+                if (sla_materials.containts(&material))
+                	continue;
+                // Iterate printers in all bundles
+				// For now, we only allow the profiles to be compatible with another profiles inside the same bundle.
+                for (const auto& printer : pair.second.preset_bundle->printers) {
+                    if(!printer.is_visible || printer.printer_technology() != ptSLA)
+                        continue;
+                    // Filter out inapplicable printers
+                    if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
+                        // Check if material is already added
+                        if(!sla_materials.containts(&material)) {
+                            sla_materials.push(&material);
+                            if (!material.alias.empty())
+                                aliases_sla[material.alias].insert(material.name);
+                        }
+                        sla_materials.add_printer(&printer);
+                    }
+                }
+            }
+        }
+        // count compatible printers        
+        for (const auto& preset : sla_materials.presets) {
+            
+            const auto filter = [preset](const std::pair<std::string, size_t> element) {
+                return preset->alias == element.first;
+            };
+            if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) {
+                continue;
+            }
+            std::vector<size_t> idx_with_same_alias;
+            for (size_t i = 0; i < sla_materials.presets.size(); ++i) {
+                if(preset->alias == sla_materials.presets[i]->alias)
+                    idx_with_same_alias.push_back(i);
+            }
+            size_t counter = 0;
+            for (const auto& printer : sla_materials.printers) {
+                if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA)
+                    continue;
+                bool compatible = false;
+                // Test otrher materials with same alias
+                for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
+                    const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]);
+                    const Preset& prntr = *printer;
+                    if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) {
+                        compatible = true;
+                        break;
+                    }
+                }
+                if (compatible)
+                    counter++;
+            }
+            sla_materials.compatibility_counter.emplace_back(preset->alias, counter);
+        }
+    }
+}
+
+void ConfigWizard::priv::on_custom_setup(const bool custom_wanted)
+{
+	custom_printer_selected = custom_wanted;
+    load_pages();
+}
+
+void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt)
+{
+    if (check_sla_selected() != any_sla_selected ||
+        check_fff_selected() != any_fff_selected) {
+        any_fff_selected = check_fff_selected();
+        any_sla_selected = check_sla_selected();
+
+        load_pages();
+    }
+
+    // Update the is_visible flag on relevant printer profiles
+    for (auto &pair : bundles) {
+        if (pair.first != evt.vendor_id) { continue; }
+
+        for (auto &preset : pair.second.preset_bundle->printers) {
+            if (preset.config.opt_string("printer_model") == evt.model_id
+                && preset.config.opt_string("printer_variant") == evt.variant_name) {
+                preset.is_visible = evt.enable;
+            }
+        }
+
+        // When a printer model is picked, but there is no material installed compatible with this printer model,
+        // install default materials for selected printer model silently.
+		check_and_install_missing_materials(page->technology, evt.model_id);
+    }
+
+    if (page->technology & T_FFF) {
+        page_filaments->clear();
+    } else if (page->technology & T_SLA) {
+        page_sla_materials->clear();
+    }
+}
+
+void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology)
+{
+    PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
+    for (const std::string& material : printer_model.default_materials)
+        appconfig_new.set(page_materials->materials->appconfig_section(), material, "1");
+}
+
+void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set<const VendorProfile::PrinterModel*> &printer_models)
+{
+    PageMaterials     *page_materials    = technology & T_FFF ? page_filaments : page_sla_materials;
+    const std::string &appconfig_section = page_materials->materials->appconfig_section();
+    
+    // Following block was unnecessary. Its enough to iterate printer_models once. Not for every vendor printer page. 
+    // Filament is selected on same page for all printers of same technology.
+    /*
+    auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models, technology](PagePrinters *page_printers, Technology technology)
+    {
+        const std::string vendor_id = page_printers->get_vendor_id();
+        for (auto& pair : bundles)
+            if (pair.first == vendor_id)
+                for (const VendorProfile::PrinterModel *printer_model : printer_models)
+                    for (const std::string &material : printer_model->default_materials)
+                        appconfig_new.set(appconfig_section, material, "1");
+    };
+
+    PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla;
+    select_default_materials_for_printer_page(page_printers, technology);
+
+    for (const auto& printer : pages_3rdparty)
+    {
+        page_printers = technology & T_FFF ? printer.second.first : printer.second.second;
+        if (page_printers)
+            select_default_materials_for_printer_page(page_printers, technology);
+    }
+    */
+
+    // Iterate printer_models and select default materials. If none available -> msg to user.
+    std::vector<const VendorProfile::PrinterModel*> models_without_default;
+    for (const VendorProfile::PrinterModel* printer_model : printer_models) {
+        if (printer_model->default_materials.empty()) {
+            models_without_default.emplace_back(printer_model);
+        } else {
+            for (const std::string& material : printer_model->default_materials)
+                appconfig_new.set(appconfig_section, material, "1");
+        }
+    }
+
+    if (!models_without_default.empty()) {
+        std::string printer_names = "\n\n";
+        for (const VendorProfile::PrinterModel* printer_model : models_without_default) {
+            printer_names += printer_model->name + "\n";
+        }
+        printer_names += "\n\n";
+        std::string message = (technology & T_FFF ?
+            GUI::format(_L("Following printer profiles has no default filament: %1%Please select one manually."), printer_names) :
+            GUI::format(_L("Following printer profiles has no default material: %1%Please select one manually."), printer_names));
+        MessageDialog msg(q, message, _L("Notice"), wxOK);
+        msg.ShowModal();
+    }
+
+    update_materials(technology);
+    ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets();
+}
+
+void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install)
+{
+    auto it = pages_3rdparty.find(vendor->id);
+    wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile");
+
+    for (PagePrinters* page : { it->second.first, it->second.second }) 
+        if (page) {
+            if (page->install && !install)
+                page->select_all(false);
+            page->install = install;
+            // if some 3rd vendor is selected, select first printer for them
+            if (install)
+                page->printer_pickers[0]->select_one(0, true);
+            page->Layout();
+        }
+
+    load_pages();
+}
+
+bool ConfigWizard::priv::on_bnt_finish()
+{
+    wxBusyCursor wait;
+    /* When Filaments or Sla Materials pages are activated, 
+     * materials for this pages are automaticaly updated and presets are reloaded.
+     * 
+     * But, if _Finish_ button was clicked without activation of those pages 
+     * (for example, just some printers were added/deleted), 
+     * than last changes wouldn't be updated for filaments/materials.
+     * SO, do that before close of Wizard
+     */
+    update_materials(T_ANY);
+    if (any_fff_selected)
+        page_filaments->reload_presets();
+    if (any_sla_selected)
+        page_sla_materials->reload_presets();
+
+	// theres no need to check that filament is selected if we have only custom printer
+    if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true;
+    // check, that there is selected at least one filament/material
+    return check_and_install_missing_materials(T_ANY);
+}
+
+// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed
+// for each Printer preset of each Printer Model installed.
+//
+// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently.
+// Otherwise the user is quieried whether to install the missing default materials or not.
+// 
+// Return true if the tested Printer Models already had materials installed.
+// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these
+// respective Printer Models or not.
+bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id)
+{
+	// Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle,
+	// which is compatible with it.
+    const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string &section)
+    {
+		const std::map<std::string, std::string> &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map<std::string, std::string>();
+    	std::set<const VendorProfile::PrinterModel*> printer_models_without_material;
+        for (const auto &pair : bundles) {
+        	const PresetCollection &materials = pair.second.preset_bundle->materials(technology);
+        	for (const auto &printer : pair.second.preset_bundle->printers) {
+                if (printer.is_visible && printer.printer_technology() == technology) {
+	            	const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer);
+	            	assert(printer_model != nullptr);
+	            	if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) &&
+	            		printer_models_without_material.find(printer_model) == printer_models_without_material.end()) {
+                    	bool has_material = false;
+                        for (const auto& preset : appconfig_presets) {
+			            	if (preset.second == "1") {
+			            		const Preset *material = materials.find_preset(preset.first, false);
+			            		if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
+				                	has_material = true;
+				                    break;
+				                }
+			                }
+			            }
+			            if (! has_material)
+			            	printer_models_without_material.insert(printer_model);
+			        }
+                }
+            }
+        }
+        assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id);
+        return printer_models_without_material;
+    };
+
+    const auto ask_and_select_default_materials = [this](const wxString &message, const std::set<const VendorProfile::PrinterModel*> &printer_models, Technology technology)
+    {
+        //wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO);
+        MessageDialog msg(q, message, _L("Notice"), wxYES_NO);
+        if (msg.ShowModal() == wxID_YES)
+            select_default_materials_for_printer_models(technology, printer_models);
+    };
+
+    const auto printer_model_list = [](const std::set<const VendorProfile::PrinterModel*> &printer_models) -> wxString {
+    	wxString out;
+    	for (const VendorProfile::PrinterModel *printer_model : printer_models) {
+            wxString name = from_u8(printer_model->name);
+    		out += "\t\t";
+    		out += name;
+    		out += "\n";
+    	}
+    	return out;
+    };
+
+    if (any_fff_selected && (technology & T_FFF)) {
+    	std::set<const VendorProfile::PrinterModel*> printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS);
+    	if (! printer_models_without_material.empty()) {
+			if (only_for_model_id.empty())
+				ask_and_select_default_materials(
+					_L("The following FFF printer models have no filament selected:") +
+					"\n\n\t" +
+					printer_model_list(printer_models_without_material) +
+					"\n\n\t" +
+					_L("Do you want to select default filaments for these FFF printer models?"),
+					printer_models_without_material,
+					T_FFF);
+			else
+				select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF);
+			return false;
+		}
+    }
+
+    if (any_sla_selected && (technology & T_SLA)) {
+    	std::set<const VendorProfile::PrinterModel*> printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS);
+    	if (! printer_models_without_material.empty()) {
+	        if (only_for_model_id.empty())
+	            ask_and_select_default_materials(
+					_L("The following SLA printer models have no materials selected:") +
+	            	"\n\n\t" +
+				   	printer_model_list(printer_models_without_material) +
+					"\n\n\t" +
+					_L("Do you want to select default SLA materials for these printer models?"),
+					printer_models_without_material,
+	            	T_SLA);
+	        else
+				select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA);
+	        return false;
+	    }
+    }
+
+    return true;
+}
+
+static std::set<std::string> get_new_added_presets(const std::map<std::string, std::string>& old_data, const std::map<std::string, std::string>& new_data) 
+{
+    auto get_aliases = [](const std::map<std::string, std::string>& data) {
+        std::set<std::string> old_aliases;
+        for (auto item : data) {
+            const std::string& name = item.first;
+            size_t pos = name.find("@");
+            old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1));
+        }
+        return old_aliases;
+    };
+
+    std::set<std::string> old_aliases = get_aliases(old_data);
+    std::set<std::string> new_aliases = get_aliases(new_data);
+    std::set<std::string> diff;
+    std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin()));
+
+    return diff;
+}
+
+static std::string get_first_added_preset(const std::map<std::string, std::string>& old_data, const std::map<std::string, std::string>& new_data)
+{
+    std::set<std::string> diff = get_new_added_presets(old_data, new_data);
+    if (diff.empty())
+        return std::string();
+    return *diff.begin();
+}
+
+bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes)
+{
+    wxString header, caption = _L("Configuration is edited in ConfigWizard");
+    const auto enabled_vendors = appconfig_new.vendors();
+    const auto enabled_vendors_old = app_config->vendors();
+
+    bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model());
+    PrinterTechnology preferred_pt = ptAny;
+    auto get_preferred_printer_technology = [enabled_vendors, enabled_vendors_old, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) {
+        const auto config = enabled_vendors.find(bundle_name);
+        PrinterTechnology pt = ptAny;
+        if (config != enabled_vendors.end()) {
+            for (const auto& model : bundle.vendor_profile->models) {
+                if (const auto model_it = config->second.find(model.id);
+                    model_it != config->second.end() && model_it->second.size() > 0) {
+                    pt = model.technology;
+                    const auto config_old = enabled_vendors_old.find(bundle_name);
+                    if (config_old == enabled_vendors_old.end() || config_old->second.find(model.id) == config_old->second.end()) {
+                        // if preferred printer model has SLA printer technology it's important to check the model for multi-part state
+                        if (pt == ptSLA && suppress_sla_printer)
+                            continue;
+                        return pt;
+                    }
+
+                    if (const auto model_it_old = config_old->second.find(model.id);
+                        model_it_old == config_old->second.end() || model_it_old->second != model_it->second) {
+                        // if preferred printer model has SLA printer technology it's important to check the model for multi-part state
+                        if (pt == ptSLA && suppress_sla_printer)
+                            continue;
+                        return pt;
+                    }
+                }
+            }
+        }
+        return pt;
+    };
+    // Prusa printers are considered first, then 3rd party.
+    if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle());
+        preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) {
+        for (const auto& bundle : bundles) {
+            if (bundle.second.is_prusa_bundle) { continue; }
+            if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny)
+                continue;
+            else if (preferred_pt == ptAny)
+                preferred_pt = pt;
+            if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)))
+                break;
+        }
+    }
+
+    if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption))
+        return false;
+
+    bool check_unsaved_preset_changes = page_welcome->reset_user_profile();
+    if (check_unsaved_preset_changes)
+        header = _L("All user presets will be deleted.");
+    int act_btns = UnsavedChangesDialog::ActionButtons::KEEP;
+    if (!check_unsaved_preset_changes)
+        act_btns |= UnsavedChangesDialog::ActionButtons::SAVE;
+
+    // Install bundles from resources if needed:
+    std::vector<std::string> install_bundles;
+    for (const auto &pair : bundles) {
+        if (! pair.second.is_in_resources) { continue; }
+
+        if (pair.second.is_prusa_bundle) {
+            // Always install Prusa bundle, because it has a lot of filaments/materials
+            // likely to be referenced by other profiles.
+            install_bundles.emplace_back(pair.first);
+            continue;
+        }
+
+        const auto vendor = enabled_vendors.find(pair.first);
+        if (vendor == enabled_vendors.end()) { continue; }
+
+        size_t size_sum = 0;
+        for (const auto &model : vendor->second) { size_sum += model.second.size(); }
+
+        if (size_sum > 0) {
+            // This vendor needs to be installed
+            install_bundles.emplace_back(pair.first);
+        }
+    }
+    if (!check_unsaved_preset_changes)
+        if ((check_unsaved_preset_changes = install_bundles.size() > 0))
+            header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size());
+
+#ifdef __linux__
+    // Desktop integration on Linux
+    if (page_welcome->integrate_desktop()) 
+        DesktopIntegrationDialog::perform_desktop_integration();
+#endif
+
+    // Decide whether to create snapshot based on run_reason and the reset profile checkbox
+    bool snapshot = true;
+    Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE;
+    switch (run_reason) {
+        case ConfigWizard::RR_DATA_EMPTY:
+            snapshot = false;
+            break;
+        case ConfigWizard::RR_DATA_LEGACY:
+            snapshot = true;
+            break;
+        case ConfigWizard::RR_DATA_INCOMPAT:
+            // In this case snapshot has already been taken by
+            // PresetUpdater with the appropriate reason
+            snapshot = false;
+            break;
+        case ConfigWizard::RR_USER:
+            snapshot = page_welcome->reset_user_profile();
+            snapshot_reason = Snapshot::SNAPSHOT_USER;
+            break;
+    }
+
+    if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Do you want to continue changing the configuration?")))
+        return false;
+
+    if (check_unsaved_preset_changes &&
+        !wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
+        return false;
+
+    if (install_bundles.size() > 0) {
+        // Install bundles from resources.
+        // Don't create snapshot - we've already done that above if applicable.
+        if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
+            return false;
+    } else {
+        BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
+    }
+
+    if (page_welcome->reset_user_profile()) {
+        BOOST_LOG_TRIVIAL(info) << "Resetting user profiles...";
+        preset_bundle->reset(true);
+    }
+
+    std::string preferred_model;
+    std::string preferred_variant;
+    auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) {
+        const auto config = enabled_vendors.find(bundle_name);
+        if (config == enabled_vendors.end())
+            return std::string();
+        for (const auto& model : bundle.vendor_profile->models) {
+            if (const auto model_it = config->second.find(model.id);
+                model_it != config->second.end() && model_it->second.size() > 0 &&
+                preferred_pt == model.technology) {
+                variant = *model_it->second.begin();
+                const auto config_old = enabled_vendors_old.find(bundle_name);
+                if (config_old == enabled_vendors_old.end())
+                    return model.id;
+                const auto model_it_old = config_old->second.find(model.id);
+                if (model_it_old == config_old->second.end())
+                    return model.id;
+                else if (model_it_old->second != model_it->second) {
+                    for (const auto& var : model_it->second)
+                        if (model_it_old->second.find(var) == model_it_old->second.end()) {
+                            variant = var;
+                            return model.id;
+                        }
+                }
+            }
+        }
+        if (!variant.empty())
+            variant.clear();
+        return std::string();
+    };
+    // Prusa printers are considered first, then 3rd party.
+    if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant);
+        preferred_model.empty()) {
+        for (const auto& bundle : bundles) {
+            if (bundle.second.is_prusa_bundle) { continue; }
+            if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant);
+                !preferred_model.empty())
+                    break;
+        }
+    }
+
+    // if unsaved changes was not cheched till this moment
+    if (!check_unsaved_preset_changes) {
+        if ((check_unsaved_preset_changes = !preferred_model.empty())) {
+            header = _L("A new Printer was installed and it will be activated.");
+            if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
+                return false;
+        }
+        else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) {
+            header = _L("Some Printers were uninstalled.");
+            if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
+                return false;
+        }
+    }
+
+    std::string first_added_filament, first_added_sla_material;
+    auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) {
+        if (appconfig_new.has_section(section_name)) {
+            // get first of new added preset names
+            const std::map<std::string, std::string>& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map<std::string, std::string>();
+            first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name));
+        }
+    };
+    get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament);
+    get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material);
+
+    // if unsaved changes was not cheched till this moment
+    if (!check_unsaved_preset_changes) {
+        if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) {
+            header = !first_added_filament.empty() ? 
+                     _L("A new filament was installed and it will be activated.") :
+                     _L("A new SLA material was installed and it will be activated.");
+            if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
+                return false;
+        }
+        else {
+            auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) {
+                return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map<std::string, std::string>()) != appconfig_new.get_section(section_name);
+            };
+            bool is_filaments_changed     = changed(AppConfig::SECTION_FILAMENTS);
+            bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS);
+            if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) {
+                header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled.");
+                if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
+                    return false;
+            }
+        }
+    }
+
+    // apply materials in app_config
+    for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS})
+        app_config->set_section(section_name, appconfig_new.get_section(section_name));
+
+    app_config->set_vendors(appconfig_new);
+
+    app_config->set("notify_release", page_update->version_check ? "all" : "none");
+    app_config->set("preset_update", page_update->preset_update ? "1" : "0");
+    app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
+
+#ifdef _WIN32
+#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+    if (page_files_association != nullptr) {
+#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+        app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0");
+        app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0");
+//        app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0");
+
+        if (wxGetApp().is_editor()) {
+            if (page_files_association->associate_3mf())
+                wxGetApp().associate_3mf_files();
+            if (page_files_association->associate_stl())
+                wxGetApp().associate_stl_files();
+        }
+//        else {
+//            if (page_files_association->associate_gcode())
+//                wxGetApp().associate_gcode_files();
+//        }
+#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+    }
+    else {
+        app_config->set("associate_3mf", "0");
+        app_config->set("associate_stl", "0");
+//        app_config->set("associate_gcode", "0");
+    }
+#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+
+#endif // _WIN32
+
+    page_mode->serialize_mode(app_config);
+
+    if (check_unsaved_preset_changes)
+        preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, 
+                                    {preferred_model, preferred_variant, first_added_filament, first_added_sla_material});
+
+    if (!only_sla_mode && page_custom->custom_wanted()) {
+        // if unsaved changes was not cheched till this moment
+        if (!check_unsaved_preset_changes && 
+            !wxGetApp().check_and_keep_current_preset_changes(caption, _L("Custom printer was installed and it will be activated."), act_btns, &apply_keeped_changes))
+            return false;
+
+        page_firmware->apply_custom_config(*custom_config);
+        page_bed->apply_custom_config(*custom_config);
+        page_diams->apply_custom_config(*custom_config);
+        page_temps->apply_custom_config(*custom_config);
+
+        const std::string profile_name = page_custom->profile_name();
+        preset_bundle->load_config_from_wizard(profile_name, *custom_config);
+    }
+
+    // Update the selections from the compatibilty.
+    preset_bundle->export_selections(*app_config);
+
+    return true;
+}
+void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add)
+{
+    const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla;
+
+    auto update = [this, add](const std::string& s, const std::string& key) {
+    	assert(! s.empty());
+        if (add)
+            appconfig_new.set(s, key, "1");
+        else
+            appconfig_new.erase(s, key); 
+    };
+
+    // add or delete presets had a same alias 
+    auto it = aliases.find(alias_key);
+    if (it != aliases.end())
+        for (const std::string& name : it->second)
+            update(section, name);
+}
+
+bool ConfigWizard::priv::check_fff_selected()
+{
+    bool ret = page_fff->any_selected();
+    for (const auto& printer: pages_3rdparty)
+        if (printer.second.first)               // FFF page
+            ret |= printer.second.first->any_selected();
+    return ret;
+}
+
+bool ConfigWizard::priv::check_sla_selected()
+{
+    bool ret = page_msla->any_selected();
+    for (const auto& printer: pages_3rdparty)
+        if (printer.second.second)               // SLA page
+            ret |= printer.second.second->any_selected();
+    return ret;
+}
+
+
+// Public
+
+ConfigWizard::ConfigWizard(wxWindow *parent)
+    : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+    , p(new priv(this))
+{
+    this->SetFont(wxGetApp().normal_font());
+
+    p->load_vendors();
+    p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({
+        "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature",
+    }));
+
+    p->index = new ConfigWizardIndex(this);
+
+    auto *vsizer = new wxBoxSizer(wxVERTICAL);
+    auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
+    auto* hline = new StaticLine(this);
+    p->btnsizer = new wxBoxSizer(wxHORIZONTAL);
+
+    // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard  without scrolling.
+    // Later, we compare that to the size of the current screen and set minimum width based on that (see below).
+    p->hscroll = new wxScrolledWindow(this);
+    p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL);
+    p->hscroll->SetSizer(p->hscroll_sizer);
+
+    topsizer->Add(p->index, 0, wxEXPAND);
+    topsizer->AddSpacer(INDEX_MARGIN);
+    topsizer->Add(p->hscroll, 1, wxEXPAND);
+
+    p->btn_sel_all = new wxButton(this, wxID_ANY, _L("Select all standard printers"));
+    p->btnsizer->Add(p->btn_sel_all);
+
+    p->btn_prev = new wxButton(this, wxID_ANY, _L("< &Back"));
+    p->btn_next = new wxButton(this, wxID_ANY, _L("&Next >"));
+    p->btn_finish = new wxButton(this, wxID_APPLY, _L("&Finish"));
+    p->btn_cancel = new wxButton(this, wxID_CANCEL, _L("Cancel"));   // Note: The label needs to be present, otherwise we get accelerator bugs on Mac
+    p->btnsizer->AddStretchSpacer();
+    p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING);
+    p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING);
+    p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING);
+    p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING);
+
+    wxGetApp().UpdateDarkUI(p->btn_sel_all);
+    wxGetApp().UpdateDarkUI(p->btn_prev);
+    wxGetApp().UpdateDarkUI(p->btn_next);
+    wxGetApp().UpdateDarkUI(p->btn_finish);
+    wxGetApp().UpdateDarkUI(p->btn_cancel);
+
+    const auto prusa_it = p->bundles.find("PrusaResearch");
+    wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found");
+    const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile;
+
+    p->add_page(p->page_welcome = new PageWelcome(this));
+
+    
+    p->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF);
+    p->only_sla_mode = !p->page_fff->has_printers;
+    if (!p->only_sla_mode) {
+        p->add_page(p->page_fff);
+        p->page_fff->is_primary_printer_page = true;
+    }
+  
+
+    p->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA);
+    p->add_page(p->page_msla);
+    if (p->only_sla_mode) {
+        p->page_msla->is_primary_printer_page = true;
+    }
+
+    if (!p->only_sla_mode) {
+	    // Pages for 3rd party vendors
+	    p->create_3rdparty_pages();   // Needs to be done _before_ creating PageVendors
+	    p->add_page(p->page_vendors = new PageVendors(this));
+	    p->add_page(p->page_custom = new PageCustom(this));
+        p->custom_printer_selected = p->page_custom->custom_wanted();
+    }
+
+    p->any_sla_selected = p->check_sla_selected();
+    p->any_fff_selected = ! p->only_sla_mode && p->check_fff_selected();
+
+    p->update_materials(T_ANY);
+    if (!p->only_sla_mode)
+        p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
+            _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") ));
+
+    p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials,
+        _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") ));
+
+    
+    p->add_page(p->page_update   = new PageUpdate(this));
+    p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this));
+#ifdef _WIN32
+#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+    // file association is not possible anymore starting with Win 8
+    if (wxPlatformInfo::Get().GetOSMajorVersion() < 8)
+#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+        p->add_page(p->page_files_association = new PageFilesAssociation(this));
+#endif // _WIN32
+    p->add_page(p->page_mode     = new PageMode(this));
+    p->add_page(p->page_firmware = new PageFirmware(this));
+    p->add_page(p->page_bed      = new PageBedShape(this));
+    p->add_page(p->page_diams    = new PageDiameters(this));
+    p->add_page(p->page_temps    = new PageTemperatures(this));
+
+    p->load_pages();
+    p->index->go_to(size_t{0});
+
+    vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
+    vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING);
+    vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN);
+
+    SetSizer(vsizer);
+    SetSizerAndFit(vsizer);
+
+    // We can now enable scrolling on hscroll
+    p->hscroll->SetScrollRate(30, 30);
+
+    on_window_geometry(this, [this]() {
+        p->init_dialog_size();
+    });
+
+    p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); });
+
+    p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
+    {
+        // check, that there is selected at least one filament/material
+        ConfigWizardPage* active_page = this->p->index->active_page();
+        if (// Leaving the filaments or SLA materials page and 
+        	(active_page == p->page_filaments || active_page == p->page_sla_materials) && 
+        	// some Printer models had no filament or SLA material selected.
+        	! p->check_and_install_missing_materials(dynamic_cast<PageMaterials*>(active_page)->materials->technology))
+        	// In that case don't leave the page and the function above queried the user whether to install default materials.
+            return;
+        this->p->index->go_next();
+    });
+
+    p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
+    {
+        if (p->on_bnt_finish())
+            this->EndModal(wxID_OK);
+    });
+
+    p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) {
+        p->any_sla_selected = true;
+        p->load_pages();
+        p->page_fff->select_all(true, false);
+        p->page_msla->select_all(true, false);
+        p->index->go_to(p->page_mode);
+    });
+
+    p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) {
+        const bool is_last = p->index->active_is_last();
+        p->btn_next->Show(! is_last);
+        if (is_last)
+            p->btn_finish->SetFocus();
+
+        Layout();
+    });
+
+    if (wxLinux_gtk3)
+        this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) {
+            ConfigWizardPage* active_page = p->index->active_page();
+            if (!active_page)
+                return;
+            for (auto page : p->all_pages)
+                if (page != active_page)
+                    page->Hide();
+            // update best size for the dialog after hiding of the non-active pages
+            vsizer->SetSizeHints(this);
+            // set initial dialog size
+            p->init_dialog_size();
+        });
+}
+
+ConfigWizard::~ConfigWizard() {}
+
+bool ConfigWizard::run(RunReason reason, StartPage start_page)
+{
+    BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page;
+
+    GUI_App &app = wxGetApp();
+
+    p->set_run_reason(reason);
+    p->set_start_page(start_page);
+
+    if (ShowModal() == wxID_OK) {
+        bool apply_keeped_changes = false;
+        if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes))
+            return false;
+
+        if (apply_keeped_changes)
+            app.apply_keeped_preset_modifications();
+
+        app.app_config->set_legacy_datadir(false);
+        app.update_mode();
+        app.obj_manipul()->update_ui_from_settings();
+        BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied";
+        return true;
+    } else {
+        BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled";
+        return false;
+    }
+}
+
+const wxString& ConfigWizard::name(const bool from_menu/* = false*/)
+{
+    // A different naming convention is used for the Wizard on Windows & GTK vs. OSX.
+    // Note: Don't call _() macro here.
+    //       This function just return the current name according to the OS.
+    //       Translation is implemented inside GUI_App::add_config_menu()
+#if __APPLE__
+    static const wxString config_wizard_name =  L("Configuration Assistant");
+    static const wxString config_wizard_name_menu = L("Configuration &Assistant");
+#else
+    static const wxString config_wizard_name = L("Configuration Wizard");
+    static const wxString config_wizard_name_menu = L("Configuration &Wizard");
+#endif
+    return from_menu ? config_wizard_name_menu : config_wizard_name;
+}
+
+void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect)
+{
+    p->index->msw_rescale();
+
+    const int em = em_unit();
+
+    msw_buttons_rescale(this, em, { wxID_APPLY, 
+                                    wxID_CANCEL,
+                                    p->btn_sel_all->GetId(),
+                                    p->btn_next->GetId(),
+                                    p->btn_prev->GetId() });
+
+    for (auto printer_picker: p->page_fff->printer_pickers)
+        msw_buttons_rescale(this, em, printer_picker->get_button_indexes());
+
+    p->init_dialog_size();
+
+    Refresh();
+}
+
+void ConfigWizard::on_sys_color_changed()
+{
+    wxGetApp().UpdateDlgDarkUI(this);
+    Refresh();
+}
+
+}
+}
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 4435f836b..af9b63acd 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -1,3333 +1,3354 @@
-#include "libslic3r/Technologies.hpp"
-#include "GUI_App.hpp"
-#include "GUI_Init.hpp"
-#include "GUI_ObjectList.hpp"
-#include "GUI_ObjectManipulation.hpp"
-#include "GUI_Factories.hpp"
-#include "format.hpp"
-#include "I18N.hpp"
-
-#include <algorithm>
-#include <iterator>
-#include <exception>
-#include <cstdlib>
-#include <regex>
-#include <string_view>
-#include <boost/nowide/fstream.hpp>
-#include <boost/algorithm/string/predicate.hpp>
-#include <boost/algorithm/string.hpp>
-#include <boost/format.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/log/trivial.hpp>
-#include <boost/nowide/convert.hpp>
-
-#include <wx/stdpaths.h>
-#include <wx/imagpng.h>
-#include <wx/display.h>
-#include <wx/menu.h>
-#include <wx/menuitem.h>
-#include <wx/filedlg.h>
-#include <wx/progdlg.h>
-#include <wx/dir.h>
-#include <wx/wupdlock.h>
-#include <wx/filefn.h>
-#include <wx/sysopt.h>
-#include <wx/richmsgdlg.h>
-#include <wx/log.h>
-#include <wx/intl.h>
-
-#include <wx/dialog.h>
-#include <wx/textctrl.h>
-#include <wx/splash.h>
-#include <wx/fontutil.h>
-
-#include "libslic3r/Utils.hpp"
-#include "libslic3r/Model.hpp"
-#include "libslic3r/I18N.hpp"
-#include "libslic3r/PresetBundle.hpp"
-#include "libslic3r/Color.hpp"
-
-#include "GUI.hpp"
-#include "GUI_Utils.hpp"
-#include "3DScene.hpp"
-#include "MainFrame.hpp"
-#include "Plater.hpp"
-#include "GLCanvas3D.hpp"
-
-#include "../Utils/PresetUpdater.hpp"
-#include "../Utils/PrintHost.hpp"
-#include "../Utils/Process.hpp"
-#include "../Utils/MacDarkMode.hpp"
-#include "../Utils/AppUpdater.hpp"
-#include "slic3r/Config/Snapshot.hpp"
-#include "ConfigSnapshotDialog.hpp"
-#include "FirmwareDialog.hpp"
-#include "Preferences.hpp"
-#include "Tab.hpp"
-#include "SysInfoDialog.hpp"
-#include "KBShortcutsDialog.hpp"
-#include "UpdateDialogs.hpp"
-#include "Mouse3DController.hpp"
-#include "RemovableDriveManager.hpp"
-#include "InstanceCheck.hpp"
-#include "NotificationManager.hpp"
-#include "UnsavedChangesDialog.hpp"
-#include "SavePresetDialog.hpp"
-#include "PrintHostDialogs.hpp"
-#include "DesktopIntegrationDialog.hpp"
-#include "SendSystemInfoDialog.hpp"
-
-#include "BitmapCache.hpp"
-#include "Notebook.hpp"
-
-#ifdef __WXMSW__
-#include <dbt.h>
-#include <shlobj.h>
-#ifdef _MSW_DARK_MODE
-#include <wx/msw/dark_mode.h>
-#endif // _MSW_DARK_MODE
-#endif
-#ifdef _WIN32
-#include <boost/dll/runtime_symbol_info.hpp>
-#endif
-
-#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
-#include <boost/beast/core/detail/base64.hpp>
-#include <boost/nowide/fstream.hpp>
-#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
-
-// Needed for forcing menu icons back under gtk2 and gtk3
-#if defined(__WXGTK20__) || defined(__WXGTK3__)
-    #include <gtk/gtk.h>
-#endif
-
-using namespace std::literals;
-
-namespace Slic3r {
-namespace GUI {
-
-class MainFrame;
-
-class SplashScreen : public wxSplashScreen
-{
-public:
-    SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition)
-        : wxSplashScreen(bitmap, splashStyle, milliseconds, static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, wxDefaultPosition, wxDefaultSize,
-#ifdef __APPLE__
-            wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP
-#else
-            wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR
-#endif // !__APPLE__
-        )
-    {
-        wxASSERT(bitmap.IsOk());
-
-        int init_dpi = get_dpi_for_window(this);
-        this->SetPosition(pos);
-        this->CenterOnScreen();
-        int new_dpi = get_dpi_for_window(this);
-
-        m_scale         = (float)(new_dpi) / (float)(init_dpi);
-        m_main_bitmap   = bitmap;
-
-        scale_bitmap(m_main_bitmap, m_scale);
-
-        // init constant texts and scale fonts
-        init_constant_text();
-
-        // this font will be used for the action string
-        m_action_font = m_constant_text.credits_font.Bold();
-
-        // draw logo and constant info text
-        Decorate(m_main_bitmap);
-    }
-
-    void SetText(const wxString& text)
-    {
-        set_bitmap(m_main_bitmap);
-        if (!text.empty()) {
-            wxBitmap bitmap(m_main_bitmap);
-
-            wxMemoryDC memDC;
-            memDC.SelectObject(bitmap);
-
-            memDC.SetFont(m_action_font);
-            memDC.SetTextForeground(wxColour(237, 107, 33));
-            memDC.DrawText(text, int(m_scale * 60), m_action_line_y_position);
-
-            memDC.SelectObject(wxNullBitmap);
-            set_bitmap(bitmap);
-#ifdef __WXOSX__
-            // without this code splash screen wouldn't be updated under OSX
-            wxYield();
-#endif
-        }
-    }
-
-    static wxBitmap MakeBitmap(wxBitmap bmp)
-    {
-        if (!bmp.IsOk())
-            return wxNullBitmap;
-
-        // create dark grey background for the splashscreen
-        // It will be 5/3 of the weight of the bitmap
-        int width = lround((double)5 / 3 * bmp.GetWidth());
-        int height = bmp.GetHeight();
-
-        wxImage image(width, height);
-        unsigned char* imgdata_ = image.GetData();
-        for (int i = 0; i < width * height; ++i) {
-            *imgdata_++ = 51;
-            *imgdata_++ = 51;
-            *imgdata_++ = 51;
-        }
-
-        wxBitmap new_bmp(image);
-
-        wxMemoryDC memDC;
-        memDC.SelectObject(new_bmp);
-        memDC.DrawBitmap(bmp, width - bmp.GetWidth(), 0, true);
-
-        return new_bmp;
-    }
-
-    void Decorate(wxBitmap& bmp)
-    {
-        if (!bmp.IsOk())
-            return;
-
-        // draw text to the box at the left of the splashscreen.
-        // this box will be 2/5 of the weight of the bitmap, and be at the left.
-        int width = lround(bmp.GetWidth() * 0.4);
-
-        // load bitmap for logo
-        BitmapCache bmp_cache;
-        int logo_size = lround(width * 0.25);
-        wxBitmap logo_bmp = *bmp_cache.load_svg(wxGetApp().logo_name(), logo_size, logo_size);
-
-        wxCoord margin = int(m_scale * 20);
-
-        wxRect banner_rect(wxPoint(0, logo_size), wxPoint(width, bmp.GetHeight()));
-        banner_rect.Deflate(margin, 2 * margin);
-
-        // use a memory DC to draw directly onto the bitmap
-        wxMemoryDC memDc(bmp);
-
-        // draw logo
-        memDc.DrawBitmap(logo_bmp, margin, margin, true);
-
-        // draw the (white) labels inside of our black box (at the left of the splashscreen)
-        memDc.SetTextForeground(wxColour(255, 255, 255));
-
-        memDc.SetFont(m_constant_text.title_font);
-        memDc.DrawLabel(m_constant_text.title,   banner_rect, wxALIGN_TOP | wxALIGN_LEFT);
-
-        int title_height = memDc.GetTextExtent(m_constant_text.title).GetY();
-        banner_rect.SetTop(banner_rect.GetTop() + title_height);
-        banner_rect.SetHeight(banner_rect.GetHeight() - title_height);
-
-        memDc.SetFont(m_constant_text.version_font);
-        memDc.DrawLabel(m_constant_text.version, banner_rect, wxALIGN_TOP | wxALIGN_LEFT);
-        int version_height = memDc.GetTextExtent(m_constant_text.version).GetY();
-
-        memDc.SetFont(m_constant_text.credits_font);
-        memDc.DrawLabel(m_constant_text.credits, banner_rect, wxALIGN_BOTTOM | wxALIGN_LEFT);
-        int credits_height = memDc.GetMultiLineTextExtent(m_constant_text.credits).GetY();
-        int text_height    = memDc.GetTextExtent("text").GetY();
-
-        // calculate position for the dynamic text
-        int logo_and_header_height = margin + logo_size + title_height + version_height;
-        m_action_line_y_position = logo_and_header_height + 0.5 * (bmp.GetHeight() - margin - credits_height - logo_and_header_height - text_height);
-    }
-
-private:
-    wxBitmap    m_main_bitmap;
-    wxFont      m_action_font;
-    int         m_action_line_y_position;
-    float       m_scale {1.0};
-
-    struct ConstantText
-    {
-        wxString title;
-        wxString version;
-        wxString credits;
-
-        wxFont   title_font;
-        wxFont   version_font;
-        wxFont   credits_font;
-
-        void init(wxFont init_font)
-        {
-            // title
-            title = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME;
-
-            // dynamically get the version to display
-            version = _L("Version") + " " + std::string(SLIC3R_VERSION);
-
-            // credits infornation
-            credits =   title + " " +
-                        _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n" +
-                        _L("Developed by Prusa Research.")+ "\n\n" +
-                        title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + "\n\n" +
-                        _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" +
-                        _L("Artwork model by M Boyer");
-
-            title_font = version_font = credits_font = init_font;
-        }
-    } 
-    m_constant_text;
-
-    void init_constant_text()
-    {
-        m_constant_text.init(get_default_font(this));
-
-        // As default we use a system font for current display.
-        // Scale fonts in respect to banner width
-
-        int text_banner_width = lround(0.4 * m_main_bitmap.GetWidth()) - roundl(m_scale * 50); // banner_width - margins
-
-        float title_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.title).GetX();
-        scale_font(m_constant_text.title_font, title_font_scale > 3.5f ? 3.5f : title_font_scale);
-
-        float version_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.version).GetX();
-        scale_font(m_constant_text.version_font, version_font_scale > 2.f ? 2.f : version_font_scale);
-
-        // The width of the credits information string doesn't respect to the banner width some times.
-        // So, scale credits_font in the respect to the longest string width
-        int   longest_string_width = word_wrap_string(m_constant_text.credits);
-        float font_scale = (float)text_banner_width / longest_string_width;
-        scale_font(m_constant_text.credits_font, font_scale);
-    }
-
-    void set_bitmap(wxBitmap& bmp)
-    {
-        m_window->SetBitmap(bmp);
-        m_window->Refresh();
-        m_window->Update();
-    }
-
-    void scale_bitmap(wxBitmap& bmp, float scale)
-    {
-        if (scale == 1.0)
-            return;
-
-        wxImage image = bmp.ConvertToImage();
-        if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0)
-            return;
-
-        int width   = int(scale * image.GetWidth());
-        int height  = int(scale * image.GetHeight());
-        image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR);
-
-        bmp = wxBitmap(std::move(image));
-    }
-
-    void scale_font(wxFont& font, float scale)
-    {
-#ifdef __WXMSW__
-        // Workaround for the font scaling in respect to the current active display,
-        // not for the primary display, as it's implemented in Font.cpp
-        // See https://github.com/wxWidgets/wxWidgets/blob/master/src/msw/font.cpp
-        // void wxNativeFontInfo::SetFractionalPointSize(float pointSizeNew)
-        wxNativeFontInfo nfi= *font.GetNativeFontInfo();
-        float pointSizeNew  = scale * font.GetPointSize();
-        nfi.lf.lfHeight     = nfi.GetLogFontHeightAtPPI(pointSizeNew, get_dpi_for_window(this));
-        nfi.pointSize       = pointSizeNew;
-        font = wxFont(nfi);
-#else
-        font.Scale(scale);
-#endif //__WXMSW__
-    }
-
-    // wrap a string for the strings no longer then 55 symbols
-    // return extent of the longest string
-    int word_wrap_string(wxString& input)
-    {
-        size_t line_len = 55;// count of symbols in one line
-        int idx = -1;
-        size_t cur_len = 0;
-
-        wxString longest_sub_string;
-        auto get_longest_sub_string = [input](wxString &longest_sub_str, size_t cur_len, size_t i) {
-            if (cur_len > longest_sub_str.Len())
-                longest_sub_str = input.SubString(i - cur_len + 1, i);
-        };
-
-        for (size_t i = 0; i < input.Len(); i++)
-        {
-            cur_len++;
-            if (input[i] == ' ')
-                idx = i;
-            if (input[i] == '\n')
-            {
-                get_longest_sub_string(longest_sub_string, cur_len, i);
-                idx = -1;
-                cur_len = 0;
-            }
-            if (cur_len >= line_len && idx >= 0)
-            {
-                get_longest_sub_string(longest_sub_string, cur_len, i);
-                input[idx] = '\n';
-                cur_len = i - static_cast<size_t>(idx);
-            }
-        }
-
-        return GetTextExtent(longest_sub_string).GetX();
-    }
-};
-
-
-#ifdef __linux__
-bool static check_old_linux_datadir(const wxString& app_name) {
-    // If we are on Linux and the datadir does not exist yet, look into the old
-    // location where the datadir was before version 2.3. If we find it there,
-    // tell the user that he might wanna migrate to the new location.
-    // (https://github.com/prusa3d/PrusaSlicer/issues/2911)
-    // To be precise, the datadir should exist, it is created when single instance
-    // lock happens. Instead of checking for existence, check the contents.
-
-    namespace fs = boost::filesystem;
-
-    std::string new_path = Slic3r::data_dir();
-
-    wxString dir;
-    if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() )
-        dir = wxFileName::GetHomeDir() + wxS("/.config");
-    std::string default_path = (dir + "/" + app_name).ToUTF8().data();
-
-    if (new_path != default_path) {
-        // This happens when the user specifies a custom --datadir.
-        // Do not show anything in that case.
-        return true;
-    }
-
-    fs::path data_dir = fs::path(new_path);
-    if (! fs::is_directory(data_dir))
-        return true; // This should not happen.
-
-    int file_count = std::distance(fs::directory_iterator(data_dir), fs::directory_iterator());
-
-    if (file_count <= 1) { // just cache dir with an instance lock
-        std::string old_path = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data();
-
-        if (fs::is_directory(old_path)) {
-            wxString msg = from_u8((boost::format(_u8L("Starting with %1% 2.3, configuration "
-                "directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\n"
-                "This directory did not exist yet (maybe you run the new version for the first time).\nHowever, "
-                "an old %1% configuration directory was detected in \n%3%.\n\n"
-                "Consider moving the contents of the old directory to the new location in order to access "
-                "your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old "
-                "location again.\n\n"
-                "What do you want to do now?")) % SLIC3R_APP_NAME % new_path % old_path).str());
-            wxString caption = from_u8((boost::format(_u8L("%s - BREAKING CHANGE")) % SLIC3R_APP_NAME).str());
-            RichMessageDialog dlg(nullptr, msg, caption, wxYES_NO);
-            dlg.SetYesNoLabels(_L("Quit, I will move my data now"), _L("Start the application"));
-            if (dlg.ShowModal() != wxID_NO)
-                return false;
-        }
-    } else {
-        // If the new directory exists, be silent. The user likely already saw the message.
-    }
-    return true;
-}
-#endif
-
-#ifdef _WIN32
-#if 0 // External Updater is replaced with AppUpdater.cpp
-static bool run_updater_win()
-{
-    // find updater exe
-    boost::filesystem::path path_updater = boost::dll::program_location().parent_path() / "prusaslicer-updater.exe";
-    // run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst
-    std::string msg;
-    bool res = create_process(path_updater, L"/silent", msg);
-    if (!res)
-        BOOST_LOG_TRIVIAL(error) << msg; 
-    return res;
-}
-#endif // 0
-#endif // _WIN32
-
-struct FileWildcards {
-    std::string_view              title;
-    std::vector<std::string_view> file_extensions;
-};
-
-static const FileWildcards file_wildcards_by_type[FT_SIZE] = {
-    /* FT_STL */     { "STL files"sv,       { ".stl"sv } },
-    /* FT_OBJ */     { "OBJ files"sv,       { ".obj"sv } },
-    /* FT_OBJECT */  { "Object files"sv,    { ".stl"sv, ".obj"sv } },
-    /* FT_AMF */     { "AMF files"sv,       { ".amf"sv, ".zip.amf"sv, ".xml"sv } },
-    /* FT_3MF */     { "3MF files"sv,       { ".3mf"sv } },
-    /* FT_GCODE */   { "G-code files"sv,    { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } },
-    /* FT_MODEL */   { "Known files"sv,     { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv } },
-    /* FT_PROJECT */ { "Project files"sv,   { ".3mf"sv, ".amf"sv, ".zip.amf"sv } },
-    /* FT_GALLERY */ { "Known files"sv,     { ".stl"sv, ".obj"sv } },
-
-    /* FT_INI */     { "INI files"sv,       { ".ini"sv } },
-    /* FT_SVG */     { "SVG files"sv,       { ".svg"sv } },
-
-    /* FT_TEX */     { "Texture"sv,         { ".png"sv, ".svg"sv } },
-
-    /* FT_SL1 */     { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } },
-};
-
-#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR
-wxString file_wildcards(FileType file_type)
-{
-    const FileWildcards& data = file_wildcards_by_type[file_type];
-    std::string title;
-    std::string mask;
-
-    // Generate cumulative first item
-    for (const std::string_view& ext : data.file_extensions) {
-        if (title.empty()) {
-            title = "*";
-            title += ext;
-            mask = title;
-        }
-        else {
-            title += ", *";
-            title += ext;
-            mask += ";*";
-            mask += ext;
-        }
-        mask += ";*";
-        mask += boost::to_upper_copy(std::string(ext));
-    }
-
-    wxString ret = GUI::format_wxstr("%s (%s)|%s", data.title, title, mask);
-
-    // Adds an item for each of the extensions
-    if (data.file_extensions.size() > 1) {
-        for (const std::string_view& ext : data.file_extensions) {
-            title = "*";
-            title += ext;
-            ret += GUI::format_wxstr("|%s (%s)|%s", data.title, title, title);
-        }
-    }
-
-    return ret;
-}
-#else
-// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms.
-// The function accepts a custom extension parameter. If the parameter is provided, the custom extension
-// will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips
-// an extension from the provided initial file name and substitutes it with the default extension (the first one in the template).
-wxString file_wildcards(FileType file_type, const std::string &custom_extension)
-{
-    const FileWildcards& data = file_wildcards_by_type[file_type];
-    std::string title;
-    std::string mask;
-    std::string custom_ext_lower;
-
-    if (! custom_extension.empty()) {
-        // Generate an extension into the title mask and into the list of extensions.
-        custom_ext_lower = boost::to_lower_copy(custom_extension);
-        const std::string custom_ext_upper = boost::to_upper_copy(custom_extension);
-        if (custom_ext_lower == custom_extension) {
-            // Add a lower case version.
-            title = std::string("*") + custom_ext_lower;
-            mask = title;
-            // Add an upper case version.
-            mask  += ";*";
-            mask  += custom_ext_upper;
-        } else if (custom_ext_upper == custom_extension) {
-            // Add an upper case version.
-            title = std::string("*") + custom_ext_upper;
-            mask = title;
-            // Add a lower case version.
-            mask += ";*";
-            mask += custom_ext_lower;
-        } else {
-            // Add the mixed case version only.
-            title = std::string("*") + custom_extension;
-            mask = title;
-        }
-    }
-
-    for (const std::string_view &ext : data.file_extensions)
-        // Only add an extension if it was not added first as the custom extension.
-        if (ext != custom_ext_lower) {
-            if (title.empty()) {
-                title = "*";
-                title += ext;
-                mask  = title;
-            } else {
-                title += ", *";
-                title += ext;
-                mask  += ";*";
-                mask  += ext;
-            }
-            mask += ";*";
-            mask += boost::to_upper_copy(std::string(ext));
-        }
-
-    return GUI::format_wxstr("%s (%s)|%s", data.title, title, mask);
-}
-#endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR
-
-static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); }
-
-#ifdef WIN32
-#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
-static void register_win32_dpi_event()
-{
-    enum { WM_DPICHANGED_ = 0x02e0 };
-
-    wxWindow::MSWRegisterMessageHandler(WM_DPICHANGED_, [](wxWindow *win, WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) {
-        const int dpi = wParam & 0xffff;
-        const auto rect = reinterpret_cast<PRECT>(lParam);
-        const wxRect wxrect(wxPoint(rect->top, rect->left), wxPoint(rect->bottom, rect->right));
-
-        DpiChangedEvent evt(EVT_DPI_CHANGED_SLICER, dpi, wxrect);
-        win->GetEventHandler()->AddPendingEvent(evt);
-
-        return true;
-    });
-}
-#endif // !wxVERSION_EQUAL_OR_GREATER_THAN
-
-static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
-
-static void register_win32_device_notification_event()
-{
-    wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
-        // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only.
-        auto main_frame = dynamic_cast<MainFrame*>(win);
-        auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater();
-        if (plater == nullptr)
-            // Maybe some other top level window like a dialog or maybe a pop-up menu?
-            return true;
-		PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
-        switch (wParam) {
-        case DBT_DEVICEARRIVAL:
-			if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
-		        plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED));
-			else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
-				PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
-//				if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) {
-//					printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name);
-				if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID)
-			        plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name)));
-			}
-            break;
-		case DBT_DEVICEREMOVECOMPLETE:
-			if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
-                plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED));
-			else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
-				PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
-//				if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME)
-//					printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name);
-				if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID)
-        			plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name)));
-			}
-			break;
-        default:
-            break;
-        }
-        return true;
-    });
-
-    wxWindow::MSWRegisterMessageHandler(MainFrame::WM_USER_MEDIACHANGED, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
-        // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only.
-        auto main_frame = dynamic_cast<MainFrame*>(win);
-        auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater();
-        if (plater == nullptr)
-            // Maybe some other top level window like a dialog or maybe a pop-up menu?
-            return true;
-        wchar_t sPath[MAX_PATH];
-        if (lParam == SHCNE_MEDIAINSERTED || lParam == SHCNE_MEDIAREMOVED) {
-            struct _ITEMIDLIST* pidl = *reinterpret_cast<struct _ITEMIDLIST**>(wParam);
-            if (! SHGetPathFromIDList(pidl, sPath)) {
-                BOOST_LOG_TRIVIAL(error) << "MediaInserted: SHGetPathFromIDList failed";
-                return false;
-            }
-        }
-        switch (lParam) {
-        case SHCNE_MEDIAINSERTED:
-        {
-            //printf("SHCNE_MEDIAINSERTED %S\n", sPath);
-            plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED));
-            break;
-        }
-        case SHCNE_MEDIAREMOVED:
-        {
-            //printf("SHCNE_MEDIAREMOVED %S\n", sPath);
-            plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED));
-            break;
-        }
-	    default:
-//          printf("Unknown\n");
-            break;
-	    }
-        return true;
-    });
-
-    wxWindow::MSWRegisterMessageHandler(WM_INPUT, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
-        auto main_frame = dynamic_cast<MainFrame*>(Slic3r::GUI::find_toplevel_parent(win));
-        auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater();
-//        if (wParam == RIM_INPUTSINK && plater != nullptr && main_frame->IsActive()) {
-        if (wParam == RIM_INPUT && plater != nullptr && main_frame->IsActive()) {
-        RAWINPUT raw;
-			UINT rawSize = sizeof(RAWINPUT);
-			::GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER));
-			if (raw.header.dwType == RIM_TYPEHID && plater->get_mouse3d_controller().handle_raw_input_win32(raw.data.hid.bRawData, raw.data.hid.dwSizeHid))
-				return true;
-		}
-        return false;
-    });
-
-	wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
-		COPYDATASTRUCT* copy_data_structure = { 0 };
-		copy_data_structure = (COPYDATASTRUCT*)lParam;
-		if (copy_data_structure->dwData == 1) {
-			LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData;
-			Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments));
-		}
-		return true;
-		});
-}
-#endif // WIN32
-
-static void generic_exception_handle()
-{
-    // Note: Some wxWidgets APIs use wxLogError() to report errors, eg. wxImage
-    // - see https://docs.wxwidgets.org/3.1/classwx_image.html#aa249e657259fe6518d68a5208b9043d0
-    //
-    // wxLogError typically goes around exception handling and display an error dialog some time
-    // after an error is logged even if exception handling and OnExceptionInMainLoop() take place.
-    // This is why we use wxLogError() here as well instead of a custom dialog, because it accumulates
-    // errors if multiple have been collected and displays just one error message for all of them.
-    // Otherwise we would get multiple error messages for one missing png, for example.
-    //
-    // If a custom error message window (or some other solution) were to be used, it would be necessary
-    // to turn off wxLogError() usage in wx APIs, most notably in wxImage
-    // - see https://docs.wxwidgets.org/trunk/classwx_image.html#aa32e5d3507cc0f8c3330135bc0befc6a
-
-    try {
-        throw;
-    } catch (const std::bad_alloc& ex) {
-        // bad_alloc in main thread is most likely fatal. Report immediately to the user (wxLogError would be delayed)
-        // and terminate the app so it is at least certain to happen now.
-        wxString errmsg = wxString::Format(_L("%s has encountered an error. It was likely caused by running out of memory. "
-                              "If you are sure you have enough RAM on your system, this may also be a bug and we would "
-                              "be glad if you reported it.\n\nThe application will now terminate."), SLIC3R_APP_NAME);
-        wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Fatal error"), wxOK | wxICON_ERROR);
-        BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what();
-        std::terminate();
-    } catch (const boost::io::bad_format_string& ex) {
-        wxString errmsg = _L("PrusaSlicer has encountered a localization error. "
-                             "Please report to PrusaSlicer team, what language was active and in which scenario "
-                             "this issue happened. Thank you.\n\nThe application will now terminate.");
-        wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Critical error"), wxOK | wxICON_ERROR);
-        BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what();
-        std::terminate();
-        throw;
-    } catch (const std::exception& ex) {
-        wxLogError(format_wxstr(_L("Internal error: %1%"), ex.what()));
-        BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what();
-        throw;
-    }
-}
-
-void GUI_App::post_init()
-{
-    assert(initialized());
-    if (! this->initialized())
-        throw Slic3r::RuntimeError("Calling post_init() while not yet initialized");
-
-    if (this->init_params->start_as_gcodeviewer) {
-        if (! this->init_params->input_files.empty())
-            this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str()));
-    }
-    else {
-        if (! this->init_params->preset_substitutions.empty())
-            show_substitutions_info(this->init_params->preset_substitutions);
-
-#if 0
-        // Load the cummulative config over the currently active profiles.
-        //FIXME if multiple configs are loaded, only the last one will have an effect.
-        // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc).
-        // As of now only the full configs are supported here.
-        if (!m_print_config.empty())
-            this->gui->mainframe->load_config(m_print_config);
-#endif
-        if (! this->init_params->load_configs.empty())
-            // Load the last config to give it a name at the UI. The name of the preset may be later
-            // changed by loading an AMF or 3MF.
-            //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
-            this->mainframe->load_config_file(this->init_params->load_configs.back());
-        // If loading a 3MF file, the config is loaded from the last one.
-        if (!this->init_params->input_files.empty()) {
-            const std::vector<size_t> res = this->plater()->load_files(this->init_params->input_files, true, true);
-            if (!res.empty() && this->init_params->input_files.size() == 1) {
-                // Update application titlebar when opening a project file
-                const std::string& filename = this->init_params->input_files.front();
-                if (boost::algorithm::iends_with(filename, ".amf") ||
-                    boost::algorithm::iends_with(filename, ".amf.xml") ||
-                    boost::algorithm::iends_with(filename, ".3mf"))
-                    this->plater()->set_project_filename(from_u8(filename));
-            }
-        }
-        if (! this->init_params->extra_config.empty())
-            this->mainframe->load_config(this->init_params->extra_config);
-    }
-
-    // show "Did you know" notification
-    if (app_config->get("show_hints") == "1" && ! is_gcode_viewer())
-        plater_->get_notification_manager()->push_hint_notification(true);
-
-    // The extra CallAfter() is needed because of Mac, where this is the only way
-    // to popup a modal dialog on start without screwing combo boxes.
-    // This is ugly but I honestly found no better way to do it.
-    // Neither wxShowEvent nor wxWindowCreateEvent work reliably.
-    if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater.
-        if (! this->check_updates(false))
-            // Configuration is not compatible and reconfigure was refused by the user. Application is closing.
-            return;
-        CallAfter([this] {
-            bool cw_showed = this->config_wizard_startup();
-            this->preset_updater->sync(preset_bundle);
-            this->app_version_check(false);
-            if (! cw_showed) {
-                // The CallAfter is needed as well, without it, GL extensions did not show.
-                // Also, we only want to show this when the wizard does not, so the new user
-                // sees something else than "we want something" on the first start.
-                show_send_system_info_dialog_if_needed();
-            }
-        });
-    }
-
-    // Set PrusaSlicer version and save to PrusaSlicer.ini or PrusaSlicerGcodeViewer.ini.
-    app_config->set("version", SLIC3R_VERSION);
-    app_config->save();
-
-#ifdef _WIN32
-    // Sets window property to mainframe so other instances can indentify it.
-    OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int);
-#endif //WIN32
-}
-
-IMPLEMENT_APP(GUI_App)
-
-GUI_App::GUI_App(EAppMode mode)
-    : wxApp()
-    , m_app_mode(mode)
-    , m_em_unit(10)
-    , m_imgui(new ImGuiWrapper())
-	, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
-	, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
-{
-	//app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp
-	this->init_app_config();
-    // init app downloader after path to datadir is set
-    m_app_updater = std::make_unique<AppUpdater>();
-}
-
-GUI_App::~GUI_App()
-{
-    if (app_config != nullptr)
-        delete app_config;
-
-    if (preset_bundle != nullptr)
-        delete preset_bundle;
-
-    if (preset_updater != nullptr)
-        delete preset_updater;
-}
-
-// If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
-// Otherwise HTML formatted for the system info dialog.
-std::string GUI_App::get_gl_info(bool for_github)
-{
-    return OpenGLManager::get_gl_info().to_string(for_github);
-}
-
-wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas)
-{
-    return m_opengl_mgr.init_glcontext(canvas);
-}
-
-bool GUI_App::init_opengl()
-{
-#ifdef __linux__
-    bool status = m_opengl_mgr.init_gl();
-    m_opengl_initialized = true;
-    return status;
-#else
-    return m_opengl_mgr.init_gl();
-#endif
-}
-
-// gets path to PrusaSlicer.ini, returns semver from first line comment
-static boost::optional<Semver> parse_semver_from_ini(std::string path)
-{
-    std::ifstream stream(path);
-    std::stringstream buffer;
-    buffer << stream.rdbuf();
-    std::string body = buffer.str();
-    size_t start = body.find("PrusaSlicer ");
-    if (start == std::string::npos)
-        return boost::none;
-    body = body.substr(start + 12);
-    size_t end = body.find_first_of(" \n");
-    if (end < body.size())
-        body.resize(end);
-    return Semver::parse(body);
-}
-
-void GUI_App::init_app_config()
-{
-	// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
-//    SetAppName(SLIC3R_APP_KEY);
-	SetAppName(SLIC3R_APP_KEY "-alpha");
-//    SetAppName(SLIC3R_APP_KEY "-beta");
-
-
-//	SetAppDisplayName(SLIC3R_APP_NAME);
-
-	// Set the Slic3r data directory at the Slic3r XS module.
-	// Unix: ~/ .Slic3r
-	// Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
-	// Mac : "~/Library/Application Support/Slic3r"
-
-    if (data_dir().empty()) {
-        #ifndef __linux__
-            set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
-        #else
-            // Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}.
-            // https://github.com/prusa3d/PrusaSlicer/issues/2911
-            wxString dir;
-            if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() )
-                dir = wxFileName::GetHomeDir() + wxS("/.config");
-            set_data_dir((dir + "/" + GetAppName()).ToUTF8().data());
-        #endif
-    } else {
-        m_datadir_redefined = true;
-    }
-
-	if (!app_config)
-        app_config = new AppConfig(is_editor() ? AppConfig::EAppMode::Editor : AppConfig::EAppMode::GCodeViewer);
-
-	// load settings
-	m_app_conf_exists = app_config->exists();
-	if (m_app_conf_exists) {
-        std::string error = app_config->load();
-        if (!error.empty()) {
-            // Error while parsing config file. We'll customize the error message and rethrow to be displayed.
-            if (is_editor()) {
-                throw Slic3r::RuntimeError(
-                    _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. "
-                        "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") +
-                    "\n\n" + app_config->config_path() + "\n\n" + error);
-            }
-            else {
-                throw Slic3r::RuntimeError(
-                    _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. "
-                        "Try to manually delete the file to recover from the error.") +
-                    "\n\n" + app_config->config_path() + "\n\n" + error);
-            }
-        }
-    }
-}
-
-// returns old config path to copy from if such exists,
-// returns an empty string if such config path does not exists or if it cannot be loaded.
-std::string GUI_App::check_older_app_config(Semver current_version, bool backup)
-{
-    std::string older_data_dir_path;
-
-    // If the config folder is redefined - do not check
-    if (m_datadir_redefined)
-        return {};
-
-    // find other version app config (alpha / beta / release)
-    std::string             config_path = app_config->config_path();
-    boost::filesystem::path parent_file_path(config_path);
-    std::string             filename = parent_file_path.filename().string();
-    parent_file_path.remove_filename().remove_filename();
-
-    std::vector<boost::filesystem::path> candidates;
-
-    if (SLIC3R_APP_KEY "-alpha" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-alpha" / filename);
-    if (SLIC3R_APP_KEY "-beta" != GetAppName())  candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-beta" / filename);
-    if (SLIC3R_APP_KEY != GetAppName())          candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY / filename);
-
-    Semver last_semver = current_version;
-    for (const auto& candidate : candidates) {
-        if (boost::filesystem::exists(candidate)) {
-            // parse
-            boost::optional<Semver>other_semver = parse_semver_from_ini(candidate.string());
-            if (other_semver && *other_semver > last_semver) {
-                last_semver = *other_semver;
-                older_data_dir_path = candidate.parent_path().string();
-            }
-        }
-    }
-    if (older_data_dir_path.empty())
-        return {};
-    BOOST_LOG_TRIVIAL(info) << "last app config file used: " << older_data_dir_path;
-    // ask about using older data folder
-
-    InfoDialog msg(nullptr
-        , format_wxstr(_L("You are opening %1% version %2%."), SLIC3R_APP_NAME, SLIC3R_VERSION)
-        , backup ? 
-        format_wxstr(_L(
-            "The active configuration was created by <b>%1% %2%</b>,"
-            "\nwhile a newer configuration was found in <b>%3%</b>"
-            "\ncreated by <b>%1% %4%</b>."
-            "\n\nShall the newer configuration be imported?"
-            "\nIf so, your active configuration will be backed up before importing the new configuration."
-        )
-            , SLIC3R_APP_NAME, current_version.to_string(), older_data_dir_path, last_semver.to_string())
-        : format_wxstr(_L(
-            "An existing configuration was found in <b>%3%</b>"
-            "\ncreated by <b>%1% %2%</b>."
-            "\n\nShall this configuration be imported?"
-        )
-            , SLIC3R_APP_NAME, last_semver.to_string(), older_data_dir_path)
-        , true, wxYES_NO);
-
-    if (backup) {
-        msg.SetButtonLabel(wxID_YES, _L("Import"));
-        msg.SetButtonLabel(wxID_NO, _L("Don't import"));
-    }
-
-    if (msg.ShowModal() == wxID_YES) {
-        std::string snapshot_id;
-        if (backup) {
-            const Config::Snapshot* snapshot{ nullptr };
-            if (! GUI::Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_USER, "",
-                _u8L("Continue and import newer configuration?"), &snapshot))
-                return {};
-            if (snapshot) {
-                // Save snapshot ID before loading the alternate AppConfig, as loading the alternate AppConfig may fail.
-                snapshot_id = snapshot->id;
-                assert(! snapshot_id.empty());
-                app_config->set("on_snapshot", snapshot_id);
-            } else
-                BOOST_LOG_TRIVIAL(error) << "Failed to take congiguration snapshot";
-        }
-
-        // load app config from older file
-        std::string error = app_config->load((boost::filesystem::path(older_data_dir_path) / filename).string());
-        if (!error.empty()) {
-            // Error while parsing config file. We'll customize the error message and rethrow to be displayed.
-            if (is_editor()) {
-                throw Slic3r::RuntimeError(
-                    _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. "
-                        "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") +
-                    "\n\n" + app_config->config_path() + "\n\n" + error);
-            }
-            else {
-                throw Slic3r::RuntimeError(
-                    _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. "
-                        "Try to manually delete the file to recover from the error.") +
-                    "\n\n" + app_config->config_path() + "\n\n" + error);
-            }
-        }
-        if (!snapshot_id.empty())
-            app_config->set("on_snapshot", snapshot_id);
-        m_app_conf_exists = true;
-        return older_data_dir_path;
-    }
-    return {};
-}
-
-void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path)
-{
-    BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path; 
-    m_single_instance_checker = std::make_unique<wxSingleInstanceChecker>(boost::nowide::widen(name), boost::nowide::widen(path));
-}
-
-bool GUI_App::OnInit()
-{
-    try {
-        return on_init_inner();
-    } catch (const std::exception&) {
-        generic_exception_handle();
-        return false;
-    }
-}
-
-bool GUI_App::on_init_inner()
-{
-    // Set initialization of image handlers before any UI actions - See GH issue #7469
-    wxInitAllImageHandlers();
-
-#if defined(_WIN32) && ! defined(_WIN64)
-    // Win32 32bit build.
-    if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") {
-        RichMessageDialog dlg(nullptr,
-            _L("You are running a 32 bit build of PrusaSlicer on 64-bit Windows."
-                "\n32 bit build of PrusaSlicer will likely not be able to utilize all the RAM available in the system."
-                "\nPlease download and install a 64 bit build of PrusaSlicer from https://www.prusa3d.cz/prusaslicer/."
-                "\nDo you wish to continue?"),
-            "PrusaSlicer", wxICON_QUESTION | wxYES_NO);
-        if (dlg.ShowModal() != wxID_YES)
-            return false;
-    }
-#endif // _WIN64
-
-    // Forcing back menu icons under gtk2 and gtk3. Solution is based on:
-    // https://docs.gtk.org/gtk3/class.Settings.html
-    // see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb
-    // TODO: Find workaround for GTK4
-#if defined(__WXGTK20__) || defined(__WXGTK3__)
-    g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL);
-#endif
-
-    // Verify resources path
-    const wxString resources_dir = from_u8(Slic3r::resources_dir());
-    wxCHECK_MSG(wxDirExists(resources_dir), false,
-        wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir));
-
-#ifdef __linux__
-    if (! check_old_linux_datadir(GetAppName())) {
-        std::cerr << "Quitting, user chose to move their data to new location." << std::endl;
-        return false;
-    }
-#endif
-
-    // Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
-//    wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0);
-    // Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible
-    // performance when working on high resolution multi-display setups.
-//    wxSystemOptions::SetOption("msw.notebook.themed-background", 0);
-
-//     Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION;
-
-    // !!! Initialization of UI settings as a language, application color mode, fonts... have to be done before first UI action.
-    // Like here, before the show InfoDialog in check_older_app_config()
-
-    // If load_language() fails, the application closes.
-    load_language(wxString(), true);
-#ifdef _MSW_DARK_MODE
-    bool init_dark_color_mode = app_config->get("dark_color_mode") == "1";
-    bool init_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1";
-    NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled);
-#endif
-    // initialize label colors and fonts
-    init_label_colours();
-    init_fonts();
-
-    std::string older_data_dir_path;
-    if (m_app_conf_exists) {
-        if (app_config->orig_version().valid() && app_config->orig_version() < *Semver::parse(SLIC3R_VERSION))
-            // Only copying configuration if it was saved with a newer slicer than the one currently running.
-            older_data_dir_path = check_older_app_config(app_config->orig_version(), true);
-    } else {
-        // No AppConfig exists, fresh install. Always try to copy from an alternate location, don't make backup of the current configuration.
-        older_data_dir_path = check_older_app_config(Semver(), false);
-    }
-
-#ifdef _MSW_DARK_MODE
-    // app_config can be updated in check_older_app_config(), so check if dark_color_mode and sys_menu_enabled was changed
-    if (bool new_dark_color_mode = app_config->get("dark_color_mode") == "1";
-        init_dark_color_mode != new_dark_color_mode) {
-        NppDarkMode::SetDarkMode(new_dark_color_mode);
-        init_label_colours();
-        update_label_colours_from_appconfig();
-    }
-    if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1";
-        init_sys_menu_enabled != new_sys_menu_enabled)
-        NppDarkMode::SetSystemMenuForApp(new_sys_menu_enabled);
-#endif
-
-    if (is_editor()) {
-        std::string msg = Http::tls_global_init();
-        std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location");
-        bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store();
-
-        if (!msg.empty() && !ssl_accept) {
-            RichMessageDialog
-                dlg(nullptr,
-                    wxString::Format(_L("%s\nDo you want to continue?"), msg),
-                    "PrusaSlicer", wxICON_QUESTION | wxYES_NO);
-            dlg.ShowCheckBox(_L("Remember my choice"));
-            if (dlg.ShowModal() != wxID_YES) return false;
-
-            app_config->set("tls_cert_store_accepted",
-                dlg.IsCheckBoxChecked() ? "yes" : "no");
-            app_config->set("tls_accepted_cert_store_location",
-                dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : "");
-        }
-    }
-
-    SplashScreen* scrn = nullptr;
-    if (app_config->get("show_splash_screen") == "1") {
-        // make a bitmap with dark grey banner on the left side
-        wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var(is_editor() ? "splashscreen.jpg" : "splashscreen-gcodepreview.jpg")), wxBITMAP_TYPE_JPEG));
-
-        // Detect position (display) to show the splash screen
-        // Now this position is equal to the mainframe position
-        wxPoint splashscreen_pos = wxDefaultPosition;
-        bool default_splashscreen_pos = true;
-        if (app_config->has("window_mainframe") && app_config->get("restore_win_position") == "1") {
-            auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe"));
-            default_splashscreen_pos = metrics == boost::none;
-            if (!default_splashscreen_pos)
-                splashscreen_pos = metrics->get_rect().GetPosition();
-        }
-
-        if (!default_splashscreen_pos) {
-            // workaround for crash related to the positioning of the window on secondary monitor
-            get_app_config()->set("restore_win_position", "crashed_at_splashscreen_pos");
-            get_app_config()->save();
-        }
-
-        // create splash screen with updated bmp
-        scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("PrusaSlicer", nullptr, 400), 
-                                wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos);
-
-        if (!default_splashscreen_pos)
-            // revert "restore_win_position" value if application wasn't crashed
-            get_app_config()->set("restore_win_position", "1");
-#ifndef __linux__
-        wxYield();
-#endif
-        scrn->SetText(_L("Loading configuration")+ dots);
-    }
-
-    preset_bundle = new PresetBundle();
-
-    // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory
-    // supplied as argument to --datadir; in that case we should still run the wizard
-    preset_bundle->setup_directories();
-    
-    if (! older_data_dir_path.empty()) {
-        preset_bundle->import_newer_configs(older_data_dir_path);
-        app_config->save();
-    }
-
-    if (is_editor()) {
-#ifdef __WXMSW__ 
-        if (app_config->get("associate_3mf") == "1")
-            associate_3mf_files();
-        if (app_config->get("associate_stl") == "1")
-            associate_stl_files();
-#endif // __WXMSW__
-
-        preset_updater = new PresetUpdater();
-        Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this);
-        Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
-            if (this->plater_ != nullptr && app_config->get("notify_release") == "all") {
-                std::string evt_string = into_u8(evt.GetString());
-                if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) {
-                    auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable);
-                    this->plater_->get_notification_manager()->push_notification( notif_type
-                        , NotificationManager::NotificationLevel::ImportantNotificationLevel
-                        , Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string)
-                        , _u8L("See Releases page.")
-                        , [](wxEvtHandler* evnthndlr) {wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }
-                    );
-                }
-            }
-            });
-        Bind(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, [this](const wxCommandEvent& evt) {
-            //lm:This does not force a render. The progress bar only updateswhen the mouse is moved.
-            if (this->plater_ != nullptr)
-                this->plater_->get_notification_manager()->set_download_progress_percentage((float)std::stoi(into_u8(evt.GetString())) / 100.f );
-        });
-
-        Bind(EVT_SLIC3R_APP_DOWNLOAD_FAILED, [this](const wxCommandEvent& evt) {
-            if (this->plater_ != nullptr)
-                this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::AppDownload);
-            if(!evt.GetString().IsEmpty())
-                show_error(nullptr, evt.GetString());
-        });
-
-        Bind(EVT_SLIC3R_APP_OPEN_FAILED, [](const wxCommandEvent& evt) {
-            show_error(nullptr, evt.GetString());
-        });
-    }
-    else {
-#ifdef __WXMSW__ 
-        if (app_config->get("associate_gcode") == "1")
-            associate_gcode_files();
-#endif // __WXMSW__
-    }
-
-    // Suppress the '- default -' presets.
-    preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
-    try {
-        // Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only.
-        // If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force
-        // installation of a compatible system preset, thus nullifying the system preset substitutions.
-        init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
-    } catch (const std::exception &ex) {
-        show_error(nullptr, ex.what());
-    }
-
-#ifdef WIN32
-#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
-    register_win32_dpi_event();
-#endif // !wxVERSION_EQUAL_OR_GREATER_THAN
-    register_win32_device_notification_event();
-#endif // WIN32
-
-    // Let the libslic3r know the callback, which will translate messages on demand.
-    Slic3r::I18N::set_translate_callback(libslic3r_translate_callback);
-
-    // application frame
-    if (scrn && is_editor())
-        scrn->SetText(_L("Preparing settings tabs") + dots);
-
-    mainframe = new MainFrame();
-    // hide settings tabs after first Layout
-    if (is_editor())
-        mainframe->select_tab(size_t(0));
-
-    sidebar().obj_list()->init_objects(); // propagate model objects to object list
-//     update_mode(); // !!! do that later
-    SetTopWindow(mainframe);
-
-    plater_->init_notification_manager();
-
-    m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
-
-    if (is_gcode_viewer()) {
-        mainframe->update_layout();
-        if (plater_ != nullptr)
-            // ensure the selected technology is ptFFF
-            plater_->set_printer_technology(ptFFF);
-    }
-    else
-        load_current_presets();
-
-    // Save the active profiles as a "saved into project".
-    update_saved_preset_from_current_preset();
-
-    if (plater_ != nullptr) {
-        // Save the names of active presets and project specific config into ProjectDirtyStateManager.
-        plater_->reset_project_dirty_initial_presets();
-        // Update Project dirty state, update application title bar.
-        plater_->update_project_dirty_from_presets();
-    }
-
-    mainframe->Show(true);
-
-    obj_list()->set_min_height();
-
-    update_mode(); // update view mode after fix of the object_list size
-
-#ifdef __APPLE__
-    other_instance_message_handler()->bring_instance_forward();
-#endif //__APPLE__
-
-    Bind(wxEVT_IDLE, [this](wxIdleEvent& event)
-    {
-        if (! plater_)
-            return;
-
-        this->obj_manipul()->update_if_dirty();
-
-        // An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT
-        // and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized.
-#ifdef __linux__
-        if (! m_post_initialized && m_opengl_initialized) {
-#else
-        if (! m_post_initialized) {
-#endif
-            m_post_initialized = true;
-#ifdef WIN32
-            this->mainframe->register_win32_callbacks();
-#endif
-            this->post_init();
-        }
-
-        if (m_post_initialized && app_config->dirty() && app_config->get("autosave") == "1")
-            app_config->save();
-    });
-
-    m_initialized = true;
-
-    if (const std::string& crash_reason = app_config->get("restore_win_position");
-        boost::starts_with(crash_reason,"crashed"))
-    {
-        wxString preferences_item = _L("Restore window position on start");
-        InfoDialog dialog(nullptr,
-            _L("PrusaSlicer started after a crash"),
-            format_wxstr(_L("PrusaSlicer crashed last time when attempting to set window position.\n"
-                "We are sorry for the inconvenience, it unfortunately happens with certain multiple-monitor setups.\n"
-                "More precise reason for the crash: \"%1%\".\n"
-                "For more information see our GitHub issue tracker: \"%2%\" and \"%3%\"\n\n"
-                "To avoid this problem, consider disabling \"%4%\" in \"Preferences\". "
-                "Otherwise, the application will most likely crash again next time."),
-                "<b>" + from_u8(crash_reason) + "</b>",
-                "<a href=http://github.com/prusa3d/PrusaSlicer/issues/2939>#2939</a>",
-                "<a href=http://github.com/prusa3d/PrusaSlicer/issues/5573>#5573</a>",
-                "<b>" + preferences_item + "</b>"),
-            true, wxYES_NO);
-
-        dialog.SetButtonLabel(wxID_YES, format_wxstr(_L("Disable \"%1%\""), preferences_item));
-        dialog.SetButtonLabel(wxID_NO,  format_wxstr(_L("Leave \"%1%\" enabled") , preferences_item));
-        
-        auto answer = dialog.ShowModal();
-        if (answer == wxID_YES)
-            app_config->set("restore_win_position", "0");
-        else if (answer == wxID_NO)
-            app_config->set("restore_win_position", "1");
-        app_config->save();
-    }
-
-    return true;
-}
-
-unsigned GUI_App::get_colour_approx_luma(const wxColour &colour)
-{
-    double r = colour.Red();
-    double g = colour.Green();
-    double b = colour.Blue();
-
-    return std::round(std::sqrt(
-        r * r * .241 +
-        g * g * .691 +
-        b * b * .068
-        ));
-}
-
-bool GUI_App::dark_mode()
-{
-#if __APPLE__
-    // The check for dark mode returns false positive on 10.12 and 10.13,
-    // which allowed setting dark menu bar and dock area, which is
-    // is detected as dark mode. We must run on at least 10.14 where the
-    // proper dark mode was first introduced.
-    return wxPlatformInfo::Get().CheckOSVersion(10, 14) && mac_dark_mode();
-#else
-    return wxGetApp().app_config->get("dark_color_mode") == "1" ? true : check_dark_mode();
-    //const unsigned luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
-    //return luma < 128;
-#endif
-}
-
-const wxColour GUI_App::get_label_default_clr_system()
-{
-    return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57);
-}
-
-const wxColour GUI_App::get_label_default_clr_modified()
-{
-    return dark_mode() ? wxColour(253, 111, 40) : wxColour(252, 77, 1);
-}
-
-void GUI_App::init_label_colours()
-{
-    m_color_label_modified          = get_label_default_clr_modified();
-    m_color_label_sys               = get_label_default_clr_system();
-
-    bool is_dark_mode = dark_mode();
-#ifdef _WIN32
-    m_color_label_default           = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
-    m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT);
-    m_color_highlight_default       = is_dark_mode ? wxColour(78, 78, 78)   : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT);
-    m_color_hovered_btn_label       = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1);
-    m_color_default_btn_label       = is_dark_mode ? wxColour(255, 181, 100): wxColour(203, 61, 0);
-    m_color_selected_btn_bg         = is_dark_mode ? wxColour(95, 73, 62)   : wxColour(228, 220, 216);
-#else
-    m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
-#endif
-    m_color_window_default          = is_dark_mode ? wxColour(43, 43, 43)   : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
-}
-
-void GUI_App::update_label_colours_from_appconfig()
-{
-    if (app_config->has("label_clr_sys")) {
-        auto str = app_config->get("label_clr_sys");
-        if (str != "")
-            m_color_label_sys = wxColour(str);
-    }
-
-    if (app_config->has("label_clr_modified")) {
-        auto str = app_config->get("label_clr_modified");
-        if (str != "")
-            m_color_label_modified = wxColour(str);
-    }
-}
-
-void GUI_App::update_label_colours()
-{
-    for (Tab* tab : tabs_list)
-        tab->update_label_colours();
-}
-
-#ifdef _WIN32
-static bool is_focused(HWND hWnd)
-{
-    HWND hFocusedWnd = ::GetFocus();
-    return hFocusedWnd && hWnd == hFocusedWnd;
-}
-
-static bool is_default(wxWindow* win)
-{
-    wxTopLevelWindow* tlw = find_toplevel_parent(win);
-    if (!tlw)
-        return false;
-        
-    return win == tlw->GetDefaultItem();
-}
-#endif
-
-void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/)
-{
-#ifdef _WIN32
-    bool is_focused_button = false;
-    bool is_default_button = false;
-    if (wxButton* btn = dynamic_cast<wxButton*>(window)) {
-        if (!(btn->GetWindowStyle() & wxNO_BORDER)) {
-            btn->SetWindowStyle(btn->GetWindowStyle() | wxNO_BORDER);
-            highlited = true;
-        }
-        // button marking
-        {
-            auto mark_button = [this, btn, highlited](const bool mark) {
-                if (btn->GetLabel().IsEmpty())
-                    btn->SetBackgroundColour(mark ? m_color_selected_btn_bg   : highlited ? m_color_highlight_default : m_color_window_default);
-                else
-                    btn->SetForegroundColour(mark ? m_color_hovered_btn_label : (is_default(btn) ? m_color_default_btn_label : m_color_label_default));
-                btn->Refresh();
-                btn->Update();
-            };
-
-            // hovering
-            btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); });
-            btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); });
-            // focusing
-            btn->Bind(wxEVT_SET_FOCUS,    [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); });
-            btn->Bind(wxEVT_KILL_FOCUS,   [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); });
-
-            is_focused_button = is_focused(btn->GetHWND());
-            is_default_button = is_default(btn);
-            if (is_focused_button || is_default_button)
-                mark_button(is_focused_button);
-        }
-    }
-    else if (wxTextCtrl* text = dynamic_cast<wxTextCtrl*>(window)) {
-        if (text->GetBorder() != wxBORDER_SIMPLE)
-            text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE);
-    }
-    else if (wxCheckListBox* list = dynamic_cast<wxCheckListBox*>(window)) {
-        list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE);
-        list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
-        for (size_t i = 0; i < list->GetCount(); i++)
-            if (wxOwnerDrawn* item = list->GetItem(i)) {
-                item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
-                item->SetTextColour(m_color_label_default);
-            }
-        return;
-    }
-    else if (dynamic_cast<wxListBox*>(window))
-        window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE);
-
-    if (!just_font)
-        window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
-    if (!is_focused_button && !is_default_button)
-        window->SetForegroundColour(m_color_label_default);
-#endif
-}
-
-// recursive function for scaling fonts for all controls in Window
-#ifdef _WIN32
-static void update_dark_children_ui(wxWindow* window, bool just_buttons_update = false)
-{
-    bool is_btn = dynamic_cast<wxButton*>(window) != nullptr;
-    if (!(just_buttons_update && !is_btn))
-        wxGetApp().UpdateDarkUI(window, is_btn);
-
-    auto children = window->GetChildren();
-    for (auto child : children) {        
-        update_dark_children_ui(child);
-    }
-}
-#endif
-
-// Note: Don't use this function for Dialog contains ScalableButtons
-void GUI_App::UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update/* = false*/)
-{
-#ifdef _WIN32
-    update_dark_children_ui(dlg, just_buttons_update);
-#endif
-}
-void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/)
-{
-#ifdef _WIN32
-    UpdateDarkUI(dvc, highlited ? dark_mode() : false);
-#ifdef _MSW_DARK_MODE
-    dvc->RefreshHeaderDarkMode(&m_normal_font);
-#endif //_MSW_DARK_MODE
-    if (dvc->HasFlag(wxDV_ROW_LINES))
-        dvc->SetAlternateRowColour(m_color_highlight_default);
-    if (dvc->GetBorder() != wxBORDER_SIMPLE)
-        dvc->SetWindowStyle(dvc->GetWindowStyle() | wxBORDER_SIMPLE);
-#endif
-}
-
-void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent)
-{
-#ifdef _WIN32
-    wxGetApp().UpdateDarkUI(parent);
-
-    auto children = parent->GetChildren();
-    for (auto child : children) {
-        if (dynamic_cast<wxStaticText*>(child))
-            child->SetForegroundColour(m_color_label_default);
-    }
-#endif
-}
-
-void GUI_App::init_fonts()
-{
-    m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
-    m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold();
-    m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
-
-#ifdef __WXMAC__
-    m_small_font.SetPointSize(11);
-    m_bold_font.SetPointSize(13);
-#endif /*__WXMAC__*/
-
-    // wxSYS_OEM_FIXED_FONT and wxSYS_ANSI_FIXED_FONT use the same as
-    // DEFAULT in wxGtk. Use the TELETYPE family as a work-around
-    m_code_font = wxFont(wxFontInfo().Family(wxFONTFAMILY_TELETYPE));
-    m_code_font.SetPointSize(m_normal_font.GetPointSize());
-}
-
-void GUI_App::update_fonts(const MainFrame *main_frame)
-{
-    /* Only normal and bold fonts are used for an application rescale,
-     * because of under MSW small and normal fonts are the same.
-     * To avoid same rescaling twice, just fill this values
-     * from rescaled MainFrame
-     */
-	if (main_frame == nullptr)
-		main_frame = this->mainframe;
-    m_normal_font   = main_frame->normal_font();
-    m_small_font    = m_normal_font;
-    m_bold_font     = main_frame->normal_font().Bold();
-    m_link_font     = m_bold_font.Underlined();
-    m_em_unit       = main_frame->em_unit();
-    m_code_font.SetPointSize(m_normal_font.GetPointSize());
-}
-
-void GUI_App::set_label_clr_modified(const wxColour& clr) 
-{
-    if (m_color_label_modified == clr)
-        return;
-    m_color_label_modified = clr;
-    const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue()));
-    app_config->set("label_clr_modified", str);
-    app_config->save();
-}
-
-void GUI_App::set_label_clr_sys(const wxColour& clr)
-{
-    if (m_color_label_sys == clr)
-        return;
-    m_color_label_sys = clr;
-    const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue()));
-    app_config->set("label_clr_sys", str);
-    app_config->save();
-}
-
-bool GUI_App::tabs_as_menu() const
-{
-    return app_config->get("tabs_as_menu") == "1"; // || dark_mode();
-}
-
-wxSize GUI_App::get_min_size() const
-{
-    return wxSize(76*m_em_unit, 49 * m_em_unit);
-}
-
-float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const
-{
-#ifdef __APPLE__
-    const float icon_sc = 1.0f; // for Retina display will be used its own scale
-#else
-    const float icon_sc = m_em_unit*0.1f;
-#endif // __APPLE__
-
-    const std::string& use_val  = app_config->get("use_custom_toolbar_size");
-    const std::string& val      = app_config->get("custom_toolbar_size");
-    const std::string& auto_val = app_config->get("auto_toolbar_size");
-
-    if (val.empty() || auto_val.empty() || use_val.empty())
-        return icon_sc;
-
-    int int_val = use_val == "0" ? 100 : atoi(val.c_str());
-    // correct value in respect to auto_toolbar_size
-    int_val = std::min(atoi(auto_val.c_str()), int_val);
-
-    if (is_limited && int_val < 50)
-        int_val = 50;
-
-    return 0.01f * int_val * icon_sc;
-}
-
-void GUI_App::set_auto_toolbar_icon_scale(float scale) const
-{
-#ifdef __APPLE__
-    const float icon_sc = 1.0f; // for Retina display will be used its own scale
-#else
-    const float icon_sc = m_em_unit * 0.1f;
-#endif // __APPLE__
-
-    long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100);
-    std::string val = std::to_string(int_val);
-
-    app_config->set("auto_toolbar_size", val);
-}
-
-// check user printer_presets for the containing information about "Print Host upload"
-void GUI_App::check_printer_presets()
-{
-    std::vector<std::string> preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers);
-    if (preset_names.empty())
-        return;
-
-    wxString msg_text =  _L("You have the following presets with saved options for \"Print Host upload\"") + ":";
-    for (const std::string& preset_name : preset_names)
-        msg_text += "\n    \"" + from_u8(preset_name) + "\",";
-    msg_text.RemoveLast();
-    msg_text += "\n\n" + _L("But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n"
-                            "Settings will be available in physical printers settings.") + "\n\n" +
-                         _L("By default new Printer devices will be named as \"Printer N\" during its creation.\n"
-                            "Note: This name can be changed later from the physical printers settings");
-
-    //wxMessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal();
-    MessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal();
-
-    preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers);
-}
-
-void GUI_App::recreate_GUI(const wxString& msg_name)
-{
-    m_is_recreating_gui = true;
-
-    mainframe->shutdown();
-
-    wxProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE);
-    dlg.Pulse();
-    dlg.Update(10, _L("Recreating") + dots);
-
-    MainFrame *old_main_frame = mainframe;
-    mainframe = new MainFrame();
-    if (is_editor())
-        // hide settings tabs after first Layout
-        mainframe->select_tab(size_t(0));
-    // Propagate model objects to object list.
-    sidebar().obj_list()->init_objects();
-    SetTopWindow(mainframe);
-
-    dlg.Update(30, _L("Recreating") + dots);
-    old_main_frame->Destroy();
-
-    dlg.Update(80, _L("Loading of current presets") + dots);
-    m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
-    load_current_presets();
-    mainframe->Show(true);
-
-    dlg.Update(90, _L("Loading of a mode view") + dots);
-
-    obj_list()->set_min_height();
-    update_mode();
-
-    // #ys_FIXME_delete_after_testing  Do we still need this  ?
-//     CallAfter([]() {
-//         // Run the config wizard, don't offer the "reset user profile" checkbox.
-//         config_wizard_startup(true);
-//     });
-
-    m_is_recreating_gui = false;
-}
-
-void GUI_App::system_info()
-{
-    SysInfoDialog dlg;
-    dlg.ShowModal();
-}
-
-void GUI_App::keyboard_shortcuts()
-{
-    KBShortcutsDialog dlg;
-    dlg.ShowModal();
-}
-
-// static method accepting a wxWindow object as first parameter
-bool GUI_App::catch_error(std::function<void()> cb,
-    //                       wxMessageDialog* message_dialog,
-    const std::string& err /*= ""*/)
-{
-    if (!err.empty()) {
-        if (cb)
-            cb();
-        //         if (message_dialog)
-        //             message_dialog->(err, "Error", wxOK | wxICON_ERROR);
-        show_error(/*this*/nullptr, err);
-        return true;
-    }
-    return false;
-}
-
-// static method accepting a wxWindow object as first parameter
-void fatal_error(wxWindow* parent)
-{
-    show_error(parent, "");
-    //     exit 1; // #ys_FIXME
-}
-
-#ifdef _WIN32
-
-#ifdef _MSW_DARK_MODE
-static void update_scrolls(wxWindow* window)
-{
-    wxWindowList::compatibility_iterator node = window->GetChildren().GetFirst();
-    while (node)
-    {
-        wxWindow* win = node->GetData();
-        if (dynamic_cast<wxScrollHelper*>(win) ||
-            dynamic_cast<wxTreeCtrl*>(win) ||
-            dynamic_cast<wxTextCtrl*>(win))
-            NppDarkMode::SetDarkExplorerTheme(win->GetHWND());
-
-        update_scrolls(win);
-        node = node->GetNext();
-    }
-}
-#endif //_MSW_DARK_MODE
-
-
-#ifdef _MSW_DARK_MODE
-void GUI_App::force_menu_update()
-{
-    NppDarkMode::SetSystemMenuForApp(app_config->get("sys_menu_enabled") == "1");
-}
-#endif //_MSW_DARK_MODE
-
-void GUI_App::force_colors_update()
-{
-#ifdef _MSW_DARK_MODE
-    NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1");
-    if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl())
-        NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND);
-    NppDarkMode::SetDarkTitleBar(mainframe->GetHWND());
-    NppDarkMode::SetDarkTitleBar(mainframe->m_settings_dialog.GetHWND());
-#endif //_MSW_DARK_MODE
-    m_force_colors_update = true;
-}
-#endif //_WIN32
-
-// Called after the Preferences dialog is closed and the program settings are saved.
-// Update the UI based on the current preferences.
-void GUI_App::update_ui_from_settings()
-{
-    update_label_colours();
-#ifdef _WIN32
-    // Upadte UI colors before Update UI from settings
-    if (m_force_colors_update) {
-        m_force_colors_update = false;
-        mainframe->force_color_changed();
-        mainframe->diff_dialog.force_color_changed();
-        mainframe->preferences_dialog->force_color_changed();
-        mainframe->printhost_queue_dlg()->force_color_changed();
-#ifdef _MSW_DARK_MODE
-        update_scrolls(mainframe);
-        if (mainframe->is_dlg_layout()) {
-            // update for tabs bar
-            UpdateDarkUI(&mainframe->m_settings_dialog);
-            mainframe->m_settings_dialog.Fit();
-            mainframe->m_settings_dialog.Refresh();
-            // update scrollbars
-            update_scrolls(&mainframe->m_settings_dialog);
-        }
-#endif //_MSW_DARK_MODE
-    }
-#endif
-    mainframe->update_ui_from_settings();
-}
-
-void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized)
-{
-    const std::string name = into_u8(window->GetName());
-
-    window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) {
-        window_pos_save(window, name);
-        event.Skip();
-    });
-
-    window_pos_restore(window, name, default_maximized);
-
-    on_window_geometry(window, [=]() {
-        window_pos_sanitize(window);
-    });
-}
-
-void GUI_App::load_project(wxWindow *parent, wxString& input_file) const
-{
-    input_file.Clear();
-    wxFileDialog dialog(parent ? parent : GetTopWindow(),
-        _L("Choose one file (3MF/AMF):"),
-        app_config->get_last_dir(), "",
-        file_wildcards(FT_PROJECT), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
-
-    if (dialog.ShowModal() == wxID_OK)
-        input_file = dialog.GetPath();
-}
-
-void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const
-{
-    input_files.Clear();
-    wxFileDialog dialog(parent ? parent : GetTopWindow(),
-        _L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):"),
-        from_u8(app_config->get_last_dir()), "",
-        file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
-
-    if (dialog.ShowModal() == wxID_OK)
-        dialog.GetPaths(input_files);
-}
-
-void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const
-{
-    input_file.Clear();
-    wxFileDialog dialog(parent ? parent : GetTopWindow(),
-        _L("Choose one file (GCODE/.GCO/.G/.ngc/NGC):"),
-        app_config->get_last_dir(), "",
-        file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
-
-    if (dialog.ShowModal() == wxID_OK)
-        input_file = dialog.GetPath();
-}
-
-bool GUI_App::switch_language()
-{
-    if (select_language()) {
-        recreate_GUI(_L("Changing of an application language") + dots);
-        return true;
-    } else {
-        return false;
-    }
-}
-
-#ifdef __linux__
-static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language,
-                                                                const wxLanguageInfo* system_language)
-{
-    constexpr size_t max_len = 50;
-    char path[max_len] = "";
-    std::vector<std::string> locales;
-    const std::string lang_prefix = into_u8(language->CanonicalName.BeforeFirst('_'));
-
-    // Call locale -a so we can parse the output to get the list of available locales
-    // We expect lines such as "en_US.utf8". Pick ones starting with the language code
-    // we are switching to. Lines with different formatting will be removed later.
-    FILE* fp = popen("locale -a", "r");
-    if (fp != NULL) {
-        while (fgets(path, max_len, fp) != NULL) {
-            std::string line(path);
-            line = line.substr(0, line.find('\n'));
-            if (boost::starts_with(line, lang_prefix))
-                locales.push_back(line);
-        }
-        pclose(fp);
-    }
-
-    // locales now contain all candidates for this language.
-    // Sort them so ones containing anything about UTF-8 are at the end.
-    std::sort(locales.begin(), locales.end(), [](const std::string& a, const std::string& b)
-    {
-        auto has_utf8 = [](const std::string & s) {
-            auto S = boost::to_upper_copy(s);
-            return S.find("UTF8") != std::string::npos || S.find("UTF-8") != std::string::npos;
-        };
-        return ! has_utf8(a) && has_utf8(b);
-    });
-
-    // Remove the suffix behind a dot, if there is one.
-    for (std::string& s : locales)
-        s = s.substr(0, s.find("."));
-
-    // We just hope that dear Linux "locale -a" returns country codes
-    // in ISO 3166-1 alpha-2 code (two letter) format.
-    // https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes
-    // To be sure, remove anything not looking as expected
-    // (any number of lowercase letters, underscore, two uppercase letters).
-    locales.erase(std::remove_if(locales.begin(),
-                                 locales.end(),
-                                 [](const std::string& s) {
-                                     return ! std::regex_match(s,
-                                         std::regex("^[a-z]+_[A-Z]{2}$"));
-                                 }),
-                   locales.end());
-
-    // Is there a candidate matching a country code of a system language? Move it to the end,
-    // while maintaining the order of matches, so that the best match ends up at the very end.
-    std::string system_country = "_" + into_u8(system_language->CanonicalName.AfterFirst('_')).substr(0, 2);
-    int cnt = locales.size();
-    for (int i=0; i<cnt; ++i)
-        if (locales[i].find(system_country) != std::string::npos) {
-            locales.emplace_back(std::move(locales[i]));
-            locales[i].clear();
-        }
-
-    // Now try them one by one.
-    for (auto it = locales.rbegin(); it != locales.rend(); ++ it)
-        if (! it->empty()) {
-            const std::string &locale = *it;
-            const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale));
-            if (wxLocale::IsAvailable(lang->Language))
-                return lang;
-        }
-    return language;
-}
-#endif
-
-int GUI_App::GetSingleChoiceIndex(const wxString& message,
-                                const wxString& caption,
-                                const wxArrayString& choices,
-                                int initialSelection)
-{
-#ifdef _WIN32
-    wxSingleChoiceDialog dialog(nullptr, message, caption, choices);
-    wxGetApp().UpdateDlgDarkUI(&dialog);
-
-    dialog.SetSelection(initialSelection);
-    return dialog.ShowModal() == wxID_OK ? dialog.GetSelection() : -1;
-#else
-    return wxGetSingleChoiceIndex(message, caption, choices, initialSelection);
-#endif
-}
-
-// select language from the list of installed languages
-bool GUI_App::select_language()
-{
-	wxArrayString translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY);
-    std::vector<const wxLanguageInfo*> language_infos;
-    language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH));
-    for (size_t i = 0; i < translations.GetCount(); ++ i) {
-	    const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]);
-        if (langinfo != nullptr)
-            language_infos.emplace_back(langinfo);
-    }
-    sort_remove_duplicates(language_infos);
-	std::sort(language_infos.begin(), language_infos.end(), [](const wxLanguageInfo* l, const wxLanguageInfo* r) { return l->Description < r->Description; });
-
-    wxArrayString names;
-    names.Alloc(language_infos.size());
-
-    // Some valid language should be selected since the application start up.
-    const wxLanguage current_language = wxLanguage(m_wxLocale->GetLanguage());
-    int 		     init_selection   		= -1;
-    int 			 init_selection_alt     = -1;
-    int 			 init_selection_default = -1;
-    for (size_t i = 0; i < language_infos.size(); ++ i) {
-        if (wxLanguage(language_infos[i]->Language) == current_language)
-        	// The dictionary matches the active language and country.
-            init_selection = i;
-        else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == m_wxLocale->GetCanonicalName().BeforeFirst('_')) ||
-        		 // if the active language is Slovak, mark the Czech language as active.
-        	     (language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && m_wxLocale->GetCanonicalName().BeforeFirst('_') == "sk"))
-        	// The dictionary matches the active language, it does not necessarily match the country.
-        	init_selection_alt = i;
-        if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en")
-        	// This will be the default selection if the active language does not match any dictionary.
-        	init_selection_default = i;
-        names.Add(language_infos[i]->Description);
-    }
-    if (init_selection == -1)
-    	// This is the dictionary matching the active language.
-    	init_selection = init_selection_alt;
-    if (init_selection != -1)
-    	// This is the language to highlight in the choice dialog initially.
-    	init_selection_default = init_selection;
-
-    const long index = GetSingleChoiceIndex(_L("Select the language"), _L("Language"), names, init_selection_default);
-	// Try to load a new language.
-    if (index != -1 && (init_selection == -1 || init_selection != index)) {
-    	const wxLanguageInfo *new_language_info = language_infos[index];
-    	if (this->load_language(new_language_info->CanonicalName, false)) {
-			// Save language at application config.
-            // Which language to save as the selected dictionary language?
-            // 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its
-            //    stability in the future:
-            //    wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH);
-            // 2) Current locale language may not match the dictionary name, see GH issue #3901
-            //    m_wxLocale->GetCanonicalName()
-            // 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name.
-			app_config->set("translation_language", new_language_info->CanonicalName.ToUTF8().data());            
-			app_config->save();
-    		return true;
-    	}
-    }
-
-    return false;
-}
-
-// Load gettext translation files and activate them at the start of the application,
-// based on the "translation_language" key stored in the application config.
-bool GUI_App::load_language(wxString language, bool initial)
-{
-    if (initial) {
-    	// There is a static list of lookup path prefixes in wxWidgets. Add ours.
-	    wxFileTranslationsLoader::AddCatalogLookupPathPrefix(from_u8(localization_dir()));
-    	// Get the active language from PrusaSlicer.ini, or empty string if the key does not exist.
-        language = app_config->get("translation_language");
-        if (! language.empty())
-        	BOOST_LOG_TRIVIAL(trace) << boost::format("translation_language provided by PrusaSlicer.ini: %1%") % language;
-
-        // Get the system language.
-        {
-	        const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage());
-	        if (lang_system != wxLANGUAGE_UNKNOWN) {
-				m_language_info_system = wxLocale::GetLanguageInfo(lang_system);
-	        	BOOST_LOG_TRIVIAL(trace) << boost::format("System language detected (user locales and such): %1%") % m_language_info_system->CanonicalName.ToUTF8().data();
-	        }
-		}
-        {
-	    	// Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance.
-	    	wxLocale temp_locale;
-	    	// Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code).
-	    	wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT);
-	    	// Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer
-	    	// and try to match them with the system specific "preferred languages". 
-	    	// There seems to be a support for that on Windows and OSX, while on Linuxes the code just returns wxLocale::GetSystemLanguage().
-	    	// The last parameter gets added to the list of detected dictionaries. This is a workaround 
-	    	// for not having the English dictionary. Let's hope wxWidgets of various versions process this call the same way.
-			wxString best_language = wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH);
-			if (! best_language.IsEmpty()) {
-				m_language_info_best = wxLocale::FindLanguageInfo(best_language);
-	        	BOOST_LOG_TRIVIAL(trace) << boost::format("Best translation language detected (may be different from user locales): %1%") % m_language_info_best->CanonicalName.ToUTF8().data();
-			}
-            #ifdef __linux__
-            wxString lc_all;
-            if (wxGetEnv("LC_ALL", &lc_all) && ! lc_all.IsEmpty()) {
-                // Best language returned by wxWidgets on Linux apparently does not respect LC_ALL.
-                // Disregard the "best" suggestion in case LC_ALL is provided.
-                m_language_info_best = nullptr;
-            }
-            #endif
-		}
-    }
-
-	const wxLanguageInfo *language_info = language.empty() ? nullptr : wxLocale::FindLanguageInfo(language);
-	if (! language.empty() && (language_info == nullptr || language_info->CanonicalName.empty())) {
-		// Fix for wxWidgets issue, where the FindLanguageInfo() returns locales with undefined ANSII code (wxLANGUAGE_KONKANI or wxLANGUAGE_MANIPURI).
-		language_info = nullptr;
-    	BOOST_LOG_TRIVIAL(error) << boost::format("Language code \"%1%\" is not supported") % language.ToUTF8().data();
-	}
-
-	if (language_info != nullptr && language_info->LayoutDirection == wxLayout_RightToLeft) {
-    	BOOST_LOG_TRIVIAL(trace) << boost::format("The following language code requires right to left layout, which is not supported by PrusaSlicer: %1%") % language_info->CanonicalName.ToUTF8().data();
-		language_info = nullptr;
-	}
-
-    if (language_info == nullptr) {
-        // PrusaSlicer does not support the Right to Left languages yet.
-        if (m_language_info_system != nullptr && m_language_info_system->LayoutDirection != wxLayout_RightToLeft)
-            language_info = m_language_info_system;
-        if (m_language_info_best != nullptr && m_language_info_best->LayoutDirection != wxLayout_RightToLeft)
-        	language_info = m_language_info_best;
-	    if (language_info == nullptr)
-			language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US);
-    }
-
-	BOOST_LOG_TRIVIAL(trace) << boost::format("Switching wxLocales to %1%") % language_info->CanonicalName.ToUTF8().data();
-
-    // Alternate language code.
-    wxLanguage language_dict = wxLanguage(language_info->Language);
-    if (language_info->CanonicalName.BeforeFirst('_') == "sk") {
-    	// Slovaks understand Czech well. Give them the Czech translation.
-    	language_dict = wxLANGUAGE_CZECH;
-		BOOST_LOG_TRIVIAL(trace) << "Using Czech dictionaries for Slovak language";
-    }
-
-    // Select language for locales. This language may be different from the language of the dictionary.
-    if (language_info == m_language_info_best || language_info == m_language_info_system) {
-        // The current language matches user's default profile exactly. That's great.
-    } else if (m_language_info_best != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_best->CanonicalName.BeforeFirst('_')) {
-        // Use whatever the operating system recommends, if it the language code of the dictionary matches the recommended language.
-        // This allows a Swiss guy to use a German dictionary without forcing him to German locales.
-        language_info = m_language_info_best;
-    } else if (m_language_info_system != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_'))
-        language_info = m_language_info_system;
-
-#ifdef __linux__
-    // If we can't find this locale , try to use different one for the language
-    // instead of just reporting that it is impossible to switch.
-    if (! wxLocale::IsAvailable(language_info->Language)) {
-        std::string original_lang = into_u8(language_info->CanonicalName);
-        language_info = linux_get_existing_locale_language(language_info, m_language_info_system);
-        BOOST_LOG_TRIVIAL(trace) << boost::format("Can't switch language to %1% (missing locales). Using %2% instead.")
-                                    % original_lang % language_info->CanonicalName.ToUTF8().data();
-    }
-#endif
-
-    if (! wxLocale::IsAvailable(language_info->Language)) {
-    	// Loading the language dictionary failed.
-    	wxString message = "Switching PrusaSlicer to language " + language_info->CanonicalName + " failed.";
-#if !defined(_WIN32) && !defined(__APPLE__)
-        // likely some linux system
-        message += "\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n";
-#endif
-        if (initial)
-        	message + "\n\nApplication will close.";
-        wxMessageBox(message, "PrusaSlicer - Switching language failed", wxOK | wxICON_ERROR);
-        if (initial)
-			std::exit(EXIT_FAILURE);
-		else
-			return false;
-    }
-
-    // Release the old locales, create new locales.
-    //FIXME wxWidgets cause havoc if the current locale is deleted. We just forget it causing memory leaks for now.
-    m_wxLocale.release();
-    m_wxLocale = Slic3r::make_unique<wxLocale>();
-    m_wxLocale->Init(language_info->Language);
-    // Override language at the active wxTranslations class (which is stored in the active m_wxLocale)
-    // to load possibly different dictionary, for example, load Czech dictionary for Slovak language.
-    wxTranslations::Get()->SetLanguage(language_dict);
-    m_wxLocale->AddCatalog(SLIC3R_APP_KEY);
-    m_imgui->set_language(into_u8(language_info->CanonicalName));
-    //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only.
-    //wxSetlocale(LC_NUMERIC, "C");
-    Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data());
-	return true;
-}
-
-Tab* GUI_App::get_tab(Preset::Type type)
-{
-    for (Tab* tab: tabs_list)
-        if (tab->type() == type)
-            return tab->completed() ? tab : nullptr; // To avoid actions with no-completed Tab
-    return nullptr;
-}
-
-ConfigOptionMode GUI_App::get_mode()
-{
-    if (!app_config->has("view_mode"))
-        return comSimple;
-
-    const auto mode = app_config->get("view_mode");
-    return mode == "expert" ? comExpert : 
-           mode == "simple" ? comSimple : comAdvanced;
-}
-
-void GUI_App::save_mode(const /*ConfigOptionMode*/int mode) 
-{
-    const std::string mode_str = mode == comExpert ? "expert" :
-                                 mode == comSimple ? "simple" : "advanced";
-    app_config->set("view_mode", mode_str);
-    app_config->save(); 
-    update_mode();
-}
-
-// Update view mode according to selected menu
-void GUI_App::update_mode()
-{
-    sidebar().update_mode();
-
-#ifdef _MSW_DARK_MODE
-    if (!wxGetApp().tabs_as_menu())
-        dynamic_cast<Notebook*>(mainframe->m_tabpanel)->UpdateMode();
-#endif
-
-    for (auto tab : tabs_list)
-        tab->update_mode();
-
-    plater()->update_menus();
-    plater()->canvas3D()->update_gizmos_on_off_state();
-}
-
-void GUI_App::add_config_menu(wxMenuBar *menu)
-{
-    auto local_menu = new wxMenu();
-    wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt));
-
-    const auto config_wizard_name = _(ConfigWizard::name(true));
-    const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Run %s"))) % config_wizard_name).str());
-    // Cmd+, is standard on OS X - what about other operating systems?
-    if (is_editor()) {
-        local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip);
-        local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots"));
-        local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot"));
-        local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates"));
-        local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application"));
-#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) 
-        //if (DesktopIntegrationDialog::integration_possible())
-        local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));    
-#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)        
-        local_menu->AppendSeparator();
-    }
-    local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots +
-#ifdef __APPLE__
-        "\tCtrl+,",
-#else
-        "\tCtrl+P",
-#endif
-        _L("Application preferences"));
-    wxMenu* mode_menu = nullptr;
-    if (is_editor()) {
-        local_menu->AppendSeparator();
-        mode_menu = new wxMenu();
-        mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple View Mode"));
-//    mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced View Mode"));
-        mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), _L("Advanced View Mode"));
-        mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _L("Expert"), _L("Expert View Mode"));
-        Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple);
-        Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced);
-        Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comExpert) evt.Check(true); }, config_id_base + ConfigMenuModeExpert);
-
-        local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s View Mode"), SLIC3R_APP_NAME));
-    }
-    local_menu->AppendSeparator();
-    local_menu->Append(config_id_base + ConfigMenuLanguage, _L("&Language"));
-    if (is_editor()) {
-        local_menu->AppendSeparator();
-        local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _L("Flash Printer &Firmware"), _L("Upload a firmware image into an Arduino based printer"));
-        // TODO: for when we're able to flash dictionaries
-        // local_menu->Append(config_id_base + FirmwareMenuDict,  _L("Flash Language File"),    _L("Upload a language dictionary file into a Prusa printer"));
-    }
-
-    local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) {
-        switch (event.GetId() - config_id_base) {
-        case ConfigMenuWizard:
-            run_wizard(ConfigWizard::RR_USER);
-            break;
-		case ConfigMenuUpdateConf:
-			check_updates(true);
-			break;
-        case ConfigMenuUpdateApp:
-            app_version_check(true);
-            break;
-#ifdef __linux__
-        case ConfigMenuDesktopIntegration:
-            show_desktop_integration_dialog();
-            break;
-#endif
-        case ConfigMenuTakeSnapshot:
-            // Take a configuration snapshot.
-            if (wxString action_name = _L("Taking a configuration snapshot");
-                check_and_save_current_preset_changes(action_name, _L("Some presets are modified and the unsaved changes will not be captured by the configuration snapshot."), false, true)) {
-                wxTextEntryDialog dlg(nullptr, action_name, _L("Snapshot name"));
-                UpdateDlgDarkUI(&dlg);
-                
-                // set current normal font for dialog children, 
-                // because of just dlg.SetFont(normal_font()) has no result;
-                for (auto child : dlg.GetChildren())
-                    child->SetFont(normal_font());
-
-                if (dlg.ShowModal() == wxID_OK)
-                    if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error(
-                            *app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data());
-                        snapshot != nullptr)
-                        app_config->set("on_snapshot", snapshot->id);
-            }
-            break;
-        case ConfigMenuSnapshots:
-            if (check_and_save_current_preset_changes(_L("Loading a configuration snapshot"), "", false)) {
-                std::string on_snapshot;
-                if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
-                    on_snapshot = app_config->get("on_snapshot");
-                ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot);
-                dlg.ShowModal();
-                if (!dlg.snapshot_to_activate().empty()) {
-                    if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) && 
-                        ! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "",
-                                GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate())))
-                        break;
-                    try {
-                        app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
-                        // Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system
-                        // presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users
-                        // are known to be creative and mess with the config files in various ways.
-                        if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
-                            ! all_substitutions.empty())
-                            show_substitutions_info(all_substitutions);
-
-                        // Load the currently selected preset into the GUI, update the preset selection box.
-                        load_current_presets();
-                    } catch (std::exception &ex) {
-                        GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what()));
-                    }
-                }
-            }
-            break;
-        case ConfigMenuPreferences:
-        {
-            open_preferences();
-            break;
-        }
-        case ConfigMenuLanguage:
-        {
-            /* Before change application language, let's check unsaved changes on 3D-Scene
-             * and draw user's attention to the application restarting after a language change
-             */
-            {
-                // the dialog needs to be destroyed before the call to switch_language()
-                // or sometimes the application crashes into wxDialogBase() destructor
-                // so we put it into an inner scope
-                wxString title = is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME);
-                title += " - " + _L("Language selection");
-                //wxMessageDialog dialog(nullptr,
-                MessageDialog dialog(nullptr,
-                    _L("Switching the language will trigger application restart.\n"
-                        "You will lose content of the plater.") + "\n\n" +
-                    _L("Do you want to proceed?"),
-                    title,
-                    wxICON_QUESTION | wxOK | wxCANCEL);
-                if (dialog.ShowModal() == wxID_CANCEL)
-                    return;
-            }
-
-            switch_language();
-            break;
-        }
-        case ConfigMenuFlashFirmware:
-            FirmwareDialog::run(mainframe);
-            break;
-        default:
-            break;
-        }
-    });
-    
-    using std::placeholders::_1;
-
-    if (mode_menu != nullptr) {
-        auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); };
-        mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple);
-        mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced);
-        mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comExpert, _1), config_id_base + ConfigMenuModeExpert);
-    }
-
-    menu->Append(local_menu, _L("&Configuration"));
-}
-
-void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/)
-{
-    mainframe->preferences_dialog->show(highlight_option, tab_name);
-
-    if (mainframe->preferences_dialog->recreate_GUI())
-        recreate_GUI(_L("Restart application") + dots);
-
-#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
-    if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
-#else
-    if (mainframe->preferences_dialog->seq_top_layer_only_changed())
-#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
-        this->plater_->refresh_print();
-
-#ifdef _WIN32
-    if (is_editor()) {
-        if (app_config->get("associate_3mf") == "1")
-            associate_3mf_files();
-        if (app_config->get("associate_stl") == "1")
-            associate_stl_files();
-    }
-    else {
-        if (app_config->get("associate_gcode") == "1")
-            associate_gcode_files();
-    }
-#endif // _WIN32
-
-    if (mainframe->preferences_dialog->settings_layout_changed()) {
-        // hide full main_sizer for mainFrame
-        mainframe->GetSizer()->Show(false);
-        mainframe->update_layout();
-        mainframe->select_tab(size_t(0));
-    }
-}
-
-bool GUI_App::has_unsaved_preset_changes() const
-{
-    PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
-    for (const Tab* const tab : tabs_list) {
-        if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty())
-            return true;
-    }
-    return false;
-}
-
-bool GUI_App::has_current_preset_changes() const
-{
-    PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
-    for (const Tab* const tab : tabs_list) {
-        if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty())
-            return true;
-    }
-    return false;
-}
-
-void GUI_App::update_saved_preset_from_current_preset()
-{
-    PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
-    for (Tab* tab : tabs_list) {
-        if (tab->supports_printer_technology(printer_technology))
-            tab->update_saved_preset_from_current_preset();
-    }
-}
-
-std::vector<const PresetCollection*> GUI_App::get_active_preset_collections() const
-{
-    std::vector<const PresetCollection*> ret;
-    PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
-    for (const Tab* tab : tabs_list)
-        if (tab->supports_printer_technology(printer_technology))
-            ret.push_back(tab->get_presets());
-    return ret;
-}
-
-// To notify the user whether he is aware that some preset changes will be lost,
-// UnsavedChangesDialog: "Discard / Save / Cancel"
-// This is called when:
-// - Close Application & Current project isn't saved
-// - Load Project      & Current project isn't saved
-// - Undo / Redo with change of print technologie
-// - Loading snapshot
-// - Loading config_file/bundle
-// UnsavedChangesDialog: "Don't save / Save / Cancel"
-// This is called when:
-// - Exporting config_bundle
-// - Taking snapshot
-bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice/* = true*/, bool dont_save_insted_of_discard/* = false*/)
-{
-    if (has_current_preset_changes()) {
-        const std::string app_config_key = remember_choice ? "default_action_on_close_application" : "";
-        int act_buttons = UnsavedChangesDialog::ActionButtons::SAVE;
-        if (dont_save_insted_of_discard)
-            act_buttons |= UnsavedChangesDialog::ActionButtons::DONT_SAVE;
-        UnsavedChangesDialog dlg(caption, header, app_config_key, act_buttons);
-        std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key);
-        if (act == "none" && dlg.ShowModal() == wxID_CANCEL)
-            return false;
-
-        if (dlg.save_preset())  // save selected changes
-        {
-            for (const std::pair<std::string, Preset::Type>& nt : dlg.get_names_and_types())
-                preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second));
-
-            load_current_presets(false);
-
-            // if we saved changes to the new presets, we should to 
-            // synchronize config.ini with the current selections.
-            preset_bundle->export_selections(*app_config);
-
-            MessageDialog(nullptr, _L_PLURAL("The preset modifications are successfully saved", 
-                                             "The presets modifications are successfully saved", dlg.get_names_and_types().size())).ShowModal();
-        }
-    }
-
-    return true;
-}
-
-void GUI_App::apply_keeped_preset_modifications()
-{
-    PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
-    for (Tab* tab : tabs_list) {
-        if (tab->supports_printer_technology(printer_technology))
-            tab->apply_config_from_cache();
-    }
-    load_current_presets(false);
-}
-
-// This is called when creating new project or load another project
-// OR close ConfigWizard
-// to ask the user what should we do with unsaved changes for presets.
-// New Project          => Current project is saved    => UnsavedChangesDialog: "Keep / Discard / Cancel"
-//                      => Current project isn't saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel"
-// Close ConfigWizard   => Current project is saved    => UnsavedChangesDialog: "Keep / Discard / Save / Cancel"
-// Note: no_nullptr postponed_apply_of_keeped_changes indicates that thie function is called after ConfigWizard is closed
-bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes/* = nullptr*/)
-{
-    if (has_current_preset_changes()) {
-        bool is_called_from_configwizard = postponed_apply_of_keeped_changes != nullptr;
-
-        const std::string app_config_key = is_called_from_configwizard ? "" : "default_action_on_new_project";
-        UnsavedChangesDialog dlg(caption, header, app_config_key, action_buttons);
-        std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key);
-        if (act == "none" && dlg.ShowModal() == wxID_CANCEL)
-            return false;
-
-        auto reset_modifications = [this, is_called_from_configwizard]() {
-            if (is_called_from_configwizard)
-                return; // no need to discared changes. It will be done fromConfigWizard closing
-
-            PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
-            for (const Tab* const tab : tabs_list) {
-                if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty())
-                    tab->m_presets->discard_current_changes();
-            }
-            load_current_presets(false);
-        };
-
-        if (dlg.discard())
-            reset_modifications();
-        else  // save selected changes
-        {
-            const auto& preset_names_and_types = dlg.get_names_and_types();
-            if (dlg.save_preset()) {
-                for (const std::pair<std::string, Preset::Type>& nt : preset_names_and_types)
-                    preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second));
-
-                // if we saved changes to the new presets, we should to 
-                // synchronize config.ini with the current selections.
-                preset_bundle->export_selections(*app_config);
-
-                wxString text = _L_PLURAL("The preset modifications are successfully saved",
-                    "The presets modifications are successfully saved", preset_names_and_types.size());
-                if (!is_called_from_configwizard)
-                    text += "\n\n" + _L("For new project all modifications will be reseted");
-
-                MessageDialog(nullptr, text).ShowModal();
-                reset_modifications();
-            }
-            else if (dlg.transfer_changes() && (dlg.has_unselected_options() || is_called_from_configwizard)) {
-                // execute this part of code only if not all modifications are keeping to the new project 
-                // OR this function is called when ConfigWizard is closed and "Keep modifications" is selected
-                for (const std::pair<std::string, Preset::Type>& nt : preset_names_and_types) {
-                    Preset::Type type = nt.second;
-                    Tab* tab = get_tab(type);
-                    std::vector<std::string> selected_options = dlg.get_selected_options(type);
-                    if (type == Preset::TYPE_PRINTER) {
-                        auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count");
-                        if (it != selected_options.end()) {
-                            // erase "extruders_count" option from the list
-                            selected_options.erase(it);
-                            // cache the extruders count
-                            static_cast<TabPrinter*>(tab)->cache_extruder_cnt();
-                        }
-                    }
-                    tab->cache_config_diff(selected_options);
-                    if (!is_called_from_configwizard)
-                        tab->m_presets->discard_current_changes();
-                }
-                if (is_called_from_configwizard)
-                    *postponed_apply_of_keeped_changes = true;
-                else
-                    apply_keeped_preset_modifications();
-            }
-        }
-    }
-
-    return true;
-}
-
-bool GUI_App::can_load_project()
-{
-    int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified."));
-    if (saved_project == wxID_CANCEL ||
-        (plater()->is_project_dirty() && saved_project == wxID_NO && 
-         !check_and_save_current_preset_changes(_L("Project is loading"), _L("Opening new project while some presets are unsaved."))))
-        return false;
-    return true;
-}
-
-bool GUI_App::check_print_host_queue()
-{
-    wxString dirty;
-    std::vector<std::pair<std::string, std::string>> jobs;
-    // Get ongoing jobs from dialog
-    mainframe->m_printhost_queue_dlg->get_active_jobs(jobs);
-    if (jobs.empty())
-        return true;
-    // Show dialog
-    wxString job_string = wxString();
-    for (const auto& job : jobs) {
-        job_string += format_wxstr("   %1% : %2% \n", job.first, job.second);
-    }
-    wxString message;
-    message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?"));
-    //wxMessageDialog dialog(mainframe,
-    MessageDialog dialog(mainframe,
-        message,
-        wxString(SLIC3R_APP_NAME) + " - " + _(L("Ongoing uploads")),
-        wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
-    if (dialog.ShowModal() == wxID_YES)
-        return true;
-
-    // TODO: If already shown, bring forward
-    mainframe->m_printhost_queue_dlg->Show();
-    return false;
-}
-
-bool GUI_App::checked_tab(Tab* tab)
-{
-    bool ret = true;
-    if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end())
-        ret = false;
-    return ret;
-}
-
-// Update UI / Tabs to reflect changes in the currently loaded presets
-void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/)
-{
-    // check printer_presets for the containing information about "Print Host upload"
-    // and create physical printer from it, if any exists
-    if (check_printer_presets_)
-        check_printer_presets();
-
-    PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
-	this->plater()->set_printer_technology(printer_technology);
-    for (Tab *tab : tabs_list)
-		if (tab->supports_printer_technology(printer_technology)) {
-			if (tab->type() == Preset::TYPE_PRINTER) {
-				static_cast<TabPrinter*>(tab)->update_pages();
-				// Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change().
-				this->plater()->force_print_bed_update();
-			}
-			tab->load_current_preset();
-		}
-}
-
-bool GUI_App::OnExceptionInMainLoop()
-{
-    generic_exception_handle();
-    return false;
-}
-
-#ifdef __APPLE__
-// This callback is called from wxEntry()->wxApp::CallOnInit()->NSApplication run
-// that is, before GUI_App::OnInit(), so we have a chance to switch GUI_App
-// to a G-code viewer.
-void GUI_App::OSXStoreOpenFiles(const wxArrayString &fileNames)
-{
-    size_t num_gcodes = 0;
-    for (const wxString &filename : fileNames)
-        if (is_gcode_file(into_u8(filename)))
-            ++ num_gcodes;
-    if (fileNames.size() == num_gcodes) {
-        // Opening PrusaSlicer by drag & dropping a G-Code onto PrusaSlicer icon in Finder,
-        // just G-codes were passed. Switch to G-code viewer mode.
-        m_app_mode = EAppMode::GCodeViewer;
-        unlock_lockfile(get_instance_hash_string() + ".lock", data_dir() + "/cache/");
-        if(app_config != nullptr)
-            delete app_config;
-        app_config = nullptr;
-        init_app_config();
-    }
-    wxApp::OSXStoreOpenFiles(fileNames);
-}
-// wxWidgets override to get an event on open files.
-void GUI_App::MacOpenFiles(const wxArrayString &fileNames)
-{
-    std::vector<std::string> files;
-    std::vector<wxString>    gcode_files;
-    std::vector<wxString>    non_gcode_files;
-    for (const auto& filename : fileNames) {
-        if (is_gcode_file(into_u8(filename)))
-            gcode_files.emplace_back(filename);
-        else {
-            files.emplace_back(into_u8(filename));
-            non_gcode_files.emplace_back(filename);
-        }
-    }
-    if (m_app_mode == EAppMode::GCodeViewer) {
-        // Running in G-code viewer.
-        // Load the first G-code into the G-code viewer.
-        // Or if no G-codes, send other files to slicer. 
-        if (! gcode_files.empty()) {
-            if (m_post_initialized)
-                this->plater()->load_gcode(gcode_files.front());
-            else
-                this->init_params->input_files = { into_u8(gcode_files.front()) };
-        }
-        if (!non_gcode_files.empty()) 
-            start_new_slicer(non_gcode_files, true);
-    } else {
-        if (! files.empty()) {
-            if (m_post_initialized) {
-                wxArrayString input_files;
-                for (size_t i = 0; i < non_gcode_files.size(); ++i)
-                    input_files.push_back(non_gcode_files[i]);
-                this->plater()->load_files(input_files);
-            } else {
-                for (const auto &f : non_gcode_files)
-                    this->init_params->input_files.emplace_back(into_u8(f));
-            }
-        }
-        for (const wxString &filename : gcode_files)
-            start_new_gcodeviewer(&filename);
-    }
-}
-#endif /* __APPLE */
-
-Sidebar& GUI_App::sidebar()
-{
-    return plater_->sidebar();
-}
-
-ObjectManipulation* GUI_App::obj_manipul()
-{
-    // If this method is called before plater_ has been initialized, return nullptr (to avoid a crash)
-    return (plater_ != nullptr) ? sidebar().obj_manipul() : nullptr;
-}
-
-ObjectSettings* GUI_App::obj_settings()
-{
-    return sidebar().obj_settings();
-}
-
-ObjectList* GUI_App::obj_list()
-{
-    return sidebar().obj_list();
-}
-
-ObjectLayers* GUI_App::obj_layers()
-{
-    return sidebar().obj_layers();
-}
-
-Plater* GUI_App::plater()
-{
-    return plater_;
-}
-
-const Plater* GUI_App::plater() const
-{
-    return plater_;
-}
-
-Model& GUI_App::model()
-{
-    return plater_->model();
-}
-wxBookCtrlBase* GUI_App::tab_panel() const
-{
-    return mainframe->m_tabpanel;
-}
-
-NotificationManager * GUI_App::notification_manager()
-{
-    return plater_->get_notification_manager();
-}
-
-// extruders count from selected printer preset
-int GUI_App::extruders_cnt() const
-{
-    const Preset& preset = preset_bundle->printers.get_selected_preset();
-    return preset.printer_technology() == ptSLA ? 1 :
-           preset.config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
-}
-
-// extruders count from edited printer preset
-int GUI_App::extruders_edited_cnt() const
-{
-    const Preset& preset = preset_bundle->printers.get_edited_preset();
-    return preset.printer_technology() == ptSLA ? 1 :
-           preset.config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
-}
-
-wxString GUI_App::current_language_code_safe() const
-{
-	// Translate the language code to a code, for which Prusa Research maintains translations.
-	const std::map<wxString, wxString> mapping {
-		{ "cs", 	"cs_CZ", },
-		{ "sk", 	"cs_CZ", },
-		{ "de", 	"de_DE", },
-		{ "es", 	"es_ES", },
-		{ "fr", 	"fr_FR", },
-		{ "it", 	"it_IT", },
-		{ "ja", 	"ja_JP", },
-		{ "ko", 	"ko_KR", },
-		{ "pl", 	"pl_PL", },
-		{ "uk", 	"uk_UA", },
-		{ "zh", 	"zh_CN", },
-		{ "ru", 	"ru_RU", },
-	};
-	wxString language_code = this->current_language_code().BeforeFirst('_');
-	auto it = mapping.find(language_code);
-	if (it != mapping.end())
-		language_code = it->second;
-	else
-		language_code = "en_US";
-	return language_code;
-}
-
-void GUI_App::open_web_page_localized(const std::string &http_address)
-{
-    open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe(), nullptr, false);
-}
-
-// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s).
-// Because of we can't to print the multi-part objects with SLA technology.
-bool GUI_App::may_switch_to_SLA_preset(const wxString& caption)
-{
-    if (model_has_multi_part_objects(model())) {
-        show_info(nullptr,
-            _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" +
-            _L("Please check your object list before preset changing."),
-            caption);
-        return false;
-    }
-    return true;
-}
-
-bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page)
-{
-    wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
-
-    if (reason == ConfigWizard::RR_USER) {
-        if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED)
-            return false;
-    }
-
-    auto wizard = new ConfigWizard(mainframe);
-    const bool res = wizard->run(reason, start_page);
-
-    if (res) {
-        load_current_presets();
-
-        // #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config() 
-        if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA)
-            may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard"));
-    }
-
-    return res;
-}
-
-void GUI_App::show_desktop_integration_dialog()
-{
-#ifdef __linux__
-    //wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
-    DesktopIntegrationDialog dialog(mainframe);
-    dialog.ShowModal();
-#endif //__linux__
-}
-
-#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
-void GUI_App::gcode_thumbnails_debug()
-{
-    const std::string BEGIN_MASK = "; thumbnail begin";
-    const std::string END_MASK = "; thumbnail end";
-    std::string gcode_line;
-    bool reading_image = false;
-    unsigned int width = 0;
-    unsigned int height = 0;
-
-    wxFileDialog dialog(GetTopWindow(), _L("Select a gcode file:"), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
-    if (dialog.ShowModal() != wxID_OK)
-        return;
-
-    std::string in_filename = into_u8(dialog.GetPath());
-    std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string();
-
-    boost::nowide::ifstream in_file(in_filename.c_str());
-    std::vector<std::string> rows;
-    std::string row;
-    if (in_file.good())
-    {
-        while (std::getline(in_file, gcode_line))
-        {
-            if (in_file.good())
-            {
-                if (boost::starts_with(gcode_line, BEGIN_MASK))
-                {
-                    reading_image = true;
-                    gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1);
-                    std::string::size_type x_pos = gcode_line.find('x');
-                    std::string width_str = gcode_line.substr(0, x_pos);
-                    width = (unsigned int)::atoi(width_str.c_str());
-                    std::string height_str = gcode_line.substr(x_pos + 1);
-                    height = (unsigned int)::atoi(height_str.c_str());
-                    row.clear();
-                }
-                else if (reading_image && boost::starts_with(gcode_line, END_MASK))
-                {
-                    std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png";
-                    boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary);
-                    if (out_file.good())
-                    {
-                        std::string decoded;
-                        decoded.resize(boost::beast::detail::base64::decoded_size(row.size()));
-                        decoded.resize(boost::beast::detail::base64::decode((void*)&decoded[0], row.data(), row.size()).first);
-
-                        out_file.write(decoded.c_str(), decoded.size());
-                        out_file.close();
-                    }
-
-                    reading_image = false;
-                    width = 0;
-                    height = 0;
-                    rows.clear();
-                }
-                else if (reading_image)
-                    row += gcode_line.substr(2);
-            }
-        }
-
-        in_file.close();
-    }
-}
-#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
-
-void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name)
-{
-    if (name.empty()) { return; }
-    const auto config_key = (boost::format("window_%1%") % name).str();
-
-    WindowMetrics metrics = WindowMetrics::from_window(window);
-    app_config->set(config_key, metrics.serialize());
-    app_config->save();
-}
-
-void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized)
-{
-    if (name.empty()) { return; }
-    const auto config_key = (boost::format("window_%1%") % name).str();
-
-    if (! app_config->has(config_key)) {
-        window->Maximize(default_maximized);
-        return;
-    }
-
-    auto metrics = WindowMetrics::deserialize(app_config->get(config_key));
-    if (! metrics) {
-        window->Maximize(default_maximized);
-        return;
-    }
-
-    const wxRect& rect = metrics->get_rect();
-
-    if (app_config->get("restore_win_position") == "1") {
-        // workaround for crash related to the positioning of the window on secondary monitor
-        app_config->set("restore_win_position", (boost::format("crashed_at_%1%_pos") % name).str());
-        app_config->save();
-        window->SetPosition(rect.GetPosition());
-
-        // workaround for crash related to the positioning of the window on secondary monitor
-        app_config->set("restore_win_position", (boost::format("crashed_at_%1%_size") % name).str());
-        app_config->save();
-        window->SetSize(rect.GetSize());
-
-        // revert "restore_win_position" value if application wasn't crashed
-        app_config->set("restore_win_position", "1");
-        app_config->save();
-    }
-    else
-        window->CenterOnScreen();
-
-    window->Maximize(metrics->get_maximized());
-}
-
-void GUI_App::window_pos_sanitize(wxTopLevelWindow* window)
-{
-    /*unsigned*/int display_idx = wxDisplay::GetFromWindow(window);
-    wxRect display;
-    if (display_idx == wxNOT_FOUND) {
-        display = wxDisplay(0u).GetClientArea();
-        window->Move(display.GetTopLeft());
-    } else {
-        display = wxDisplay(display_idx).GetClientArea();
-    }
-
-    auto metrics = WindowMetrics::from_window(window);
-    metrics.sanitize_for_display(display);
-    if (window->GetScreenRect() != metrics.get_rect()) {
-        window->SetSize(metrics.get_rect());
-    }
-}
-
-bool GUI_App::config_wizard_startup()
-{
-    if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) {
-        run_wizard(ConfigWizard::RR_DATA_EMPTY);
-        return true;
-    } else if (get_app_config()->legacy_datadir()) {
-        // Looks like user has legacy pre-vendorbundle data directory,
-        // explain what this is and run the wizard
-
-        MsgDataLegacy dlg;
-        dlg.ShowModal();
-
-        run_wizard(ConfigWizard::RR_DATA_LEGACY);
-        return true;
-    }
-    return false;
-}
-
-bool GUI_App::check_updates(const bool verbose)
-{	
-	PresetUpdater::UpdateResult updater_result;
-	try {
-		updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION);
-		if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
-			mainframe->Close();
-            // Applicaiton is closing.
-            return false;
-		}
-		else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) {
-            m_app_conf_exists = true;
-		}
-		else if (verbose && updater_result == PresetUpdater::R_NOOP) {
-			MsgNoUpdates dlg;
-			dlg.ShowModal();
-		}
-	}
-	catch (const std::exception & ex) {
-		show_error(nullptr, ex.what());
-	}
-    // Applicaiton will continue.
-    return true;
-}
-
-bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/)
-{
-    bool launch = true;
-
-    // warning dialog containes a "Remember my choice" checkbox
-    std::string option_key = "suppress_hyperlinks";
-    if (force_remember_choice || app_config->get(option_key).empty()) {
-        if (app_config->get(option_key).empty()) {
-            RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
-            dialog.ShowCheckBox(_L("Remember my choice"));
-            auto answer = dialog.ShowModal();
-            launch = answer == wxID_YES;
-            if (dialog.IsCheckBoxChecked()) {
-                wxString preferences_item = _L("Suppress to open hyperlink in browser");
-                wxString msg =
-                    _L("PrusaSlicer will remember your choice.") + "\n\n" +
-                    _L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" +
-                    format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item);
-
-                MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
-                if (msg_dlg.ShowModal() == wxID_CANCEL)
-                    return false;
-                app_config->set(option_key, answer == wxID_NO ? "1" : "0");
-            }
-        }
-        if (launch)
-            launch = app_config->get(option_key) != "1";
-    }
-    // warning dialog doesn't containe a "Remember my choice" checkbox
-    // and will be shown only when "Suppress to open hyperlink in browser" is ON.
-    else if (app_config->get(option_key) == "1") {
-        MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
-        launch = dialog.ShowModal() == wxID_YES;
-    }
-
-    return  launch && wxLaunchDefaultBrowser(url, flags);
-}
-
-// static method accepting a wxWindow object as first parameter
-// void warning_catcher{
-//     my($self, $message_dialog) = @_;
-//     return sub{
-//         my $message = shift;
-//         return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ;
-//         my @params = ($message, 'Warning', wxOK | wxICON_WARNING);
-//         $message_dialog
-//             ? $message_dialog->(@params)
-//             : Wx::MessageDialog->new($self, @params)->ShowModal;
-//     };
-// }
-
-// Do we need this function???
-// void GUI_App::notify(message) {
-//     auto frame = GetTopWindow();
-//     // try harder to attract user attention on OS X
-//     if (!frame->IsActive())
-//         frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO);
-// 
-//     // There used to be notifier using a Growl application for OSX, but Growl is dead.
-//     // The notifier also supported the Linux X D - bus notifications, but that support was broken.
-//     //TODO use wxNotificationMessage ?
-// }
-
-
-#ifdef __WXMSW__
-static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue)
-{
-    // see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
-    wchar_t szValueCurrent[1000];
-    DWORD dwType;
-    DWORD dwSize = sizeof(szValueCurrent);
-
-    int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize);
-
-    bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
-
-    if ((iRC != ERROR_SUCCESS) && !bDidntExist)
-        // an error occurred
-        return false;
-
-    if (!bDidntExist) {
-        if (dwType != REG_SZ)
-            // invalid type
-            return false;
-
-        if (::wcscmp(szValueCurrent, pszValue) == 0)
-            // value already set
-            return false;
-    }
-
-    DWORD dwDisposition;
-    HKEY hkey;
-    iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition);
-    bool ret = false;
-    if (iRC == ERROR_SUCCESS) {
-        iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t));
-        if (iRC == ERROR_SUCCESS)
-            ret = true;
-    }
-
-    RegCloseKey(hkey);
-    return ret;
-}
-
-void GUI_App::associate_3mf_files()
-{
-    wchar_t app_path[MAX_PATH];
-    ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
-
-    std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
-    std::wstring prog_id = L"Prusa.Slicer.1";
-    std::wstring prog_desc = L"PrusaSlicer";
-    std::wstring prog_command = prog_path + L" \"%1\"";
-    std::wstring reg_base = L"Software\\Classes";
-    std::wstring reg_extension = reg_base + L"\\.3mf";
-    std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
-    std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
-
-    bool is_new = false;
-    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
-    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
-    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
-
-    if (is_new)
-        // notify Windows only when any of the values gets changed
-        ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
-}
-
-void GUI_App::associate_stl_files()
-{
-    wchar_t app_path[MAX_PATH];
-    ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
-
-    std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
-    std::wstring prog_id = L"Prusa.Slicer.1";
-    std::wstring prog_desc = L"PrusaSlicer";
-    std::wstring prog_command = prog_path + L" \"%1\"";
-    std::wstring reg_base = L"Software\\Classes";
-    std::wstring reg_extension = reg_base + L"\\.stl";
-    std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
-    std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
-
-    bool is_new = false;
-    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
-    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
-    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
-
-    if (is_new)
-        // notify Windows only when any of the values gets changed
-        ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
-}
-
-void GUI_App::associate_gcode_files()
-{
-    wchar_t app_path[MAX_PATH];
-    ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
-
-    std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
-    std::wstring prog_id = L"PrusaSlicer.GCodeViewer.1";
-    std::wstring prog_desc = L"PrusaSlicerGCodeViewer";
-    std::wstring prog_command = prog_path + L" \"%1\"";
-    std::wstring reg_base = L"Software\\Classes";
-    std::wstring reg_extension = reg_base + L"\\.gcode";
-    std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
-    std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
-
-    bool is_new = false;
-    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
-    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
-    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
-
-    if (is_new)
-        // notify Windows only when any of the values gets changed
-        ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
-}
-#endif // __WXMSW__
-
-
-void GUI_App::on_version_read(wxCommandEvent& evt)
-{
-    app_config->set("version_online", into_u8(evt.GetString()));
-    app_config->save();
-    std::string opt = app_config->get("notify_release");
-    if (this->plater_ == nullptr || (opt != "all" && opt != "release")) {
-        return;
-    }
-    if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) {
-        return;
-    }
-    // notification
-    /*
-    this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable
-        , NotificationManager::NotificationLevel::ImportantNotificationLevel
-        , Slic3r::format(_u8L("New release version %1% is available."), evt.GetString())
-        , _u8L("See Download page.")
-        , [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; }
-    );
-    */
-    // updater 
-    // read triggered_by_user that was set when calling  GUI_App::app_version_check
-    app_updater(m_app_updater->get_triggered_by_user());
-}
-
-void GUI_App::app_updater(bool from_user)
-{
-    DownloadAppData app_data = m_app_updater->get_app_data();
-
-    if (from_user && (!app_data.version || *app_data.version <= *Semver::parse(SLIC3R_VERSION)))
-    {
-        BOOST_LOG_TRIVIAL(info) << "There is no newer version online.";
-        MsgNoAppUpdates no_update_dialog;
-        no_update_dialog.ShowModal();
-        return;
-
-    }
-
-    assert(!app_data.url.empty());
-    assert(!app_data.target_path.empty());
-
-    // dialog with new version info
-    AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version);
-    auto dialog_result = dialog.ShowModal();
-    // checkbox "do not show again"
-    if (dialog.disable_version_check()) {
-        app_config->set("notify_release", "none");
-    }
-    // Doesn't wish to update
-    if (dialog_result != wxID_OK) {
-        return;
-    }
-    // dialog with new version download (installer or app dependent on system) including path selection
-    AppUpdateDownloadDialog dwnld_dlg(*app_data.version, app_data.target_path);
-    dialog_result = dwnld_dlg.ShowModal();
-    //  Doesn't wish to download
-    if (dialog_result != wxID_OK) {
-        return;
-    }
-    app_data.target_path =dwnld_dlg.get_download_path();
-
-    // start download
-    this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get()));
-    app_data.start_after = dwnld_dlg.run_after_download();
-    m_app_updater->set_app_data(std::move(app_data));
-    m_app_updater->sync_download();
-}
-
-void GUI_App::app_version_check(bool from_user)
-{
-    if (from_user) {
-        if (m_app_updater->get_download_ongoing()) {
-            MessageDialog msgdlg(nullptr, _L("Download of new version is already ongoing. Do you wish to continue?"), _L("Notice"), wxYES_NO);
-            if (msgdlg.ShowModal() != wxID_YES)
-                return;
-        }
-    }
-    std::string version_check_url = app_config->version_check_url();
-    m_app_updater->sync_version(version_check_url, from_user);
-}
-
-} // GUI
-} //Slic3r
+#include "libslic3r/Technologies.hpp"
+#include "GUI_App.hpp"
+#include "GUI_Init.hpp"
+#include "GUI_ObjectList.hpp"
+#include "GUI_ObjectManipulation.hpp"
+#include "GUI_Factories.hpp"
+#include "format.hpp"
+#include "I18N.hpp"
+
+#include <algorithm>
+#include <iterator>
+#include <exception>
+#include <cstdlib>
+#include <regex>
+#include <string_view>
+#include <boost/nowide/fstream.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/log/trivial.hpp>
+#include <boost/nowide/convert.hpp>
+
+#include <wx/stdpaths.h>
+#include <wx/imagpng.h>
+#include <wx/display.h>
+#include <wx/menu.h>
+#include <wx/menuitem.h>
+#include <wx/filedlg.h>
+#include <wx/progdlg.h>
+#include <wx/dir.h>
+#include <wx/wupdlock.h>
+#include <wx/filefn.h>
+#include <wx/sysopt.h>
+#include <wx/richmsgdlg.h>
+#include <wx/log.h>
+#include <wx/intl.h>
+
+#include <wx/dialog.h>
+#include <wx/textctrl.h>
+#include <wx/splash.h>
+#include <wx/fontutil.h>
+
+#include "libslic3r/Utils.hpp"
+#include "libslic3r/Model.hpp"
+#include "libslic3r/I18N.hpp"
+#include "libslic3r/PresetBundle.hpp"
+#include "libslic3r/Color.hpp"
+
+#include "GUI.hpp"
+#include "GUI_Utils.hpp"
+#include "3DScene.hpp"
+#include "MainFrame.hpp"
+#include "Plater.hpp"
+#include "GLCanvas3D.hpp"
+
+#include "../Utils/PresetUpdater.hpp"
+#include "../Utils/PrintHost.hpp"
+#include "../Utils/Process.hpp"
+#include "../Utils/MacDarkMode.hpp"
+#include "../Utils/AppUpdater.hpp"
+#include "slic3r/Config/Snapshot.hpp"
+#include "ConfigSnapshotDialog.hpp"
+#include "FirmwareDialog.hpp"
+#include "Preferences.hpp"
+#include "Tab.hpp"
+#include "SysInfoDialog.hpp"
+#include "KBShortcutsDialog.hpp"
+#include "UpdateDialogs.hpp"
+#include "Mouse3DController.hpp"
+#include "RemovableDriveManager.hpp"
+#include "InstanceCheck.hpp"
+#include "NotificationManager.hpp"
+#include "UnsavedChangesDialog.hpp"
+#include "SavePresetDialog.hpp"
+#include "PrintHostDialogs.hpp"
+#include "DesktopIntegrationDialog.hpp"
+#include "SendSystemInfoDialog.hpp"
+
+#include "BitmapCache.hpp"
+#include "Notebook.hpp"
+
+#ifdef __WXMSW__
+#include <dbt.h>
+#include <shlobj.h>
+#ifdef _MSW_DARK_MODE
+#include <wx/msw/dark_mode.h>
+#endif // _MSW_DARK_MODE
+#endif
+#ifdef _WIN32
+#include <boost/dll/runtime_symbol_info.hpp>
+#endif
+
+#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
+#include <boost/beast/core/detail/base64.hpp>
+#include <boost/nowide/fstream.hpp>
+#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
+
+// Needed for forcing menu icons back under gtk2 and gtk3
+#if defined(__WXGTK20__) || defined(__WXGTK3__)
+    #include <gtk/gtk.h>
+#endif
+
+using namespace std::literals;
+
+namespace Slic3r {
+namespace GUI {
+
+class MainFrame;
+
+class SplashScreen : public wxSplashScreen
+{
+public:
+    SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition)
+        : wxSplashScreen(bitmap, splashStyle, milliseconds, static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, wxDefaultPosition, wxDefaultSize,
+#ifdef __APPLE__
+            wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP
+#else
+            wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR
+#endif // !__APPLE__
+        )
+    {
+        wxASSERT(bitmap.IsOk());
+
+        int init_dpi = get_dpi_for_window(this);
+        this->SetPosition(pos);
+        this->CenterOnScreen();
+        int new_dpi = get_dpi_for_window(this);
+
+        m_scale         = (float)(new_dpi) / (float)(init_dpi);
+        m_main_bitmap   = bitmap;
+
+        scale_bitmap(m_main_bitmap, m_scale);
+
+        // init constant texts and scale fonts
+        init_constant_text();
+
+        // this font will be used for the action string
+        m_action_font = m_constant_text.credits_font.Bold();
+
+        // draw logo and constant info text
+        Decorate(m_main_bitmap);
+    }
+
+    void SetText(const wxString& text)
+    {
+        set_bitmap(m_main_bitmap);
+        if (!text.empty()) {
+            wxBitmap bitmap(m_main_bitmap);
+
+            wxMemoryDC memDC;
+            memDC.SelectObject(bitmap);
+
+            memDC.SetFont(m_action_font);
+            memDC.SetTextForeground(wxColour(237, 107, 33));
+            memDC.DrawText(text, int(m_scale * 60), m_action_line_y_position);
+
+            memDC.SelectObject(wxNullBitmap);
+            set_bitmap(bitmap);
+#ifdef __WXOSX__
+            // without this code splash screen wouldn't be updated under OSX
+            wxYield();
+#endif
+        }
+    }
+
+    static wxBitmap MakeBitmap(wxBitmap bmp)
+    {
+        if (!bmp.IsOk())
+            return wxNullBitmap;
+
+        // create dark grey background for the splashscreen
+        // It will be 5/3 of the weight of the bitmap
+        int width = lround((double)5 / 3 * bmp.GetWidth());
+        int height = bmp.GetHeight();
+
+        wxImage image(width, height);
+        unsigned char* imgdata_ = image.GetData();
+        for (int i = 0; i < width * height; ++i) {
+            *imgdata_++ = 51;
+            *imgdata_++ = 51;
+            *imgdata_++ = 51;
+        }
+
+        wxBitmap new_bmp(image);
+
+        wxMemoryDC memDC;
+        memDC.SelectObject(new_bmp);
+        memDC.DrawBitmap(bmp, width - bmp.GetWidth(), 0, true);
+
+        return new_bmp;
+    }
+
+    void Decorate(wxBitmap& bmp)
+    {
+        if (!bmp.IsOk())
+            return;
+
+        // draw text to the box at the left of the splashscreen.
+        // this box will be 2/5 of the weight of the bitmap, and be at the left.
+        int width = lround(bmp.GetWidth() * 0.4);
+
+        // load bitmap for logo
+        BitmapCache bmp_cache;
+        int logo_size = lround(width * 0.25);
+        wxBitmap logo_bmp = *bmp_cache.load_svg(wxGetApp().logo_name(), logo_size, logo_size);
+
+        wxCoord margin = int(m_scale * 20);
+
+        wxRect banner_rect(wxPoint(0, logo_size), wxPoint(width, bmp.GetHeight()));
+        banner_rect.Deflate(margin, 2 * margin);
+
+        // use a memory DC to draw directly onto the bitmap
+        wxMemoryDC memDc(bmp);
+
+        // draw logo
+        memDc.DrawBitmap(logo_bmp, margin, margin, true);
+
+        // draw the (white) labels inside of our black box (at the left of the splashscreen)
+        memDc.SetTextForeground(wxColour(255, 255, 255));
+
+        memDc.SetFont(m_constant_text.title_font);
+        memDc.DrawLabel(m_constant_text.title,   banner_rect, wxALIGN_TOP | wxALIGN_LEFT);
+
+        int title_height = memDc.GetTextExtent(m_constant_text.title).GetY();
+        banner_rect.SetTop(banner_rect.GetTop() + title_height);
+        banner_rect.SetHeight(banner_rect.GetHeight() - title_height);
+
+        memDc.SetFont(m_constant_text.version_font);
+        memDc.DrawLabel(m_constant_text.version, banner_rect, wxALIGN_TOP | wxALIGN_LEFT);
+        int version_height = memDc.GetTextExtent(m_constant_text.version).GetY();
+
+        memDc.SetFont(m_constant_text.credits_font);
+        memDc.DrawLabel(m_constant_text.credits, banner_rect, wxALIGN_BOTTOM | wxALIGN_LEFT);
+        int credits_height = memDc.GetMultiLineTextExtent(m_constant_text.credits).GetY();
+        int text_height    = memDc.GetTextExtent("text").GetY();
+
+        // calculate position for the dynamic text
+        int logo_and_header_height = margin + logo_size + title_height + version_height;
+        m_action_line_y_position = logo_and_header_height + 0.5 * (bmp.GetHeight() - margin - credits_height - logo_and_header_height - text_height);
+    }
+
+private:
+    wxBitmap    m_main_bitmap;
+    wxFont      m_action_font;
+    int         m_action_line_y_position;
+    float       m_scale {1.0};
+
+    struct ConstantText
+    {
+        wxString title;
+        wxString version;
+        wxString credits;
+
+        wxFont   title_font;
+        wxFont   version_font;
+        wxFont   credits_font;
+
+        void init(wxFont init_font)
+        {
+            // title
+            title = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME;
+
+            // dynamically get the version to display
+            version = _L("Version") + " " + std::string(SLIC3R_VERSION);
+
+            // credits infornation
+            credits =   title + " " +
+                        _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n" +
+                        _L("Developed by Prusa Research.")+ "\n\n" +
+                        title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + "\n\n" +
+                        _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" +
+                        _L("Artwork model by M Boyer");
+
+            title_font = version_font = credits_font = init_font;
+        }
+    } 
+    m_constant_text;
+
+    void init_constant_text()
+    {
+        m_constant_text.init(get_default_font(this));
+
+        // As default we use a system font for current display.
+        // Scale fonts in respect to banner width
+
+        int text_banner_width = lround(0.4 * m_main_bitmap.GetWidth()) - roundl(m_scale * 50); // banner_width - margins
+
+        float title_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.title).GetX();
+        scale_font(m_constant_text.title_font, title_font_scale > 3.5f ? 3.5f : title_font_scale);
+
+        float version_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.version).GetX();
+        scale_font(m_constant_text.version_font, version_font_scale > 2.f ? 2.f : version_font_scale);
+
+        // The width of the credits information string doesn't respect to the banner width some times.
+        // So, scale credits_font in the respect to the longest string width
+        int   longest_string_width = word_wrap_string(m_constant_text.credits);
+        float font_scale = (float)text_banner_width / longest_string_width;
+        scale_font(m_constant_text.credits_font, font_scale);
+    }
+
+    void set_bitmap(wxBitmap& bmp)
+    {
+        m_window->SetBitmap(bmp);
+        m_window->Refresh();
+        m_window->Update();
+    }
+
+    void scale_bitmap(wxBitmap& bmp, float scale)
+    {
+        if (scale == 1.0)
+            return;
+
+        wxImage image = bmp.ConvertToImage();
+        if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0)
+            return;
+
+        int width   = int(scale * image.GetWidth());
+        int height  = int(scale * image.GetHeight());
+        image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR);
+
+        bmp = wxBitmap(std::move(image));
+    }
+
+    void scale_font(wxFont& font, float scale)
+    {
+#ifdef __WXMSW__
+        // Workaround for the font scaling in respect to the current active display,
+        // not for the primary display, as it's implemented in Font.cpp
+        // See https://github.com/wxWidgets/wxWidgets/blob/master/src/msw/font.cpp
+        // void wxNativeFontInfo::SetFractionalPointSize(float pointSizeNew)
+        wxNativeFontInfo nfi= *font.GetNativeFontInfo();
+        float pointSizeNew  = scale * font.GetPointSize();
+        nfi.lf.lfHeight     = nfi.GetLogFontHeightAtPPI(pointSizeNew, get_dpi_for_window(this));
+        nfi.pointSize       = pointSizeNew;
+        font = wxFont(nfi);
+#else
+        font.Scale(scale);
+#endif //__WXMSW__
+    }
+
+    // wrap a string for the strings no longer then 55 symbols
+    // return extent of the longest string
+    int word_wrap_string(wxString& input)
+    {
+        size_t line_len = 55;// count of symbols in one line
+        int idx = -1;
+        size_t cur_len = 0;
+
+        wxString longest_sub_string;
+        auto get_longest_sub_string = [input](wxString &longest_sub_str, size_t cur_len, size_t i) {
+            if (cur_len > longest_sub_str.Len())
+                longest_sub_str = input.SubString(i - cur_len + 1, i);
+        };
+
+        for (size_t i = 0; i < input.Len(); i++)
+        {
+            cur_len++;
+            if (input[i] == ' ')
+                idx = i;
+            if (input[i] == '\n')
+            {
+                get_longest_sub_string(longest_sub_string, cur_len, i);
+                idx = -1;
+                cur_len = 0;
+            }
+            if (cur_len >= line_len && idx >= 0)
+            {
+                get_longest_sub_string(longest_sub_string, cur_len, i);
+                input[idx] = '\n';
+                cur_len = i - static_cast<size_t>(idx);
+            }
+        }
+
+        return GetTextExtent(longest_sub_string).GetX();
+    }
+};
+
+
+#ifdef __linux__
+bool static check_old_linux_datadir(const wxString& app_name) {
+    // If we are on Linux and the datadir does not exist yet, look into the old
+    // location where the datadir was before version 2.3. If we find it there,
+    // tell the user that he might wanna migrate to the new location.
+    // (https://github.com/prusa3d/PrusaSlicer/issues/2911)
+    // To be precise, the datadir should exist, it is created when single instance
+    // lock happens. Instead of checking for existence, check the contents.
+
+    namespace fs = boost::filesystem;
+
+    std::string new_path = Slic3r::data_dir();
+
+    wxString dir;
+    if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() )
+        dir = wxFileName::GetHomeDir() + wxS("/.config");
+    std::string default_path = (dir + "/" + app_name).ToUTF8().data();
+
+    if (new_path != default_path) {
+        // This happens when the user specifies a custom --datadir.
+        // Do not show anything in that case.
+        return true;
+    }
+
+    fs::path data_dir = fs::path(new_path);
+    if (! fs::is_directory(data_dir))
+        return true; // This should not happen.
+
+    int file_count = std::distance(fs::directory_iterator(data_dir), fs::directory_iterator());
+
+    if (file_count <= 1) { // just cache dir with an instance lock
+        std::string old_path = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data();
+
+        if (fs::is_directory(old_path)) {
+            wxString msg = from_u8((boost::format(_u8L("Starting with %1% 2.3, configuration "
+                "directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\n"
+                "This directory did not exist yet (maybe you run the new version for the first time).\nHowever, "
+                "an old %1% configuration directory was detected in \n%3%.\n\n"
+                "Consider moving the contents of the old directory to the new location in order to access "
+                "your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old "
+                "location again.\n\n"
+                "What do you want to do now?")) % SLIC3R_APP_NAME % new_path % old_path).str());
+            wxString caption = from_u8((boost::format(_u8L("%s - BREAKING CHANGE")) % SLIC3R_APP_NAME).str());
+            RichMessageDialog dlg(nullptr, msg, caption, wxYES_NO);
+            dlg.SetYesNoLabels(_L("Quit, I will move my data now"), _L("Start the application"));
+            if (dlg.ShowModal() != wxID_NO)
+                return false;
+        }
+    } else {
+        // If the new directory exists, be silent. The user likely already saw the message.
+    }
+    return true;
+}
+#endif
+
+#ifdef _WIN32
+#if 0 // External Updater is replaced with AppUpdater.cpp
+static bool run_updater_win()
+{
+    // find updater exe
+    boost::filesystem::path path_updater = boost::dll::program_location().parent_path() / "prusaslicer-updater.exe";
+    // run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst
+    std::string msg;
+    bool res = create_process(path_updater, L"/silent", msg);
+    if (!res)
+        BOOST_LOG_TRIVIAL(error) << msg; 
+    return res;
+}
+#endif // 0
+#endif // _WIN32
+
+struct FileWildcards {
+    std::string_view              title;
+    std::vector<std::string_view> file_extensions;
+};
+
+static const FileWildcards file_wildcards_by_type[FT_SIZE] = {
+    /* FT_STL */     { "STL files"sv,       { ".stl"sv } },
+    /* FT_OBJ */     { "OBJ files"sv,       { ".obj"sv } },
+    /* FT_OBJECT */  { "Object files"sv,    { ".stl"sv, ".obj"sv } },
+    /* FT_AMF */     { "AMF files"sv,       { ".amf"sv, ".zip.amf"sv, ".xml"sv } },
+    /* FT_3MF */     { "3MF files"sv,       { ".3mf"sv } },
+    /* FT_GCODE */   { "G-code files"sv,    { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } },
+    /* FT_MODEL */   { "Known files"sv,     { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv } },
+    /* FT_PROJECT */ { "Project files"sv,   { ".3mf"sv, ".amf"sv, ".zip.amf"sv } },
+    /* FT_GALLERY */ { "Known files"sv,     { ".stl"sv, ".obj"sv } },
+
+    /* FT_INI */     { "INI files"sv,       { ".ini"sv } },
+    /* FT_SVG */     { "SVG files"sv,       { ".svg"sv } },
+
+    /* FT_TEX */     { "Texture"sv,         { ".png"sv, ".svg"sv } },
+
+    /* FT_SL1 */     { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } },
+};
+
+#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR
+wxString file_wildcards(FileType file_type)
+{
+    const FileWildcards& data = file_wildcards_by_type[file_type];
+    std::string title;
+    std::string mask;
+
+    // Generate cumulative first item
+    for (const std::string_view& ext : data.file_extensions) {
+        if (title.empty()) {
+            title = "*";
+            title += ext;
+            mask = title;
+        }
+        else {
+            title += ", *";
+            title += ext;
+            mask += ";*";
+            mask += ext;
+        }
+        mask += ";*";
+        mask += boost::to_upper_copy(std::string(ext));
+    }
+
+    wxString ret = GUI::format_wxstr("%s (%s)|%s", data.title, title, mask);
+
+    // Adds an item for each of the extensions
+    if (data.file_extensions.size() > 1) {
+        for (const std::string_view& ext : data.file_extensions) {
+            title = "*";
+            title += ext;
+            ret += GUI::format_wxstr("|%s (%s)|%s", data.title, title, title);
+        }
+    }
+
+    return ret;
+}
+#else
+// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms.
+// The function accepts a custom extension parameter. If the parameter is provided, the custom extension
+// will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips
+// an extension from the provided initial file name and substitutes it with the default extension (the first one in the template).
+wxString file_wildcards(FileType file_type, const std::string &custom_extension)
+{
+    const FileWildcards& data = file_wildcards_by_type[file_type];
+    std::string title;
+    std::string mask;
+    std::string custom_ext_lower;
+
+    if (! custom_extension.empty()) {
+        // Generate an extension into the title mask and into the list of extensions.
+        custom_ext_lower = boost::to_lower_copy(custom_extension);
+        const std::string custom_ext_upper = boost::to_upper_copy(custom_extension);
+        if (custom_ext_lower == custom_extension) {
+            // Add a lower case version.
+            title = std::string("*") + custom_ext_lower;
+            mask = title;
+            // Add an upper case version.
+            mask  += ";*";
+            mask  += custom_ext_upper;
+        } else if (custom_ext_upper == custom_extension) {
+            // Add an upper case version.
+            title = std::string("*") + custom_ext_upper;
+            mask = title;
+            // Add a lower case version.
+            mask += ";*";
+            mask += custom_ext_lower;
+        } else {
+            // Add the mixed case version only.
+            title = std::string("*") + custom_extension;
+            mask = title;
+        }
+    }
+
+    for (const std::string_view &ext : data.file_extensions)
+        // Only add an extension if it was not added first as the custom extension.
+        if (ext != custom_ext_lower) {
+            if (title.empty()) {
+                title = "*";
+                title += ext;
+                mask  = title;
+            } else {
+                title += ", *";
+                title += ext;
+                mask  += ";*";
+                mask  += ext;
+            }
+            mask += ";*";
+            mask += boost::to_upper_copy(std::string(ext));
+        }
+
+    return GUI::format_wxstr("%s (%s)|%s", data.title, title, mask);
+}
+#endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR
+
+static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); }
+
+#ifdef WIN32
+#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
+static void register_win32_dpi_event()
+{
+    enum { WM_DPICHANGED_ = 0x02e0 };
+
+    wxWindow::MSWRegisterMessageHandler(WM_DPICHANGED_, [](wxWindow *win, WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) {
+        const int dpi = wParam & 0xffff;
+        const auto rect = reinterpret_cast<PRECT>(lParam);
+        const wxRect wxrect(wxPoint(rect->top, rect->left), wxPoint(rect->bottom, rect->right));
+
+        DpiChangedEvent evt(EVT_DPI_CHANGED_SLICER, dpi, wxrect);
+        win->GetEventHandler()->AddPendingEvent(evt);
+
+        return true;
+    });
+}
+#endif // !wxVERSION_EQUAL_OR_GREATER_THAN
+
+static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
+
+static void register_win32_device_notification_event()
+{
+    wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
+        // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only.
+        auto main_frame = dynamic_cast<MainFrame*>(win);
+        auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater();
+        if (plater == nullptr)
+            // Maybe some other top level window like a dialog or maybe a pop-up menu?
+            return true;
+		PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
+        switch (wParam) {
+        case DBT_DEVICEARRIVAL:
+			if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
+		        plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED));
+			else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
+				PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
+//				if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) {
+//					printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name);
+				if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID)
+			        plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name)));
+			}
+            break;
+		case DBT_DEVICEREMOVECOMPLETE:
+			if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
+                plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED));
+			else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
+				PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
+//				if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME)
+//					printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name);
+				if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID)
+        			plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name)));
+			}
+			break;
+        default:
+            break;
+        }
+        return true;
+    });
+
+    wxWindow::MSWRegisterMessageHandler(MainFrame::WM_USER_MEDIACHANGED, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
+        // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only.
+        auto main_frame = dynamic_cast<MainFrame*>(win);
+        auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater();
+        if (plater == nullptr)
+            // Maybe some other top level window like a dialog or maybe a pop-up menu?
+            return true;
+        wchar_t sPath[MAX_PATH];
+        if (lParam == SHCNE_MEDIAINSERTED || lParam == SHCNE_MEDIAREMOVED) {
+            struct _ITEMIDLIST* pidl = *reinterpret_cast<struct _ITEMIDLIST**>(wParam);
+            if (! SHGetPathFromIDList(pidl, sPath)) {
+                BOOST_LOG_TRIVIAL(error) << "MediaInserted: SHGetPathFromIDList failed";
+                return false;
+            }
+        }
+        switch (lParam) {
+        case SHCNE_MEDIAINSERTED:
+        {
+            //printf("SHCNE_MEDIAINSERTED %S\n", sPath);
+            plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED));
+            break;
+        }
+        case SHCNE_MEDIAREMOVED:
+        {
+            //printf("SHCNE_MEDIAREMOVED %S\n", sPath);
+            plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED));
+            break;
+        }
+	    default:
+//          printf("Unknown\n");
+            break;
+	    }
+        return true;
+    });
+
+    wxWindow::MSWRegisterMessageHandler(WM_INPUT, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
+        auto main_frame = dynamic_cast<MainFrame*>(Slic3r::GUI::find_toplevel_parent(win));
+        auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater();
+//        if (wParam == RIM_INPUTSINK && plater != nullptr && main_frame->IsActive()) {
+        if (wParam == RIM_INPUT && plater != nullptr && main_frame->IsActive()) {
+        RAWINPUT raw;
+			UINT rawSize = sizeof(RAWINPUT);
+			::GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER));
+			if (raw.header.dwType == RIM_TYPEHID && plater->get_mouse3d_controller().handle_raw_input_win32(raw.data.hid.bRawData, raw.data.hid.dwSizeHid))
+				return true;
+		}
+        return false;
+    });
+
+	wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
+		COPYDATASTRUCT* copy_data_structure = { 0 };
+		copy_data_structure = (COPYDATASTRUCT*)lParam;
+		if (copy_data_structure->dwData == 1) {
+			LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData;
+			Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments));
+		}
+		return true;
+		});
+}
+#endif // WIN32
+
+static void generic_exception_handle()
+{
+    // Note: Some wxWidgets APIs use wxLogError() to report errors, eg. wxImage
+    // - see https://docs.wxwidgets.org/3.1/classwx_image.html#aa249e657259fe6518d68a5208b9043d0
+    //
+    // wxLogError typically goes around exception handling and display an error dialog some time
+    // after an error is logged even if exception handling and OnExceptionInMainLoop() take place.
+    // This is why we use wxLogError() here as well instead of a custom dialog, because it accumulates
+    // errors if multiple have been collected and displays just one error message for all of them.
+    // Otherwise we would get multiple error messages for one missing png, for example.
+    //
+    // If a custom error message window (or some other solution) were to be used, it would be necessary
+    // to turn off wxLogError() usage in wx APIs, most notably in wxImage
+    // - see https://docs.wxwidgets.org/trunk/classwx_image.html#aa32e5d3507cc0f8c3330135bc0befc6a
+
+    try {
+        throw;
+    } catch (const std::bad_alloc& ex) {
+        // bad_alloc in main thread is most likely fatal. Report immediately to the user (wxLogError would be delayed)
+        // and terminate the app so it is at least certain to happen now.
+        wxString errmsg = wxString::Format(_L("%s has encountered an error. It was likely caused by running out of memory. "
+                              "If you are sure you have enough RAM on your system, this may also be a bug and we would "
+                              "be glad if you reported it.\n\nThe application will now terminate."), SLIC3R_APP_NAME);
+        wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Fatal error"), wxOK | wxICON_ERROR);
+        BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what();
+        std::terminate();
+    } catch (const boost::io::bad_format_string& ex) {
+        wxString errmsg = _L("PrusaSlicer has encountered a localization error. "
+                             "Please report to PrusaSlicer team, what language was active and in which scenario "
+                             "this issue happened. Thank you.\n\nThe application will now terminate.");
+        wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Critical error"), wxOK | wxICON_ERROR);
+        BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what();
+        std::terminate();
+        throw;
+    } catch (const std::exception& ex) {
+        wxLogError(format_wxstr(_L("Internal error: %1%"), ex.what()));
+        BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what();
+        throw;
+    }
+}
+
+void GUI_App::post_init()
+{
+    assert(initialized());
+    if (! this->initialized())
+        throw Slic3r::RuntimeError("Calling post_init() while not yet initialized");
+
+    if (this->init_params->start_as_gcodeviewer) {
+        if (! this->init_params->input_files.empty())
+            this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str()));
+    }
+    else {
+        if (! this->init_params->preset_substitutions.empty())
+            show_substitutions_info(this->init_params->preset_substitutions);
+
+#if 0
+        // Load the cummulative config over the currently active profiles.
+        //FIXME if multiple configs are loaded, only the last one will have an effect.
+        // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc).
+        // As of now only the full configs are supported here.
+        if (!m_print_config.empty())
+            this->gui->mainframe->load_config(m_print_config);
+#endif
+        if (! this->init_params->load_configs.empty())
+            // Load the last config to give it a name at the UI. The name of the preset may be later
+            // changed by loading an AMF or 3MF.
+            //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
+            this->mainframe->load_config_file(this->init_params->load_configs.back());
+        // If loading a 3MF file, the config is loaded from the last one.
+        if (!this->init_params->input_files.empty()) {
+            const std::vector<size_t> res = this->plater()->load_files(this->init_params->input_files, true, true);
+            if (!res.empty() && this->init_params->input_files.size() == 1) {
+                // Update application titlebar when opening a project file
+                const std::string& filename = this->init_params->input_files.front();
+                if (boost::algorithm::iends_with(filename, ".amf") ||
+                    boost::algorithm::iends_with(filename, ".amf.xml") ||
+                    boost::algorithm::iends_with(filename, ".3mf"))
+                    this->plater()->set_project_filename(from_u8(filename));
+            }
+        }
+        if (! this->init_params->extra_config.empty())
+            this->mainframe->load_config(this->init_params->extra_config);
+    }
+
+    // show "Did you know" notification
+    if (app_config->get("show_hints") == "1" && ! is_gcode_viewer())
+        plater_->get_notification_manager()->push_hint_notification(true);
+
+    // The extra CallAfter() is needed because of Mac, where this is the only way
+    // to popup a modal dialog on start without screwing combo boxes.
+    // This is ugly but I honestly found no better way to do it.
+    // Neither wxShowEvent nor wxWindowCreateEvent work reliably.
+    if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater.
+        if (! this->check_updates(false))
+            // Configuration is not compatible and reconfigure was refused by the user. Application is closing.
+            return;
+        CallAfter([this] {
+            bool cw_showed = this->config_wizard_startup();
+            this->preset_updater->sync(preset_bundle);
+            this->app_version_check(false);
+            if (! cw_showed) {
+                // The CallAfter is needed as well, without it, GL extensions did not show.
+                // Also, we only want to show this when the wizard does not, so the new user
+                // sees something else than "we want something" on the first start.
+                show_send_system_info_dialog_if_needed();
+            }
+        });
+    }
+
+    // Set PrusaSlicer version and save to PrusaSlicer.ini or PrusaSlicerGcodeViewer.ini.
+    app_config->set("version", SLIC3R_VERSION);
+    app_config->save();
+
+#ifdef _WIN32
+    // Sets window property to mainframe so other instances can indentify it.
+    OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int);
+#endif //WIN32
+}
+
+IMPLEMENT_APP(GUI_App)
+
+GUI_App::GUI_App(EAppMode mode)
+    : wxApp()
+    , m_app_mode(mode)
+    , m_em_unit(10)
+    , m_imgui(new ImGuiWrapper())
+	, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
+	, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
+{
+	//app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp
+	this->init_app_config();
+    // init app downloader after path to datadir is set
+    m_app_updater = std::make_unique<AppUpdater>();
+}
+
+GUI_App::~GUI_App()
+{
+    if (app_config != nullptr)
+        delete app_config;
+
+    if (preset_bundle != nullptr)
+        delete preset_bundle;
+
+    if (preset_updater != nullptr)
+        delete preset_updater;
+}
+
+// If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
+// Otherwise HTML formatted for the system info dialog.
+std::string GUI_App::get_gl_info(bool for_github)
+{
+    return OpenGLManager::get_gl_info().to_string(for_github);
+}
+
+wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas)
+{
+    return m_opengl_mgr.init_glcontext(canvas);
+}
+
+bool GUI_App::init_opengl()
+{
+#ifdef __linux__
+    bool status = m_opengl_mgr.init_gl();
+    m_opengl_initialized = true;
+    return status;
+#else
+    return m_opengl_mgr.init_gl();
+#endif
+}
+
+// gets path to PrusaSlicer.ini, returns semver from first line comment
+static boost::optional<Semver> parse_semver_from_ini(std::string path)
+{
+    std::ifstream stream(path);
+    std::stringstream buffer;
+    buffer << stream.rdbuf();
+    std::string body = buffer.str();
+    size_t start = body.find("PrusaSlicer ");
+    if (start == std::string::npos)
+        return boost::none;
+    body = body.substr(start + 12);
+    size_t end = body.find_first_of(" \n");
+    if (end < body.size())
+        body.resize(end);
+    return Semver::parse(body);
+}
+
+void GUI_App::init_app_config()
+{
+	// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
+//    SetAppName(SLIC3R_APP_KEY);
+	SetAppName(SLIC3R_APP_KEY "-alpha");
+//    SetAppName(SLIC3R_APP_KEY "-beta");
+
+
+//	SetAppDisplayName(SLIC3R_APP_NAME);
+
+	// Set the Slic3r data directory at the Slic3r XS module.
+	// Unix: ~/ .Slic3r
+	// Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
+	// Mac : "~/Library/Application Support/Slic3r"
+
+    if (data_dir().empty()) {
+        #ifndef __linux__
+            set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
+        #else
+            // Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}.
+            // https://github.com/prusa3d/PrusaSlicer/issues/2911
+            wxString dir;
+            if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() )
+                dir = wxFileName::GetHomeDir() + wxS("/.config");
+            set_data_dir((dir + "/" + GetAppName()).ToUTF8().data());
+        #endif
+    } else {
+        m_datadir_redefined = true;
+    }
+
+	if (!app_config)
+        app_config = new AppConfig(is_editor() ? AppConfig::EAppMode::Editor : AppConfig::EAppMode::GCodeViewer);
+
+	// load settings
+	m_app_conf_exists = app_config->exists();
+	if (m_app_conf_exists) {
+        std::string error = app_config->load();
+        if (!error.empty()) {
+            // Error while parsing config file. We'll customize the error message and rethrow to be displayed.
+            if (is_editor()) {
+                throw Slic3r::RuntimeError(
+                    _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. "
+                        "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") +
+                    "\n\n" + app_config->config_path() + "\n\n" + error);
+            }
+            else {
+                throw Slic3r::RuntimeError(
+                    _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. "
+                        "Try to manually delete the file to recover from the error.") +
+                    "\n\n" + app_config->config_path() + "\n\n" + error);
+            }
+        }
+    }
+}
+
+// returns old config path to copy from if such exists,
+// returns an empty string if such config path does not exists or if it cannot be loaded.
+std::string GUI_App::check_older_app_config(Semver current_version, bool backup)
+{
+    std::string older_data_dir_path;
+
+    // If the config folder is redefined - do not check
+    if (m_datadir_redefined)
+        return {};
+
+    // find other version app config (alpha / beta / release)
+    std::string             config_path = app_config->config_path();
+    boost::filesystem::path parent_file_path(config_path);
+    std::string             filename = parent_file_path.filename().string();
+    parent_file_path.remove_filename().remove_filename();
+
+    std::vector<boost::filesystem::path> candidates;
+
+    if (SLIC3R_APP_KEY "-alpha" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-alpha" / filename);
+    if (SLIC3R_APP_KEY "-beta" != GetAppName())  candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-beta" / filename);
+    if (SLIC3R_APP_KEY != GetAppName())          candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY / filename);
+
+    Semver last_semver = current_version;
+    for (const auto& candidate : candidates) {
+        if (boost::filesystem::exists(candidate)) {
+            // parse
+            boost::optional<Semver>other_semver = parse_semver_from_ini(candidate.string());
+            if (other_semver && *other_semver > last_semver) {
+                last_semver = *other_semver;
+                older_data_dir_path = candidate.parent_path().string();
+            }
+        }
+    }
+    if (older_data_dir_path.empty())
+        return {};
+    BOOST_LOG_TRIVIAL(info) << "last app config file used: " << older_data_dir_path;
+    // ask about using older data folder
+
+    InfoDialog msg(nullptr
+        , format_wxstr(_L("You are opening %1% version %2%."), SLIC3R_APP_NAME, SLIC3R_VERSION)
+        , backup ? 
+        format_wxstr(_L(
+            "The active configuration was created by <b>%1% %2%</b>,"
+            "\nwhile a newer configuration was found in <b>%3%</b>"
+            "\ncreated by <b>%1% %4%</b>."
+            "\n\nShall the newer configuration be imported?"
+            "\nIf so, your active configuration will be backed up before importing the new configuration."
+        )
+            , SLIC3R_APP_NAME, current_version.to_string(), older_data_dir_path, last_semver.to_string())
+        : format_wxstr(_L(
+            "An existing configuration was found in <b>%3%</b>"
+            "\ncreated by <b>%1% %2%</b>."
+            "\n\nShall this configuration be imported?"
+        )
+            , SLIC3R_APP_NAME, last_semver.to_string(), older_data_dir_path)
+        , true, wxYES_NO);
+
+    if (backup) {
+        msg.SetButtonLabel(wxID_YES, _L("Import"));
+        msg.SetButtonLabel(wxID_NO, _L("Don't import"));
+    }
+
+    if (msg.ShowModal() == wxID_YES) {
+        std::string snapshot_id;
+        if (backup) {
+            const Config::Snapshot* snapshot{ nullptr };
+            if (! GUI::Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_USER, "",
+                _u8L("Continue and import newer configuration?"), &snapshot))
+                return {};
+            if (snapshot) {
+                // Save snapshot ID before loading the alternate AppConfig, as loading the alternate AppConfig may fail.
+                snapshot_id = snapshot->id;
+                assert(! snapshot_id.empty());
+                app_config->set("on_snapshot", snapshot_id);
+            } else
+                BOOST_LOG_TRIVIAL(error) << "Failed to take congiguration snapshot";
+        }
+
+        // load app config from older file
+        std::string error = app_config->load((boost::filesystem::path(older_data_dir_path) / filename).string());
+        if (!error.empty()) {
+            // Error while parsing config file. We'll customize the error message and rethrow to be displayed.
+            if (is_editor()) {
+                throw Slic3r::RuntimeError(
+                    _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. "
+                        "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") +
+                    "\n\n" + app_config->config_path() + "\n\n" + error);
+            }
+            else {
+                throw Slic3r::RuntimeError(
+                    _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. "
+                        "Try to manually delete the file to recover from the error.") +
+                    "\n\n" + app_config->config_path() + "\n\n" + error);
+            }
+        }
+        if (!snapshot_id.empty())
+            app_config->set("on_snapshot", snapshot_id);
+        m_app_conf_exists = true;
+        return older_data_dir_path;
+    }
+    return {};
+}
+
+void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path)
+{
+    BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path; 
+    m_single_instance_checker = std::make_unique<wxSingleInstanceChecker>(boost::nowide::widen(name), boost::nowide::widen(path));
+}
+
+bool GUI_App::OnInit()
+{
+    try {
+        return on_init_inner();
+    } catch (const std::exception&) {
+        generic_exception_handle();
+        return false;
+    }
+}
+
+bool GUI_App::on_init_inner()
+{
+    // Set initialization of image handlers before any UI actions - See GH issue #7469
+    wxInitAllImageHandlers();
+
+#if defined(_WIN32) && ! defined(_WIN64)
+    // Win32 32bit build.
+    if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") {
+        RichMessageDialog dlg(nullptr,
+            _L("You are running a 32 bit build of PrusaSlicer on 64-bit Windows."
+                "\n32 bit build of PrusaSlicer will likely not be able to utilize all the RAM available in the system."
+                "\nPlease download and install a 64 bit build of PrusaSlicer from https://www.prusa3d.cz/prusaslicer/."
+                "\nDo you wish to continue?"),
+            "PrusaSlicer", wxICON_QUESTION | wxYES_NO);
+        if (dlg.ShowModal() != wxID_YES)
+            return false;
+    }
+#endif // _WIN64
+
+    // Forcing back menu icons under gtk2 and gtk3. Solution is based on:
+    // https://docs.gtk.org/gtk3/class.Settings.html
+    // see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb
+    // TODO: Find workaround for GTK4
+#if defined(__WXGTK20__) || defined(__WXGTK3__)
+    g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL);
+#endif
+
+    // Verify resources path
+    const wxString resources_dir = from_u8(Slic3r::resources_dir());
+    wxCHECK_MSG(wxDirExists(resources_dir), false,
+        wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir));
+
+#ifdef __linux__
+    if (! check_old_linux_datadir(GetAppName())) {
+        std::cerr << "Quitting, user chose to move their data to new location." << std::endl;
+        return false;
+    }
+#endif
+
+    // Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
+//    wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0);
+    // Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible
+    // performance when working on high resolution multi-display setups.
+//    wxSystemOptions::SetOption("msw.notebook.themed-background", 0);
+
+//     Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION;
+
+    // !!! Initialization of UI settings as a language, application color mode, fonts... have to be done before first UI action.
+    // Like here, before the show InfoDialog in check_older_app_config()
+
+    // If load_language() fails, the application closes.
+    load_language(wxString(), true);
+#ifdef _MSW_DARK_MODE
+    bool init_dark_color_mode = app_config->get("dark_color_mode") == "1";
+    bool init_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1";
+    NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled);
+#endif
+    // initialize label colors and fonts
+    init_label_colours();
+    init_fonts();
+
+    std::string older_data_dir_path;
+    if (m_app_conf_exists) {
+        if (app_config->orig_version().valid() && app_config->orig_version() < *Semver::parse(SLIC3R_VERSION))
+            // Only copying configuration if it was saved with a newer slicer than the one currently running.
+            older_data_dir_path = check_older_app_config(app_config->orig_version(), true);
+    } else {
+        // No AppConfig exists, fresh install. Always try to copy from an alternate location, don't make backup of the current configuration.
+        older_data_dir_path = check_older_app_config(Semver(), false);
+    }
+
+#ifdef _MSW_DARK_MODE
+    // app_config can be updated in check_older_app_config(), so check if dark_color_mode and sys_menu_enabled was changed
+    if (bool new_dark_color_mode = app_config->get("dark_color_mode") == "1";
+        init_dark_color_mode != new_dark_color_mode) {
+        NppDarkMode::SetDarkMode(new_dark_color_mode);
+        init_label_colours();
+        update_label_colours_from_appconfig();
+    }
+    if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1";
+        init_sys_menu_enabled != new_sys_menu_enabled)
+        NppDarkMode::SetSystemMenuForApp(new_sys_menu_enabled);
+#endif
+
+    if (is_editor()) {
+        std::string msg = Http::tls_global_init();
+        std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location");
+        bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store();
+
+        if (!msg.empty() && !ssl_accept) {
+            RichMessageDialog
+                dlg(nullptr,
+                    wxString::Format(_L("%s\nDo you want to continue?"), msg),
+                    "PrusaSlicer", wxICON_QUESTION | wxYES_NO);
+            dlg.ShowCheckBox(_L("Remember my choice"));
+            if (dlg.ShowModal() != wxID_YES) return false;
+
+            app_config->set("tls_cert_store_accepted",
+                dlg.IsCheckBoxChecked() ? "yes" : "no");
+            app_config->set("tls_accepted_cert_store_location",
+                dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : "");
+        }
+    }
+
+    SplashScreen* scrn = nullptr;
+    if (app_config->get("show_splash_screen") == "1") {
+        // make a bitmap with dark grey banner on the left side
+        wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var(is_editor() ? "splashscreen.jpg" : "splashscreen-gcodepreview.jpg")), wxBITMAP_TYPE_JPEG));
+
+        // Detect position (display) to show the splash screen
+        // Now this position is equal to the mainframe position
+        wxPoint splashscreen_pos = wxDefaultPosition;
+        bool default_splashscreen_pos = true;
+        if (app_config->has("window_mainframe") && app_config->get("restore_win_position") == "1") {
+            auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe"));
+            default_splashscreen_pos = metrics == boost::none;
+            if (!default_splashscreen_pos)
+                splashscreen_pos = metrics->get_rect().GetPosition();
+        }
+
+        if (!default_splashscreen_pos) {
+            // workaround for crash related to the positioning of the window on secondary monitor
+            get_app_config()->set("restore_win_position", "crashed_at_splashscreen_pos");
+            get_app_config()->save();
+        }
+
+        // create splash screen with updated bmp
+        scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("PrusaSlicer", nullptr, 400), 
+                                wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos);
+
+        if (!default_splashscreen_pos)
+            // revert "restore_win_position" value if application wasn't crashed
+            get_app_config()->set("restore_win_position", "1");
+#ifndef __linux__
+        wxYield();
+#endif
+        scrn->SetText(_L("Loading configuration")+ dots);
+    }
+
+    preset_bundle = new PresetBundle();
+
+    // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory
+    // supplied as argument to --datadir; in that case we should still run the wizard
+    preset_bundle->setup_directories();
+    
+    if (! older_data_dir_path.empty()) {
+        preset_bundle->import_newer_configs(older_data_dir_path);
+        app_config->save();
+    }
+
+    if (is_editor()) {
+#ifdef __WXMSW__ 
+#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+        // file association is not possible anymore starting with Win 8
+        if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
+#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+            if (app_config->get("associate_3mf") == "1")
+                associate_3mf_files();
+            if (app_config->get("associate_stl") == "1")
+                associate_stl_files();
+#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+        }
+#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+#endif // __WXMSW__
+
+        preset_updater = new PresetUpdater();
+        Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this);
+        Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
+            if (this->plater_ != nullptr && app_config->get("notify_release") == "all") {
+                std::string evt_string = into_u8(evt.GetString());
+                if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) {
+                    auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable);
+                    this->plater_->get_notification_manager()->push_notification( notif_type
+                        , NotificationManager::NotificationLevel::ImportantNotificationLevel
+                        , Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string)
+                        , _u8L("See Releases page.")
+                        , [](wxEvtHandler* evnthndlr) {wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }
+                    );
+                }
+            }
+            });
+        Bind(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, [this](const wxCommandEvent& evt) {
+            //lm:This does not force a render. The progress bar only updateswhen the mouse is moved.
+            if (this->plater_ != nullptr)
+                this->plater_->get_notification_manager()->set_download_progress_percentage((float)std::stoi(into_u8(evt.GetString())) / 100.f );
+        });
+
+        Bind(EVT_SLIC3R_APP_DOWNLOAD_FAILED, [this](const wxCommandEvent& evt) {
+            if (this->plater_ != nullptr)
+                this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::AppDownload);
+            if(!evt.GetString().IsEmpty())
+                show_error(nullptr, evt.GetString());
+        });
+
+        Bind(EVT_SLIC3R_APP_OPEN_FAILED, [](const wxCommandEvent& evt) {
+            show_error(nullptr, evt.GetString());
+        });
+    }
+    else {
+#ifdef __WXMSW__ 
+#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+        // file association is not possible anymore starting with Win 8
+        if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
+#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+            if (app_config->get("associate_gcode") == "1")
+                associate_gcode_files();
+#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+        }
+#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+#endif // __WXMSW__
+    }
+
+    // Suppress the '- default -' presets.
+    preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
+    try {
+        // Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only.
+        // If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force
+        // installation of a compatible system preset, thus nullifying the system preset substitutions.
+        init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
+    } catch (const std::exception &ex) {
+        show_error(nullptr, ex.what());
+    }
+
+#ifdef WIN32
+#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
+    register_win32_dpi_event();
+#endif // !wxVERSION_EQUAL_OR_GREATER_THAN
+    register_win32_device_notification_event();
+#endif // WIN32
+
+    // Let the libslic3r know the callback, which will translate messages on demand.
+    Slic3r::I18N::set_translate_callback(libslic3r_translate_callback);
+
+    // application frame
+    if (scrn && is_editor())
+        scrn->SetText(_L("Preparing settings tabs") + dots);
+
+    mainframe = new MainFrame();
+    // hide settings tabs after first Layout
+    if (is_editor())
+        mainframe->select_tab(size_t(0));
+
+    sidebar().obj_list()->init_objects(); // propagate model objects to object list
+//     update_mode(); // !!! do that later
+    SetTopWindow(mainframe);
+
+    plater_->init_notification_manager();
+
+    m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
+
+    if (is_gcode_viewer()) {
+        mainframe->update_layout();
+        if (plater_ != nullptr)
+            // ensure the selected technology is ptFFF
+            plater_->set_printer_technology(ptFFF);
+    }
+    else
+        load_current_presets();
+
+    // Save the active profiles as a "saved into project".
+    update_saved_preset_from_current_preset();
+
+    if (plater_ != nullptr) {
+        // Save the names of active presets and project specific config into ProjectDirtyStateManager.
+        plater_->reset_project_dirty_initial_presets();
+        // Update Project dirty state, update application title bar.
+        plater_->update_project_dirty_from_presets();
+    }
+
+    mainframe->Show(true);
+
+    obj_list()->set_min_height();
+
+    update_mode(); // update view mode after fix of the object_list size
+
+#ifdef __APPLE__
+    other_instance_message_handler()->bring_instance_forward();
+#endif //__APPLE__
+
+    Bind(wxEVT_IDLE, [this](wxIdleEvent& event)
+    {
+        if (! plater_)
+            return;
+
+        this->obj_manipul()->update_if_dirty();
+
+        // An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT
+        // and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized.
+#ifdef __linux__
+        if (! m_post_initialized && m_opengl_initialized) {
+#else
+        if (! m_post_initialized) {
+#endif
+            m_post_initialized = true;
+#ifdef WIN32
+            this->mainframe->register_win32_callbacks();
+#endif
+            this->post_init();
+        }
+
+        if (m_post_initialized && app_config->dirty() && app_config->get("autosave") == "1")
+            app_config->save();
+    });
+
+    m_initialized = true;
+
+    if (const std::string& crash_reason = app_config->get("restore_win_position");
+        boost::starts_with(crash_reason,"crashed"))
+    {
+        wxString preferences_item = _L("Restore window position on start");
+        InfoDialog dialog(nullptr,
+            _L("PrusaSlicer started after a crash"),
+            format_wxstr(_L("PrusaSlicer crashed last time when attempting to set window position.\n"
+                "We are sorry for the inconvenience, it unfortunately happens with certain multiple-monitor setups.\n"
+                "More precise reason for the crash: \"%1%\".\n"
+                "For more information see our GitHub issue tracker: \"%2%\" and \"%3%\"\n\n"
+                "To avoid this problem, consider disabling \"%4%\" in \"Preferences\". "
+                "Otherwise, the application will most likely crash again next time."),
+                "<b>" + from_u8(crash_reason) + "</b>",
+                "<a href=http://github.com/prusa3d/PrusaSlicer/issues/2939>#2939</a>",
+                "<a href=http://github.com/prusa3d/PrusaSlicer/issues/5573>#5573</a>",
+                "<b>" + preferences_item + "</b>"),
+            true, wxYES_NO);
+
+        dialog.SetButtonLabel(wxID_YES, format_wxstr(_L("Disable \"%1%\""), preferences_item));
+        dialog.SetButtonLabel(wxID_NO,  format_wxstr(_L("Leave \"%1%\" enabled") , preferences_item));
+        
+        auto answer = dialog.ShowModal();
+        if (answer == wxID_YES)
+            app_config->set("restore_win_position", "0");
+        else if (answer == wxID_NO)
+            app_config->set("restore_win_position", "1");
+        app_config->save();
+    }
+
+    return true;
+}
+
+unsigned GUI_App::get_colour_approx_luma(const wxColour &colour)
+{
+    double r = colour.Red();
+    double g = colour.Green();
+    double b = colour.Blue();
+
+    return std::round(std::sqrt(
+        r * r * .241 +
+        g * g * .691 +
+        b * b * .068
+        ));
+}
+
+bool GUI_App::dark_mode()
+{
+#if __APPLE__
+    // The check for dark mode returns false positive on 10.12 and 10.13,
+    // which allowed setting dark menu bar and dock area, which is
+    // is detected as dark mode. We must run on at least 10.14 where the
+    // proper dark mode was first introduced.
+    return wxPlatformInfo::Get().CheckOSVersion(10, 14) && mac_dark_mode();
+#else
+    return wxGetApp().app_config->get("dark_color_mode") == "1" ? true : check_dark_mode();
+    //const unsigned luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+    //return luma < 128;
+#endif
+}
+
+const wxColour GUI_App::get_label_default_clr_system()
+{
+    return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57);
+}
+
+const wxColour GUI_App::get_label_default_clr_modified()
+{
+    return dark_mode() ? wxColour(253, 111, 40) : wxColour(252, 77, 1);
+}
+
+void GUI_App::init_label_colours()
+{
+    m_color_label_modified          = get_label_default_clr_modified();
+    m_color_label_sys               = get_label_default_clr_system();
+
+    bool is_dark_mode = dark_mode();
+#ifdef _WIN32
+    m_color_label_default           = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
+    m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT);
+    m_color_highlight_default       = is_dark_mode ? wxColour(78, 78, 78)   : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT);
+    m_color_hovered_btn_label       = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1);
+    m_color_default_btn_label       = is_dark_mode ? wxColour(255, 181, 100): wxColour(203, 61, 0);
+    m_color_selected_btn_bg         = is_dark_mode ? wxColour(95, 73, 62)   : wxColour(228, 220, 216);
+#else
+    m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
+#endif
+    m_color_window_default          = is_dark_mode ? wxColour(43, 43, 43)   : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
+}
+
+void GUI_App::update_label_colours_from_appconfig()
+{
+    if (app_config->has("label_clr_sys")) {
+        auto str = app_config->get("label_clr_sys");
+        if (str != "")
+            m_color_label_sys = wxColour(str);
+    }
+
+    if (app_config->has("label_clr_modified")) {
+        auto str = app_config->get("label_clr_modified");
+        if (str != "")
+            m_color_label_modified = wxColour(str);
+    }
+}
+
+void GUI_App::update_label_colours()
+{
+    for (Tab* tab : tabs_list)
+        tab->update_label_colours();
+}
+
+#ifdef _WIN32
+static bool is_focused(HWND hWnd)
+{
+    HWND hFocusedWnd = ::GetFocus();
+    return hFocusedWnd && hWnd == hFocusedWnd;
+}
+
+static bool is_default(wxWindow* win)
+{
+    wxTopLevelWindow* tlw = find_toplevel_parent(win);
+    if (!tlw)
+        return false;
+        
+    return win == tlw->GetDefaultItem();
+}
+#endif
+
+void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/)
+{
+#ifdef _WIN32
+    bool is_focused_button = false;
+    bool is_default_button = false;
+    if (wxButton* btn = dynamic_cast<wxButton*>(window)) {
+        if (!(btn->GetWindowStyle() & wxNO_BORDER)) {
+            btn->SetWindowStyle(btn->GetWindowStyle() | wxNO_BORDER);
+            highlited = true;
+        }
+        // button marking
+        {
+            auto mark_button = [this, btn, highlited](const bool mark) {
+                if (btn->GetLabel().IsEmpty())
+                    btn->SetBackgroundColour(mark ? m_color_selected_btn_bg   : highlited ? m_color_highlight_default : m_color_window_default);
+                else
+                    btn->SetForegroundColour(mark ? m_color_hovered_btn_label : (is_default(btn) ? m_color_default_btn_label : m_color_label_default));
+                btn->Refresh();
+                btn->Update();
+            };
+
+            // hovering
+            btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); });
+            btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); });
+            // focusing
+            btn->Bind(wxEVT_SET_FOCUS,    [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); });
+            btn->Bind(wxEVT_KILL_FOCUS,   [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); });
+
+            is_focused_button = is_focused(btn->GetHWND());
+            is_default_button = is_default(btn);
+            if (is_focused_button || is_default_button)
+                mark_button(is_focused_button);
+        }
+    }
+    else if (wxTextCtrl* text = dynamic_cast<wxTextCtrl*>(window)) {
+        if (text->GetBorder() != wxBORDER_SIMPLE)
+            text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE);
+    }
+    else if (wxCheckListBox* list = dynamic_cast<wxCheckListBox*>(window)) {
+        list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE);
+        list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
+        for (size_t i = 0; i < list->GetCount(); i++)
+            if (wxOwnerDrawn* item = list->GetItem(i)) {
+                item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
+                item->SetTextColour(m_color_label_default);
+            }
+        return;
+    }
+    else if (dynamic_cast<wxListBox*>(window))
+        window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE);
+
+    if (!just_font)
+        window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
+    if (!is_focused_button && !is_default_button)
+        window->SetForegroundColour(m_color_label_default);
+#endif
+}
+
+// recursive function for scaling fonts for all controls in Window
+#ifdef _WIN32
+static void update_dark_children_ui(wxWindow* window, bool just_buttons_update = false)
+{
+    bool is_btn = dynamic_cast<wxButton*>(window) != nullptr;
+    if (!(just_buttons_update && !is_btn))
+        wxGetApp().UpdateDarkUI(window, is_btn);
+
+    auto children = window->GetChildren();
+    for (auto child : children) {        
+        update_dark_children_ui(child);
+    }
+}
+#endif
+
+// Note: Don't use this function for Dialog contains ScalableButtons
+void GUI_App::UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update/* = false*/)
+{
+#ifdef _WIN32
+    update_dark_children_ui(dlg, just_buttons_update);
+#endif
+}
+void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/)
+{
+#ifdef _WIN32
+    UpdateDarkUI(dvc, highlited ? dark_mode() : false);
+#ifdef _MSW_DARK_MODE
+    dvc->RefreshHeaderDarkMode(&m_normal_font);
+#endif //_MSW_DARK_MODE
+    if (dvc->HasFlag(wxDV_ROW_LINES))
+        dvc->SetAlternateRowColour(m_color_highlight_default);
+    if (dvc->GetBorder() != wxBORDER_SIMPLE)
+        dvc->SetWindowStyle(dvc->GetWindowStyle() | wxBORDER_SIMPLE);
+#endif
+}
+
+void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent)
+{
+#ifdef _WIN32
+    wxGetApp().UpdateDarkUI(parent);
+
+    auto children = parent->GetChildren();
+    for (auto child : children) {
+        if (dynamic_cast<wxStaticText*>(child))
+            child->SetForegroundColour(m_color_label_default);
+    }
+#endif
+}
+
+void GUI_App::init_fonts()
+{
+    m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
+    m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold();
+    m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
+
+#ifdef __WXMAC__
+    m_small_font.SetPointSize(11);
+    m_bold_font.SetPointSize(13);
+#endif /*__WXMAC__*/
+
+    // wxSYS_OEM_FIXED_FONT and wxSYS_ANSI_FIXED_FONT use the same as
+    // DEFAULT in wxGtk. Use the TELETYPE family as a work-around
+    m_code_font = wxFont(wxFontInfo().Family(wxFONTFAMILY_TELETYPE));
+    m_code_font.SetPointSize(m_normal_font.GetPointSize());
+}
+
+void GUI_App::update_fonts(const MainFrame *main_frame)
+{
+    /* Only normal and bold fonts are used for an application rescale,
+     * because of under MSW small and normal fonts are the same.
+     * To avoid same rescaling twice, just fill this values
+     * from rescaled MainFrame
+     */
+	if (main_frame == nullptr)
+		main_frame = this->mainframe;
+    m_normal_font   = main_frame->normal_font();
+    m_small_font    = m_normal_font;
+    m_bold_font     = main_frame->normal_font().Bold();
+    m_link_font     = m_bold_font.Underlined();
+    m_em_unit       = main_frame->em_unit();
+    m_code_font.SetPointSize(m_normal_font.GetPointSize());
+}
+
+void GUI_App::set_label_clr_modified(const wxColour& clr) 
+{
+    if (m_color_label_modified == clr)
+        return;
+    m_color_label_modified = clr;
+    const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue()));
+    app_config->set("label_clr_modified", str);
+    app_config->save();
+}
+
+void GUI_App::set_label_clr_sys(const wxColour& clr)
+{
+    if (m_color_label_sys == clr)
+        return;
+    m_color_label_sys = clr;
+    const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue()));
+    app_config->set("label_clr_sys", str);
+    app_config->save();
+}
+
+bool GUI_App::tabs_as_menu() const
+{
+    return app_config->get("tabs_as_menu") == "1"; // || dark_mode();
+}
+
+wxSize GUI_App::get_min_size() const
+{
+    return wxSize(76*m_em_unit, 49 * m_em_unit);
+}
+
+float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const
+{
+#ifdef __APPLE__
+    const float icon_sc = 1.0f; // for Retina display will be used its own scale
+#else
+    const float icon_sc = m_em_unit*0.1f;
+#endif // __APPLE__
+
+    const std::string& use_val  = app_config->get("use_custom_toolbar_size");
+    const std::string& val      = app_config->get("custom_toolbar_size");
+    const std::string& auto_val = app_config->get("auto_toolbar_size");
+
+    if (val.empty() || auto_val.empty() || use_val.empty())
+        return icon_sc;
+
+    int int_val = use_val == "0" ? 100 : atoi(val.c_str());
+    // correct value in respect to auto_toolbar_size
+    int_val = std::min(atoi(auto_val.c_str()), int_val);
+
+    if (is_limited && int_val < 50)
+        int_val = 50;
+
+    return 0.01f * int_val * icon_sc;
+}
+
+void GUI_App::set_auto_toolbar_icon_scale(float scale) const
+{
+#ifdef __APPLE__
+    const float icon_sc = 1.0f; // for Retina display will be used its own scale
+#else
+    const float icon_sc = m_em_unit * 0.1f;
+#endif // __APPLE__
+
+    long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100);
+    std::string val = std::to_string(int_val);
+
+    app_config->set("auto_toolbar_size", val);
+}
+
+// check user printer_presets for the containing information about "Print Host upload"
+void GUI_App::check_printer_presets()
+{
+    std::vector<std::string> preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers);
+    if (preset_names.empty())
+        return;
+
+    wxString msg_text =  _L("You have the following presets with saved options for \"Print Host upload\"") + ":";
+    for (const std::string& preset_name : preset_names)
+        msg_text += "\n    \"" + from_u8(preset_name) + "\",";
+    msg_text.RemoveLast();
+    msg_text += "\n\n" + _L("But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n"
+                            "Settings will be available in physical printers settings.") + "\n\n" +
+                         _L("By default new Printer devices will be named as \"Printer N\" during its creation.\n"
+                            "Note: This name can be changed later from the physical printers settings");
+
+    //wxMessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal();
+    MessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal();
+
+    preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers);
+}
+
+void GUI_App::recreate_GUI(const wxString& msg_name)
+{
+    m_is_recreating_gui = true;
+
+    mainframe->shutdown();
+
+    wxProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE);
+    dlg.Pulse();
+    dlg.Update(10, _L("Recreating") + dots);
+
+    MainFrame *old_main_frame = mainframe;
+    mainframe = new MainFrame();
+    if (is_editor())
+        // hide settings tabs after first Layout
+        mainframe->select_tab(size_t(0));
+    // Propagate model objects to object list.
+    sidebar().obj_list()->init_objects();
+    SetTopWindow(mainframe);
+
+    dlg.Update(30, _L("Recreating") + dots);
+    old_main_frame->Destroy();
+
+    dlg.Update(80, _L("Loading of current presets") + dots);
+    m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
+    load_current_presets();
+    mainframe->Show(true);
+
+    dlg.Update(90, _L("Loading of a mode view") + dots);
+
+    obj_list()->set_min_height();
+    update_mode();
+
+    // #ys_FIXME_delete_after_testing  Do we still need this  ?
+//     CallAfter([]() {
+//         // Run the config wizard, don't offer the "reset user profile" checkbox.
+//         config_wizard_startup(true);
+//     });
+
+    m_is_recreating_gui = false;
+}
+
+void GUI_App::system_info()
+{
+    SysInfoDialog dlg;
+    dlg.ShowModal();
+}
+
+void GUI_App::keyboard_shortcuts()
+{
+    KBShortcutsDialog dlg;
+    dlg.ShowModal();
+}
+
+// static method accepting a wxWindow object as first parameter
+bool GUI_App::catch_error(std::function<void()> cb,
+    //                       wxMessageDialog* message_dialog,
+    const std::string& err /*= ""*/)
+{
+    if (!err.empty()) {
+        if (cb)
+            cb();
+        //         if (message_dialog)
+        //             message_dialog->(err, "Error", wxOK | wxICON_ERROR);
+        show_error(/*this*/nullptr, err);
+        return true;
+    }
+    return false;
+}
+
+// static method accepting a wxWindow object as first parameter
+void fatal_error(wxWindow* parent)
+{
+    show_error(parent, "");
+    //     exit 1; // #ys_FIXME
+}
+
+#ifdef _WIN32
+
+#ifdef _MSW_DARK_MODE
+static void update_scrolls(wxWindow* window)
+{
+    wxWindowList::compatibility_iterator node = window->GetChildren().GetFirst();
+    while (node)
+    {
+        wxWindow* win = node->GetData();
+        if (dynamic_cast<wxScrollHelper*>(win) ||
+            dynamic_cast<wxTreeCtrl*>(win) ||
+            dynamic_cast<wxTextCtrl*>(win))
+            NppDarkMode::SetDarkExplorerTheme(win->GetHWND());
+
+        update_scrolls(win);
+        node = node->GetNext();
+    }
+}
+#endif //_MSW_DARK_MODE
+
+
+#ifdef _MSW_DARK_MODE
+void GUI_App::force_menu_update()
+{
+    NppDarkMode::SetSystemMenuForApp(app_config->get("sys_menu_enabled") == "1");
+}
+#endif //_MSW_DARK_MODE
+
+void GUI_App::force_colors_update()
+{
+#ifdef _MSW_DARK_MODE
+    NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1");
+    if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl())
+        NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND);
+    NppDarkMode::SetDarkTitleBar(mainframe->GetHWND());
+    NppDarkMode::SetDarkTitleBar(mainframe->m_settings_dialog.GetHWND());
+#endif //_MSW_DARK_MODE
+    m_force_colors_update = true;
+}
+#endif //_WIN32
+
+// Called after the Preferences dialog is closed and the program settings are saved.
+// Update the UI based on the current preferences.
+void GUI_App::update_ui_from_settings()
+{
+    update_label_colours();
+#ifdef _WIN32
+    // Upadte UI colors before Update UI from settings
+    if (m_force_colors_update) {
+        m_force_colors_update = false;
+        mainframe->force_color_changed();
+        mainframe->diff_dialog.force_color_changed();
+        mainframe->preferences_dialog->force_color_changed();
+        mainframe->printhost_queue_dlg()->force_color_changed();
+#ifdef _MSW_DARK_MODE
+        update_scrolls(mainframe);
+        if (mainframe->is_dlg_layout()) {
+            // update for tabs bar
+            UpdateDarkUI(&mainframe->m_settings_dialog);
+            mainframe->m_settings_dialog.Fit();
+            mainframe->m_settings_dialog.Refresh();
+            // update scrollbars
+            update_scrolls(&mainframe->m_settings_dialog);
+        }
+#endif //_MSW_DARK_MODE
+    }
+#endif
+    mainframe->update_ui_from_settings();
+}
+
+void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized)
+{
+    const std::string name = into_u8(window->GetName());
+
+    window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) {
+        window_pos_save(window, name);
+        event.Skip();
+    });
+
+    window_pos_restore(window, name, default_maximized);
+
+    on_window_geometry(window, [=]() {
+        window_pos_sanitize(window);
+    });
+}
+
+void GUI_App::load_project(wxWindow *parent, wxString& input_file) const
+{
+    input_file.Clear();
+    wxFileDialog dialog(parent ? parent : GetTopWindow(),
+        _L("Choose one file (3MF/AMF):"),
+        app_config->get_last_dir(), "",
+        file_wildcards(FT_PROJECT), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+
+    if (dialog.ShowModal() == wxID_OK)
+        input_file = dialog.GetPath();
+}
+
+void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const
+{
+    input_files.Clear();
+    wxFileDialog dialog(parent ? parent : GetTopWindow(),
+        _L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):"),
+        from_u8(app_config->get_last_dir()), "",
+        file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
+
+    if (dialog.ShowModal() == wxID_OK)
+        dialog.GetPaths(input_files);
+}
+
+void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const
+{
+    input_file.Clear();
+    wxFileDialog dialog(parent ? parent : GetTopWindow(),
+        _L("Choose one file (GCODE/.GCO/.G/.ngc/NGC):"),
+        app_config->get_last_dir(), "",
+        file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+
+    if (dialog.ShowModal() == wxID_OK)
+        input_file = dialog.GetPath();
+}
+
+bool GUI_App::switch_language()
+{
+    if (select_language()) {
+        recreate_GUI(_L("Changing of an application language") + dots);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+#ifdef __linux__
+static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language,
+                                                                const wxLanguageInfo* system_language)
+{
+    constexpr size_t max_len = 50;
+    char path[max_len] = "";
+    std::vector<std::string> locales;
+    const std::string lang_prefix = into_u8(language->CanonicalName.BeforeFirst('_'));
+
+    // Call locale -a so we can parse the output to get the list of available locales
+    // We expect lines such as "en_US.utf8". Pick ones starting with the language code
+    // we are switching to. Lines with different formatting will be removed later.
+    FILE* fp = popen("locale -a", "r");
+    if (fp != NULL) {
+        while (fgets(path, max_len, fp) != NULL) {
+            std::string line(path);
+            line = line.substr(0, line.find('\n'));
+            if (boost::starts_with(line, lang_prefix))
+                locales.push_back(line);
+        }
+        pclose(fp);
+    }
+
+    // locales now contain all candidates for this language.
+    // Sort them so ones containing anything about UTF-8 are at the end.
+    std::sort(locales.begin(), locales.end(), [](const std::string& a, const std::string& b)
+    {
+        auto has_utf8 = [](const std::string & s) {
+            auto S = boost::to_upper_copy(s);
+            return S.find("UTF8") != std::string::npos || S.find("UTF-8") != std::string::npos;
+        };
+        return ! has_utf8(a) && has_utf8(b);
+    });
+
+    // Remove the suffix behind a dot, if there is one.
+    for (std::string& s : locales)
+        s = s.substr(0, s.find("."));
+
+    // We just hope that dear Linux "locale -a" returns country codes
+    // in ISO 3166-1 alpha-2 code (two letter) format.
+    // https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes
+    // To be sure, remove anything not looking as expected
+    // (any number of lowercase letters, underscore, two uppercase letters).
+    locales.erase(std::remove_if(locales.begin(),
+                                 locales.end(),
+                                 [](const std::string& s) {
+                                     return ! std::regex_match(s,
+                                         std::regex("^[a-z]+_[A-Z]{2}$"));
+                                 }),
+                   locales.end());
+
+    // Is there a candidate matching a country code of a system language? Move it to the end,
+    // while maintaining the order of matches, so that the best match ends up at the very end.
+    std::string system_country = "_" + into_u8(system_language->CanonicalName.AfterFirst('_')).substr(0, 2);
+    int cnt = locales.size();
+    for (int i=0; i<cnt; ++i)
+        if (locales[i].find(system_country) != std::string::npos) {
+            locales.emplace_back(std::move(locales[i]));
+            locales[i].clear();
+        }
+
+    // Now try them one by one.
+    for (auto it = locales.rbegin(); it != locales.rend(); ++ it)
+        if (! it->empty()) {
+            const std::string &locale = *it;
+            const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale));
+            if (wxLocale::IsAvailable(lang->Language))
+                return lang;
+        }
+    return language;
+}
+#endif
+
+int GUI_App::GetSingleChoiceIndex(const wxString& message,
+                                const wxString& caption,
+                                const wxArrayString& choices,
+                                int initialSelection)
+{
+#ifdef _WIN32
+    wxSingleChoiceDialog dialog(nullptr, message, caption, choices);
+    wxGetApp().UpdateDlgDarkUI(&dialog);
+
+    dialog.SetSelection(initialSelection);
+    return dialog.ShowModal() == wxID_OK ? dialog.GetSelection() : -1;
+#else
+    return wxGetSingleChoiceIndex(message, caption, choices, initialSelection);
+#endif
+}
+
+// select language from the list of installed languages
+bool GUI_App::select_language()
+{
+	wxArrayString translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY);
+    std::vector<const wxLanguageInfo*> language_infos;
+    language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH));
+    for (size_t i = 0; i < translations.GetCount(); ++ i) {
+	    const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]);
+        if (langinfo != nullptr)
+            language_infos.emplace_back(langinfo);
+    }
+    sort_remove_duplicates(language_infos);
+	std::sort(language_infos.begin(), language_infos.end(), [](const wxLanguageInfo* l, const wxLanguageInfo* r) { return l->Description < r->Description; });
+
+    wxArrayString names;
+    names.Alloc(language_infos.size());
+
+    // Some valid language should be selected since the application start up.
+    const wxLanguage current_language = wxLanguage(m_wxLocale->GetLanguage());
+    int 		     init_selection   		= -1;
+    int 			 init_selection_alt     = -1;
+    int 			 init_selection_default = -1;
+    for (size_t i = 0; i < language_infos.size(); ++ i) {
+        if (wxLanguage(language_infos[i]->Language) == current_language)
+        	// The dictionary matches the active language and country.
+            init_selection = i;
+        else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == m_wxLocale->GetCanonicalName().BeforeFirst('_')) ||
+        		 // if the active language is Slovak, mark the Czech language as active.
+        	     (language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && m_wxLocale->GetCanonicalName().BeforeFirst('_') == "sk"))
+        	// The dictionary matches the active language, it does not necessarily match the country.
+        	init_selection_alt = i;
+        if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en")
+        	// This will be the default selection if the active language does not match any dictionary.
+        	init_selection_default = i;
+        names.Add(language_infos[i]->Description);
+    }
+    if (init_selection == -1)
+    	// This is the dictionary matching the active language.
+    	init_selection = init_selection_alt;
+    if (init_selection != -1)
+    	// This is the language to highlight in the choice dialog initially.
+    	init_selection_default = init_selection;
+
+    const long index = GetSingleChoiceIndex(_L("Select the language"), _L("Language"), names, init_selection_default);
+	// Try to load a new language.
+    if (index != -1 && (init_selection == -1 || init_selection != index)) {
+    	const wxLanguageInfo *new_language_info = language_infos[index];
+    	if (this->load_language(new_language_info->CanonicalName, false)) {
+			// Save language at application config.
+            // Which language to save as the selected dictionary language?
+            // 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its
+            //    stability in the future:
+            //    wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH);
+            // 2) Current locale language may not match the dictionary name, see GH issue #3901
+            //    m_wxLocale->GetCanonicalName()
+            // 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name.
+			app_config->set("translation_language", new_language_info->CanonicalName.ToUTF8().data());            
+			app_config->save();
+    		return true;
+    	}
+    }
+
+    return false;
+}
+
+// Load gettext translation files and activate them at the start of the application,
+// based on the "translation_language" key stored in the application config.
+bool GUI_App::load_language(wxString language, bool initial)
+{
+    if (initial) {
+    	// There is a static list of lookup path prefixes in wxWidgets. Add ours.
+	    wxFileTranslationsLoader::AddCatalogLookupPathPrefix(from_u8(localization_dir()));
+    	// Get the active language from PrusaSlicer.ini, or empty string if the key does not exist.
+        language = app_config->get("translation_language");
+        if (! language.empty())
+        	BOOST_LOG_TRIVIAL(trace) << boost::format("translation_language provided by PrusaSlicer.ini: %1%") % language;
+
+        // Get the system language.
+        {
+	        const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage());
+	        if (lang_system != wxLANGUAGE_UNKNOWN) {
+				m_language_info_system = wxLocale::GetLanguageInfo(lang_system);
+	        	BOOST_LOG_TRIVIAL(trace) << boost::format("System language detected (user locales and such): %1%") % m_language_info_system->CanonicalName.ToUTF8().data();
+	        }
+		}
+        {
+	    	// Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance.
+	    	wxLocale temp_locale;
+	    	// Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code).
+	    	wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT);
+	    	// Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer
+	    	// and try to match them with the system specific "preferred languages". 
+	    	// There seems to be a support for that on Windows and OSX, while on Linuxes the code just returns wxLocale::GetSystemLanguage().
+	    	// The last parameter gets added to the list of detected dictionaries. This is a workaround 
+	    	// for not having the English dictionary. Let's hope wxWidgets of various versions process this call the same way.
+			wxString best_language = wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH);
+			if (! best_language.IsEmpty()) {
+				m_language_info_best = wxLocale::FindLanguageInfo(best_language);
+	        	BOOST_LOG_TRIVIAL(trace) << boost::format("Best translation language detected (may be different from user locales): %1%") % m_language_info_best->CanonicalName.ToUTF8().data();
+			}
+            #ifdef __linux__
+            wxString lc_all;
+            if (wxGetEnv("LC_ALL", &lc_all) && ! lc_all.IsEmpty()) {
+                // Best language returned by wxWidgets on Linux apparently does not respect LC_ALL.
+                // Disregard the "best" suggestion in case LC_ALL is provided.
+                m_language_info_best = nullptr;
+            }
+            #endif
+		}
+    }
+
+	const wxLanguageInfo *language_info = language.empty() ? nullptr : wxLocale::FindLanguageInfo(language);
+	if (! language.empty() && (language_info == nullptr || language_info->CanonicalName.empty())) {
+		// Fix for wxWidgets issue, where the FindLanguageInfo() returns locales with undefined ANSII code (wxLANGUAGE_KONKANI or wxLANGUAGE_MANIPURI).
+		language_info = nullptr;
+    	BOOST_LOG_TRIVIAL(error) << boost::format("Language code \"%1%\" is not supported") % language.ToUTF8().data();
+	}
+
+	if (language_info != nullptr && language_info->LayoutDirection == wxLayout_RightToLeft) {
+    	BOOST_LOG_TRIVIAL(trace) << boost::format("The following language code requires right to left layout, which is not supported by PrusaSlicer: %1%") % language_info->CanonicalName.ToUTF8().data();
+		language_info = nullptr;
+	}
+
+    if (language_info == nullptr) {
+        // PrusaSlicer does not support the Right to Left languages yet.
+        if (m_language_info_system != nullptr && m_language_info_system->LayoutDirection != wxLayout_RightToLeft)
+            language_info = m_language_info_system;
+        if (m_language_info_best != nullptr && m_language_info_best->LayoutDirection != wxLayout_RightToLeft)
+        	language_info = m_language_info_best;
+	    if (language_info == nullptr)
+			language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US);
+    }
+
+	BOOST_LOG_TRIVIAL(trace) << boost::format("Switching wxLocales to %1%") % language_info->CanonicalName.ToUTF8().data();
+
+    // Alternate language code.
+    wxLanguage language_dict = wxLanguage(language_info->Language);
+    if (language_info->CanonicalName.BeforeFirst('_') == "sk") {
+    	// Slovaks understand Czech well. Give them the Czech translation.
+    	language_dict = wxLANGUAGE_CZECH;
+		BOOST_LOG_TRIVIAL(trace) << "Using Czech dictionaries for Slovak language";
+    }
+
+    // Select language for locales. This language may be different from the language of the dictionary.
+    if (language_info == m_language_info_best || language_info == m_language_info_system) {
+        // The current language matches user's default profile exactly. That's great.
+    } else if (m_language_info_best != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_best->CanonicalName.BeforeFirst('_')) {
+        // Use whatever the operating system recommends, if it the language code of the dictionary matches the recommended language.
+        // This allows a Swiss guy to use a German dictionary without forcing him to German locales.
+        language_info = m_language_info_best;
+    } else if (m_language_info_system != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_'))
+        language_info = m_language_info_system;
+
+#ifdef __linux__
+    // If we can't find this locale , try to use different one for the language
+    // instead of just reporting that it is impossible to switch.
+    if (! wxLocale::IsAvailable(language_info->Language)) {
+        std::string original_lang = into_u8(language_info->CanonicalName);
+        language_info = linux_get_existing_locale_language(language_info, m_language_info_system);
+        BOOST_LOG_TRIVIAL(trace) << boost::format("Can't switch language to %1% (missing locales). Using %2% instead.")
+                                    % original_lang % language_info->CanonicalName.ToUTF8().data();
+    }
+#endif
+
+    if (! wxLocale::IsAvailable(language_info->Language)) {
+    	// Loading the language dictionary failed.
+    	wxString message = "Switching PrusaSlicer to language " + language_info->CanonicalName + " failed.";
+#if !defined(_WIN32) && !defined(__APPLE__)
+        // likely some linux system
+        message += "\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n";
+#endif
+        if (initial)
+        	message + "\n\nApplication will close.";
+        wxMessageBox(message, "PrusaSlicer - Switching language failed", wxOK | wxICON_ERROR);
+        if (initial)
+			std::exit(EXIT_FAILURE);
+		else
+			return false;
+    }
+
+    // Release the old locales, create new locales.
+    //FIXME wxWidgets cause havoc if the current locale is deleted. We just forget it causing memory leaks for now.
+    m_wxLocale.release();
+    m_wxLocale = Slic3r::make_unique<wxLocale>();
+    m_wxLocale->Init(language_info->Language);
+    // Override language at the active wxTranslations class (which is stored in the active m_wxLocale)
+    // to load possibly different dictionary, for example, load Czech dictionary for Slovak language.
+    wxTranslations::Get()->SetLanguage(language_dict);
+    m_wxLocale->AddCatalog(SLIC3R_APP_KEY);
+    m_imgui->set_language(into_u8(language_info->CanonicalName));
+    //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only.
+    //wxSetlocale(LC_NUMERIC, "C");
+    Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data());
+	return true;
+}
+
+Tab* GUI_App::get_tab(Preset::Type type)
+{
+    for (Tab* tab: tabs_list)
+        if (tab->type() == type)
+            return tab->completed() ? tab : nullptr; // To avoid actions with no-completed Tab
+    return nullptr;
+}
+
+ConfigOptionMode GUI_App::get_mode()
+{
+    if (!app_config->has("view_mode"))
+        return comSimple;
+
+    const auto mode = app_config->get("view_mode");
+    return mode == "expert" ? comExpert : 
+           mode == "simple" ? comSimple : comAdvanced;
+}
+
+void GUI_App::save_mode(const /*ConfigOptionMode*/int mode) 
+{
+    const std::string mode_str = mode == comExpert ? "expert" :
+                                 mode == comSimple ? "simple" : "advanced";
+    app_config->set("view_mode", mode_str);
+    app_config->save(); 
+    update_mode();
+}
+
+// Update view mode according to selected menu
+void GUI_App::update_mode()
+{
+    sidebar().update_mode();
+
+#ifdef _MSW_DARK_MODE
+    if (!wxGetApp().tabs_as_menu())
+        dynamic_cast<Notebook*>(mainframe->m_tabpanel)->UpdateMode();
+#endif
+
+    for (auto tab : tabs_list)
+        tab->update_mode();
+
+    plater()->update_menus();
+    plater()->canvas3D()->update_gizmos_on_off_state();
+}
+
+void GUI_App::add_config_menu(wxMenuBar *menu)
+{
+    auto local_menu = new wxMenu();
+    wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt));
+
+    const auto config_wizard_name = _(ConfigWizard::name(true));
+    const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Run %s"))) % config_wizard_name).str());
+    // Cmd+, is standard on OS X - what about other operating systems?
+    if (is_editor()) {
+        local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip);
+        local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots"));
+        local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot"));
+        local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates"));
+        local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application"));
+#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) 
+        //if (DesktopIntegrationDialog::integration_possible())
+        local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));    
+#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)        
+        local_menu->AppendSeparator();
+    }
+    local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots +
+#ifdef __APPLE__
+        "\tCtrl+,",
+#else
+        "\tCtrl+P",
+#endif
+        _L("Application preferences"));
+    wxMenu* mode_menu = nullptr;
+    if (is_editor()) {
+        local_menu->AppendSeparator();
+        mode_menu = new wxMenu();
+        mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple View Mode"));
+//    mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced View Mode"));
+        mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), _L("Advanced View Mode"));
+        mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _L("Expert"), _L("Expert View Mode"));
+        Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple);
+        Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced);
+        Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comExpert) evt.Check(true); }, config_id_base + ConfigMenuModeExpert);
+
+        local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s View Mode"), SLIC3R_APP_NAME));
+    }
+    local_menu->AppendSeparator();
+    local_menu->Append(config_id_base + ConfigMenuLanguage, _L("&Language"));
+    if (is_editor()) {
+        local_menu->AppendSeparator();
+        local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _L("Flash Printer &Firmware"), _L("Upload a firmware image into an Arduino based printer"));
+        // TODO: for when we're able to flash dictionaries
+        // local_menu->Append(config_id_base + FirmwareMenuDict,  _L("Flash Language File"),    _L("Upload a language dictionary file into a Prusa printer"));
+    }
+
+    local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) {
+        switch (event.GetId() - config_id_base) {
+        case ConfigMenuWizard:
+            run_wizard(ConfigWizard::RR_USER);
+            break;
+		case ConfigMenuUpdateConf:
+			check_updates(true);
+			break;
+        case ConfigMenuUpdateApp:
+            app_version_check(true);
+            break;
+#ifdef __linux__
+        case ConfigMenuDesktopIntegration:
+            show_desktop_integration_dialog();
+            break;
+#endif
+        case ConfigMenuTakeSnapshot:
+            // Take a configuration snapshot.
+            if (wxString action_name = _L("Taking a configuration snapshot");
+                check_and_save_current_preset_changes(action_name, _L("Some presets are modified and the unsaved changes will not be captured by the configuration snapshot."), false, true)) {
+                wxTextEntryDialog dlg(nullptr, action_name, _L("Snapshot name"));
+                UpdateDlgDarkUI(&dlg);
+                
+                // set current normal font for dialog children, 
+                // because of just dlg.SetFont(normal_font()) has no result;
+                for (auto child : dlg.GetChildren())
+                    child->SetFont(normal_font());
+
+                if (dlg.ShowModal() == wxID_OK)
+                    if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error(
+                            *app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data());
+                        snapshot != nullptr)
+                        app_config->set("on_snapshot", snapshot->id);
+            }
+            break;
+        case ConfigMenuSnapshots:
+            if (check_and_save_current_preset_changes(_L("Loading a configuration snapshot"), "", false)) {
+                std::string on_snapshot;
+                if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
+                    on_snapshot = app_config->get("on_snapshot");
+                ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot);
+                dlg.ShowModal();
+                if (!dlg.snapshot_to_activate().empty()) {
+                    if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) && 
+                        ! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "",
+                                GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate())))
+                        break;
+                    try {
+                        app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
+                        // Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system
+                        // presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users
+                        // are known to be creative and mess with the config files in various ways.
+                        if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
+                            ! all_substitutions.empty())
+                            show_substitutions_info(all_substitutions);
+
+                        // Load the currently selected preset into the GUI, update the preset selection box.
+                        load_current_presets();
+                    } catch (std::exception &ex) {
+                        GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what()));
+                    }
+                }
+            }
+            break;
+        case ConfigMenuPreferences:
+        {
+            open_preferences();
+            break;
+        }
+        case ConfigMenuLanguage:
+        {
+            /* Before change application language, let's check unsaved changes on 3D-Scene
+             * and draw user's attention to the application restarting after a language change
+             */
+            {
+                // the dialog needs to be destroyed before the call to switch_language()
+                // or sometimes the application crashes into wxDialogBase() destructor
+                // so we put it into an inner scope
+                wxString title = is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME);
+                title += " - " + _L("Language selection");
+                //wxMessageDialog dialog(nullptr,
+                MessageDialog dialog(nullptr,
+                    _L("Switching the language will trigger application restart.\n"
+                        "You will lose content of the plater.") + "\n\n" +
+                    _L("Do you want to proceed?"),
+                    title,
+                    wxICON_QUESTION | wxOK | wxCANCEL);
+                if (dialog.ShowModal() == wxID_CANCEL)
+                    return;
+            }
+
+            switch_language();
+            break;
+        }
+        case ConfigMenuFlashFirmware:
+            FirmwareDialog::run(mainframe);
+            break;
+        default:
+            break;
+        }
+    });
+    
+    using std::placeholders::_1;
+
+    if (mode_menu != nullptr) {
+        auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); };
+        mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple);
+        mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced);
+        mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comExpert, _1), config_id_base + ConfigMenuModeExpert);
+    }
+
+    menu->Append(local_menu, _L("&Configuration"));
+}
+
+void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/)
+{
+    mainframe->preferences_dialog->show(highlight_option, tab_name);
+
+    if (mainframe->preferences_dialog->recreate_GUI())
+        recreate_GUI(_L("Restart application") + dots);
+
+#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
+    if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
+#else
+    if (mainframe->preferences_dialog->seq_top_layer_only_changed())
+#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
+        this->plater_->refresh_print();
+
+#ifdef _WIN32
+#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+    // file association is not possible anymore starting with Win 8
+    if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
+#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+        if (is_editor()) {
+            if (app_config->get("associate_3mf") == "1")
+                associate_3mf_files();
+            if (app_config->get("associate_stl") == "1")
+                associate_stl_files();
+        }
+        else {
+            if (app_config->get("associate_gcode") == "1")
+                associate_gcode_files();
+        }
+#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+    }
+#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+#endif // _WIN32
+
+    if (mainframe->preferences_dialog->settings_layout_changed()) {
+        // hide full main_sizer for mainFrame
+        mainframe->GetSizer()->Show(false);
+        mainframe->update_layout();
+        mainframe->select_tab(size_t(0));
+    }
+}
+
+bool GUI_App::has_unsaved_preset_changes() const
+{
+    PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
+    for (const Tab* const tab : tabs_list) {
+        if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty())
+            return true;
+    }
+    return false;
+}
+
+bool GUI_App::has_current_preset_changes() const
+{
+    PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
+    for (const Tab* const tab : tabs_list) {
+        if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty())
+            return true;
+    }
+    return false;
+}
+
+void GUI_App::update_saved_preset_from_current_preset()
+{
+    PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
+    for (Tab* tab : tabs_list) {
+        if (tab->supports_printer_technology(printer_technology))
+            tab->update_saved_preset_from_current_preset();
+    }
+}
+
+std::vector<const PresetCollection*> GUI_App::get_active_preset_collections() const
+{
+    std::vector<const PresetCollection*> ret;
+    PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
+    for (const Tab* tab : tabs_list)
+        if (tab->supports_printer_technology(printer_technology))
+            ret.push_back(tab->get_presets());
+    return ret;
+}
+
+// To notify the user whether he is aware that some preset changes will be lost,
+// UnsavedChangesDialog: "Discard / Save / Cancel"
+// This is called when:
+// - Close Application & Current project isn't saved
+// - Load Project      & Current project isn't saved
+// - Undo / Redo with change of print technologie
+// - Loading snapshot
+// - Loading config_file/bundle
+// UnsavedChangesDialog: "Don't save / Save / Cancel"
+// This is called when:
+// - Exporting config_bundle
+// - Taking snapshot
+bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice/* = true*/, bool dont_save_insted_of_discard/* = false*/)
+{
+    if (has_current_preset_changes()) {
+        const std::string app_config_key = remember_choice ? "default_action_on_close_application" : "";
+        int act_buttons = UnsavedChangesDialog::ActionButtons::SAVE;
+        if (dont_save_insted_of_discard)
+            act_buttons |= UnsavedChangesDialog::ActionButtons::DONT_SAVE;
+        UnsavedChangesDialog dlg(caption, header, app_config_key, act_buttons);
+        std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key);
+        if (act == "none" && dlg.ShowModal() == wxID_CANCEL)
+            return false;
+
+        if (dlg.save_preset())  // save selected changes
+        {
+            for (const std::pair<std::string, Preset::Type>& nt : dlg.get_names_and_types())
+                preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second));
+
+            load_current_presets(false);
+
+            // if we saved changes to the new presets, we should to 
+            // synchronize config.ini with the current selections.
+            preset_bundle->export_selections(*app_config);
+
+            MessageDialog(nullptr, _L_PLURAL("The preset modifications are successfully saved", 
+                                             "The presets modifications are successfully saved", dlg.get_names_and_types().size())).ShowModal();
+        }
+    }
+
+    return true;
+}
+
+void GUI_App::apply_keeped_preset_modifications()
+{
+    PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
+    for (Tab* tab : tabs_list) {
+        if (tab->supports_printer_technology(printer_technology))
+            tab->apply_config_from_cache();
+    }
+    load_current_presets(false);
+}
+
+// This is called when creating new project or load another project
+// OR close ConfigWizard
+// to ask the user what should we do with unsaved changes for presets.
+// New Project          => Current project is saved    => UnsavedChangesDialog: "Keep / Discard / Cancel"
+//                      => Current project isn't saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel"
+// Close ConfigWizard   => Current project is saved    => UnsavedChangesDialog: "Keep / Discard / Save / Cancel"
+// Note: no_nullptr postponed_apply_of_keeped_changes indicates that thie function is called after ConfigWizard is closed
+bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes/* = nullptr*/)
+{
+    if (has_current_preset_changes()) {
+        bool is_called_from_configwizard = postponed_apply_of_keeped_changes != nullptr;
+
+        const std::string app_config_key = is_called_from_configwizard ? "" : "default_action_on_new_project";
+        UnsavedChangesDialog dlg(caption, header, app_config_key, action_buttons);
+        std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key);
+        if (act == "none" && dlg.ShowModal() == wxID_CANCEL)
+            return false;
+
+        auto reset_modifications = [this, is_called_from_configwizard]() {
+            if (is_called_from_configwizard)
+                return; // no need to discared changes. It will be done fromConfigWizard closing
+
+            PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
+            for (const Tab* const tab : tabs_list) {
+                if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty())
+                    tab->m_presets->discard_current_changes();
+            }
+            load_current_presets(false);
+        };
+
+        if (dlg.discard())
+            reset_modifications();
+        else  // save selected changes
+        {
+            const auto& preset_names_and_types = dlg.get_names_and_types();
+            if (dlg.save_preset()) {
+                for (const std::pair<std::string, Preset::Type>& nt : preset_names_and_types)
+                    preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second));
+
+                // if we saved changes to the new presets, we should to 
+                // synchronize config.ini with the current selections.
+                preset_bundle->export_selections(*app_config);
+
+                wxString text = _L_PLURAL("The preset modifications are successfully saved",
+                    "The presets modifications are successfully saved", preset_names_and_types.size());
+                if (!is_called_from_configwizard)
+                    text += "\n\n" + _L("For new project all modifications will be reseted");
+
+                MessageDialog(nullptr, text).ShowModal();
+                reset_modifications();
+            }
+            else if (dlg.transfer_changes() && (dlg.has_unselected_options() || is_called_from_configwizard)) {
+                // execute this part of code only if not all modifications are keeping to the new project 
+                // OR this function is called when ConfigWizard is closed and "Keep modifications" is selected
+                for (const std::pair<std::string, Preset::Type>& nt : preset_names_and_types) {
+                    Preset::Type type = nt.second;
+                    Tab* tab = get_tab(type);
+                    std::vector<std::string> selected_options = dlg.get_selected_options(type);
+                    if (type == Preset::TYPE_PRINTER) {
+                        auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count");
+                        if (it != selected_options.end()) {
+                            // erase "extruders_count" option from the list
+                            selected_options.erase(it);
+                            // cache the extruders count
+                            static_cast<TabPrinter*>(tab)->cache_extruder_cnt();
+                        }
+                    }
+                    tab->cache_config_diff(selected_options);
+                    if (!is_called_from_configwizard)
+                        tab->m_presets->discard_current_changes();
+                }
+                if (is_called_from_configwizard)
+                    *postponed_apply_of_keeped_changes = true;
+                else
+                    apply_keeped_preset_modifications();
+            }
+        }
+    }
+
+    return true;
+}
+
+bool GUI_App::can_load_project()
+{
+    int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified."));
+    if (saved_project == wxID_CANCEL ||
+        (plater()->is_project_dirty() && saved_project == wxID_NO && 
+         !check_and_save_current_preset_changes(_L("Project is loading"), _L("Opening new project while some presets are unsaved."))))
+        return false;
+    return true;
+}
+
+bool GUI_App::check_print_host_queue()
+{
+    wxString dirty;
+    std::vector<std::pair<std::string, std::string>> jobs;
+    // Get ongoing jobs from dialog
+    mainframe->m_printhost_queue_dlg->get_active_jobs(jobs);
+    if (jobs.empty())
+        return true;
+    // Show dialog
+    wxString job_string = wxString();
+    for (const auto& job : jobs) {
+        job_string += format_wxstr("   %1% : %2% \n", job.first, job.second);
+    }
+    wxString message;
+    message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?"));
+    //wxMessageDialog dialog(mainframe,
+    MessageDialog dialog(mainframe,
+        message,
+        wxString(SLIC3R_APP_NAME) + " - " + _(L("Ongoing uploads")),
+        wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
+    if (dialog.ShowModal() == wxID_YES)
+        return true;
+
+    // TODO: If already shown, bring forward
+    mainframe->m_printhost_queue_dlg->Show();
+    return false;
+}
+
+bool GUI_App::checked_tab(Tab* tab)
+{
+    bool ret = true;
+    if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end())
+        ret = false;
+    return ret;
+}
+
+// Update UI / Tabs to reflect changes in the currently loaded presets
+void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/)
+{
+    // check printer_presets for the containing information about "Print Host upload"
+    // and create physical printer from it, if any exists
+    if (check_printer_presets_)
+        check_printer_presets();
+
+    PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
+	this->plater()->set_printer_technology(printer_technology);
+    for (Tab *tab : tabs_list)
+		if (tab->supports_printer_technology(printer_technology)) {
+			if (tab->type() == Preset::TYPE_PRINTER) {
+				static_cast<TabPrinter*>(tab)->update_pages();
+				// Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change().
+				this->plater()->force_print_bed_update();
+			}
+			tab->load_current_preset();
+		}
+}
+
+bool GUI_App::OnExceptionInMainLoop()
+{
+    generic_exception_handle();
+    return false;
+}
+
+#ifdef __APPLE__
+// This callback is called from wxEntry()->wxApp::CallOnInit()->NSApplication run
+// that is, before GUI_App::OnInit(), so we have a chance to switch GUI_App
+// to a G-code viewer.
+void GUI_App::OSXStoreOpenFiles(const wxArrayString &fileNames)
+{
+    size_t num_gcodes = 0;
+    for (const wxString &filename : fileNames)
+        if (is_gcode_file(into_u8(filename)))
+            ++ num_gcodes;
+    if (fileNames.size() == num_gcodes) {
+        // Opening PrusaSlicer by drag & dropping a G-Code onto PrusaSlicer icon in Finder,
+        // just G-codes were passed. Switch to G-code viewer mode.
+        m_app_mode = EAppMode::GCodeViewer;
+        unlock_lockfile(get_instance_hash_string() + ".lock", data_dir() + "/cache/");
+        if(app_config != nullptr)
+            delete app_config;
+        app_config = nullptr;
+        init_app_config();
+    }
+    wxApp::OSXStoreOpenFiles(fileNames);
+}
+// wxWidgets override to get an event on open files.
+void GUI_App::MacOpenFiles(const wxArrayString &fileNames)
+{
+    std::vector<std::string> files;
+    std::vector<wxString>    gcode_files;
+    std::vector<wxString>    non_gcode_files;
+    for (const auto& filename : fileNames) {
+        if (is_gcode_file(into_u8(filename)))
+            gcode_files.emplace_back(filename);
+        else {
+            files.emplace_back(into_u8(filename));
+            non_gcode_files.emplace_back(filename);
+        }
+    }
+    if (m_app_mode == EAppMode::GCodeViewer) {
+        // Running in G-code viewer.
+        // Load the first G-code into the G-code viewer.
+        // Or if no G-codes, send other files to slicer. 
+        if (! gcode_files.empty()) {
+            if (m_post_initialized)
+                this->plater()->load_gcode(gcode_files.front());
+            else
+                this->init_params->input_files = { into_u8(gcode_files.front()) };
+        }
+        if (!non_gcode_files.empty()) 
+            start_new_slicer(non_gcode_files, true);
+    } else {
+        if (! files.empty()) {
+            if (m_post_initialized) {
+                wxArrayString input_files;
+                for (size_t i = 0; i < non_gcode_files.size(); ++i)
+                    input_files.push_back(non_gcode_files[i]);
+                this->plater()->load_files(input_files);
+            } else {
+                for (const auto &f : non_gcode_files)
+                    this->init_params->input_files.emplace_back(into_u8(f));
+            }
+        }
+        for (const wxString &filename : gcode_files)
+            start_new_gcodeviewer(&filename);
+    }
+}
+#endif /* __APPLE */
+
+Sidebar& GUI_App::sidebar()
+{
+    return plater_->sidebar();
+}
+
+ObjectManipulation* GUI_App::obj_manipul()
+{
+    // If this method is called before plater_ has been initialized, return nullptr (to avoid a crash)
+    return (plater_ != nullptr) ? sidebar().obj_manipul() : nullptr;
+}
+
+ObjectSettings* GUI_App::obj_settings()
+{
+    return sidebar().obj_settings();
+}
+
+ObjectList* GUI_App::obj_list()
+{
+    return sidebar().obj_list();
+}
+
+ObjectLayers* GUI_App::obj_layers()
+{
+    return sidebar().obj_layers();
+}
+
+Plater* GUI_App::plater()
+{
+    return plater_;
+}
+
+const Plater* GUI_App::plater() const
+{
+    return plater_;
+}
+
+Model& GUI_App::model()
+{
+    return plater_->model();
+}
+wxBookCtrlBase* GUI_App::tab_panel() const
+{
+    return mainframe->m_tabpanel;
+}
+
+NotificationManager * GUI_App::notification_manager()
+{
+    return plater_->get_notification_manager();
+}
+
+// extruders count from selected printer preset
+int GUI_App::extruders_cnt() const
+{
+    const Preset& preset = preset_bundle->printers.get_selected_preset();
+    return preset.printer_technology() == ptSLA ? 1 :
+           preset.config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
+}
+
+// extruders count from edited printer preset
+int GUI_App::extruders_edited_cnt() const
+{
+    const Preset& preset = preset_bundle->printers.get_edited_preset();
+    return preset.printer_technology() == ptSLA ? 1 :
+           preset.config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
+}
+
+wxString GUI_App::current_language_code_safe() const
+{
+	// Translate the language code to a code, for which Prusa Research maintains translations.
+	const std::map<wxString, wxString> mapping {
+		{ "cs", 	"cs_CZ", },
+		{ "sk", 	"cs_CZ", },
+		{ "de", 	"de_DE", },
+		{ "es", 	"es_ES", },
+		{ "fr", 	"fr_FR", },
+		{ "it", 	"it_IT", },
+		{ "ja", 	"ja_JP", },
+		{ "ko", 	"ko_KR", },
+		{ "pl", 	"pl_PL", },
+		{ "uk", 	"uk_UA", },
+		{ "zh", 	"zh_CN", },
+		{ "ru", 	"ru_RU", },
+	};
+	wxString language_code = this->current_language_code().BeforeFirst('_');
+	auto it = mapping.find(language_code);
+	if (it != mapping.end())
+		language_code = it->second;
+	else
+		language_code = "en_US";
+	return language_code;
+}
+
+void GUI_App::open_web_page_localized(const std::string &http_address)
+{
+    open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe(), nullptr, false);
+}
+
+// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s).
+// Because of we can't to print the multi-part objects with SLA technology.
+bool GUI_App::may_switch_to_SLA_preset(const wxString& caption)
+{
+    if (model_has_multi_part_objects(model())) {
+        show_info(nullptr,
+            _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" +
+            _L("Please check your object list before preset changing."),
+            caption);
+        return false;
+    }
+    return true;
+}
+
+bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page)
+{
+    wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
+
+    if (reason == ConfigWizard::RR_USER) {
+        if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED)
+            return false;
+    }
+
+    auto wizard = new ConfigWizard(mainframe);
+    const bool res = wizard->run(reason, start_page);
+
+    if (res) {
+        load_current_presets();
+
+        // #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config() 
+        if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA)
+            may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard"));
+    }
+
+    return res;
+}
+
+void GUI_App::show_desktop_integration_dialog()
+{
+#ifdef __linux__
+    //wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
+    DesktopIntegrationDialog dialog(mainframe);
+    dialog.ShowModal();
+#endif //__linux__
+}
+
+#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
+void GUI_App::gcode_thumbnails_debug()
+{
+    const std::string BEGIN_MASK = "; thumbnail begin";
+    const std::string END_MASK = "; thumbnail end";
+    std::string gcode_line;
+    bool reading_image = false;
+    unsigned int width = 0;
+    unsigned int height = 0;
+
+    wxFileDialog dialog(GetTopWindow(), _L("Select a gcode file:"), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+    if (dialog.ShowModal() != wxID_OK)
+        return;
+
+    std::string in_filename = into_u8(dialog.GetPath());
+    std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string();
+
+    boost::nowide::ifstream in_file(in_filename.c_str());
+    std::vector<std::string> rows;
+    std::string row;
+    if (in_file.good())
+    {
+        while (std::getline(in_file, gcode_line))
+        {
+            if (in_file.good())
+            {
+                if (boost::starts_with(gcode_line, BEGIN_MASK))
+                {
+                    reading_image = true;
+                    gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1);
+                    std::string::size_type x_pos = gcode_line.find('x');
+                    std::string width_str = gcode_line.substr(0, x_pos);
+                    width = (unsigned int)::atoi(width_str.c_str());
+                    std::string height_str = gcode_line.substr(x_pos + 1);
+                    height = (unsigned int)::atoi(height_str.c_str());
+                    row.clear();
+                }
+                else if (reading_image && boost::starts_with(gcode_line, END_MASK))
+                {
+                    std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png";
+                    boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary);
+                    if (out_file.good())
+                    {
+                        std::string decoded;
+                        decoded.resize(boost::beast::detail::base64::decoded_size(row.size()));
+                        decoded.resize(boost::beast::detail::base64::decode((void*)&decoded[0], row.data(), row.size()).first);
+
+                        out_file.write(decoded.c_str(), decoded.size());
+                        out_file.close();
+                    }
+
+                    reading_image = false;
+                    width = 0;
+                    height = 0;
+                    rows.clear();
+                }
+                else if (reading_image)
+                    row += gcode_line.substr(2);
+            }
+        }
+
+        in_file.close();
+    }
+}
+#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
+
+void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name)
+{
+    if (name.empty()) { return; }
+    const auto config_key = (boost::format("window_%1%") % name).str();
+
+    WindowMetrics metrics = WindowMetrics::from_window(window);
+    app_config->set(config_key, metrics.serialize());
+    app_config->save();
+}
+
+void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized)
+{
+    if (name.empty()) { return; }
+    const auto config_key = (boost::format("window_%1%") % name).str();
+
+    if (! app_config->has(config_key)) {
+        window->Maximize(default_maximized);
+        return;
+    }
+
+    auto metrics = WindowMetrics::deserialize(app_config->get(config_key));
+    if (! metrics) {
+        window->Maximize(default_maximized);
+        return;
+    }
+
+    const wxRect& rect = metrics->get_rect();
+
+    if (app_config->get("restore_win_position") == "1") {
+        // workaround for crash related to the positioning of the window on secondary monitor
+        app_config->set("restore_win_position", (boost::format("crashed_at_%1%_pos") % name).str());
+        app_config->save();
+        window->SetPosition(rect.GetPosition());
+
+        // workaround for crash related to the positioning of the window on secondary monitor
+        app_config->set("restore_win_position", (boost::format("crashed_at_%1%_size") % name).str());
+        app_config->save();
+        window->SetSize(rect.GetSize());
+
+        // revert "restore_win_position" value if application wasn't crashed
+        app_config->set("restore_win_position", "1");
+        app_config->save();
+    }
+    else
+        window->CenterOnScreen();
+
+    window->Maximize(metrics->get_maximized());
+}
+
+void GUI_App::window_pos_sanitize(wxTopLevelWindow* window)
+{
+    /*unsigned*/int display_idx = wxDisplay::GetFromWindow(window);
+    wxRect display;
+    if (display_idx == wxNOT_FOUND) {
+        display = wxDisplay(0u).GetClientArea();
+        window->Move(display.GetTopLeft());
+    } else {
+        display = wxDisplay(display_idx).GetClientArea();
+    }
+
+    auto metrics = WindowMetrics::from_window(window);
+    metrics.sanitize_for_display(display);
+    if (window->GetScreenRect() != metrics.get_rect()) {
+        window->SetSize(metrics.get_rect());
+    }
+}
+
+bool GUI_App::config_wizard_startup()
+{
+    if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) {
+        run_wizard(ConfigWizard::RR_DATA_EMPTY);
+        return true;
+    } else if (get_app_config()->legacy_datadir()) {
+        // Looks like user has legacy pre-vendorbundle data directory,
+        // explain what this is and run the wizard
+
+        MsgDataLegacy dlg;
+        dlg.ShowModal();
+
+        run_wizard(ConfigWizard::RR_DATA_LEGACY);
+        return true;
+    }
+    return false;
+}
+
+bool GUI_App::check_updates(const bool verbose)
+{	
+	PresetUpdater::UpdateResult updater_result;
+	try {
+		updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION);
+		if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
+			mainframe->Close();
+            // Applicaiton is closing.
+            return false;
+		}
+		else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) {
+            m_app_conf_exists = true;
+		}
+		else if (verbose && updater_result == PresetUpdater::R_NOOP) {
+			MsgNoUpdates dlg;
+			dlg.ShowModal();
+		}
+	}
+	catch (const std::exception & ex) {
+		show_error(nullptr, ex.what());
+	}
+    // Applicaiton will continue.
+    return true;
+}
+
+bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/)
+{
+    bool launch = true;
+
+    // warning dialog containes a "Remember my choice" checkbox
+    std::string option_key = "suppress_hyperlinks";
+    if (force_remember_choice || app_config->get(option_key).empty()) {
+        if (app_config->get(option_key).empty()) {
+            RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
+            dialog.ShowCheckBox(_L("Remember my choice"));
+            auto answer = dialog.ShowModal();
+            launch = answer == wxID_YES;
+            if (dialog.IsCheckBoxChecked()) {
+                wxString preferences_item = _L("Suppress to open hyperlink in browser");
+                wxString msg =
+                    _L("PrusaSlicer will remember your choice.") + "\n\n" +
+                    _L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" +
+                    format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item);
+
+                MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
+                if (msg_dlg.ShowModal() == wxID_CANCEL)
+                    return false;
+                app_config->set(option_key, answer == wxID_NO ? "1" : "0");
+            }
+        }
+        if (launch)
+            launch = app_config->get(option_key) != "1";
+    }
+    // warning dialog doesn't containe a "Remember my choice" checkbox
+    // and will be shown only when "Suppress to open hyperlink in browser" is ON.
+    else if (app_config->get(option_key) == "1") {
+        MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
+        launch = dialog.ShowModal() == wxID_YES;
+    }
+
+    return  launch && wxLaunchDefaultBrowser(url, flags);
+}
+
+// static method accepting a wxWindow object as first parameter
+// void warning_catcher{
+//     my($self, $message_dialog) = @_;
+//     return sub{
+//         my $message = shift;
+//         return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ;
+//         my @params = ($message, 'Warning', wxOK | wxICON_WARNING);
+//         $message_dialog
+//             ? $message_dialog->(@params)
+//             : Wx::MessageDialog->new($self, @params)->ShowModal;
+//     };
+// }
+
+// Do we need this function???
+// void GUI_App::notify(message) {
+//     auto frame = GetTopWindow();
+//     // try harder to attract user attention on OS X
+//     if (!frame->IsActive())
+//         frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO);
+// 
+//     // There used to be notifier using a Growl application for OSX, but Growl is dead.
+//     // The notifier also supported the Linux X D - bus notifications, but that support was broken.
+//     //TODO use wxNotificationMessage ?
+// }
+
+
+#ifdef __WXMSW__
+static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue)
+{
+    // see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
+    wchar_t szValueCurrent[1000];
+    DWORD dwType;
+    DWORD dwSize = sizeof(szValueCurrent);
+
+    int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize);
+
+    bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
+
+    if ((iRC != ERROR_SUCCESS) && !bDidntExist)
+        // an error occurred
+        return false;
+
+    if (!bDidntExist) {
+        if (dwType != REG_SZ)
+            // invalid type
+            return false;
+
+        if (::wcscmp(szValueCurrent, pszValue) == 0)
+            // value already set
+            return false;
+    }
+
+    DWORD dwDisposition;
+    HKEY hkey;
+    iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition);
+    bool ret = false;
+    if (iRC == ERROR_SUCCESS) {
+        iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t));
+        if (iRC == ERROR_SUCCESS)
+            ret = true;
+    }
+
+    RegCloseKey(hkey);
+    return ret;
+}
+
+void GUI_App::associate_3mf_files()
+{
+    wchar_t app_path[MAX_PATH];
+    ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
+
+    std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
+    std::wstring prog_id = L"Prusa.Slicer.1";
+    std::wstring prog_desc = L"PrusaSlicer";
+    std::wstring prog_command = prog_path + L" \"%1\"";
+    std::wstring reg_base = L"Software\\Classes";
+    std::wstring reg_extension = reg_base + L"\\.3mf";
+    std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
+    std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
+
+    bool is_new = false;
+    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
+    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
+    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
+
+    if (is_new)
+        // notify Windows only when any of the values gets changed
+        ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
+}
+
+void GUI_App::associate_stl_files()
+{
+    wchar_t app_path[MAX_PATH];
+    ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
+
+    std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
+    std::wstring prog_id = L"Prusa.Slicer.1";
+    std::wstring prog_desc = L"PrusaSlicer";
+    std::wstring prog_command = prog_path + L" \"%1\"";
+    std::wstring reg_base = L"Software\\Classes";
+    std::wstring reg_extension = reg_base + L"\\.stl";
+    std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
+    std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
+
+    bool is_new = false;
+    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
+    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
+    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
+
+    if (is_new)
+        // notify Windows only when any of the values gets changed
+        ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
+}
+
+void GUI_App::associate_gcode_files()
+{
+    wchar_t app_path[MAX_PATH];
+    ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
+
+    std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
+    std::wstring prog_id = L"PrusaSlicer.GCodeViewer.1";
+    std::wstring prog_desc = L"PrusaSlicerGCodeViewer";
+    std::wstring prog_command = prog_path + L" \"%1\"";
+    std::wstring reg_base = L"Software\\Classes";
+    std::wstring reg_extension = reg_base + L"\\.gcode";
+    std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
+    std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
+
+    bool is_new = false;
+    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
+    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
+    is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
+
+    if (is_new)
+        // notify Windows only when any of the values gets changed
+        ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
+}
+#endif // __WXMSW__
+
+
+void GUI_App::on_version_read(wxCommandEvent& evt)
+{
+    app_config->set("version_online", into_u8(evt.GetString()));
+    app_config->save();
+    std::string opt = app_config->get("notify_release");
+    if (this->plater_ == nullptr || (opt != "all" && opt != "release")) {
+        return;
+    }
+    if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) {
+        return;
+    }
+    // notification
+    /*
+    this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable
+        , NotificationManager::NotificationLevel::ImportantNotificationLevel
+        , Slic3r::format(_u8L("New release version %1% is available."), evt.GetString())
+        , _u8L("See Download page.")
+        , [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; }
+    );
+    */
+    // updater 
+    // read triggered_by_user that was set when calling  GUI_App::app_version_check
+    app_updater(m_app_updater->get_triggered_by_user());
+}
+
+void GUI_App::app_updater(bool from_user)
+{
+    DownloadAppData app_data = m_app_updater->get_app_data();
+
+    if (from_user && (!app_data.version || *app_data.version <= *Semver::parse(SLIC3R_VERSION)))
+    {
+        BOOST_LOG_TRIVIAL(info) << "There is no newer version online.";
+        MsgNoAppUpdates no_update_dialog;
+        no_update_dialog.ShowModal();
+        return;
+
+    }
+
+    assert(!app_data.url.empty());
+    assert(!app_data.target_path.empty());
+
+    // dialog with new version info
+    AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version);
+    auto dialog_result = dialog.ShowModal();
+    // checkbox "do not show again"
+    if (dialog.disable_version_check()) {
+        app_config->set("notify_release", "none");
+    }
+    // Doesn't wish to update
+    if (dialog_result != wxID_OK) {
+        return;
+    }
+    // dialog with new version download (installer or app dependent on system) including path selection
+    AppUpdateDownloadDialog dwnld_dlg(*app_data.version, app_data.target_path);
+    dialog_result = dwnld_dlg.ShowModal();
+    //  Doesn't wish to download
+    if (dialog_result != wxID_OK) {
+        return;
+    }
+    app_data.target_path =dwnld_dlg.get_download_path();
+
+    // start download
+    this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get()));
+    app_data.start_after = dwnld_dlg.run_after_download();
+    m_app_updater->set_app_data(std::move(app_data));
+    m_app_updater->sync_download();
+}
+
+void GUI_App::app_version_check(bool from_user)
+{
+    if (from_user) {
+        if (m_app_updater->get_download_ongoing()) {
+            MessageDialog msgdlg(nullptr, _L("Download of new version is already ongoing. Do you wish to continue?"), _L("Notice"), wxYES_NO);
+            if (msgdlg.ShowModal() != wxID_YES)
+                return;
+        }
+    }
+    std::string version_check_url = app_config->version_check_url();
+    m_app_updater->sync_version(version_check_url, from_user);
+}
+
+} // GUI
+} //Slic3r
diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp
index 660cfc7f0..3da843f89 100644
--- a/src/slic3r/GUI/Preferences.cpp
+++ b/src/slic3r/GUI/Preferences.cpp
@@ -1,960 +1,967 @@
-#include "Preferences.hpp"
-#include "OptionsGroup.hpp"
-#include "GUI_App.hpp"
-#include "Plater.hpp"
-#include "MsgDialog.hpp"
-#include "I18N.hpp"
-#include "libslic3r/AppConfig.hpp"
-#include <wx/notebook.h>
-#include "Notebook.hpp"
-#include "ButtonsDescription.hpp"
-#include "OG_CustomCtrl.hpp"
-#include "GLCanvas3D.hpp"
-
-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("use_custom_toolbar_size") == "1";
-
-	// update colors for color pickers
-	update_color(m_sys_colour, wxGetApp().get_label_clr_sys());
-	update_color(m_mod_colour, wxGetApp().get_label_clr_modified());
-
-	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"));
-}
-
-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,
-								const t_config_enum_values *enum_keys_map,
-								std::initializer_list<std::string> enum_values,
-								std::initializer_list<std::string> enum_labels,
-								ConfigOptionMode mode = comSimple)
-{
-	ConfigOptionDef def = {opt_key, coEnum };
-	def.label = label;
-	def.tooltip = tooltip;
-	def.mode = mode;
-	def.enum_keys_map = enum_keys_map;
-	def.enum_values = std::vector<std::string>(enum_values);
-	def.enum_labels = std::vector<std::string>(enum_labels);
-
-	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_searcer(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("remember_output_path") == "1" : 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("autocenter") == "1");
-
-		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("background_processing") == "1");
-
-		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("export_sources_full_pathnames") == "1");
-
-#ifdef _WIN32
-		// Please keep in sync with ConfigWizard
-		append_bool_option(m_optgroup_general, "associate_3mf",
-			L("Associate .3mf files to PrusaSlicer"),
-			L("If enabled, sets PrusaSlicer as default application to open .3mf files."),
-			app_config->get("associate_3mf") == "1");
-
-		append_bool_option(m_optgroup_general, "associate_stl",
-			L("Associate .stl files to PrusaSlicer"),
-			L("If enabled, sets PrusaSlicer as default application to open .stl files."),
-			app_config->get("associate_stl") == "1");
-#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("preset_update") == "1");
-
-		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("no_defaults") == "1");
-
-		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("show_incompatible_presets") == "1");
-
-		m_optgroup_general->append_separator();
-
-		append_bool_option(m_optgroup_general, "show_drop_project_dialog",
-			L("Show drop project dialog"),
-			L("When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load."),
-			app_config->get("show_drop_project_dialog") == "1");
-
-		append_bool_option(m_optgroup_general, "single_instance",
-#if __APPLE__
-			L("Allow just a single PrusaSlicer 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 PrusaSlicer instance"),
-			L("If this is enabled, when starting PrusaSlicer and another instance of the same PrusaSlicer is already running, that instance will be reactivated instead."),
-#endif
-		app_config->has("single_instance") ? app_config->get("single_instance") == "1" : 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 PrusaSlicer,\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 PrusaSlicer 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 PrusaSlicer G-code Viewer"),
-			L("If enabled, sets PrusaSlicer G-code Viewer as default application to open .gcode files."),
-			app_config->get("associate_gcode") == "1");
-	}
-#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("use_retina_opengl") == "1");
-#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("show_splash_screen") == "1");
-
-	append_bool_option(m_optgroup_general, "restore_win_position",
-		L("Restore window position on start"),
-		L("If enabled, PrusaSlicer will be open at the position it was closed"),
-		app_config->get("restore_win_position") == "1");
-
-    // 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("clear_undo_redo_stack_on_new_project") == "1");
-
-#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("use_legacy_3DConnexion") == "1");
-#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("use_perspective_camera") == "1");
-
-	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("use_free_camera") == "1");
-
-	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("reverse_mouse_wheel_zoom") == "1");
-
-	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");
-			get_app_config()->save();
-			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("seq_top_layer_only") == "1");
-
-	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("show_collapse_button") == "1");
-
-		append_bool_option(m_optgroup_gui, "suppress_hyperlinks",
-			L("Suppress to open hyperlink in browser"),
-			L("If enabled, PrusaSlicer 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("suppress_hyperlinks") == "1");
-
-		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("color_mapinulation_panel") == "1");
-
-		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("order_volumes") == "1");
-
-#if ENABLE_SHOW_NON_MANIFOLD_EDGES
-		append_bool_option(m_optgroup_gui, "non_manifold_edges",
-			L("Show non-manifold edges"),
-			L("If enabled, shows non-manifold edges."),
-			app_config->get("non_manifold_edges") == "1");
-#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES
-
-#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("tabs_as_menu") == "1");
-#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("show_hints") == "1");
-
-		append_enum_option(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")))),
-			&ConfigOptionEnum<NotifyReleaseMode>::get_enum_values(),
-			{"all", "release", "none"},
-			{L("All"), L("Release only"), 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("use_custom_toolbar_size") == "1");
-	}
-
-	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("use_custom_toolbar_size") == "1");
-
-		create_settings_mode_widget();
-		create_settings_text_color_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("use_environment_map") == "1");
-
-		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("dark_color_mode") == "1");
-
-		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 standart Windows system menu,\n"
-			"but on some combination od display scales it can look ugly. If disabled, old UI will be used."),
-			app_config->get("sys_menu_enabled") == "1");
-		}
-
-		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()
-#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&)
-{
-	std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled" };
-
-	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);
-
-	app_config->save();
-	if (wxGetApp().is_editor()) {
-		wxGetApp().set_label_clr_sys(m_sys_colour->GetColour());
-		wxGetApp().set_label_clr_modified(m_mod_colour->GetColour());
-	}
-
-	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
-	
-	wxGetApp().update_ui_from_settings();
-	clear_cache();
-}
-
-void PreferencesDialog::revert(wxEvent&)
-{
-	auto app_config = get_app_config();
-
-	bool save_app_config = false;
-	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);
-		save_app_config |= true;
-	}
-	if (m_use_custom_toolbar_size != (get_app_config()->get("use_custom_toolbar_size") == "1")) {
-		app_config->set("use_custom_toolbar_size", m_use_custom_toolbar_size ? "1" : "0");
-		save_app_config |= true;
-
-		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);
-	}
-	if (save_app_config)
-		app_config->save();
-
-
-	for (auto value : m_values) {
-		bool reverted = false;
-		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(key) == "1");
-			continue;
-		}
-		if (key == "new_settings_layout_mode") {
-			m_rb_new_settings_layout_mode->SetValue(app_config->get(key) == "1");
-			continue;
-		}
-		if (key == "dlg_settings_layout_mode") {
-			m_rb_dlg_settings_layout_mode->SetValue(app_config->get(key) == "1");
-			continue;
-		}
-
-		for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui
-#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(key) == "1"))
-				break;
-		}
-		if (key == "tabs_as_menu") {
-			m_rb_new_settings_layout_mode->Show(app_config->get(key) != "1");
-			refresh_og(m_optgroup_gui);
-			continue;
-		}
-	}
-
-	clear_cache();
-	EndModal(wxID_CANCEL);
-}
-
-void PreferencesDialog::msw_rescale()
-{
-	for (ConfigOptionsGroup* og : this->optgroups())
-		og->msw_rescale();
-#ifdef _WIN32
-	m_optgroup_dark_mode->msw_rescale();
-#endif //_WIN32
-#if ENABLE_ENVIRONMENT_MAP
-	m_optgroup_render->msw_rescale();
-#endif // ENABLE_ENVIRONMENT_MAP
-
-    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());
-		app_config->save();
-		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();
-	wxGetApp().UpdateDarkUI(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("old_settings_layout_mode") == "1");
-	add_radio(&m_rb_new_settings_layout_mode, ++id, app_config->get("new_settings_layout_mode") == "1");
-	add_radio(&m_rb_dlg_settings_layout_mode, ++id, app_config->get("dlg_settings_layout_mode") == "1");
-
-#ifdef _MSW_DARK_MODE
-	if (app_config->get("tabs_as_menu") == "1") {
-		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_searcer(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);
-	ButtonsDescription::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_searcer(m_optgroup_gui, 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
-#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
+#include "Preferences.hpp"
+#include "OptionsGroup.hpp"
+#include "GUI_App.hpp"
+#include "Plater.hpp"
+#include "MsgDialog.hpp"
+#include "I18N.hpp"
+#include "libslic3r/AppConfig.hpp"
+#include <wx/notebook.h>
+#include "Notebook.hpp"
+#include "ButtonsDescription.hpp"
+#include "OG_CustomCtrl.hpp"
+#include "GLCanvas3D.hpp"
+
+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("use_custom_toolbar_size") == "1";
+
+	// update colors for color pickers
+	update_color(m_sys_colour, wxGetApp().get_label_clr_sys());
+	update_color(m_mod_colour, wxGetApp().get_label_clr_modified());
+
+	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"));
+}
+
+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,
+								const t_config_enum_values *enum_keys_map,
+								std::initializer_list<std::string> enum_values,
+								std::initializer_list<std::string> enum_labels,
+								ConfigOptionMode mode = comSimple)
+{
+	ConfigOptionDef def = {opt_key, coEnum };
+	def.label = label;
+	def.tooltip = tooltip;
+	def.mode = mode;
+	def.enum_keys_map = enum_keys_map;
+	def.enum_values = std::vector<std::string>(enum_values);
+	def.enum_labels = std::vector<std::string>(enum_labels);
+
+	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_searcer(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("remember_output_path") == "1" : 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("autocenter") == "1");
+
+		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("background_processing") == "1");
+
+		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("export_sources_full_pathnames") == "1");
+
+#ifdef _WIN32
+#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+		// file association is not possible anymore starting with Win 8
+		if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
+#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+			// Please keep in sync with ConfigWizard
+			append_bool_option(m_optgroup_general, "associate_3mf",
+				L("Associate .3mf files to PrusaSlicer"),
+				L("If enabled, sets PrusaSlicer as default application to open .3mf files."),
+				app_config->get("associate_3mf") == "1");
+
+			append_bool_option(m_optgroup_general, "associate_stl",
+				L("Associate .stl files to PrusaSlicer"),
+				L("If enabled, sets PrusaSlicer as default application to open .stl files."),
+				app_config->get("associate_stl") == "1");
+#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+		}
+#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
+#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("preset_update") == "1");
+
+		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("no_defaults") == "1");
+
+		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("show_incompatible_presets") == "1");
+
+		m_optgroup_general->append_separator();
+
+		append_bool_option(m_optgroup_general, "show_drop_project_dialog",
+			L("Show drop project dialog"),
+			L("When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load."),
+			app_config->get("show_drop_project_dialog") == "1");
+
+		append_bool_option(m_optgroup_general, "single_instance",
+#if __APPLE__
+			L("Allow just a single PrusaSlicer 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 PrusaSlicer instance"),
+			L("If this is enabled, when starting PrusaSlicer and another instance of the same PrusaSlicer is already running, that instance will be reactivated instead."),
+#endif
+		app_config->has("single_instance") ? app_config->get("single_instance") == "1" : 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 PrusaSlicer,\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 PrusaSlicer 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 PrusaSlicer G-code Viewer"),
+			L("If enabled, sets PrusaSlicer G-code Viewer as default application to open .gcode files."),
+			app_config->get("associate_gcode") == "1");
+	}
+#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("use_retina_opengl") == "1");
+#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("show_splash_screen") == "1");
+
+	append_bool_option(m_optgroup_general, "restore_win_position",
+		L("Restore window position on start"),
+		L("If enabled, PrusaSlicer will be open at the position it was closed"),
+		app_config->get("restore_win_position") == "1");
+
+    // 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("clear_undo_redo_stack_on_new_project") == "1");
+
+#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("use_legacy_3DConnexion") == "1");
+#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("use_perspective_camera") == "1");
+
+	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("use_free_camera") == "1");
+
+	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("reverse_mouse_wheel_zoom") == "1");
+
+	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");
+			get_app_config()->save();
+			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("seq_top_layer_only") == "1");
+
+	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("show_collapse_button") == "1");
+
+		append_bool_option(m_optgroup_gui, "suppress_hyperlinks",
+			L("Suppress to open hyperlink in browser"),
+			L("If enabled, PrusaSlicer 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("suppress_hyperlinks") == "1");
+
+		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("color_mapinulation_panel") == "1");
+
+		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("order_volumes") == "1");
+
+#if ENABLE_SHOW_NON_MANIFOLD_EDGES
+		append_bool_option(m_optgroup_gui, "non_manifold_edges",
+			L("Show non-manifold edges"),
+			L("If enabled, shows non-manifold edges."),
+			app_config->get("non_manifold_edges") == "1");
+#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES
+
+#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("tabs_as_menu") == "1");
+#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("show_hints") == "1");
+
+		append_enum_option(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")))),
+			&ConfigOptionEnum<NotifyReleaseMode>::get_enum_values(),
+			{"all", "release", "none"},
+			{L("All"), L("Release only"), 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("use_custom_toolbar_size") == "1");
+	}
+
+	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("use_custom_toolbar_size") == "1");
+
+		create_settings_mode_widget();
+		create_settings_text_color_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("use_environment_map") == "1");
+
+		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("dark_color_mode") == "1");
+
+		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 standart Windows system menu,\n"
+			"but on some combination od display scales it can look ugly. If disabled, old UI will be used."),
+			app_config->get("sys_menu_enabled") == "1");
+		}
+
+		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()
+#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&)
+{
+	std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled" };
+
+	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);
+
+	app_config->save();
+	if (wxGetApp().is_editor()) {
+		wxGetApp().set_label_clr_sys(m_sys_colour->GetColour());
+		wxGetApp().set_label_clr_modified(m_mod_colour->GetColour());
+	}
+
+	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
+	
+	wxGetApp().update_ui_from_settings();
+	clear_cache();
+}
+
+void PreferencesDialog::revert(wxEvent&)
+{
+	auto app_config = get_app_config();
+
+	bool save_app_config = false;
+	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);
+		save_app_config |= true;
+	}
+	if (m_use_custom_toolbar_size != (get_app_config()->get("use_custom_toolbar_size") == "1")) {
+		app_config->set("use_custom_toolbar_size", m_use_custom_toolbar_size ? "1" : "0");
+		save_app_config |= true;
+
+		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);
+	}
+	if (save_app_config)
+		app_config->save();
+
+
+	for (auto value : m_values) {
+		bool reverted = false;
+		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(key) == "1");
+			continue;
+		}
+		if (key == "new_settings_layout_mode") {
+			m_rb_new_settings_layout_mode->SetValue(app_config->get(key) == "1");
+			continue;
+		}
+		if (key == "dlg_settings_layout_mode") {
+			m_rb_dlg_settings_layout_mode->SetValue(app_config->get(key) == "1");
+			continue;
+		}
+
+		for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui
+#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(key) == "1"))
+				break;
+		}
+		if (key == "tabs_as_menu") {
+			m_rb_new_settings_layout_mode->Show(app_config->get(key) != "1");
+			refresh_og(m_optgroup_gui);
+			continue;
+		}
+	}
+
+	clear_cache();
+	EndModal(wxID_CANCEL);
+}
+
+void PreferencesDialog::msw_rescale()
+{
+	for (ConfigOptionsGroup* og : this->optgroups())
+		og->msw_rescale();
+#ifdef _WIN32
+	m_optgroup_dark_mode->msw_rescale();
+#endif //_WIN32
+#if ENABLE_ENVIRONMENT_MAP
+	m_optgroup_render->msw_rescale();
+#endif // ENABLE_ENVIRONMENT_MAP
+
+    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());
+		app_config->save();
+		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();
+	wxGetApp().UpdateDarkUI(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("old_settings_layout_mode") == "1");
+	add_radio(&m_rb_new_settings_layout_mode, ++id, app_config->get("new_settings_layout_mode") == "1");
+	add_radio(&m_rb_dlg_settings_layout_mode, ++id, app_config->get("dlg_settings_layout_mode") == "1");
+
+#ifdef _MSW_DARK_MODE
+	if (app_config->get("tabs_as_menu") == "1") {
+		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_searcer(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);
+	ButtonsDescription::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_searcer(m_optgroup_gui, 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
+#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
diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp
index 296c58622..6291d8734 100644
--- a/src/slic3r/Utils/FixModelByWin10.cpp
+++ b/src/slic3r/Utils/FixModelByWin10.cpp
@@ -1,449 +1,449 @@
-#ifdef HAS_WIN10SDK
-
-#ifndef NOMINMAX
-# define NOMINMAX
-#endif
-
-// Windows Runtime
-#include <roapi.h>
-// for ComPtr
-#include <wrl/client.h>
-
-// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/
-#include <winrt/robuffer.h>
-#include <winrt/windows.storage.provider.h>
-#include <winrt/windows.graphics.printing3d.h>
-
-#include "FixModelByWin10.hpp"
-
-#include <atomic>
-#include <chrono>
-#include <cstdint>
-#include <condition_variable>
-#include <exception>
-#include <string>
-#include <thread>
-
-#include <boost/filesystem.hpp>
-#include <boost/nowide/convert.hpp>
-#include <boost/nowide/cstdio.hpp>
-#include <boost/thread.hpp>
-
-#include "libslic3r/Model.hpp"
-#include "libslic3r/Print.hpp"
-#include "libslic3r/PresetBundle.hpp"
-#include "libslic3r/Format/3mf.hpp"
-#include "../GUI/GUI.hpp"
-#include "../GUI/I18N.hpp"
-#include "../GUI/MsgDialog.hpp"
-
-#include <wx/msgdlg.h>
-#include <wx/progdlg.h>
-
-extern "C"{
-	// from rapi.h
-	typedef HRESULT (__stdcall* FunctionRoInitialize)(int);
-	typedef HRESULT (__stdcall* FunctionRoUninitialize)();
-	typedef HRESULT	(__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance);
-	typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory);
-	// from winstring.h
-	typedef HRESULT	(__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32  length, HSTRING *string);
-	typedef HRESULT	(__stdcall* FunctionWindowsDelteString)(HSTRING string);
-}
-
-namespace Slic3r {
-
-HMODULE							s_hRuntimeObjectLibrary  = nullptr;
-FunctionRoInitialize			s_RoInitialize			 = nullptr;
-FunctionRoUninitialize			s_RoUninitialize		 = nullptr;
-FunctionRoActivateInstance		s_RoActivateInstance     = nullptr;
-FunctionRoGetActivationFactory	s_RoGetActivationFactory = nullptr;
-FunctionWindowsCreateString		s_WindowsCreateString    = nullptr;
-FunctionWindowsDelteString		s_WindowsDeleteString    = nullptr;
-
-bool winrt_load_runtime_object_library()
-{
-	if (s_hRuntimeObjectLibrary == nullptr)
-		s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll");
-	if (s_hRuntimeObjectLibrary != nullptr) {
-		s_RoInitialize			 = (FunctionRoInitialize)			GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize");
-		s_RoUninitialize		 = (FunctionRoUninitialize)			GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize");
-		s_RoActivateInstance	 = (FunctionRoActivateInstance)		GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance");
-		s_RoGetActivationFactory = (FunctionRoGetActivationFactory)	GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory");
-		s_WindowsCreateString	 = (FunctionWindowsCreateString)	GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString");
-		s_WindowsDeleteString	 = (FunctionWindowsDelteString)		GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString");
-	}
-	return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString;
-}
-
-static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst)
-{
-	HSTRING hClassName;
-	HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName);
-	if (S_OK != hr) 
-		return hr;
-	hr = (*s_RoActivateInstance)(hClassName, pinst);
-	(*s_WindowsDeleteString)(hClassName);
-	return hr;
-}
-
-template<typename TYPE>
-static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst)
-{
-	IInspectable *pinspectable = nullptr;
-	HRESULT hr = winrt_activate_instance(class_name, &pinspectable);
-	if (S_OK != hr)
-		return hr;
-	hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst);
-	pinspectable->Release();
-	return hr;
-}
-
-static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst)
-{
-	HSTRING hClassName;
-	HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName);
-	if (S_OK != hr)
-		return hr;
-	hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst);
-	(*s_WindowsDeleteString)(hClassName);
-	return hr;
-}
-
-template<typename TYPE>
-static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst)
-{
-	return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast<void**>(pinst));
-}
-
-// To be called often to test whether to cancel the operation.
-typedef std::function<void ()> ThrowOnCancelFn;
-
-template<typename T>
-static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr<T> &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100)
-{
-	Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
-	asyncAction.As(&asyncInfo);
-	AsyncStatus status;
-	// Ugly blocking loop until the RepairAsync call finishes.
-//FIXME replace with a callback.
-// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage
-	for (;;) {
-		asyncInfo->get_Status(&status);
-		if (status != AsyncStatus::Started)
-			return status;
-		throw_on_cancel();
-		::Sleep(blocking_tick_ms);
-	}
-}
-
-static HRESULT winrt_open_file_stream(
-	const std::wstring									 &path,
-	ABI::Windows::Storage::FileAccessMode				  mode,
-	ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream,
-	ThrowOnCancelFn										  throw_on_cancel)
-{
-	// Get the file factory.
-	Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFileStatics> fileFactory;
-	HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf());
-	if (FAILED(hr)) return hr;
-
-	// Open the file asynchronously.
-	HSTRING hstr_path;
-	hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path);
-	if (FAILED(hr)) return hr;
-	Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::StorageFile*>> fileOpenAsync;
-	hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf());
-	if (FAILED(hr)) return hr;
-	(*s_WindowsDeleteString)(hstr_path);
-
-	// Wait until the file gets open, get the actual file.
-	AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel);
-	Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFile> storageFile;
-	if (status == AsyncStatus::Completed) {
-		hr = fileOpenAsync->GetResults(storageFile.GetAddressOf());
-	} else {
-		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
-		hr = fileOpenAsync.As(&asyncInfo);
-		if (FAILED(hr)) return hr;
-		HRESULT err;
-		hr = asyncInfo->get_ErrorCode(&err);
-		return FAILED(hr) ? hr : err;
-	}
-
-	Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> fileStreamAsync;
-	hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf());
-	if (FAILED(hr)) return hr;
-
-	status = winrt_async_await(fileStreamAsync, throw_on_cancel);
-	if (status == AsyncStatus::Completed) {
-		hr = fileStreamAsync->GetResults(fileStream);
-	} else {
-		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
-		hr = fileStreamAsync.As(&asyncInfo);
-		if (FAILED(hr)) return hr;
-		HRESULT err;
-		hr = asyncInfo->get_ErrorCode(&err);
-		if (!FAILED(hr))
-			hr = err;
-	}
-	return hr;
-}
-
-bool is_windows10()
-{
-	HKEY hKey;
-	LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey);
-	if (lRes == ERROR_SUCCESS) {
-		WCHAR szBuffer[512];
-		DWORD dwBufferSize = sizeof(szBuffer);
-		lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize);
-		if (lRes == ERROR_SUCCESS)
-			return wcsncmp(szBuffer, L"Windows 10", 10) == 0;
-		RegCloseKey(hKey);
-	}
-	return false;
-}
-
-// Progress function, to be called regularly to update the progress.
-typedef std::function<void (const char * /* message */, unsigned /* progress */)> ProgressFn;
-
-void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel)
-{
-	if (! is_windows10())
-		throw Slic3r::RuntimeError("fix_model_by_win10_sdk called on non Windows 10 system");
-
-	if (! winrt_load_runtime_object_library())
-		throw Slic3r::RuntimeError("Failed to initialize the WinRT library.");
-
-	HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED);
-	{
-		on_progress(L("Exporting source model"), 20);
-
-		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream>       fileStream;
-		hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel);
-
-		Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3D3MFPackage> printing3d3mfpackage;
-		hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf());
-
-		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Graphics::Printing3D::Printing3DModel*>> modelAsync;
-		hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf());
-
-		AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel);
-		Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3DModel>	  model;
-		if (status == AsyncStatus::Completed)
-			hr = modelAsync->GetResults(model.GetAddressOf());
-		else
-			throw Slic3r::RuntimeError(L("Failed loading the input model."));
-
-		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::Collections::IVector<ABI::Windows::Graphics::Printing3D::Printing3DMesh*>> meshes;
-		hr = model->get_Meshes(meshes.GetAddressOf());
-		unsigned num_meshes = 0;
-		hr = meshes->get_Size(&num_meshes);
-		
-		on_progress(L("Repairing model by the Netfabb service"), 40);
-		
-		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction>					  repairAsync;
-		hr = model->RepairAsync(repairAsync.GetAddressOf());
-		status = winrt_async_await(repairAsync, throw_on_cancel);
-		if (status != AsyncStatus::Completed)
-			throw Slic3r::RuntimeError(L("Mesh repair failed."));
-		repairAsync->GetResults();
-
-		on_progress(L("Loading repaired model"), 60);
-
-		// Verify the number of meshes returned after the repair action.
-		meshes.Reset();
-		hr = model->get_Meshes(meshes.GetAddressOf());
-		hr = meshes->get_Size(&num_meshes);
-
-		// Save model to this class' Printing3D3MFPackage.
-		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction>					  saveToPackageAsync;
-		hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf());
-		status = winrt_async_await(saveToPackageAsync, throw_on_cancel);
-		if (status != AsyncStatus::Completed)
-			throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed."));
-		hr = saveToPackageAsync->GetResults();
-
-		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> generatorStreamAsync;
-		hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf());
-		status = winrt_async_await(generatorStreamAsync, throw_on_cancel);
-		if (status != AsyncStatus::Completed)
-			throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed."));
-		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> generatorStream;
-		hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf());
-
-		// Go to the beginning of the stream.
-		generatorStream->Seek(0);
-		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IInputStream> inputStream;
-		hr = generatorStream.As(&inputStream);
-
-		// Get the buffer factory.
-		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory;
-		hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf());
-
-		// Open the destination file.
-		FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb");
-		try {
-			Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
-			byte														                  *buffer_ptr;
-			bufferFactory->Create(65536 * 2048, buffer.GetAddressOf());
-			{
-				Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
-				buffer.As(&bufferByteAccess);
-				hr = bufferByteAccess->Buffer(&buffer_ptr);
-			}
-			uint32_t length;
-			hr = buffer->get_Length(&length);
-			Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer*, UINT32>> asyncRead;
-			for (;;) {
-				hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf());
-				status = winrt_async_await(asyncRead, throw_on_cancel);
-				if (status != AsyncStatus::Completed)
-					throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed."));
-				hr = buffer->get_Length(&length);
-				if (length == 0)
-					break;
-				fwrite(buffer_ptr, length, 1, fout);
-			}
-		} catch (...) {
-			fclose(fout);
-			throw;
- 		}
-		fclose(fout);
-		// Here all the COM objects will be released through the ComPtr destructors.
-	}
-	(*s_RoUninitialize)();
-}
-
-class RepairCanceledException : public std::exception {
-public:
-   const char* what() const throw() { return "Model repair has been canceled"; }
-};
-
-// returt FALSE, if fixing was canceled
-// fix_result is empty, if fixing finished successfully
-// fix_result containes a message if fixing failed 
-bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxProgressDialog& progress_dialog, const wxString& msg_header, std::string& fix_result)
-{
-	std::mutex 						mutex;
-	std::condition_variable			condition;
-	std::unique_lock<std::mutex>	lock(mutex);
-	struct Progress {
-		std::string 				message;
-		int 						percent  = 0;
-		bool						updated = false;
-	} progress;
-	std::atomic<bool>				canceled = false;
-	std::atomic<bool>				finished = false;
-
-	std::vector<ModelVolume*> volumes;
-	if (volume_idx == -1)
-		volumes = model_object.volumes;
-	else
-		volumes.emplace_back(model_object.volumes[volume_idx]);
-
-	// Executing the calculation in a background thread, so that the COM context could be created with its own threading model.
-	// (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context).
-	bool   success = false;
-	size_t ivolume = 0;
-	auto on_progress = [&mutex, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) {
-        std::lock_guard<std::mutex> lk(mutex);
-		progress.message = msg;
-		progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size()));
-		progress.updated = true;
-	    condition.notify_all();
-	};
-	auto worker_thread = boost::thread([&model_object, &volumes, &ivolume, on_progress, &success, &canceled, &finished]() {
-		try {
-			std::vector<TriangleMesh> meshes_repaired;
-			meshes_repaired.reserve(volumes.size());
-			for (; ivolume < volumes.size(); ++ ivolume) {
-				on_progress(L("Exporting source model"), 0);
-				boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
-				path_src += ".3mf";
-				Model model;
-                ModelObject *mo = model.add_object();
-                mo->add_volume(*volumes[ivolume]);
-
-                // We are about to save a 3mf, fix it by netfabb and load the fixed 3mf back.
-                // store_3mf currently bakes the volume transformation into the mesh itself.
-                // If we then loaded the repaired 3mf and pushed the mesh into the original ModelVolume
-                // (which remembers the matrix the whole time), the transformation would be used twice.
-                // We will therefore set the volume transform on the dummy ModelVolume to identity.
-                mo->volumes.back()->set_transformation(Geometry::Transformation());
-
-                mo->add_instance();
-				if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false, nullptr, false)) {
-					boost::filesystem::remove(path_src);
-					throw Slic3r::RuntimeError(L("Export of a temporary 3mf file failed"));
-				}
-				model.clear_objects();
-				model.clear_materials();
-				boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
-				path_dst += ".3mf";
-				fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, 
-					[&canceled]() { if (canceled) throw RepairCanceledException(); });
-				boost::filesystem::remove(path_src);
-	            // PresetBundle bundle;
-				on_progress(L("Loading repaired model"), 80);
-				DynamicPrintConfig config;
-				ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent };
-				bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false);
-			    boost::filesystem::remove(path_dst);
-				if (! loaded)
-	 				throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed"));
-	 			if (model.objects.size() == 0)
-	 				throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any object"));
-	 			if (model.objects.size() > 1)
-	 				throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one object"));
-	 			if (model.objects.front()->volumes.size() == 0)
-	 				throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any volume"));
-				if (model.objects.front()->volumes.size() > 1)
-	 				throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one volume"));
-	 			meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh()));
-			}
-			for (size_t i = 0; i < volumes.size(); ++ i) {
-				volumes[i]->set_mesh(std::move(meshes_repaired[i]));
-				volumes[i]->calculate_convex_hull();
-				volumes[i]->set_new_unique_id();
-			}
-			model_object.invalidate_bounding_box();
-			-- ivolume;
-			on_progress(L("Model repair finished"), 100);
-			success  = true;
-			finished = true;
-		} catch (RepairCanceledException & /* ex */) {
-			canceled = true;
-			finished = true;
-			on_progress(L("Model repair canceled"), 100);
-		} catch (std::exception &ex) {
-			success = false;
-			finished = true;
-			on_progress(ex.what(), 100);
-		}
-	});
-    while (! finished) {
-		condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; });
-		// decrease progress.percent value to avoid closing of the progress dialog
-		if (!progress_dialog.Update(progress.percent-1, msg_header + _(progress.message)))
-			canceled = true;
-		else
-			progress_dialog.Fit();
-		progress.updated = false;
-    }
-
-	if (canceled) {
-		// Nothing to show.
-	} else if (success) {
-		fix_result = "";
-	} else {
-		fix_result = progress.message;
-	}
-	worker_thread.join();
-	return !canceled;
-}
-
-} // namespace Slic3r
-
-#endif /* HAS_WIN10SDK */
+#ifdef HAS_WIN10SDK
+
+#ifndef NOMINMAX
+# define NOMINMAX
+#endif
+
+// Windows Runtime
+#include <roapi.h>
+// for ComPtr
+#include <wrl/client.h>
+
+// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/
+#include <winrt/robuffer.h>
+#include <winrt/windows.storage.provider.h>
+#include <winrt/windows.graphics.printing3d.h>
+
+#include "FixModelByWin10.hpp"
+
+#include <atomic>
+#include <chrono>
+#include <cstdint>
+#include <condition_variable>
+#include <exception>
+#include <string>
+#include <thread>
+
+#include <boost/filesystem.hpp>
+#include <boost/nowide/convert.hpp>
+#include <boost/nowide/cstdio.hpp>
+#include <boost/thread.hpp>
+
+#include "libslic3r/Model.hpp"
+#include "libslic3r/Print.hpp"
+#include "libslic3r/PresetBundle.hpp"
+#include "libslic3r/Format/3mf.hpp"
+#include "../GUI/GUI.hpp"
+#include "../GUI/I18N.hpp"
+#include "../GUI/MsgDialog.hpp"
+
+#include <wx/msgdlg.h>
+#include <wx/progdlg.h>
+
+extern "C"{
+	// from rapi.h
+	typedef HRESULT (__stdcall* FunctionRoInitialize)(int);
+	typedef HRESULT (__stdcall* FunctionRoUninitialize)();
+	typedef HRESULT	(__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance);
+	typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory);
+	// from winstring.h
+	typedef HRESULT	(__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32  length, HSTRING *string);
+	typedef HRESULT	(__stdcall* FunctionWindowsDelteString)(HSTRING string);
+}
+
+namespace Slic3r {
+
+HMODULE							s_hRuntimeObjectLibrary  = nullptr;
+FunctionRoInitialize			s_RoInitialize			 = nullptr;
+FunctionRoUninitialize			s_RoUninitialize		 = nullptr;
+FunctionRoActivateInstance		s_RoActivateInstance     = nullptr;
+FunctionRoGetActivationFactory	s_RoGetActivationFactory = nullptr;
+FunctionWindowsCreateString		s_WindowsCreateString    = nullptr;
+FunctionWindowsDelteString		s_WindowsDeleteString    = nullptr;
+
+bool winrt_load_runtime_object_library()
+{
+	if (s_hRuntimeObjectLibrary == nullptr)
+		s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll");
+	if (s_hRuntimeObjectLibrary != nullptr) {
+		s_RoInitialize			 = (FunctionRoInitialize)			GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize");
+		s_RoUninitialize		 = (FunctionRoUninitialize)			GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize");
+		s_RoActivateInstance	 = (FunctionRoActivateInstance)		GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance");
+		s_RoGetActivationFactory = (FunctionRoGetActivationFactory)	GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory");
+		s_WindowsCreateString	 = (FunctionWindowsCreateString)	GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString");
+		s_WindowsDeleteString	 = (FunctionWindowsDelteString)		GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString");
+	}
+	return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString;
+}
+
+static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst)
+{
+	HSTRING hClassName;
+	HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName);
+	if (S_OK != hr) 
+		return hr;
+	hr = (*s_RoActivateInstance)(hClassName, pinst);
+	(*s_WindowsDeleteString)(hClassName);
+	return hr;
+}
+
+template<typename TYPE>
+static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst)
+{
+	IInspectable *pinspectable = nullptr;
+	HRESULT hr = winrt_activate_instance(class_name, &pinspectable);
+	if (S_OK != hr)
+		return hr;
+	hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst);
+	pinspectable->Release();
+	return hr;
+}
+
+static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst)
+{
+	HSTRING hClassName;
+	HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName);
+	if (S_OK != hr)
+		return hr;
+	hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst);
+	(*s_WindowsDeleteString)(hClassName);
+	return hr;
+}
+
+template<typename TYPE>
+static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst)
+{
+	return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast<void**>(pinst));
+}
+
+// To be called often to test whether to cancel the operation.
+typedef std::function<void ()> ThrowOnCancelFn;
+
+template<typename T>
+static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr<T> &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100)
+{
+	Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
+	asyncAction.As(&asyncInfo);
+	AsyncStatus status;
+	// Ugly blocking loop until the RepairAsync call finishes.
+//FIXME replace with a callback.
+// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage
+	for (;;) {
+		asyncInfo->get_Status(&status);
+		if (status != AsyncStatus::Started)
+			return status;
+		throw_on_cancel();
+		::Sleep(blocking_tick_ms);
+	}
+}
+
+static HRESULT winrt_open_file_stream(
+	const std::wstring									 &path,
+	ABI::Windows::Storage::FileAccessMode				  mode,
+	ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream,
+	ThrowOnCancelFn										  throw_on_cancel)
+{
+	// Get the file factory.
+	Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFileStatics> fileFactory;
+	HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf());
+	if (FAILED(hr)) return hr;
+
+	// Open the file asynchronously.
+	HSTRING hstr_path;
+	hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path);
+	if (FAILED(hr)) return hr;
+	Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::StorageFile*>> fileOpenAsync;
+	hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf());
+	if (FAILED(hr)) return hr;
+	(*s_WindowsDeleteString)(hstr_path);
+
+	// Wait until the file gets open, get the actual file.
+	AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel);
+	Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFile> storageFile;
+	if (status == AsyncStatus::Completed) {
+		hr = fileOpenAsync->GetResults(storageFile.GetAddressOf());
+	} else {
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
+		hr = fileOpenAsync.As(&asyncInfo);
+		if (FAILED(hr)) return hr;
+		HRESULT err;
+		hr = asyncInfo->get_ErrorCode(&err);
+		return FAILED(hr) ? hr : err;
+	}
+
+	Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> fileStreamAsync;
+	hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf());
+	if (FAILED(hr)) return hr;
+
+	status = winrt_async_await(fileStreamAsync, throw_on_cancel);
+	if (status == AsyncStatus::Completed) {
+		hr = fileStreamAsync->GetResults(fileStream);
+	} else {
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
+		hr = fileStreamAsync.As(&asyncInfo);
+		if (FAILED(hr)) return hr;
+		HRESULT err;
+		hr = asyncInfo->get_ErrorCode(&err);
+		if (!FAILED(hr))
+			hr = err;
+	}
+	return hr;
+}
+
+bool is_windows10()
+{
+	HKEY hKey;
+	LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey);
+	if (lRes == ERROR_SUCCESS) {
+		WCHAR szBuffer[512];
+		DWORD dwBufferSize = sizeof(szBuffer);
+		lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize);
+		if (lRes == ERROR_SUCCESS)
+			return wcsncmp(szBuffer, L"Windows 10", 10) == 0;
+		RegCloseKey(hKey);
+	}
+	return false;
+}
+
+// Progress function, to be called regularly to update the progress.
+typedef std::function<void (const char * /* message */, unsigned /* progress */)> ProgressFn;
+
+void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel)
+{
+	if (! is_windows10())
+		throw Slic3r::RuntimeError("fix_model_by_win10_sdk called on non Windows 10 system");
+
+	if (! winrt_load_runtime_object_library())
+		throw Slic3r::RuntimeError("Failed to initialize the WinRT library.");
+
+	HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED);
+	{
+		on_progress(L("Exporting source model"), 20);
+
+		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream>       fileStream;
+		hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel);
+
+		Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3D3MFPackage> printing3d3mfpackage;
+		hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf());
+
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Graphics::Printing3D::Printing3DModel*>> modelAsync;
+		hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf());
+
+		AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel);
+		Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3DModel>	  model;
+		if (status == AsyncStatus::Completed)
+			hr = modelAsync->GetResults(model.GetAddressOf());
+		else
+			throw Slic3r::RuntimeError(L("Failed loading the input model."));
+
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::Collections::IVector<ABI::Windows::Graphics::Printing3D::Printing3DMesh*>> meshes;
+		hr = model->get_Meshes(meshes.GetAddressOf());
+		unsigned num_meshes = 0;
+		hr = meshes->get_Size(&num_meshes);
+		
+		on_progress(L("Repairing model by the Netfabb service"), 40);
+		
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction>					  repairAsync;
+		hr = model->RepairAsync(repairAsync.GetAddressOf());
+		status = winrt_async_await(repairAsync, throw_on_cancel);
+		if (status != AsyncStatus::Completed)
+			throw Slic3r::RuntimeError(L("Mesh repair failed."));
+		repairAsync->GetResults();
+
+		on_progress(L("Loading repaired model"), 60);
+
+		// Verify the number of meshes returned after the repair action.
+		meshes.Reset();
+		hr = model->get_Meshes(meshes.GetAddressOf());
+		hr = meshes->get_Size(&num_meshes);
+
+		// Save model to this class' Printing3D3MFPackage.
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction>					  saveToPackageAsync;
+		hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf());
+		status = winrt_async_await(saveToPackageAsync, throw_on_cancel);
+		if (status != AsyncStatus::Completed)
+			throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed."));
+		hr = saveToPackageAsync->GetResults();
+
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> generatorStreamAsync;
+		hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf());
+		status = winrt_async_await(generatorStreamAsync, throw_on_cancel);
+		if (status != AsyncStatus::Completed)
+			throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed."));
+		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> generatorStream;
+		hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf());
+
+		// Go to the beginning of the stream.
+		generatorStream->Seek(0);
+		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IInputStream> inputStream;
+		hr = generatorStream.As(&inputStream);
+
+		// Get the buffer factory.
+		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory;
+		hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf());
+
+		// Open the destination file.
+		FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb");
+		try {
+			Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
+			byte														                  *buffer_ptr;
+			bufferFactory->Create(65536 * 2048, buffer.GetAddressOf());
+			{
+				Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
+				buffer.As(&bufferByteAccess);
+				hr = bufferByteAccess->Buffer(&buffer_ptr);
+			}
+			uint32_t length;
+			hr = buffer->get_Length(&length);
+			Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer*, UINT32>> asyncRead;
+			for (;;) {
+				hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf());
+				status = winrt_async_await(asyncRead, throw_on_cancel);
+				if (status != AsyncStatus::Completed)
+					throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed."));
+				hr = buffer->get_Length(&length);
+				if (length == 0)
+					break;
+				fwrite(buffer_ptr, length, 1, fout);
+			}
+		} catch (...) {
+			fclose(fout);
+			throw;
+ 		}
+		fclose(fout);
+		// Here all the COM objects will be released through the ComPtr destructors.
+	}
+	(*s_RoUninitialize)();
+}
+
+class RepairCanceledException : public std::exception {
+public:
+   const char* what() const throw() { return "Model repair has been canceled"; }
+};
+
+// returt FALSE, if fixing was canceled
+// fix_result is empty, if fixing finished successfully
+// fix_result containes a message if fixing failed 
+bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxProgressDialog& progress_dialog, const wxString& msg_header, std::string& fix_result)
+{
+	std::mutex 						mutex;
+	std::condition_variable			condition;
+	std::unique_lock<std::mutex>	lock(mutex);
+	struct Progress {
+		std::string 				message;
+		int 						percent  = 0;
+		bool						updated = false;
+	} progress;
+	std::atomic<bool>				canceled = false;
+	std::atomic<bool>				finished = false;
+
+	std::vector<ModelVolume*> volumes;
+	if (volume_idx == -1)
+		volumes = model_object.volumes;
+	else
+		volumes.emplace_back(model_object.volumes[volume_idx]);
+
+	// Executing the calculation in a background thread, so that the COM context could be created with its own threading model.
+	// (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context).
+	bool   success = false;
+	size_t ivolume = 0;
+	auto on_progress = [&mutex, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) {
+        std::lock_guard<std::mutex> lk(mutex);
+		progress.message = msg;
+		progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size()));
+		progress.updated = true;
+	    condition.notify_all();
+	};
+	auto worker_thread = boost::thread([&model_object, &volumes, &ivolume, on_progress, &success, &canceled, &finished]() {
+		try {
+			std::vector<TriangleMesh> meshes_repaired;
+			meshes_repaired.reserve(volumes.size());
+			for (; ivolume < volumes.size(); ++ ivolume) {
+				on_progress(L("Exporting source model"), 0);
+				boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
+				path_src += ".3mf";
+				Model model;
+                ModelObject *mo = model.add_object();
+                mo->add_volume(*volumes[ivolume]);
+
+                // We are about to save a 3mf, fix it by netfabb and load the fixed 3mf back.
+                // store_3mf currently bakes the volume transformation into the mesh itself.
+                // If we then loaded the repaired 3mf and pushed the mesh into the original ModelVolume
+                // (which remembers the matrix the whole time), the transformation would be used twice.
+                // We will therefore set the volume transform on the dummy ModelVolume to identity.
+                mo->volumes.back()->set_transformation(Geometry::Transformation());
+
+                mo->add_instance();
+				if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false, nullptr, false)) {
+					boost::filesystem::remove(path_src);
+					throw Slic3r::RuntimeError(L("Export of a temporary 3mf file failed"));
+				}
+				model.clear_objects();
+				model.clear_materials();
+				boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
+				path_dst += ".3mf";
+				fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, 
+					[&canceled]() { if (canceled) throw RepairCanceledException(); });
+				boost::filesystem::remove(path_src);
+	            // PresetBundle bundle;
+				on_progress(L("Loading repaired model"), 80);
+				DynamicPrintConfig config;
+				ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent };
+				bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false);
+			    boost::filesystem::remove(path_dst);
+				if (! loaded)
+	 				throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed"));
+	 			if (model.objects.size() == 0)
+	 				throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any object"));
+	 			if (model.objects.size() > 1)
+	 				throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one object"));
+	 			if (model.objects.front()->volumes.size() == 0)
+	 				throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any volume"));
+				if (model.objects.front()->volumes.size() > 1)
+	 				throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one volume"));
+	 			meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh()));
+			}
+			for (size_t i = 0; i < volumes.size(); ++ i) {
+				volumes[i]->set_mesh(std::move(meshes_repaired[i]));
+				volumes[i]->calculate_convex_hull();
+				volumes[i]->set_new_unique_id();
+			}
+			model_object.invalidate_bounding_box();
+			-- ivolume;
+			on_progress(L("Model repair finished"), 100);
+			success  = true;
+			finished = true;
+		} catch (RepairCanceledException & /* ex */) {
+			canceled = true;
+			finished = true;
+			on_progress(L("Model repair canceled"), 100);
+		} catch (std::exception &ex) {
+			success = false;
+			finished = true;
+			on_progress(ex.what(), 100);
+		}
+	});
+    while (! finished) {
+		condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; });
+		// decrease progress.percent value to avoid closing of the progress dialog
+		if (!progress_dialog.Update(progress.percent-1, msg_header + _(progress.message)))
+			canceled = true;
+		else
+			progress_dialog.Fit();
+		progress.updated = false;
+    }
+
+	if (canceled) {
+		// Nothing to show.
+	} else if (success) {
+		fix_result = "";
+	} else {
+		fix_result = progress.message;
+	}
+	worker_thread.join();
+	return !canceled;
+}
+
+} // namespace Slic3r
+
+#endif /* HAS_WIN10SDK */