/*
 * Copyright (c) 2020-2026 Valve Corporation
 * Copyright (c) 2020-2026 LunarG, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Authors:
 * - Christophe Riccio <christophe@lunarg.com>
 */

#include "widget_tab_configurations_layer.h"
#include "widget_resize_combobox.h"
#include "tab_configurations.h"
#include "mainwindow.h"
#include "style.h"
#include "item_tree.h"

#include "../vkconfig_core/configurator.h"
#include "../vkconfig_core/generate_layers_settings_md.h"
#include "../vkconfig_core/generate_layers_settings_html.h"
#include "../vkconfig_core/type_hide_message.h"

#include <QSettings>
#include <QMenu>
#include <QFileDialog>
#include <QDesktopServices>
#include <QMessageBox>
#include <QShortcut>

static std::string BuildPlatformsLog(int platforms) {
    std::string log;

    if (platforms != 0) {
        log += "Platforms: ";

        const std::vector<std::string>& list = GetPlatformTokens(platforms);
        for (std::size_t i = 0, n = list.size(); i < n; ++i) {
            log += list[i];
            if (i < n - 1) {
                log += ", ";
            }
        }

        log += "\n";
    }

    return log;
}

static std::string BuildPropertiesLog(const Layer& layer) {
    std::string description;
    if (!layer.description.empty()) {
        description += layer.description + "\n";
    }
    description += "API Version: " + layer.api_version.str() + " - Implementation Version: " + layer.implementation_version + "\n";
    description += ::BuildPlatformsLog(layer.platforms);

    description += "\n";
    description += layer.manifest_path.AbsolutePath() + "\n";
    description += format("- %s Layers Path \n", GetToken(layer.type));
    description += "- File Format: " + layer.file_format_version.str() + "\n";
    description += "- Layer Binary Path:\n    " + layer.binary_path.AbsolutePath() + "\n";
    description += "\n";
    description +=
        format("Total Settings Count: %d - Total Presets Count: %d", CountSettings(layer.settings), layer.presets.size());
    return description;
}

TabConfigurations::TabConfigurations(MainWindow& window, std::shared_ptr<Ui::MainWindow> ui)
    : Tab(TAB_CONFIGURATIONS, window, ui), _settings_tree_manager(ui) {
    this->ui->configurations_list->installEventFilter(&window);
    this->ui->configurations_layers_list->installEventFilter(&window);
    this->ui->configurations_settings->installEventFilter(&window);

    this->connect(this->ui->configurations_group_box_override, SIGNAL(toggled(bool)), this,
                  SLOT(on_configurations_override_toggled(bool)));

    this->connect(this->ui->configurations_executable_scope, SIGNAL(currentIndexChanged(int)), this,
                  SLOT(on_configurations_executable_scope_currentIndexChanged(int)));
    this->connect(this->ui->configurations_executable_list, SIGNAL(currentIndexChanged(int)), this,
                  SLOT(on_configurations_executable_list_currentIndexChanged(int)));
    this->connect(this->ui->configurations_executable_append, SIGNAL(clicked()), this,
                  SLOT(on_configurations_executable_append_pressed()));
    this->connect(this->ui->configurations_executable_remove, SIGNAL(clicked()), this,
                  SLOT(on_configurations_executable_remove_pressed()));
    this->connect(this->ui->configurations_settings_reset, SIGNAL(clicked()), this,
                  SLOT(on_configurations_settings_reset_pressed()));

    this->connect(this->ui->configurations_group_box_list, SIGNAL(toggled(bool)), this, SLOT(on_configurations_list_toggled(bool)));
    this->connect(this->ui->configurations_group_box_settings, SIGNAL(toggled(bool)), this,
                  SLOT(on_configurations_layers_settings_toggled(bool)));

    this->connect(this->ui->configurations_list, SIGNAL(itemChanged(QListWidgetItem*)), this,
                  SLOT(on_configurations_list_itemChanged(QListWidgetItem*)));
    this->connect(this->ui->configurations_list, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this,
                  SLOT(on_configurations_list_itemDoubleClicked(QListWidgetItem*)));
    this->connect(this->ui->configurations_list, SIGNAL(currentRowChanged(int)), this,
                  SLOT(on_configurations_list_currentRowChanged(int)));

    this->connect(this->ui->configurations_layers_list, SIGNAL(currentRowChanged(int)), this,
                  SLOT(on_configurations_layers_list_currentRowChanged(int)));

    this->connect(&this->_settings_tree_manager, SIGNAL(signalLayerVersionChanged()), this,
                  SLOT(on_configurations_layerVersionChanged()));

    QSettings settings("LunarG", VKCONFIG_SHORT_NAME);
    this->ui->splitter_main->restoreState(settings.value("vkconfig3/mainwindow/splitter_main_state").toByteArray());
    this->ui->splitter_configurations->restoreState(
        settings.value("vkconfig3/mainwindow/splitter_configurations_state").toByteArray());
    this->ui->splitter_settings->restoreState(settings.value("vkconfig3/mainwindow/splitter_settings_state").toByteArray());

    Configurator& configurator = Configurator::Get();

    this->ui->configurations_group_box_scope->blockSignals(true);
    this->ui->configurations_group_box_scope->setVisible(configurator.configuration_show_scope ||
                                                         configurator.GetExecutableScope() != EXECUTABLE_ANY);
    this->ui->configurations_group_box_scope->blockSignals(true);

    this->ui->configurations_executable_scope->blockSignals(true);
    this->ui->configurations_executable_scope->clear();
    for (int i = 0, n = EXECUTABLE_SCOPE_COUNT; i < n; ++i) {
        ExecutableScope scope = static_cast<ExecutableScope>(i);
        this->ui->configurations_executable_scope->addItem(GetLabel(scope));
        this->ui->configurations_executable_scope->setItemData(i, GetTooltip(scope), Qt::ToolTipRole);
    }

    ExecutableScope current_scope = configurator.GetExecutableScope();
    this->ui->configurations_executable_scope->setCurrentIndex(current_scope);
    this->ui->configurations_executable_scope->blockSignals(false);

    this->layer_display_mode = new ResizeComboBox(this->ui->configurations_group_box_layers, 0);
    for (int i = 0, n = LAYERS_DISPLAY_COUNT; i < n; ++i) {
        LayersDisplayMode mode = static_cast<LayersDisplayMode>(i);
        this->layer_display_mode->addItem(::GetLabel(mode));
    }
    this->layer_display_mode->setCurrentIndex(configurator.layers_display_mode);
    QFont font = this->layer_display_mode->font();
    font.setBold(false);
    this->layer_display_mode->setFont(font);
    this->layer_display_mode->setMinimumSize(196, 24);
    this->layer_display_mode->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
    this->layer_display_mode->adjustSize();
    this->ui->configurations_group_box_layers->installEventFilter(this->layer_display_mode);
    this->connect(this->layer_display_mode, SIGNAL(currentIndexChanged(int)), this,
                  SLOT(on_configurations_layers_display_currentIndexChanged(int)));

    QShortcut* shortcut_override =
        new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Space), this->ui->configurations_group_box_override);
    this->connect(shortcut_override, SIGNAL(activated()), this, SLOT(on_configurations_override_toggled()));

    this->UpdateUI(UPDATE_REBUILD_UI);
}

TabConfigurations::~TabConfigurations() {
    QSettings settings("LunarG", VKCONFIG_SHORT_NAME);
    settings.setValue("vkconfig3/mainwindow/splitter_main_state", ui->splitter_main->saveState());
    settings.setValue("vkconfig3/mainwindow/splitter_configurations_state", ui->splitter_configurations->saveState());
    settings.setValue("vkconfig3/mainwindow/splitter_settings_state", ui->splitter_settings->saveState());
}

