/* SPDX-License-Identifier: GPL-3.0-or-later
 * SPDX-FileCopyrightText: 2022-2025 Soren Stoutner <soren@stoutner.com>
 *
 * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc/>.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <https://www.gnu.org/licenses/>.
 */

// Application headers.
#include "DomainSettingsDialog.h"
#include "Settings.h"
#include "ui_DomainSettingsDialog.h"
#include "databases/DomainsDatabase.h"

// Qt toolkit headers.
#include <QInputDialog>
#include <QMessageBox>
#include <QPushButton>
#include <QTimer>

// Define the public static int constants.
const int DomainSettingsDialog::SHOW_ALL_DOMAINS = 0;
const int DomainSettingsDialog::EDIT_DOMAIN = 1;

// Construct the class.
DomainSettingsDialog::DomainSettingsDialog(QWidget *parentWidgetPointer, const int &startType, const QString &domainName) : QDialog(parentWidgetPointer)
{
    // Set the window title.
    setWindowTitle(i18nc("The domain settings dialog window title", "Domain Settings"));

    // Set the window modality.
    setWindowModality(Qt::WindowModality::ApplicationModal);

    // Instantiate the user agent helper.
    userAgentHelperPointer = new UserAgentHelper();

    // Instantiate the domain settings dialog UI.
    Ui::DomainSettingsDialog domainSettingsDialogUi;

    // Setup the UI.
    domainSettingsDialogUi.setupUi(this);

    // Get handles for the widgets.
    domainsListViewPointer = domainSettingsDialogUi.domainsListView;
    domainSettingsWidgetPointer = domainSettingsDialogUi.domainSettingsWidget;
    domainNameLineEditPointer = domainSettingsDialogUi.domainNameLineEdit;
    javaScriptWidgetPointer = domainSettingsDialogUi.javaScriptWidget;
    javaScriptComboBoxPointer = domainSettingsDialogUi.javaScriptComboBox;
    javaScriptLabelPointer = domainSettingsDialogUi.javaScriptLabel;
    localStorageWidgetPointer = domainSettingsDialogUi.localStorageWidget;
    localStorageComboBoxPointer = domainSettingsDialogUi.localStorageComboBox;
    localStorageLabelPointer = domainSettingsDialogUi.localStorageLabel;
    domStorageWidgetPointer = domainSettingsDialogUi.domStorageWidget;
    domStorageComboBoxPointer = domainSettingsDialogUi.domStorageComboBox;
    domStorageLabelPointer = domainSettingsDialogUi.domStorageLabel;
    userAgentWidgetPointer = domainSettingsDialogUi.userAgentWidget;
    userAgentComboBoxPointer = domainSettingsDialogUi.userAgentComboBox;
    userAgentLabelPointer = domainSettingsDialogUi.userAgentLabel;
    ultraPrivacyWidgetPointer = domainSettingsDialogUi.ultraPrivacyWidget;
    ultraPrivacyComboBoxPointer = domainSettingsDialogUi.ultraPrivacyComboBox;
    ultraPrivacyLabelPointer = domainSettingsDialogUi.ultraPrivacyLabel;
    ultraListWidgetPointer = domainSettingsDialogUi.ultraListWidget;
    ultraListComboBoxPointer = domainSettingsDialogUi.ultraListComboBox;
    ultraListLabelPointer = domainSettingsDialogUi.ultraListLabel;
    easyPrivacyWidgetPointer = domainSettingsDialogUi.easyPrivacyWidget;
    easyPrivacyComboBoxPointer = domainSettingsDialogUi.easyPrivacyComboBox;
    easyPrivacyLabelPointer = domainSettingsDialogUi.easyPrivacyLabel;
    easyListWidgetPointer = domainSettingsDialogUi.easyListWidget;
    easyListComboBoxPointer = domainSettingsDialogUi.easyListComboBox;
    easyListLabelPointer = domainSettingsDialogUi.easyListLabel;
    fanboysAnnoyanceListWidgetPointer = domainSettingsDialogUi.fanboysAnnoyanceListWidget;
    fanboysAnnoyanceListComboBoxPointer = domainSettingsDialogUi.fanboysAnnoyanceListComboBox;
    fanboysAnnoyanceListLabelPointer = domainSettingsDialogUi.fanboysAnnoyanceListLabel;
    zoomFactorWidgetPointer = domainSettingsDialogUi.zoomFactorWidget;
    zoomFactorComboBoxPointer = domainSettingsDialogUi.zoomFactorComboBox;
    customZoomFactorSpinBoxPointer = domainSettingsDialogUi.customZoomFactorSpinBox;
    QPushButton *addDomainButtonPointer = domainSettingsDialogUi.addDomainButton;
    deleteDomainButtonPointer = domainSettingsDialogUi.deleteDomainButton;
    deleteAllDomainsButtonPointer = domainSettingsDialogUi.deleteAllDomainsButton;
    QDialogButtonBox *dialogButtonBoxPointer = domainSettingsDialogUi.dialogButtonBox;
    applyButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Apply);
    resetButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Reset);

    // Set the button icons that require a fallback icon.
    deleteAllDomainsButtonPointer->setIcon(QIcon::fromTheme(QLatin1String("albumfolder-user-trash"), QIcon::fromTheme(QLatin1String("list-remove"))));

    // Create a table model.
    domainsTableModelPointer = new QSqlTableModel(nullptr, QSqlDatabase::database(DomainsDatabase::CONNECTION_NAME));

    // Set the table for the model.
    domainsTableModelPointer->setTable(DomainsDatabase::DOMAINS_TABLE);

    // Set the edit strategy to be manual.
    domainsTableModelPointer->setEditStrategy(QSqlTableModel::EditStrategy::OnManualSubmit);

    // Sort the output alphabetically.
    domainsTableModelPointer->setSort(1, Qt::SortOrder::AscendingOrder);

    // Set the model for the list view.
    domainsListViewPointer->setModel(domainsTableModelPointer);

    // Set the visible column to be the domain name.
    domainsListViewPointer->setModelColumn(1);

    // Get the domains selection model pointer.
    domainsSelectionModelPointer = domainsListViewPointer->selectionModel();

    // Disable editing of the list view.
    domainsListViewPointer->setEditTriggers(QAbstractItemView::NoEditTriggers);

    // Read the data from the database and apply it to the table model.
    domainsTableModelPointer->select();

    // Get the default palette.
    defaultPalette = javaScriptWidgetPointer->palette();

    // Populate the highlighted palette.
    highlightedPalette = defaultPalette;

    // Get the default highlight color.
    QColor alphaHighlightColor = defaultPalette.color(QPalette::Highlight);

    // Set the highlight color to be partially transparent.
    alphaHighlightColor.setAlpha(64);

    // Set highlighted background color.
    highlightedPalette.setColor(QPalette::Window, alphaHighlightColor);

    // Setup the dialog according to the start type.
    switch (startType)
    {
        case SHOW_ALL_DOMAINS:
        {
            // Select the first entry in the list view.
            domainsListViewPointer->setCurrentIndex(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabase::DOMAIN_NAME)));

            break;
        }

        case EDIT_DOMAIN:
        {
            // Find the index for the new domain.  `1` returns the first match.
            QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabase::DOMAIN_NAME)),
                                                                             Qt::DisplayRole, domainName, 1, Qt::MatchWrap);

            // Move to the new domain.
            domainsListViewPointer->setCurrentIndex(newDomainIndex[0]);

            break;
        }
    }

    // Populate the domain settings.
    domainSelected(domainsSelectionModelPointer->currentIndex());

    // Handle clicks on the domains.
    connect(domainsListViewPointer, SIGNAL(activated(QModelIndex)), this, SLOT(domainSelected(QModelIndex)));

    // Process changes to the domain settings.
    connect(domainNameLineEditPointer, SIGNAL(textEdited(QString)), this, SLOT(domainNameChanged(QString)));
    connect(javaScriptComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(javaScriptChanged(int)));
    connect(localStorageComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(localStorageChanged(int)));
    connect(domStorageComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(domStorageChanged(int)));
    connect(userAgentComboBoxPointer, SIGNAL(currentTextChanged(QString)), this, SLOT(userAgentChanged(QString)));
    connect(ultraPrivacyComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(ultraPrivacyChanged(int)));
    connect(ultraListComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(ultraListChanged(int)));
    connect(easyPrivacyComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(easyPrivacyChanged(int)));
    connect(easyListComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(easyListChanged(int)));
    connect(fanboysAnnoyanceListComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(fanboysAnnoyanceListChanged(int)));
    connect(zoomFactorComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(zoomFactorComboBoxChanged(int)));
    connect(customZoomFactorSpinBoxPointer, SIGNAL(valueChanged(double)), this, SLOT(customZoomFactorChanged(double)));

    // Connect the buttons.
    connect(addDomainButtonPointer, SIGNAL(clicked()), this, SLOT(showAddMessageBox()));
    connect(deleteDomainButtonPointer, SIGNAL(clicked()), this, SLOT(showDeleteMessageBox()));
    connect(deleteAllDomainsButtonPointer, SIGNAL(clicked()), this, SLOT(showDeleteAllMessageBox()));
    connect(resetButtonPointer, SIGNAL(clicked()), this, SLOT(reset()));
    connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(ok()));
    connect(applyButtonPointer, SIGNAL(clicked()), this, SLOT(apply()));
    connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(cancel()));

    // Update the DOM storage status.
    updateDomStorageStatus();

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::apply() const
{
    // Get the current index.
    QModelIndex currentIndex = domainsListViewPointer->currentIndex();

    // Get the ID of the current index row.
    QVariant currentId = currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::ID)).data();

    // Submit all pending changes.
    domainsTableModelPointer->submitAll();

    // Find the new index for the selected id.  The `1` keeps searching after the first match.
    QModelIndexList newIndexList = domainsTableModelPointer->match(currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::ID)), Qt::DisplayRole, currentId,
                                                                   1, Qt::MatchWrap);

    // Select the new index.
    domainsListViewPointer->setCurrentIndex(newIndexList[0].siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::DOMAIN_NAME)));

    // Update the buttons.
    updateButtons();

    // Emit the domain settings updated signal.
    Q_EMIT domainSettingsUpdated();
}

