From 1ce11bc5f6630bf81aa67bdaca411fbea93dc017 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Sat, 9 Jul 2022 16:29:04 -0700 Subject: [PATCH] Implement file downloads. --- CMakeLists.txt | 1 + src/CMakeLists.txt | 3 + src/dialogs/CMakeLists.txt | 1 + src/dialogs/SaveDialog.cpp | 94 ++++++++++++++++++++++ src/dialogs/SaveDialog.h | 50 ++++++++++++ src/uis/DomainSettingsDialog.ui | 2 +- src/uis/SaveDialog.ui | 137 ++++++++++++++++++++++++++++++++ src/views/BrowserView.cpp | 59 +++++++++++++- src/views/BrowserView.h | 2 + src/windows/BrowserWindow.cpp | 48 +++++------ src/windows/BrowserWindow.h | 4 +- 11 files changed, 373 insertions(+), 28 deletions(-) create mode 100644 src/dialogs/SaveDialog.cpp create mode 100644 src/dialogs/SaveDialog.h create mode 100644 src/uis/SaveDialog.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index d6408ec..ff4b909 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ find_package(KF5 ${KDE_FRAMEWORKS_MIN_VERSION} REQUIRED COMPONENTS DBusAddons DocTools I18n + KIO XmlGui ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 335a27d..499230e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,6 +40,7 @@ ki18n_wrap_ui(privacy-browser uis/CookiesDialog.ui uis/DomainSettingsDialog.ui uis/DurableCookiesDialog.ui + uis/SaveDialog.ui uis/SettingsGeneral.ui uis/SettingsPrivacy.ui ) @@ -59,6 +60,8 @@ target_link_libraries(privacy-browser KF5::DBusAddons KF5::DocTools KF5::I18n + KF5::KIOCore + KF5::KIOWidgets KF5::XmlGui ) diff --git a/src/dialogs/CMakeLists.txt b/src/dialogs/CMakeLists.txt index 2b722cc..7120f4d 100644 --- a/src/dialogs/CMakeLists.txt +++ b/src/dialogs/CMakeLists.txt @@ -22,4 +22,5 @@ target_sources(privacy-browser PRIVATE CookiesDialog.cpp DomainSettingsDialog.cpp DurableCookiesDialog.cpp + SaveDialog.cpp ) diff --git a/src/dialogs/SaveDialog.cpp b/src/dialogs/SaveDialog.cpp new file mode 100644 index 0000000..fff857d --- /dev/null +++ b/src/dialogs/SaveDialog.cpp @@ -0,0 +1,94 @@ +/* + * Copyright © 2022 Soren Stoutner . + * + * This file is part of Privacy Browser PC . + * + * Privacy Browser PC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser PC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser PC. If not, see . + */ + +// Application headers. +#include "SaveDialog.h" +#include "ui_SaveDialog.h" + +// KDE Frameworks headers. +#include + +// Qt toolkit headers. +#include +#include +#include +#include + +SaveDialog::SaveDialog(QWebEngineDownloadItem *downloadItemPointer) +{ + // Set the dialog window title. + setWindowTitle(i18nc("The save dialog window title", "Save")); + + // Set the window modality. + setWindowModality(Qt::WindowModality::ApplicationModal); + + // Instantiate the save dialog UI. + Ui::SaveDialog saveDialogUi; + + // Setup the UI. + saveDialogUi.setupUi(this); + + // Get handles for the widgets. + QLabel *urlLabelPointer = saveDialogUi.urlLabel; + QLabel *filetypeLabelPointer = saveDialogUi.fileTypeLabel; + QLabel *mimeTypeLabelPointer = saveDialogUi.mimeTypeLabel; + QLabel *sizeLabelPointer = saveDialogUi.sizeLabel; + QDialogButtonBox *dialogButtonBoxPointer = saveDialogUi.dialogButtonBox; + QPushButton *saveButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::Save); + + // Get the URL and the suggested file name. + downloadUrl = downloadItemPointer->url(); + suggestedFileName = downloadItemPointer->suggestedFileName(); + QString mimeType = downloadItemPointer->mimeType(); + + // Get a MIME type database. + QMimeDatabase mimeDatabase; + + // Populate the labels. + urlLabelPointer->setText("" + downloadUrl.toString() + ""); + filetypeLabelPointer->setText("" + mimeDatabase.mimeTypeForName(mimeType).comment() + ""); + mimeTypeLabelPointer->setText("" + mimeType + ""); + + // Populate the download size label. + if (downloadItemPointer->totalBytes() == -1) // The file size is unknown. + sizeLabelPointer->setText(i18nc("Unknown download file size. The bold style should be preserved.", "unknown")); + else // The file size is known. Format it according to the locale. + sizeLabelPointer->setText(ki18nc("Download file size. The bold style should be preserved.", "%1 bytes").subs(downloadItemPointer->totalBytes()).toString()); + + // Connect the buttons. + connect(saveButtonPointer, SIGNAL(clicked()), this, SLOT(showFileDialog())); + connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(reject())); + + // Create the keyboard shortcuts. + QShortcut *sShortcutPointer = new QShortcut(QKeySequence(i18nc("The save key shortcut.", "s")), this); + QShortcut *cShortcutPointer = new QShortcut(QKeySequence(i18nc("The close key shortcut.", "c")), this); + + // Connect the shortcuts. + connect(sShortcutPointer, SIGNAL(activated()), this, SLOT(showFileDialog())); + connect(cShortcutPointer, SIGNAL(activated()), this, SLOT(reject())); +} + +void SaveDialog::showFileDialog() +{ + // Show the file picker dialog. + emit showSaveFilePickerDialog(downloadUrl, suggestedFileName); + + // Close the dialog. + reject(); +} diff --git a/src/dialogs/SaveDialog.h b/src/dialogs/SaveDialog.h new file mode 100644 index 0000000..36354c1 --- /dev/null +++ b/src/dialogs/SaveDialog.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2022 Soren Stoutner . + * + * This file is part of Privacy Browser PC . + * + * Privacy Browser PC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser PC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser PC. If not, see . + */ + +#ifndef SAVEDIALOG_H +#define SAVEDIALOG_H + +// Qt toolkit headers. +#include +#include +#include + +class SaveDialog : public QDialog +{ + // Include the Q_OBJECT macro. + Q_OBJECT + +public: + // The primary constructor. + explicit SaveDialog(QWebEngineDownloadItem *downloadItemPointer); + +signals: + // The signals. + void showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName); + +private Q_SLOTS: + // The private slots. + void showFileDialog(); + +private: + // The private variables. + QUrl downloadUrl; + QString suggestedFileName; +}; +#endif diff --git a/src/uis/DomainSettingsDialog.ui b/src/uis/DomainSettingsDialog.ui index 9cf7324..d7eac8e 100644 --- a/src/uis/DomainSettingsDialog.ui +++ b/src/uis/DomainSettingsDialog.ui @@ -90,7 +90,7 @@ - + Domain name diff --git a/src/uis/SaveDialog.ui b/src/uis/SaveDialog.ui new file mode 100644 index 0000000..489b8c8 --- /dev/null +++ b/src/uis/SaveDialog.ui @@ -0,0 +1,137 @@ + + + + + + SaveDialog + + + + + + + + <b>Would you like to download the following file?</b> + + + + Qt::RichText + + + + Qt::AlignCenter + + + + 10 + + + + + + + + + File details + + + + + + + + + URL: + + + + + + + + Qt::RichText + + + + + + + + + File type: + + + + + + + + Qt::RichText + + + + + + + + + MIME type: + + + + + + + + Qt::RichText + + + + + + + + + Size: + + + + + + + + Qt::RichText + + + + + + + + + + + + QDialogButtonBox::Save | QDialogButtonBox::Cancel + + + + + + diff --git a/src/views/BrowserView.cpp b/src/views/BrowserView.cpp index b3f593f..2a23d81 100644 --- a/src/views/BrowserView.cpp +++ b/src/views/BrowserView.cpp @@ -23,14 +23,20 @@ #include "ui_BrowserView.h" #include "databases/CookiesDatabase.h" #include "databases/DomainsDatabase.h" +#include "dialogs/SaveDialog.h" #include "filters/MouseEventFilter.h" #include "helpers/SearchEngineHelper.h" #include "helpers/UserAgentHelper.h" #include "interceptors/UrlRequestInterceptor.h" #include "windows/BrowserWindow.h" -// Qt framework headers. +// KDE Framework headers. +#include +#include + +// Qt toolkit headers. #include +#include #include #include #include @@ -161,6 +167,9 @@ BrowserView::BrowserView(QWidget *parent) : QWidget(parent) // Set the URL request interceptor. webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer); + // Handle file downloads. + connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *))); + // Reapply the domain settings when the host changes. connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString))); @@ -640,6 +649,54 @@ void BrowserView::refresh() const webEngineViewPointer->reload(); } +void BrowserView::showSaveDialog(QWebEngineDownloadItem *downloadItemPointer) const +{ + // Instantiate the save dialog. + SaveDialog *saveDialogPointer = new SaveDialog(downloadItemPointer); + + // Connect the save button. + connect(saveDialogPointer, SIGNAL(showSaveFilePickerDialog(QUrl &, QString &)), this, SLOT(showSaveFilePickerDialog(QUrl &, QString &))); + + // Show the dialog. + saveDialogPointer->show(); +} + +void BrowserView::showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName) +{ + // Create a save file dialog. + QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); + + // 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 windows 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(); +} + void BrowserView::toggleDomStorage() const { // Toggle DOM storage. diff --git a/src/views/BrowserView.h b/src/views/BrowserView.h index e2b5275..8007aa7 100644 --- a/src/views/BrowserView.h +++ b/src/views/BrowserView.h @@ -106,6 +106,8 @@ private Q_SLOTS: void loadStarted() const; void pageLinkHovered(const QString &linkUrl) const; void printWebpage(QPrinter *printerPointer) const; + void showSaveDialog(QWebEngineDownloadItem *downloadItemPointer) const; + void showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName); void updateUrl(const QUrl &url) const; private: diff --git a/src/windows/BrowserWindow.cpp b/src/windows/BrowserWindow.cpp index 33aef29..2484061 100644 --- a/src/windows/BrowserWindow.cpp +++ b/src/windows/BrowserWindow.cpp @@ -189,8 +189,8 @@ BrowserWindow::BrowserWindow() : KXmlGuiWindow() // Display dialogs. connect(zoomFactorActionPointer, SIGNAL(triggered()), this, SLOT(getZoomFactorFromUser())); - connect(cookiesActionPointer, SIGNAL(triggered()), this, SLOT(openCookiesDialog())); - connect(domainSettingsActionPointer, SIGNAL(triggered()), this, SLOT(openDomainSettings())); + connect(cookiesActionPointer, SIGNAL(triggered()), this, SLOT(showCookiesDialog())); + connect(domainSettingsActionPointer, SIGNAL(triggered()), this, SLOT(showDomainSettingsDialog())); // Connect the URL toolbar actions. connect(javaScriptActionPointer, SIGNAL(triggered()), this, SLOT(toggleJavaScript())); @@ -447,7 +447,27 @@ void BrowserWindow::loadUrlFromLineEdit(const QString &url) const browserViewPointer->loadUrlFromLineEdit(url); } -void BrowserWindow::openCookiesDialog() +void BrowserWindow::refresh() const +{ + // Remove the focus from the URL line edit. + urlLineEditPointer->clearFocus(); + + // Refresh the web page. + browserViewPointer->refresh(); +} + +void BrowserWindow::removeCookieFromList(const QNetworkCookie &cookie) const +{ + //qDebug() << "Remove cookie: " << cookie.toRawForm(); + + // Remove the cookie from the list. + cookieListPointer->remove(cookie); + + // Update the action text. + cookiesActionPointer->setText(i18nc("The Cookies action, which also displays the number of cookies", "Cookies - %1", cookieListPointer->size())); +} + +void BrowserWindow::showCookiesDialog() { // Remove the focus from the URL line edit. urlLineEditPointer->clearFocus(); @@ -464,7 +484,7 @@ void BrowserWindow::openCookiesDialog() connect(cookiesDialogPointer, SIGNAL(deleteCookie(QNetworkCookie)), browserViewPointer, SLOT(deleteCookieFromStore(QNetworkCookie))); } -void BrowserWindow::openDomainSettings() const +void BrowserWindow::showDomainSettingsDialog() const { // Remove the focus from the URL line edit. urlLineEditPointer->clearFocus(); @@ -479,26 +499,6 @@ void BrowserWindow::openDomainSettings() const connect(domainSettingsDialogPointer, SIGNAL(domainSettingsUpdated()), browserViewPointer, SLOT(applyDomainSettingsAndReload())); } -void BrowserWindow::refresh() const -{ - // Remove the focus from the URL line edit. - urlLineEditPointer->clearFocus(); - - // Refresh the web page. - browserViewPointer->refresh(); -} - -void BrowserWindow::removeCookieFromList(const QNetworkCookie &cookie) const -{ - //qDebug() << "Remove cookie: " << cookie.toRawForm(); - - // Remove the cookie from the list. - cookieListPointer->remove(cookie); - - // Update the action text. - cookiesActionPointer->setText(i18nc("The Cookies action, which also displays the number of cookies", "Cookies - %1", cookieListPointer->size())); -} - void BrowserWindow::showProgressBar(const int &progress) const { // Set the progress bar value. diff --git a/src/windows/BrowserWindow.h b/src/windows/BrowserWindow.h index aa9e2ee..c05f942 100644 --- a/src/windows/BrowserWindow.h +++ b/src/windows/BrowserWindow.h @@ -60,11 +60,11 @@ private Q_SLOTS: void getZoomFactorFromUser(); void home() const; void loadUrlFromLineEdit(const QString &url) const; - void openCookiesDialog(); - void openDomainSettings() const; void refresh() const; void removeCookieFromList(const QNetworkCookie &cookie) const; void settingsConfigure(); + void showCookiesDialog(); + void showDomainSettingsDialog() const; void showProgressBar(const int &progress) const; void toggleDomStorage() const; void toggleJavaScript() const; -- 2.43.0