void TabConfigurations::UpdateUI_Configurations(UpdateUIMode mode) {
    (void)mode;

    Configurator& configurator = Configurator::Get();
    const Executable* executable = configurator.GetActiveExecutable();

    this->ui->configurations_executable_scope->blockSignals(true);
    this->ui->configurations_executable_scope->setCurrentIndex(configurator.GetExecutableScope());
    this->ui->configurations_executable_scope->blockSignals(false);

    this->ui->configurations_executable_list->blockSignals(true);
    this->ui->configurations_executable_list->setToolTip(executable->path.AbsolutePath().c_str());
    this->ui->configurations_executable_list->blockSignals(false);

    this->ui->configurations_list->blockSignals(true);
    this->ui->configurations_list->clear();

    int current_row = -1;

    for (std::size_t i = 0, n = configurator.configurations.available_configurations.size(); i < n; ++i) {
        const Configuration& configuration = configurator.configurations.available_configurations[i];

        std::vector<std::string> missing_layers;
        const bool has_missing_layer = configuration.HasMissingLayer(configurator.layers, missing_layers);

        std::string configuration_tooltip = configuration.key;
        if (configuration.override_settings) {
            configuration_tooltip += " (";
            configuration_tooltip += configuration.override_settings_path.AbsolutePath();
            configuration_tooltip += ")";
        }

        ListItem* item = new ListItem(configuration.key.c_str());
        item->setFlags(item->flags() | Qt::ItemIsEditable);
        item->setText(configuration.key.c_str());

        if (configurator.GetActiveConfiguration() == &configuration) {
            item->setIcon(::Get(configurator.current_theme_mode, has_missing_layer ? ::ICON_SYSTEM_INVALID : ::ICON_SYSTEM_ON));
            item->setToolTip(configuration_tooltip.c_str());
            current_row = static_cast<int>(i);
        } else {
            item->setIcon(::Get(configurator.current_theme_mode, has_missing_layer ? ::ICON_SYSTEM_INVALID : ::ICON_SYSTEM_OFF));
            item->setToolTip(
                format("Select the '%s' configuration to use it with Vulkan executables", configuration.key.c_str()).c_str());
        }

        if (has_missing_layer) {
            item->setToolTip(
                format("The '%s' configuration has missing layers. These layers are ignored.", configuration.key.c_str()).c_str());
        }

        ui->configurations_list->addItem(item);
    }

    if (current_row != -1) {
        this->ui->configurations_list->setCurrentRow(current_row);
    }

    this->ui->configurations_list->blockSignals(false);
}

void TabConfigurations::UpdateUI_Applications(UpdateUIMode ui_update_mode) {
    Configurator& configurator = Configurator::Get();

    const std::vector<Executable>& executables = configurator.executables.GetExecutables();

    this->ui->configurations_executable_list->blockSignals(true);

    if (ui_update_mode == UPDATE_REBUILD_UI) {
        this->ui->configurations_executable_list->clear();
        for (std::size_t i = 0, n = executables.size(); i < n; ++i) {
            const Executable& executable = executables[i];

            this->ui->configurations_executable_list->addItem(executable.path.RelativePath().c_str());
        }
    }

    if (!executables.empty()) {
        this->ui->configurations_executable_list->setCurrentIndex(configurator.executables.GetActiveExecutableIndex());
    }

    this->ui->configurations_executable_list->blockSignals(false);
}

void TabConfigurations::UpdateUI_Layers(UpdateUIMode mode) {
    (void)mode;

    this->ui->configurations_layers_list->blockSignals(true);
    this->ui->configurations_layers_list->clear();

    Configurator& configurator = Configurator::Get();
    this->ui->configurations_group_box_layers->setEnabled(configurator.HasEnabledUI(ENABLE_UI_LAYERS));
    this->ui->execute_closer_application_label->setVisible(configurator.layers_display_mode != LAYERS_DISPLAY_ENABLED_ONLY);
    this->ui->execute_closer_driver_label->setVisible(configurator.layers_display_mode != LAYERS_DISPLAY_ENABLED_ONLY);
    this->ui->configurations_layers_list->setDragEnabled(configurator.layers_display_mode != LAYERS_DISPLAY_ENABLED_ONLY);

    Configuration* configuration = configurator.GetActiveConfiguration();
    if (configuration != nullptr) {
        bool selected_layer = configuration->selected_layer_name.empty();
        bool first_implicit_layer = true;

        for (std::size_t i = 0, n = configuration->parameters.size(); i < n; ++i) {
            Parameter& parameter = configuration->parameters[i];

            switch (configurator.layers_display_mode) {
                default:
                    break;
                case LAYERS_DISPLAY_ENABLED_ONLY: {
                    if (configurator.layers_display_mode == LAYERS_DISPLAY_ENABLED_ONLY) {
                        if (parameter.control != LAYER_CONTROL_ON && parameter.control != LAYER_CONTROL_OFF) {
                            continue;
                        }
                    }
                    break;
                }
                case LAYERS_DISPLAY_EXPLICIT_ONLY: {
                    if (parameter.IsAutoImplicitLayer()) {
                        if (first_implicit_layer) {
                            first_implicit_layer = false;

                            QListWidgetItem* item = new ListItem(implicit_layers);
                            item->setFlags(item->flags() | Qt::ItemIsSelectable);
                            item->setSizeHint(QSize(0, ITEM_HEIGHT));
                            if (configurator.layers_display_mode != LAYERS_DISPLAY_ENABLED_ONLY) {
                                item->setIcon(::Get(configurator.current_theme_mode, ICON_DRAG));
                            }
                            this->ui->configurations_layers_list->addItem(item);

                            QLabel* layer_widget = new QLabel("Vulkan Implicit Layers Located by Vulkan Configurator");
                            this->ui->configurations_layers_list->setItemWidget(item, layer_widget);
                        }
                        continue;
                    }
                    break;
                }
            }

            QListWidgetItem* item = new ListItem(parameter.key.c_str());
            item->setFlags(item->flags() | Qt::ItemIsSelectable);
            item->setSizeHint(QSize(0, ITEM_HEIGHT));
            if (configurator.layers_display_mode != LAYERS_DISPLAY_ENABLED_ONLY) {
                item->setIcon(::Get(configurator.current_theme_mode, ICON_DRAG));
            }
            this->ui->configurations_layers_list->addItem(item);

            ConfigurationLayerWidget* layer_widget = new ConfigurationLayerWidget(this, parameter);

            this->ui->configurations_layers_list->setItemWidget(item, layer_widget);
            if (configuration->selected_layer_name == parameter.key) {
                this->ui->configurations_layers_list->setCurrentItem(item);
                selected_layer = true;
            }
        }

        if (!selected_layer) {
            configuration->selected_layer_name.clear();
        }
        // resizeEvent(nullptr);

        this->ui->configurations_layers_list->update();
    }

    this->ui->configurations_layers_list->blockSignals(false);
}

void TabConfigurations::UpdateUI_Settings(UpdateUIMode mode) {
    (void)mode;

    Configurator& configurator = Configurator::Get();

    if (configurator.GetActiveConfiguration() == nullptr) {
        this->_settings_tree_manager.CleanupGUI();
    } else {
        this->_settings_tree_manager.CreateGUI();
    }

    const Parameter* parameter = configurator.GetActiveParameter();
    if (parameter != nullptr) {
        if (configurator.GetActiveConfiguration()->override_settings) {
            this->ui->configurations_group_box_settings->setToolTip("Disabled: Use External Layerd Settings file is enabled");
        } else {
            const std::string tooltip = format("Check to apply '%s' layer settings", parameter->key.c_str());
            this->ui->configurations_group_box_settings->setToolTip(tooltip.c_str());
        }
    }
}

