]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blobdiff - src/widgets/TabWidget.cpp
Fix missing icons on Gnome. https://redmine.stoutner.com/issues/993
[PrivacyBrowserPC.git] / src / widgets / TabWidget.cpp
index ff8563c96a9b5d8b311aca160f51b3098e5ab41c..cbb84b3dfe3c9d9f081da5870c48d662b1dbe039 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
 #include "ui_AddTabWidget.h"
 #include "ui_TabWidget.h"
 #include "databases/CookiesDatabase.h"
-#include "databases/DomainsDatabase.h"
 #include "dialogs/SaveDialog.h"
 #include "filters/MouseEventFilter.h"
 #include "helpers/SearchEngineHelper.h"
-#include "helpers/UserAgentHelper.h"
-#include "interceptors/UrlRequestInterceptor.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 = QStringLiteral("");
+QString TabWidget::webEngineDefaultUserAgent = QLatin1String("");
 
 // Construct the class.
 TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
 {
+    // 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;
@@ -58,25 +76,31 @@ TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
     tabWidgetUi.setupUi(this);
 
     // Get a handle for the tab widget.
-    tabWidgetPointer = tabWidgetUi.tabWidget;
+    qTabWidgetPointer = tabWidgetUi.tabWidget;
 
     // Setup the add tab UI.
-    addTabWidgetUi.setupUi(tabWidgetPointer);
+    addTabWidgetUi.setupUi(qTabWidgetPointer);
 
     // Get handles for the add tab widgets.
     QWidget *addTabWidgetPointer = addTabWidgetUi.addTabQWidget;
     QPushButton *addTabButtonPointer = addTabWidgetUi.addTabButton;
 
     // Display the add tab widget.
-    tabWidgetPointer->setCornerWidget(addTabWidgetPointer);
+    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"));
 
     // Add the first tab.
     addFirstTab();
 
     // Process tab events.
-    connect(tabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
+    connect(qTabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
     connect(addTabButtonPointer, SIGNAL(clicked()), this, SLOT(addTab()));
-    connect(tabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));
+    connect(qTabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));
 
     // Store a copy of the WebEngine default user agent.
     webEngineDefaultUserAgent = currentWebEngineProfilePointer->httpUserAgent();
@@ -95,10 +119,10 @@ TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
 TabWidget::~TabWidget()
 {
     // Manually delete each WebEngine page.
-    for (int i = 0; i < tabWidgetPointer->count(); ++i)
+    for (int i = 0; i < qTabWidgetPointer->count(); ++i)
     {
         // Get the privacy WebEngine view.
-        PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(i));
+        PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(i));
 
         // Deletion the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
         delete privacyWebEngineViewPointer->page();
@@ -108,18 +132,18 @@ TabWidget::~TabWidget()
 // 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.
+    // 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(QStringLiteral(".")))
+    if (!cookie.domain().startsWith(QLatin1String(".")))
     {
         // Populate the URL.
         url.setHost(cookie.domain());
-        url.setScheme(QStringLiteral("https"));
+        url.setScheme(QLatin1String("https"));
 
         // Clear the domain from the cookie.
-        cookie.setDomain(QStringLiteral(""));
+        cookie.setDomain(QLatin1String(""));
     }
 
     // Add the cookie to the store.
@@ -138,35 +162,30 @@ void TabWidget::addFirstTab()
     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.
-    tabWidgetPointer->currentWidget()->setFocus();
+    qTabWidgetPointer->currentWidget()->setFocus();
 }
 
-PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
+PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool backgroundTab)
 {
     // Create a privacy WebEngine view.
     PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView();
 
     // Add a new tab.
-    int newTabIndex = tabWidgetPointer->addTab(privacyWebEngineViewPointer, i18nc("New tab label.", "New Tab"));
+    int newTabIndex = qTabWidgetPointer->addTab(privacyWebEngineViewPointer, i18nc("New tab label.", "New Tab"));
 
     // Set the default tab icon.
-    tabWidgetPointer->setTabIcon(newTabIndex, defaultTabIcon);
+    qTabWidgetPointer->setTabIcon(newTabIndex, defaultFavoriteIcon);
 
-    // Create an off-the-record profile (the default when no profile name is specified).
-    QWebEngineProfile *webEngineProfilePointer = new QWebEngineProfile(QStringLiteral(""));
-
-    // Create a WebEngine page.
-    QWebEnginePage *webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
-
-    // Set the WebEngine page.
-    privacyWebEngineViewPointer->setPage(webEnginePagePointer);
+    // Get handles for the WebEngine page and profile.
+    QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
+    QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
 
     // Get handles for the web engine elements.
     QWebEngineCookieStore *webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
     QWebEngineSettings *webEngineSettingsPointer = webEnginePagePointer->settings();
 
     // Update the URL line edit when the URL changes.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::urlChanged, [privacyWebEngineViewPointer, this] (const QUrl &newUrl)
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::urlChanged, [this, privacyWebEngineViewPointer] (const QUrl &newUrl)
     {
         // Only update the UI if this is the current tab.
         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
@@ -178,24 +197,71 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
             emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
             emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
         }
+    });
 
-        // Reapply the zoom factor.  This is a bug in QWebEngineView that resets the zoom with every load.  It can be removed once <https://redmine.stoutner.com/issues/799> is fixed.
-        privacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
+    // Update the title when it changes.
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, privacyWebEngineViewPointer] (const QString &title)
+    {
+        // Get the index for this tab.
+        int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
+
+        // Update the title for this tab.
+        qTabWidgetPointer->setTabText(tabIndex, title);
+
+        // Update the window title if this is the current tab.
+        if (tabIndex == qTabWidgetPointer->currentIndex())
+            emit updateWindowTitle(title);
     });
 
-    // Update the progress bar when a load is started.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadStarted, [privacyWebEngineViewPointer, this] ()
+    // Connect the loading favorite icon movie to the tab icon.
+    connect(loadingFavoriteIconMoviePointer, &QMovie::frameChanged, [this, privacyWebEngineViewPointer]
+    {
+        // Get the index for this tab.
+        int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
+
+        // 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, 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(privacyWebEngineViewPointer);
+
+        // 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)
             emit showProgressBar(0);
+
+        // Start the loading favorite icon movie.
+        loadingFavoriteIconMoviePointer->start();
     });
 
     // Update the progress bar when a load progresses.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadProgress, [privacyWebEngineViewPointer, this] (const int progress)
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadProgress, [this, privacyWebEngineViewPointer] (const int progress)
     {
         // Store the load progress.
         privacyWebEngineViewPointer->loadProgressInt = progress;
@@ -206,14 +272,75 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
     });
 
     // Update the progress bar when a load finishes.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [privacyWebEngineViewPointer, this] ()
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [this, 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)
             emit hideProgressBar();