void DomainSettingsDialog::cancel()
{
    // Revert all pending changes.
    domainsTableModelPointer->revertAll();

    // Close the dialog.
    reject();
}

void DomainSettingsDialog::customZoomFactorChanged(const double newValue) const
{
    // Update the domains table model.
    domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::CUSTOM_ZOOM_FACTOR)), newValue);

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::domStorageChanged(const int newIndex) const
{
    // Update the domains table model.
    domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::DOM_STORAGE)), newIndex);

    // Populate the DOM storage label.
    populateLabel(DOM_STORAGE);

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::domainNameChanged(const QString &updatedDomainName) const
{
    // Update the domains table model.
    domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex(), updatedDomainName);

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::domainSelected(const QModelIndex &modelIndex) const
{
    // Populate the domain name line edit pointer.
    domainNameLineEditPointer->setText(modelIndex.data().toString());

    // Populate the JavaScript combo box.
    javaScriptComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::JAVASCRIPT)).data().toInt());

    // Populate the local storage combo box.
    localStorageComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::LOCAL_STORAGE)).data().toInt());

    // Populate the DOM storage combo box.
    domStorageComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::DOM_STORAGE)).data().toInt());

    // Get the user agent string.
    QString userAgent = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::USER_AGENT)).data().toString();

    // Get the user agent index.
    int userAgentIndex = UserAgentHelper::getDomainSettingsUserAgentIndex(userAgent);

    // Set the user agent combo box index.
    userAgentComboBoxPointer->setCurrentIndex(userAgentIndex);

    // Set the custom user agent if specified.
    if (userAgentIndex == -1) userAgentComboBoxPointer->setCurrentText(userAgent);

    // Populate the filter lists combo boxes.
    ultraPrivacyComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::ULTRAPRIVACY)).data().toInt());
    ultraListComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::ULTRALIST)).data().toInt());
    easyPrivacyComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::EASYPRIVACY)).data().toInt());
    easyListComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::EASYLIST)).data().toInt());
    fanboysAnnoyanceListComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::FANBOYS_ANNOYANCE_LIST)).data().toInt());

    // Get the zoom factor combo box index.
    int zoomFactorComboBoxIndex = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::ZOOM_FACTOR)).data().toInt();

    // Populate the zoom factor combo box.
    zoomFactorComboBoxPointer->setCurrentIndex(zoomFactorComboBoxIndex);

    // Get the combo box palette.
    QPalette comboBoxPalette = zoomFactorComboBoxPointer->palette();

    // Get the combo box window color.
    QColor comboBoxWindowColor = comboBoxPalette.color(QPalette::Window);

    // Set the combo box window color back to the palette.  This makes the system think the palette has changed.
    comboBoxPalette.setColor(QPalette::Window, comboBoxWindowColor);

    // Explicitly set the combo box palette.  Because the system thinks the palette has been changed, and because the combo box palette is being explicitly set,
    // the system will not propagate the highlighted palette from the widget pointer palette down to the combo box.  <https://redmine.stoutner.com/issues/1252>
    zoomFactorComboBoxPointer->setPalette(comboBoxPalette);

    // Populate the custom zoom factor spin box according to the zoom factor combo box.
    if (zoomFactorComboBoxIndex == 0)  // System default zoom factor is selected.
    {
        // Display the default zoom factor.
        customZoomFactorSpinBoxPointer->setValue(Settings::zoomFactor());

        // Use the default palette.
        zoomFactorWidgetPointer->setPalette(defaultPalette);
    }
    else  // Custom zoom factor is selected.
    {
        // Display the custom zoom factor from the domain settings.
        customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::CUSTOM_ZOOM_FACTOR)).data().toDouble());

        // Use the highlighted palette.
        zoomFactorWidgetPointer->setPalette(highlightedPalette);
    }

    // Set the initial status of the custom zoom factor spin box.
    customZoomFactorSpinBoxPointer->setEnabled(zoomFactorComboBoxIndex);

    // Populate the labels.
    populateLabel(JAVASCRIPT);
    populateLabel(LOCAL_STORAGE);
    populateLabel(DOM_STORAGE);
    populateLabel(ULTRAPRIVACY);
    populateLabel(ULTRALIST);
    populateLabel(EASYPRIVACY);
    populateLabel(EASYLIST);
    populateLabel(FANBOYS_ANNOYANCE_LIST);
    populateUserAgentLabel(userAgentComboBoxPointer->currentText());

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::easyListChanged(const int newIndex) const
{
    // Update the domains table model.
    domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::EASYLIST)), newIndex);

    // Populate the EasyList label.
    populateLabel(EASYLIST);

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::easyPrivacyChanged(const int newIndex) const
{
    // Update the domains table model.
    domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::EASYPRIVACY)), newIndex);

    // Populate the EasyPrivacy label.
    populateLabel(EASYPRIVACY);

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::fanboysAnnoyanceListChanged(const int newIndex) const
{
    // Update the domains table model.
    domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::FANBOYS_ANNOYANCE_LIST)), newIndex);

    // Populate the Fanboy's Annoyance List label.
    populateLabel(FANBOYS_ANNOYANCE_LIST);

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::javaScriptChanged(const int newIndex) const
{
    // Update the domains table model.
    domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::JAVASCRIPT)), newIndex);

    // Populate the JavaScript label.
    populateLabel(JAVASCRIPT);

    // Update the DOM storage status.
    updateDomStorageStatus();

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::localStorageChanged(const int newIndex) const
{
    // Update the domains table model.
    domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::LOCAL_STORAGE)), newIndex);

    // Populate the local storage label.
    populateLabel(LOCAL_STORAGE);

    // Update the DOM storage status.
    updateDomStorageStatus();

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::ok()
{
    // Submit all pending changes.
    domainsTableModelPointer->submitAll();

    // Emit the domain settings updated signal.
    Q_EMIT domainSettingsUpdated();

    // Close the dialog.
    accept();
}