void TabConfigurations::UpdateUI(UpdateUIMode ui_update_mode) {
    const Configurator& configurator = Configurator::Get();

    this->ui->configurations_group_box_override->blockSignals(true);
    this->ui->configurations_group_box_override->setChecked(configurator.layers_override_enabled);
    this->ui->configurations_group_box_override->blockSignals(false);

    this->UpdateUI_Configurations(ui_update_mode);
    this->UpdateUI_Applications(ui_update_mode);
    this->UpdateUI_Layers(ui_update_mode);
    this->UpdateUI_Settings(ui_update_mode);

    const ExecutableScope scope = configurator.GetExecutableScope();

    const bool enabled_executable = ::EnabledExecutables(scope);

    if (enabled_executable) {
        const Executable* executable = configurator.GetActiveExecutable();

        if (executable != nullptr) {
            const std::string path = executable->path.RelativePath();

            const std::string state = executable->enabled ? "Uncheck to disable" : "Check to enable";

            if (scope == EXECUTABLE_PER) {
                this->ui->configurations_group_box_list->setToolTip(
                    format("%s to select a layers configuration for '%s' executable", state.c_str(), path.c_str()).c_str());
            } else {
                this->ui->configurations_group_box_list->setToolTip(
                    format("%s to enable the layers configuration for '%s' executable", state.c_str(), path.c_str()).c_str());
            }
        }
    } else if (scope == EXECUTABLE_ANY) {
        this->ui->configurations_group_box_list->setToolTip("Select the active layers configuration for any executable");
    } else {
        this->ui->configurations_group_box_list->setToolTip(
            "Change the 'Vulkan Layers Configuration scope' to apply a configuration.");
    }

    this->ui->configurations_group_box_scope->blockSignals(true);
    this->ui->configurations_group_box_scope->setVisible(configurator.configuration_show_scope);
    this->ui->configurations_group_box_scope->setChecked(configurator.layers_override_enabled);
    this->ui->configurations_group_box_scope->blockSignals(true);

    this->ui->configurations_executable_scope->setToolTip(::GetTooltip(scope));
    this->ui->configurations_executable_list->setEnabled(enabled_executable);
    this->ui->configurations_executable_append->setEnabled(enabled_executable);
    this->ui->configurations_executable_remove->setEnabled(enabled_executable);

    this->ui->configurations_group_box_list->blockSignals(true);
    this->ui->configurations_group_box_list->setEnabled(configurator.HasEnabledUI(ENABLE_UI_CONFIG));
    this->ui->configurations_group_box_list->setCheckable(enabled_executable);
    const Executable* executable = configurator.executables.GetActiveExecutable();
    if (executable != nullptr) {
        this->ui->configurations_group_box_list->setChecked(executable->enabled);
    }
    this->ui->configurations_group_box_list->blockSignals(false);

    this->window.UpdateUI_Status();
}

void TabConfigurations::CleanUI() { this->_settings_tree_manager.CleanupGUI(); }

