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