void DomainSettingsDialog::populateLabel(const int label) const
{
    // Create the label strings.
    QString enabledString;
    QString enabledBoldString;
    QString disabledString;
    QString disabledBoldString;

    // Create the system default bool.
    bool systemDefaultEnabled;

    // Create the widget pointers.
    QWidget *widgetPointer;
    QComboBox *comboBoxPointer;
    QLabel *labelPointer;

    // Populate the local variables.
    switch (label)
    {
        case (JAVASCRIPT):
        {
            // Populate the label strings.
            enabledString = i18nc("Domain settings JavaScript label.", "JavaScript enabled");
            enabledBoldString = i18nc("Domain settings JavaScript label.  The <b> tags should be retained.", "<b>JavaScript enabled</b>");
            disabledString = i18nc("Domain settings JavaScript label.", "JavaScript disabled");
            disabledBoldString = i18nc("Domain settings JavaScript label.  The <b> tags should be retained.", "<b>JavaScript disabled</b>");

            // Populate the system default bool.
            systemDefaultEnabled = Settings::javaScriptEnabled();

            // Populate the widget pointers.
            widgetPointer = javaScriptWidgetPointer;
            comboBoxPointer = javaScriptComboBoxPointer;
            labelPointer = javaScriptLabelPointer;

            break;
        }

        case (LOCAL_STORAGE):
        {
            // Populate the label strings.
            enabledString = i18nc("Domain settings local storage label.", "Local storage enabled");
            enabledBoldString = i18nc("Domain settings local storage label.  The <b> tags should be retained.", "<b>Local storage enabled</b>");
            disabledString = i18nc("Domain settings local storage label.", "Local storage disabled");
            disabledBoldString = i18nc("Domain settings local storage label.  The <b> tags should be retained.", "<b>Local storage disabled</b>");

            // Populate the system default bool.
            systemDefaultEnabled = Settings::localStorageEnabled();

            // Populate the widget pointers.
            widgetPointer = localStorageWidgetPointer;
            comboBoxPointer = localStorageComboBoxPointer;
            labelPointer = localStorageLabelPointer;

            break;
        }

        case (DOM_STORAGE):
        {
            // Populate the label strings.
            enabledString = i18nc("Domain settings DOM storage label.", "DOM storage enabled");
            enabledBoldString = i18nc("Domain settings DOM storage label.  The <b> tags should be retained.", "<b>DOM storage enabled</b>");
            disabledString = i18nc("Domain settings DOM storage label.", "DOM storage disabled");
            disabledBoldString = i18nc("Domain settings DOM storage label.  The <b> tags should be retained.", "<b>DOM storage disabled</b>");

            // Populate the system default bool.
            systemDefaultEnabled = Settings::domStorageEnabled();

            // Populate the widget pointers.
            widgetPointer = domStorageWidgetPointer;
            comboBoxPointer = domStorageComboBoxPointer;
            labelPointer = domStorageLabelPointer;

            break;
        }

        case (ULTRAPRIVACY):
        {
            // Populate the label strings.
            enabledString = i18nc("Domain settings UltraPrivacy enabled label.", "UltraPrivacy enabled");
            enabledBoldString = i18nc("Domain settings UltraPrivacy enabled bold label.  The <b> tags should be retained.", "<b>UltraPrivacy enabled</b>");
            disabledString = i18nc("Domain settings UltraPrivacy disabled label.", "UltraPrivacy disabled");
            disabledBoldString = i18nc("Domain settings UltraPrivacy disabled bold label.  The <b> tags should be retained.", "<b>UltraPrivacy disabled</b>");

            // Populate the system default bool.
            systemDefaultEnabled = Settings::ultraPrivacyEnabled();

            // Populate the widget pointers.
            widgetPointer = ultraPrivacyWidgetPointer;
            comboBoxPointer = ultraPrivacyComboBoxPointer;
            labelPointer = ultraPrivacyLabelPointer;

            break;
        }

        case (ULTRALIST):
        {
            // Populate the label strings.
            enabledString = i18nc("Domain settings UltraList enabled label.", "UltraList enabled");
            enabledBoldString = i18nc("Domain settings UltraList enabled bold label.  The <b> tags should be retained.", "<b>UltraList enabled</b>");
            disabledString = i18nc("Domain settings UltraList disabled label.", "UltraList disabled");
            disabledBoldString = i18nc("Domain settings UltraList disabled bold label.  The <b> tags should be retained.", "<b>UltraList disabled</b>");

            // Populate the system default bool.
            systemDefaultEnabled = Settings::ultraListEnabled();

            // Populate the widget pointers.
            widgetPointer = ultraListWidgetPointer;
            comboBoxPointer = ultraListComboBoxPointer;
            labelPointer = ultraListLabelPointer;

            break;
        }

        case (EASYPRIVACY):
        {
            // Populate the label strings.
            enabledString = i18nc("Domain settings EasyPrivacy enabled label.", "EasyPrivacy enabled");
            enabledBoldString = i18nc("Domain settings EasyPrivacy enabled bold label.  The <b> tags should be retained.", "<b>EasyPrivacy enabled</b>");
            disabledString = i18nc("Domain settings EasyPrivacy disabled label.", "EasyPrivacy disabled");
            disabledBoldString = i18nc("Domain settings EasyPrivacy disabled bold label.  The <b> tags should be retained.", "<b>EasyPrivacy disabled</b>");

            // Populate the system default bool.
            systemDefaultEnabled = Settings::easyPrivacyEnabled();

            // Populate the widget pointers.
            widgetPointer = easyPrivacyWidgetPointer;
            comboBoxPointer = easyPrivacyComboBoxPointer;
            labelPointer = easyPrivacyLabelPointer;

            break;
        }

        case (EASYLIST):
        {
            // Populate the label strings.
            enabledString = i18nc("Domain settings EasyList enabled label.", "EasyList enabled");
            enabledBoldString = i18nc("Domain settings EasyList enabled bold label.  The <b> tags should be retained.", "<b>EasyList enabled</b>");
            disabledString = i18nc("Domain settings EasyList disabled label.", "EasyList disabled");
            disabledBoldString = i18nc("Domain settings EasyList disabled bold label.  The <b> tags should be retained.", "<b>EasyList disabled</b>");

            // Populate the system default bool.
            systemDefaultEnabled = Settings::easyListEnabled();

            // Populate the widget pointers.
            widgetPointer = easyListWidgetPointer;
            comboBoxPointer = easyListComboBoxPointer;
            labelPointer = easyListLabelPointer;

            break;
        }

        case (FANBOYS_ANNOYANCE_LIST):
        {
            // Populate the label strings.
            enabledString = i18nc("Domain settings Fanboy’s Annoyance List enabled label.", "Fanboy’s Annoyance List enabled");
            enabledBoldString = i18nc("Domain settings Fanboy’s Annoyance List enabled bold label.  The <b> tags should be retained.", "<b>Fanboy’s Annoyance List enabled</b>");
            disabledString = i18nc("Domain settings Fanboy’s Annoyance List disabled label.", "Fanboy’s Annoyance List disabled");
            disabledBoldString = i18nc("Domain settings Fanboy’s Annoyance List disabled bold label.  The <b> tags should be retained.", "<b>Fanboy’s Annoyance List disabled</b>");

            // Populate the system default bool.
            systemDefaultEnabled = Settings::fanboysAnnoyanceListEnabled();

            // Populate the widget pointers.
            widgetPointer = fanboysAnnoyanceListWidgetPointer;
            comboBoxPointer = fanboysAnnoyanceListComboBoxPointer;
            labelPointer = fanboysAnnoyanceListLabelPointer;

            break;
        }
    }

    // Get the combo box palette.
    QPalette comboBoxPalette = comboBoxPointer->palette();

    // Get the combo box window color.
    QColor comboBoxWindowColor = comboBoxPalette.color(QPalette::Window);

    // Set the combo box window color back to the palette.  This makes the system think the palette has changed.
    comboBoxPalette.setColor(QPalette::Window, comboBoxWindowColor);

    // Explicitly set the combo box palette.  Because the system thinks the palette has been changed, and because the combo box palette is being explicitly set,
    // the system will not propagate the highlighted palette from the widget pointer palette down to the combo box.  <https://redmine.stoutner.com/issues/1252>
    comboBoxPointer->setPalette(comboBoxPalette);

    // Populate the label and widget palette according to the currently selected combo box index.
    switch (comboBoxPointer->currentIndex())
    {
        case (DomainsDatabase::SYSTEM_DEFAULT):
        {
            // Set the text according to the system default.
            if (systemDefaultEnabled)
                labelPointer->setText(enabledString);
            else
                labelPointer->setText(disabledString);

            // Reset the palette.
            widgetPointer->setPalette(defaultPalette);
            break;
        }

        case (DomainsDatabase::ENABLED):
        {
            // Set the enabled bold text.
            labelPointer->setText(enabledBoldString);

            // Set the widget palette.
            widgetPointer->setPalette(highlightedPalette);
            break;
        }

        case (DomainsDatabase::DISABLED):
        {
            // Set the disabled bold text.
            labelPointer->setText(disabledBoldString);

            // Set the widget palette.
            widgetPointer->setPalette(highlightedPalette);
            break;
        }
    }
}