bool TabConfigurations::EventFilter(QObject* target, QEvent* event) {
    if (target == nullptr || event == nullptr) {
        return true;
    }

    QEvent::Type event_type = event->type();

    if (event_type == QEvent::Wheel) {
        return true;
    }

    Configurator& configurator = Configurator::Get();

    if (target == this->ui->configurations_layers_list && event_type == QEvent::ChildRemoved) {
        // Layers were reordered, we need to update the configuration

        std::vector<std::string> layer_names;
        for (int i = 0, n = this->ui->configurations_layers_list->count(); i < n; ++i) {
            QListWidgetItem* item = this->ui->configurations_layers_list->item(i);
            layer_names.push_back(static_cast<ListItem*>(item)->key.c_str());

            /*
            QWidget *widget = ui->configurations_layers_list->itemWidget(ui->configurations_layers_list->item(i));
            if (widget != nullptr) {
                ConfigurationLayerWidget *layer_widget = dynamic_cast<ConfigurationLayerWidget *>(widget);
                if (layer_widget != nullptr) {
                    layer_names.push_back(layer_widget->layer_name);
                }
            }
*/
        }

        Configuration* configuration = configurator.GetActiveConfiguration();
        if (configuration != nullptr) {
            QListWidgetItem* item = this->ui->configurations_layers_list->currentItem();
            if (item != nullptr) {
                Parameter* parameter = configuration->Find(static_cast<ListItem*>(item)->key);
                if (parameter != nullptr) {
                    parameter->was_explicitly_rank = true;
                }
            }

            configuration->Reorder(layer_names);
            configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT);

            this->UpdateUI_Settings(UPDATE_REBUILD_UI);
        }

        return false;
    }

    if (!configurator.layers_override_enabled || !ui->configurations_list->isEnabled()) {
        return false;
    } else if (target == this->ui->configurations_list) {
        QContextMenuEvent* right_click = dynamic_cast<QContextMenuEvent*>(event);
        if (right_click) {
            ListItem* item = static_cast<ListItem*>(this->ui->configurations_list->itemAt(right_click->pos()));

            std::string name;

            if (item != nullptr) {
                name = item->key;
            }

            QMenu menu(ui->configurations_list);
            QFont subtitle_font = menu.font();
            subtitle_font.setBold(true);

            QAction* action_new = new QAction("Create a new Configuration", nullptr);
            action_new->setEnabled(true);
            menu.addAction(action_new);

            QAction* action_import = new QAction("Import a Configuration file...", nullptr);
            action_import->setEnabled(true);
            menu.addAction(action_import);

            QAction* action_export_config = new QAction("Export the Configuration file...", nullptr);
            action_export_config->setEnabled(item != nullptr);
            menu.addAction(action_export_config);

            menu.addSeparator();

            QAction* action_rename = new QAction("Rename the Configuration", nullptr);
            action_rename->setEnabled(item != nullptr);
            menu.addAction(action_rename);

            QAction* action_duplicate = new QAction("Duplicate the Configuration", nullptr);
            action_duplicate->setEnabled(item != nullptr);
            menu.addAction(action_duplicate);

            QAction* action_delete = new QAction("Delete the Configuration", nullptr);
            action_delete->setEnabled(item != nullptr);
            menu.addAction(action_delete);

            menu.addSeparator();

            QAction* action_use_external_settings = new QAction("Use External vk_layer_settings.txt file...", nullptr);
            action_use_external_settings->setCheckable(true);
            Configuration* configuration = configurator.GetActiveConfiguration();
            if (configuration != nullptr) {
                action_use_external_settings->setChecked(configuration->override_settings);
                if (configuration->override_settings) {
                    action_use_external_settings->setToolTip(
                        "Select another external layer settings file to control layer settings instead of Vulkan Configurator UI.");
                } else {
                    action_use_external_settings->setToolTip(
                        "Disable the layer settings control from Vulkan Configurator UI to use an external file.");
                }
            }
            action_use_external_settings->setEnabled(item != nullptr);
            menu.addAction(action_use_external_settings);

            QAction* action_disable_external_settings = new QAction("Disable External vk_layer_settings.txt file", nullptr);
            if (configuration != nullptr) {
                action_disable_external_settings->setEnabled(configuration->override_settings);
                if (configuration->override_settings) {
                    action_disable_external_settings->setToolTip("Restore the layer settings control from Vulkan Configurator UI.");
                }
            } else {
                action_disable_external_settings->setEnabled(false);
            }
            menu.addAction(action_disable_external_settings);

            menu.addSeparator();

            std::string label = name;
            if (!label.empty()) {
                label = format(" '%s'", label.c_str());
            }

            QAction* action_reset_order = new QAction(format("Reset the%s Default Layers order", label.c_str()).c_str(), nullptr);
            action_reset_order->setEnabled(item != nullptr);
            action_reset_order->setToolTip("Reset the configuration, discarding all changes of this configuration.");
            menu.addAction(action_reset_order);

            QAction* action_reset_one = new QAction(format("Reset the%s Default Configuration", label.c_str()).c_str(), nullptr);
            action_reset_one->setEnabled(configurator.configurations.IsDefaultConfiguration(name));
            action_reset_one->setToolTip("Reset the configuration, discarding all changes of this configuration.");
            menu.addAction(action_reset_one);

            QAction* action_reset_all = new QAction("Reset All Default Configurations", nullptr);
            action_reset_all->setEnabled(true);
            action_reset_all->setToolTip("Reset all the default configurations, discarding all changes of these configurations.");
            menu.addAction(action_reset_all);

            menu.addSeparator();

            if (!name.empty()) {
                name = format(" '%s'", name.c_str());
            }

            QAction* action_export_html = new QAction(
                format("Generate%s configuration %s...", name.c_str(), GetLabel(GENERATE_SETTINGS_HTML)).c_str(), nullptr);
            action_export_html->setEnabled(item != nullptr);
            menu.addAction(action_export_html);

            QAction* action_export_markdown = new QAction(
                format("Generate%s configuration %s...", name.c_str(), GetLabel(GENERATE_SETTINGS_MARKDOWN)).c_str(), nullptr);
            action_export_markdown->setEnabled(item != nullptr);
            menu.addAction(action_export_markdown);

            QAction* action_export_settings = new QAction(
                format("Generate%s configuration %s...", name.c_str(), GetLabel(GENERATE_SETTINGS_TXT)).c_str(), nullptr);
            action_export_settings->setEnabled(item != nullptr);
            menu.addAction(action_export_settings);

            QAction* action_export_env_variables_bash_script = new QAction(
                format("Generate%s configuration %s...", name.c_str(), GetLabel(GENERATE_SETTINGS_BASH)).c_str(), nullptr);
            action_export_env_variables_bash_script->setEnabled(item != nullptr);
            menu.addAction(action_export_env_variables_bash_script);

            QAction* action_export_env_variables_cmd_script = new QAction(
                format("Generate%s configuration %s...", name.c_str(), GetLabel(GENERATE_SETTINGS_CMD)).c_str(), nullptr);
            action_export_env_variables_cmd_script->setEnabled(item != nullptr);
            menu.addAction(action_export_env_variables_cmd_script);

            QAction* action_export_extension_code = new QAction(
                format("Generate%s configuration %s...", name.c_str(), GetLabel(GENERATE_SETTINGS_HPP_VULKAN_H)).c_str(), nullptr);
            action_export_extension_code->setEnabled(item != nullptr);
            menu.addAction(action_export_extension_code);

            QAction* action_export_extension_code_hpp = new QAction(
                format("Generate%s configuration %s...", name.c_str(), GetLabel(GENERATE_SETTINGS_HPP_VULKAN_HPP)).c_str(),
                nullptr);
            action_export_extension_code_hpp->setEnabled(item != nullptr);
            menu.addAction(action_export_extension_code_hpp);

            QPoint point(right_click->globalX(), right_click->globalY());
            QAction* action = menu.exec(point);

            if (action == action_new) {
                this->OnContextMenuNewClicked(item);
            } else if (action == action_import) {
                this->OnContextMenuImportClicked(item);
            } else if (action == action_rename) {
                this->OnContextMenuRenameClicked(item);
            } else if (action == action_duplicate) {
                this->OnContextMenuDuplicateClicked(item);
            } else if (action == action_delete) {
                this->OnContextMenuDeleteClicked(item);
            } else if (action == action_reset_order) {
                this->OnContextMenuResetLayersOrderClicked(item);
            } else if (action == action_reset_one) {
                this->OnContextMenuResetOneClicked(item);
            } else if (action == action_reset_all) {
                this->OnContextMenuResetAllClicked(item);
            } else if (action == action_export_config) {
                this->OnContextMenuExportConfigsClicked(item);
            } else if (action == action_export_env_variables_bash_script) {
                this->GenerateClicked(GENERATE_SETTINGS_BASH);
            } else if (action == action_export_env_variables_cmd_script) {
                this->GenerateClicked(GENERATE_SETTINGS_CMD);
            } else if (action == action_export_extension_code) {
                this->GenerateClicked(GENERATE_SETTINGS_HPP_VULKAN_H);
            } else if (action == action_export_extension_code_hpp) {
                this->GenerateClicked(GENERATE_SETTINGS_HPP_VULKAN_HPP);
            } else if (action == action_export_settings) {
                this->GenerateClicked(GENERATE_SETTINGS_TXT);
            } else if (action == action_export_markdown) {
                this->GenerateClicked(GENERATE_SETTINGS_MARKDOWN);
            } else if (action == action_export_html) {
                this->GenerateClicked(GENERATE_SETTINGS_HTML);
            } else if (action == action_use_external_settings) {
                this->on_configuration_settings_file_search_pressed();
            } else if (action == action_disable_external_settings) {
                this->on_configuration_settings_file_disable_pressed();
            }
        }
    } else if (target == this->ui->configurations_layers_list) {
        QContextMenuEvent* right_click = dynamic_cast<QContextMenuEvent*>(event);
        if (right_click) {
            ListItem* item = static_cast<ListItem*>(this->ui->configurations_layers_list->itemAt(right_click->pos()));

            if (item != nullptr) {
                const Configuration* configuration = configurator.GetActiveConfiguration();
                const Parameter* parameter = item->key == implicit_layers ? nullptr : configuration->Find(item->key);

                const Layer* layer =
                    item->key == implicit_layers ? nullptr : configurator.layers.FindFromManifest(parameter->manifest);

                QMenu menu(this->ui->configurations_layers_list);

                QAction* action_description = new QAction("Open the Layer Description...", nullptr);
                action_description->setEnabled(layer != nullptr);
                menu.addAction(action_description);

                QAction* export_html_action = new QAction("Open the Layer HTML Documentation...", nullptr);
                export_html_action->setEnabled(layer != nullptr);
                menu.addAction(export_html_action);

                QAction* export_markdown_action = new QAction("Open the Layer Markdown Documentation...", nullptr);
                export_markdown_action->setEnabled(layer != nullptr);
                menu.addAction(export_markdown_action);

                QAction* visit_layer_website_action = new QAction("Visit the Layer Website...", nullptr);
                visit_layer_website_action->setEnabled(layer != nullptr ? !layer->url.Empty() : false);
                menu.addAction(visit_layer_website_action);

                QPoint point(right_click->globalX(), right_click->globalY());
                QAction* action = menu.exec(point);

                if (action == action_description) {
                    assert(layer != nullptr);

                    std::string title = layer->key;
                    if (layer->status != STATUS_STABLE) {
                        title += format(" (%s)", GetToken(layer->status));
                    }

                    std::string text;
                    if (!layer->introduction.empty()) {
                        text += layer->introduction + "\n\n";
                    }
                    text += BuildPropertiesLog(*layer);

                    QMessageBox alert;
                    alert.setWindowTitle(title.c_str());
                    alert.setText(text.c_str());
                    alert.setStandardButtons(QMessageBox::Ok);
                    alert.setDefaultButton(QMessageBox::Ok);
                    alert.setIcon(QMessageBox::Information);
                    alert.exec();
                } else if (action == visit_layer_website_action) {
                    QDesktopServices::openUrl(QUrl(layer->url.AbsolutePath(false).c_str()));
                } else if (action == export_html_action) {
                    const std::string path = format("%s/%s.html", AbsolutePath(Path::APPDATA).c_str(), layer->key.c_str());
                    ::GenerateSettingsHTML(configurator, layer, path);
                    std::string url = "file:///" + path;
                    QDesktopServices::openUrl(QUrl(url.c_str()));
                } else if (action == export_markdown_action) {
                    const std::string path = format("%s/%s.md", AbsolutePath(Path::APPDATA).c_str(), layer->key.c_str());
                    ::GenerateSettingsMarkdown(configurator, layer, path);
                    QDesktopServices::openUrl(QUrl(("file:///" + path).c_str()));
                }
            }
        }
    } else if (target == this->ui->configurations_settings) {
        QContextMenuEvent* right_click = dynamic_cast<QContextMenuEvent*>(event);
        if (right_click) {
            TreeItem* item = static_cast<TreeItem*>(this->ui->configurations_settings->itemAt(right_click->pos()));

            if (item != nullptr) {
                const Configuration* configuration = configurator.GetActiveConfiguration();
                const Parameter* parameter = configuration->GetActiveParameter();

                const Layer* layer = configurator.layers.FindFromManifest(parameter->manifest);

                const SettingMeta* setting = ::FindSetting(layer->settings, item->key.c_str());
                assert(setting != nullptr);

                QMenu menu(this->ui->configurations_settings);

                QAction* action_description = new QAction("Open the brief setting description...", nullptr);
                action_description->setEnabled(layer != nullptr);
                menu.addAction(action_description);

                QAction* export_html_action = new QAction("Open the detailed setting HTML documentation...", nullptr);
                export_html_action->setEnabled(layer != nullptr);
                menu.addAction(export_html_action);

                QAction* export_url_action = new QAction("Open the dedicated feature documentation...", nullptr);
                export_url_action->setEnabled(layer != nullptr);
                if (layer != nullptr) {
                    export_url_action->setEnabled(!setting->url.Empty());
                }
                menu.addAction(export_url_action);

                QPoint point(right_click->globalX(), right_click->globalY());
                QAction* action = menu.exec(point);

                if (action == action_description) {
                    std::string title = format("%s (%s)", setting->label.c_str(), setting->key.c_str());
                    if (setting->status != STATUS_STABLE) {
                        title += format(" (%s)", ::GetToken(setting->status));
                    }

                    std::string text = setting->description;
                    text += "\n\n";
                    if (!setting->detailed.empty()) {
                        text += setting->detailed;
                        text += "\n\n";
                    }

                    if (setting->type != SETTING_GROUP) {
                        text += "VK_EXT_layer_settings name:\n";
                        text += format("    %s\n", setting->key.c_str());
                        text += "vk_layer_settings.txt variable:\n";
                        text += format("    %s\n", (GetLayerSettingPrefix(layer->key) + setting->key).c_str());
                        text += "Environment variables:\n";
                        std::vector<std::string> envs = BuildEnvVariablesList(layer->key.c_str(), setting->key.c_str());
                        if (!setting->env.empty()) {
                            envs.push_back(setting->env);
                        }
                        for (std::size_t i = 0, n = envs.size(); i < n; ++i) {
                            text += format("    %s\n", envs[i].c_str());
                        }
                        text += "\n";

                        text += format("Type: %s\n", ::GetToken(setting->type));
                        text += format("Default Value: %s\n", setting->Export(EXPORT_MODE_DOC).c_str());
                        if (setting->type == SETTING_ENUM || setting->type == SETTING_FLAGS) {
                            text += "Possible Values:\n";
                            const SettingMetaEnumeration* setting_enum = static_cast<const SettingMetaEnumeration*>(setting);
                            for (std::size_t i = 0, n = setting_enum->enum_values.size(); i < n; ++i) {
                                if (configurator.GetUseLayerDebugMode()) {
                                    if (setting_enum->enum_values[i].view == SETTING_VIEW_HIDDEN) {
                                        continue;
                                    }
                                } else {
                                    if (setting_enum->enum_values[i].view != SETTING_VIEW_STANDARD) {
                                        continue;
                                    }
                                }

                                text += format("- %s\n", setting_enum->enum_values[i].key.c_str());
                            }
                            text += "\n";
                        }
                    }

                    text += ::BuildPlatformsLog(setting->platform_flags);

                    QMessageBox alert;
                    alert.setWindowTitle(title.c_str());
                    alert.setText(text.c_str());
                    alert.setStandardButtons(QMessageBox::Ok);
                    alert.setDefaultButton(QMessageBox::Ok);
                    alert.setIcon(QMessageBox::Information);
                    alert.exec();
                } else if (action == export_html_action) {
                    const std::string path = format("%s/%s.html", AbsolutePath(Path::APPDATA).c_str(), layer->key.c_str());
                    ::GenerateSettingsHTML(configurator, layer, path);

                    std::string url = ConvertStandardSeparators(format("file:///%s#%s-detailed", path.c_str(), item->key.c_str()));
                    QDesktopServices::openUrl(QUrl(url.c_str()));
                } else if (action == export_url_action) {
                    const SettingMeta* setting = ::FindSetting(layer->settings, item->key.c_str());

                    const std::string url = ConvertStandardSeparators(setting->url.AbsolutePath());
                    QDesktopServices::openUrl(QUrl(url.c_str()));
                }
            }
        }
    }

    // Pass it on
    return false;
}

