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