X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserPC.git;a=blobdiff_plain;f=src%2Fwidgets%2FTabWidget.cpp;h=4edb304d0db6c45ed5d757a6d4a5f6cd20ad5596;hp=eb82f23d2fed09c49c4739479963ff2aba519fc5;hb=06a69a2d38bf73c0c5219f94c345b19142bb1646;hpb=6acd73c4148bac2a8c2f637e70080a43b12fd14e diff --git a/src/widgets/TabWidget.cpp b/src/widgets/TabWidget.cpp index eb82f23..4edb304 100644 --- a/src/widgets/TabWidget.cpp +++ b/src/widgets/TabWidget.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2022 Soren Stoutner . + * Copyright 2022 Soren Stoutner . * * This file is part of Privacy Browser PC . * @@ -27,13 +27,13 @@ #include "dialogs/SaveDialog.h" #include "filters/MouseEventFilter.h" #include "helpers/SearchEngineHelper.h" -#include "helpers/UserAgentHelper.h" #include "interceptors/UrlRequestInterceptor.h" #include "windows/BrowserWindow.h" // KDE Framework headers. #include #include +#include // Qt toolkit headers. #include @@ -45,11 +45,14 @@ #include // Initialize the public static variables. -QString TabWidget::webEngineDefaultUserAgent = QStringLiteral(""); +QString TabWidget::webEngineDefaultUserAgent = QLatin1String(""); // Construct the class. TabWidget::TabWidget(QWidget *parent) : QWidget(parent) { + // Instantiate the user agent helper. + userAgentHelperPointer = new UserAgentHelper(); + // Instantiate the UIs. Ui::TabWidget tabWidgetUi; Ui::AddTabWidget addTabWidgetUi; @@ -112,14 +115,14 @@ void TabWidget::addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *w QUrl url; // Check to see if the domain does not start with a `.` because Qt makes this harder than it should be. - if (!cookie.domain().startsWith(QStringLiteral("."))) + if (!cookie.domain().startsWith(QLatin1String("."))) { // Populate the URL. url.setHost(cookie.domain()); - url.setScheme(QStringLiteral("https")); + url.setScheme(QLatin1String("https")); // Clear the domain from the cookie. - cookie.setDomain(QStringLiteral("")); + cookie.setDomain(QLatin1String("")); } // Add the cookie to the store. @@ -141,7 +144,7 @@ void TabWidget::addFirstTab() tabWidgetPointer->currentWidget()->setFocus(); } -PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView) +PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool backgroundTab) { // Create a privacy WebEngine view. PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView(); @@ -153,7 +156,7 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView) tabWidgetPointer->setTabIcon(newTabIndex, defaultTabIcon); // Create an off-the-record profile (the default when no profile name is specified). - QWebEngineProfile *webEngineProfilePointer = new QWebEngineProfile(QStringLiteral("")); + QWebEngineProfile *webEngineProfilePointer = new QWebEngineProfile(QLatin1String("")); // Create a WebEngine page. QWebEnginePage *webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer); @@ -216,6 +219,9 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView) emit hideProgressBar(); }); + // Display find text results. + connect(webEnginePagePointer, SIGNAL(findTextFinished(const QWebEngineFindTextResult &)), this, SLOT(findTextFinished(const QWebEngineFindTextResult &))); + // Handle full screen requests. connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest))); @@ -325,11 +331,21 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView) tabWidgetPointer->setTabIcon(tabIndex, icon); }); - // Move to the new tab. - tabWidgetPointer->setCurrentIndex(newTabIndex); + // Enable spell checking. + webEngineProfilePointer->setSpellCheckEnabled(true); + + // Set the spell check language. + webEngineProfilePointer->setSpellCheckLanguages({QLatin1String("en_US")}); + + // Populate the zoom factor. This is necessary if a URL is being loaded, like a local URL, that does not trigger `applyDomainSettings()`. + privacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor()); + + // Move to the new tab if it is not a background tab. + if (!backgroundTab) + tabWidgetPointer->setCurrentIndex(newTabIndex); // Clear the URL line edit focus so that it populates correctly when opening a new tab from the context menu. - if (focusNewWebEngineView) + if (removeUrlLineEditFocus) emit clearUrlLineEditFocus(); // Return the privacy WebEngine view pointer. @@ -441,7 +457,7 @@ void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWe // Set the DOM storage status. switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt()) { - // Set the default DOM storage status. + // Set the default DOM storage status. QWebEngineSettings confusingly calls this local storage. case (DomainsDatabase::SYSTEM_DEFAULT): { currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled()); @@ -449,7 +465,7 @@ void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWe break; } - // Disable DOM storage. + // Disable DOM storage. QWebEngineSettings confusingly calls this local storage. case (DomainsDatabase::DISABLED): { currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false); @@ -457,7 +473,7 @@ void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWe break; } - // Enable DOM storage. + // Enable DOM storage. QWebEngineSettings confusingly calls this local storage. case (DomainsDatabase::ENABLED): { currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true); @@ -487,7 +503,7 @@ void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWe else // The hostname does not have domain settings. { // Reset the domain settings name. - currentPrivacyWebEngineViewPointer->domainSettingsName = QStringLiteral(""); + currentPrivacyWebEngineViewPointer->domainSettingsName = QLatin1String(""); // Set the JavaScript status. currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled()); @@ -509,7 +525,7 @@ void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWe } // Update the UI. - emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral("")); + emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String("")); emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled)); emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled); emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled)); @@ -545,7 +561,7 @@ void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const userAgentName.remove('&'); // Apply the user agent. - currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName)); + currentWebEngineProfilePointer->setHttpUserAgent(userAgentHelperPointer->getUserAgentFromTranslatedName(userAgentName)); // Update the user agent actions. emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false); @@ -606,6 +622,52 @@ void TabWidget::deleteTab(const int tabIndex) } } +void TabWidget::findPrevious(const QString &text) const +{ + // Store the current text. + currentPrivacyWebEngineViewPointer->findString = text; + + // Find the previous text in the current privacy WebEngine. + if (currentPrivacyWebEngineViewPointer->findCaseSensitive) + currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively|QWebEnginePage::FindBackward); + else + currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindBackward); +} + +void TabWidget::findText(const QString &text) const +{ + // Store the current text. + currentPrivacyWebEngineViewPointer->findString = text; + + // Find the text in the current privacy WebEngine. + if (currentPrivacyWebEngineViewPointer->findCaseSensitive) + currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively); + else + currentPrivacyWebEngineViewPointer->findText(text); + + // Clear the currently selected text in the WebEngine page if the find text is empty. + if (text.isEmpty()) + currentWebEnginePagePointer->action(QWebEnginePage::Unselect)->activate(QAction::Trigger); +} + +void TabWidget::findTextFinished(const QWebEngineFindTextResult &findTextResult) +{ + // Update the find text UI if it wasn't simply wiping the current find text selection. Otherwise the UI temporarially flashes `0/0`. + if (wipingCurrentFindTextSelection) // The current selection is being wiped. + { + // Reset the flag. + wipingCurrentFindTextSelection = false; + } + else // A new search has been performed. + { + // Store the result. + currentPrivacyWebEngineViewPointer->findTextResult = findTextResult; + + // Update the UI. + emit updateFindTextResults(findTextResult); + } +} + void TabWidget::forward() const { // Go forward. @@ -794,59 +856,159 @@ void TabWidget::setTabBarVisible(const bool visible) const tabWidgetPointer->tabBar()->setVisible(visible); } -void TabWidget::showSaveDialog(QWebEngineDownloadItem *downloadItemPointer) const +void TabWidget::showSaveDialog(QWebEngineDownloadItem *webEngineDownloadItemPointer) { - // Instantiate the save dialog. - SaveDialog *saveDialogPointer = new SaveDialog(downloadItemPointer); + // Get the download attributes. + QUrl downloadUrl = webEngineDownloadItemPointer->url(); + QString mimeTypeString = webEngineDownloadItemPointer->mimeType(); + QString suggestedFileName = webEngineDownloadItemPointer->suggestedFileName(); + int totalBytes = webEngineDownloadItemPointer->totalBytes(); + + // Check to see if local storage (cookies) is enabled. + if (currentPrivacyWebEngineViewPointer->localStorageEnabled) // Local storage (cookies) is enabled. Use WebEngine's downloader. + { + // Instantiate the save dialog. + SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes); - // Connect the save button. - connect(saveDialogPointer, SIGNAL(showSaveFilePickerDialog(QUrl &, QString &)), this, SLOT(showSaveFilePickerDialog(QUrl &, QString &))); + // Display the save dialog. + int saveDialogResult = saveDialogPointer->exec(); - // Show the dialog. - saveDialogPointer->show(); -} + // Process the save dialog results. + if (saveDialogResult == QDialog::Accepted) // Save was selected. + { + // Get the download directory. + QString downloadDirectory = Settings::downloadLocation(); -void TabWidget::showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName) -{ - // Get the download location. - QString downloadDirectory = Settings::downloadLocation(); + // Resolve the system download directory if specified. + if (downloadDirectory == QLatin1String("System Download Directory")) + downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); - // Resolve the system download directory if specified. - if (downloadDirectory == QStringLiteral("System Download Directory")) - downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + // Display a save file dialog. + QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName); - // Create a save file dialog. - QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory); + // Process the save file path. + if (!saveFilePath.isEmpty()) // The file save path is populated. + { + // Create a save file path file info. + QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath); - // Tell the dialog to use a save button. - saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave); + // Get the canonical save path and file name. + QString absoluteSavePath = saveFilePathFileInfo.absolutePath(); + QString saveFileName = saveFilePathFileInfo.fileName(); - // Populate the file name from the download item pointer. - saveFileDialogPointer->selectFile(suggestedFileName); + // Set the download directory and file name. + webEngineDownloadItemPointer->setDownloadDirectory(absoluteSavePath); + webEngineDownloadItemPointer->setDownloadFileName(saveFileName); - // Prevent interaction with the parent window while the dialog is open. - saveFileDialogPointer->setWindowModality(Qt::WindowModal); + // Create a file download notification. + KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload")); - // Process the saving of the file. The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur. - auto saveFile = [saveFileDialogPointer, &downloadUrl] () { - // Get the save location. The dialog box should only allow the selecting of one file location. - QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0); + // Set the notification title. + fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download")); - // Create a file copy job. `-1` creates the file with default permissions. - KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite); + // Set the notification text. + fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName)); - // Set the download job to display any error messages. - fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true); + // Set the notification icon. + fileDownloadNotificationPointer->setIconName(QLatin1String("download")); - // Start the download. - fileCopyJobPointer->start(); - }; + // Set the action list cancel button. + fileDownloadNotificationPointer->setActions(QStringList({i18nc("Download notification action","Cancel")})); - // Handle clicks on the save button. - connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile); + // Set the notification to display indefinitely. + fileDownloadNotificationPointer->setFlags(KNotification::Persistent); - // Show the dialog. - saveFileDialogPointer->show(); + // Prevent the notification from being autodeleted if it is closed. Otherwise, the updates to the notification below cause a crash. + fileDownloadNotificationPointer->setAutoDelete(false); + + // Display the notification. + fileDownloadNotificationPointer->sendEvent(); + + // Handle clicks on the cancel button. + connect(fileDownloadNotificationPointer, &KNotification::action1Activated, [webEngineDownloadItemPointer, saveFileName] () + { + // Cancel the download. + webEngineDownloadItemPointer->cancel(); + + // Create a file download notification. + KNotification *canceledDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload")); + + // Set the notification title. + canceledDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download")); + + // Set the new text. + canceledDownloadNotificationPointer->setText(i18nc("Download canceled notification", "%1 download canceled", saveFileName)); + + // Set the notification icon. + canceledDownloadNotificationPointer->setIconName(QLatin1String("download")); + + // Display the notification. + canceledDownloadNotificationPointer->sendEvent(); + }); + + // Update the notification when the download progresses. + connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::downloadProgress, [fileDownloadNotificationPointer, saveFileName] (qint64 bytesReceived, qint64 totalBytes) + { + // Calculate the download percentage. + int downloadPercentage = 100 * bytesReceived / totalBytes; + + // Set the new text. Total bytes will be 0 if the download size is unknown. + if (totalBytes > 0) + fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1\% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName, + bytesReceived, totalBytes)); + else + fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1: %2 bytes downloaded", saveFileName, bytesReceived)); + + // Display the updated notification. + fileDownloadNotificationPointer->update(); + }); + + // Update the notification when the download finishes. The save file name must be copied into the lambda or a crash occurs. + connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::finished, [fileDownloadNotificationPointer, saveFileName, saveFilePath] () + { + // Set the new text. + fileDownloadNotificationPointer->setText(i18nc("Download finished notification text", "%1 download finished", saveFileName)); + + // Set the URL so the file options will be displayed. + fileDownloadNotificationPointer->setUrls(QList {QUrl(saveFilePath)}); + + // Remove the actions from the notification. + fileDownloadNotificationPointer->setActions(QStringList()); + + // Set the notification to disappear after a timeout. + fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout); + + // Display the updated notification. + fileDownloadNotificationPointer->update(); + }); + + // Start the download. + webEngineDownloadItemPointer->accept(); + } + else // The file save path is not populated. + { + // Cancel the download. + webEngineDownloadItemPointer->cancel(); + } + } + else // Cancel was selected. + { + // Cancel the download. + webEngineDownloadItemPointer->cancel(); + } + } + else // Local storage (cookies) is disabled. Use KDE's native downloader. + // 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. + { + // Instantiate the save dialog. `true` instructs it to use the native downloader + SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true); + + // Connect the save button. + connect(saveDialogPointer, SIGNAL(useNativeDownloader(QUrl &, QString &)), this, SLOT(useNativeDownloader(QUrl &, QString &))); + + // Show the dialog. + saveDialogPointer->show(); + } } void TabWidget::toggleDomStorage() const @@ -861,6 +1023,21 @@ void TabWidget::toggleDomStorage() const currentPrivacyWebEngineViewPointer->reload(); } +void TabWidget::toggleFindCaseSensitive(const QString &text) +{ + // Toggle find case sensitive. + currentPrivacyWebEngineViewPointer->findCaseSensitive = !currentPrivacyWebEngineViewPointer->findCaseSensitive; + + // Set the wiping current find text selection flag. + wipingCurrentFindTextSelection = true; + + // Wipe the previous search. Otherwise currently highlighted words will remain highlighted. + findText(QLatin1String("")); + + // Update the find text. + findText(text); +} + void TabWidget::toggleJavaScript() const { // Toggle JavaScript. @@ -898,22 +1075,72 @@ void TabWidget::updateUiWithTabSettings() // Clear the URL line edit focus. emit clearUrlLineEditFocus(); - // Update the UI. + // Update the actions. emit updateBackAction(currentWebEngineHistoryPointer->canGoBack()); emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size()); - emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral("")); emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled)); emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward()); emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled)); emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled); - emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title()); - emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url()); emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true); emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor()); + // Update the URL. + emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title()); + emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String("")); + emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url()); + + // Update the find text. + emit updateFindText(currentPrivacyWebEngineViewPointer->findString, currentPrivacyWebEngineViewPointer->findCaseSensitive); + emit updateFindTextResults(currentPrivacyWebEngineViewPointer->findTextResult); + // Update the progress bar. if (currentPrivacyWebEngineViewPointer->loadProgressInt >= 0) emit showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt); else emit hideProgressBar(); } + +void TabWidget::useNativeDownloader(QUrl &downloadUrl, QString &suggestedFileName) +{ + // Get the download directory. + QString downloadDirectory = Settings::downloadLocation(); + + // Resolve the system download directory if specified. + if (downloadDirectory == QLatin1String("System Download Directory")) + downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + + // Create a save file dialog. + QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory); + + // Tell the dialog to use a save button. + saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave); + + // Populate the file name from the download item pointer. + saveFileDialogPointer->selectFile(suggestedFileName); + + // Prevent interaction with the parent window while the dialog is open. + saveFileDialogPointer->setWindowModality(Qt::WindowModal); + + // Process the saving of the file. The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur. + auto saveFile = [saveFileDialogPointer, downloadUrl] () + { + // Get the save location. The dialog box should only allow the selecting of one file location. + QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0); + + // Create a file copy job. `-1` creates the file with default permissions. + KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite); + + // Set the download job to display any error messages. + fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true); + + // Start the download. + fileCopyJobPointer->start(); + }; + + // Handle clicks on the save button. + connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile); + + // Show the dialog. + saveFileDialogPointer->show(); +}