void DomainSettingsDialog::populateUserAgentLabel(const QString &userAgentName) const
{
    // Populate the label according to the type.
    if (userAgentName == userAgentHelperPointer->SYSTEM_DEFAULT_TRANSLATED)
    {
        // Display the system default user agent name.
        userAgentLabelPointer->setText(userAgentHelperPointer->getTranslatedUserAgentNameFromDatabaseName(Settings::userAgent()));

        // Reset the palette.
        userAgentWidgetPointer->setPalette(defaultPalette);
    }
    else
    {
        // Display the user agent name in bold.
        userAgentLabelPointer->setText(QLatin1String("<strong>") + userAgentName + QLatin1String("</strong>"));

        // Get the combo box palette.
        QPalette comboBoxPalette = userAgentComboBoxPointer->palette();

        // Get the combo box window color.
        QColor comboBoxWindowColor = comboBoxPalette.color(QPalette::Window);

        // Set the combo box window color back to the palette.  This makes the system think the palette has changed.
        comboBoxPalette.setColor(QPalette::Window, comboBoxWindowColor);

        // Explicitly set the combo box palette.  Because the system thinks the palette has been changed, and because the combo box palette is being explicitly set,
        // the system will not propagate the highlighted palette from the widget pointer palette down to the combo box.  <https://redmine.stoutner.com/issues/1252>
        userAgentComboBoxPointer->setPalette(comboBoxPalette);

        // Set the widget palette.
        userAgentWidgetPointer->setPalette(highlightedPalette);
    }
}