+
+        // Get the index for this tab.
+        int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
+
+        // Display the current favorite icon
+        qTabWidgetPointer->setTabIcon(tabIndex, privacyWebEngineViewPointer->favoriteIcon);
+
+        // Create a no tabs loading variable.
+        bool noTabsLoading = true;
+
+        // Check to see if any other tabs are loading.
+        for (int i = 0; i < qTabWidgetPointer->count(); i++)
+        {
+            // Get the privacy WebEngine view for the tab.
+            PrivacyWebEngineView *webEngineViewPointer = qobject_cast<PrivacyWebEngineView*>(qTabWidgetPointer->widget(i));
+
+            // Check to see if it is currently loading.
+            if (webEngineViewPointer->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 factor when changed by CTRL-Scrolling.  This can be modified when <https://redmine.stoutner.com/issues/845> is fixed.
+    connect(webEnginePagePointer, &QWebEnginePage::contentsSizeChanged, [webEnginePagePointer, this] ()
+    {
+        // Only update the zoom factor action text if this is the current tab.
+        if (webEnginePagePointer == currentWebEnginePagePointer)
+            emit updateZoomFactorAction(webEnginePagePointer->zoomFactor());
     });
 
     // Display find text results.
@@ -228,15 +355,6 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
     // Handle file downloads.
     connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
 
-    // Instantiate the URL request interceptor.
-    UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
-
-    // Set the URL request interceptor.
-    webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
-
-    // Reapply the domain settings when the host changes.
-    connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
-
     // Set the local storage filter.
     webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
     {
@@ -264,7 +382,7 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
         return false;
     });
 
-    // Disable JavaScript by default (this prevetns JavaScript from being enabled on a new tab before domain settings are loaded).
+    // 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.
@@ -282,8 +400,14 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
     // 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);
+
     // Update the cookies action.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [privacyWebEngineViewPointer, this] (const int numberOfCookies)
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [this, privacyWebEngineViewPointer] (const int numberOfCookies)
     {
         // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
@@ -301,47 +425,24 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
     for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
         addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
 
-    // Update the title when it changes.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, privacyWebEngineViewPointer] (const QString &title)
-    {
-        // Get the index for this tab.
-        int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
-
-        // Update the title for this tab.
-        tabWidgetPointer->setTabText(tabIndex, title);
-
-        // Update the window title if this is the current tab.
-        if (tabIndex == tabWidgetPointer->currentIndex())
-            emit updateWindowTitle(title);
-    });
-
-    // Update the icon when it changes.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [privacyWebEngineViewPointer, this] (const QIcon &icon)
-    {
-        // Get the index for this tab.
-        int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
-
-        // Update the icon for this tab.
-        if (icon.isNull())
-            tabWidgetPointer->setTabIcon(tabIndex, defaultTabIcon);
-        else
-            tabWidgetPointer->setTabIcon(tabIndex, icon);
-    });
-
     // Enable spell checking.
     webEngineProfilePointer->setSpellCheckEnabled(true);
 
     // Set the spell check language.
-    webEngineProfilePointer->setSpellCheckLanguages({QStringLiteral("en_US")});
+    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());
 
-    // Move to the new tab.
-    tabWidgetPointer->setCurrentIndex(newTabIndex);
+    // 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 (focusNewWebEngineView)
+    if (removeUrlLineEditFocus)
         emit clearUrlLineEditFocus();
 
     // Return the privacy WebEngine view pointer.
@@ -352,9 +453,9 @@ void TabWidget::applyApplicationSettings()
 {
     // Set the tab position.
     if (Settings::tabsOnTop())
-        tabWidgetPointer->setTabPosition(QTabWidget::North);
+        qTabWidgetPointer->setTabPosition(QTabWidget::North);
     else
-        tabWidgetPointer->setTabPosition(QTabWidget::South);
+        qTabWidgetPointer->setTabPosition(QTabWidget::South);
 
     // Set the search engine URL.
     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
@@ -363,174 +464,10 @@ void TabWidget::applyApplicationSettings()
     emit updateSearchEngineActions(Settings::searchEngine(), true);
 }
 