void TabConfigurations::OnRenameConfiguration(QListWidgetItem* list_item) {
    ListItem* item = dynamic_cast<ListItem*>(list_item);
    if (item == nullptr) {
        return;
    }

    // This is the new name we want to use for the configuration
    const std::string& new_name = item->text().toStdString();
    bool valid_new_name = true;

    if (new_name.empty()) {
        valid_new_name = false;
        QMessageBox alert;
        alert.setWindowTitle("Renaming of the layers configuration failed...");
        alert.setText("The configuration name is empty.");
        alert.setInformativeText("The configuration name is required.");
        alert.setStandardButtons(QMessageBox::Ok);
        alert.setDefaultButton(QMessageBox::Ok);
        alert.setIcon(QMessageBox::Warning);
        alert.exec();
    } else if (!IsPortableFilename(new_name)) {
        valid_new_name = false;
        QMessageBox alert;
        alert.setWindowTitle("Invalid name for a configuration...");
        alert.setText("The configuration name is used to build a filename.");
        alert.setInformativeText("The name can't contain any of the following characters: \\ / : * \" < > |.");
        alert.setStandardButtons(QMessageBox::Ok);
        alert.setDefaultButton(QMessageBox::Ok);
        alert.setIcon(QMessageBox::Warning);
        alert.exec();
    } else if (new_name.size() > 255) {
        valid_new_name = false;
        QMessageBox alert;
        alert.setWindowTitle("Configuration name is too long...");
        alert.setText("The configuration name is used to build a filename.");
        alert.setInformativeText("The name must be a maximum of 255 characters.");
        alert.setStandardButtons(QMessageBox::Ok);
        alert.setDefaultButton(QMessageBox::Ok);
        alert.setIcon(QMessageBox::Warning);
        alert.exec();
    }

    Configurator& configurator = Configurator::Get();

    if (configurator.configurations.FindConfiguration(new_name) != nullptr) {
        valid_new_name = false;

        QMessageBox alert;
        alert.setWindowTitle("Renaming of the layers configuration failed...");
        alert.setText("There is already a configuration with the same name.");
        alert.setInformativeText("Use a different name for the configuration.");
        alert.setStandardButtons(QMessageBox::Ok);
        alert.setDefaultButton(QMessageBox::Ok);
        alert.setIcon(QMessageBox::Warning);
        alert.exec();
    }

    if (valid_new_name) {
        configurator.configurations.RenameConfiguration(item->key, new_name);
        item->key = new_name;
        configurator.SetActiveConfigurationName(new_name);

        this->UpdateUI_Configurations(UPDATE_REBUILD_UI);
    } else {
        // If the configurate name is empty or the configuration name is taken, keep old configuration name
        ui->configurations_list->blockSignals(true);
        item->setText(item->key.c_str());  // "item->key" stored configuration old name
        ui->configurations_list->blockSignals(false);

        configurator.SetActiveConfigurationName(item->key);
    }

    configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT);
}