void DomainSettingsDialog::reset() const
{
    // Cancel all pending changes.
    domainsTableModelPointer->revertAll();

    // Repopulate the domain settings.
    domainSelected(domainsListViewPointer->currentIndex());

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::showAddMessageBox()
{
    // Create an OK flag.
    bool okClicked;

    // Display a dialog to request the new domain name from the user.
    QString newDomainName = QInputDialog::getText(this, i18nc("Add domain dialog title", "Add Domain"),
                                                  i18nc("Add domain message.  The \n\n are newline codes that should be retained",
                                                        "Add a new domain.  Doing so will also save any pending changes that have been made to other domains.\n\n"
                                                        "*. may be prepended to a domain to include all subdomains (eg. *.stoutner.com)."),
                                                  QLineEdit::Normal, QString(), &okClicked);

    // Add the new domain if the user clicked OK.
    if (okClicked)
    {
        // Add the new domain.
        DomainsDatabase::addDomain(newDomainName);

        // Submit all pending changes.  This reloads the model from the database, getting the new domain added above.
        domainsTableModelPointer->submitAll();


        // Find the index for the new domain.  `-1` allows for multiple entries to be returned.
        QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabase::DOMAIN_NAME)),
                                                                         Qt::DisplayRole, newDomainName, -1, Qt::MatchWrap);

        // Move to the new domain.  If there are multiple domains with the same name, the new one should be the last in the list.
        domainsListViewPointer->setCurrentIndex(newDomainIndex[newDomainIndex.size() - 1]);

        // Populate the domain settings.
        domainSelected(domainsSelectionModelPointer->currentIndex());

        // Update the buttons.
        updateButtons();

        // Emit the domain settings updated signal.
        Q_EMIT domainSettingsUpdated();
    }
}

