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