-// This exists as a separate function from `applyDomainSettings()` so it can be listed as a slot and function without the need for a boolean argument.
-// Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
 void TabWidget::applyDomainSettingsAndReload()
 {
     // Apply the domain settings.  `true` reloads the website.
-    applyDomainSettings(currentPrivacyWebEngineViewPointer->url().host(), true);
-}
-
-// This exists as a separate function from `applyDomainSettings()` so it can be listed as a slot and function without the need for a boolean argument.
-// Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
-void TabWidget::applyDomainSettingsWithoutReloading(const QString &hostname)
-{
-    // Apply the domain settings  `false` does not reload the website.
-    applyDomainSettings(hostname, false);
-}
-
-// Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
-void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
-{
-    // Get the record for the hostname.
-    QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
-
-    // Check if the hostname has domain settings.
-    if (domainQuery.isValid())  // The hostname has domain settings.
-    {
-        // Get the domain record.
-        QSqlRecord domainRecord = domainQuery.record();
-
-        // Store the domain settings name.
-        currentPrivacyWebEngineViewPointer->domainSettingsName = domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString();
-
-        // Set the JavaScript status.
-        switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
-        {
-            // Set the default JavaScript status.
-            case (DomainsDatabase::SYSTEM_DEFAULT):
-            {
-                currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
-
-                break;
-            }
-
-            // Disable JavaScript.
-            case (DomainsDatabase::DISABLED):
-            {
-                currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
-
-                break;
-            }
-
-            // Enable JavaScript.
-            case (DomainsDatabase::ENABLED):
-            {
-                currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
-
-                break;
-            }
-        }
-
-        // Set the local storage status.
-        switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
-        {
-            // Set the default local storage status.
-            case (DomainsDatabase::SYSTEM_DEFAULT):
-            {
-                currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
-
-                break;
-            }
-
-            // Disable local storage.
-            case (DomainsDatabase::DISABLED):
-            {
-                currentPrivacyWebEngineViewPointer->localStorageEnabled = false;
-
-                break;
-            }
-
-            // Enable local storage.
-            case (DomainsDatabase::ENABLED):
-            {
-                currentPrivacyWebEngineViewPointer->localStorageEnabled = true;
-
-                break;
-            }
-        }
-
-        // Set the DOM storage status.
-        switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
-        {
-            // Set the default DOM storage status.
-            case (DomainsDatabase::SYSTEM_DEFAULT):
-            {
-                currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
-
-                break;
-            }
-
-            // Disable DOM storage.
-            case (DomainsDatabase::DISABLED):
-            {
-                currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
-
-                break;
-            }
-
-            // Enable DOM storage.
-            case (DomainsDatabase::ENABLED):
-            {
-                currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
-
-                break;
-            }
-        }
-
-        // Set the user agent.
-        currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));
-
-        // Check if a custom zoom factor is set.
-        if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
-        {
-            // Store the current zoom factor.
-            currentZoomFactor = domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble();
-        }
-        else
-        {
-            // Reset the current zoom factor.
-            currentZoomFactor = Settings::zoomFactor();
-        }
-
-        // Set the zoom factor.    The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
-        currentPrivacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
-    }
-    else  // The hostname does not have domain settings.
-    {
-        // Reset the domain settings name.
-        currentPrivacyWebEngineViewPointer->domainSettingsName = QStringLiteral("");
-
-        // Set the JavaScript status.
-        currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
-
-        // Set the local storage status.
-        currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
-
-        // Set DOM storage.  In QWebEngineSettings it is called Local Storage.
-        currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
-
-        // Set the user agent.
-        currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
-
-        // Store the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
-        currentZoomFactor = Settings::zoomFactor();
-
-        // Set the zoom factor.
-        currentPrivacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
-    }
-
-    // Update the UI.
-    emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
-    emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
-    emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
-    emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
-    emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
-    emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
-
-    // Reload the website if requested.
-    if (reloadWebsite)
-        currentPrivacyWebEngineViewPointer->reload();
+    currentPrivacyWebEngineViewPointer->applyDomainSettings(currentPrivacyWebEngineViewPointer->url().host(), true);
 }
 
 void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
@@ -544,7 +481,7 @@ void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
     // Store the search engine string.
     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
 
-    // Update the search engine actionas.
+    // Update the search engine actions.
     emit updateSearchEngineActions(searchEngineName, false);
 }
 
@@ -557,7 +494,7 @@ void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
     userAgentName.remove('&');
 
     // Apply the user agent.
-    currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
+    currentWebEngineProfilePointer->setHttpUserAgent(userAgentHelperPointer->getUserAgentFromTranslatedName(userAgentName));
 
     // Update the user agent actions.
     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
@@ -566,16 +503,34 @@ void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
     currentPrivacyWebEngineViewPointer->reload();
 }
 
-// This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
-void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor)
+void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor) const
 {
-    // Update the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
-    currentZoomFactor = zoomFactor;
-
     // Set the zoom factor.
     currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactor);
 }
 
+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 *webEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->currentWidget());
+
+        // Get the WebEngine page pointer.
+        QWebEnginePage *webEnginePagePointer = webEngineViewPointer->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.
@@ -597,13 +552,13 @@ void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
 void TabWidget::deleteTab(const int tabIndex)
 {
     // Get the privacy WebEngine view.
-    PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(tabIndex));
+    PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(tabIndex));
 