void TabConfigurations::OnContextMenuNewClicked(ListItem* item) {
    (void)item;  // We don't need this

    Configurator& configurator = Configurator::Get();

    Configuration& configuration = configurator.configurations.CreateConfiguration(configurator.layers, "New Configuration");
    configurator.SetActiveConfigurationName(configuration.key);
    configurator.Override(OVERRIDE_AREA_ALL);

    this->UpdateUI_Configurations(UPDATE_REBUILD_UI);
    this->UpdateUI_Layers(UPDATE_REBUILD_UI);
    this->UpdateUI_Settings(UPDATE_REBUILD_UI);
}

void TabConfigurations::OnContextMenuImportClicked(ListItem* item) {
    (void)item;  // We don't need this

    Configurator& configurator = Configurator::Get();

    const Path& path_import = configurator.configurations.last_path_import_config;
    const std::string selected_path = QFileDialog::getOpenFileName(&this->window, "Import Layers Configuration File",
                                                                   path_import.AbsolutePath().c_str(), "JSON configuration(*.json)")
                                          .toStdString();

    if (selected_path.empty()) {
        return;
    }

    std::string configuration_name;
    const bool result = configurator.configurations.ImportConfiguration(configurator.layers, selected_path, configuration_name);

    if (result) {
        configurator.SetActiveConfigurationName(configuration_name);
        configurator.Override(OVERRIDE_AREA_ALL);

        this->UpdateUI_Configurations(UPDATE_REBUILD_UI);
        this->UpdateUI_Layers(UPDATE_REBUILD_UI);
        this->UpdateUI_Settings(UPDATE_REBUILD_UI);
    } else {
        QMessageBox msg;
        msg.setIcon(QMessageBox::Critical);
        msg.setWindowTitle("Importing of a layers Configuration file failed...");
        msg.setText(format("%s is not a valid layers configuration file or it could not be read.", selected_path.c_str()).c_str());
        msg.exec();
    }
}

void TabConfigurations::OnContextMenuRenameClicked(ListItem* item) {
    assert(item);

    ui->configurations_list->editItem(item);
}

void TabConfigurations::OnContextMenuDuplicateClicked(ListItem* item) {
    assert(item);
    assert(!item->key.empty());

    Configurator& configurator = Configurator::Get();
    const Configuration& duplicated_configuration =
        configurator.configurations.DuplicateConfiguration(configurator.layers, item->key);

    item->key = duplicated_configuration.key;
    configurator.SetActiveConfigurationName(item->key);

    configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT);

    this->UpdateUI_Configurations(UPDATE_REBUILD_UI);
    this->UpdateUI_Layers(UPDATE_REBUILD_UI);
    this->UpdateUI_Settings(UPDATE_REBUILD_UI);
}

void TabConfigurations::OnContextMenuDeleteClicked(ListItem* item) {
    assert(item);
    assert(!item->key.empty());

    // Let make sure...
    QMessageBox alert;
    alert.setWindowTitle(format("Removing *%s* configuration...", item->key.c_str()).c_str());
    alert.setText(format("Are you sure you want to remove the *%s* configuration?", item->key.c_str()).c_str());
    alert.setInformativeText("All the data from this configuration will be lost.");
    alert.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
    alert.setDefaultButton(QMessageBox::Yes);
    alert.setIcon(QMessageBox::Warning);
    if (alert.exec() == QMessageBox::No) {
        return;
    }

    Configurator& configurator = Configurator::Get();
    configurator.configurations.RemoveConfiguration(item->key);
    configurator.SetActiveConfigurationName("");

    this->UpdateUI(UPDATE_REBUILD_UI);
}

void TabConfigurations::OnContextMenuResetLayersOrderClicked(ListItem* item) {
    assert(item);
    assert(!item->key.empty());

    Configurator& configurator = Configurator::Get();
    Configuration* configuration = configurator.configurations.FindConfiguration(item->key);
    assert(configuration != nullptr);

    if (!(configurator.Get(HIDE_MESSAGE_LAYERS_ORDER_RESET))) {
        QMessageBox alert;
        alert.setWindowTitle(format("Resetting *%s* Layers order...", configuration->key.c_str()).c_str());
        alert.setText(format("Are you sure you want to reset the *%s* layers order?", configuration->key.c_str()).c_str());
        alert.setInformativeText("The Vulkan Layers order will be reset to the default order.");
        alert.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
        alert.setDefaultButton(QMessageBox::Yes);
        alert.setCheckBox(new QCheckBox("Do not show again."));
        alert.setIcon(QMessageBox::Warning);
        int retval = alert.exec();

        if (alert.checkBox()->isChecked()) {
            configurator.Set(HIDE_MESSAGE_LAYERS_ORDER_RESET);
        }

        if (retval == QMessageBox::No) {
            return;
        }
    }

    configuration->ResetLayersOrder(configurator.layers);

    this->UpdateUI(UPDATE_REBUILD_UI);
}

void TabConfigurations::OnContextMenuResetOneClicked(ListItem* item) {
    assert(item);
    assert(!item->key.empty());

    Configurator& configurator = Configurator::Get();
    Configuration* configuration = configurator.configurations.FindConfiguration(item->key);
    assert(configuration != nullptr);

    QMessageBox alert;
    alert.setWindowTitle(format("Resetting *%s* layers configuration...", configuration->key.c_str()).c_str());
    alert.setText(format("Are you sure you want to reset the *%s* layers configuration?", configuration->key.c_str()).c_str());
    if (configuration->IsDefault())
        alert.setInformativeText(
            format("The layers configuration, including layers settings, will be restored to default built-in *%s* configuration.",
                   configuration->key.c_str())
                .c_str());
    else if (configurator.configurations.HasFile(*configuration))
        alert.setInformativeText(format("The layers configuration, including layers settings, will be reloaded using the *%s* "
                                        "saved file from previous %s run.",
                                        configuration->key.c_str(), VKCONFIG_NAME)
                                     .c_str());
    else
        alert.setInformativeText("The configuration layers and settings will be reset to zero.");
    alert.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
    alert.setDefaultButton(QMessageBox::Yes);
    alert.setIcon(QMessageBox::Warning);
    if (alert.exec() == QMessageBox::No) {
        return;
    }

    configuration->Reset(configurator.layers);

    this->UpdateUI(UPDATE_REBUILD_UI);
}

void TabConfigurations::OnContextMenuResetAllClicked(ListItem* item) {
    (void)item;

    Configurator& configurator = Configurator::Get();

    QMessageBox alert;
    alert.setWindowTitle("Resetting all default layers configurations...");
    alert.setText("Are you sure you want to reset the default loader configurations?");
    alert.setInformativeText(
        "The layers configurations, including layers settings, will be restored to default built-in configurations.");
    alert.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
    alert.setDefaultButton(QMessageBox::Yes);
    alert.setIcon(QMessageBox::Warning);
    if (alert.exec() == QMessageBox::No) {
        return;
    }

    configurator.configurations.ResetDefaultConfigurations(configurator.layers);
    configurator.Override(OVERRIDE_AREA_ALL);

    this->UpdateUI(UPDATE_REBUILD_UI);
}

