]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blob - src/widgets/TabWidget.cpp
Add a default folder icon to the edit folder dialog. https://redmine.stoutner.com...
[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 cookies action.
456     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [this, privacyWebEngineViewPointer] (const int numberOfCookies)
457     {
458         // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
459         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
460             emit updateCookiesAction(numberOfCookies);
461     });
462
463     // Process cookie changes.
464     connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(addCookieToList(QNetworkCookie)));
465     connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(removeCookieFromList(QNetworkCookie)));
466
467     // Get a list of durable cookies.
468     QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
469
470     // Add the durable cookies to the store.
471     for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
472         addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
473
474     // Enable spell checking.
475     webEngineProfilePointer->setSpellCheckEnabled(true);
476
477     // Set the spell check language.
478     webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
479
480     // Populate the zoom factor.  This is necessary if a URL is being loaded, like a local URL, that does not trigger `applyDomainSettings()`.
481     privacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
482
483     // Update the UI when domain settings are applied.
484     connect(privacyWebEngineViewPointer, SIGNAL(updateUi(const PrivacyWebEngineView*)), this, SLOT(updateUiFromWebEngineView(const PrivacyWebEngineView*)));
485
486     // Move to the new tab if it is not a background tab.
487     if (!backgroundTab)
488         qTabWidgetPointer->setCurrentIndex(newTabIndex);
489
490     // Clear the URL line edit focus so that it populates correctly when opening a new tab from the context menu.
491     if (removeUrlLineEditFocus)
492         emit clearUrlLineEditFocus();
493
494     if (urlString != nullptr)
495         privacyWebEngineViewPointer->load(QUrl::fromUserInput(urlString));
496
497     // Return the privacy WebEngine view pointer.
498     return privacyWebEngineViewPointer;
499 }
500
501 void TabWidget::applyApplicationSettings()
502 {
503     // Set the tab position.
504     if (Settings::tabsOnTop())
505         qTabWidgetPointer->setTabPosition(QTabWidget::North);
506     else
507         qTabWidgetPointer->setTabPosition(QTabWidget::South);
508
509     // Get the number of tabs.
510     int numberOfTabs = qTabWidgetPointer->count();
511
512     // Apply the spatial navigation settings to each WebEngine.
513     for (int i = 0; i < numberOfTabs; ++i) {
514         // Get the WebEngine view pointer.
515         PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
516
517         // Apply the spatial navigation settings to each page.
518         privacyWebEngineViewPointer->page()->settings()->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, Settings::spatialNavigation());
519     }
520
521     // Set the search engine URL.
522     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
523
524     // Emit the update search engine actions signal.
525     emit updateSearchEngineActions(Settings::searchEngine(), true);
526 }
527
528 void TabWidget::applyDomainSettingsAndReload()
529 {
530     // Get the number of tabs.
531     int numberOfTabs = qTabWidgetPointer->count();
532
533     // Apply the domain settings to each WebEngine.
534     for (int i = 0; i < numberOfTabs; ++i) {
535         // Get the WebEngine view pointer.
536         PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
537
538         // Apply the spatial navigation settings to each page.
539         privacyWebEngineViewPointer->applyDomainSettings(privacyWebEngineViewPointer->url().host(), true);
540     }
541 }
542
543 void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
544 {
545     // Store the search engine name.
546     QString searchEngineName = searchEngineActionPointer->text();
547
548     // Strip out any `&` characters.
549     searchEngineName.remove('&');
550
551     // Store the search engine string.
552     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
553
554     // Update the search engine actions.
555     emit updateSearchEngineActions(searchEngineName, false);
556 }
557
558 void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
559 {
560     // Get the user agent name.
561     QString userAgentName = userAgentActionPointer->text();
562
563     // Strip out any `&` characters.
564     userAgentName.remove('&');
565
566     // Apply the user agent.
567     currentWebEngineProfilePointer->setHttpUserAgent(userAgentHelperPointer->getUserAgentFromTranslatedName(userAgentName));
568
569     // Update the user agent actions.
570     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
571
572     // Reload the website.
573     currentPrivacyWebEngineViewPointer->reload();
574 }
575
576 void TabWidget::applyOnTheFlyZoomFactor(const double zoomFactorDouble) const
577 {
578     // Set the zoom factor.
579     currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactorDouble);
580 }
581
582 void TabWidget::applySpellCheckLanguages() const
583 {
584     // Get the number of tab.
585     int numberOfTabs = qTabWidgetPointer->count();
586
587     // Set the spell check languages for each tab.
588     for (int i = 0; i < numberOfTabs; ++i)
589     {
590         // Get the WebEngine view pointer.
591         PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
592
593         // Get the WebEngine page pointer.
594         QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
595
596         // Get the WebEngine profile pointer.
597         QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
598
599         // Set the spell check languages.
600         webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
601     }
602 }
603
604 void TabWidget::back() const
605 {
606     // Go back.
607     currentPrivacyWebEngineViewPointer->back();
608 }
609
610 void TabWidget::deleteAllCookies() const
611 {
612     // Delete all the cookies.
613     currentWebEngineCookieStorePointer->deleteAllCookies();
614 }
615
616 void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
617 {
618     // Delete the cookie.
619     currentWebEngineCookieStorePointer->deleteCookie(cookie);
620 }
621
622 void TabWidget::deleteTab(const int tabIndex)
623 {
624     // Get the tab splitter widget.
625     QWidget *tabSplitterWidgetPointer = qTabWidgetPointer->widget(tabIndex);
626
627     // Get the WebEngine views.
628     PrivacyWebEngineView *privacyWebEngineViewPointer = tabSplitterWidgetPointer->findChild<PrivacyWebEngineView *>();
629     DevToolsWebEngineView *devToolsWebEngineViewPointer = tabSplitterWidgetPointer->findChild<DevToolsWebEngineView *>();
630
631     // Process the tab delete according to the number of tabs.
632     if (qTabWidgetPointer->count() > 1)  // There is more than one tab.
633     {
634         // Remove the tab.
635         qTabWidgetPointer->removeTab(tabIndex);
636
637         // Delete the WebEngine pages to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
638         delete privacyWebEngineViewPointer->page();
639         delete devToolsWebEngineViewPointer->page();
640
641         // Delete the WebEngine views.
642         delete privacyWebEngineViewPointer;
643         delete devToolsWebEngineViewPointer;
644
645         // Delete the tab splitter widget.
646         delete tabSplitterWidgetPointer;
647     }
648     else  // There is only one tab.
649     {
650         // Close Privacy Browser.
651         window()->close();
652     }
653 }
654
655 void TabWidget::findPrevious(const QString &text) const
656 {
657     // Store the current text.
658     currentPrivacyWebEngineViewPointer->findString = text;
659
660     // Find the previous text in the current privacy WebEngine.
661     if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
662         currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively|QWebEnginePage::FindBackward);
663     else
664         currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindBackward);
665 }
666
667 void TabWidget::findText(const QString &text) const
668 {
669     // Store the current text.
670     currentPrivacyWebEngineViewPointer->findString = text;
671
672     // Find the text in the current privacy WebEngine.
673     if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
674         currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively);
675     else
676         currentPrivacyWebEngineViewPointer->findText(text);
677
678     // Clear the currently selected text in the WebEngine page if the find text is empty.
679     if (text.isEmpty())
680         currentWebEnginePagePointer->action(QWebEnginePage::Unselect)->activate(QAction::Trigger);
681 }
682
683 void TabWidget::findTextFinished(const QWebEngineFindTextResult &findTextResult)
684 {
685     // Update the find text UI if it wasn't simply wiping the current find text selection.  Otherwise the UI temporarily flashes `0/0`.
686     if (wipingCurrentFindTextSelection)  // The current selection is being wiped.
687     {
688         // Reset the flag.
689         wipingCurrentFindTextSelection = false;
690     }
691     else  // A new search has been performed.
692     {
693         // Store the result.
694         currentPrivacyWebEngineViewPointer->findTextResult = findTextResult;
695
696         // Update the UI.
697         emit updateFindTextResults(findTextResult);
698     }
699 }
700
701 void TabWidget::forward() const
702 {
703     // Go forward.
704     currentPrivacyWebEngineViewPointer->forward();
705 }
706
707 void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
708 {
709     // Make it so.
710     emit fullScreenRequested(fullScreenRequest.toggleOn());
711
712     // Accept the request.
713     fullScreenRequest.accept();
714 }
715
716 std::list<QNetworkCookie>* TabWidget::getCookieList() const
717 {
718     // Return the current cookie list.
719     return currentPrivacyWebEngineViewPointer->cookieListPointer;
720 }
721
722 QIcon TabWidget::getCurrentTabFavoritIcon() const
723 {
724     // Return the current Privacy WebEngine favorite icon.
725     return currentPrivacyWebEngineViewPointer->favoriteIcon;
726 }
727
728 QString TabWidget::getCurrentTabTitle() const
729 {
730     // Return the current Privacy WebEngine title.
731     return currentPrivacyWebEngineViewPointer->title();
732 }
733
734 QString TabWidget::getCurrentTabUrl() const
735 {
736     // Return the current Privacy WebEngine URL as a string.
737     return currentPrivacyWebEngineViewPointer->url().toString();
738 }
739
740 QString TabWidget::getCurrentUserAgent() const
741 {
742     // Return the current WebEngine user agent.
743     return currentWebEngineProfilePointer->httpUserAgent();
744 }
745
746 QString& TabWidget::getDomainSettingsName() const
747 {
748     // Return the domain settings name.
749     return currentPrivacyWebEngineViewPointer->domainSettingsName;
750 }
751
752 void TabWidget::home() const
753 {
754     // Load the homepage.
755     currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
756 }
757
758 PrivacyWebEngineView* TabWidget::loadBlankInitialWebsite()
759 {
760     // Apply the application settings.
761     applyApplicationSettings();
762
763     // Return the current privacy WebEngine view pointer.
764     return currentPrivacyWebEngineViewPointer;
765 }
766
767 void TabWidget::loadInitialWebsite()
768 {
769     // Apply the application settings.
770     applyApplicationSettings();
771
772     // Get the arguments.
773     QStringList argumentsStringList = qApp->arguments();
774
775     // Check to see if the arguments lists contains a URL.
776     if (argumentsStringList.size() > 1)
777     {
778         // Load the URL from the arguments list.
779         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
780     }
781     else
782     {
783         // Load the homepage.
784         home();
785     }
786 }
787
788 void TabWidget::loadUrlFromLineEdit(QString url) const
789 {
790     // Decide if the text is more likely to be a URL or a search.
791     if (url.startsWith("file://") || url.startsWith("view-source:"))  // The text is likely a file or view source URL.
792     {
793         // Load the URL.
794         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
795     }
796     else if (url.contains("."))  // The text is likely a URL.
797     {
798         // Check if the URL does not start with a valid protocol.
799         if (!url.startsWith("http"))
800         {
801             // Add `https://` to the beginning of the URL.
802             url = "https://" + url;
803         }
804
805         // Load the URL.
806         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
807     }
808     else  // The text is likely a search.
809     {
810         // Load the search.
811         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
812     }
813 }
814
815 void TabWidget::mouseBack() const
816 {
817     // Go back if possible.
818     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
819     {
820         // Clear the URL line edit focus.
821         emit clearUrlLineEditFocus();
822
823         // Go back.
824         currentPrivacyWebEngineViewPointer->back();
825     }
826 }
827
828 void TabWidget::mouseForward() const
829 {
830     // Go forward if possible.
831     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
832     {
833         // Clear the URL line edit focus.
834         emit clearUrlLineEditFocus();
835
836         // Go forward.
837         currentPrivacyWebEngineViewPointer->forward();
838     }
839 }
840
841 void TabWidget::pageLinkHovered(const QString &linkUrl) const
842 {
843     // Emit a signal so that the browser window can update the status bar.
844     emit linkHovered(linkUrl);
845 }
846
847 void TabWidget::stopLoadingFavoriteIconMovie() const
848 {
849     // 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>
850     loadingFavoriteIconMoviePointer->stop();
851 }
852
853 void TabWidget::print() const
854 {
855     // Create a printer.
856     QPrinter printer;
857
858     // Set the resolution to be 300 dpi.
859     printer.setResolution(300);
860
861     // Create a printer dialog.
862     QPrintDialog printDialog(&printer, currentPrivacyWebEngineViewPointer);
863
864     // Display the dialog and print the page if instructed.
865     if (printDialog.exec() == QDialog::Accepted)
866         printWebpage(&printer);
867 }
868
869 void TabWidget::printPreview() const
870 {
871     // Create a printer.
872     QPrinter printer;
873
874     // Set the resolution to be 300 dpi.
875     printer.setResolution(300);
876
877     // Create a print preview dialog.
878     QPrintPreviewDialog printPreviewDialog(&printer, currentPrivacyWebEngineViewPointer);
879
880     // Generate the print preview.
881     connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
882
883     // Display the dialog.
884     printPreviewDialog.exec();
885 }
886
887 void TabWidget::printWebpage(QPrinter *printerPointer) const
888 {
889     // Create an event loop.  For some reason, the print preview doesn't produce any output unless it is run inside an event loop.
890     QEventLoop eventLoop;
891
892     // Print the webpage, converting the callback above into a `QWebEngineCallback<bool>`.
893     // Printing requires that the printer be a pointer, not a reference, or it will crash with much cursing.
894     currentWebEnginePagePointer->print(printerPointer, [&eventLoop](bool printSuccess)
895     {
896         // Instruct the compiler to ignore the unused parameter.
897         (void) printSuccess;
898
899         // Quit the loop.
900         eventLoop.quit();
901     });
902
903     // Execute the loop.
904     eventLoop.exec();
905 }
906
907 void TabWidget::refresh() const
908 {
909     // Reset the HTTP authentication dialog counter.
910     currentPrivacyWebEngineViewPointer->httpAuthenticationDialogsDisplayed = 0;
911
912     // Reload the website.
913     currentPrivacyWebEngineViewPointer->reload();
914 }
915
916 void TabWidget::reloadAndBypassCache() const
917 {
918     // Reload the website, bypassing the cache.
919     currentWebEnginePagePointer->triggerAction(QWebEnginePage::ReloadAndBypassCache);
920 }
921
922 void TabWidget::saveArchive()
923 {
924     // Get the suggested file name.
925     QString suggestedFileName = currentPrivacyWebEngineViewPointer->title() + ".mht";
926
927     // Get the download directory.
928     QString downloadDirectory = Settings::downloadDirectory();
929
930     // Resolve the system download directory if specified.
931     if (downloadDirectory == QLatin1String("System Download Directory"))
932         downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
933
934     // Get a file path from the file picker.
935     QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
936
937     // Save the webpage as an archive if the file save path is populated.
938     if (!saveFilePath.isEmpty())
939     {
940         // Update the download directory if specified.
941         if (Settings::autoUpateDownloadDirectory())
942             updateDownloadDirectory(saveFilePath);
943
944         // Set the saving archive flag.  Otherwise, a second download tries to run.
945         savingArchive = true;
946
947         // Save the archive.
948         currentWebEnginePagePointer->save(saveFilePath);
949     }
950 }
951
952 void TabWidget::setTabBarVisible(const bool visible) const
953 {
954     // Set the tab bar visibility.
955     qTabWidgetPointer->tabBar()->setVisible(visible);
956 }
957
958 void TabWidget::showSaveDialog(QWebEngineDownloadItem *webEngineDownloadItemPointer)
959 {
960     // Only show the save dialog if an archive is not currently being saved.  Otherwise, two save dialogs will be shown.
961     if (!savingArchive)
962     {
963         // Get the download attributes.
964         QUrl downloadUrl = webEngineDownloadItemPointer->url();
965         QString mimeTypeString = webEngineDownloadItemPointer->mimeType();
966         QString suggestedFileName = webEngineDownloadItemPointer->suggestedFileName();
967         int totalBytes = webEngineDownloadItemPointer->totalBytes();
968
969         // Check to see if Privacy Browser is not running KDE or if local storage (cookies) is enabled.
970         if (!isRunningKde || currentPrivacyWebEngineViewPointer->localStorageEnabled)  // KDE is not running or local storage (cookies) is enabled.  Use WebEngine's downloader.
971         {
972             // Instantiate the save dialog.
973             SaveDialog *saveDialogPointer = new SaveDialog(this, downloadUrl, mimeTypeString, totalBytes);
974
975             // Display the save dialog.
976             int saveDialogResult = saveDialogPointer->exec();
977
978             // Process the save dialog results.
979             if (saveDialogResult == QDialog::Accepted)  // Save was selected.
980             {
981                 // Get the download directory.
982                 QString downloadDirectory = Settings::downloadDirectory();
983
984                 // Resolve the system download directory if specified.
985                 if (downloadDirectory == QLatin1String("System Download Directory"))
986                     downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
987
988                 // Get a file path from the file picker.
989                 QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
990
991                 // Process the save file path.
992                 if (!saveFilePath.isEmpty())  // The file save path is populated.
993                 {
994                     // Update the download directory if specified.
995                     if (Settings::autoUpateDownloadDirectory())
996                         updateDownloadDirectory(saveFilePath);
997
998                     // Create a save file path file info.
999                     QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath);
1000
1001                     // Get the canonical save path and file name.
1002                     QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
1003                     QString saveFileName = saveFilePathFileInfo.fileName();
1004
1005                     // Set the download directory and file name.
1006                     webEngineDownloadItemPointer->setDownloadDirectory(absoluteSavePath);
1007                     webEngineDownloadItemPointer->setDownloadFileName(saveFileName);
1008
1009                     // Create a file download notification.
1010                     KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
1011
1012                     // Set the notification title.
1013                     fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
1014
1015                     // Set the notification text.
1016                     fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));
1017
1018                     // Get the download icon from the theme.
1019                     QIcon downloadIcon = QIcon::fromTheme(QLatin1String("download"), QIcon::fromTheme(QLatin1String("document-save")));
1020
1021                     // Set the notification icon.
1022                     fileDownloadNotificationPointer->setIconName(downloadIcon.name());
1023
1024                     // Set the action list cancel button.
1025                     fileDownloadNotificationPointer->setActions(QStringList({i18nc("Download notification action","Cancel")}));
1026
1027                     // Prevent the notification from being autodeleted if it is closed.  Otherwise, the updates to the notification below cause a crash.
1028                     fileDownloadNotificationPointer->setAutoDelete(false);
1029
1030                     // Handle clicks on the cancel button.
1031                     connect(fileDownloadNotificationPointer, &KNotification::action1Activated, [webEngineDownloadItemPointer, saveFileName] ()
1032                     {
1033                         // Cancel the download.
1034                         webEngineDownloadItemPointer->cancel();
1035
1036                         // Create a file download notification.
1037                         KNotification *canceledDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
1038
1039                         // Set the notification title.
1040                         canceledDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
1041
1042                         // Set the new text.
1043                         canceledDownloadNotificationPointer->setText(i18nc("Download canceled notification", "%1 download canceled", saveFileName));
1044
1045                         // Set the notification icon.
1046                         canceledDownloadNotificationPointer->setIconName(QLatin1String("download"));
1047
1048                         // Display the notification.
1049                         canceledDownloadNotificationPointer->sendEvent();
1050                     });
1051
1052                     // Update the notification when the download progresses.
1053                     connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::downloadProgress, [fileDownloadNotificationPointer, saveFileName] (qint64 bytesReceived, qint64 totalBytes)
1054                     {
1055                         // Set the new text.  Total bytes will be 0 if the download size is unknown.
1056                         if (totalBytes > 0)
1057                         {
1058                             // Calculate the download percentage.
1059                             int downloadPercentage = 100 * bytesReceived / totalBytes;
1060
1061                             // Set the file download notification text.
1062                             fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1\% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
1063                                                                         bytesReceived, totalBytes));
1064                         }
1065                         else
1066                         {
1067                             // Set the file download notification text.
1068                             fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1:  %2 bytes downloaded", saveFileName, bytesReceived));
1069                         }
1070
1071                         // Display the updated notification.
1072                         fileDownloadNotificationPointer->update();
1073                     });
1074
1075                     // Update the notification when the download finishes.  The save file name must be copied into the lambda or a crash occurs.
1076                     connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::finished, [fileDownloadNotificationPointer, saveFileName, saveFilePath] ()
1077                     {
1078                         // Set the new text.
1079                         fileDownloadNotificationPointer->setText(i18nc("Download finished notification text", "%1 download finished", saveFileName));
1080
1081                         // Set the URL so the file options will be displayed.
1082                         fileDownloadNotificationPointer->setUrls(QList<QUrl> {QUrl(saveFilePath)});
1083
1084                         // Remove the actions from the notification.
1085                         fileDownloadNotificationPointer->setActions(QStringList());
1086
1087                         // Set the notification to disappear after a timeout.
1088                         fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout);
1089
1090                         // Display the updated notification.
1091                         fileDownloadNotificationPointer->update();
1092                     });
1093
1094                     // Display the notification.
1095                     fileDownloadNotificationPointer->sendEvent();
1096
1097                     // Start the download.
1098                     webEngineDownloadItemPointer->accept();
1099                 }
1100                 else  // The file save path is not populated.
1101                 {
1102                     // Cancel the download.
1103                     webEngineDownloadItemPointer->cancel();
1104                 }
1105             }
1106             else  // Cancel was selected.
1107             {
1108                 // Cancel the download.
1109                 webEngineDownloadItemPointer->cancel();
1110             }
1111         }
1112         else  // KDE is running and local storage (cookies) is disabled.  Use KDE's native downloader.
1113             // 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.
1114         {
1115             // Instantiate the save dialog.  `true` instructs it to use the native downloader
1116             SaveDialog *saveDialogPointer = new SaveDialog(this, downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true);
1117
1118             // Connect the save button.
1119             connect(saveDialogPointer, SIGNAL(useNativeKdeDownloader(QUrl &, QString &)), this, SLOT(useNativeKdeDownloader(QUrl &, QString &)));
1120
1121             // Show the dialog.
1122             saveDialogPointer->show();
1123         }
1124     }
1125
1126     // Reset the saving archive flag.
1127     savingArchive = false;
1128 }
1129
1130 void TabWidget::stop() const
1131 {
1132     // Stop the loading of the current privacy WebEngine.
1133     currentPrivacyWebEngineViewPointer->stop();
1134 }
1135
1136 void TabWidget::toggleDeveloperTools(const bool enabled) const
1137 {
1138     // Get a handle for the current developer tools WebEngine.
1139     DevToolsWebEngineView *devToolsWebEngineViewPointer = qTabWidgetPointer->currentWidget()->findChild<DevToolsWebEngineView *>();
1140
1141     if (enabled)
1142     {
1143         // Set the zoom factor on the development tools WebEngine.
1144         devToolsWebEngineViewPointer->setZoomFactor(currentWebEnginePagePointer->zoomFactor());
1145
1146         // Enable the development tools.
1147         currentWebEnginePagePointer->setDevToolsPage(devToolsWebEngineViewPointer->page());
1148
1149         // Enable JavaScript on the development tools WebEngine.
1150         devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
1151
1152         // Display the developer tools.
1153         devToolsWebEngineViewPointer->setVisible(true);
1154     }
1155     else
1156     {
1157         // Disable JavaScript on the development tools WebEngine to prevent error messages from being written to the console.
1158         devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
1159
1160         // Disable the development tools.
1161         currentWebEnginePagePointer->setDevToolsPage(nullptr);
1162
1163         // Hide the developer tools.
1164         devToolsWebEngineViewPointer->setVisible(false);
1165     }
1166 }
1167
1168 void TabWidget::toggleDomStorage() const
1169 {
1170     // Toggle DOM storage.
1171     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1172
1173     // Update the DOM storage action.
1174     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1175
1176     // Reload the website.
1177     currentPrivacyWebEngineViewPointer->reload();
1178 }
1179
1180 void TabWidget::toggleFindCaseSensitive(const QString &text)
1181 {
1182     // Toggle find case sensitive.
1183     currentPrivacyWebEngineViewPointer->findCaseSensitive = !currentPrivacyWebEngineViewPointer->findCaseSensitive;
1184
1185     // Set the wiping current find text selection flag.
1186     wipingCurrentFindTextSelection = true;
1187
1188     // Wipe the previous search.  Otherwise currently highlighted words will remain highlighted.
1189     findText(QLatin1String(""));
1190
1191     // Update the find text.
1192     findText(text);
1193 }
1194
1195 void TabWidget::toggleJavaScript() const
1196 {
1197     // Toggle JavaScript.
1198     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1199
1200     // Update the JavaScript action.
1201     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1202
1203     // Reload the website.
1204     currentPrivacyWebEngineViewPointer->reload();
1205 }
1206
1207 void TabWidget::toggleLocalStorage()
1208 {
1209     // Toggle local storage.
1210     currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
1211
1212     // Update the local storage action.
1213     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1214
1215     // Reload the website.
1216     currentPrivacyWebEngineViewPointer->reload();
1217 }
1218
1219 void TabWidget::updateDownloadDirectory(QString newDownloadDirectory) const
1220 {
1221     // Remove the file name from the save file path.
1222     newDownloadDirectory.truncate(newDownloadDirectory.lastIndexOf(QLatin1Char('/')));
1223
1224     // Update the download location.
1225     Settings::setDownloadDirectory(newDownloadDirectory);
1226
1227     // Get a handle for the KConfig skeleton.
1228     KConfigSkeleton *kConfigSkeletonPointer = Settings::self();
1229
1230     // Write the settings to disk.
1231     kConfigSkeletonPointer->save();
1232 }
1233
1234 void TabWidget::updateUiFromWebEngineView(const PrivacyWebEngineView *privacyWebEngineViewPointer) const
1235 {
1236     // Only update the UI if the signal was emitted from the current privacy WebEngine.
1237     if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
1238     {
1239         // Update the UI.
1240         emit updateDefaultZoomFactor(currentPrivacyWebEngineViewPointer->defaultZoomFactor);
1241         emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
1242         emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1243         emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1244         emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1245         emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
1246         emit updateZoomActions(currentPrivacyWebEngineViewPointer->zoomFactor());
1247     }
1248 }
1249
1250 void TabWidget::updateUiWithTabSettings()
1251 {
1252     // Update the current WebEngine pointers.
1253     currentPrivacyWebEngineViewPointer = qTabWidgetPointer->currentWidget()->findChild<PrivacyWebEngineView *>();
1254     currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
1255     currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
1256     currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
1257     currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
1258     currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();
1259
1260     // Clear the URL line edit focus.
1261     emit clearUrlLineEditFocus();
1262
1263     // Get a handle for the development tools WebEngine view.
1264     DevToolsWebEngineView *devToolsWebEngineViewPointer = qTabWidgetPointer->currentWidget()->findChild<DevToolsWebEngineView *>();
1265
1266     // Update the actions.
1267     emit updateDefaultZoomFactor(currentPrivacyWebEngineViewPointer->defaultZoomFactor);
1268     emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
1269     emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
1270     emit updateDeveloperToolsAction(devToolsWebEngineViewPointer->isVisible());
1271     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1272     emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
1273     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1274     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1275     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
1276     emit updateZoomActions(currentPrivacyWebEngineViewPointer->zoomFactor());
1277
1278     // Update the URL.
1279     emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
1280     emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
1281     emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
1282
1283     // Update the find text.
1284     emit updateFindText(currentPrivacyWebEngineViewPointer->findString, currentPrivacyWebEngineViewPointer->findCaseSensitive);
1285     emit updateFindTextResults(currentPrivacyWebEngineViewPointer->findTextResult);
1286
1287     // Update the progress bar.
1288     if (currentPrivacyWebEngineViewPointer->loadProgressInt >= 0)
1289         emit showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt);
1290     else
1291         emit hideProgressBar();
1292 }
1293
1294 void TabWidget::useNativeKdeDownloader(QUrl &downloadUrl, QString &suggestedFileName)
1295 {
1296     // Get the download directory.
1297     QString downloadDirectory = Settings::downloadDirectory();
1298
1299     // Resolve the system download directory if specified.
1300     if (downloadDirectory == QLatin1String("System Download Directory"))
1301         downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
1302
1303     // Create a save file dialog.
1304     QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
1305
1306     // Tell the dialog to use a save button.
1307     saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
1308
1309     // Populate the file name from the download item pointer.
1310     saveFileDialogPointer->selectFile(suggestedFileName);
1311
1312     // Prevent interaction with the parent window while the dialog is open.
1313     saveFileDialogPointer->setWindowModality(Qt::WindowModal);
1314
1315     // Process the saving of the file.  The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
1316     auto saveFile = [saveFileDialogPointer, downloadUrl, this] ()
1317     {
1318         // Get the save location.  The dialog box should only allow the selecting of one file location.
1319         QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
1320
1321         // Update the download directory if specified.
1322         if (Settings::autoUpateDownloadDirectory())
1323             updateDownloadDirectory(saveLocation.toLocalFile());
1324
1325         // Create a file copy job.  `-1` creates the file with default permissions.
1326         KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
1327
1328         // Set the download job to display any warning and error messages.
1329         fileCopyJobPointer->uiDelegate()->setAutoWarningHandlingEnabled(true);
1330         fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
1331
1332         // Start the download.
1333         fileCopyJobPointer->start();
1334     };
1335
1336     // Handle clicks on the save button.
1337     connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
1338
1339     // Show the dialog.
1340     saveFileDialogPointer->show();
1341 }