]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blob - src/widgets/TabWidget.cpp
eb82f23d2fed09c49c4739479963ff2aba519fc5
[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     // Handle full screen requests.
220     connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
221
222     // Listen for hovered link URLs.
223     connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
224
225     // Handle file downloads.
226     connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
227
228     // Instantiate the URL request interceptor.
229     UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
230
231     // Set the URL request interceptor.
232     webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
233
234     // Reapply the domain settings when the host changes.
235     connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
236
237     // Set the local storage filter.
238     webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
239     {
240         // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
241         if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
242         {
243             //qDebug().noquote().nospace() << "Third-party request blocked:  " << filterRequest.origin;
244
245             // Return false.
246             return false;
247         }
248
249         // Allow the request if local storage is enabled.
250         if (privacyWebEngineViewPointer->localStorageEnabled)
251         {
252             //qDebug().noquote().nospace() << "Request allowed by local storage:  " << filterRequest.origin;
253
254             // Return true.
255             return true;
256         }
257
258         //qDebug().noquote().nospace() << "Request blocked by default:  " << filterRequest.origin;
259
260         // Block any remaining local storage requests.
261         return false;
262     });
263
264     // Disable JavaScript by default (this prevetns JavaScript from being enabled on a new tab before domain settings are loaded).
265     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
266
267     // Don't allow JavaScript to open windows.
268     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
269
270     // Allow keyboard navigation.
271     webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
272
273     // Enable full screen support.
274     webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
275
276     // Require user interaction to play media.
277     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
278
279     // Limit WebRTC to public IP addresses.
280     webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
281
282     // Update the cookies action.
283     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [privacyWebEngineViewPointer, this] (const int numberOfCookies)
284     {
285         // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
286         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
287             emit updateCookiesAction(numberOfCookies);
288     });
289
290     // Process cookie changes.
291     connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(addCookieToList(QNetworkCookie)));
292     connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(removeCookieFromList(QNetworkCookie)));
293
294     // Get a list of durable cookies.
295     QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
296
297     // Add the durable cookies to the store.
298     for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
299         addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
300
301     // Update the title when it changes.
302     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, privacyWebEngineViewPointer] (const QString &title)
303     {
304         // Get the index for this tab.
305         int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
306
307         // Update the title for this tab.
308         tabWidgetPointer->setTabText(tabIndex, title);
309
310         // Update the window title if this is the current tab.
311         if (tabIndex == tabWidgetPointer->currentIndex())
312             emit updateWindowTitle(title);
313     });
314
315     // Update the icon when it changes.
316     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [privacyWebEngineViewPointer, this] (const QIcon &icon)
317     {
318         // Get the index for this tab.
319         int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
320
321         // Update the icon for this tab.
322         if (icon.isNull())
323             tabWidgetPointer->setTabIcon(tabIndex, defaultTabIcon);
324         else
325             tabWidgetPointer->setTabIcon(tabIndex, icon);
326     });
327
328     // Move to the new tab.
329     tabWidgetPointer->setCurrentIndex(newTabIndex);
330
331     // Clear the URL line edit focus so that it populates correctly when opening a new tab from the context menu.
332     if (focusNewWebEngineView)
333         emit clearUrlLineEditFocus();
334
335     // Return the privacy WebEngine view pointer.
336     return privacyWebEngineViewPointer;
337 }
338
339 void TabWidget::applyApplicationSettings()
340 {
341     // Set the tab position.
342     if (Settings::tabsOnTop())
343         tabWidgetPointer->setTabPosition(QTabWidget::North);
344     else
345         tabWidgetPointer->setTabPosition(QTabWidget::South);
346
347     // Set the search engine URL.
348     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
349
350     // Emit the update search engine actions signal.
351     emit updateSearchEngineActions(Settings::searchEngine(), true);
352 }
353
354 // 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.
355 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
356 void TabWidget::applyDomainSettingsAndReload()
357 {
358     // Apply the domain settings.  `true` reloads the website.
359     applyDomainSettings(currentPrivacyWebEngineViewPointer->url().host(), true);
360 }
361
362 // 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.
363 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
364 void TabWidget::applyDomainSettingsWithoutReloading(const QString &hostname)
365 {
366     // Apply the domain settings  `false` does not reload the website.
367     applyDomainSettings(hostname, false);
368 }
369
370 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
371 void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
372 {
373     // Get the record for the hostname.
374     QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
375
376     // Check if the hostname has domain settings.
377     if (domainQuery.isValid())  // The hostname has domain settings.
378     {
379         // Get the domain record.
380         QSqlRecord domainRecord = domainQuery.record();
381
382         // Store the domain settings name.
383         currentPrivacyWebEngineViewPointer->domainSettingsName = domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString();
384
385         // Set the JavaScript status.
386         switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
387         {
388             // Set the default JavaScript status.
389             case (DomainsDatabase::SYSTEM_DEFAULT):
390             {
391                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
392
393                 break;
394             }
395
396             // Disable JavaScript.
397             case (DomainsDatabase::DISABLED):
398             {
399                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
400
401                 break;
402             }
403
404             // Enable JavaScript.
405             case (DomainsDatabase::ENABLED):
406             {
407                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
408
409                 break;
410             }
411         }
412
413         // Set the local storage status.
414         switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
415         {
416             // Set the default local storage status.
417             case (DomainsDatabase::SYSTEM_DEFAULT):
418             {
419                 currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
420
421                 break;
422             }
423
424             // Disable local storage.
425             case (DomainsDatabase::DISABLED):
426             {
427                 currentPrivacyWebEngineViewPointer->localStorageEnabled = false;
428
429                 break;
430             }
431
432             // Enable local storage.
433             case (DomainsDatabase::ENABLED):
434             {
435                 currentPrivacyWebEngineViewPointer->localStorageEnabled = true;
436
437                 break;
438             }
439         }
440
441         // Set the DOM storage status.
442         switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
443         {
444             // Set the default DOM storage status.
445             case (DomainsDatabase::SYSTEM_DEFAULT):
446             {
447                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
448
449                 break;
450             }
451
452             // Disable DOM storage.
453             case (DomainsDatabase::DISABLED):
454             {
455                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
456
457                 break;
458             }
459
460             // Enable DOM storage.
461             case (DomainsDatabase::ENABLED):
462             {
463                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
464
465                 break;
466             }
467         }
468
469         // Set the user agent.
470         currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));
471
472         // Check if a custom zoom factor is set.
473         if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
474         {
475             // Store the current zoom factor.
476             currentZoomFactor = domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble();
477         }
478         else
479         {
480             // Reset the current zoom factor.
481             currentZoomFactor = Settings::zoomFactor();
482         }
483
484         // Set the zoom factor.    The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
485         currentPrivacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
486     }
487     else  // The hostname does not have domain settings.
488     {
489         // Reset the domain settings name.
490         currentPrivacyWebEngineViewPointer->domainSettingsName = QStringLiteral("");
491
492         // Set the JavaScript status.
493         currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
494
495         // Set the local storage status.
496         currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
497
498         // Set DOM storage.  In QWebEngineSettings it is called Local Storage.
499         currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
500
501         // Set the user agent.
502         currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
503
504         // Store the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
505         currentZoomFactor = Settings::zoomFactor();
506
507         // Set the zoom factor.
508         currentPrivacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
509     }
510
511     // Update the UI.
512     emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
513     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
514     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
515     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
516     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
517     emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
518
519     // Reload the website if requested.
520     if (reloadWebsite)
521         currentPrivacyWebEngineViewPointer->reload();
522 }
523
524 void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
525 {
526     // Store the search engine name.
527     QString searchEngineName = searchEngineActionPointer->text();
528
529     // Strip out any `&` characters.
530     searchEngineName.remove('&');
531
532     // Store the search engine string.
533     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
534
535     // Update the search engine actionas.
536     emit updateSearchEngineActions(searchEngineName, false);
537 }
538
539 void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
540 {
541     // Get the user agent name.
542     QString userAgentName = userAgentActionPointer->text();
543
544     // Strip out any `&` characters.
545     userAgentName.remove('&');
546
547     // Apply the user agent.
548     currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
549
550     // Update the user agent actions.
551     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
552
553     // Reload the website.
554     currentPrivacyWebEngineViewPointer->reload();
555 }
556
557 // This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
558 void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor)
559 {
560     // Update the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
561     currentZoomFactor = zoomFactor;
562
563     // Set the zoom factor.
564     currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactor);
565 }
566
567 void TabWidget::back() const
568 {
569     // Go back.
570     currentPrivacyWebEngineViewPointer->back();
571 }
572
573 void TabWidget::deleteAllCookies() const
574 {
575     // Delete all the cookies.
576     currentWebEngineCookieStorePointer->deleteAllCookies();
577 }
578
579 void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
580 {
581     // Delete the cookie.
582     currentWebEngineCookieStorePointer->deleteCookie(cookie);
583 }
584
585 void TabWidget::deleteTab(const int tabIndex)
586 {
587     // Get the privacy WebEngine view.
588     PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(tabIndex));
589
590     // Proccess the tab delete according to the number of tabs.
591     if (tabWidgetPointer->count() > 1)  // There is more than one tab.
592     {
593         // Delete the tab.
594         tabWidgetPointer->removeTab(tabIndex);
595
596         // Delete the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
597         delete privacyWebEngineViewPointer->page();
598
599         // Delete the privacy WebEngine view.
600         delete privacyWebEngineViewPointer;
601     }
602     else  // There is only one tab.
603     {
604         // Close Privacy Browser.
605         window()->close();
606     }
607 }
608
609 void TabWidget::forward() const
610 {
611     // Go forward.
612     currentPrivacyWebEngineViewPointer->forward();
613 }
614
615 void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
616 {
617     // Make it so.
618     emit fullScreenRequested(fullScreenRequest.toggleOn());
619
620     // Accept the request.
621     fullScreenRequest.accept();
622 }
623
624 std::list<QNetworkCookie>* TabWidget::getCookieList() const
625 {
626     // Return the current cookie list.
627     return currentPrivacyWebEngineViewPointer->cookieListPointer;
628 }
629
630 QString& TabWidget::getDomainSettingsName() const
631 {
632     // Return the domain settings name.
633     return currentPrivacyWebEngineViewPointer->domainSettingsName;
634 }
635
636 void TabWidget::home() const
637 {
638     // Load the homepage.
639     currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
640 }
641
642 PrivacyWebEngineView* TabWidget::loadBlankInitialWebsite()
643 {
644     // Apply the application settings.
645     applyApplicationSettings();
646
647     // Return the current privacy WebEngine view pointer.
648     return currentPrivacyWebEngineViewPointer;
649 }
650
651 void TabWidget::loadInitialWebsite()
652 {
653     // Apply the application settings.
654     applyApplicationSettings();
655
656     // Get the arguments.
657     QStringList argumentsStringList = qApp->arguments();
658
659     // Check to see if the arguments lists contains a URL.
660     if (argumentsStringList.size() > 1)
661     {
662         // Load the URL from the arguments list.
663         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
664     }
665     else
666     {
667         // Load the homepage.
668         home();
669     }
670 }
671
672 void TabWidget::loadUrlFromLineEdit(QString url) const
673 {
674     // Decide if the text is more likely to be a URL or a search.
675     if (url.startsWith("file://"))  // The text is likely a file URL.
676     {
677         // Load the URL.
678         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
679     }
680     else if (url.contains("."))  // The text is likely a URL.
681     {
682         // Check if the URL does not start with a valid protocol.
683         if (!url.startsWith("http"))
684         {
685             // Add `https://` to the beginning of the URL.
686             url = "https://" + url;
687         }
688
689         // Load the URL.
690         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
691     }
692     else  // The text is likely a search.
693     {
694         // Load the search.
695         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
696     }
697 }
698
699 void TabWidget::mouseBack() const
700 {
701     // Go back if possible.
702     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
703     {
704         // Clear the URL line edit focus.
705         emit clearUrlLineEditFocus();
706
707         // Go back.
708         currentPrivacyWebEngineViewPointer->back();
709     }
710 }
711
712 void TabWidget::mouseForward() const
713 {
714     // Go forward if possible.
715     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
716     {
717         // Clear the URL line edit focus.
718         emit clearUrlLineEditFocus();
719
720         // Go forward.
721         currentPrivacyWebEngineViewPointer->forward();
722     }
723 }
724
725 void TabWidget::pageLinkHovered(const QString &linkUrl) const
726 {
727     // Emit a signal so that the browser window can update the status bar.
728     emit linkHovered(linkUrl);
729 }
730
731 void TabWidget::print() const
732 {
733     // Create a printer.
734     QPrinter printer;
735
736     // Set the resolution to be 300 dpi.
737     printer.setResolution(300);
738
739     // Create a printer dialog.
740     QPrintDialog printDialog(&printer, currentPrivacyWebEngineViewPointer);
741
742     // Display the dialog and print the page if instructed.
743     if (printDialog.exec() == QDialog::Accepted)
744         printWebpage(&printer);
745 }
746
747 void TabWidget::printPreview() const
748 {
749     // Create a printer.
750     QPrinter printer;
751
752     // Set the resolution to be 300 dpi.
753     printer.setResolution(300);
754
755     // Create a print preview dialog.
756     QPrintPreviewDialog printPreviewDialog(&printer, currentPrivacyWebEngineViewPointer);
757
758     // Generate the print preview.
759     connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
760
761     // Display the dialog.
762     printPreviewDialog.exec();
763 }
764
765 void TabWidget::printWebpage(QPrinter *printerPointer) const
766 {
767     // Create an event loop.  For some reason, the print preview doesn't produce any output unless it is run inside an event loop.
768     QEventLoop eventLoop;
769
770     // Print the webpage, converting the callback above into a `QWebEngineCallback<bool>`.
771     // Printing requires that the printer be a pointer, not a reference, or it will crash with much cursing.
772     currentWebEnginePagePointer->print(printerPointer, [&eventLoop](bool printSuccess)
773     {
774         // Instruct the compiler to ignore the unused parameter.
775         (void) printSuccess;
776
777         // Quit the loop.
778         eventLoop.quit();
779     });
780
781     // Execute the loop.
782     eventLoop.exec();
783 }
784
785 void TabWidget::refresh() const
786 {
787     // Reload the website.
788     currentPrivacyWebEngineViewPointer->reload();
789 }
790
791 void TabWidget::setTabBarVisible(const bool visible) const
792 {
793     // Set the tab bar visibility.
794     tabWidgetPointer->tabBar()->setVisible(visible);
795 }
796
797 void TabWidget::showSaveDialog(QWebEngineDownloadItem *downloadItemPointer) const
798 {
799     // Instantiate the save dialog.
800     SaveDialog *saveDialogPointer = new SaveDialog(downloadItemPointer);
801
802     // Connect the save button.
803     connect(saveDialogPointer, SIGNAL(showSaveFilePickerDialog(QUrl &, QString &)), this, SLOT(showSaveFilePickerDialog(QUrl &, QString &)));
804
805     // Show the dialog.
806     saveDialogPointer->show();
807 }
808
809 void TabWidget::showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName)
810 {
811     // Get the download location.
812     QString downloadDirectory = Settings::downloadLocation();
813
814     // Resolve the system download directory if specified.
815     if (downloadDirectory == QStringLiteral("System Download Directory"))
816         downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
817
818     // Create a save file dialog.
819     QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
820
821     // Tell the dialog to use a save button.
822     saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
823
824     // Populate the file name from the download item pointer.
825     saveFileDialogPointer->selectFile(suggestedFileName);
826
827     // Prevent interaction with the parent window while the dialog is open.
828     saveFileDialogPointer->setWindowModality(Qt::WindowModal);
829
830     // Process the saving of the file.  The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
831     auto saveFile = [saveFileDialogPointer, &downloadUrl] () {
832         // Get the save location.  The dialog box should only allow the selecting of one file location.
833         QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
834
835         // Create a file copy job.  `-1` creates the file with default permissions.
836         KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
837
838         // Set the download job to display any error messages.
839         fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
840
841         // Start the download.
842         fileCopyJobPointer->start();
843     };
844
845     // Handle clicks on the save button.
846     connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
847
848     // Show the dialog.
849     saveFileDialogPointer->show();
850 }
851
852 void TabWidget::toggleDomStorage() const
853 {
854     // Toggle DOM storage.
855     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
856
857     // Update the DOM storage action.
858     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
859
860     // Reload the website.
861     currentPrivacyWebEngineViewPointer->reload();
862 }
863
864 void TabWidget::toggleJavaScript() const
865 {
866     // Toggle JavaScript.
867     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
868
869     // Update the JavaScript action.
870     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
871
872     // Reload the website.
873     currentPrivacyWebEngineViewPointer->reload();
874 }
875
876 void TabWidget::toggleLocalStorage()
877 {
878     // Toggle local storeage.
879     currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
880
881     // Update the local storage action.
882     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
883
884     // Reload the website.
885     currentPrivacyWebEngineViewPointer->reload();
886 }
887
888 void TabWidget::updateUiWithTabSettings()
889 {
890     // Update the current WebEngine pointers.
891     currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->currentWidget());
892     currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
893     currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
894     currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
895     currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
896     currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();
897
898     // Clear the URL line edit focus.
899     emit clearUrlLineEditFocus();
900
901     // Update the UI.
902     emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
903     emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
904     emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
905     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
906     emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
907     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
908     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
909     emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
910     emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
911     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
912     emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
913
914     // Update the progress bar.
915     if (currentPrivacyWebEngineViewPointer->loadProgressInt >= 0)
916         emit showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt);
917     else
918         emit hideProgressBar();
919 }