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