]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blob - src/widgets/TabWidget.cpp
Enable spell checking. https://redmine.stoutner.com/issues/840
[PrivacyBrowserPC.git] / src / widgets / TabWidget.cpp
1 /*
2  * Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
5  *
6  * Privacy Browser PC is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser PC is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 // Application headers.
21 #include "TabWidget.h"
22 #include "Settings.h"
23 #include "ui_AddTabWidget.h"
24 #include "ui_TabWidget.h"
25 #include "databases/CookiesDatabase.h"
26 #include "databases/DomainsDatabase.h"
27 #include "dialogs/SaveDialog.h"
28 #include "filters/MouseEventFilter.h"
29 #include "helpers/SearchEngineHelper.h"
30 #include "interceptors/UrlRequestInterceptor.h"
31 #include "windows/BrowserWindow.h"
32
33 // KDE Framework headers.
34 #include <KIO/FileCopyJob>
35 #include <KIO/JobUiDelegate>
36 #include <KNotification>
37
38 // Qt toolkit headers.
39 #include <QAction>
40 #include <QFileDialog>
41 #include <QGraphicsScene>
42 #include <QGraphicsView>
43 #include <QPrintDialog>
44 #include <QPrintPreviewDialog>
45 #include <QPrinter>
46
47 // Initialize the public static variables.
48 QString TabWidget::webEngineDefaultUserAgent = QLatin1String("");
49
50 // Construct the class.
51 TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
52 {
53     // Instantiate the user agent helper.
54     userAgentHelperPointer = new UserAgentHelper();
55
56     // Instantiate the UIs.
57     Ui::TabWidget tabWidgetUi;
58     Ui::AddTabWidget addTabWidgetUi;
59
60     // Setup the main UI.
61     tabWidgetUi.setupUi(this);
62
63     // Get a handle for the tab widget.
64     tabWidgetPointer = tabWidgetUi.tabWidget;
65
66     // Setup the add tab UI.
67     addTabWidgetUi.setupUi(tabWidgetPointer);
68
69     // Get handles for the add tab widgets.
70     QWidget *addTabWidgetPointer = addTabWidgetUi.addTabQWidget;
71     QPushButton *addTabButtonPointer = addTabWidgetUi.addTabButton;
72
73     // Display the add tab widget.
74     tabWidgetPointer->setCornerWidget(addTabWidgetPointer);
75
76     // Add the first tab.
77     addFirstTab();
78
79     // Process tab events.
80     connect(tabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
81     connect(addTabButtonPointer, SIGNAL(clicked()), this, SLOT(addTab()));
82     connect(tabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));
83
84     // Store a copy of the WebEngine default user agent.
85     webEngineDefaultUserAgent = currentWebEngineProfilePointer->httpUserAgent();
86
87     // Instantiate the mouse event filter pointer.
88     MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
89
90     // Install the mouse event filter.
91     qApp->installEventFilter(mouseEventFilterPointer);
92
93     // Process mouse forward and back commands.
94     connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
95     connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
96 }
97
98 TabWidget::~TabWidget()
99 {
100     // Manually delete each WebEngine page.
101     for (int i = 0; i < tabWidgetPointer->count(); ++i)
102     {
103         // Get the privacy WebEngine view.
104         PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(i));
105
106         // Deletion the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
107         delete privacyWebEngineViewPointer->page();
108     }
109 }
110
111 // 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.
112 void TabWidget::addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *webEngineCookieStorePointer) const
113 {
114     // Create a url.
115     QUrl url;
116
117     // 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>
118     if (!cookie.domain().startsWith(QLatin1String(".")))
119     {
120         // Populate the URL.
121         url.setHost(cookie.domain());
122         url.setScheme(QLatin1String("https"));
123
124         // Clear the domain from the cookie.
125         cookie.setDomain(QLatin1String(""));
126     }
127
128     // Add the cookie to the store.
129     if (webEngineCookieStorePointer == nullptr)
130         currentWebEngineCookieStorePointer->setCookie(cookie, url);
131     else
132         webEngineCookieStorePointer->setCookie(cookie, url);
133 }
134
135 void TabWidget::addFirstTab()
136 {
137     // Create the first tab.
138     addTab();
139
140     // Update the UI with the tab settings.
141     updateUiWithTabSettings();
142
143     // 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.
144     tabWidgetPointer->currentWidget()->setFocus();
145 }
146
147 PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool backgroundTab)
148 {
149     // Create a privacy WebEngine view.
150     PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView();
151
152     // Add a new tab.
153     int newTabIndex = tabWidgetPointer->addTab(privacyWebEngineViewPointer, i18nc("New tab label.", "New Tab"));
154
155     // Set the default tab icon.
156     tabWidgetPointer->setTabIcon(newTabIndex, defaultTabIcon);
157
158     // Create an off-the-record profile (the default when no profile name is specified).
159     QWebEngineProfile *webEngineProfilePointer = new QWebEngineProfile(QLatin1String(""));
160
161     // Create a WebEngine page.
162     QWebEnginePage *webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
163
164     // Set the WebEngine page.
165     privacyWebEngineViewPointer->setPage(webEnginePagePointer);
166
167     // Get handles for the web engine elements.
168     QWebEngineCookieStore *webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
169     QWebEngineSettings *webEngineSettingsPointer = webEnginePagePointer->settings();
170
171     // Update the URL line edit when the URL changes.
172     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::urlChanged, [privacyWebEngineViewPointer, this] (const QUrl &newUrl)
173     {
174         // Only update the UI if this is the current tab.
175         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
176         {
177             // Update the URL line edit.
178             emit updateUrlLineEdit(newUrl);
179
180             // Update the status of the forward and back buttons.
181             emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
182             emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
183         }
184
185         // 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.
186         privacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
187     });
188
189     // Update the progress bar when a load is started.
190     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadStarted, [privacyWebEngineViewPointer, this] ()
191     {
192         // Store the load progress.
193         privacyWebEngineViewPointer->loadProgressInt = 0;
194
195         // Show the progress bar if this is the current tab.
196         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
197             emit showProgressBar(0);
198     });
199
200     // Update the progress bar when a load progresses.
201     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadProgress, [privacyWebEngineViewPointer, this] (const int progress)
202     {
203         // Store the load progress.
204         privacyWebEngineViewPointer->loadProgressInt = progress;
205
206         // Update the progress bar if this is the current tab.
207         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
208             emit showProgressBar(progress);
209     });
210
211     // Update the progress bar when a load finishes.
212     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [privacyWebEngineViewPointer, this] ()
213     {
214         // Store the load progress.
215         privacyWebEngineViewPointer->loadProgressInt = -1;
216
217         // Hide the progress bar if this is the current tab.
218         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
219             emit hideProgressBar();
220     });
221
222     // Display find text results.
223     connect(webEnginePagePointer, SIGNAL(findTextFinished(const QWebEngineFindTextResult &)), this, SLOT(findTextFinished(const QWebEngineFindTextResult &)));
224
225     // Handle full screen requests.
226     connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
227
228     // Listen for hovered link URLs.
229     connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
230
231     // Handle file downloads.
232     connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
233
234     // Instantiate the URL request interceptor.
235     UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
236
237     // Set the URL request interceptor.
238     webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
239
240     // Reapply the domain settings when the host changes.
241     connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
242
243     // Set the local storage filter.
244     webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
245     {
246         // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
247         if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
248         {
249             //qDebug().noquote().nospace() << "Third-party request blocked:  " << filterRequest.origin;
250
251             // Return false.
252             return false;
253         }
254
255         // Allow the request if local storage is enabled.
256         if (privacyWebEngineViewPointer->localStorageEnabled)
257         {
258             //qDebug().noquote().nospace() << "Request allowed by local storage:  " << filterRequest.origin;
259
260             // Return true.
261             return true;
262         }
263
264         //qDebug().noquote().nospace() << "Request blocked by default:  " << filterRequest.origin;
265
266         // Block any remaining local storage requests.
267         return false;
268     });
269
270     // Disable JavaScript by default (this prevents JavaScript from being enabled on a new tab before domain settings are loaded).
271     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
272
273     // Don't allow JavaScript to open windows.
274     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
275
276     // Allow keyboard navigation.
277     webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
278
279     // Enable full screen support.
280     webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
281
282     // Require user interaction to play media.
283     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
284
285     // Limit WebRTC to public IP addresses.
286     webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
287
288     // Enable the PDF viewer (it should be enabled by default, but it is nice to be explicit in case the defaults change).
289     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PdfViewerEnabled, true);
290
291     // Plugins must be enabled for the PDF viewer to work.  <https://doc.qt.io/qt-5/qtwebengine-features.html#pdf-file-viewing>
292     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PluginsEnabled, true);
293
294     // Update the cookies action.
295     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [privacyWebEngineViewPointer, this] (const int numberOfCookies)
296     {
297         // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
298         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
299             emit updateCookiesAction(numberOfCookies);
300     });
301
302     // Process cookie changes.
303     connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(addCookieToList(QNetworkCookie)));
304     connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(removeCookieFromList(QNetworkCookie)));
305
306     // Get a list of durable cookies.
307     QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
308
309     // Add the durable cookies to the store.
310     for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
311         addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
312
313     // Update the title when it changes.
314     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, privacyWebEngineViewPointer] (const QString &title)
315     {
316         // Get the index for this tab.
317         int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
318
319         // Update the title for this tab.
320         tabWidgetPointer->setTabText(tabIndex, title);
321
322         // Update the window title if this is the current tab.
323         if (tabIndex == tabWidgetPointer->currentIndex())
324             emit updateWindowTitle(title);
325     });
326
327     // Update the icon when it changes.
328     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [privacyWebEngineViewPointer, this] (const QIcon &icon)
329     {
330         // Get the index for this tab.
331         int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
332
333         // Update the icon for this tab.
334         if (icon.isNull())
335             tabWidgetPointer->setTabIcon(tabIndex, defaultTabIcon);
336         else
337             tabWidgetPointer->setTabIcon(tabIndex, icon);
338     });
339
340     // Enable spell checking.
341     webEngineProfilePointer->setSpellCheckEnabled(true);
342
343     // Set the spell check language.
344     webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
345
346     // Populate the zoom factor.  This is necessary if a URL is being loaded, like a local URL, that does not trigger `applyDomainSettings()`.
347     privacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
348
349     // Move to the new tab if it is not a background tab.
350     if (!backgroundTab)
351         tabWidgetPointer->setCurrentIndex(newTabIndex);
352
353     // Clear the URL line edit focus so that it populates correctly when opening a new tab from the context menu.
354     if (removeUrlLineEditFocus)
355         emit clearUrlLineEditFocus();
356
357     // Return the privacy WebEngine view pointer.
358     return privacyWebEngineViewPointer;
359 }
360
361 void TabWidget::applyApplicationSettings()
362 {
363     // Set the tab position.
364     if (Settings::tabsOnTop())
365         tabWidgetPointer->setTabPosition(QTabWidget::North);
366     else
367         tabWidgetPointer->setTabPosition(QTabWidget::South);
368
369     // Set the search engine URL.
370     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
371
372     // Emit the update search engine actions signal.
373     emit updateSearchEngineActions(Settings::searchEngine(), true);
374 }
375
376 // 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.
377 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
378 void TabWidget::applyDomainSettingsAndReload()
379 {
380     // Apply the domain settings.  `true` reloads the website.
381     applyDomainSettings(currentPrivacyWebEngineViewPointer->url().host(), true);
382 }
383
384 // 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.
385 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
386 void TabWidget::applyDomainSettingsWithoutReloading(const QString &hostname)
387 {
388     // Apply the domain settings  `false` does not reload the website.
389     applyDomainSettings(hostname, false);
390 }
391
392 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
393 void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
394 {
395     // Get the record for the hostname.
396     QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
397
398     // Check if the hostname has domain settings.
399     if (domainQuery.isValid())  // The hostname has domain settings.
400     {
401         // Get the domain record.
402         QSqlRecord domainRecord = domainQuery.record();
403
404         // Store the domain settings name.
405         currentPrivacyWebEngineViewPointer->domainSettingsName = domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString();
406
407         // Set the JavaScript status.
408         switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
409         {
410             // Set the default JavaScript status.
411             case (DomainsDatabase::SYSTEM_DEFAULT):
412             {
413                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
414
415                 break;
416             }
417
418             // Disable JavaScript.
419             case (DomainsDatabase::DISABLED):
420             {
421                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
422
423                 break;
424             }
425
426             // Enable JavaScript.
427             case (DomainsDatabase::ENABLED):
428             {
429                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
430
431                 break;
432             }
433         }
434
435         // Set the local storage status.
436         switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
437         {
438             // Set the default local storage status.
439             case (DomainsDatabase::SYSTEM_DEFAULT):
440             {
441                 currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
442
443                 break;
444             }
445
446             // Disable local storage.
447             case (DomainsDatabase::DISABLED):
448             {
449                 currentPrivacyWebEngineViewPointer->localStorageEnabled = false;
450
451                 break;
452             }
453
454             // Enable local storage.
455             case (DomainsDatabase::ENABLED):
456             {
457                 currentPrivacyWebEngineViewPointer->localStorageEnabled = true;
458
459                 break;
460             }
461         }
462
463         // Set the DOM storage status.
464         switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
465         {
466             // Set the default DOM storage status.  QWebEngineSettings confusingly calls this local storage.
467             case (DomainsDatabase::SYSTEM_DEFAULT):
468             {
469                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
470
471                 break;
472             }
473
474             // Disable DOM storage.  QWebEngineSettings confusingly calls this local storage.
475             case (DomainsDatabase::DISABLED):
476             {
477                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
478
479                 break;
480             }
481
482             // Enable DOM storage.  QWebEngineSettings confusingly calls this local storage.
483             case (DomainsDatabase::ENABLED):
484             {
485                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
486
487                 break;
488             }
489         }
490
491         // Set the user agent.
492         currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));
493
494         // Check if a custom zoom factor is set.
495         if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
496         {
497             // Store the current zoom factor.
498             currentZoomFactor = domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble();
499         }
500         else
501         {
502             // Reset the current zoom factor.
503             currentZoomFactor = Settings::zoomFactor();
504         }
505
506         // Set the zoom factor.    The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
507         currentPrivacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
508     }
509     else  // The hostname does not have domain settings.
510     {
511         // Reset the domain settings name.
512         currentPrivacyWebEngineViewPointer->domainSettingsName = QLatin1String("");
513
514         // Set the JavaScript status.
515         currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
516
517         // Set the local storage status.
518         currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
519
520         // Set DOM storage.  In QWebEngineSettings it is called Local Storage.
521         currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
522
523         // Set the user agent.
524         currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
525
526         // Store the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
527         currentZoomFactor = Settings::zoomFactor();
528
529         // Set the zoom factor.
530         currentPrivacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
531     }
532
533     // Update the UI.
534     emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
535     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
536     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
537     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
538     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
539     emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
540
541     // Reload the website if requested.
542     if (reloadWebsite)
543         currentPrivacyWebEngineViewPointer->reload();
544 }
545
546 void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
547 {
548     // Store the search engine name.
549     QString searchEngineName = searchEngineActionPointer->text();
550
551     // Strip out any `&` characters.
552     searchEngineName.remove('&');
553
554     // Store the search engine string.
555     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
556
557     // Update the search engine actions.
558     emit updateSearchEngineActions(searchEngineName, false);
559 }
560
561 void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
562 {
563     // Get the user agent name.
564     QString userAgentName = userAgentActionPointer->text();
565
566     // Strip out any `&` characters.
567     userAgentName.remove('&');
568
569     // Apply the user agent.
570     currentWebEngineProfilePointer->setHttpUserAgent(userAgentHelperPointer->getUserAgentFromTranslatedName(userAgentName));
571
572     // Update the user agent actions.
573     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
574
575     // Reload the website.
576     currentPrivacyWebEngineViewPointer->reload();
577 }
578
579 // This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
580 void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor)
581 {
582     // Update the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
583     currentZoomFactor = zoomFactor;
584
585     // Set the zoom factor.
586     currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactor);
587 }
588
589 void TabWidget::applySpellCheckLanguages() const
590 {
591     // Get the number of tab.
592     int numberOfTabs = tabWidgetPointer->count();
593
594     // Set the spell check languages for each tab.
595     for (int i = 0; i < numberOfTabs; ++i)
596     {
597         // Get the WebEngine view pointer.
598         PrivacyWebEngineView *webEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->currentWidget());
599
600         // Get the WebEngine page pointer.
601         QWebEnginePage *webEnginePagePointer = webEngineViewPointer->page();
602
603         // Get the WebEngine profile pointer.
604         QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
605
606         // Set the spell check languages.
607         webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
608     }
609 }
610
611 void TabWidget::back() const
612 {
613     // Go back.
614     currentPrivacyWebEngineViewPointer->back();
615 }
616
617 void TabWidget::deleteAllCookies() const
618 {
619     // Delete all the cookies.
620     currentWebEngineCookieStorePointer->deleteAllCookies();
621 }
622
623 void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
624 {
625     // Delete the cookie.
626     currentWebEngineCookieStorePointer->deleteCookie(cookie);
627 }
628
629 void TabWidget::deleteTab(const int tabIndex)
630 {
631     // Get the privacy WebEngine view.
632     PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(tabIndex));
633
634     // Proccess the tab delete according to the number of tabs.
635     if (tabWidgetPointer->count() > 1)  // There is more than one tab.
636     {
637         // Delete the tab.
638         tabWidgetPointer->removeTab(tabIndex);
639
640         // Delete the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
641         delete privacyWebEngineViewPointer->page();
642
643         // Delete the privacy WebEngine view.
644         delete privacyWebEngineViewPointer;
645     }
646     else  // There is only one tab.
647     {
648         // Close Privacy Browser.
649         window()->close();
650     }
651 }
652
653 void TabWidget::findPrevious(const QString &text) const
654 {
655     // Store the current text.
656     currentPrivacyWebEngineViewPointer->findString = text;
657
658     // Find the previous text in the current privacy WebEngine.
659     if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
660         currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively|QWebEnginePage::FindBackward);
661     else
662         currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindBackward);
663 }
664
665 void TabWidget::findText(const QString &text) const
666 {
667     // Store the current text.
668     currentPrivacyWebEngineViewPointer->findString = text;
669
670     // Find the text in the current privacy WebEngine.
671     if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
672         currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively);
673     else
674         currentPrivacyWebEngineViewPointer->findText(text);
675
676     // Clear the currently selected text in the WebEngine page if the find text is empty.
677     if (text.isEmpty())
678         currentWebEnginePagePointer->action(QWebEnginePage::Unselect)->activate(QAction::Trigger);
679 }
680
681 void TabWidget::findTextFinished(const QWebEngineFindTextResult &findTextResult)
682 {
683     // Update the find text UI if it wasn't simply wiping the current find text selection.  Otherwise the UI temporarially flashes `0/0`.
684     if (wipingCurrentFindTextSelection)  // The current selection is being wiped.
685     {
686         // Reset the flag.
687         wipingCurrentFindTextSelection = false;
688     }
689     else  // A new search has been performed.
690     {
691         // Store the result.
692         currentPrivacyWebEngineViewPointer->findTextResult = findTextResult;
693
694         // Update the UI.
695         emit updateFindTextResults(findTextResult);
696     }
697 }
698
699 void TabWidget::forward() const
700 {
701     // Go forward.
702     currentPrivacyWebEngineViewPointer->forward();
703 }
704
705 void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
706 {
707     // Make it so.
708     emit fullScreenRequested(fullScreenRequest.toggleOn());
709
710     // Accept the request.
711     fullScreenRequest.accept();
712 }
713
714 std::list<QNetworkCookie>* TabWidget::getCookieList() const
715 {
716     // Return the current cookie list.
717     return currentPrivacyWebEngineViewPointer->cookieListPointer;
718 }
719
720 QString& TabWidget::getDomainSettingsName() const
721 {
722     // Return the domain settings name.
723     return currentPrivacyWebEngineViewPointer->domainSettingsName;
724 }
725
726 void TabWidget::home() const
727 {
728     // Load the homepage.
729     currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
730 }
731
732 PrivacyWebEngineView* TabWidget::loadBlankInitialWebsite()
733 {
734     // Apply the application settings.
735     applyApplicationSettings();
736
737     // Return the current privacy WebEngine view pointer.
738     return currentPrivacyWebEngineViewPointer;
739 }
740
741 void TabWidget::loadInitialWebsite()
742 {
743     // Apply the application settings.
744     applyApplicationSettings();
745
746     // Get the arguments.
747     QStringList argumentsStringList = qApp->arguments();
748
749     // Check to see if the arguments lists contains a URL.
750     if (argumentsStringList.size() > 1)
751     {
752         // Load the URL from the arguments list.
753         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
754     }
755     else
756     {
757         // Load the homepage.
758         home();
759     }
760 }
761
762 void TabWidget::loadUrlFromLineEdit(QString url) const
763 {
764     // Decide if the text is more likely to be a URL or a search.
765     if (url.startsWith("file://"))  // The text is likely a file URL.
766     {
767         // Load the URL.
768         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
769     }
770     else if (url.contains("."))  // The text is likely a URL.
771     {
772         // Check if the URL does not start with a valid protocol.
773         if (!url.startsWith("http"))
774         {
775             // Add `https://` to the beginning of the URL.
776             url = "https://" + url;
777         }
778
779         // Load the URL.
780         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
781     }
782     else  // The text is likely a search.
783     {
784         // Load the search.
785         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
786     }
787 }
788
789 void TabWidget::mouseBack() const
790 {
791     // Go back if possible.
792     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
793     {
794         // Clear the URL line edit focus.
795         emit clearUrlLineEditFocus();
796
797         // Go back.
798         currentPrivacyWebEngineViewPointer->back();
799     }
800 }
801
802 void TabWidget::mouseForward() const
803 {
804     // Go forward if possible.
805     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
806     {
807         // Clear the URL line edit focus.
808         emit clearUrlLineEditFocus();
809
810         // Go forward.
811         currentPrivacyWebEngineViewPointer->forward();
812     }
813 }
814
815 void TabWidget::pageLinkHovered(const QString &linkUrl) const
816 {
817     // Emit a signal so that the browser window can update the status bar.
818     emit linkHovered(linkUrl);
819 }
820
821 void TabWidget::print() const
822 {
823     // Create a printer.
824     QPrinter printer;
825
826     // Set the resolution to be 300 dpi.
827     printer.setResolution(300);
828
829     // Create a printer dialog.
830     QPrintDialog printDialog(&printer, currentPrivacyWebEngineViewPointer);
831
832     // Display the dialog and print the page if instructed.
833     if (printDialog.exec() == QDialog::Accepted)
834         printWebpage(&printer);
835 }
836
837 void TabWidget::printPreview() const
838 {
839     // Create a printer.
840     QPrinter printer;
841
842     // Set the resolution to be 300 dpi.
843     printer.setResolution(300);
844
845     // Create a print preview dialog.
846     QPrintPreviewDialog printPreviewDialog(&printer, currentPrivacyWebEngineViewPointer);
847
848     // Generate the print preview.
849     connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
850
851     // Display the dialog.
852     printPreviewDialog.exec();
853 }
854
855 void TabWidget::printWebpage(QPrinter *printerPointer) const
856 {
857     // Create an event loop.  For some reason, the print preview doesn't produce any output unless it is run inside an event loop.
858     QEventLoop eventLoop;
859
860     // Print the webpage, converting the callback above into a `QWebEngineCallback<bool>`.
861     // Printing requires that the printer be a pointer, not a reference, or it will crash with much cursing.
862     currentWebEnginePagePointer->print(printerPointer, [&eventLoop](bool printSuccess)
863     {
864         // Instruct the compiler to ignore the unused parameter.
865         (void) printSuccess;
866
867         // Quit the loop.
868         eventLoop.quit();
869     });
870
871     // Execute the loop.
872     eventLoop.exec();
873 }
874
875 void TabWidget::refresh() const
876 {
877     // Reload the website.
878     currentPrivacyWebEngineViewPointer->reload();
879 }
880
881 void TabWidget::setTabBarVisible(const bool visible) const
882 {
883     // Set the tab bar visibility.
884     tabWidgetPointer->tabBar()->setVisible(visible);
885 }
886
887 void TabWidget::showSaveDialog(QWebEngineDownloadItem *webEngineDownloadItemPointer)
888 {
889     // Get the download attributes.
890     QUrl downloadUrl = webEngineDownloadItemPointer->url();
891     QString mimeTypeString = webEngineDownloadItemPointer->mimeType();
892     QString suggestedFileName = webEngineDownloadItemPointer->suggestedFileName();
893     int totalBytes = webEngineDownloadItemPointer->totalBytes();
894
895     // Check to see if local storage (cookies) is enabled.
896     if (currentPrivacyWebEngineViewPointer->localStorageEnabled)  // Local storage (cookies) is enabled.  Use WebEngine's downloader.
897     {
898         // Instantiate the save dialog.
899         SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes);
900
901         // Display the save dialog.
902         int saveDialogResult = saveDialogPointer->exec();
903
904         // Process the save dialog results.
905         if (saveDialogResult == QDialog::Accepted)  // Save was selected.
906         {
907             // Get the download directory.
908             QString downloadDirectory = Settings::downloadLocation();
909
910             // Resolve the system download directory if specified.
911             if (downloadDirectory == QLatin1String("System Download Directory"))
912                 downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
913
914             // Display a save file dialog.
915             QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
916
917             // Process the save file path.
918             if (!saveFilePath.isEmpty())  // The file save path is populated.
919             {
920                 // Create a save file path file info.
921                 QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath);
922
923                 // Get the canonical save path and file name.
924                 QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
925                 QString saveFileName = saveFilePathFileInfo.fileName();
926
927                 // Set the download directory and file name.
928                 webEngineDownloadItemPointer->setDownloadDirectory(absoluteSavePath);
929                 webEngineDownloadItemPointer->setDownloadFileName(saveFileName);
930
931                 // Create a file download notification.
932                 KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
933
934                 // Set the notification title.
935                 fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
936
937                 // Set the notification text.
938                 fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));
939
940                 // Set the notification icon.
941                 fileDownloadNotificationPointer->setIconName(QLatin1String("download"));
942
943                 // Set the action list cancel button.
944                 fileDownloadNotificationPointer->setActions(QStringList({i18nc("Download notification action","Cancel")}));
945
946                 // Set the notification to display indefinitely.
947                 fileDownloadNotificationPointer->setFlags(KNotification::Persistent);
948
949                 // Prevent the notification from being autodeleted if it is closed.  Otherwise, the updates to the notification below cause a crash.
950                 fileDownloadNotificationPointer->setAutoDelete(false);
951
952                 // Display the notification.
953                 fileDownloadNotificationPointer->sendEvent();
954
955                 // Handle clicks on the cancel button.
956                 connect(fileDownloadNotificationPointer, &KNotification::action1Activated, [webEngineDownloadItemPointer, saveFileName] ()
957                 {
958                     // Cancel the download.
959                     webEngineDownloadItemPointer->cancel();
960
961                     // Create a file download notification.
962                     KNotification *canceledDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
963
964                     // Set the notification title.
965                     canceledDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
966
967                     // Set the new text.
968                     canceledDownloadNotificationPointer->setText(i18nc("Download canceled notification", "%1 download canceled", saveFileName));
969
970                     // Set the notification icon.
971                     canceledDownloadNotificationPointer->setIconName(QLatin1String("download"));
972
973                     // Display the notification.
974                     canceledDownloadNotificationPointer->sendEvent();
975                 });
976
977                 // Update the notification when the download progresses.
978                 connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::downloadProgress, [fileDownloadNotificationPointer, saveFileName] (qint64 bytesReceived, qint64 totalBytes)
979                 {
980                     // Calculate the download percentage.
981                     int downloadPercentage = 100 * bytesReceived / totalBytes;
982
983                     // Set the new text.  Total bytes will be 0 if the download size is unknown.
984                     if (totalBytes > 0)
985                         fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1\% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
986                                                                     bytesReceived, totalBytes));
987                     else
988                         fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1:  %2 bytes downloaded", saveFileName, bytesReceived));
989
990                     // Display the updated notification.
991                     fileDownloadNotificationPointer->update();
992                 });
993
994                 // Update the notification when the download finishes.  The save file name must be copied into the lambda or a crash occurs.
995                 connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::finished, [fileDownloadNotificationPointer, saveFileName, saveFilePath] ()
996                 {
997                     // Set the new text.
998                     fileDownloadNotificationPointer->setText(i18nc("Download finished notification text", "%1 download finished", saveFileName));
999
1000                     // Set the URL so the file options will be displayed.
1001                     fileDownloadNotificationPointer->setUrls(QList<QUrl> {QUrl(saveFilePath)});
1002
1003                     // Remove the actions from the notification.
1004                     fileDownloadNotificationPointer->setActions(QStringList());
1005
1006                     // Set the notification to disappear after a timeout.
1007                     fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout);
1008
1009                     // Display the updated notification.
1010                     fileDownloadNotificationPointer->update();
1011                 });
1012
1013                 // Start the download.
1014                 webEngineDownloadItemPointer->accept();
1015             }
1016             else  // The file save path is not populated.
1017             {
1018                 // Cancel the download.
1019                 webEngineDownloadItemPointer->cancel();
1020             }
1021         }
1022         else  // Cancel was selected.
1023         {
1024             // Cancel the download.
1025             webEngineDownloadItemPointer->cancel();
1026         }
1027     }
1028     else  // Local storage (cookies) is disabled.  Use KDE's native downloader.
1029           // 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.
1030     {
1031         // Instantiate the save dialog.  `true` instructs it to use the native downloader
1032         SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true);
1033
1034         // Connect the save button.
1035         connect(saveDialogPointer, SIGNAL(useNativeDownloader(QUrl &, QString &)), this, SLOT(useNativeDownloader(QUrl &, QString &)));
1036
1037         // Show the dialog.
1038         saveDialogPointer->show();
1039     }
1040 }
1041
1042 void TabWidget::toggleDomStorage() const
1043 {
1044     // Toggle DOM storage.
1045     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1046
1047     // Update the DOM storage action.
1048     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1049
1050     // Reload the website.
1051     currentPrivacyWebEngineViewPointer->reload();
1052 }
1053
1054 void TabWidget::toggleFindCaseSensitive(const QString &text)
1055 {
1056     // Toggle find case sensitive.
1057     currentPrivacyWebEngineViewPointer->findCaseSensitive = !currentPrivacyWebEngineViewPointer->findCaseSensitive;
1058
1059     // Set the wiping current find text selection flag.
1060     wipingCurrentFindTextSelection = true;
1061
1062     // Wipe the previous search.  Otherwise currently highlighted words will remain highlighted.
1063     findText(QLatin1String(""));
1064
1065     // Update the find text.
1066     findText(text);
1067 }
1068
1069 void TabWidget::toggleJavaScript() const
1070 {
1071     // Toggle JavaScript.
1072     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1073
1074     // Update the JavaScript action.
1075     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1076
1077     // Reload the website.
1078     currentPrivacyWebEngineViewPointer->reload();
1079 }
1080
1081 void TabWidget::toggleLocalStorage()
1082 {
1083     // Toggle local storage.
1084     currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
1085
1086     // Update the local storage action.
1087     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1088
1089     // Reload the website.
1090     currentPrivacyWebEngineViewPointer->reload();
1091 }
1092
1093 void TabWidget::updateUiWithTabSettings()
1094 {
1095     // Update the current WebEngine pointers.
1096     currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->currentWidget());
1097     currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
1098     currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
1099     currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
1100     currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
1101     currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();
1102
1103     // Clear the URL line edit focus.
1104     emit clearUrlLineEditFocus();
1105
1106     // Update the actions.
1107     emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
1108     emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
1109     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1110     emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
1111     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1112     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1113     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
1114     emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
1115
1116     // Update the URL.
1117     emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
1118     emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
1119     emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
1120
1121     // Update the find text.
1122     emit updateFindText(currentPrivacyWebEngineViewPointer->findString, currentPrivacyWebEngineViewPointer->findCaseSensitive);
1123     emit updateFindTextResults(currentPrivacyWebEngineViewPointer->findTextResult);
1124
1125     // Update the progress bar.
1126     if (currentPrivacyWebEngineViewPointer->loadProgressInt >= 0)
1127         emit showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt);
1128     else
1129         emit hideProgressBar();
1130 }
1131
1132 void TabWidget::useNativeDownloader(QUrl &downloadUrl, QString &suggestedFileName)
1133 {
1134     // Get the download directory.
1135     QString downloadDirectory = Settings::downloadLocation();
1136
1137     // Resolve the system download directory if specified.
1138     if (downloadDirectory == QLatin1String("System Download Directory"))
1139         downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
1140
1141     // Create a save file dialog.
1142     QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
1143
1144     // Tell the dialog to use a save button.
1145     saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
1146
1147     // Populate the file name from the download item pointer.
1148     saveFileDialogPointer->selectFile(suggestedFileName);
1149
1150     // Prevent interaction with the parent window while the dialog is open.
1151     saveFileDialogPointer->setWindowModality(Qt::WindowModal);
1152
1153     // Process the saving of the file.  The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
1154     auto saveFile = [saveFileDialogPointer, downloadUrl] ()
1155     {
1156         // Get the save location.  The dialog box should only allow the selecting of one file location.
1157         QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
1158
1159         // Create a file copy job.  `-1` creates the file with default permissions.
1160         KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
1161
1162         // Set the download job to display any error messages.
1163         fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
1164
1165         // Start the download.
1166         fileCopyJobPointer->start();
1167     };
1168
1169     // Handle clicks on the save button.
1170     connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
1171
1172     // Show the dialog.
1173     saveFileDialogPointer->show();
1174 }