void TabConfigurations::OnContextMenuExportConfigsClicked(ListItem* item) {
    assert(item);

    Configurator& configurator = Configurator::Get();

    const Path path_export = configurator.configurations.last_path_export_config.RelativePath() + "/" + item->key + ".json";
    const std::string& selected_path =
        QFileDialog::getSaveFileName(&this->window, "Export Layers Configuration File", path_export.AbsolutePath().c_str(),
                                     "JSON configuration(*.json)")
            .toStdString();

    if (selected_path.empty()) {
        return;
    }

    const bool result = configurator.configurations.ExportConfiguration(configurator.layers, selected_path, item->key);

    if (!result) {
        QMessageBox msg;
        msg.setIcon(QMessageBox::Critical);
        msg.setWindowTitle("Exporting of a Layers Configuration file failed...");
        msg.setText(format("Couldn't be create '%s' Layers configuration file.", selected_path.c_str()).c_str());
        msg.exec();
    } else {
        QDesktopServices::openUrl(QUrl::fromLocalFile(selected_path.c_str()));
    }
}

void TabConfigurations::GenerateClicked(GenerateSettingsMode mode) {
    Configurator& configurator = Configurator::Get();

    const Configuration* configuration = configurator.GetActiveConfiguration();
    if (configuration == nullptr) {
        return;
    }

    const Path path_export =
        configurator.configurations.last_path_export_settings.AbsolutePath() + "/" + configuration->key + ::GetDefaultFileExt(mode);
    const std::string caption = format("Generate '%s' configuration %s", configuration->key.c_str(), ::GetLabel(mode));
    const std::string filter = format("%s (*%s)", ::GetLabel(mode), ::GetDefaultFileExt(mode));
    const std::string& selected_path =
        QFileDialog::getSaveFileName(&this->window, caption.c_str(), path_export.AbsolutePath().c_str(), filter.c_str())
            .toStdString();

    if (selected_path.empty()) {
        return;
    }

    const bool result = configurator.Generate(mode, selected_path.c_str());

    if (!result) {
        const std::string title =
            format("Generation of the '%s' configuration %s failed...", configuration->key.c_str(), ::GetLabel(mode));

        QMessageBox msg;
        msg.setIcon(QMessageBox::Critical);
        msg.setWindowTitle(title.c_str());
        msg.setText(format("Couldn't create '%s' file.", selected_path.c_str()).c_str());
        msg.exec();
    } else {
        const Path saved_path(selected_path);
        configurator.configurations.last_path_export_settings = saved_path.AbsoluteDir();
        QDesktopServices::openUrl(QUrl::fromLocalFile(saved_path.AbsoluteDir().c_str()));
    }
}

void TabConfigurations::on_configurations_override_toggled() {
    this->ui->configurations_group_box_override->setChecked(!this->ui->configurations_group_box_override->isChecked());
}

void TabConfigurations::on_configurations_override_toggled(bool checked) {
    Configurator& configurator = Configurator::Get();
    configurator.layers_override_enabled = checked;
    configurator.Override(OVERRIDE_AREA_ALL);

    this->UpdateUI(UPDATE_REBUILD_UI);
}

void TabConfigurations::on_configurations_executable_scope_currentIndexChanged(int index) {
    Configurator& configurator = Configurator::Get();

    const ExecutableScope scope = static_cast<ExecutableScope>(index);
    switch (scope) {
        default: {
            configurator.SetExecutableScope(scope);
            configurator.Override(OVERRIDE_AREA_ALL);
            break;
        }
        case EXECUTABLE_PER: {
            if (!(configurator.Get(HIDE_MESSAGE_NOTIFICATION_EXECUTABLE_PER))) {
                QMessageBox message;
                message.setWindowTitle(format("Selected scope: '%s'", ::GetLabel(scope)).c_str());
                message.setText(::GetTooltip(scope));
                message.setInformativeText(
                    "As the vk_layer_settings.txt file is written in the executable working directory, all the executables "
                    "with the same working directory will share the same layers configuration.\n\nDo you want to continue?");
                message.setIcon(QMessageBox::Information);
                message.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
                message.setDefaultButton(QMessageBox::Ok);
                message.setCheckBox(new QCheckBox("Do not show again."));
                int retval = message.exec();

                if (message.checkBox()->isChecked()) {
                    configurator.Set(HIDE_MESSAGE_NOTIFICATION_EXECUTABLE_PER);
                }

                if (retval == QMessageBox::Cancel) {
                    break;
                }
            }

            configurator.SetExecutableScope(scope);
            configurator.Override(OVERRIDE_AREA_ALL);
            break;
        }
    }

    this->UpdateUI(UPDATE_REFRESH_UI);
}

void TabConfigurations::on_configurations_executable_list_currentIndexChanged(int index) {
    Configurator& configurator = Configurator::Get();
    configurator.executables.SetActiveExecutable(index);

    this->UpdateUI(UPDATE_REFRESH_UI);
    this->window.UpdateUI_Status();
}

void TabConfigurations::on_configurations_executable_append_pressed() {
    Configurator& configurator = Configurator::Get();

    const Path& last_path = configurator.executables.last_path_executable;
    const Path selected_path =
        QFileDialog::getOpenFileName(&this->window, "Executable Path", last_path.AbsolutePath().c_str(), ::GetExecutableFilter())
            .toStdString();

    if (selected_path.Empty()) {
        return;
    }

    configurator.executables.AppendExecutable(selected_path);

    this->UpdateUI(UPDATE_REBUILD_UI);
}

void TabConfigurations::on_configurations_executable_remove_pressed() {
    Configurator& configurator = Configurator::Get();
    const Executable* executable = configurator.executables.GetActiveExecutable();
    assert(executable != nullptr);

    if (!(configurator.Get(HIDE_MESSAGE_WARN_REMOVE_EXECUTABLE))) {
        QMessageBox message;
        message.setWindowTitle(format("Removing an executable from %s", VKCONFIG_NAME).c_str());
        message.setText(format("The '%s' executable '%s' is being removed, and its options will be lost.", VKCONFIG_NAME,
                               executable->path.AbsolutePath().c_str())
                            .c_str());
        message.setInformativeText("Do you want to continue?");
        message.setIcon(QMessageBox::Warning);
        message.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
        message.setDefaultButton(QMessageBox::Yes);
        message.setCheckBox(new QCheckBox("Do not show again."));
        int ret_val = message.exec();

        if (message.checkBox()->isChecked()) {
            configurator.Set(HIDE_MESSAGE_WARN_REMOVE_EXECUTABLE);
        }

        if (ret_val == QMessageBox::No) {
            return;
        }
    }

    configurator.Surrender(OVERRIDE_AREA_ALL);
    configurator.executables.RemoveExecutable();
    configurator.Override(OVERRIDE_AREA_ALL);

    this->UpdateUI(UPDATE_REBUILD_UI);
}

void TabConfigurations::on_configurations_list_toggled(bool checked) {
    Configurator& configurator = Configurator::Get();
    Executable* executable = configurator.GetActiveExecutable();
    if (executable != nullptr) {
        executable->enabled = checked;

        this->UpdatePerExecutableConfigurations();

        configurator.Override(OVERRIDE_AREA_ALL);
    }

    this->UpdateUI(UPDATE_REFRESH_UI);
}