void DomainSettingsDialog::showDeleteAllMessageBox() const
{
    // Instantiate a delete all message box.
    QMessageBox deleteAllMessageBox;

    // Set the icon.
    deleteAllMessageBox.setIcon(QMessageBox::Warning);

    // Set the window title.
    deleteAllMessageBox.setWindowTitle(i18nc("Delete all domains dialog title", "Delete All Domains"));

    // Set the text.
    deleteAllMessageBox.setText(i18nc("Delete all domains dialog main message", "Delete all domains?"));

    // Set the informative test.
    deleteAllMessageBox.setInformativeText(i18nc("Delete all domains dialog secondary message", "This action cannot be undone."));

    // Set the standard buttons.
    deleteAllMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);

    // Set the default button.
    deleteAllMessageBox.setDefaultButton(QMessageBox::No);

    // Display the dialog and capture the return value.
    int returnValue = deleteAllMessageBox.exec();

    // Delete all domains if instructed.
    if (returnValue == QMessageBox::Yes)
    {
        // Get the count of all rows.
        int rowCount = domainsTableModelPointer->rowCount();

        // Delete all the rows, starting with 0.
        domainsTableModelPointer->removeRows(0, rowCount);

        // Submit all pending changes.
        domainsTableModelPointer->submitAll();

        // Update the buttons.
        updateButtons();

        // Emit the domain settings updated signal.
        Q_EMIT domainSettingsUpdated();
    }
}