-    // Proccess the tab delete according to the number of tabs.
-    if (tabWidgetPointer->count() > 1)  // There is more than one tab.
+    // Process the tab delete according to the number of tabs.
+    if (qTabWidgetPointer->count() > 1)  // There is more than one tab.
     {
         // Delete the tab.
-        tabWidgetPointer->removeTab(tabIndex);
+        qTabWidgetPointer->removeTab(tabIndex);
 
         // Delete the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
         delete privacyWebEngineViewPointer->page();
@@ -648,7 +603,7 @@ void TabWidget::findText(const QString &text) const
 
 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 temporarially flashes `0/0`.
+    // 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.
@@ -849,62 +804,168 @@ void TabWidget::refresh() const
 void TabWidget::setTabBarVisible(const bool visible) const
 {
     // Set the tab bar visibility.
-    tabWidgetPointer->tabBar()->setVisible(visible);
+    qTabWidgetPointer->tabBar()->setVisible(visible);
 }
 
-void TabWidget::showSaveDialog(QWebEngineDownloadItem *downloadItemPointer) const
+void TabWidget::showSaveDialog(QWebEngineDownloadItem *webEngineDownloadItemPointer)
 {
-    // Instantiate the save dialog.
-    SaveDialog *saveDialogPointer = new SaveDialog(downloadItemPointer);
+    // Get the download attributes.
+    QUrl downloadUrl = webEngineDownloadItemPointer->url();
+    QString mimeTypeString = webEngineDownloadItemPointer->mimeType();
+    QString suggestedFileName = webEngineDownloadItemPointer->suggestedFileName();
+    int totalBytes = webEngineDownloadItemPointer->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(downloadUrl, mimeTypeString, totalBytes);
 
-    // Connect the save button.
-    connect(saveDialogPointer, SIGNAL(showSaveFilePickerDialog(QUrl &, QString &)), this, SLOT(showSaveFilePickerDialog(QUrl &, QString &)));
+        // Display the save dialog.
+        int saveDialogResult = saveDialogPointer->exec();
 
-    // Show the dialog.
-    saveDialogPointer->show();
-}
+        // Process the save dialog results.
+        if (saveDialogResult == QDialog::Accepted)  // Save was selected.
+        {
+            // Get the download directory.
+            QString downloadDirectory = Settings::downloadLocation();
 
-void TabWidget::showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName)
-{
-    // Get the download location.
-    QString downloadDirectory = Settings::downloadLocation();
+            // Resolve the system download directory if specified.
+            if (downloadDirectory == QLatin1String("System Download Directory"))
+                downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
 
-    // Resolve the system download directory if specified.
-    if (downloadDirectory == QStringLiteral("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);
 
-    // Create a save file dialog.
-    QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
+            // Process the save file path.
+            if (!saveFilePath.isEmpty())  // The file save path is populated.
+            {
+                // Create a save file path file info.
+                QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath);
 
-    // Tell the dialog to use a save button.
-    saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
+                // Get the canonical save path and file name.
+                QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
+                QString saveFileName = saveFilePathFileInfo.fileName();
 
-    // Populate the file name from the download item pointer.
-    saveFileDialogPointer->selectFile(suggestedFileName);
+                // Set the download directory and file name.
+                webEngineDownloadItemPointer->setDownloadDirectory(absoluteSavePath);
+                webEngineDownloadItemPointer->setDownloadFileName(saveFileName);
 
-    // Prevent interaction with the parent window while the dialog is open.
-    saveFileDialogPointer->setWindowModality(Qt::WindowModal);
+                // Create a file download notification.
+                KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
 
-    // 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] () {
-        // Get the save location.  The dialog box should only allow the selecting of one file location.
-        QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
+                // Set the notification title.
+                fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
 
-        // 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 notification text.
+                fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));
 
-        // Set the download job to display any error messages.
-        fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
+                // Set the notification icon.
+                fileDownloadNotificationPointer->setIconName(QLatin1String("download"));
 
-        // Start the download.
-        fileCopyJobPointer->start();
-    };
+                // Set the action list cancel button.
+                fileDownloadNotificationPointer->setActions(QStringList({i18nc("Download notification action","Cancel")}));
 
-    // Handle clicks on the save button.
-    connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
+                // Set the notification to display indefinitely.
+                fileDownloadNotificationPointer->setFlags(KNotification::Persistent);
 
-    // Show the dialog.
-    saveFileDialogPointer->show();
+                // Prevent the notification from being autodeleted if it is closed.  Otherwise, the updates to the notification below cause a crash.
+                fileDownloadNotificationPointer->setAutoDelete(false);
+
+                // Display the notification.
+                fileDownloadNotificationPointer->sendEvent();
+
+                // Handle clicks on the cancel button.
+                connect(fileDownloadNotificationPointer, &KNotification::action1Activated, [webEngineDownloadItemPointer, saveFileName] ()
+                {
+                    // Cancel the download.
+                    webEngineDownloadItemPointer->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(webEngineDownloadItemPointer, &QWebEngineDownloadItem::downloadProgress, [fileDownloadNotificationPointer, saveFileName] (qint64 bytesReceived, qint64 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 * bytesReceived / totalBytes;
+
+                        // Set the file download notification text.
+                        fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1\% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
+                                                                    bytesReceived, totalBytes));
+                    }
+                    else
+                    {
+                        // Set the file download notification text.
+                        fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1:  %2 bytes downloaded", saveFileName, bytesReceived));
+                    }
+
+                    // Display the updated notification.
+                    fileDownloadNotificationPointer->update();
+                });
+
+                // Update the notification when the download finishes.  The save file name must be copied into the lambda or a crash occurs.
+                connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::finished, [fileDownloadNotificationPointer, saveFileName, saveFilePath] ()
+                {
+                    // 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->setActions(QStringList());
+
+                    // Set the notification to disappear after a timeout.
+                    fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout);
+
+                    // Display the updated notification.
+                    fileDownloadNotificationPointer->update();
+                });
+
+                // Start the download.
+                webEngineDownloadItemPointer->accept();
+            }
+            else  // The file save path is not populated.
+            {
+                // Cancel the download.
+                webEngineDownloadItemPointer->cancel();
+            }
+        }
+        else  // Cancel was selected.
+        {
+            // Cancel the download.
+            webEngineDownloadItemPointer->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(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();
+    }
 }
 
 void TabWidget::toggleDomStorage() const
