/* 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 "DevToolsWebEngineView.h"
#include "TabWidget.h"
#include "Settings.h"
#include "ui_AddTabWidget.h"
#include "ui_TabWidget.h"
#include "databases/CookiesDatabase.h"
#include "dialogs/SaveDialog.h"
#include "filters/MouseEventFilter.h"
#include "helpers/SearchEngineHelper.h"
#include "windows/BrowserWindow.h"

// KDE Framework headers.
#include <KIO/FileCopyJob>
#include <KIO/JobUiDelegate>
#include <KNotification>

// Qt toolkit headers.
#include <QAction>
#include <QFileDialog>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QMessageBox>
#include <QPrintDialog>
#include <QPrintPreviewDialog>
#include <QPrinter>

// Initialize the public static variables.
QString TabWidget::webEngineDefaultUserAgent = QLatin1String("");

// Construct the class.
TabWidget::TabWidget(QWidget *windowPointer) : QWidget(windowPointer)
{
    // Create a QProcess to check if KDE is running.
    QProcess *checkIfRunningKdeQProcessPointer = new QProcess();

    // Create an argument string list that contains `ksmserver` (KDE Session Manager).
    QStringList argument = QStringList(QLatin1String("ksmserver"));

    // Run `pidof` to check for the presence of `ksmserver`.
    checkIfRunningKdeQProcessPointer->start(QLatin1String("pidof"), argument);

    // Monitor any standard output.
    connect(checkIfRunningKdeQProcessPointer, &QProcess::readyReadStandardOutput, [this]
    {
        // If there is any standard output, `ksmserver` is running.
        isRunningKde = true;
    });

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

    // Instantiate the UIs.
    Ui::TabWidget tabWidgetUi;
    Ui::AddTabWidget addTabWidgetUi;

    // Setup the main UI.
    tabWidgetUi.setupUi(this);

    // Get a handle for the tab widget.
    qTabWidgetPointer = tabWidgetUi.tabWidget;

    // Setup the add tab UI.
    addTabWidgetUi.setupUi(qTabWidgetPointer);

    // Get handles for the add tab widgets.
    QWidget *addTabWidgetPointer = addTabWidgetUi.addTabQWidget;
    QPushButton *addTabButtonPointer = addTabWidgetUi.addTabButton;

    // Display the add tab widget.
    qTabWidgetPointer->setCornerWidget(addTabWidgetPointer);

    // Create the loading favorite icon movie.
    loadingFavoriteIconMoviePointer = new QMovie();

    // Set the loading favorite icon movie file name.
    loadingFavoriteIconMoviePointer->setFileName(QStringLiteral(":/icons/loading.gif"));

    // Stop the loading favorite icon movie if the window is destroyed.  Otherwise, the app will crash if there is more than one window open and a window is closed while at tab is loading.
    connect(windowPointer, SIGNAL(destroyed()), this, SLOT(stopLoadingFavoriteIconMovie()));

    // Add the first tab.
    addFirstTab();

    // Process tab events.
    connect(qTabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
    connect(addTabButtonPointer, SIGNAL(clicked()), this, SLOT(addTab()));
    connect(qTabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));

    // Store a copy of the WebEngine default user agent.
    webEngineDefaultUserAgent = currentWebEngineProfilePointer->httpUserAgent();

    // Instantiate the mouse event filter pointer.
    MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();

    // Install the mouse event filter.
    qApp->installEventFilter(mouseEventFilterPointer);

    // Process mouse forward and back commands.
    connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
    connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
}

TabWidget::~TabWidget()
{
    // Get the number of tabs.
    int numberOfTabs = qTabWidgetPointer->count();

    // Manually delete each WebEngine page.
    for (int i = 0; i < numberOfTabs; ++i)
    {
        // Get the tab splitter widget.
        QWidget *tabSplitterWidgetPointer = qTabWidgetPointer->widget(i);

        // Get the WebEngine views.
        PrivacyWebEngineView *privacyWebEngineViewPointer = tabSplitterWidgetPointer->findChild<PrivacyWebEngineView *>();
        DevToolsWebEngineView *devToolsWebEngineViewPointer = tabSplitterWidgetPointer->findChild<DevToolsWebEngineView *>();

        // Deletion the WebEngine pages to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
        delete privacyWebEngineViewPointer->page();
        delete devToolsWebEngineViewPointer->page();
    }
}

// The cookie is copied instead of referenced so that changes made to the cookie do not create a race condition with the display of the cookie in the dialog.
void TabWidget::addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *webEngineCookieStorePointer) const
{
    // Create a URL.
    QUrl url;

    // Check to see if the domain does not start with a `.` because Qt makes this harder than it should be.  <https://doc.qt.io/qt-5/qwebenginecookiestore.html#setCookie>
    if (!cookie.domain().startsWith(QLatin1String(".")))
    {
        // Populate the URL.
        url.setHost(cookie.domain());
        url.setScheme(QLatin1String("https"));

        // Clear the domain from the cookie.
        cookie.setDomain(QLatin1String(""));
    }

    // Add the cookie to the store.
    if (webEngineCookieStorePointer == nullptr)
        currentWebEngineCookieStorePointer->setCookie(cookie, url);
    else
        webEngineCookieStorePointer->setCookie(cookie, url);
}

void TabWidget::addFirstTab()
{
    // Create the first tab.
    addTab();

    // Update the UI with the tab settings.
    updateUiWithTabSettings();

    // Set the focus on the current tab widget.  This prevents the tab bar from showing a blue bar under the label of the first tab.
    qTabWidgetPointer->currentWidget()->setFocus();
}

PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool adjacent, const bool backgroundTab, const QString urlString)
{
    // Create a splitter widget.
    QSplitter *splitterPointer = new QSplitter();

    // Set the splitter to be vertical.
    splitterPointer->setOrientation(Qt::Vertical);

    // Set the splitter handle size.
    splitterPointer->setHandleWidth(5);

    // Create the WebEngines.
    PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView();
    DevToolsWebEngineView *devToolsWebEngineViewPointer = new DevToolsWebEngineView();

    // Add the WebEngines to the splitter.
    splitterPointer->addWidget(privacyWebEngineViewPointer);
    splitterPointer->addWidget(devToolsWebEngineViewPointer);

    // Initialize the new tab index.
    int newTabIndex = 0;

    // Add a new tab.
    if (adjacent)  // Add the new tab adjacent to the current tab.
        newTabIndex = qTabWidgetPointer->insertTab((qTabWidgetPointer->currentIndex() + 1), splitterPointer, i18nc("New tab label.", "New Tab"));
    else  // Add the new tab at the end of the list.
        newTabIndex = qTabWidgetPointer->addTab(splitterPointer, i18nc("New tab label.", "New Tab"));

    // Set the default tab icon.
    qTabWidgetPointer->setTabIcon(newTabIndex, defaultFavoriteIcon);

    // Get handles for the WebEngine components.
    QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
    QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
    QWebEngineCookieStore *webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
    QWebEngineSettings *webEngineSettingsPointer = webEnginePagePointer->settings();

    // Set the development tools WebEngine.
    webEnginePagePointer->setDevToolsPage(devToolsWebEngineViewPointer->page());

    // Initially hide the development tools WebEngine.
    devToolsWebEngineViewPointer->setVisible(false);

    // Initially disable the development tools WebEngine.
    webEnginePagePointer->setDevToolsPage(nullptr);

    // Disable JavaScript on the development tools WebEngine to prevent error messages from being written to the console.
    devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);

    // Update the URL line edit when the URL changes.
    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::urlChanged, [this, privacyWebEngineViewPointer] (const QUrl &newUrl)
    {
        // Only update the UI if this is the current tab.
        if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
        {
            // Update the URL line edit.
            Q_EMIT updateUrlLineEdit(newUrl);

            // Update the status of the forward and back buttons.
            Q_EMIT updateBackAction(currentWebEngineHistoryPointer->canGoBack());
            Q_EMIT updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
        }
    });

    // Update the title when it changes.
    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, splitterPointer] (const QString &title)
    {
        // Get the index for this tab.
        int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);

        // Update the title for this tab.
        qTabWidgetPointer->setTabText(tabIndex, title);

        // Update the window title if this is the current tab.
        if (tabIndex == qTabWidgetPointer->currentIndex())
            Q_EMIT updateWindowTitle(title);
    });

    // Connect the loading favorite icon movie to the tab icon.
    connect(loadingFavoriteIconMoviePointer, &QMovie::frameChanged, [this, splitterPointer, privacyWebEngineViewPointer]
    {
        // Get the index for this tab.
        int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);

        // Display the loading favorite icon if this tab is loading.
        if (privacyWebEngineViewPointer->isLoading)
            qTabWidgetPointer->setTabIcon(tabIndex, loadingFavoriteIconMoviePointer->currentPixmap());
    });

    // Update the icon when it changes.
    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [this, splitterPointer, privacyWebEngineViewPointer] (const QIcon &newFavoriteIcon)
    {
        // Store the favorite icon in the privacy web engine view.
        if (newFavoriteIcon.isNull())
            privacyWebEngineViewPointer->favoriteIcon = defaultFavoriteIcon;
        else
            privacyWebEngineViewPointer->favoriteIcon = newFavoriteIcon;

        // Get the index for this tab.
        int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);

        // Update the icon for this tab.
        if (newFavoriteIcon.isNull())
            qTabWidgetPointer->setTabIcon(tabIndex, defaultFavoriteIcon);
        else
            qTabWidgetPointer->setTabIcon(tabIndex, newFavoriteIcon);
    });

    // Update the progress bar and the favorite icon when a load is started.
    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadStarted, [this, privacyWebEngineViewPointer] ()
    {
        // Set the privacy web engine view to be loading.
        privacyWebEngineViewPointer->isLoading = true;

        // Store the load progress.
        privacyWebEngineViewPointer->loadProgressInt = 0;

        // Show the progress bar if this is the current tab.
        if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
            Q_EMIT showProgressBar(0);

        // Start the loading favorite icon movie.
        loadingFavoriteIconMoviePointer->start();
    });

    // Update the progress bar when a load progresses.
    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadProgress, [this, privacyWebEngineViewPointer] (const int progress)
    {
        // Store the load progress.
        privacyWebEngineViewPointer->loadProgressInt = progress;

        // Update the progress bar if this is the current tab.
        if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
            Q_EMIT showProgressBar(progress);
    });

    // Update the progress bar when a load finishes.
    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [this, splitterPointer, privacyWebEngineViewPointer] ()
    {
        // Set the privacy web engine view to be not loading.
        privacyWebEngineViewPointer->isLoading = false;

        // Store the load progress.
        privacyWebEngineViewPointer->loadProgressInt = -1;

        // Hide the progress bar if this is the current tab.
        if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
            Q_EMIT hideProgressBar();

        // Get the index for this tab.
        int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);

        // Display the current favorite icon
        qTabWidgetPointer->setTabIcon(tabIndex, privacyWebEngineViewPointer->favoriteIcon);

        // Create a no tabs loading variable.
        bool noTabsLoading = true;

        // Get the number of tabs.
        int numberOfTabs = qTabWidgetPointer->count();

        // Check to see if any other tabs are loading.
        for (int i = 0; i < numberOfTabs; i++)
        {
            // Get the privacy WebEngine view for the tab.
            PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();

            // Check to see if it is currently loading.  If at least one tab is loading, this flag will end up being marked `false` when the for loop has finished.
            if (privacyWebEngineViewPointer->isLoading)
                noTabsLoading = false;
        }

        // Stop the loading favorite icon movie if there are no loading tabs.
        if (noTabsLoading)
            loadingFavoriteIconMoviePointer->stop();
    });

    // Display HTTP Ping blocked dialogs.
    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::displayHttpPingBlockedDialog, [this, privacyWebEngineViewPointer] (const QString &httpPingUrl)
    {
        // Only display the HTTP Ping blocked dialog if this is the current tab.
        if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
        {
            // Instantiate an HTTP ping blocked message box.
            QMessageBox httpPingBlockedMessageBox;

            // Set the icon.
            httpPingBlockedMessageBox.setIcon(QMessageBox::Information);

            // Set the window title.
            httpPingBlockedMessageBox.setWindowTitle(i18nc("HTTP Ping blocked dialog title", "HTTP Ping Blocked"));

            // Set the text.
            httpPingBlockedMessageBox.setText(i18nc("HTTP Ping blocked dialog text", "This request has been blocked because it sends a naughty HTTP ping to %1.", httpPingUrl));

            // Set the standard button.
            httpPingBlockedMessageBox.setStandardButtons(QMessageBox::Ok);

            // Display the message box.
            httpPingBlockedMessageBox.exec();
        }
    });

    // Update the zoom actions when they are changed.
    connect(webEnginePagePointer, &QWebEnginePage::zoomFactorChanged, [webEnginePagePointer, this] (const qreal newZoomFactor)
    {
        // Only update the zoom actions if this is the current tab.
        if (webEnginePagePointer == currentWebEnginePagePointer)
            Q_EMIT updateZoomActions(newZoomFactor);
    });

    // Display find text results.
    connect(webEnginePagePointer, SIGNAL(findTextFinished(const QWebEngineFindTextResult &)), this, SLOT(findTextFinished(const QWebEngineFindTextResult &)));

    // Handle full screen requests.
    connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));

    // Listen for hovered link URLs.
    connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));

    // Handle file downloads.
    connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadRequest *)), this, SLOT(showSaveDialog(QWebEngineDownloadRequest *)));

    // Set the local storage filter.
    webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
    {
        // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
        if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QUrl(QLatin1String(""))))
        {
            //qDebug().noquote().nospace() << "Third-party request blocked:  " << filterRequest.origin;

            // Return false.
            return false;
        }

        // Allow the request if local storage is enabled.
        if (privacyWebEngineViewPointer->localStorageEnabled)
        {
            //qDebug().noquote().nospace() << "Request allowed by local storage:  " << filterRequest.origin;

            // Return true.
            return true;
        }

        //qDebug().noquote().nospace() << "Request blocked by default:  " << filterRequest.origin;

        // Block any remaining local storage requests.
        return false;
    });

    // Disable JavaScript by default (this prevents JavaScript from being enabled on a new tab before domain settings are loaded).
    webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);

    // Don't allow JavaScript to open windows.
    webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);

    // Allow keyboard navigation between links and input fields.
    webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, Settings::spatialNavigation());

    // Enable full screen support.
    webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);

    // Require user interaction to play media.
    webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);

    // Limit WebRTC to public IP addresses.
    webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);

    // Enable the PDF viewer (it should be enabled by default, but it is nice to be explicit in case the defaults change).
    webEngineSettingsPointer->setAttribute(QWebEngineSettings::PdfViewerEnabled, true);

    // Plugins must be enabled for the PDF viewer to work.  <https://doc.qt.io/qt-5/qtwebengine-features.html#pdf-file-viewing>
    webEngineSettingsPointer->setAttribute(QWebEngineSettings::PluginsEnabled, true);

    // Allow JavaScript to paste to (but not copy from) the clipboard, but only with user interaction.
    webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, true);

    // Update the blocked requests action.
    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::requestBlocked, [this, privacyWebEngineViewPointer] (const QVector<int> blockedRequestsVector)
    {
        // Update the blocked requests action if the specified privacy WebEngine view is the current privacy WebEngine view.
        if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
            Q_EMIT blockedRequestsUpdated(blockedRequestsVector);
    });

    // Update the cookies action.
    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::numberOfCookiesChanged, [this, privacyWebEngineViewPointer] (const int numberOfCookies)
    {
        // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
        if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
            Q_EMIT cookiesChanged(numberOfCookies);
    });

    // Process cookie changes.
    connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(addCookieToList(QNetworkCookie)));
    connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(removeCookieFromList(QNetworkCookie)));

    // Get a list of durable cookies.
    QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();

    // Add the durable cookies to the store.
    for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
        addCookieToStore(*cookiePointer, webEngineCookieStorePointer);

    // Enable spell checking.
    webEngineProfilePointer->setSpellCheckEnabled(true);

    // Set the spell check language.
    webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());

    // Populate the zoom factor.  This is necessary if a URL is being loaded, like a local URL, that does not trigger `applyDomainSettings()`.
    privacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());

    // Update the UI when domain settings are applied.
    connect(privacyWebEngineViewPointer, SIGNAL(updateUi(const PrivacyWebEngineView*)), this, SLOT(updateUiFromWebEngineView(const PrivacyWebEngineView*)));

    // Move to the new tab if it is not a background tab.
    if (!backgroundTab)
        qTabWidgetPointer->setCurrentIndex(newTabIndex);

    // Clear the URL line edit focus so that it populates correctly when opening a new tab from the context menu.
    if (removeUrlLineEditFocus)
        Q_EMIT clearUrlLineEditFocus();

    // Load the URL if it isn't blank.
    if (urlString != QLatin1String(""))
        privacyWebEngineViewPointer->load(QUrl::fromUserInput(urlString));

    // Return the privacy WebEngine view pointer.
    return privacyWebEngineViewPointer;
}

void TabWidget::applyApplicationSettings()
{
    // Set the tab position.
    if (Settings::tabsOnTop())
        qTabWidgetPointer->setTabPosition(QTabWidget::North);
    else
        qTabWidgetPointer->setTabPosition(QTabWidget::South);

    // Get the number of tabs.
    int numberOfTabs = qTabWidgetPointer->count();

    // Apply the spatial navigation settings to each WebEngine.
    for (int i = 0; i < numberOfTabs; ++i) {
        // Get the WebEngine view pointer.
        PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();

        // Apply the spatial navigation settings to each page.
        privacyWebEngineViewPointer->page()->settings()->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, Settings::spatialNavigation());
    }

    // Set the search engine URL.
    searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());

    // Emit the update search engine actions signal.
    Q_EMIT updateSearchEngineActions(Settings::searchEngine(), true);
}

void TabWidget::applyDomainSettingsAndReload()
{
    // Get the number of tabs.
    int numberOfTabs = qTabWidgetPointer->count();

    // Apply the domain settings to each WebEngine.
    for (int i = 0; i < numberOfTabs; ++i) {
        // Get the WebEngine view pointer.
        PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();

        // Apply the domain settings settings to each page.  `false` indicates that history is not being navigated.
        privacyWebEngineViewPointer->applyDomainSettings(privacyWebEngineViewPointer->url(), false);
    }
}

void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
{
    // Store the search engine name.
    QString searchEngineName = searchEngineActionPointer->text();

    // Strip out any `&` characters.
    searchEngineName.remove(QLatin1Char('&'));

    // Store the search engine string.
    searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);

    // Update the search engine actions.
    Q_EMIT updateSearchEngineActions(searchEngineName, false);
}

void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
{
    // Get the user agent name.
    QString userAgentName = userAgentActionPointer->text();

    // Strip out any `&` characters.
    userAgentName.remove(QLatin1Char('&'));

    // Apply the user agent.
    currentWebEngineProfilePointer->setHttpUserAgent(userAgentHelperPointer->getUserAgentFromTranslatedName(userAgentName));

    // Update the user agent actions.
    Q_EMIT updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);

    // Reload the website.
    currentPrivacyWebEngineViewPointer->reload();
}

void TabWidget::applyOnTheFlyZoomFactor(const double zoomFactorDouble) const
{
    // Set the zoom factor.
    currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactorDouble);
}

void TabWidget::applySpellCheckLanguages() const
{
    // Get the number of tab.
    int numberOfTabs = qTabWidgetPointer->count();

    // Set the spell check languages for each tab.
    for (int i = 0; i < numberOfTabs; ++i)
    {
        // Get the WebEngine view pointer.
        PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();

        // Get the WebEngine page pointer.
        QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();

        // Get the WebEngine profile pointer.
        QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();

        // Set the spell check languages.
        webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
    }
}

void TabWidget::back() const
{
    // Go back.
    currentPrivacyWebEngineViewPointer->back();
}

void TabWidget::deleteAllCookies() const
{
    // Delete all the cookies.
    currentWebEngineCookieStorePointer->deleteAllCookies();
}

void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
{
    // Delete the cookie.
    currentWebEngineCookieStorePointer->deleteCookie(cookie);
}

void TabWidget::deleteTab(const int tabIndex)
{
    // Get the tab splitter widget.
    QWidget *tabSplitterWidgetPointer = qTabWidgetPointer->widget(tabIndex);

    // Get the WebEngine views.
    PrivacyWebEngineView *privacyWebEngineViewPointer = tabSplitterWidgetPointer->findChild<PrivacyWebEngineView *>();
    DevToolsWebEngineView *devToolsWebEngineViewPointer = tabSplitterWidgetPointer->findChild<DevToolsWebEngineView *>();

    // Process the tab delete according to the number of tabs.
    if (qTabWidgetPointer->count() > 1)  // There is more than one tab.
    {
        // Remove the tab.
        qTabWidgetPointer->removeTab(tabIndex);

        // Delete the WebEngine pages to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
        delete privacyWebEngineViewPointer->page();
        delete devToolsWebEngineViewPointer->page();

        // Delete the WebEngine views.
        delete privacyWebEngineViewPointer;
        delete devToolsWebEngineViewPointer;

        // Delete the tab splitter widget.
        delete tabSplitterWidgetPointer;
    }
    else  // There is only one tab.
    {
        // Close Privacy Browser.
        window()->close();
    }
}

void TabWidget::findPrevious(const QString &text) const
{
    // Store the current text.
    currentPrivacyWebEngineViewPointer->findString = text;

    // Find the previous text in the current privacy WebEngine.
    if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
        currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively|QWebEnginePage::FindBackward);
    else
        currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindBackward);
}

void TabWidget::findText(const QString &text) const
{
    // Store the current text.
    currentPrivacyWebEngineViewPointer->findString = text;

    // Find the text in the current privacy WebEngine.
    if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
        currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively);
    else
        currentPrivacyWebEngineViewPointer->findText(text);

    // Clear the currently selected text in the WebEngine page if the find text is empty.
    if (text.isEmpty())
        currentWebEnginePagePointer->action(QWebEnginePage::Unselect)->activate(QAction::Trigger);
}

void TabWidget::findTextFinished(const QWebEngineFindTextResult &findTextResult)
{
    // Update the find text UI if it wasn't simply wiping the current find text selection.  Otherwise the UI temporarily flashes `0/0`.
    if (wipingCurrentFindTextSelection)  // The current selection is being wiped.
    {
        // Reset the flag.
        wipingCurrentFindTextSelection = false;
    }
    else  // A new search has been performed.
    {
        // Store the result.
        currentPrivacyWebEngineViewPointer->findTextResult = findTextResult;

        // Update the UI.
        Q_EMIT updateFindTextResults(findTextResult);
    }
}

void TabWidget::forward() const
{
    // Go forward.
    currentPrivacyWebEngineViewPointer->forward();
}

void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
{
    // Make it so.
    Q_EMIT fullScreenRequested(fullScreenRequest.toggleOn());

    // Accept the request.
    fullScreenRequest.accept();
}

std::list<QNetworkCookie>* TabWidget::getCookieList() const
{
    // Return the current cookie list.
    return currentPrivacyWebEngineViewPointer->cookieListPointer;
}

QIcon TabWidget::getCurrentTabFavoritIcon() const
{
    // Return the current Privacy WebEngine favorite icon.
    return currentPrivacyWebEngineViewPointer->favoriteIcon;
}

QString TabWidget::getCurrentTabTitle() const
{
    // Return the current Privacy WebEngine title.
    return currentPrivacyWebEngineViewPointer->title();
}

QString TabWidget::getCurrentTabUrl() const
{
    // Return the current Privacy WebEngine URL as a string.
    return currentPrivacyWebEngineViewPointer->url().toString();
}

QString TabWidget::getCurrentUserAgent() const
{
    // Return the current WebEngine user agent.
    return currentWebEngineProfilePointer->httpUserAgent();
}

QString& TabWidget::getDomainSettingsName() const
{
    // Return the domain settings name.
    return currentPrivacyWebEngineViewPointer->domainSettingsName;
}

void TabWidget::home() const
{
    // Load the homepage.
    currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
}

PrivacyWebEngineView* TabWidget::loadBlankInitialWebsite()
{
    // Apply the application settings.
    applyApplicationSettings();

    // Return the current privacy WebEngine view pointer.
    return currentPrivacyWebEngineViewPointer;
}

void TabWidget::loadInitialWebsite()
{
    // Apply the application settings.
    applyApplicationSettings();

    // Get the arguments.
    QStringList argumentsStringList = qApp->arguments();

    // Check to see if the arguments lists contains a URL.
    if (argumentsStringList.size() > 1)
    {
        // Load the URL from the arguments list.
        currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
    }
    else
    {
        // Load the homepage.
        home();
    }
}

void TabWidget::loadUrlFromLineEdit(QString url) const
{
    // Decide if the text is more likely to be a URL or a search.
    if (url.startsWith(QLatin1String("file://")) || url.startsWith(QLatin1String("view-source:")))  // The text is likely a file or view source URL.
    {
        // Load the URL.
        currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
    }
    else if (url.contains(QLatin1String(".")))  // The text is likely a URL.
    {
        // Check if the URL does not start with a valid protocol.
        if (!url.startsWith(QLatin1String("http")))
        {
            // Add `https://` to the beginning of the URL.
            url = QLatin1String("https://") + url;
        }

        // Load the URL.
        currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
    }
    else  // The text is likely a search.
    {
        // Load the search.
        currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
    }
}

void TabWidget::mouseBack() const
{
    // Go back if possible.
    if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
    {
        // Clear the URL line edit focus.
        Q_EMIT clearUrlLineEditFocus();

        // Go back.
        currentPrivacyWebEngineViewPointer->back();
    }
}

void TabWidget::mouseForward() const
{
    // Go forward if possible.
    if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
    {
        // Clear the URL line edit focus.
        Q_EMIT clearUrlLineEditFocus();

        // Go forward.
        currentPrivacyWebEngineViewPointer->forward();
    }
}

void TabWidget::pageLinkHovered(const QString &linkUrl) const
{
    // Emit a signal so that the browser window can update the status bar.
    Q_EMIT linkHovered(linkUrl);
}

void TabWidget::stopLoadingFavoriteIconMovie() const
{
    // Stop the loading favorite icon movie.  Otherwise, the browser will crash if a second window is closed while a tab in it is loading.  <https://redmine.stoutner.com/issues/1010>
    loadingFavoriteIconMoviePointer->stop();
}

void TabWidget::print() const
{
    // Create a printer.
    QPrinter *printerPointer = new QPrinter();

    // Set the resolution to be 300 dpi.
    printerPointer->setResolution(300);

    // Create a printer dialog.
    QPrintDialog printDialog(printerPointer, currentPrivacyWebEngineViewPointer);

    // Display the dialog and print the page if instructed.
    if (printDialog.exec() == QDialog::Accepted)
        currentPrivacyWebEngineViewPointer->print(printerPointer);
}

void TabWidget::printPreview() const
{
    // Create a printer.
    QPrinter *printerPointer = new QPrinter();

    // Set the resolution to be 300 dpi.
    printerPointer->setResolution(300);

    // Create a print preview dialog.
    QPrintPreviewDialog printPreviewDialog(printerPointer, currentPrivacyWebEngineViewPointer);

    // Generate the print preview.
    auto generatePrintPreview = [this, printerPointer](){
        // Create an event loop.  The print preview must be generated in a loop or nothing is displayed.
        QEventLoop eventLoop;

        // Quit the event loop once the print preview has been generated.
        connect(currentPrivacyWebEngineViewPointer, &QWebEngineView::printFinished, &eventLoop, &QEventLoop::quit);

        // Generate the print preview for the current webpage.
        currentPrivacyWebEngineViewPointer->print(printerPointer);

        // Execute the loop.
        eventLoop.exec();
    };

    // Generate the preview.
    connect(&printPreviewDialog, &QPrintPreviewDialog::paintRequested, this, generatePrintPreview);

    // Display the dialog.
    printPreviewDialog.exec();
}

void TabWidget::refresh() const
{
    // Reset the HTTP authentication dialog counter.
    currentPrivacyWebEngineViewPointer->httpAuthenticationDialogsDisplayed = 0;

    // Reload the website.
    currentPrivacyWebEngineViewPointer->reload();
}

void TabWidget::reloadAndBypassCache() const
{
    // Reload the website, bypassing the cache.
    currentWebEnginePagePointer->triggerAction(QWebEnginePage::ReloadAndBypassCache);
}

void TabWidget::saveArchive()
{
    // Get the suggested file name.
    QString suggestedFileName = currentPrivacyWebEngineViewPointer->title() + QLatin1String(".mht");

    // Get the download directory.
    QString downloadDirectory = Settings::downloadDirectory();

    // Resolve the system download directory if specified.
    if (downloadDirectory == QLatin1String("System Download Directory"))
        downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);

    // Get a file path from the file picker.
    QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);

    // Save the webpage as an archive if the file save path is populated.
    if (!saveFilePath.isEmpty())
    {
        // Update the download directory if specified.
        if (Settings::autoUpateDownloadDirectory())
            updateDownloadDirectory(saveFilePath);

        // Set the saving archive flag.  Otherwise, a second download tries to run.
        savingArchive = true;

        // Save the archive.
        currentWebEnginePagePointer->save(saveFilePath);
    }
}

void TabWidget::setTabBarVisible(const bool visible) const
{
    // Set the tab bar visibility.
    qTabWidgetPointer->tabBar()->setVisible(visible);
}

void TabWidget::showSaveDialog(QWebEngineDownloadRequest *webEngineDownloadRequestPointer)
{
    // Only show the save dialog if an archive is not currently being saved.  Otherwise, two save dialogs will be shown.
    if (!savingArchive)
    {
        // Get the download attributes.
        QUrl downloadUrl = webEngineDownloadRequestPointer->url();
        QString mimeTypeString = webEngineDownloadRequestPointer->mimeType();
        QString suggestedFileName = webEngineDownloadRequestPointer->suggestedFileName();
        int totalBytes = webEngineDownloadRequestPointer->totalBytes();

        // Check to see if Privacy Browser is not running KDE or if local storage (cookies) is enabled.
        if (!isRunningKde || currentPrivacyWebEngineViewPointer->localStorageEnabled)  // KDE is not running or local storage (cookies) is enabled.  Use WebEngine's downloader.
        {
            // Instantiate the save dialog.
            SaveDialog *saveDialogPointer = new SaveDialog(this, downloadUrl, mimeTypeString, totalBytes);

            // Display the save dialog.
            int saveDialogResult = saveDialogPointer->exec();

            // Process the save dialog results.
            if (saveDialogResult == QDialog::Accepted)  // Save was selected.
            {
                // Get the download directory.
                QString downloadDirectory = Settings::downloadDirectory();

                // Resolve the system download directory if specified.
                if (downloadDirectory == QLatin1String("System Download Directory"))
                    downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);

                // Get a file path from the file picker.
                QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);

                // Process the save file path.
                if (!saveFilePath.isEmpty())  // The file save path is populated.
                {
                    // Update the download directory if specified.
                    if (Settings::autoUpateDownloadDirectory())
                        updateDownloadDirectory(saveFilePath);

                    // Create a save file path file info.
                    QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath);

                    // Get the canonical save path and file name.
                    QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
                    QString saveFileName = saveFilePathFileInfo.fileName();

                    // Set the download directory and file name.
                    webEngineDownloadRequestPointer->setDownloadDirectory(absoluteSavePath);
                    webEngineDownloadRequestPointer->setDownloadFileName(saveFileName);

                    // Create a file download notification.
                    KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));

                    // Set the notification title.
                    fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));

                    // Set the notification text.
                    fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));

                    // Get the download icon from the theme.
                    QIcon downloadIcon = QIcon::fromTheme(QLatin1String("download"), QIcon::fromTheme(QLatin1String("document-save")));

                    // Set the notification icon.
                    fileDownloadNotificationPointer->setIconName(downloadIcon.name());

                    // Add the cancel action.
                    KNotificationAction *cancelActionPointer = fileDownloadNotificationPointer->addDefaultAction(i18nc("Download notification action","Cancel"));

                    // Prevent the notification from being autodeleted if it is closed.  Otherwise, the updates to the notification below cause a crash.
                    fileDownloadNotificationPointer->setAutoDelete(false);

                    // Handle clicks on the cancel action.
                    connect(cancelActionPointer, &KNotificationAction::activated, [webEngineDownloadRequestPointer, saveFileName] ()
                    {
                        // Cancel the download.
                        webEngineDownloadRequestPointer->cancel();

                        // Create a file download notification.
                        KNotification *canceledDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));

                        // Set the notification title.
                        canceledDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));

                        // Set the new text.
                        canceledDownloadNotificationPointer->setText(i18nc("Download canceled notification", "%1 download canceled", saveFileName));

                        // Set the notification icon.
                        canceledDownloadNotificationPointer->setIconName(QLatin1String("download"));

                        // Display the notification.
                        canceledDownloadNotificationPointer->sendEvent();
                    });

                    // Update the notification when the download progresses.
                    connect(webEngineDownloadRequestPointer, &QWebEngineDownloadRequest::receivedBytesChanged, [webEngineDownloadRequestPointer, fileDownloadNotificationPointer, saveFileName] ()
                    {
                        // Get the download request information.
                        qint64 receivedBytes = webEngineDownloadRequestPointer->receivedBytes();
                        qint64 totalBytes = webEngineDownloadRequestPointer->totalBytes();

                        // Set the new text.  Total bytes will be 0 if the download size is unknown.
                        if (totalBytes > 0)
                        {
                            // Calculate the download percentage.
                            int downloadPercentage = 100 * receivedBytes / totalBytes;

                            // Set the file download notification text.
                            fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1%% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
                                                                           receivedBytes, totalBytes));
                        }
                        else
                        {
                            // Set the file download notification text.
                            fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1:  %2 bytes downloaded", saveFileName, receivedBytes));
                        }

                        // Display the updated notification.
                        fileDownloadNotificationPointer->sendEvent();
                    });

                    // Update the notification when the download finishes.  The save file name must be copied into the lambda or a crash occurs.
                    connect(webEngineDownloadRequestPointer, &QWebEngineDownloadRequest::isFinishedChanged, [webEngineDownloadRequestPointer, fileDownloadNotificationPointer, saveFileName,
                            saveFilePath] ()
                    {
                        // Update the notification if the download is finished.
                        if (webEngineDownloadRequestPointer->isFinished())
                        {
                            // Set the new text.
                            fileDownloadNotificationPointer->setText(i18nc("Download finished notification text", "%1 download finished", saveFileName));

                            // Set the URL so the file options will be displayed.
                            fileDownloadNotificationPointer->setUrls(QList<QUrl> {QUrl(saveFilePath)});

                            // Remove the actions from the notification.
                            fileDownloadNotificationPointer->clearActions();

                            // Set the notification to disappear after a timeout.
                            fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout);

                            // Display the updated notification.
                            fileDownloadNotificationPointer->sendEvent();
                        }
                    });

                    // Display the notification.
                    fileDownloadNotificationPointer->sendEvent();

                    // Start the download.
                    webEngineDownloadRequestPointer->accept();
                }
                else  // The file save path is not populated.
                {
                    // Cancel the download.
                    webEngineDownloadRequestPointer->cancel();
                }
            }
            else  // Cancel was selected.
            {
                // Cancel the download.
                webEngineDownloadRequestPointer->cancel();
            }
        }
        else  // KDE is running and local storage (cookies) is disabled.  Use KDE's native downloader.
            // This must use the show command to launch a separate dialog which cancels WebEngine's automatic background download of the file to a temporary location.
        {
            // Instantiate the save dialog.  `true` instructs it to use the native downloader
            SaveDialog *saveDialogPointer = new SaveDialog(this, downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true);

            // Connect the save button.
            connect(saveDialogPointer, SIGNAL(useNativeKdeDownloader(QUrl &, QString &)), this, SLOT(useNativeKdeDownloader(QUrl &, QString &)));

            // Show the dialog.
            saveDialogPointer->show();
        }
    }

    // Reset the saving archive flag.
    savingArchive = false;
}

void TabWidget::stop() const
{
    // Stop the loading of the current privacy WebEngine.
    currentPrivacyWebEngineViewPointer->stop();
}

void TabWidget::storeCurrentUrlText(const QString &urlText) const
{
    // Store the current URL text in the privacy WebEngine view.
    currentPrivacyWebEngineViewPointer->currentUrlText = urlText;
}

void TabWidget::toggleDeveloperTools(const bool enabled) const
{
    // Get handles for the current tab widgets.
    QSplitter *splitterPointer = qobject_cast<QSplitter*>(qTabWidgetPointer->currentWidget());
    DevToolsWebEngineView *devToolsWebEngineViewPointer = splitterPointer->findChild<DevToolsWebEngineView *>();

    if (enabled)
    {
        // Set the zoom factor on the development tools WebEngine.
        devToolsWebEngineViewPointer->setZoomFactor(currentWebEnginePagePointer->zoomFactor());

        // Enable the development tools.
        currentWebEnginePagePointer->setDevToolsPage(devToolsWebEngineViewPointer->page());

        // Enable JavaScript on the development tools WebEngine.
        devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);

        // Display the developer tools.
        devToolsWebEngineViewPointer->setVisible(true);

        // Split the visible space equally between the main WebEngine and the developer tools WebEngine.
        splitterPointer->setSizes(QList<int>({1, 1}));
    }
    else
    {
        // Disable JavaScript on the development tools WebEngine to prevent error messages from being written to the console.
        devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);

        // Disable the development tools.
        currentWebEnginePagePointer->setDevToolsPage(nullptr);

        // Hide the developer tools.
        devToolsWebEngineViewPointer->setVisible(false);
    }
}

void TabWidget::toggleDomStorage() const
{
    // Toggle DOM storage.
    currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));

    // Update the DOM storage action.
    Q_EMIT updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));

    // Reload the website.
    currentPrivacyWebEngineViewPointer->reload();
}

void TabWidget::toggleEasyList() const
{
    // Toggle EasyList.
    currentPrivacyWebEngineViewPointer->easyListEnabled = !currentPrivacyWebEngineViewPointer->easyListEnabled;

    // Reload the website.
    currentPrivacyWebEngineViewPointer->reload();
}

void TabWidget::toggleEasyPrivacy() const
{
    // Toggle EasyPrivacy.
    currentPrivacyWebEngineViewPointer->easyPrivacyEnabled = !currentPrivacyWebEngineViewPointer->easyPrivacyEnabled;

    // Reload the website.
    currentPrivacyWebEngineViewPointer->reload();
}

void TabWidget::toggleFanboysAnnoyanceList() const
{
    // Toggle Fanboy's Annoyance List.
    currentPrivacyWebEngineViewPointer->fanboysAnnoyanceListEnabled = !currentPrivacyWebEngineViewPointer->fanboysAnnoyanceListEnabled;

    // Reload the website.
    currentPrivacyWebEngineViewPointer->reload();
}

void TabWidget::toggleFindCaseSensitive(const QString &text)
{
    // Toggle find case sensitive.
    currentPrivacyWebEngineViewPointer->findCaseSensitive = !currentPrivacyWebEngineViewPointer->findCaseSensitive;

    // Set the wiping current find text selection flag.
    wipingCurrentFindTextSelection = true;

    // Wipe the previous search.  Otherwise currently highlighted words will remain highlighted.
    findText(QLatin1String(""));

    // Update the find text.
    findText(text);
}

void TabWidget::toggleJavaScript() const
{
    // Toggle JavaScript.
    currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));

    // Update the JavaScript action.
    Q_EMIT updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));

    // Reload the website.
    currentPrivacyWebEngineViewPointer->reload();
}

void TabWidget::toggleLocalStorage()
{
    // Toggle local storage.
    currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;

    // Update the local storage action.
    Q_EMIT updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);

    // Reload the website.
    currentPrivacyWebEngineViewPointer->reload();
}

void TabWidget::toggleUltraList() const
{
    // Toggle UltraList.
    currentPrivacyWebEngineViewPointer->ultraListEnabled = !currentPrivacyWebEngineViewPointer->ultraListEnabled;

    // Reload the website.
    currentPrivacyWebEngineViewPointer->reload();
}

void TabWidget::toggleUltraPrivacy() const
{
    // Toggle UltraPrivacy.
    currentPrivacyWebEngineViewPointer->ultraPrivacyEnabled = !currentPrivacyWebEngineViewPointer->ultraPrivacyEnabled;

    // Reload the website.
    currentPrivacyWebEngineViewPointer->reload();
}

void TabWidget::updateDownloadDirectory(QString newDownloadDirectory) const
{
    // Remove the file name from the save file path.
    newDownloadDirectory.truncate(newDownloadDirectory.lastIndexOf(QLatin1Char('/')));

    // Update the download location.
    Settings::setDownloadDirectory(newDownloadDirectory);

    // Get a handle for the KConfig skeleton.
    KConfigSkeleton *kConfigSkeletonPointer = Settings::self();

    // Write the settings to disk.
    kConfigSkeletonPointer->save();
}

void TabWidget::updateUiFromWebEngineView(const PrivacyWebEngineView *privacyWebEngineViewPointer) const
{
    // Only update the UI if the signal was emitted from the current privacy WebEngine.
    if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
    {
        // Update the UI.
        Q_EMIT easyListStatusChanged(currentPrivacyWebEngineViewPointer->easyListEnabled);
        Q_EMIT easyPrivacyStatusChanged(currentPrivacyWebEngineViewPointer->easyPrivacyEnabled);
        Q_EMIT fanboysAnnoyanceListStatusChanged(currentPrivacyWebEngineViewPointer->fanboysAnnoyanceListEnabled);
        Q_EMIT ultraListStatusChanged(currentPrivacyWebEngineViewPointer->ultraListEnabled);
        Q_EMIT ultraPrivacyStatusChanged(currentPrivacyWebEngineViewPointer->ultraPrivacyEnabled);
        Q_EMIT updateDefaultZoomFactor(currentPrivacyWebEngineViewPointer->defaultZoomFactor);
        Q_EMIT updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
        Q_EMIT updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
        Q_EMIT updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
        Q_EMIT updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
        Q_EMIT updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
        Q_EMIT updateZoomActions(currentPrivacyWebEngineViewPointer->zoomFactor());
    }
}

void TabWidget::updateUiWithTabSettings()
{
    // Clear the URL line edit focus.
    Q_EMIT clearUrlLineEditFocus();

    // Update the current WebEngine pointers.
    currentPrivacyWebEngineViewPointer = qTabWidgetPointer->currentWidget()->findChild<PrivacyWebEngineView *>();
    currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
    currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
    currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
    currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
    currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();

    // Get a handle for the development tools WebEngine view.
    DevToolsWebEngineView *devToolsWebEngineViewPointer = qTabWidgetPointer->currentWidget()->findChild<DevToolsWebEngineView *>();

    // Update the actions.
    Q_EMIT easyListStatusChanged(currentPrivacyWebEngineViewPointer->easyListEnabled);
    Q_EMIT easyPrivacyStatusChanged(currentPrivacyWebEngineViewPointer->easyPrivacyEnabled);
    Q_EMIT fanboysAnnoyanceListStatusChanged(currentPrivacyWebEngineViewPointer->fanboysAnnoyanceListEnabled);
    Q_EMIT ultraListStatusChanged(currentPrivacyWebEngineViewPointer->ultraListEnabled);
    Q_EMIT ultraPrivacyStatusChanged(currentPrivacyWebEngineViewPointer->ultraPrivacyEnabled);
    Q_EMIT blockedRequestsUpdated(currentPrivacyWebEngineViewPointer->blockedRequestsVector);
    Q_EMIT cookiesChanged(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
    Q_EMIT updateBackAction(currentWebEngineHistoryPointer->canGoBack());
    Q_EMIT updateDefaultZoomFactor(currentPrivacyWebEngineViewPointer->defaultZoomFactor);
    Q_EMIT updateDeveloperToolsAction(devToolsWebEngineViewPointer->isVisible());
    Q_EMIT updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
    Q_EMIT updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
    Q_EMIT updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
    Q_EMIT updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
    Q_EMIT updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
    Q_EMIT updateZoomActions(currentPrivacyWebEngineViewPointer->zoomFactor());

    // Update the find text.
    Q_EMIT updateFindText(currentPrivacyWebEngineViewPointer->findString, currentPrivacyWebEngineViewPointer->findCaseSensitive);
    Q_EMIT updateFindTextResults(currentPrivacyWebEngineViewPointer->findTextResult);

    // Update the progress bar.
    if (currentPrivacyWebEngineViewPointer->loadProgressInt >= 0)
        Q_EMIT showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt);
    else
        Q_EMIT hideProgressBar();

    // Update the URL.
    Q_EMIT updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
    Q_EMIT updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
    Q_EMIT updateUrlLineEdit(QUrl(currentPrivacyWebEngineViewPointer->currentUrlText));
}

void TabWidget::useNativeKdeDownloader(QUrl &downloadUrl, QString &suggestedFileName)
{
    // Get the download directory.
    QString downloadDirectory = Settings::downloadDirectory();

    // Resolve the system download directory if specified.
    if (downloadDirectory == QLatin1String("System Download Directory"))
        downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);

    // Create a save file dialog.
    QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);

    // Tell the dialog to use a save button.
    saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);

    // Populate the file name from the download item pointer.
    saveFileDialogPointer->selectFile(suggestedFileName);

    // Prevent interaction with the parent window while the dialog is open.
    saveFileDialogPointer->setWindowModality(Qt::WindowModal);

    // Process the saving of the file.  The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
    auto saveFile = [saveFileDialogPointer, downloadUrl, this] ()
    {
        // Get the save location.  The dialog box should only allow the selecting of one file location.
        QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);

        // Update the download directory if specified.
        if (Settings::autoUpateDownloadDirectory())
            updateDownloadDirectory(saveLocation.toLocalFile());

        // Create a file copy job.  `-1` creates the file with default permissions.
        KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);

        // Set the download job to display any warning and error messages.
        fileCopyJobPointer->uiDelegate()->setAutoWarningHandlingEnabled(true);
        fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);

        // Start the download.
        fileCopyJobPointer->start();
    };

    // Handle clicks on the save button.
    connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);

    // Show the dialog.
    saveFileDialogPointer->show();
}