void DomainSettingsDialog::showDeleteMessageBox() const
{
    // Instantiate a delete message box.
    QMessageBox deleteMessageBox;

    // Set the icon.
    deleteMessageBox.setIcon(QMessageBox::Warning);

    // Set the window title.
    deleteMessageBox.setWindowTitle(i18nc("Delete domain dialog title", "Delete Domain"));

    // Set the text.
    deleteMessageBox.setText(i18nc("Delete domain dialog main message", "Delete the current domain?"));

    // Set the informative text.
    deleteMessageBox.setInformativeText(i18nc("Delete domain dialog secondary message", "Doing so will also save any pending changes that have been made to other domains."));

    // Set the standard buttons.
    deleteMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);

    // Set the default button.
    deleteMessageBox.setDefaultButton(QMessageBox::No);

    // Display the dialog and capture the return value.
    int returnValue = deleteMessageBox.exec();

    // Delete the domain if instructed.
    if (returnValue == QMessageBox::Yes)
    {
        // Get the current index.
        QModelIndex currentIndex = domainsListViewPointer->currentIndex();

        // Delete the current row.
        domainsTableModelPointer->removeRow(domainsSelectionModelPointer->currentIndex().row());

        // Submit all pending changes.
        domainsTableModelPointer->submitAll();

        // Select the row next to the deleted item if one exists.
        if (domainsTableModelPointer->rowCount() > 0)
        {
            // Check the row of the deleted item.
            if (currentIndex.row() == 0)  // The first row was deleted.
            {
                // Reselect the current index.
                domainsListViewPointer->setCurrentIndex(currentIndex);
            }
            else  // A subsequent row was deleted.
            {
                // Select the crow above the deleted itemm.
                domainsListViewPointer->setCurrentIndex(currentIndex.siblingAtRow(currentIndex.row() - 1));
            }

            // Populate the domain settings.
            domainSelected(domainsListViewPointer->currentIndex());
        }

        // Update the buttons.
        updateButtons();

        // Emit the domain settings updated signal.
        Q_EMIT domainSettingsUpdated();
    }
}