@@ -928,7 +989,7 @@ void TabWidget::toggleFindCaseSensitive(const QString &text)
     wipingCurrentFindTextSelection = true;
 
     // Wipe the previous search.  Otherwise currently highlighted words will remain highlighted.
-    findText(QStringLiteral(""));
+    findText(QLatin1String(""));
 
     // Update the find text.
     findText(text);
@@ -948,7 +1009,7 @@ void TabWidget::toggleJavaScript() const
 
 void TabWidget::toggleLocalStorage()
 {
-    // Toggle local storeage.
+    // Toggle local storage.
     currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
 
     // Update the local storage action.
@@ -958,10 +1019,25 @@ void TabWidget::toggleLocalStorage()
     currentPrivacyWebEngineViewPointer->reload();
 }
 
+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.
+        emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
+        emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
+        emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
+        emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
+        emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
+        emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
+    }
+}
+
 void TabWidget::updateUiWithTabSettings()
 {
     // Update the current WebEngine pointers.
-    currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->currentWidget());
+    currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->currentWidget());
     currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
     currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
     currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
@@ -983,7 +1059,7 @@ void TabWidget::updateUiWithTabSettings()
 
     // Update the URL.
     emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
-    emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
+    emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
     emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
 
     // Update the find text.
@@ -996,3 +1072,47 @@ void TabWidget::updateUiWithTabSettings()
     else
         emit hideProgressBar();
 }
+
+void TabWidget::useNativeKdeDownloader(QUrl &downloadUrl, QString &suggestedFileName)
+{
+    // Get the download directory.
+    QString downloadDirectory = Settings::downloadLocation();
+
+    // 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] ()
+    {
+        // Get the save location.  The dialog box should only allow the selecting of one file location.
+        QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
+
+        // 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 error messages.
+        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();
+}