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