void DomainSettingsDialog::ultraListChanged(const int newIndex) const
{
    // Update the domains table model.
    domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::ULTRALIST)), newIndex);

    // Populate the UltraList label.
    populateLabel(ULTRALIST);

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::ultraPrivacyChanged(const int newIndex) const
{
    // Update the domains table model.
    domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::ULTRAPRIVACY)), newIndex);

    // Populate the UltraPrivacy label.
    populateLabel(ULTRAPRIVACY);

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::updateButtons() const
{
    // Get
    bool atLeastOneDomainSettingExists = domainsTableModelPointer->rowCount() > 0;

    // Update the delete button status.
    deleteDomainButtonPointer->setEnabled(domainsSelectionModelPointer->hasSelection());

    // Update the delete all button status.
    deleteAllDomainsButtonPointer->setEnabled(atLeastOneDomainSettingExists);

    // Update the reset button status.
    resetButtonPointer->setEnabled(domainsTableModelPointer->isDirty());

    // Update the apply button status.
    applyButtonPointer->setEnabled(domainsTableModelPointer->isDirty());

    // Display the domain settings if there is at least one domain.
    domainSettingsWidgetPointer->setVisible(atLeastOneDomainSettingExists);
}

void DomainSettingsDialog::updateDomStorageStatus() const
{
    // Instantiate tracking variables.
    bool javaScriptEnabled;
    bool localStorageEnabled;

    // Populate the JavaScript tracker.
    switch (javaScriptComboBoxPointer->currentIndex())
    {
        case (DomainsDatabase::SYSTEM_DEFAULT):
        {
            // Update the tracker according to the system default.
            if (Settings::javaScriptEnabled())
                javaScriptEnabled = true;
            else
                javaScriptEnabled = false;

            break;
        }

        case (DomainsDatabase::ENABLED):
        {
            // Update the tracker.
            javaScriptEnabled = true;

            break;
        }

        case (DomainsDatabase::DISABLED):
        {
            // Update the tracker.
            javaScriptEnabled = false;

            break;
        }
    }

    // Populate the local storage tracker.
    switch (localStorageComboBoxPointer->currentIndex())
    {
        case (DomainsDatabase::SYSTEM_DEFAULT):
        {
            // Update the tracker according to the system default.
            if (Settings::localStorageEnabled())
                localStorageEnabled = true;
            else
                localStorageEnabled = false;

            break;
        }

        case (DomainsDatabase::ENABLED):
        {
            // Update the tracker.
            localStorageEnabled = true;

            break;
        }

        case (DomainsDatabase::DISABLED):
        {
            // Update the tracker.
            localStorageEnabled = false;

            break;
        }
    }

    // Only enable DOM storage if both JavaScript and local storage are enabled.
    domStorageComboBoxPointer->setEnabled(javaScriptEnabled && localStorageEnabled);
}

void DomainSettingsDialog::userAgentChanged(const QString &updatedUserAgent) const
{
    // Update the domains table model.
    domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::USER_AGENT)),
                                      userAgentHelperPointer->getDatabaseUserAgentNameFromTranslatedName(updatedUserAgent));

    // Populate the user agent label.
    populateUserAgentLabel(updatedUserAgent);

    // Update the buttons.
    updateButtons();
}

void DomainSettingsDialog::zoomFactorComboBoxChanged(const int newIndex) const
{
    // Get the current model index.
    QModelIndex modelIndex = domainsSelectionModelPointer->currentIndex();

    // Update the domains table model.
    domainsTableModelPointer->setData(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::ZOOM_FACTOR)), newIndex);

    // Populate the custom zoom factor spin box according to the zoom factor combo box.
    if (newIndex == 0)  // System default zoom factor is selected.
    {
        // Display the default zoom factor.
        customZoomFactorSpinBoxPointer->setValue(Settings::zoomFactor());

        // Reset the palette.
        zoomFactorWidgetPointer->setPalette(defaultPalette);
    }
    else  // Custom zoom factor is selected.
    {
        // Display the custom zoom factor from the domain settings.
        customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::CUSTOM_ZOOM_FACTOR)).data().toDouble());

        // Use the highlighted palette.
        zoomFactorWidgetPointer->setPalette(highlightedPalette);
    }

    // Update the status of the custom zoom factor spin box.
    customZoomFactorSpinBoxPointer->setEnabled(newIndex);

    // Update the buttons.
    updateButtons();
}