void TabConfigurations::on_configurations_layers_settings_toggled(bool checked) {
    Configurator& configurator = Configurator::Get();

    Parameter* parameter = configurator.GetActiveParameter();
    if (parameter != nullptr) {
        parameter->override_settings = checked;

        configurator.Override(OVERRIDE_AREA_LAYERS_SETTINGS_BIT);
    }

    // The layer version combobox is on even when the layer settings group is unchecked
    this->UpdateUI_Settings(UPDATE_REFRESH_UI);
}

void TabConfigurations::on_configurations_list_itemDoubleClicked(QListWidgetItem* item) { ui->configurations_list->editItem(item); }

/// An item has been changed. Check for edit of the items name (configuration name)
void TabConfigurations::on_configurations_list_itemChanged(QListWidgetItem* item) { this->OnRenameConfiguration(item); }

void TabConfigurations::UpdatePerExecutableConfigurations() {
    Configurator& configurator = Configurator::Get();
    if (configurator.GetExecutableScope() != EXECUTABLE_PER) {
        return;
    }

    Configuration* configuration = configurator.GetActiveConfiguration();
    if (configuration == nullptr) {
        return;
    }

    std::vector<Path> updated_executable_paths;
    if (configurator.executables.UpdateConfigurations(updated_executable_paths)) {
        if (!configurator.Get(HIDE_MESSAGE_NOTIFICATION_PER_CONFIG_UPDATE)) {
            QMessageBox message;
            message.setWindowTitle("Vulkan Executable Layers Configuration Updated");
            message.setText(
                format("Per Executable Layers Configuration Scope requires that all executables with the same working "
                       "directory use the same layers configuration. The following executables were switch to '%s' configuration:",
                       configuration->key.c_str())
                    .c_str());
            std::string informative;
            for (std::size_t i = 0, n = updated_executable_paths.size(); i < n; ++i) {
                informative += format("- %s\n", updated_executable_paths[i].RelativePath().c_str());
            }

            message.setInformativeText(informative.c_str());
            message.setIcon(QMessageBox::Warning);
            message.setCheckBox(new QCheckBox("Do not show again."));
            message.exec();

            if (message.checkBox()->isChecked()) {
                configurator.Set(HIDE_MESSAGE_NOTIFICATION_PER_CONFIG_UPDATE);
            }
        }
    }
}

void TabConfigurations::on_configurations_list_currentRowChanged(int currentRow) {
    this->_settings_tree_manager.CleanupGUI();

    if (currentRow == -1) {
        return;  // No row selected
    }

    QListWidgetItem* list_item = ui->configurations_list->item(currentRow);
    ListItem* item = dynamic_cast<ListItem*>(list_item);

    if (item == nullptr) {
        return;
    }

    Configurator& configurator = Configurator::Get();
    const Configuration* selected_configuration = configurator.configurations.FindConfiguration(item->key);

    if (configurator.GetActiveConfiguration() != selected_configuration) {
        configurator.SetActiveConfigurationName(item->key);

        if (!configurator.Get(HIDE_MESSAGE_WARN_MISSING_LAYERS_IGNORE)) {
            std::vector<std::string> missing_layers;

            const Configuration* configuration = configurator.GetActiveConfiguration();
            if (configuration->HasMissingLayer(configurator.layers, missing_layers)) {
                std::string text = format("The selected '%s' configuration is missing the layers:\n", configuration->key.c_str());
                for (std::size_t i = 0, n = missing_layers.size(); i < n; ++i) {
                    text += format(" - %s\n", missing_layers[i].c_str());
                }
                text += "These layers will be ignored.";

                QMessageBox alert;
                alert.setWindowTitle("Missing Vulkan layers...");
                alert.setText(text.c_str());
                alert.setIcon(QMessageBox::Warning);
                alert.setCheckBox(new QCheckBox("Do not show again."));
                alert.setInformativeText(format("Use the '%s' tab to add the missing layers.", GetLabel(TAB_LAYERS_PATHS)).c_str());

                alert.exec();
                if (alert.checkBox()->isChecked()) {
                    configurator.Set(HIDE_MESSAGE_WARN_MISSING_LAYERS_IGNORE);
                }
            }
        }

        this->UpdatePerExecutableConfigurations();

        configurator.Override(OVERRIDE_AREA_ALL);

        this->UpdateUI(UPDATE_REBUILD_UI);
    }
}

void TabConfigurations::on_configuration_settings_file_search_pressed() {
    Configurator& configurator = Configurator::Get();

    if (!configurator.Get(HIDE_MESSAGE_NOTIFICATION_EXTERNAL_LAYERS_SETTINGS)) {
        QMessageBox message;
        message.setWindowTitle("Using an external layer settings file to control the Vulkan layers");
        message.setText(
            "When selecting the layers configuration, the Vulkan layers settings will be controled by the selected file instead of "
            "Vulkan Configurator UI");
        message.setInformativeText("Do you want to continue?");
        message.setIcon(QMessageBox::Information);
        message.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
        message.setDefaultButton(QMessageBox::Yes);
        message.setCheckBox(new QCheckBox("Do not show again."));

        int ret_val = message.exec();
        if (message.checkBox()->isChecked()) {
            configurator.Set(HIDE_MESSAGE_NOTIFICATION_EXTERNAL_LAYERS_SETTINGS);
        }

        if (ret_val == QMessageBox::No) {
            return;
        }
    }

    Configuration* configuration = configurator.GetActiveConfiguration();
    assert(configuration != nullptr);

    const std::string input_path = configuration->override_settings_path.Empty()
                                       ? Path(Path::HOME).AbsolutePath()
                                       : configuration->override_settings_path.AbsolutePath();

    const std::string selected_path = QFileDialog::getOpenFileName(&this->window, "Locate a Layers Settings File",
                                                                   input_path.c_str(), "vk_layer_settings.txt(*.txt)")
                                          .toStdString();

    if (!selected_path.empty()) {
        configuration->override_settings = true;
        configuration->override_settings_path = selected_path;
    }

    configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT);

    this->UpdateUI_Configurations(UPDATE_REFRESH_UI);
    this->UpdateUI_Settings(UPDATE_REFRESH_UI);
}

void TabConfigurations::on_configuration_settings_file_disable_pressed() {
    Configurator& configurator = Configurator::Get();
    Configuration* configuration = configurator.GetActiveConfiguration();
    assert(configuration != nullptr);

    configuration->override_settings = false;

    configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT);
    this->UpdateUI_Configurations(UPDATE_REFRESH_UI);
    this->UpdateUI_Settings(UPDATE_REFRESH_UI);
}

void TabConfigurations::on_configurations_layers_list_currentRowChanged(int currentRow) {
    this->_settings_tree_manager.CleanupGUI();

    if (currentRow == -1) {
        return;  // No row selected
    }

    ListItem* item = static_cast<ListItem*>(this->ui->configurations_layers_list->item(currentRow));
    if (item == nullptr) {
        return;
    }

    const std::string& layer_key = item->key;

    Configurator& configurator = Configurator::Get();

    Configuration* configuration = configurator.GetActiveConfiguration();
    assert(configuration != nullptr);

    if (configuration->selected_layer_name != layer_key) {
        configuration->selected_layer_name = layer_key;

        this->UpdateUI_Settings(UPDATE_REBUILD_UI);
    }
}

void TabConfigurations::on_configurations_layers_display_currentIndexChanged(int index) {
    Configurator& configurator = Configurator::Get();
    configurator.layers_display_mode = static_cast<LayersDisplayMode>(index);

    this->UpdateUI(UPDATE_REBUILD_UI);
}

void TabConfigurations::on_configurations_layerVersionChanged() { this->UpdateUI_Layers(UPDATE_REBUILD_UI); }
