]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/commitdiff
Add a default folder icon to the edit folder dialog. https://redmine.stoutner.com... master
authorSoren Stoutner <soren@stoutner.com>
Wed, 27 Mar 2024 23:03:36 +0000 (16:03 -0700)
committerSoren Stoutner <soren@stoutner.com>
Wed, 27 Mar 2024 23:03:36 +0000 (16:03 -0700)
114 files changed:
.gitignore
CMakeLists.txt
COPYING
changelog [new file with mode: 0644]
doc/CMakeLists.txt
doc/cookies.png [new file with mode: 0644]
doc/index.docbook
doc/javascript.png [new file with mode: 0644]
doc/loading.gif [new file with mode: 0644]
doc/privacybrowser-monochrome.png [new file with mode: 0644]
doc/privacybrowser-window.png [new file with mode: 0644]
doc/privacybrowser.png [new file with mode: 0644]
po/privacybrowser.pot
src/CMakeLists.txt
src/com.stoutner.privacybrowser.appdata.xml
src/com.stoutner.privacybrowser.desktop
src/databases/BookmarksDatabase.cpp [new file with mode: 0644]
src/databases/BookmarksDatabase.h [new file with mode: 0644]
src/databases/CMakeLists.txt
src/databases/CookiesDatabase.cpp
src/databases/CookiesDatabase.h
src/databases/DomainsDatabase.cpp
src/databases/DomainsDatabase.h
src/delegates/CMakeLists.txt
src/dialogs/AddBookmarkDialog.cpp [new file with mode: 0644]
src/dialogs/AddBookmarkDialog.h [new file with mode: 0644]
src/dialogs/AddFolderDialog.cpp [new file with mode: 0644]
src/dialogs/AddFolderDialog.h [new file with mode: 0644]
src/dialogs/AddOrEditCookieDialog.cpp
src/dialogs/AddOrEditCookieDialog.h
src/dialogs/BookmarksDialog.cpp [new file with mode: 0644]
src/dialogs/BookmarksDialog.h [new file with mode: 0644]
src/dialogs/CMakeLists.txt
src/dialogs/CookiesDialog.cpp
src/dialogs/CookiesDialog.h
src/dialogs/DomainSettingsDialog.cpp
src/dialogs/DomainSettingsDialog.h
src/dialogs/DurableCookiesDialog.cpp
src/dialogs/DurableCookiesDialog.h
src/dialogs/EditBookmarkDialog.cpp [new file with mode: 0644]
src/dialogs/EditBookmarkDialog.h [new file with mode: 0644]
src/dialogs/EditFolderDialog.cpp [new file with mode: 0644]
src/dialogs/EditFolderDialog.h [new file with mode: 0644]
src/dialogs/HttpAuthenticationDialog.cpp [new file with mode: 0644]
src/dialogs/HttpAuthenticationDialog.h [new file with mode: 0644]
src/dialogs/SaveDialog.cpp
src/dialogs/SaveDialog.h
src/dialogs/SettingsDialog.cpp [new file with mode: 0644]
src/dialogs/SettingsDialog.h [new file with mode: 0644]
src/filters/CMakeLists.txt
src/helpers/CMakeLists.txt
src/helpers/FolderHelper.cpp [new file with mode: 0644]
src/helpers/FolderHelper.h [new file with mode: 0644]
src/helpers/UserAgentHelper.cpp
src/helpers/UserAgentHelper.h
src/icons/1024-apps-privacybrowser.png [new file with mode: 0644]
src/icons/128-apps-privacybrowser.png [new file with mode: 0644]
src/icons/16-apps-privacybrowser.png [new file with mode: 0644]
src/icons/22-apps-privacybrowser.png [new file with mode: 0644]
src/icons/24-apps-privacybrowser.png [new file with mode: 0644]
src/icons/256-apps-privacybrowser.png [new file with mode: 0644]
src/icons/32-apps-privacybrowser.png [new file with mode: 0644]
src/icons/48-apps-privacybrowser.png [new file with mode: 0644]
src/icons/512-apps-privacybrowser.png [new file with mode: 0644]
src/icons/64-apps-privacybrowser.png [new file with mode: 0644]
src/icons/javascript-warning.svg
src/icons/loading.gif [new file with mode: 0644]
src/icons/privacy-mode.svg
src/icons/privacybrowser-symbolic.svg [new file with mode: 0644]
src/icons/sc-apps-privacy-browser.svg [deleted file]
src/icons/sc-apps-privacybrowser.svg [new file with mode: 0644]
src/interceptors/CMakeLists.txt
src/interceptors/UrlRequestInterceptor.cpp
src/interceptors/UrlRequestInterceptor.h
src/main.cpp
src/privacybrowser.1 [new file with mode: 0644]
src/privacybrowser.notifyrc [new file with mode: 0644]
src/resources.qrc
src/settings/Settings.kcfg
src/settings/Settings.kcfgc
src/structs/BookmarkStruct.h [new file with mode: 0644]
src/ui.rcs/CMakeLists.txt
src/ui.rcs/browser_window_ui.rc [deleted file]
src/ui.rcs/browserwindowui.rc [new file with mode: 0644]
src/uis/AddBookmarkDialog.ui [new file with mode: 0644]
src/uis/AddFolderDialog.ui [new file with mode: 0644]
src/uis/AddOrEditCookieDialog.ui
src/uis/AddTabWidget.ui
src/uis/BookmarksDialog.ui [new file with mode: 0644]
src/uis/CookiesDialog.ui
src/uis/DomainSettingsDialog.ui
src/uis/DurableCookiesDialog.ui
src/uis/EditBookmarkDialog.ui [new file with mode: 0644]
src/uis/EditFolderDialog.ui [new file with mode: 0644]
src/uis/HttpAuthenticationDialog.ui [new file with mode: 0644]
src/uis/SaveDialog.ui
src/uis/SettingsGeneral.ui
src/uis/SettingsPrivacy.ui
src/uis/SettingsSpellCheck.ui [new file with mode: 0644]
src/uis/TabWidget.ui
src/widgets/CMakeLists.txt
src/widgets/DevToolsWebEngineView.cpp [new file with mode: 0644]
src/widgets/DevToolsWebEngineView.h [new file with mode: 0644]
src/widgets/DraggableTreeView.cpp [new file with mode: 0644]
src/widgets/DraggableTreeView.h [new file with mode: 0644]
src/widgets/PrivacyWebEngineView.cpp
src/widgets/PrivacyWebEngineView.h
src/widgets/TabWidget.cpp
src/widgets/TabWidget.h
src/widgets/UrlLineEdit.cpp [new file with mode: 0644]
src/widgets/UrlLineEdit.h [new file with mode: 0644]
src/windows/BrowserWindow.cpp
src/windows/BrowserWindow.h
src/windows/CMakeLists.txt

index 9c087ba095c6acde46c074c12b7f8bd824352465..eea15f57459bed5926c5a9a64e92cb7154904cb5 100644 (file)
@@ -1,2 +1,3 @@
+/bin
 /build
 /.kdev4
index ff4b909c293507591cd1496f227b2478fe12b05e..42b28dd527ca4dd9ae1208fe41b3e56abfdbbdc5 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
@@ -42,10 +42,20 @@ include(ECMInstallIcons)
 include(ECMQtDeclareLoggingCategory)
 include(FeatureSummary)
 
+# Include the Position Independent Executable checker.  <https://cmake.org/cmake/help/latest/policy/CMP0083.html>
+include(CheckPIESupported)
+
+# Check to see if Position Independent Executable is supported in the current linker and environment.
+check_pie_supported()
+
+# Add the Position Independent Executable compiler flag if it is supported in the current linker and environment.  <https://cmake.org/cmake/help/latest/variable/CMAKE_POSITION_INDEPENDENT_CODE.html>
+set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
+
 # Load the Qt components.
 find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
     Core
     Gui
+    PrintSupport
     Sql
     WebEngineCore
     WebEngineWidgets
@@ -61,6 +71,7 @@ find_package(KF5 ${KDE_FRAMEWORKS_MIN_VERSION} REQUIRED COMPONENTS
     DBusAddons
     DocTools
     I18n
+    Notifications
     KIO
     XmlGui
 )
@@ -69,9 +80,9 @@ find_package(KF5 ${KDE_FRAMEWORKS_MIN_VERSION} REQUIRED COMPONENTS
 add_subdirectory(doc)
 add_subdirectory(src)
 
-# Make it possible to use the PO files fetched by the fetch-translations step.
-ki18n_install(po)
-kdoctools_install(po)
+# Install the PO files.  These should be enabled once there are PO translations.
+#ki18n_install(po)
+#kdoctools_install(po)
 
 # Generate a summary.
 feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/COPYING b/COPYING
index 3b013331cc9a799c94d70c9f5b5c965748eb4ad3..7cf0ecb902105a203c48a00dee37df42be93a594 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -1,4 +1,4 @@
-Privacy Browser PC copyright © 2016-2017,2021-2022 Soren Stoutner <soren@stoutner.com>.
+Privacy Browser PC copyright 2016-2017,2021-2024 Soren Stoutner <soren@stoutner.com>.
 
 This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 
diff --git a/changelog b/changelog
new file mode 100644 (file)
index 0000000..6ec8ce3
--- /dev/null
+++ b/changelog
@@ -0,0 +1,34 @@
+# Version 0.5 (12 October 2023)
+ * Add bookmarks.
+ * Add zoom controls to the status bar and a default zoom shortcut.
+ * Add keyboard shortcuts for the URL toolbar actions.
+ * Add an action to view page source.
+ * Change the domain settings combo boxes to list enabled above disabled.
+ * File downloads can now show the size before the download begins.
+ * Add PIE (Position Independent Executable) compiler flags.
+ * Fix a bug that sometimes allowed multiple domain settings to be created.
+ * Fix the download notification not being cleared on Xfce.
+
+# Version 0.4 (13 June 2023)
+ * Add a setting to control spatial navigation.
+ * Add an action to reload and bypass cache.
+ * Fix a crash if one Privacy Browser window is closed while a tab within it is loading.
+ * Add keyboard+click commands to the Handbook.
+
+# Version 0.3 (8 May 2023)
+ * Add the changelog to the Handbook
+ * Add the missing current domain settings icon on Gnome and Xfce.
+ * Make changes to build on Guix.
+
+# Version 0.2 (17 April 2023)
+ * Fix a crash on GNOME when downloading a file with local storage disabled.
+ * Fix problems with missing icons on GNOME.
+ * Display an animated favorite icon while a webpage is loading.
+ * Fix the Handbook on non-KDE systems.
+ * Change the order of entries in the WebEngine context menu.
+ * Make spellcheck languages easier to click on.
+ * Only generate a HTTP ping dialog if the request is made by the current tab.
+ * Add a section to the Handbook about HTTP pings.
+
+# Version 0.1 (11 March 2023)
+ * Initial release.
index ffd9ed3bb313ea93482feac64e5f3adbfcd3c7ef..1f54e4ac293df349defd3317dc766272da72b523 100644 (file)
@@ -1 +1,22 @@
+# Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+#
+# This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+
+# Create the documentation.
 kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR privacybrowser)
+
+# Install extra graphic files (the automatic installer doesn't pick up .gif files).
+install(FILES loading.gif DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en/privacybrowser)
diff --git a/doc/cookies.png b/doc/cookies.png
new file mode 100644 (file)
index 0000000..fecf17c
Binary files /dev/null and b/doc/cookies.png differ
index b135352d27c5ce0756cb4c4f8e4fae2e99ba449e..27ee1fc310350518021857851bb41de99bcee7c6 100644 (file)
 <?xml version="1.0" ?>
-<!DOCTYPE book PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
-  <!-- Define an entity for your application if it is not part of KDE
-       Git -->
-  <!ENTITY kmyapplication "<application>privacybrowserpc</application>">
-  <!ENTITY i18n-translatable-entity "<application>Translatable Entity</application>">
-  <!ENTITY % addindex "IGNORE">
-  <!ENTITY % English "INCLUDE">
-
-  <!-- Do not define any other entities; instead, use the entities
-       from entities/general.entities and en/user.entities. -->
-]>
 
 <!--
-Use this template for application docbooks
-For kioslave, systemsettings (kcontrol) modules and simple/short application docbooks use article-template.docbook
-Rename this template to index.docbook and place into the directory doc/ or doc/appname if you have several applications in one doc directory
--->
-
-<!-- kdoctemplate v0.13.0 2017-07-31 ltoscano
-     add CC BY-SA 4.0 International and set as default
-
-     kdoctemplate v0.12.0 2016-04-23 lueck
-     add translatable entities
-     remove help.menu.documentation entity
-     and add some examples how to use the common menus Settings and Help
-
-     kdoctemplate v0.11.0 2015-04-03 lueck
-     updated instructions for date + releaseinfo
-     remove ENTITY package - not used anymore
-     add info about KDE Games special chapters
-     Appendix Installation is now optional
-
-     kdoctemplate v0.10.0 2014-04-02 ltoscano
-     updated to KF5/kdoctools
-     references to KDE reworded
-
-     kdoctemplate v0.9.2 2012-10-15 pino
-     update versions to 4.9
-     update years to 2012
-     switch from CVS to Git
-     add id attribute to <book>
-     
-     kdoctemplate v0.9.1 2010-10-27 lueck
-     changed releaseinfo format
-     removed screenshot format="EPS
-     added comment when to write a command reference
-     added tip using id's in varlistentries
-     removed refentry stuff
-     
-     kdoctemplate v0.9 January 10 2003
-     Changes to comments to clarify entity usage January 10 2003
-     Minor update to "Credits and Licenses" section on August 24, 2000
-     Removed "Revision history" section on 22 January 2001
-     Changed to Installation/Help menu entities 18 October 2001
-     Other minor cleanup and changes 18 October 2001
-     FPI change and minor changes November 2002 -->
-
-<!--
-This template was designed by: David Rugge davidrugge@mindspring.com
-with lots of help from: Eric Bischoff ebisch@cybercable.tm.fr
-and Frederik Fouvry fouvry@sfs.nphil.uni-tuebingen.de
-of the KDE DocBook team.
-
-You may freely use this template for writing any sort of KDE documentation.
-If you have any changes or improvements, please let us know.
-
-Remember:
-- in XML, the case of the <tags> and attributes is relevant ;
-- also, quote all attributes.
-
-Please don't forget to remove all these comments in your final documentation,
-thanks ;-).
--->
-
-<!-- ................................................................ -->
-
-<!-- The language must NOT be changed here. -->
-<!-- If you are writing original documentation in a language other -->
-<!-- than English, change the language above ONLY, not here -->
-<!-- Change the value of id to the name of your application -->
-<book id="kmyapplication" lang="&language;">
-
-<!-- This header contains all of the meta-information for the document such
-as Authors, publish date, the abstract, and Keywords -->
-
-<bookinfo>
-<title>The &kmyapplication; Handbook</title>
-
-<authorgroup>
-<author>
-<!-- This is just put in as an example.  For real documentation, please
-     define a general entity in entities/contributor.entities, e.g.
-<!ENTITY George.N.Ugnacious "<personname><firstname>George</firstname><othername>N.</othername><surname>Ugnacious</surname></personname>">
-<!ENTITY George.N.Ugnacious.mail "<email>gnu@kde.org</email>">
-and use `&George.N.Ugnacious; &George.N.Ugnacious.mail;' in the author element.
- -->
-<personname>
-<firstname>George</firstname>
-<othername>N.</othername>
-<surname>Ugnacious</surname>
-</personname>
-<email>gnu@kde.org</email>
-</author>
-<!-- TRANS:ROLES_OF_TRANSLATORS -->
-</authorgroup>
-
-<copyright>
-<year>2015</year>
-<holder>George N. Ugnacious</holder>
-</copyright>
-<legalnotice>&CCBYSA4Notice;</legalnotice>
-
-<!-- Date of the documentation
-Change date/releaseinfo only if
-   docbook is updated and verified to be valid for the current app version
-   or
-   docbook is proofread and verified to be valid for the current app version
-Don't forget to include this last date.
-Please respect the format of the date (YYYY-MM-DD),it is used by scripts.
--->
-<date>2016-04-23</date>
-
-<!--version information of Frameworks/Plasma/Applications this documentation is valid for.
-Example:
-Frameworks xx.yy for docbooks in frameworks
-Plasma xx.yy for docbooks in plasma workspace
-Applications xx.yy for docbooks released as Applications
-xx.yy (Applications xx.yy) for docbooks with own version released as Applications
-$applicationname xx.yy for applications with independent release schedule (extragear/playground)
--->
-<releaseinfo>Frameworks xx.yy or Plasma xx.yy or Applications xx.yy or xx.yy (Applications xx.yy) or $applicationname xx.yy</releaseinfo>
-
-<!-- Abstract about this handbook -->
-
-<abstract>
-<para>
-&kmyapplication; is an application specially designed to do nothing you would
-ever want.
-</para>
-</abstract>
-
-<!-- This is a set of Keywords for indexing by search engines.
-Please at least include KDE, the KDE section it is in, the name
- of your application, and a few relevant keywords. -->
-
-<keywordset>
-<keyword>KDE</keyword>
-<keyword>kdeutils</keyword>
-<keyword>Kapp</keyword>
-<keyword>nothing</keyword>
-<keyword>nothing else</keyword>
-</keywordset>
-
-</bookinfo>
-
-<!-- The contents of the documentation begin here.  Label
-each chapter so with the id attribute. This is necessary for two reasons: it
-allows you to easily reference the chapter from other chapters of your
-document, and if there is no ID, the name of the generated HTML files will vary
-from time to time making it hard to manage for maintainers and for the CVS
-system. Any chapter labelled (OPTIONAL) may be left out at the author's
-discretion. Other chapters should not be left out in order to maintain a
-consistent documentation style across all KDE apps. -->
-
-
-<!-- KDE Games have special chapters
-
-Introduction
-
-How to play
-
-Game Rules, Strategies and Tips
-
-Interface Overview
-
-Frequently asked questions
-
-Credits and License
-
-Please use one of index.docbook files from kdegames
-
--->
-<chapter id="introduction">
-<title>Introduction</title>
-
-<!-- The introduction chapter contains a brief introduction for the
-application that explains what it does and where to report
-problems. Basically a long version of the abstract.  Don't include a
-revision history. (see installation appendix comment) -->
-
-<para>
-&kmyapplication; is a program that lets you do absolutely nothing. Please report
-any problems or feature requests to the &kde; mailing lists.
-</para>
-</chapter>
-
-<chapter id="using-kapp">
-<title>Using &kmyapplication;</title>
-
-<!-- This chapter should tell the user how to use your app. You should use as
-many sections (Chapter, Sect1, Sect3, etc...) as is necessary to fully document
-your application. -->
-
-<para>
-
-<!-- Note that all graphics should be in .png format. Use no gifs because of
-patent issues. -->
-
-<screenshot>
-<screeninfo>Here's a screenshot of &kmyapplication;</screeninfo>
-       <mediaobject>
-         <imageobject>
-           <imagedata fileref="screenshot.png" format="PNG"/>
-         </imageobject>
-         <textobject>
-           <phrase>Screenshot</phrase>
-         </textobject>
-       </mediaobject>
-</screenshot>
-</para>
-
-
-<sect1 id="kapp-features">
-<title>More &kmyapplication; features</title>
-
-<para>It slices! It dices! and it comes with a free toaster!</para>
-<para>
-The Squiggle Tool <guiicon><inlinemediaobject>
-         <imageobject>
-           <imagedata fileref="squiggle.png" format="PNG"/>
-         </imageobject>
-         <textobject>
-           <phrase>Squiggle</phrase>
-         </textobject>
-</inlinemediaobject></guiicon> is used to draw squiggly lines all over
-the &kmyapplication; main window. It's not a bug, it's a feature!
-</para>
-
-</sect1>
-</chapter>
-
-<chapter id="commands">
-<title>Command Reference</title>
-
-<!-- (OPTIONAL, BUT RECOMMENDED) This chapter should list all of the
-application windows and their menubar and toolbar commands for easy reference.
-Also include any keys that have a special function but have no equivalent in the
-menus or toolbars. 
-This may not be necessary for small apps or apps with no tool or menu bars.
-
-Don't bother users with well known kde menu items like Settings->Shortcuts etc. 
-
-Use cases for a command reference:
-
-Applications with many menu items (Kate/Konqueror) 
-Applications with different modes and menus (KWrite/Konqueror)
--> Enables search for menu items
-
-For Applications with default menu items and some special items where user 
-needs additional information use something like:
-"Apart from the common KDE menu items you find these action in the menu:
-
-File -> Special Action: Explanation of special action
-
-Tools -> Special Tool: Explanation of special tool
-
-Use variablelist markup for this
--->
-
-<sect1 id="kapp-mainwindow">
-<title>The main &kmyapplication; window</title>
-
-<sect2>
-<title>The File Menu</title>
-<para>
-<variablelist>
-<varlistentry  id="file-new">
-<!-- Tip: With id's here, then you can use them like 
-"select <xref linkend="file-new"/> to open the file dialog"
-which will be expanded to:
-"select File->New (Ctrl+N) to open the file dialog"
--->
-<term><menuchoice>
-<shortcut>
-<keycombo action="simul">&Ctrl;<keycap>N</keycap></keycombo>
-</shortcut>
-<guimenu>File</guimenu>
-<guimenuitem>New</guimenuitem>
-</menuchoice></term>
-<listitem><para><action>Creates a new document</action></para></listitem>
-</varlistentry>
-
-<varlistentry  id="file-save">
-<term><menuchoice>
-<shortcut>
-<keycombo action="simul">&Ctrl;<keycap>S</keycap></keycombo>
-</shortcut>
-<guimenu>File</guimenu>
-<guimenuitem>Save</guimenuitem>
-</menuchoice></term>
-<listitem><para><action>Saves the document</action></para></listitem>
-</varlistentry>
-
-<varlistentry  id="file-quit">
-<term><menuchoice>
-<shortcut>
-<keycombo action="simul">&Ctrl;<keycap>Q</keycap></keycombo>
-</shortcut>
-<guimenu>File</guimenu>
-<guimenuitem>Quit</guimenuitem>
-</menuchoice></term>
-<listitem><para><action>Quits</action> &kmyapplication;</para></listitem>
-</varlistentry>
-</variablelist>
-</para>
-
-</sect2>
-
-<!-- Examples how to use the common menus Settings and Help -->
-
-<sect2 id="settings-help-menu">
-<title>The Settings and Help Menu</title>
-<para>
-&kmyapplication; has the common &kde; <guimenu>Settings</guimenu> and <guimenu>Help</guimenu>
-menu items, for more information read the sections about the <ulink url="help:/fundamentals/ui.html#menus-settings"
->Settings Menu</ulink> and <ulink url="help:/fundamentals/ui.html#menus-help">Help Menu</ulink>
-of the &kde; Fundamentals.
-</para>
-</sect2>
-
-<sect2 id="help-menu1">
-<title>The Help Menu</title>
-<para>
-&kmyapplication; has the common &kde; <guimenu>Help</guimenu> menu item, for more information read the section
-about the <ulink url="help:/fundamentals/ui.html#menus-help">Help Menu</ulink> of the &kde; Fundamentals.
-</para>
-</sect2>
-
-<sect2 id="menu-commands">
-<title>Menu Items</title>
-<para>Apart from the common &kde; menus described in the <ulink url="help:/fundamentals/ui.html#menus">Menu</ulink>
-chapter of the &kde; Fundamentals documentation &kmyapplication; has these application specific menu entries:
-</para>
-<!-- variablelist -->
-</sect2>
-
-<sect2 id="help-menu2">
-<title>The Help Menu</title>
-<para>&kmyapplication; has a default &kde; <guimenu>Help</guimenu> menu as described in the
-<ulink url="help:/fundamentals/ui.html#menus-help">&kde; Fundamentals</ulink>
-with two additional entries:</para>
-<!-- variablelist -->
-</sect2>
-
-</sect1>
-</chapter>
-
-<chapter id="developers">
-<title>Developer's Guide to &kmyapplication;</title>
-
-<!-- (OPTIONAL) A Programming/Scripting reference chapter should be
-used for apps that use plugins or that provide their own scripting hooks
-and/or development libraries. -->
-
-<para>
-Programming &kmyapplication; plugins is a joy to behold.
-</para>
-
-
-</chapter>
-
-<chapter id="faq">
-<title>Questions and Answers</title>
-
-<!-- (OPTIONAL but recommended) This chapter should include all of the silly
-(and not-so-silly) newbie questions that fill up your mailbox. This chapter
-should be reserved for BRIEF questions and answers! If one question uses more
-than a page or so then it should probably be part of the
-"Using this Application" chapter instead. You should use links to
-cross-reference questions to the parts of your documentation that answer them.
-This is also a great place to provide pointers to other FAQ's if your users
-must do some complicated configuration on other programs in order for your
-application work. -->
-
-<qandaset id="faqlist">
-<qandaentry>
-<question>
-<para>My Mouse doesn't work. How do I quit &kmyapplication;?</para>
-</question>
-<answer>
-<para>You silly goose! Check out the <link linkend="commands">Commands
-Section</link> for the answer.</para>
-</answer>
-</qandaentry>
-<qandaentry>
-<question>
-<para>Why can I not twiddle my documents?</para>
-</question>
-<answer>
-<para>You can only twiddle your documents if you have the foobar.lib
-installed.</para>
-</answer>
-</qandaentry>
-</qandaset>
-</chapter>
-
-<chapter id="credits">
-
-<!-- Include credits for the programmers, documentation writers, and
-contributors here. The license for your software should then be included below
-the credits with a reference to the appropriate license file included in the KDE
-distribution. -->
-
-<title>Credits and License</title>
-
-<para>
-&kmyapplication;
-</para>
-<para>
-Program copyright 2010-2014 John Q. Hacker <email>jqh@kde.org</email>
-</para>
-<para>
-Contributors:
-<itemizedlist>
-<listitem><para>Konqui the &kde; Dragon <email>konqui@kde.org</email></para>
-</listitem>
-<listitem><para>Tux the &Linux; Penguin <email>tux@linux.org</email></para>
-</listitem>
-</itemizedlist>
-</para>
-
-<para>
-Documentation Copyright &copy; 2012-2014 George N. Ugnacious <email>gnu@kde.org</email>
-</para>
-
-<!-- TRANS:CREDIT_FOR_TRANSLATORS -->
-
-<!-- License for new documents after 2017-07-27 (or relicensed) -->
-
-&underCCBYSA4;           <!-- CC BY-SA 4.0: do not remove -->
-
-<!-- FDL: old license; only for reference (and old documents) -->
-<!--
-&underFDL;
--->
-
-<!-- Determine which license your application is licensed under,
-     and delete all the remaining licenses below:
-
-     (NOTE:  All documentation are licensed under the CC BY-SA 4.0,
-     regardless of what license the application uses) -->
-
-&underGPL;              <!-- GPL License -->
-&underBSDLicense;        <!-- BSD License -->
-&underArtisticLicense;   <!-- BSD Artistic License -->
-&underX11License;        <!-- X11 License  -->
-
-</chapter>
+  Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
 
-<!-- Appendix Installation is optional, usually not required
+  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 
-Entities &install.intro.documentation; + &install.compile.documentation; will be removed in later kdoctools
+  Permission is granted to copy, distribute and/or modify this document
+  under the terms of the GNU Free Documentation License, Version 1.3
+  or any later version published by the Free Software Foundation;
+  with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
 
--->
+  You should have received a copy of the GNU Free Documentation License
+  along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>. -->
 
-<appendix id="installation">
-<title>Installation</title>
-
-<sect1 id="getting-kapp">
-<title>How to obtain &kmyapplication;</title>
-
-<!-- This first entity contains boiler plate for applications that are
-part of KDE archive.  You should remove it if you are releasing your
-application -->
-
-&install.intro.documentation;
+<!DOCTYPE book PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
+    <!-- Privacy Browser’s name -->
+    <!ENTITY privacybrowser "<application>Privacy Browser</application>">
 
-</sect1>
+    <!-- People. -->
+    <!ENTITY Soren.Stoutner "<personname><firstname>Soren</firstname><surname>Stoutner</surname></personname>">
+    <!ENTITY Soren.Stoutner.mail "<email>soren@stoutner.com</email>">
 
-<sect1 id="requirements">
-<title>Requirements</title>
+    <!-- Set the language of this documentation. -->
+    <!ENTITY % English "INCLUDE">
+]>
 
-<!--
-List any special requirements for your application here. This should include:
-.Libraries or other software that is not included in kdesupport or kf5
-.Hardware requirements like amount of RAM, disk space, graphics card
-capabilities, screen resolution, special expansion cards, etc.
-.Operating systems the app will run on. If your app is designed only for a
-specific OS, (you wrote a graphical LILO configurator for example) put this
-information here.
--->
-
-<para>
-In order to successfully use &kmyapplication;, you need few libraries from
-&kf5; 5.1 (bar, baz). Foobar.lib is
-required in order to support the advanced &kmyapplication; features. &kmyapplication; uses
-about 5 megs of memory to run, but this may vary depending on your
-platform and configuration.
-</para>
-
-<para>
-All required libraries as well as &kmyapplication; itself can be found
-on <ulink url="ftp://ftp.kapp.org">The &kmyapplication; home page</ulink>.
-</para>
-
-<!-- For a list of updates, you may refer to the application web site
-or the ChangeLog file, or ... -->
-<para>
-You can find a list of changes at <ulink
-url="http://apps.kde.org/kapp">http://apps.kde.org/kapp</ulink>.
-</para>
-</sect1>
-
-<sect1 id="compilation">
-<title>Compilation and Installation</title>
-
-<!-- This entity contains the boilerplate text for standard -->
-<!-- compilation instructions.  If your application requires any -->
-<!-- special handling, remove it, and replace with your own text. -->
-
-&install.compile.documentation;
-
-</sect1>
-
-<sect1 id="configuration">
-<title>Configuration</title>
-
-<para>Don't forget to tell your system to start the <filename>dtd</filename>
-dicer-toaster daemon first, or &kmyapplication; won't work!</para>
-
-</sect1>
-
-</appendix>
-
-&documentation.index;
+<book id="privacybrowser" lang="&language;">
+    <bookinfo>
+        <title>The &privacybrowser; Handbook</title>
+
+        <authorgroup>
+            <author>&Soren.Stoutner; &Soren.Stoutner.mail;</author>
+
+            <!-- Add translators here.-->
+        </authorgroup>
+
+        <copyright>
+            <year>2016-2017, 2021-2024</year>
+            <holder>&Soren.Stoutner;</holder>
+        </copyright>
+
+        <!-- Documentation license. -->
+        <legalnotice>&FDLNotice;</legalnotice>
+
+        <!-- Last update. -->
+        <date>2024-01-06</date>
+
+        <!-- The version of Privacy Browser this documentation is written for. -->
+        <releaseinfo>&privacybrowser; version 0.5</releaseinfo>
+
+        <!-- Abstract about this handbook -->
+        <abstract>
+            <para>
+                &privacybrowser; is a web browser that respects your privacy.
+            </para>
+
+            <para>
+                The only way to prevent data from being abused is to prevent it from being collected in the first place.
+            </para>
+        </abstract>
+
+        <!-- This is a set of Keywords for indexing by search engines. -->
+        <keywordset>
+            <keyword>KDE</keyword>
+            <keyword>privacy</keyword>
+            <keyword>browser</keyword>
+        </keywordset>
+    </bookinfo>
+
+    <!-- Introduction. -->
+    <chapter id="introduction">
+        <title>Introduction</title>
+
+        <para>
+            &privacybrowser; is currently in an early alpha state.
+            Most of the features are not yet implemented, but I thought it would be useful to publish it so that users can track the progress and submit feedback.
+        </para>
+
+        <para>
+            To distinguish between the Android and the PC version, the website, issue tracker,
+            and code base refer to this version as <ulink url="https://www.stoutner.com/privacy-browser-pc/">Privacy Browser PC</ulink>.
+        </para>
+
+        <para>
+            The best place to discuss the development of Privacy Browser is <ulink url="https://redmine.stoutner.com/projects/privacy-browser-pc/boards">on the forum</ulink>.
+            I also frequently post on my <ulink url="https://fosstodon.org/@privacybrowser">Mastodon account</ulink> regarding the development status.
+        </para>
+
+        <!-- Qt WebEngine. -->
+        <sect1 id="qt-webengine">
+            <title>Qt WebEngine</title>
+
+            <para>
+                Privacy Browser uses <ulink url="https://doc.qt.io/qt-5/qtwebengine-index.html">Qt WebEngine</ulink> to render websites.
+                Qt WebEngine is based on the <ulink url="https://www.chromium.org/blink/">Chromium Blink</ulink> source code.
+                Because Privacy Browser is built on the <ulink url="https://api.kde.org/frameworks/index.html">KDE Framework</ulink>,
+                it currently uses the <ulink url="https://community.kde.org/Schedules/Plasma_6">Qt 5</ulink> packages.
+            </para>
+
+            <para>
+                The current Qt 5 packages are in long-term support mode.
+                From a feature perspective, Qt WebEngine 5.15.x is based on <ulink url="https://wiki.qt.io/QtWebEngine/ChromiumVersions">Chromium 87.0.4280.144</ulink>.
+                Security fixes are backported every few months with <ulink url="https://wiki.qt.io/Qt_5.15_Release#Release_Plan">each release</ulink>.
+            </para>
+        </sect1>
+
+        <!-- Bugs and missing features. -->
+        <sect1 id="bugs-and-missing-features">
+            <title>Bugs and Missing Features</title>
+
+            <para>
+                There is a list of feature requests and known bugs at <ulink url="https://redmine.stoutner.com/projects/privacy-browser-pc/issues">redmine.stoutner.com</ulink>.
+                Users should anticipate that all the current features of <ulink url="https://www.stoutner.com/privacy-browser-android/">Privacy Browser Android</ulink>
+                will also be implemented in Privacy Browser PC.
+                There is no need at this point to create features requests for these as they will be added as I start working on each feature and have a better idea of how they will be implemented.
+                However, each feature that has already been implemented should be bug free.
+                If you discover a bug that is not already documented at <ulink url="https://redmine.stoutner.com/projects/privacy-browser-pc/issues">redmine.stoutner.com</ulink> please add it.
+            </para>
+
+            <para>
+                Below is a list of known prominent bugs or missing features in this alpha release.
+            </para>
+
+            <itemizedlist>
+                <listitem><para>The page zoom is <ulink url="https://redmine.stoutner.com/issues/799">momentarily reset</ulink> every time a new URL is loaded.</para></listitem>
+                <listitem><para>If domain settings change the user agent, loading of the new URL is interrupted and the
+                        <ulink url="https://redmine.stoutner.com/issues/821">previous site is reloaded</ulink>.</para></listitem>
+                <listitem><para>Browser <ulink url="https://redmine.stoutner.com/issues/831">error messages are not displayed</ulink> unless JavaScript is enabled.</para></listitem>
+                <listitem><para>Filter lists are <ulink url="https://redmine.stoutner.com/issues/969">not yet implemented</ulink>.</para></listitem>
+            </itemizedlist>
+        </sect1>
+    </chapter>
+
+    <!-- Using Privacy Browser. -->
+    <chapter id="using-privacy-browser">
+        <title>Using &privacybrowser;</title>
+
+        <para>
+            <mediaobject>
+                <imageobject>
+                    <imagedata fileref="privacybrowser-window.png" format="PNG"/>
+                </imageobject>
+
+                <textobject>
+                    <phrase>Screenshot</phrase>
+                </textobject>
+            </mediaobject>
+        </para>
+
+        <!-- JavaScript. -->
+        <sect1 id="javascript">
+            <title>JavaScript</title>
+
+            <para>
+                JavaScript allows web pages to run scripts (programs) on your device. It allows web pages to function more like apps, but it also allows web pages to spy on you.
+                Most of the tracking on the internet does not work when JavaScript is disabled.
+                JavaScript can be toggled by clicking on the privacy shield, which is blue if JavaScript is disabled and red when it is enabled.
+                <inlinemediaobject>
+                    <imageobject>
+                        <imagedata fileref="javascript.png" format="PNG"/>
+                    </imageobject>
+
+                    <textobject>
+                        <phrase>JavaScript</phrase>
+                    </textobject>
+                </inlinemediaobject>
+            </para>
+        </sect1>
+
+        <!-- Local Storage. -->
+        <sect1 id="local-storage">
+            <title>Local Storage</title>
+
+            <para>
+                <ulink url="https://doc.qt.io/qt-5/qwebenginecookiestore.html#setCookieFilter">Local storage</ulink>
+                in Privacy Browser encompasses cookies, DOM storage, IndexedDB, service workers, and the filesystem API.
+                Local storage can be toggled through an action on the toolbar.
+            </para>
+
+            <!-- Cookies. -->
+            <sect2 id="cookies">
+                <title>Cookies</title>
+
+                <para>
+                    <ulink url="https://en.wikipedia.org/wiki/HTTP_cookie">Cookies</ulink>
+                    allow websites to store small pieces of information for a specific host that are sent in the HTTP header every time the browser connects to that host.
+                    Privacy Browser allows a maximum of <ulink url="http://browsercookielimits.iain.guru/">180 cookies with a maximum size of 4096 bytes per cookie</ulink> to be set per domain.
+                    Cookies are often used to track users across the web, particularly third-party cookies (which are completely blocked in Privacy Browser).
+                    They are also used as a security mechanism on websites where you log in to identify it is you as you browse from page to page on a site.
+                </para>
+
+                <para>
+                    <mediaobject>
+                        <imageobject>
+                            <imagedata fileref="cookies.png" format="PNG"/>
+                        </imageobject>
+
+                        <textobject>
+                            <phrase>Cookies</phrase>
+                        </textobject>
+                    </mediaobject>
+                </para>
+
+                <para>
+                    The cookies dialog is opened from <xref linkend="settings-cookies"/>.
+                    Durable cookies are shared with all tabs that are opened after they are made durable and are preserved even when Privacy Browser is restarted.
+                    This allows users to stay logged in to sites of their choosing. No cookies are durable by default. Making a cookie durable requires specific user interaction.
+                </para>
+
+                <para>
+                    All other cookies are specific to the tab where they are created and are destroyed when the tab is closed.
+                </para>
+            </sect2>
+
+            <!-- DOM storage. -->
+            <sect2 id="dom-storage">
+                <title>DOM storage</title>
+
+                <para>
+                    <ulink url="https://en.wikipedia.org/wiki/Web_storage">DOM (Document Object Model) storage</ulink>, also knows as web storage,
+                    allows web pages to store information on a client device.
+                    The storage capacity is larger than for cookies and the data is not automatically sent in the headers with every HTTP request.
+                    In Privacy Browser, each website is allowed to store a <ulink url="https://arty.name/localstorage.html">5 MB of data</ulink> in DOM storage.
+                    DOM storage requires JavaScript to function, and, in addition, requires an extra toggle to be enabled.
+                    In Privacy Browser, DOM storage is limited to the tab where it is created and is destroyed when the tab is closed.
+                </para>
+            </sect2>
+
+            <!-- IndexedDB. -->
+            <sect2 id="indexeddb">
+                <title>IndexedDB</title>
+
+                <para>
+                    <ulink url="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</ulink>
+                    provides web pages with a local database where they can store “significant amounts of structured data”.
+                    There is disagreement on the internet about the maximum size of an IndexedDB database, probably because the various rendering engines keep changing their mind.
+                    But it is usually listed at somewhere between 20% and 80% of <emphasis>your entire hard drive</emphasis> with each individual domain limited to some segment of that.
+                    IndexedDB requires JavaScript to function.
+                    In Privacy Browser, this database is limited to the tab where it is created and is destroyed when the tab is closed.
+                </para>
+            </sect2>
+
+            <!-- Service Workers. -->
+            <sect2 id="service-workers">
+                <title>Service Workers</title>
+
+                <para>
+                    <ulink url="https://developer.chrome.com/docs/workbox/service-worker-overview/">Service workers</ulink> are offline JavaScript proxies of a website.
+                    They have their own cache that is usually hidden and hard to clear.
+                    They were designed by people who want the web browser to become the operating system and run full “apps”.
+                    In Privacy Browser, service workers are limited to the tab where they are created and are destroyed when the tab is closed.
+                </para>
+            </sect2>
+
+            <!-- Filesystem API. -->
+            <sect2 id="filesystem-api">
+                <title>Filesystem API</title>
+
+                <para>
+                    The <ulink url="https://developer.chrome.com/articles/file-system-access/">filesystem API</ulink> grants the browser direct access to the files on your system.
+                    Like service workers, the filesystem API is a summarily bad idea thought up by those who want the browser to become an operating system.
+                    Even when JavaScript and local storage are enabled, the filesystem API does not work in Privacy Browser.
+                </para>
+            </sect2>
+        </sect1>
+
+        <!-- User Agent. -->
+        <sect1 id="user-agent">
+            <title>User Agent</title>
+
+            <para>
+                The user agent is a text string that is sent as part of every HTTP header that identifies the browser to the web server.
+                Privacy Browser's default user agent is <code>PrivacyBrowser/1.0</code>.
+                Qt WebEngine 5.15.15’s default user agent is <code>Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.15.15 Chrome/87.0.4280.144 Safari/537.36</code>.
+            </para>
+
+            <para>
+                Over the years user agents have become quite lengthy,
+                partially because they tend to include a <ulink url="https://webaim.org/blog/user-agent-string-history/">brief history of the internet</ulink>.
+                In the modern world they serve almost no good purpose, but some web developers still think they need them so they can send different version of their website to different browsers.
+                Some servers <ulink url="https://www.stoutner.com/user-agent-problems/">refuse to function correctly</ulink> if they don't like the user agent that is sent.
+            </para>
+
+            <para>
+                At some point in the future Privacy Browser will send no user agent by default.
+                Not only is that currently impossible because the Qt WebEngine doesn't allow you to not send a user agent (I will probably have to fork it to enable that functionality),
+                but even web servers that don't care what the user agent is often refuse to send an answer if there is no user agent at all.
+                Getting rid of this relic of the internet is going to take some time and a retraining of common expectations.
+            </para>
+        </sect1>
+
+        <!-- Domain Settings. -->
+        <sect1 id="domain-settings">
+            <title>Domain Settings</title>
+
+            <para>
+                Domain setting make it easy to automatically change JavaScript, local storage, user agent, and other settings when the domain changes.
+                Domain settings for the current domain can be accessed through the domain settings button at the far right of the URL line edit.
+                Domain settings for all domains can be accessed through <xref linkend="settings-domain-settings"/>. When domain settings are active, the URL line edit will have a green background.
+            </para>
+        </sect1>
+
+        <!-- HTTP Pings. -->
+        <sect1 id="http-pings">
+            <title>HTTP Pings</title>
+
+            <para>
+                HTTP hyperlinks can have an extra <ulink url="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#ping">ping attribute</ulink> that sends a POST request to a different URL.
+                These HTTP pings are commonly used for tracking.
+                <ulink url="https://www.theregister.com/2019/04/06/apple_safari_changes/">Most browsers</ulink> send HTTP pings without informing the user
+                <ulink url="https://lists.w3.org/Archives/Public/public-html/2018May/0027.html">in any way</ulink>
+                (despite what The Register article says, current versions of FireFox also happily send HTTP pings).
+            </para>
+
+            <para>
+                Privacy Browser blocks all HTTP pings and presents a dialog informing the user every time the current tab attempts to send one.
+                Hopefully, as more people become aware of what HTTP pings are, web sites will become more embarrassed about using them.
+                This is a classic example of how those who wrote the HTTP specs do not have the best interests of internet users at heart.
+                I would like the day to come when this is removed from both the internet and the spec.
+                Here is an example of me <ulink url="https://forum.f-droid.org/t/the-f-droid-forum-should-stop-tracking-links-with-http-pings/">raising the issue</ulink> with F-Droid.
+            </para>
+        </sect1>
+
+        <!-- Spell Checking. -->
+        <sect1 id="spell-checking">
+            <title>Spell Checking</title>
+
+            <para>
+                Privacy Browser uses the binary <filename>.bdic</filename> Hunspell dictionary format
+                <ulink url="https://doc.qt.io/qt-5/qtwebengine-features.html#spellchecker">utilized by Qt WebEngine</ulink>
+                which was <ulink url="https://sites.google.com/a/chromium.org/dev/developers/how-tos/editing-the-spell-checking-dictionaries">created by Google for Chromium</ulink>.
+                Debian’s Hunspell dictionary language packages are slowly adding support for the <filename>.bdic</filename> format. Those which have are listed by Privacy Browser as suggested packages.
+            </para>
+
+            <para>
+                Once a <filename>.bdic</filename> dictionary is installed, it can be enabled in Privacy Browser’s settings.
+            </para>
+        </sect1>
+    </chapter>
+
+    <!-- Commands. -->
+    <chapter id="commands">
+        <title>Command Reference</title>
+
+        <!-- Main Window. -->
+        <sect1 id="main-window">
+            <title>Main Window</title>
+
+            <!-- File. -->
+            <sect2>
+                <title>File</title>
+
+                <variablelist>
+                    <!-- New Tab. -->
+                    <varlistentry id="file-new-tab">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>T</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>File</guimenu>
+
+                                <guimenuitem>New Tab</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Create a new tab.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- New Window. -->
+                    <varlistentry id="file-new-window">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>N</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>File</guimenu>
+
+                                <guimenuitem>New Window</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Create a new window.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Save Archive. -->
+                    <varlistentry id="file-save-archive">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>A</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>File</guimenu>
+
+                                <guimenuitem>Save Archive</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Save the webpage as an MHT (MIME encapsulation of aggregate HTML documents) archive.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Print. -->
+                    <varlistentry  id="file-print">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>P</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>File</guimenu>
+
+                                <guimenuitem>Print</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Print the document.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Print Preview. -->
+                    <varlistentry  id="file-print-preview">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;&Shift;<keycap>P</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>File</guimenu>
+
+                                <guimenuitem>Print Preview</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Display the print preview dialog.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Quit. -->
+                    <varlistentry  id="file-quit">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>Q</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>File</guimenu>
+
+                                <guimenuitem>Quit</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Quit &privacybrowser;.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                </variablelist>
+            </sect2>
+
+            <!-- Edit. -->
+            <sect2>
+                <title>Edit</title>
+
+                <variablelist>
+                    <!-- Find. -->
+                    <varlistentry id="edit-find">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>F</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>Edit</guimenu>
+
+                                <guimenuitem>Find</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Display the find toolbar and focus the find text line edit. If the toolbar is already displayed, the find text line edit is refocused and the text it contains is selected.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+
+                    <!-- Find Next. -->
+                    <varlistentry id="edit-find-next">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycap>F3</keycap>
+                                </shortcut>
+
+                                <guimenu>Edit</guimenu>
+
+                                <guimenuitem>Find Next</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Find the next entry on the page that matches the specified text. This action is only displayed if the find toolbar is visible.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Find Previous. -->
+                    <varlistentry id="edit-find-previous">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Shift;<keycap>F3</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>Edit</guimenu>
+
+                                <guimenuitem>Find Previous</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Find the previous entry on the page that matches the specified text. This action is only displayed if the find toolbar is visible.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                </variablelist>
+            </sect2>
+
+            <!-- View. -->
+            <sect2>
+                <title>View</title>
+
+                <variablelist>
+                    <!-- Zoom Default. -->
+                    <varlistentry id="zoom-default">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>0</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>View</guimenu>
+
+                                <guimenuitem>Zoom Default</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Return to either the app or the domain default zoom factor.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Zoom In. -->
+                    <varlistentry id="zoom-in">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>+</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>View</guimenu>
+
+                                <guimenuitem>Zoom In</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Increment the zoom factor by 0.25. Valid factors range from 0.25 to 5.00.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+
+                    <!-- Zoom Out. -->
+                    <varlistentry id="zoom-out">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>-</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>View</guimenu>
+
+                                <guimenuitem>Zoom Out</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Decrement the zoom factor by 0.25. Valid factors range from 0.25 to 5.00.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Refresh. -->
+                    <varlistentry id="view-refresh">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycap>F5</keycap>
+                                </shortcut>
+
+                                <guimenu>View</guimenu>
+
+                                <guimenuitem>Refresh</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Reload the website in the current tab.  When Refresh is visible, <xref linkend="view-stop"/> is hidden.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Reload and Bypass Cache. -->
+                    <varlistentry id="view-reload-and-bypass-cache">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>F5</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>View</guimenu>
+
+                                <guimenuitem>Reload and Bypass Cache</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Reload the website in the current tab bypassing any information in the cache and loading everything from the webserver.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Stop. -->
+                    <varlistentry id="view-stop">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;&Shift;<keycap>X</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>View</guimenu>
+
+                                <guimenuitem>Stop</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Stop the loading of the website in the current tab.  When Stop is visible, <xref linkend="view-refresh"/> is hidden.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- View Source. -->
+                    <varlistentry id="view-source">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>U</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>View</guimenu>
+
+                                <guimenuitem>View Source</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Toggle between viewing the source and viewing the rendered website.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- View Source in New Tab. -->
+                    <varlistentry id="view-source-in-new-tab">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;&Shift;<keycap>U</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>View</guimenu>
+
+                                <guimenuitem>View Source in New Tab></guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Open a new tab displaying the source of the current tab, or a new tab displaying the rendered version if the source is already displayed.
+                                Note that right-clicking on the background of a rendered website will display a context menu with a "View page source" entry, which performs the first of these actions.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Developer Tools. -->
+                    <varlistentry id="view-developer-tools">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycap>F12</keycap>
+                                </shortcut>
+
+                                <guimenu>View</guimenu>
+
+                                <guimenuitem>Developer Tools</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Display the developer tools, which are used to debug websites.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Full Screen Mode. -->
+                    <varlistentry id="view-full-screen-mode">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;&Shift;<keycap>F</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>View</guimenu>
+
+                                <guimenuitem>Full Screen Mode</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Toggle full screen mode. This performs the same action as <xref linkend="f11"/>.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                </variablelist>
+            </sect2>
+
+            <!-- Go. -->
+            <sect2>
+                <title>Go</title>
+
+                <variablelist>
+                    <!-- Back. -->
+                    <varlistentry id="go-back">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Alt;<keycap>Left</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>Go</guimenu>
+
+                                <guimenuitem>Back</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Navigate back one step in the current tab’s history. The action is disabled if it is not possible to go back.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Forward. -->
+                    <varlistentry id="go-forward">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Alt;<keycap>Right</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>Go</guimenu>
+
+                                <guimenuitem>Forward</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Navigate forward one step in the current tab’s history. The action is disabled if it is not possible to go forward.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Home. -->
+                    <varlistentry id="go-home">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Alt;<keycap>Home</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>Go</guimenu>
+
+                                <guimenuitem>Home</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Load the home page in the current tab.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                </variablelist>
+            </sect2>
+
+            <!-- On-The-Fly Settings. -->
+            <sect2>
+                <title>On-The-Fly Settings</title>
+
+                <variablelist>
+                    <!-- JavaScript. -->
+                    <varlistentry id="on-the-fly-settings-javascript">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>J</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>On-The-Fly Settings</guimenu>
+
+                                <guimenuitem>JavaScript</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Toggle JavaScript in the current tab.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Local Storage. -->
+                    <varlistentry id="on-the-fly-settings-local-storage">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>L</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>On-The-Fly Settings</guimenu>
+
+                                <guimenuitem>Local Storage</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Toggle local storage in the current tab.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- DOM Storage. -->
+                    <varlistentry id="on-the-fly-settings-dom-storage">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>D</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>On-The-Fly Settings</guimenu>
+
+                                <guimenuitem>DOM Storage</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Toggle DOM storage in the current tab.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                </variablelist>
+
+                <!-- User Agent. -->
+                <sect3>
+                    <title>User Agent</title>
+
+                    <variablelist>
+                        <!-- Privacy Browser. -->
+                        <varlistentry id="user-agent-privacy-browser">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Alt;<keycap>P</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>User Agent</guisubmenu>
+
+                                    <guimenuitem>Privacy Browser</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use the Privacy Browser user agent for the current tab.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- WebEngine Default. -->
+                        <varlistentry id="user-agent-webengine-default">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Alt;<keycap>W</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>User Agent</guisubmenu>
+
+                                    <guimenuitem>WebEngine Default</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use the WebEngine Default user agent for the current tab.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- Firefox on Linux. -->
+                        <varlistentry id="user-agent-firefox-linux">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Alt;<keycap>F</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>User Agent</guisubmenu>
+
+                                    <guimenuitem>Firefox on Linux</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use the Firefox on Linux user agent for the current tab.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- Chromium on Linux. -->
+                        <varlistentry id="user-agent-chromium-linux">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Alt;<keycap>C</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>User Agent</guisubmenu>
+
+                                    <guimenuitem>Chromium on Linux</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use the Chromium on Linux user agent for the current tab.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- Firefox on Windows. -->
+                        <varlistentry id="user-agent-firefox-windows">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Alt;&Shift;<keycap>F</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>User Agent</guisubmenu>
+
+                                    <guimenuitem>Firefox on Windows</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use the Firefox on Windows user agent for the current tab.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- Chrome on Windows. -->
+                        <varlistentry id="user-agent-chrome-windows">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Alt;&Shift;<keycap>C</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>User Agent</guisubmenu>
+
+                                    <guimenuitem>Chrome on Windows</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use the Chrome on Windows user agent for the current tab.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- Edge on Windows. -->
+                        <varlistentry id="user-agent-edge-windows">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Alt;<keycap>E</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>User Agent</guisubmenu>
+
+                                    <guimenuitem>Edge on Windows</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use the Edge on Windows user agent for the current tab.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- Safari on macOS. -->
+                        <varlistentry id="user-agent-safari-macOS">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Alt;<keycap>S</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>User Agent</guisubmenu>
+
+                                    <guimenuitem>Safari on macOS</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use the Safari on macOS user agent for the current tab.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- Custom User Agent. -->
+                        <varlistentry id="user-agent-custom">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Alt;&Shift;<keycap>C</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>User Agent</guisubmenu>
+
+                                    <guimenuitem>Custom</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use the custom user agent for the current tab. This action is only active if a custom user agent is set in the settings.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+                    </variablelist>
+                </sect3>
+
+                <!-- Zoom. -->
+                <sect3>
+                    <title>Zoom</title>
+
+                    <variablelist>
+                        <!-- Zoom Factor. -->
+                        <varlistentry id="on-the-fly-settings-zoom-factor">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Alt;<keycap>Z</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guimenuitem>Zoom Factor</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Display the zoom factor dialog.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+                    </variablelist>
+                </sect3>
+
+                <!-- Search Engine. -->
+                <sect3>
+                    <title>Search Engine</title>
+
+                    <variablelist>
+                        <!-- Mojeek. -->
+                        <varlistentry id="search-engine-mojeek">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Shift;<keycap>M</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>Search Engine</guisubmenu>
+
+                                    <guimenuitem>Mojeek</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use Mojeek as the search engine.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- Monocles. -->
+                        <varlistentry id="search-engine-monocles">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Shift;<keycap>O</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>Search Engine</guisubmenu>
+
+                                    <guimenuitem>Monocles</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use Mojeek as the search engine.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- MetaGer. -->
+                        <varlistentry id="search-engine-metager">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Shift;<keycap>E</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>Search Engine</guisubmenu>
+
+                                    <guimenuitem>MetaGer</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use MetaGer as the search engine.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- Google. -->
+                        <varlistentry id="search-engine-google">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Shift;<keycap>G</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>Search Engine</guisubmenu>
+
+                                    <guimenuitem>Google</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use Google as the search engine.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- Bing. -->
+                        <varlistentry id="search-engine-bing">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Shift;<keycap>B</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>Search Engine</guisubmenu>
+
+                                    <guimenuitem>Bing</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use Bing as the search engine.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- Yahoo. -->
+                        <varlistentry id="search-engine-yahoo">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Shift;<keycap>Y</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>Search Engine</guisubmenu>
+
+                                    <guimenuitem>Yahoo</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use Yahoo as the search engine.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+
+                        <!-- Custom Search Engine. -->
+                        <varlistentry id="search-engine-custom">
+                            <term>
+                                <menuchoice>
+                                    <shortcut>
+                                        <keycombo action="simul">&Ctrl;&Shift;<keycap>C</keycap></keycombo>
+                                    </shortcut>
+
+                                    <guimenu>On-The-Fly Settings</guimenu>
+
+                                    <guisubmenu>Search Engine</guisubmenu>
+
+                                    <guimenuitem>Custom</guimenuitem>
+                                </menuchoice>
+                            </term>
+
+                            <listitem>
+                                <para>
+                                    Use the custom search engine. This action is only active if a custom search engine is set in the settings.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+                    </variablelist>
+                </sect3>
+            </sect2>
+
+            <!-- Bookmarks. -->
+            <sect2>
+                <title>Bookmarks</title>
+
+                <variablelist>
+                    <!-- Edit Bookmarks. -->
+                    <varlistentry id="bookmarks-edit-bookmarks">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;&Alt;&Shift;<keycap>B</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>Bookmarks</guimenu>
+
+                                <guimenuitem>Edit Bookamrks</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Open the bookmark editing dialog.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- View Bookmarks Toolbar. -->
+                    <varlistentry id="bookmarks-view-bookmarks-toolbar">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;&Alt;<keycap>B</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>Bookmarks</guimenu>
+
+                                <guimenuitem>View Bookmarks Toolbar</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Toggle the visibility of the bookmarks toolbar.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Add Bookmark. -->
+                    <varlistentry id="bookmarks-add-bookmark">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>B</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>Bookmarks</guimenu>
+
+                                <guimenuitem>Add Bookmark</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Add a new bookmark.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Add Folder. -->
+                    <varlistentry id="bookmarks-add-folder">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Meta;<keycap>F</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>Bookmarks</guimenu>
+
+                                <guimenuitem>Add Folder</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Add a bookmark folder.  The meta key on most keyboards is the Windows key.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                </variablelist>
+            </sect2>
+
+            <!-- Settings. -->
+            <sect2>
+                <title>Settings</title>
+
+                <variablelist>
+                    <!-- Domain Settings. -->
+                    <varlistentry id="settings-domain-settings">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;&Shift;<keycap>D</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>Settings</guimenu>
+
+                                <guimenuitem>Domain Settings</guimenuitem>
+
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Display the domain settings dialog.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Cookies. -->
+                    <varlistentry id="settings-cookies">
+                        <term>
+                            <menuchoice>
+                                <shortcut>
+                                    <keycombo action="simul">&Ctrl;<keycap>;</keycap></keycombo>
+                                </shortcut>
+
+                                <guimenu>Settings</guimenu>
+
+                                <guimenuitem>Cookies</guimenuitem>
+                            </menuchoice>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Display the cookies dialog.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                </variablelist>
+            </sect2>
+
+            <!-- Common Settings and Help Entries. -->
+            <sect2 id="common-settings-help-menus">
+                <title>Common Settings and Help Entries</title>
+
+                <para>
+                    &privacybrowser; has many common &kde; <guimenu>Settings</guimenu> and <guimenu>Help</guimenu> menu items.
+                    For more information read the sections about the <ulink url="help:/fundamentals/ui.html#menus-settings">Settings Menu</ulink>
+                    and <ulink url="help:/fundamentals/ui.html#menus-help">Help Menu</ulink>.
+                </para>
+            </sect2>
+
+            <!-- Other Commands. -->
+            <sect2>
+                <title>Other Commands</title>
+
+                <variablelist>
+                    <!-- Find Case Sensitive. -->
+                    <varlistentry id="find-case-sensitive">
+                        <term>
+                            <command>
+                                &Ctrl;+<keycap>S</keycap>
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Toggle find case sensitive if the find toolbar is visible.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Open link in new tab. -->
+                    <varlistentry id="new-tab">
+                        <term>
+                            <command>
+                                &Ctrl;+&Shift;+Click
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Open link in new tab. This performs the same action as right-clicking on the link and selecting “Open link in new tab”.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Open link in background tab. -->
+                    <varlistentry id="new-background-tab">
+                        <term>
+                            <command>
+                                &Ctrl;+Click
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Open link in new background tab. This performs the same action as right-clicking on the link and selecting “Open link in new background tab”.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Open link in new window. -->
+                    <varlistentry id="new-window">
+                        <term>
+                            <command>
+                                &Shift;+Click
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Open link in new window. This performs the same action as right-clicking on the link and selecting “Open link in new window”.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Save link. -->
+                    <varlistentry id="save-link">
+                        <term>
+                            <command>
+                                &Alt;+Click
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Save link. This performs the same action as right-clicking on the link and selecting “Save link”.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- F11. -->
+                    <varlistentry id="f11">
+                        <term>
+                            <command>
+                                <keycap>F11</keycap>
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Toggle full screen mode. This performs the same action as <xref linkend="view-full-screen-mode"/>.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Escape. -->
+                    <varlistentry id="escape">
+                        <term>
+                            <command>
+                                <keycap>&Esc;</keycap>
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Escape runs the first command that applies from the following list.
+                            </para>
+
+                            <itemizedlist>
+                                <listitem>
+                                    <para>
+                                        Exit full screen browsing.
+                                    </para>
+                                </listitem>
+
+                                <listitem>
+                                    <para>
+                                        Clear the find text line edit and the find text highlights.
+                                    </para>
+                                </listitem>
+
+                                <listitem>
+                                    <para>
+                                        Hide the find text toolbar.
+                                    </para>
+                                </listitem>
+                            </itemizedlist>
+                        </listitem>
+                    </varlistentry>
+                </variablelist>
+            </sect2>
+        </sect1>
+
+        <!-- Cookies Dialog. -->
+        <sect1 id="cookies-dialog">
+            <title>Cookies Dialog</title>
+
+            <para>
+                The cookies dialog is accessed through <xref linkend="settings-cookies"/> (see <xref linkend="cookies"/>).
+            </para>
+
+            <sect2 id="cookies-dialog-commands">
+                <title>Commands</title>
+
+                <variablelist>
+                    <!-- Add Cookie. -->
+                    <varlistentry id="add-cookie">
+                        <term>
+                            <command>
+                                <keycap>A</keycap>
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Open the add cookie dialog.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Edit Cookie. -->
+                    <varlistentry id="edit-cookie">
+                        <term>
+                            <command>
+                                <keycap>E</keycap>
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Open the edit cookie dialog. This command is only valid if a cookie is currently selected.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Delete Cookies. -->
+                    <varlistentry id="delete-cookies">
+                        <term>
+                            <command>
+                                <keycap>D</keycap> or <keycap>&Del;</keycap>
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Delete the currently selected cookies. A dialog will confirm the deletion before it is processed.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Delete All Cookies. -->
+                    <varlistentry id="delete-all-cookie">
+                        <term>
+                            <command>
+                                <keycap>L</keycap>
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Delete all the cookies. A dialog will confirm the deletion before it is processed.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Close. -->
+                    <varlistentry id="close-cookies-dialog">
+                        <term>
+                            <command>
+                                <keycap>C</keycap> or <keycombo action="simul">&Ctrl;<keycap>Q</keycap></keycombo>
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Close the cookies dialog.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                </variablelist>
+            </sect2>
+        </sect1>
+
+        <!-- Save Dialog. -->
+        <sect1 id="save-dialog">
+            <title>Save Dialog</title>
+
+            <para>
+                The save dialog pops up when a download is initiated.
+            </para>
+
+            <sect2 id="save-dialog-commands">
+                <title>Commands</title>
+
+                <variablelist>
+                    <!-- Save. -->
+                    <varlistentry id="save-dialog-save">
+                        <term>
+                            <command>
+                                <keycap>S</keycap>
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Save the file.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <!-- Close. -->
+                    <varlistentry id="save-dialog-close">
+                        <term>
+                            <command>
+                                <keycap>C</keycap> or <keycombo action="simul">&Ctrl;<keycap>Q</keycap></keycombo>
+                            </command>
+                        </term>
+
+                        <listitem>
+                            <para>
+                                Close the save dialog without saving the file.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                </variablelist>
+            </sect2>
+        </sect1>
+    </chapter>
+
+    <!-- Changelog. -->
+    <chapter id="changelog">
+        <title>Changelog</title>
+
+        <!-- Version 0.5. -->
+        <sect1 id="version_0.5">
+            <title><ulink url="https://www.stoutner.com/privacy-browser-pc-0-5/">0.5</ulink> -
+                <ulink url="https://gitweb.stoutner.com/?p=PrivacyBrowserPC.git;a=commitdiff;h=a385ca128cb7bcc34ab5467edf21eb5e5664471a;ds=sidebyside">12 October 2023</ulink></title>
+
+            <itemizedlist>
+                <listitem><para>Add <ulink url="https://redmine.stoutner.com/issues/968">bookmarks</ulink>.</para></listitem>
+                <listitem><para>Add <ulink url="https://redmine.stoutner.com/issues/1031">zoom controls</ulink> to the status bar and a
+                        <ulink url="https://redmine.stoutner.com/issues/1044">default zoom shortcut</ulink>.</para></listitem>
+                <listitem><para>Add <ulink url="https://redmine.stoutner.com/issues/1037">keyboard shortcuts</ulink> for the URL toolbar actions.</para></listitem>
+                <listitem><para>Add an action to <ulink url="https://redmine.stoutner.com/issues/1022">view page source</ulink>.</para></listitem>
+                <listitem><para>Change the <ulink url="https://redmine.stoutner.com/issues/1019">domain settings combo boxes</ulink> to list
+                        <code>enabled</code> above <code>disabled</code>.</para></listitem>
+                <listitem><para>File downloads can now <ulink url="https://redmine.stoutner.com/issues/869">show the size</ulink> before the download begins.</para></listitem>
+                <listitem><para>Add PIE (<ulink url="https://redmine.stoutner.com/issues/1021">Position Independent Executable</ulink>) compiler flags.</para></listitem>
+                <listitem><para>Fix a bug that sometimes allowed <ulink url="https://redmine.stoutner.com/issues/1018">multiple domain settings to be created</ulink>.</para></listitem>
+                <listitem><para>Fix the download notification <ulink url="https://redmine.stoutner.com/issues/1017">not being cleared on Xfce</ulink>.</para></listitem>
+            </itemizedlist>
+        </sect1>
+
+        <!-- Version 0.4. -->
+        <sect1 id="version_0.4">
+            <title><ulink url="https://www.stoutner.com/privacy-browser-pc-0-4/">0.4</ulink> -
+                <ulink url="https://gitweb.stoutner.com/?p=PrivacyBrowserPC.git;a=commitdiff;h=b4c8c8d02113d14c2a07751eb3b0c1bdeec7abb4">13 June 2023</ulink></title>
+
+            <itemizedlist>
+                <listitem><para>Add a setting to <ulink url="https://redmine.stoutner.com/issues/1002">control spatial navigation</ulink>.</para></listitem>
+                <listitem><para>Add an action to <ulink url="https://redmine.stoutner.com/issues/982">reload and bypass cache</ulink>.</para></listitem>
+                <listitem><para>Fix a crash if one Privacy Browser window is closed while a <ulink url="https://redmine.stoutner.com/issues/1010">tab within it is loading</ulink>.</para></listitem>
+                <listitem><para>Add <ulink url="https://redmine.stoutner.com/issues/1009">keyboard+click commands</ulink> to the Handbook.</para></listitem>
+            </itemizedlist>
+        </sect1>
+
+        <!-- Version 0.3. -->
+        <sect1 id="version_0.3">
+            <title><ulink url="https://www.stoutner.com/privacy-browser-pc-0-3/">0.3</ulink> -
+                <ulink url="https://gitweb.stoutner.com/?p=PrivacyBrowserPC.git;a=commitdiff;h=ace098e8677ac0d6468b825c73e65b82c0d6993e">8 May 2023</ulink></title>
+
+            <itemizedlist>
+                <listitem><para>Add the changelog <ulink url="https://redmine.stoutner.com/issues/1000">to the Handbook</ulink>.</para></listitem>
+                <listitem><para>Add the missing <ulink url="https://redmine.stoutner.com/issues/999">current domain settings icon</ulink> on Gnome and Xfce.</para></listitem>
+                <listitem><para>Make changes to <ulink url="https://redmine.stoutner.com/issues/1005">build on Guix</ulink>.</para></listitem>
+            </itemizedlist>
+        </sect1>
+
+        <!-- Version 0.2. -->
+        <sect1 id="version_0.2">
+            <title><ulink url="https://www.stoutner.com/privacy-browser-pc-0-2/">0.2</ulink> -
+                <ulink url="https://gitweb.stoutner.com/?p=PrivacyBrowserPC.git;a=commitdiff;h=44b5d3a1f6a5e7fc2aa8530845f26eba7bc26f9a">17 April 2023</ulink></title>
+
+            <itemizedlist>
+                <listitem><para>Fix a crash on GNOME when <ulink url="https://redmine.stoutner.com/issues/994">downloading a file</ulink> with local storage disabled.</para></listitem>
+                <listitem><para>Fix problems with <ulink url="https://redmine.stoutner.com/issues/992">missing</ulink>
+                    <ulink url="https://redmine.stoutner.com/issues/993">icons</ulink> on GNOME.</para></listitem>
+                <listitem><para>Display an <ulink url="https://redmine.stoutner.com/issues/980">animated favorite icon</ulink> while a webpage is loading.</para></listitem>
+                <listitem><para><ulink url="https://redmine.stoutner.com/issues/975">Fix the Handbook</ulink> on non-KDE systems.</para></listitem>
+                <listitem><para>Change the <ulink url="https://redmine.stoutner.com/issues/978">order of entries</ulink> in the WebEngine context menu.</para></listitem>
+                <listitem><para>Make spellcheck languages <ulink url="https://redmine.stoutner.com/issues/958">easier to click on</ulink>.</para></listitem>
+                <listitem><para>Only generate a HTTP ping dialog if the <ulink url="https://redmine.stoutner.com/issues/979">request is made by the current tab</ulink>.</para></listitem>
+                <listitem><para>Add a section to the Handbook about <ulink url="https://redmine.stoutner.com/issues/976">HTTP pings</ulink>.</para></listitem>
+            </itemizedlist>
+        </sect1>
+
+        <!-- Version 0.1. -->
+        <sect1 id="version_0.1">
+            <title><ulink url="https://www.stoutner.com/privacy-browser-pc-0-1/">0.1</ulink> -
+                <ulink url="https://gitweb.stoutner.com/?p=PrivacyBrowserPC.git;a=commitdiff;h=73459c6685ec5e58d776f2594c9b587802363b1c">11 March 2023</ulink></title>
+
+            <itemizedlist>
+                <listitem><para>Initial release.</para></listitem>
+            </itemizedlist>
+        </sect1>
+    </chapter>
+
+    <!-- FAQ. -->
+    <chapter id="faq">
+        <title>Questions and Answers</title>
+
+        <qandaset id="faqlist">
+            <qandaentry>
+                <!-- Why are there no questions. -->
+                <question>
+                    <para>
+                        Why are there no questions?
+                    </para>
+                </question>
+
+                <answer>
+                    <para>
+                        Because Privacy Browser is so perfectly designed that none have ever been asked. Also, because this is an alpha release and nobody has had a chance to ask them yet. ;)
+                    </para>
+                </answer>
+            </qandaentry>
+        </qandaset>
+    </chapter>
+
+    <!-- Credits. -->
+    <chapter id="credits">
+        <title>Credits and License</title>
+
+        <para>
+            Privacy Browser PC copyright 2016-2017,2021-2024 Soren Stoutner <ulink url="mailto:soren@stoutner.com">soren@stoutner.com</ulink>.
+        </para>
+
+        <!-- Program. -->
+        <sect1 id="program">
+            <title>Program</title>
+
+            <para>
+                The source code is available at <ulink url="https://gitweb.stoutner.com/?p=PrivacyBrowserPC.git;a=summary">gitweb.stoutner.com</ulink>
+                or by running <filename>git clone https://git.stoutner.com/PrivacyBrowserPC.git</filename>.
+            </para>
+
+            <para>
+                Translators:
+                <itemizedlist>
+                    <listitem>
+                        <para>
+                            Translations will be added in a future release.
+                        </para>
+                    </listitem>
+                </itemizedlist>
+            </para>
+
+            <!-- The program license. -->
+            &underGPL;
+        </sect1>
+
+        <!-- Documentation. -->
+        <sect1 id="documentation">
+            <title>Documentation</title>
+
+            <para>
+                <filename>doc/index.docbook</filename> and <filename>src/com.stoutner.privacybrowser.appdata.xml</filename> are released under the
+                <ulink url="https://www.gnu.org/licenses/fdl-1.3.html">GFDL-1.3 license</ulink> with no Front-Cover or Back-Cover Texts or Invariant Sections.
+                All other documentation is released under the <ulink url="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</ulink>.
+            </para>
+
+            <!-- The documentation license. -->
+            &underFDL;
+        </sect1>
+
+        <!-- Icons. -->
+        <sect1 id="icons">
+            <title>Icons</title>
+
+            <para>
+                <inlinemediaobject>
+                    <imageobject>
+                        <imagedata fileref="privacybrowser.png" format="PNG"/>
+                    </imageobject>
+
+                    <textobject>
+                        <phrase>
+                            Privacy Browser
+                        </phrase>
+                    </textobject>
+                </inlinemediaobject>
+
+                <inlinemediaobject>
+                    <imageobject>
+                        <imagedata fileref="javascript.png" format="PNG"/>
+                    </imageobject>
+
+                    <textobject>
+                        <phrase>
+                            JavaScript
+                        </phrase>
+                    </textobject>
+                </inlinemediaobject>
+
+                <inlinemediaobject>
+                    <imageobject>
+                        <imagedata fileref="privacybrowser-monochrome.png" format="PNG"/>
+                    </imageobject>
+
+                    <textobject>
+                        <phrase>
+                            Privacy Browser Monochrome
+                        </phrase>
+                    </textobject>
+                </inlinemediaobject>
+
+                are derived from <filename>security</filename> and <filename>language</filename>, which are part of the <ulink url="https://fonts.google.com/icons">Android Material icon set</ulink>
+                and are released under the <ulink url="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</ulink>. Modifications copyright 2016-2017,2021-2023 Soren Stoutner.
+                The resulting images are released under the <ulink url="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</ulink>.
+            </para>
+
+            <para>
+                <inlinemediaobject>
+                    <imageobject>
+                        <imagedata fileref="loading.gif" format="GIF"/>
+                    </imageobject>
+
+                    <textobject>
+                        <phrase>
+                            Loading
+                        </phrase>
+                    </textobject>
+                </inlinemediaobject>
+
+                comes from <ulink url="https://github.com/Codelessly/FlutterLoadingGIFs/blob/master/packages/cupertino_activity_indicator_selective.gif">FlutterLoadingGIFs</ulink>
+                where it is named <filename>cupertino_activity_indicator_selective.gif</filename>.
+                It is released under the <ulink url="https://opensource.org/license/0bsd/">Zero-Clause BSD License</ulink>.
+            </para>
+        </sect1>
+    </chapter>
 </book>
-
-<!--
-Local Variables:
-mode: xml
-sgml-minimize-attributes:nil
-sgml-general-insert-case:lower
-sgml-indent-step:0
-sgml-indent-data:nil
-End:
-
-vim:tabstop=2:shiftwidth=2:expandtab
-kate: space-indent on; indent-width 2; tab-width 2; indent-mode none;
--->
diff --git a/doc/javascript.png b/doc/javascript.png
new file mode 100644 (file)
index 0000000..b06507a
Binary files /dev/null and b/doc/javascript.png differ
diff --git a/doc/loading.gif b/doc/loading.gif
new file mode 100644 (file)
index 0000000..b2dd625
Binary files /dev/null and b/doc/loading.gif differ
diff --git a/doc/privacybrowser-monochrome.png b/doc/privacybrowser-monochrome.png
new file mode 100644 (file)
index 0000000..6042a59
Binary files /dev/null and b/doc/privacybrowser-monochrome.png differ
diff --git a/doc/privacybrowser-window.png b/doc/privacybrowser-window.png
new file mode 100644 (file)
index 0000000..e753474
Binary files /dev/null and b/doc/privacybrowser-window.png differ
diff --git a/doc/privacybrowser.png b/doc/privacybrowser.png
new file mode 100644 (file)
index 0000000..100693c
Binary files /dev/null and b/doc/privacybrowser.png differ
index 4cf447629c5039f821dc7283e856cd73aa55e48e..4fbbcbca09fdb7a0a18ca02567cbb7ffc42be4fe 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
 # You should have received a copy of the GNU General Public License
 # along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>.
 
-
 #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-01-29 11:34-0700\n"
+"POT-Creation-Date: 2023-05-08 14:31-0700\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language: \n"
 "MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Type: text/plain; charset=CHARSET\n"
 "Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
 
-#: src/mainwindow.cpp:74
+#: src/widgets/TabWidget.cpp:174
 #, kde-format
-msgctxt "@title:tab"
-msgid "General"
+msgctxt "New tab label."
+msgid "New Tab"
+msgstr ""
+
+#: src/widgets/TabWidget.cpp:325
+#, kde-format
+msgctxt "HTTP Ping blocked dialog title"
+msgid "HTTP Ping Blocked"
+msgstr ""
+
+#: src/widgets/TabWidget.cpp:328
+#, kde-format
+msgctxt "HTTP Ping blocked dialog text"
+msgid ""
+"This request has been blocked because it sends a naughty HTTP ping to %1."
+msgstr ""
+
+#: src/widgets/TabWidget.cpp:838 src/widgets/TabWidget.cpp:1086
+#, kde-format
+msgctxt "Save file dialog caption"
+msgid "Save File"
+msgstr ""
+
+#: src/widgets/TabWidget.cpp:858 src/widgets/TabWidget.cpp:888
+#, kde-format
+msgctxt "Download notification title"
+msgid "Download"
+msgstr ""
+
+#: src/widgets/TabWidget.cpp:861
+#, kde-format
+msgctxt "Downloading notification text"
+msgid "Downloading %1"
+msgstr ""
+
+#: src/widgets/TabWidget.cpp:867
+#, kde-format
+msgctxt "Download notification action"
+msgid "Cancel"
+msgstr ""
+
+#: src/widgets/TabWidget.cpp:891
+#, kde-format
+msgctxt "Download canceled notification"
+msgid "%1 download canceled"
+msgstr ""
+
+#: src/widgets/TabWidget.cpp:910
+#, kde-format
+msgctxt "Download progress notification text"
+msgid "%1% of %2 downloaded (%3 of %4 bytes)"
+msgstr ""
+
+#: src/widgets/TabWidget.cpp:916
+#, kde-format
+msgctxt "Download progress notification text"
+msgid "%1:  %2 bytes downloaded"
+msgstr ""
+
+#: src/widgets/TabWidget.cpp:927
+#, kde-format
+msgctxt "Download finished notification text"
+msgid "%1 download finished"
+msgstr ""
+
+#: src/dialogs/SaveDialog.cpp:36
+#, kde-format
+msgctxt "The save dialog window title"
+msgid "Save"
+msgstr ""
+
+#: src/dialogs/SaveDialog.cpp:84
+#, kde-format
+msgctxt "Unknown download file size.  The bold style should be preserved."
+msgid "<b>unknown</b>"
+msgstr ""
+
+#: src/dialogs/SaveDialog.cpp:86
+#, kde-format
+msgctxt "Download file size.  The bold style should be preserved."
+msgid "<b>%1 bytes</b>"
+msgstr ""
+
+#: src/dialogs/SaveDialog.cpp:89
+#, kde-format
+msgctxt "The save key shortcut."
+msgid "s"
+msgstr ""
+
+#: src/dialogs/SaveDialog.cpp:90 src/dialogs/CookiesDialog.cpp:281
+#, kde-format
+msgctxt "The close key shortcut."
+msgid "c"
+msgstr ""
+
+#: src/dialogs/DurableCookiesDialog.cpp:32
+#, kde-format
+msgctxt "The durable cookies dialog window title"
+msgid "Durable Cookies"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:40
+#, kde-format
+msgctxt "The domain settings dialog window title"
+msgid "Domain Settings"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:382
+#, kde-format
+msgctxt "Domain settings DOM storage label."
+msgid "DOM storage enabled"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:384
+#, kde-format
+msgctxt "Domain settings DOM storage label."
+msgid "DOM storage disabled"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:395
+#, kde-format
+msgctxt "Domain settings DOM storage label.  The <b> tags should be retained."
+msgid "<b>DOM storage disabled</b>"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:406
+#, kde-format
+msgctxt "Domain settings DOM storage label.  The <b> tags should be retained."
+msgid "<b>DOM storage enabled</b>"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:425
+#, kde-format
+msgctxt "Domain settings JavaScript label."
+msgid "JavaScript enabled"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:427
+#, kde-format
+msgctxt "Domain settings JavaScript label."
+msgid "JavaScript disabled"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:438
+#, kde-format
+msgctxt "Domain settings JavaScript label.  The <b> tags should be retained."
+msgid "<b>JavaScript disabled</b>"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:449
+#, kde-format
+msgctxt "Domain settings JavaScript label.  The <b> tags should be retained."
+msgid "<b>JavaScript enabled</b>"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:468
+#, kde-format
+msgctxt "Domain settings local storage label."
+msgid "Local storage enabled"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:470
+#, kde-format
+msgctxt "Domain settings local storage label."
+msgid "Local storage disabled"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:481
+#, kde-format
+msgctxt ""
+"Domain settings local storage label.  The <b> tags should be retained."
+msgid "<b>Local storage disabled</b>"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:492
+#, kde-format
+msgctxt ""
+"Domain settings local storage label.  The <b> tabs should be retained."
+msgid "<b>Local storage enabled</b>"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:542
+#, kde-format
+msgctxt "Add domain dialog title"
+msgid "Add Domain"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:544
+#, kde-format
+msgctxt ""
+"Add domain message.  The \n"
+"\n"
+" are newline codes that should be retained"
+msgid ""
+"Add a new domain.  Doing so will also save any pending changes that have "
+"been made to other domains.\n"
+"\n"
+"*. may be prepended to a domain to include all subdomains (eg. *.stoutner."
+"com)."
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:561
+#, kde-format
+msgctxt "Delete domain dialog title"
+msgid "Delete Domain"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:564
+#, kde-format
+msgctxt "Delete domain dialog main message"
+msgid "Delete the current domain?"
+msgstr ""
+
+#: src/dialogs/DomainSettingsDialog.cpp:567
+#, kde-format
+msgctxt "Delete domain dialog secondary message"
+msgid ""
+"Doing so will also save any pending changes that have been made to other "
+"domains."
+msgstr ""
+
+#: src/dialogs/AddOrEditCookieDialog.cpp:41
+#, kde-format
+msgctxt "The add cookie dialog window title."
+msgid "Add Cookie"
+msgstr ""
+
+#: src/dialogs/AddOrEditCookieDialog.cpp:43
+#, kde-format
+msgctxt "The edit cookie dialog window title."
+msgid "Edit Cookie"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:124
+#, kde-format
+msgctxt "The cookies dialog window title"
+msgid "Cookies"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:145
+#, kde-format
+msgctxt "The cookie Name header."
+msgid "Name"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:146
+#, kde-format
+msgctxt "The cookie Durable header."
+msgid "Durable"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:147
+#, kde-format
+msgctxt "The cookie Path header."
+msgid "Path"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:148
+#, kde-format
+msgctxt "The cookie Expiration Date header."
+msgid "Expiration Date"
 msgstr ""
 
-#: src/main.cpp:46
+#: src/dialogs/CookiesDialog.cpp:149
 #, kde-format
+msgctxt "The cookie HTTP Only header."
+msgid "HTTP Only"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:150
+#, kde-format
+msgctxt "The cookie Secure header."
+msgid "Secure"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:151
+#, kde-format
+msgctxt "The cookie Value header."
+msgid "Value"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:155
+#, kde-format
+msgctxt "The cookie Name tool tip."
+msgid ""
+"The name identifies the cookie.  Each cookie has a unique combination of "
+"domain, name, and path."
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:157
+#, kde-format
+msgctxt "The cookie Durable tool tip"
+msgid ""
+"Durable cookies persist across restarts, irrespective of the expiration "
+"date. All other cookies are deleted when Privacy Browser closes, "
+"irrespective of the expiration date."
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:158
+#, kde-format
+msgctxt "The cookie Path tool tip."
+msgid "Websites can restrict cookie access to subpath of their URL."
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:160
+#, kde-format
+msgctxt "The cookie Expiration Date tool tip."
+msgid ""
+"Cookies without an expiration date are known as session cookies and are "
+"expected to be deleted every time the browser closes."
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:162
+#, kde-format
+msgctxt "The cookie HTTP Only tool tip."
+msgid ""
+"Restrict cookie access to HTTP (and HTTPS). This prevents JavaScript from "
+"accessing the cookie, which hardens it against cross-site scripting attacks."
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:163
+#, kde-format
+msgctxt "The cookie Secure tool tip."
+msgid ""
+"Only allow the cookie to be transferred across HTTPS (as opposed to HTTP)."
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:164
+#, kde-format
+msgctxt "The cookie Value tool tip."
+msgid "The value contains the cookie data."
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:205 src/dialogs/CookiesDialog.cpp:208
+#: src/dialogs/CookiesDialog.cpp:209 src/dialogs/CookiesDialog.cpp:409
+#: src/dialogs/CookiesDialog.cpp:412 src/dialogs/CookiesDialog.cpp:413
+#: src/dialogs/CookiesDialog.cpp:786
+#, kde-format
+msgid "yes"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:205 src/dialogs/CookiesDialog.cpp:208
+#: src/dialogs/CookiesDialog.cpp:209 src/dialogs/CookiesDialog.cpp:409
+#: src/dialogs/CookiesDialog.cpp:412 src/dialogs/CookiesDialog.cpp:413
+#, kde-format
+msgid "no"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:258 src/dialogs/CookiesDialog.cpp:827
+#, kde-format
+msgctxt "View the durable cookies button"
+msgid "Durable cookies - %1"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:276
+#, kde-format
+msgctxt "The add cookie key shortcut."
+msgid "a"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:277
+#, kde-format
+msgctxt "The edit cookie key shortcut."
+msgid "e"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:278
+#, kde-format
+msgctxt "The delete cookie key shortcut."
+msgid "d"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:280
+#, kde-format
+msgctxt "The delete all key shortcut."
+msgid "l"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:618
+#, kde-format
+msgctxt "Delete all cookies dialog title"
+msgid "Delete All Cookies"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:621
+#, kde-format
+msgctxt "Delete all cookies dialog text"
+msgid "Delete all cookies?"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:624 src/dialogs/CookiesDialog.cpp:686
+#, kde-format
+msgctxt "Delete durable cookies check box"
+msgid "Delete even if durable"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:694
+#, kde-format
+msgctxt "Delete cookies dialog title"
+msgid "Delete %1 Cookie"
+msgid_plural "Delete 1% Cookies"
+msgstr[0] ""
+msgstr[1] ""
+
+#: src/dialogs/CookiesDialog.cpp:697
+#, kde-format
+msgctxt "Delete cookies dialog text"
+msgid "Delete %1 cookie?"
+msgid_plural "Delete %1 cookies?"
+msgstr[0] ""
+msgstr[1] ""
+
+#: src/dialogs/CookiesDialog.cpp:702
+#, kde-format
+msgctxt "Delete cookie dialog title"
+msgid "Delete 1 Cookie"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:705
+#, kde-format
+msgctxt "Delete cookie dialog text"
+msgid "Delete 1 cookie?"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:812
+#, kde-format
+msgctxt "Delete cookies button."
+msgid "&Delete %1 cookie"
+msgid_plural "&Delete %1 cookies"
+msgstr[0] ""
+msgstr[1] ""
+
+#: src/dialogs/CookiesDialog.cpp:817
+#, kde-format
+msgctxt "Delete cookies button."
+msgid "&Delete 1 cookie"
+msgstr ""
+
+#: src/dialogs/CookiesDialog.cpp:823
+#, kde-format
+msgctxt "Delete cookie button."
+msgid "&Delete cookie"
+msgstr ""
+
+#: src/main.cpp:48
+#, kde-format
+msgctxt "Program Name"
 msgid "Privacy Browser"
 msgstr ""
 
-#: src/main.cpp:49
+#: src/main.cpp:51
 #, kde-format
+msgctxt "Developer Information"
 msgid "Soren Stoutner"
 msgstr ""
 
-#: src/main.cpp:49
+#: src/main.cpp:51
 #, kde-format
+msgctxt "Developer Information"
 msgid "Principal developer"
 msgstr ""
 
-#: src/main.cpp:53
+#: src/main.cpp:56
 #, kde-format
-msgid "Copyright © 2016-2017,2021-2022 Soren Stoutner <soren@stoutner.com>"
+msgctxt "Copyright"
+msgid "Copyright 2016-2017,2021-2023 Soren Stoutner <soren@stoutner.com>"
 msgstr ""
 
-#: src/main.cpp:58
+#: src/main.cpp:62
 #, kde-format
+msgctxt "Tagline"
 msgid "A web browser that respects your privacy."
 msgstr ""
 
-#: rc.cpp:3
+#: src/helpers/UserAgentHelper.cpp:51
 #, kde-format
-msgid "Zoom factor"
+msgctxt "User agents"
+msgid "System default"
 msgstr ""
 
-#: rc.cpp:6
+#: src/helpers/UserAgentHelper.cpp:52
 #, kde-format
-msgid "Set the zoom factor between 0.25 and 5.00."
+msgctxt "User agents"
+msgid "Privacy Browser"
 msgstr ""
 
-#: rc.cpp:9
+#: src/helpers/UserAgentHelper.cpp:53
 #, kde-format
-msgid "Set the zoom factor between 0.25 and 5.00.  The default is 1.00."
+msgctxt "User agents"
+msgid "WebEngine default"
 msgstr ""
 
-#: rc.cpp:12
+#: src/helpers/UserAgentHelper.cpp:54
+#, kde-format
+msgctxt "User agents"
+msgid "Firefox on Linux"
+msgstr ""
+
+#: src/helpers/UserAgentHelper.cpp:55
+#, kde-format
+msgctxt "User agents"
+msgid "Chromium on Linux"
+msgstr ""
+
+#: src/helpers/UserAgentHelper.cpp:56
+#, kde-format
+msgctxt "User agents"
+msgid "Firefox on Windows"
+msgstr ""
+
+#: src/helpers/UserAgentHelper.cpp:57
+#, kde-format
+msgctxt "User agents"
+msgid "Chrome on Windows"
+msgstr ""
+
+#: src/helpers/UserAgentHelper.cpp:58
+#, kde-format
+msgctxt "User agents"
+msgid "Edge on Windows"
+msgstr ""
+
+#: src/helpers/UserAgentHelper.cpp:59
+#, kde-format
+msgctxt "User agents"
+msgid "Safari on macOS"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:151
+#, kde-format
+msgctxt "New tab action"
+msgid "New Tab"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:152
+#, kde-format
+msgctxt "New window action"
+msgid "New Window"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:161
+#, kde-format
+msgctxt "Search engine"
+msgid "Mojeek"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:162
+#, kde-format
+msgctxt "Search engine"
+msgid "Monocles"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:163
+#, kde-format
+msgctxt "Search engine"
+msgid "MetaGer"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:164
+#, kde-format
+msgctxt "Search engine"
+msgid "Google"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:165
+#, kde-format
+msgctxt "Search engine"
+msgid "Bing"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:166
+#, kde-format
+msgctxt "Search engine"
+msgid "Yahoo"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:167
+#, kde-format
+msgctxt "Domain Settings action"
+msgid "Domain Settings"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:168 src/windows/BrowserWindow.cpp:983
+#, kde-format
+msgctxt "The Cookies action, which also displays the number of cookies"
+msgid "Cookies - %1"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:169
+#, kde-format
+msgctxt "JavaScript action"
+msgid "JavaScript"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:170
+#, kde-format
+msgctxt "The Local Storage action"
+msgid "Local Storage"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:171
+#, kde-format
+msgctxt "DOM Storage action"
+msgid "DOM Storage"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:172
+#, kde-format
+msgctxt "Find Case Sensitive action"
+msgid "Find Case Sensitive"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:173
+#, kde-format
+msgctxt "Hide Find Text action"
+msgid "Hide Find Text"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:208
+#, kde-format
+msgctxt "The open new tab key sequence."
+msgid "Ctrl+T"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:209
+#, kde-format
+msgctxt "The open new window key sequence."
+msgid "Ctrl+N"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:210
+#, kde-format
+msgctxt "The print preview key sequence."
+msgid "Ctrl+Shift+P"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:211
+#, kde-format
+msgctxt "The Privacy Browser user agent key sequence."
+msgid "Ctrl+Alt+P"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:212
+#, kde-format
+msgctxt "The WebEngine Default user agent key sequence."
+msgid "Ctrl+Alt+W"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:213
+#, kde-format
+msgctxt "The Firefox on Linux user agent key sequence."
+msgid "Ctrl+Alt+F"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:214
+#, kde-format
+msgctxt "The Chromium on Linux user agent key sequence."
+msgid "Ctrl+Alt+C"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:215
+#, kde-format
+msgctxt "The Firefox on Windows user agent key sequence."
+msgid "Ctrl+Alt+Shift+F"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:216
+#, kde-format
+msgctxt "The Chrome on Windows user agent key sequence."
+msgid "Ctrl+Alt+Shift+C"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:217
+#, kde-format
+msgctxt "The Edge on Windows user agent key sequence."
+msgid "Ctrl+Alt+E"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:218
+#, kde-format
+msgctxt "The Safari on macOS user agent key sequence."
+msgid "Ctrl+Alt+S"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:219
+#, kde-format
+msgctxt "The custom user agent key sequence."
+msgid "Alt+Shift+C"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:220
+#, kde-format
+msgctxt "The zoom factor key sequence."
+msgid "Ctrl+Alt+Z"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:221
+#, kde-format
+msgctxt "The Mojeek search engine key sequence."
+msgid "Ctrl+Shift+M"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:222
+#, kde-format
+msgctxt "The Monocles search engine key sequence."
+msgid "Ctrl+Shift+O"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:223
+#, kde-format
+msgctxt "The MetaGer search engine key sequence."
+msgid "Ctrl+Shift+E"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:224
+#, kde-format
+msgctxt "The Google search engine key sequence."
+msgid "Ctrl+Shift+G"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:225
+#, kde-format
+msgctxt "The Bing search engine key sequence."
+msgid "Ctrl+Shift+B"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:226
+#, kde-format
+msgctxt "The Yahoo search engine key sequence."
+msgid "Ctrl+Shift+Y"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:227
+#, kde-format
+msgctxt "The custom search engine key sequence."
+msgid "Ctrl+Shift+C"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:228
+#, kde-format
+msgctxt "The domain settings key sequence."
+msgid "Ctrl+D"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:229
+#, kde-format
+msgctxt "The cookies dialog key sequence."
+msgid "Ctrl+;"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:335
+#, kde-format
+msgctxt "The URL line edit placeholder text"
+msgid "URL or Search Terms"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:336
+#, kde-format
+msgctxt "The find line edit placeholder text"
+msgid "Find Text"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:352
+#, kde-format
+msgctxt "Default find results."
+msgid "0/0"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:418
+#, kde-format
+msgctxt "The toggle full screen shortcut."
+msgid "F11"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:462
+#, kde-format
+msgctxt "The domain settings dialog title"
+msgid "Domain Settings"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:576
+#, kde-format
+msgctxt "The on-the-fly zoom factor dialog title"
+msgid "On-The-Fly Zoom Factor"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:577
+#, kde-format
+msgctxt "The instruction text of the on-the-fly zoom factor dialog"
+msgid "Enter a zoom factor between 0.25 and 5.00"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:665
+#, kde-format
+msgctxt "Select download location dialog caption"
+msgid "Select Download Location"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:814
+#, kde-format
+msgctxt "Settings tab title"
+msgid "Privacy"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:815
+#, kde-format
+msgctxt "Settings tab title"
+msgid "General"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:816
+#, kde-format
+msgctxt "Settings tab title"
+msgid "Spell Check"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1074
+#, kde-format
+msgctxt "The main search engine menu action"
+msgid "Search Engine - Mojeek"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1085
+#, kde-format
+msgctxt "The main search engine menu action"
+msgid "Search Engine - Monocles"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1096
+#, kde-format
+msgctxt "The main search engine menu action"
+msgid "Search Engine - MetaGer"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1107
+#, kde-format
+msgctxt "The main search engine menu action"
+msgid "Search Engine - Google"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1118
+#, kde-format
+msgctxt "The main search engine menu action"
+msgid "Search Engine - Bing"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1129
+#, kde-format
+msgctxt "The main search engine menu action"
+msgid "Search Engine - Yahoo"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1140
+#, kde-format
+msgctxt "The main search engine menu action"
+msgid "Search Engine - Custom"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1165 src/windows/BrowserWindow.cpp:1299
+#, kde-format
+msgctxt "@action"
+msgid "Custom"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1184
+#, kde-format
+msgctxt "The main user agent menu action"
+msgid "User Agent - Privacy Browser"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1195
+#, kde-format
+msgctxt "The main user agent menu action"
+msgid "User Agent - WebEngine default"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1206
+#, kde-format
+msgctxt "The main user agent menu action"
+msgid "User Agent - Firefox on Linux"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1217
+#, kde-format
+msgctxt "The main user agent menu action"
+msgid "User Agent - Chromium on Linux"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1228
+#, kde-format
+msgctxt "The main user agent menu action"
+msgid "User Agent - Firefox on Windows"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1239
+#, kde-format
+msgctxt "The main user agent menu action"
+msgid "User Agent - Chrome on Windows"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1250
+#, kde-format
+msgctxt "The main user agent menu action"
+msgid "User Agent - Edge on Windows"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1261
+#, kde-format
+msgctxt "The main user agent menu action"
+msgid "User Agent - Safari on macOS"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1272
+#, kde-format
+msgctxt "The main user agent menu action"
+msgid "User Agent - Custom"
+msgstr ""
+
+#: src/windows/BrowserWindow.cpp:1309
+#, kde-format
+msgctxt "@action"
+msgid "Zoom Factor - %1"
+msgstr ""
+
+#: rc.cpp:3
+#, kde-format
+msgid "On-The-Fly Settings"
+msgstr ""
+
+#: rc.cpp:6
+#, kde-format
+msgid "Navigation Toolbar"
+msgstr ""
+
+#: rc.cpp:9
+#, kde-format
+msgid "URL Toolbar"
+msgstr ""
+
+#: rc.cpp:12
+#, kde-format
+msgid "Add cookie"
+msgstr ""
+
+#: rc.cpp:15
+#, kde-format
+msgid "Edit cookie"
+msgstr ""
+
+#: rc.cpp:18
+#, kde-format
+msgid "Delete cookie"
+msgstr ""
+
+#: rc.cpp:21
+#, kde-format
+msgid "Delete all"
+msgstr ""
+
+#: rc.cpp:24
+#, kde-format
+msgid "Add domain"
+msgstr ""
+
+#: rc.cpp:27
+#, kde-format
+msgid "Delete domain"
+msgstr ""
+
+#: rc.cpp:30
+#, kde-format
+msgid "Domain name"
+msgstr ""
+
+#: rc.cpp:33
+#, kde-format
+msgid ""
+"*. may be prepended to a domain to include all subdomains (eg. *.stoutner."
+"com)."
+msgstr ""
+
+#: rc.cpp:36 rc.cpp:126
+#, kde-format
+msgid "JavaScript"
+msgstr ""
+
+#: rc.cpp:39
+#, kde-format
+msgid "JavaScript allows websites to run programs (scripts) on the device."
+msgstr ""
+
+#: rc.cpp:42 rc.cpp:57 rc.cpp:72 rc.cpp:87 rc.cpp:120
+#, kde-format
+msgid "System default"
+msgstr ""
+
+#: rc.cpp:45
+#, kde-format
+msgid "JavaScript disabled"
+msgstr ""
+
+#: rc.cpp:48
+#, kde-format
+msgid "JavaScript enabled"
+msgstr ""
+
+#: rc.cpp:51 rc.cpp:132
+#, kde-format
+msgid "Local storage"
+msgstr ""
+
+#: rc.cpp:54 rc.cpp:135
+#, kde-format
+msgid ""
+"Local storage includes cookies, DOM storage, IndexedDB, service workers, and "
+"the filesystem API.  DOM storage also requires a separate control to be "
+"enabled.  Local storage is disabled by default."
+msgstr ""
+
+#: rc.cpp:60
+#, kde-format
+msgid "Local storage disabled"
+msgstr ""
+
+#: rc.cpp:63
+#, kde-format
+msgid "Local storage enabled"
+msgstr ""
+
+#: rc.cpp:66 rc.cpp:138
+#, kde-format
+msgid "DOM storage"
+msgstr ""
+
+#: rc.cpp:69
+#, kde-format
+msgid ""
+"DOM storage, sometimes called web storage, is like cookies on steroids.  To "
+"function, it requires that both JavaScript and local storage be enabled."
+msgstr ""
+
+#: rc.cpp:75
+#, kde-format
+msgid "DOM storage disabled"
+msgstr ""
+
+#: rc.cpp:78
+#, kde-format
+msgid "DOM storage enabled"
+msgstr ""
+
+#: rc.cpp:81 rc.cpp:144
+#, kde-format
+msgid "User agent"
+msgstr ""
+
+#: rc.cpp:84
+#, kde-format
+msgid ""
+"The user agent identifies the browser to the web server.  It serves no "
+"useful purpose, but many web servers refuse to return the web page if they "
+"don't see a user agent they like."
+msgstr ""
+
+#: rc.cpp:90 rc.cpp:150
+#, kde-format
+msgid "Privacy Browser"
+msgstr ""
+
+#: rc.cpp:93
+#, kde-format
+msgid "WebEngine default"
+msgstr ""
+
+#: rc.cpp:96
+#, kde-format
+msgid "Firefox on Linux"
+msgstr ""
+
+#: rc.cpp:99
+#, kde-format
+msgid "Chromium on Linux"
+msgstr ""
+
+#: rc.cpp:102
+#, kde-format
+msgid "Firefox on Windows"
+msgstr ""
+
+#: rc.cpp:105
+#, kde-format
+msgid "Chrome on Windows"
+msgstr ""
+
+#: rc.cpp:108
+#, kde-format
+msgid "Edge on Windows"
+msgstr ""
+
+#: rc.cpp:111
+#, kde-format
+msgid "Safari on macOS"
+msgstr ""
+
+#: rc.cpp:114 rc.cpp:207
+#, kde-format
+msgid "Zoom factor"
+msgstr ""
+
+#: rc.cpp:117
+#, kde-format
+msgid "Valid values for the zoom factor are between 0.25 and 5.00."
+msgstr ""
+
+#: rc.cpp:123
+#, kde-format
+msgid "Custom"
+msgstr ""
+
+#: rc.cpp:129
+#, kde-format
+msgid ""
+"JavaScript allows websites to run programs (scripts) on the device.  The "
+"default is disabled."
+msgstr ""
+
+#: rc.cpp:141
+#, kde-format
+msgid ""
+"DOM storage, sometimes called web storage, is like cookies on steroids.  To "
+"function, it requires that both JavaScript and local storage be enabled.  It "
+"is disabled by default."
+msgstr ""
+
+#: rc.cpp:147
+#, kde-format
+msgid ""
+"The user agent identifies the browser to the web server.  It serves no "
+"useful purpose, but many web servers refuse to return the web page if they "
+"don't see a user agent they like.  The default is Privacy Browser."
+msgstr ""
+
+#: rc.cpp:153
+#, kde-format
+msgid "WebEngine Default"
+msgstr ""
+
+#: rc.cpp:156
+#, kde-format
+msgid "Firefox Linux"
+msgstr ""
+
+#: rc.cpp:159
+#, kde-format
+msgid "Chromium Linux"
+msgstr ""
+
+#: rc.cpp:162
+#, kde-format
+msgid "Firefox Windows"
+msgstr ""
+
+#: rc.cpp:165
+#, kde-format
+msgid "Chrome Windows"
+msgstr ""
+
+#: rc.cpp:168
+#, kde-format
+msgid "Edge Windows"
+msgstr ""
+
+#: rc.cpp:171
+#, kde-format
+msgid "Safari macOS"
+msgstr ""
+
+#: rc.cpp:174
+#, kde-format
+msgid ""
+"Spell checking languages can be added by installing the Hunspell language "
+"packages. One or more languages can be selected. All selected languages will "
+"be checked simultaneously."
+msgstr ""
+
+#: rc.cpp:177
+#, kde-format
+msgid "Homepage"
+msgstr ""
+
+#: rc.cpp:180
+#, kde-format
+msgid "The default is https://www.mojeek.com/."
+msgstr ""
+
+#: rc.cpp:183
+#, kde-format
+msgid "Search engine"
+msgstr ""
+
+#: rc.cpp:186
+#, kde-format
+msgid "The default is Mojeek."
+msgstr ""
+
+#: rc.cpp:189
+#, kde-format
+msgid "Mojeek"
+msgstr ""
+
+#: rc.cpp:192
+#, kde-format
+msgid "Monocles"
+msgstr ""
+
+#: rc.cpp:195
+#, kde-format
+msgid "MetaGer"
+msgstr ""
+
+#: rc.cpp:198
+#, kde-format
+msgid "Google"
+msgstr ""
+
+#: rc.cpp:201
+#, kde-format
+msgid "Bing"
+msgstr ""
+
+#: rc.cpp:204
+#, kde-format
+msgid "Yahoo"
+msgstr ""
+
+#: rc.cpp:210
+#, kde-format
+msgid ""
+"Valid values for the zoom factor are between 0.25 and 5.00.  The default is "
+"1.00."
+msgstr ""
+
+#: rc.cpp:213
+#, kde-format
+msgid "Download Location"
+msgstr ""
+
+#: rc.cpp:216
+#, kde-format
+msgid "The default is System Download Directory."
+msgstr ""
+
+#: rc.cpp:219
+#, kde-format
+msgid "System Download Directory"
+msgstr ""
+
+#: rc.cpp:222
+#, kde-format
+msgid "Browse"
+msgstr ""
+
+#: rc.cpp:225
+#, kde-format
+msgid "Tabs on top"
+msgstr ""
+
+#: rc.cpp:228
+#, kde-format
+msgid "Display the tabs at the top of the screen.  The default is enabled."
+msgstr ""
+
+#: rc.cpp:231
+#, kde-format
+msgid "Full Screen Browsing"
+msgstr ""
+
+#: rc.cpp:234
+#, kde-format
+msgid "Hide menu bar"
+msgstr ""
+
+#: rc.cpp:237
+#, kde-format
+msgid "Hide the menu bar when browsing full screen. The default is enabled."
+msgstr ""
+
+#: rc.cpp:240
+#, kde-format
+msgid "Hide toolbars"
+msgstr ""
+
+#: rc.cpp:243
+#, kde-format
+msgid "Hide the toolbars when browsing full screen.  The default is enabled."
+msgstr ""
+
+#: rc.cpp:246
+#, kde-format
+msgid "Hide tab bar"
+msgstr ""
+
+#: rc.cpp:249
+#, kde-format
+msgid "Hide the tab bar when browsing full screen.  The default is enabled."
+msgstr ""
+
+#: rc.cpp:252
+#, kde-format
+msgid "Hide status bar"
+msgstr ""
+
+#: rc.cpp:255
+#, kde-format
+msgid "Hide the status bar when browsing full screen.  The default is enabled."
+msgstr ""
+
+#: rc.cpp:258
+#, kde-format
+msgid "<b>Would you like to download the following file?</b>"
+msgstr ""
+
+#: rc.cpp:261
+#, kde-format
+msgid "File details"
+msgstr ""
+
+#: rc.cpp:264
+#, kde-format
+msgid "URL:"
+msgstr ""
+
+#: rc.cpp:267
+#, kde-format
+msgid "File type:"
+msgstr ""
+
+#: rc.cpp:270
+#, kde-format
+msgid "MIME type:"
+msgstr ""
+
+#: rc.cpp:273
+#, kde-format
+msgid "Size:"
+msgstr ""
+
+#: rc.cpp:276
+#, kde-format
+msgid "&Add cookie"
+msgstr ""
+
+#: rc.cpp:279
+#, kde-format
+msgid "&Delete cookie"
+msgstr ""
+
+#: rc.cpp:282
+#, kde-format
+msgid "Delete &all"
+msgstr ""
+
+#: rc.cpp:285
+#, kde-format
+msgid "Cookies prepended by a period are accessible to all subdomains."
+msgstr ""
+
+#: rc.cpp:288
+#, kde-format
+msgid "Domain"
+msgstr ""
+
+#: rc.cpp:291 rc.cpp:309 rc.cpp:321 rc.cpp:336 rc.cpp:348
+#, kde-format
+msgid "&nbsp;&nbsp;&nbsp;"
+msgstr ""
+
+#: rc.cpp:294
+#, kde-format
+msgid ""
+"The name identifies the cookie.  Each cookie has a unique combination of "
+"domain, name, and path."
+msgstr ""
+
+#: rc.cpp:297
+#, kde-format
+msgid "Name"
+msgstr ""
+
+#: rc.cpp:300 rc.cpp:306
+#, kde-format
+msgid ""
+"Durable cookies pursist across restarts, irrespective of the expiration "
+"date. All other cookies are deleted when Privacy Browser closes, "
+"irrespective of the expiration date."
+msgstr ""
+
+#: rc.cpp:303
+#, kde-format
+msgid "Durable"
+msgstr ""
+
+#: rc.cpp:312
+#, kde-format
+msgid "Websites can restrict cookie access to subpath of their URL."
+msgstr ""
+
+#: rc.cpp:315
+#, kde-format
+msgid "Path"
+msgstr ""
+
+#: rc.cpp:318
+#, kde-format
+msgid "/"
+msgstr ""
+
+#: rc.cpp:324 rc.cpp:330
+#, kde-format
+msgid ""
+"Cookies without an expiration date are known as session cookies and are "
+"expected to be deleted every time the browser closes."
+msgstr ""
+
+#: rc.cpp:327
+#, kde-format
+msgid "Expiration date"
+msgstr ""
+
+#: rc.cpp:333
+#, kde-format
+msgid "&nbsp;"
+msgstr ""
+
+#: rc.cpp:339 rc.cpp:345
+#, kde-format
+msgid ""
+"Restrict cookie access to HTTP (and HTTPS). This prevents JavaScript from "
+"accessing the cookie, which hardens it against cross-site scripting attacks."
+msgstr ""
+
+#: rc.cpp:342
+#, kde-format
+msgid "HTTP only"
+msgstr ""
+
+#: rc.cpp:351 rc.cpp:357
+#, kde-format
+msgid ""
+"Only allow the cookie to be transferred across HTTPS (as opposed to HTTP)."
+msgstr ""
+
+#: rc.cpp:354
+#, kde-format
+msgid "Secure"
+msgstr ""
+
+#: rc.cpp:360
+#, kde-format
+msgid "The value contains the cookie data."
+msgstr ""
+
+#: rc.cpp:363
 #, kde-format
-msgid "Main Toolbar"
+msgid "Value"
 msgstr ""
 
-#: rc.cpp:13
+#: rc.cpp:364
 #, kde-format
 msgctxt "NAME OF TRANSLATORS"
 msgid "Your names"
 msgstr ""
 
-#: rc.cpp:14
+#: rc.cpp:365
 #, kde-format
 msgctxt "EMAIL OF TRANSLATORS"
 msgid "Your emails"
index d80c89d7ca56e3fde102f18fe7c8ab14a0ff5d9a..837e42a9b952fbff1f06278774cc9b3d641a16b3 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
 # along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>.
 
 # Create the executable and add the resources.
-add_executable(privacy-browser resources.qrc)
+add_executable(privacybrowser resources.qrc)
 
 # List the sources to include in the executable.
-target_sources(privacy-browser PRIVATE
+target_sources(privacybrowser PRIVATE
     main.cpp
 )
 
 # Add the Qt logging category.  This will create the `debug.h` header file.
-ecm_qt_declare_logging_category(privacy-browser
+ecm_qt_declare_logging_category(privacybrowser
     HEADER debug.h
     IDENTIFIER PRIVACYBROWSER
     CATEGORY_NAME "privacybrowser"
 )
 
 # Include the KConfig controller file.
-kconfig_add_kcfg_files(privacy-browser settings/Settings.kcfgc)
+kconfig_add_kcfg_files(privacybrowser settings/Settings.kcfgc)
 
 # Use KDE Frameworks to handle internationalization of the following UI files.
-ki18n_wrap_ui(privacy-browser
+ki18n_wrap_ui(privacybrowser
+    uis/AddBookmarkDialog.ui
+    uis/AddFolderDialog.ui
     uis/AddOrEditCookieDialog.ui
     uis/AddTabWidget.ui
+    uis/BookmarksDialog.ui
     uis/CookiesDialog.ui
     uis/DomainSettingsDialog.ui
     uis/DurableCookiesDialog.ui
+    uis/EditBookmarkDialog.ui
+    uis/EditFolderDialog.ui
+    uis/HttpAuthenticationDialog.ui
     uis/SaveDialog.ui
     uis/SettingsGeneral.ui
     uis/SettingsPrivacy.ui
+    uis/SettingsSpellCheck.ui
     uis/TabWidget.ui
 )
 
 # Link the following libraries.
-target_link_libraries(privacy-browser
+target_link_libraries(privacybrowser
     Qt5::Core
     Qt5::Gui
     Qt5::Sql
+    Qt5::PrintSupport
     Qt5::Widgets
     Qt5::WebEngineCore
     Qt5::WebEngineWidgets
@@ -61,6 +69,7 @@ target_link_libraries(privacy-browser
     KF5::DBusAddons
     KF5::DocTools
     KF5::I18n
+    KF5::Notifications
     KF5::KIOCore
     KF5::KIOWidgets
     KF5::XmlGui
@@ -78,13 +87,29 @@ add_subdirectory(widgets)
 add_subdirectory(windows)
 
 # Install Privacy Browser using the default KDE arguments.
-install(TARGETS privacy-browser ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
+install(TARGETS privacybrowser ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
 
 # Install Privacy Browser according to the instructions in the desktop file, specifying the permissions.
 install(PROGRAMS com.stoutner.privacybrowser.desktop DESTINATION ${KDE_INSTALL_APPDIR} PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
 
-# Install Privacy Browser's metadata file.
+# Install the metadata file.
 install(FILES com.stoutner.privacybrowser.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
 
-# Install Privacy Browser's icon.
-ecm_install_icons(ICONS icons/sc-apps-privacy-browser.svg DESTINATION ${KDE_INSTALL_ICONDIR})
+# Install the notifyrc file.
+install(FILES privacybrowser.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
+
+# Install the standard icons.
+ecm_install_icons(ICONS icons/sc-apps-privacybrowser.svg DESTINATION ${KDE_INSTALL_ICONDIR})
+ecm_install_icons(ICONS icons/16-apps-privacybrowser.png DESTINATION ${KDE_INSTALL_ICONDIR})
+ecm_install_icons(ICONS icons/22-apps-privacybrowser.png DESTINATION ${KDE_INSTALL_ICONDIR})
+ecm_install_icons(ICONS icons/24-apps-privacybrowser.png DESTINATION ${KDE_INSTALL_ICONDIR})
+ecm_install_icons(ICONS icons/32-apps-privacybrowser.png DESTINATION ${KDE_INSTALL_ICONDIR})
+ecm_install_icons(ICONS icons/48-apps-privacybrowser.png DESTINATION ${KDE_INSTALL_ICONDIR})
+ecm_install_icons(ICONS icons/64-apps-privacybrowser.png DESTINATION ${KDE_INSTALL_ICONDIR})
+ecm_install_icons(ICONS icons/128-apps-privacybrowser.png DESTINATION ${KDE_INSTALL_ICONDIR})
+ecm_install_icons(ICONS icons/256-apps-privacybrowser.png DESTINATION ${KDE_INSTALL_ICONDIR})
+ecm_install_icons(ICONS icons/512-apps-privacybrowser.png DESTINATION ${KDE_INSTALL_ICONDIR})
+ecm_install_icons(ICONS icons/1024-apps-privacybrowser.png DESTINATION ${KDE_INSTALL_ICONDIR})
+
+# Install the symbolic icon (used by Gnome).
+install(FILES icons/privacybrowser-symbolic.svg DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/symbolic/apps/)
index 49704a95f0367fa19e84fb9d20c48c313d2232c1..99e921fecdf91037940ce7f695ae2f6171d46830 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 
   Permission is granted to copy, distribute and/or modify this document
   under the terms of the GNU Free Documentation License, Version 1.3
@@ -14,6 +14,7 @@
   along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>. -->
 
 <!-- The XML specification can be found at <https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html>. -->
+<!-- The file can be validated using `appstreamcli validate com.stoutner.privacybrowser.appdata.xml`. -->
 <component type="desktop-application">
     <id>com.stoutner.privacybrowser</id>
 
 
     <name>Privacy Browser</name>
 
-    <summary>A web browser that respects your privacy.</summary>
+    <summary>A web browser that respects your privacy</summary>
+
+    <description>
+        <p>
+            Privacy Browser is a web browser based on Qt WebEngine with a focus on privacy and security.
+            Features like JavaScript and cookies are disabled by default but are easy to automatically enable on-the-fly or by domain.
+        </p>
+    </description>
 
     <categories>
         <category>Network</category>
     <launchable type="desktop-id">com.stoutner.privacybrowser.desktop</launchable>
 
     <releases>
-        <release version="0.1.1" date="2022-03-01"/>
-        <release version="0.1" date="2022-01-22"/>
+        <release version="0.5" date="2023-10-12">
+            <description>
+                <p>Add bookmarks.</p>
+                <p>Add zoom controls to the status bar and a default zoom shortcut.</p>
+                <p>Add keyboard shortcuts for the URL toolbar actions.</p>
+                <p>Add an action to view page source.</p>
+                <p>Change the domain settings combo boxes to list enabled above disabled.</p>
+                <p>File downloads can now show the size before the download begins.</p>
+                <p>Add PIE (Position Independent Executable) compiler flags.</p>
+                <p>Fix a bug that sometimes allowed multiple domain settings to be created.</p>
+                <p>Fix the download notification not being cleared on Xfce.</p>
+            </description>
+        </release>
+
+        <release version="0.4" date="2023-06-13">
+            <description>
+                <p>Add a setting to control spatial navigation.</p>
+                <p>Add an action to reload and bypass cache.</p>
+                <p>Fix a crash if one Privacy Browser window is closed while a tab within it is loading.</p>
+                <p>Add keyboard+click commands to the Handbook.</p>
+            </description>
+        </release>
+
+        <release version="0.3" date="2023-05-08">
+            <description>
+                <p>Add the changelog to the Handbook.</p>
+                <p>Add the missing current domain settings icon on Gnome and Xfce.</p>
+                <p>Make changes to build on Guix.</p>
+            </description>
+        </release>
+
+        <release version="0.2" date="2023-04-17">
+            <description>
+                <p>Fix a crash on GNOME when downloading a file with local storage disabled.</p>
+                <p>Fix problems with missing icons on GNOME.</p>
+                <p>Display an animated favorite icon while a webpage is loading.</p>
+                <p>Fix the Handbook on non-KDE systems.</p>
+                <p>Change the order of entries in the WebEngine context menu.</p>
+                <p>Make spellcheck languages easier to click on.</p>
+                <p>Only generate a HTTP ping dialog if the request is made by the current tab.</p>
+                <p>Add a section to the Handbook about HTTP pings.</p>
+            </description>
+        </release>
+
+        <release version="0.1" date="2023-03-11">
+            <description>
+                <p>Initial release.</p>
+            </description>
+        </release>
     </releases>
 
     <provides>
-        <binary>privacy-browser</binary>
+        <binary>privacybrowser</binary>
     </provides>
 
     <project_license>GPL-3.0-or-later</project_license>
 
     <developer_name>Soren Stoutner</developer_name>
 
-    <!-- TODO -->
     <screenshots>
         <screenshot type="default">
-            <image> https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html#tag-screenshots </image>
+            <image type="source">https://gitweb.stoutner.com/?p=PrivacyBrowserPC.git;a=blob_plain;f=doc/privacybrowser-window.png;hb=HEAD</image>
+            <caption>The main Privacy Browser window.</caption>
         </screenshot>
     </screenshots>
 
     <update_contact>soren@stoutner.com</update_contact>
+
+    <!-- This content rating was generated from <https://hughsie.github.io/oars/generate.html>. -->
+    <content_rating type="oars-1.0" />
 </component>
index a7ca9d61b0a7433b8d48efdf3297092a4aa45ed8..6132f6ffb2bfc40a86749ce2501f65df0687f14f 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
@@ -16,6 +16,7 @@
 # along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>.
 
 # The specification for the .desktop file can be found at <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html>.
+# The file can be validated by running `desktop-file-validate com.stoutner.privacybrowser.desktop`.
 [Desktop Entry]
 # Set the type.
 Type=Application
@@ -30,16 +31,16 @@ GenericName=Web Browser
 Categories=Network;WebBrowser;
 
 # Set the icon.
-Icon=privacy-browser.svg
+Icon=privacybrowser
 
 # Set the executable with the optional URL argument.
-Exec=privacy-browser %u
+Exec=privacybrowser %u
 
 # Specify the WM class, which groups windows of the same application together.
-StartupWMClass=privacy-browser
+StartupWMClass=privacybrowser
 
-# TODO.  Set the document path.
+# Set the handbook docbook path.
 X-DocPath=privacybrowser/index.html
 
 # Enumerate the supported MIME types.
-MimeType=text/html;text/xml;application/xhtml+xml;application/xml;x-scheme-handler/http;x-scheme-handler/https;
+MimeType=text/html;application/xml;application/xhtml+xml;application/x-mimearchive;x-scheme-handler/http;x-scheme-handler/https;
diff --git a/src/databases/BookmarksDatabase.cpp b/src/databases/BookmarksDatabase.cpp
new file mode 100644 (file)
index 0000000..1050ba7
--- /dev/null
@@ -0,0 +1,1015 @@
+/*
+ * Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "BookmarksDatabase.h"
+
+// Define the private static schema constants.
+const int BookmarksDatabase::SCHEMA_VERSION = 0;
+
+// Define the public static constants.
+const QString BookmarksDatabase::CONNECTION_NAME = "bookmarks_database";
+const QString BookmarksDatabase::BOOKMARK_NAME = "bookmark_name";
+const QString BookmarksDatabase::BOOKMARKS_TABLE = "bookmarks";
+const QString BookmarksDatabase::BOOKMARK_URL = "bookmark_url";
+const QString BookmarksDatabase::DISPLAY_ORDER = "display_order";
+const QString BookmarksDatabase::FAVORITE_ICON = "favorite_icon";
+const QString BookmarksDatabase::FOLDER_ID = "folder_id";
+const QString BookmarksDatabase::ID = "_id";
+const QString BookmarksDatabase::IS_FOLDER = "is_folder";
+const QString BookmarksDatabase::PARENT_FOLDER_ID = "parent_folder_id";
+
+// Construct the class.
+BookmarksDatabase::BookmarksDatabase() {}
+
+void BookmarksDatabase::addDatabase()
+{
+    // Add the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), CONNECTION_NAME);
+
+    // Set the database name.
+    bookmarksDatabase.setDatabaseName(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bookmarks.db");
+
+    // Open the database.
+    if (bookmarksDatabase.open())  // Opening the database succeeded.
+    {
+        // Check to see if the bookmarks table already exists.
+        if (bookmarksDatabase.tables().contains(BOOKMARKS_TABLE))  // The bookmarks table already exists.
+        {
+            // Query the database schema version.
+            QSqlQuery schemaVersionQuery = bookmarksDatabase.exec(QStringLiteral("PRAGMA user_version"));
+
+            // Move to the first record.
+            schemaVersionQuery.first();
+
+            // Get the current schema version.
+            int currentSchemaVersion = schemaVersionQuery.value(0).toInt();
+
+            // Check to see if the schema has been updated.
+            if (currentSchemaVersion < SCHEMA_VERSION)
+            {
+                // Run the schema update code.
+
+                // Update the schema version.
+                bookmarksDatabase.exec("PRAGMA user_version = " + QString::number(SCHEMA_VERSION));
+            }
+        }
+        else  // The bookmarks table does not exist.
+        {
+            // Instantiate a create table query.
+            QSqlQuery createTableQuery(bookmarksDatabase);
+
+            // Populate the create table query.
+            createTableQuery.prepare("CREATE TABLE " + BOOKMARKS_TABLE + "(" +
+                                      ID + " INTEGER PRIMARY KEY, " +
+                                      BOOKMARK_NAME + " TEXT, " +
+                                      BOOKMARK_URL + " TEXT, " +
+                                      PARENT_FOLDER_ID + " INTEGER DEFAULT 0, " +
+                                      DISPLAY_ORDER + " INTEGER DEFAULT 0, " +
+                                      IS_FOLDER + " BOOLEAN DEFAULT FALSE, " +
+                                      FOLDER_ID + " INTEGER DEFAULT 0, " +
+                                      FAVORITE_ICON + " BLOB)");
+
+            // Execute the query.
+            if (!createTableQuery.exec())
+            {
+                // Log any errors.
+                qDebug().noquote().nospace() << "Error creating table:  " << bookmarksDatabase.lastError();
+            }
+
+            // Set the schema version.
+            bookmarksDatabase.exec("PRAGMA user_version = " + QString::number(SCHEMA_VERSION));
+        }
+    }
+    else  // Opening the database failed.
+    {
+        // Write the last database error message to the debug output.
+        qDebug().noquote().nospace() << "Error opening database:  " << bookmarksDatabase.lastError();
+    }
+};
+
+void BookmarksDatabase::addBookmark(const BookmarkStruct *bookmarkStructPointer)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Get the folder item count.
+    int folderItemCount = getFolderItemCount(bookmarkStructPointer->parentFolderId);
+
+    // Instantiate an add bookmark query.
+    QSqlQuery addBookmarkQuery(bookmarksDatabase);
+
+    // Prepare the add bookmark query.
+    addBookmarkQuery.prepare("INSERT INTO " + BOOKMARKS_TABLE + " (" +
+                              BOOKMARK_NAME + ", " +
+                              BOOKMARK_URL + ", " +
+                              PARENT_FOLDER_ID + ", " +
+                              DISPLAY_ORDER + ", " +
+                              FAVORITE_ICON + ") " +
+                              "VALUES (:bookmark_name, :bookmark_url, :parent_folder_id, :display_order, :favorite_icon)"
+    );
+
+    // Bind the query values.
+    addBookmarkQuery.bindValue(":bookmark_name", bookmarkStructPointer->name);
+    addBookmarkQuery.bindValue(":bookmark_url", bookmarkStructPointer->url);
+    addBookmarkQuery.bindValue(":parent_folder_id", bookmarkStructPointer->parentFolderId);
+    addBookmarkQuery.bindValue(":display_order", folderItemCount);
+    addBookmarkQuery.bindValue(":favorite_icon", getFavoriteIconBase64String(bookmarkStructPointer->favoriteIcon));
+
+    // Execute the add bookmark query.
+    addBookmarkQuery.exec();
+}
+
+void BookmarksDatabase::addFolder(const BookmarkStruct *bookmarkStructPointer)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Get the folder item count.
+    int folderItemCount = getFolderItemCount(bookmarkStructPointer->parentFolderId);
+
+    // Instantiate an add folder query.
+    QSqlQuery addFolderQuery(bookmarksDatabase);
+
+    // Prepare the add folder query.
+    addFolderQuery.prepare("INSERT INTO " + BOOKMARKS_TABLE + " (" +
+                              BOOKMARK_NAME + ", " +
+                              PARENT_FOLDER_ID + ", " +
+                              DISPLAY_ORDER + ", " +
+                              IS_FOLDER + ", " +
+                              FOLDER_ID + ", " +
+                              FAVORITE_ICON + ") " +
+                              "VALUES (:bookmark_name, :parent_folder_id, :display_order, :is_folder, :folder_id, :favorite_icon)"
+    );
+
+    // Bind the query values.
+    addFolderQuery.bindValue(":bookmark_name", bookmarkStructPointer->name);
+    addFolderQuery.bindValue(":parent_folder_id", bookmarkStructPointer->parentFolderId);
+    addFolderQuery.bindValue(":display_order", folderItemCount);
+    addFolderQuery.bindValue(":is_folder", 1);
+    addFolderQuery.bindValue(":folder_id", generateFolderId());
+    addFolderQuery.bindValue(":favorite_icon", getFavoriteIconBase64String(bookmarkStructPointer->favoriteIcon));
+
+    // Execute the add folder query.
+    addFolderQuery.exec();
+}
+
+void BookmarksDatabase::deleteBookmark(const int databaseId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a delete bookmark query.
+    QSqlQuery deleteBookmarkQuery(bookmarksDatabase);
+
+    // Prepare the delete bookmark query.
+    deleteBookmarkQuery.prepare("DELETE FROM " + BOOKMARKS_TABLE + " WHERE " + ID + " = :id");
+
+    // Bind the query values.
+    deleteBookmarkQuery.bindValue(":id", databaseId);
+
+    // Execute the query.
+    deleteBookmarkQuery.exec();
+}
+
+void BookmarksDatabase::deleteBookmarks(const QString url)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a parent folder IDs query.
+    QSqlQuery parentFolderIdsQuery(bookmarksDatabase);
+
+    // Prepare the parent folder IDs query.
+    parentFolderIdsQuery.prepare("SELECT " + PARENT_FOLDER_ID + " FROM " + BOOKMARKS_TABLE + " WHERE " + IS_FOLDER + " = 0 AND " + BOOKMARK_URL + " = :url");
+
+    // Bind the query values.
+    parentFolderIdsQuery.bindValue(":url", url);
+
+    // Execute the query.
+    parentFolderIdsQuery.exec();
+
+    // Instantiate a delete bookmarks query.
+    QSqlQuery deleteBookmarksQuery(bookmarksDatabase);
+
+    // Prepare the delete bookmark query.
+    deleteBookmarksQuery.prepare("DELETE FROM " + BOOKMARKS_TABLE + " WHERE " + IS_FOLDER + " = 0 AND " + BOOKMARK_URL + " = :url");
+
+    // Bind the query values.
+    deleteBookmarksQuery.bindValue(":url", url);
+
+    // Execute the query.
+    deleteBookmarksQuery.exec();
+
+    // Create a parent folder IDs list.  A standard list can be sorted and deduplicated.
+    std::list<double> parentFolderIdsList;
+
+    // Populate the parent folder IDs list.
+    while (parentFolderIdsQuery.next())
+    {
+        // Add the parent folder ID to the list.
+        parentFolderIdsList.push_back(parentFolderIdsQuery.value(PARENT_FOLDER_ID).toDouble());
+    }
+
+    // Sort the parent folder IDs list.
+    parentFolderIdsList.sort();
+
+    // Remove duplicate entries from the parent folder IDs list.
+    parentFolderIdsList.unique();
+
+    // Update the display order of each folder where a bookmark was deleted.
+    for (const double parentFolderId : parentFolderIdsList)
+        updateFolderContentsDisplayOrder(parentFolderId);
+}
+
+double BookmarksDatabase::generateFolderId()
+{
+    // Get the current time in epoch format (milliseconds).
+    double possibleFolderId = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
+
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a existing folder query.
+    QSqlQuery existingFolderQuery(bookmarksDatabase);
+
+    // Prepare the existing folder query.
+    existingFolderQuery.prepare("SELECT " + ID + " FROM " + BOOKMARKS_TABLE + " WHERE " + FOLDER_ID + " = :possible_folder_id");
+
+    // Bind the query values.
+    existingFolderQuery.bindValue(":possible_folder_id", possibleFolderId);
+
+    // Execute the query.
+    existingFolderQuery.exec();
+
+    // Generate a new folder ID if this one is not unique.  The existing folder query will only be valid if there is at least one item.
+    if (existingFolderQuery.isValid())
+        possibleFolderId = generateFolderId();
+
+    return possibleFolderId;
+}
+
+QList<QString>* BookmarksDatabase::getAllFolderUrls(const double folderId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a folder URLs query.
+    QSqlQuery folderUrlsQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    folderUrlsQuery.setForwardOnly(true);
+
+    // Prepare the folder URLs query.
+    folderUrlsQuery.prepare("SELECT " + BOOKMARK_URL + ", " + IS_FOLDER + ", " + FOLDER_ID + " FROM " + BOOKMARKS_TABLE + " WHERE " + PARENT_FOLDER_ID + " = :parent_folder_id");
+
+    // Bind the query values.
+    folderUrlsQuery.bindValue(":parent_folder_id", folderId);
+
+    // Execute the query.
+    folderUrlsQuery.exec();
+
+    // Create a folder URLs list.
+    QList<QString> *folderUrlsListPointer = new QList<QString>;
+
+    // Populate the folder URLs list.
+    while (folderUrlsQuery.next())
+    {
+        // Process the entry according to the type.
+        if (folderUrlsQuery.value(IS_FOLDER).toBool())  // This is a folder.
+        {
+            // Get the subfolder URLs to the list.
+            folderUrlsListPointer->append(*getAllFolderUrls(folderUrlsQuery.value(FOLDER_ID).toDouble()));
+        }
+        else  // This is a bookmark.
+        {
+            // Add the URL to the list.
+            folderUrlsListPointer->append(folderUrlsQuery.value(BOOKMARK_URL).toString());
+        }
+    }
+
+    // Return the folder URLs list.
+    return folderUrlsListPointer;
+}
+
+BookmarkStruct* BookmarksDatabase::getBookmark(const int databaseId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a bookmark query.
+    QSqlQuery bookmarkQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    bookmarkQuery.setForwardOnly(true);
+
+    // Prepare the bookmark query.
+    bookmarkQuery.prepare("SELECT * FROM " + BOOKMARKS_TABLE + " WHERE " + ID + " = :id");
+
+    // Bind the query values.
+    bookmarkQuery.bindValue(":id", databaseId);
+
+    // Execute the query.
+    bookmarkQuery.exec();
+
+    // Move to the first entry.
+    bookmarkQuery.first();
+
+    // Create a bookmark struct.
+    struct BookmarkStruct *bookmarkStructPointer = new BookmarkStruct();
+
+    // Get the favorite icon base 64 byte array.
+    QByteArray favoriteIconByteArray = QByteArray::fromBase64(bookmarkQuery.value(FAVORITE_ICON).toByteArray());
+
+    // Create a favorite icon pixmap.
+    QPixmap favoriteIconPixmap;
+
+    // Load the pixmap from byte array.
+    favoriteIconPixmap.loadFromData(favoriteIconByteArray);
+
+    // Populate the bookmark struct.
+    bookmarkStructPointer->databaseId = bookmarkQuery.value(ID).toInt();
+    bookmarkStructPointer->name = bookmarkQuery.value(BOOKMARK_NAME).toString();
+    bookmarkStructPointer->url = bookmarkQuery.value(BOOKMARK_URL).toString();
+    bookmarkStructPointer->parentFolderId = bookmarkQuery.value(PARENT_FOLDER_ID).toDouble();
+    bookmarkStructPointer->displayOrder = bookmarkQuery.value(DISPLAY_ORDER).toInt();
+    bookmarkStructPointer->isFolder = bookmarkQuery.value(IS_FOLDER).toBool();
+    bookmarkStructPointer->folderId = bookmarkQuery.value(FOLDER_ID).toDouble();
+    bookmarkStructPointer->favoriteIcon = QIcon(favoriteIconPixmap);
+
+    // Return the bookmark struct pointer.
+    return bookmarkStructPointer;
+}
+
+std::list<BookmarkStruct>* BookmarksDatabase::getBookmarks()
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a bookmarks query.
+    QSqlQuery bookmarksQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    bookmarksQuery.setForwardOnly(true);
+
+    // Prepare the bookmarks query.
+    bookmarksQuery.prepare("SELECT * FROM " + BOOKMARKS_TABLE + " ORDER BY " + DISPLAY_ORDER + " ASC");
+
+    // Execute the query.
+    bookmarksQuery.exec();
+
+    // Create a bookmark list.
+    std::list<BookmarkStruct> *bookmarkListPointer = new std::list<BookmarkStruct>;
+
+    // Populate the bookmark list.
+    while (bookmarksQuery.next())
+    {
+        // Create a bookmark struct.
+        struct BookmarkStruct bookmarkStruct;
+
+        // Get the favorite icon base 64 byte array.
+        QByteArray favoriteIconByteArray = QByteArray::fromBase64(bookmarksQuery.value(FAVORITE_ICON).toByteArray());
+
+        // Create a favorite icon pixmap.
+        QPixmap favoriteIconPixmap;
+
+        // Load the pixmap from byte array.
+        favoriteIconPixmap.loadFromData(favoriteIconByteArray);
+
+        // Populate the bookmark struct.
+        bookmarkStruct.databaseId = bookmarksQuery.value(ID).toInt();
+        bookmarkStruct.name = bookmarksQuery.value(BOOKMARK_NAME).toString();
+        bookmarkStruct.url = bookmarksQuery.value(BOOKMARK_URL).toString();
+        bookmarkStruct.parentFolderId = bookmarksQuery.value(PARENT_FOLDER_ID).toDouble();
+        bookmarkStruct.displayOrder = bookmarksQuery.value(DISPLAY_ORDER).toInt();
+        bookmarkStruct.isFolder = bookmarksQuery.value(IS_FOLDER).toBool();
+        bookmarkStruct.folderId = bookmarksQuery.value(FOLDER_ID).toDouble();
+        bookmarkStruct.favoriteIcon = QIcon(favoriteIconPixmap);
+
+        // Add the bookmark to the list.
+        bookmarkListPointer->push_back(bookmarkStruct);
+    }
+
+    // Return the bookmark list.
+    return bookmarkListPointer;
+}
+
+QList<BookmarkStruct>* BookmarksDatabase::getBookmarksInFolderExcept(const double folderId, QList<int> *exceptDatabaseIdsListPointer)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a bookmarks query.
+    QSqlQuery bookmarksQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    bookmarksQuery.setForwardOnly(true);
+
+    // Create an IDs not to get string.
+    QString idsNotToGetString;
+
+    for (const int databaseId : *exceptDatabaseIdsListPointer)
+    {
+        // Check to see if there the string already has at least one number.
+        if (!idsNotToGetString.isEmpty())
+        {
+            // This is not the first number, so add a `,`.
+            idsNotToGetString.append(QLatin1Char(','));
+        }
+
+        // Append the database ID.
+        idsNotToGetString.append(QString::number(databaseId));
+    }
+
+    // Prepare the bookmarks query.
+    bookmarksQuery.prepare("SELECT * FROM " + BOOKMARKS_TABLE + " WHERE " + PARENT_FOLDER_ID + " = :parent_folder_id AND " + ID + " NOT IN (" + idsNotToGetString + ") ORDER BY " + DISPLAY_ORDER + " ASC");
+
+    // Bind the query values.
+    bookmarksQuery.bindValue(":parent_folder_id", folderId);
+
+    // Execute the query.
+    bookmarksQuery.exec();
+
+    // Create a bookmark list.
+    QList<BookmarkStruct> *bookmarkListPointer = new QList<BookmarkStruct>;
+
+    // Populate the bookmark list.
+    while (bookmarksQuery.next())
+    {
+        // Create a bookmark struct.
+        struct BookmarkStruct bookmarkStruct;
+
+        // Get the favorite icon base 64 byte array.
+        QByteArray favoriteIconByteArray = QByteArray::fromBase64(bookmarksQuery.value(FAVORITE_ICON).toByteArray());
+
+        // Create a favorite icon pixmap.
+        QPixmap favoriteIconPixmap;
+
+        // Load the pixmap from byte array.
+        favoriteIconPixmap.loadFromData(favoriteIconByteArray);
+
+        // Populate the bookmark struct.
+        bookmarkStruct.databaseId = bookmarksQuery.value(ID).toInt();
+        bookmarkStruct.name = bookmarksQuery.value(BOOKMARK_NAME).toString();
+        bookmarkStruct.url = bookmarksQuery.value(BOOKMARK_URL).toString();
+        bookmarkStruct.parentFolderId = bookmarksQuery.value(PARENT_FOLDER_ID).toDouble();
+        bookmarkStruct.displayOrder = bookmarksQuery.value(DISPLAY_ORDER).toInt();
+        bookmarkStruct.isFolder = bookmarksQuery.value(IS_FOLDER).toBool();
+        bookmarkStruct.folderId = bookmarksQuery.value(FOLDER_ID).toDouble();
+        bookmarkStruct.favoriteIcon = QIcon(favoriteIconPixmap);
+
+        // Add the bookmark to the list.
+        bookmarkListPointer->push_back(bookmarkStruct);
+    }
+
+    // Return the bookmark list.
+    return bookmarkListPointer;
+}
+
+QString BookmarksDatabase::getFavoriteIconBase64String(const QIcon &favoriteIcon)
+{
+    // Get a favorite icon pixmap.
+    QPixmap favoriteIconPixmap = favoriteIcon.pixmap(32, 32);
+
+    // Create a favorite icon byte array.
+    QByteArray favoriteIconByteArray;
+
+    // Create a favorite icon buffer.
+    QBuffer favoriteIconBuffer(&favoriteIconByteArray);
+
+    // Open the buffer.
+    favoriteIconBuffer.open(QIODevice::WriteOnly);
+
+    // Convert the favorite icon pixmap into a byte array in PNG format.
+    favoriteIconPixmap.save(&favoriteIconBuffer, "PNG");
+
+    // Close the buffer.
+    favoriteIconBuffer.close();
+
+    // Convert the favorite icon byte array to a base 64 string.
+    QString favoriteIconBase64String = favoriteIconByteArray.toBase64();
+
+    // Return the favorite icon base 64 string.
+    return favoriteIconBase64String;
+}
+
+QList<BookmarkStruct>* BookmarksDatabase::getFolderContents(const double folderId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a folder contents query.
+    QSqlQuery folderContentsQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    folderContentsQuery.setForwardOnly(true);
+
+    // Prepare the folder contents query.
+    folderContentsQuery.prepare("SELECT * FROM " + BOOKMARKS_TABLE + " WHERE " + PARENT_FOLDER_ID + " = :parent_folder_id ORDER BY " + DISPLAY_ORDER + " ASC");
+
+    // Bind the query values.
+    folderContentsQuery.bindValue(":parent_folder_id", folderId);
+
+    // Execute the query.
+    folderContentsQuery.exec();
+
+    // Create a folder contents list.
+    QList<BookmarkStruct> *folderContentsListPointer = new QList<BookmarkStruct>;
+
+    // Populate the folder contents list.
+    while (folderContentsQuery.next())
+    {
+        // Create a bookmark struct.
+        struct BookmarkStruct bookmarkStruct;
+
+        // Get the favorite icon base 64 byte array.
+        QByteArray favoriteIconByteArray = QByteArray::fromBase64(folderContentsQuery.value(FAVORITE_ICON).toByteArray());
+
+        // Create a favorite icon pixmap.
+        QPixmap favoriteIconPixmap;
+
+        // Load the pixmap from byte array.
+        favoriteIconPixmap.loadFromData(favoriteIconByteArray);
+
+        // Populate the bookmark struct.
+        bookmarkStruct.databaseId = folderContentsQuery.value(ID).toInt();
+        bookmarkStruct.name = folderContentsQuery.value(BOOKMARK_NAME).toString();
+        bookmarkStruct.url = folderContentsQuery.value(BOOKMARK_URL).toString();
+        bookmarkStruct.parentFolderId = folderContentsQuery.value(PARENT_FOLDER_ID).toDouble();
+        bookmarkStruct.displayOrder = folderContentsQuery.value(DISPLAY_ORDER).toInt();
+        bookmarkStruct.isFolder = folderContentsQuery.value(IS_FOLDER).toBool();
+        bookmarkStruct.folderId = folderContentsQuery.value(FOLDER_ID).toDouble();
+        bookmarkStruct.favoriteIcon = QIcon(favoriteIconPixmap);
+
+        // Add the item to the list.
+        folderContentsListPointer->append(bookmarkStruct);
+    }
+
+    // Return the folder contents list.
+    return folderContentsListPointer;
+}
+
+QList<int>* BookmarksDatabase::getFolderContentsDatabaseIds(const double folderId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a folder contents query.
+    QSqlQuery folderContentsQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    folderContentsQuery.setForwardOnly(true);
+
+    // Prepare the folder contents query.
+    folderContentsQuery.prepare("SELECT " + ID + " FROM " + BOOKMARKS_TABLE + " WHERE " + PARENT_FOLDER_ID + " = :parent_folder_id");
+
+    // Bind the query values.
+    folderContentsQuery.bindValue(":parent_folder_id", folderId);
+
+    // Execute the query.
+    folderContentsQuery.exec();
+
+    // Create a folder contents database ID list.
+    QList<int> *folderContentsDatabaseIdsListPointer = new QList<int>;
+
+    // Populate the folder contents list.
+    while (folderContentsQuery.next())
+    {
+        // Add the database ID to the list.
+        folderContentsDatabaseIdsListPointer->append(folderContentsQuery.value(ID).toInt());
+    }
+
+    // Return the folder contents database ID list.
+    return folderContentsDatabaseIdsListPointer;
+}
+
+QList<int> *BookmarksDatabase::getFolderContentsDatabaseIdsRecursively(const double folderId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a folder contents query.
+    QSqlQuery folderContentsQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    folderContentsQuery.setForwardOnly(true);
+
+    // Prepare the folder contents query.
+    folderContentsQuery.prepare("SELECT " + ID + ", " + IS_FOLDER + ", " + FOLDER_ID + " FROM " + BOOKMARKS_TABLE + " WHERE " + PARENT_FOLDER_ID + " = :parent_folder_id");
+
+    // Bind the query values.
+    folderContentsQuery.bindValue(":parent_folder_id", folderId);
+
+    // Execute the query.
+    folderContentsQuery.exec();
+
+    // Create a folder contents database ID list.
+    QList<int> *folderContentsDatabaseIdsListPointer = new QList<int>;
+
+    // Populate the folder contents list.
+    while (folderContentsQuery.next())
+    {
+        // Add the database ID to the list.
+        folderContentsDatabaseIdsListPointer->append(folderContentsQuery.value(ID).toInt());
+
+        // Recursively get the contents if this is a subfolder.
+        if (folderContentsQuery.value(IS_FOLDER).toBool())
+            folderContentsDatabaseIdsListPointer->append(*getFolderContentsDatabaseIdsRecursively(folderContentsQuery.value(FOLDER_ID).toDouble()));
+    }
+
+    // Return the folder contents database ID list.
+    return folderContentsDatabaseIdsListPointer;
+}
+
+int BookmarksDatabase::getFolderDatabaseId(const double folderId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a folder database ID query.
+    QSqlQuery folderDatabaseIdQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    folderDatabaseIdQuery.setForwardOnly(true);
+
+    // Prepare the folder database ID query.
+    folderDatabaseIdQuery.prepare("SELECT " + ID + " FROM " + BOOKMARKS_TABLE + " WHERE " + FOLDER_ID + " = :folder_id");
+
+    // Bind the query values.
+    folderDatabaseIdQuery.bindValue(":folder_id", folderId);
+
+    // Execute the query.
+    folderDatabaseIdQuery.exec();
+
+    // Move to the first entry.
+    folderDatabaseIdQuery.first();
+
+    // Return the folder database ID.
+    return folderDatabaseIdQuery.value(ID).toInt();
+}
+
+double BookmarksDatabase::getFolderId(const int databaseId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a folder ID query.
+    QSqlQuery folderIdQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    folderIdQuery.setForwardOnly(true);
+
+    // Prepare the folder ID query.
+    folderIdQuery.prepare("SELECT " + FOLDER_ID + " FROM " + BOOKMARKS_TABLE + " WHERE " + ID + " = :database_id");
+
+    // Bind the query values.
+    folderIdQuery.bindValue(":database_id", databaseId);
+
+    // Execute the query.
+    folderIdQuery.exec();
+
+    // Move to the first entry.
+    folderIdQuery.first();
+
+    // Return the folder ID.
+    return folderIdQuery.value(FOLDER_ID).toDouble();
+}
+
+int BookmarksDatabase::getFolderItemCount(const double folderId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a folder contents query.
+    QSqlQuery folderContentsQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    folderContentsQuery.setForwardOnly(true);
+
+    // Prepare the folder contents query.
+    folderContentsQuery.prepare("SELECT " + ID + " FROM " + BOOKMARKS_TABLE + " WHERE " + PARENT_FOLDER_ID + " = :parent_folder_id");
+
+    // Bind the query values.
+    folderContentsQuery.bindValue(":parent_folder_id", folderId);
+
+    // Execute the query.
+    folderContentsQuery.exec();
+
+    // Move to the last row.
+    folderContentsQuery.last();
+
+    // Initialize an item count variable.
+    int itemCount = 0;
+
+    // Check to see if the query is valid (there is at least one item).
+    if (folderContentsQuery.isValid())
+    {
+        // Get the number of rows (which is zero based) and add one to calculate the number of bookmarks.
+        itemCount = folderContentsQuery.at() + 1;
+    }
+
+    // Return the item count.
+    return itemCount;
+}
+
+double BookmarksDatabase::getParentFolderId(const int databaseId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a parent folder ID query.
+    QSqlQuery parentFolderIdQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    parentFolderIdQuery.setForwardOnly(true);
+
+    // Prepare the parent folder ID query.
+    parentFolderIdQuery.prepare("SELECT " + PARENT_FOLDER_ID + " FROM " + BOOKMARKS_TABLE + " WHERE " + ID + " = :database_id");
+
+    // Bind the query values.
+    parentFolderIdQuery.bindValue(":database_id", databaseId);
+
+    // Execute the query.
+    parentFolderIdQuery.exec();
+
+    // Move to the first entry.
+    parentFolderIdQuery.first();
+
+    // Return the parent folder ID.
+    return parentFolderIdQuery.value(PARENT_FOLDER_ID).toDouble();
+}
+
+QList<BookmarkStruct>* BookmarksDatabase::getSubfolders(const double folderId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a subfolders query.
+    QSqlQuery subfoldersQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    subfoldersQuery.setForwardOnly(true);
+
+    // Prepare the subfolders query.
+    subfoldersQuery.prepare("SELECT * FROM " + BOOKMARKS_TABLE + " WHERE " + IS_FOLDER + " = 1 AND " + PARENT_FOLDER_ID + " = :parent_folder_id ORDER BY " + DISPLAY_ORDER + " ASC");
+
+    // Bind the query values.
+    subfoldersQuery.bindValue(":parent_folder_id", folderId);
+
+    // Execute the query.
+    subfoldersQuery.exec();
+
+    // Create a subfolder list.
+    QList<BookmarkStruct> *subfoldersListPointer = new QList<BookmarkStruct>;
+
+    // Populate the subfolder list.
+    while (subfoldersQuery.next())
+    {
+        // Create a bookmark struct.
+        struct BookmarkStruct bookmarkStruct;
+
+        // Get the favorite icon base 64 byte array.
+        QByteArray favoriteIconByteArray = QByteArray::fromBase64(subfoldersQuery.value(FAVORITE_ICON).toByteArray());
+
+        // Create a favorite icon pixmap.
+        QPixmap favoriteIconPixmap;
+
+        // Load the pixmap from byte array.
+        favoriteIconPixmap.loadFromData(favoriteIconByteArray);
+
+        // Populate the bookmark struct.
+        bookmarkStruct.databaseId = subfoldersQuery.value(ID).toInt();
+        bookmarkStruct.name = subfoldersQuery.value(BOOKMARK_NAME).toString();
+        bookmarkStruct.parentFolderId = subfoldersQuery.value(PARENT_FOLDER_ID).toDouble();
+        bookmarkStruct.displayOrder = subfoldersQuery.value(DISPLAY_ORDER).toInt();
+        bookmarkStruct.isFolder = subfoldersQuery.value(IS_FOLDER).toBool();
+        bookmarkStruct.folderId = subfoldersQuery.value(FOLDER_ID).toDouble();
+        bookmarkStruct.favoriteIcon = QIcon(favoriteIconPixmap);
+
+        // Add the subfolder to the list.
+        subfoldersListPointer->append(bookmarkStruct);
+    }
+
+    // Return the subfolders list.
+    return subfoldersListPointer;
+}
+
+bool BookmarksDatabase::isBookmarked(const QString url)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate an is bookmarked query.
+    QSqlQuery isBookmarkedQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    isBookmarkedQuery.setForwardOnly(true);
+
+    // Prepare the is bookmarked query.
+    isBookmarkedQuery.prepare("SELECT " + ID + " FROM " + BOOKMARKS_TABLE + " WHERE " + IS_FOLDER + " = 0 AND " + BOOKMARK_URL + " = :url");
+
+    // Bind the query values.
+    isBookmarkedQuery.bindValue(":url", url);
+
+    // Execute the query.
+    isBookmarkedQuery.exec();
+
+    // Move to the first entry.
+    isBookmarkedQuery.first();
+
+    // Return true if the query is valid (there is at least one item).
+    return isBookmarkedQuery.isValid();
+}
+
+bool BookmarksDatabase::isFolder(const int databaseId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate an is folder query.
+    QSqlQuery isFolderQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    isFolderQuery.setForwardOnly(true);
+
+    // Prepare the is folder query.
+    isFolderQuery.prepare("SELECT " + IS_FOLDER + " FROM " + BOOKMARKS_TABLE + " WHERE " + ID + " = :id");
+
+    // Bind the query values.
+    isFolderQuery.bindValue(":id", databaseId);
+
+    // Execute the query.
+    isFolderQuery.exec();
+
+    // Move to the first entry.
+    isFolderQuery.first();
+
+    // Return the folder status.
+    return isFolderQuery.value(IS_FOLDER).toBool();
+}
+
+void BookmarksDatabase::updateBookmark(const BookmarkStruct *bookmarkStructPointer)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate an update bookmark query.
+    QSqlQuery updateBookmarkQuery(bookmarksDatabase);
+
+    // Prepare the update bookmark query.
+    updateBookmarkQuery.prepare("UPDATE " + BOOKMARKS_TABLE + " SET " +
+                                BOOKMARK_NAME + " = :bookmark_name, " +
+                                BOOKMARK_URL + " = :bookmark_url, " +
+                                PARENT_FOLDER_ID + " = :parent_folder_id, " +
+                                DISPLAY_ORDER + " = :display_order, " +
+                                FAVORITE_ICON + "= :favorite_icon " +
+                                "WHERE " + ID + " = :id");
+
+    // Bind the query values.
+    updateBookmarkQuery.bindValue(":bookmark_name", bookmarkStructPointer->name);
+    updateBookmarkQuery.bindValue(":bookmark_url", bookmarkStructPointer->url);
+    updateBookmarkQuery.bindValue(":parent_folder_id", bookmarkStructPointer->parentFolderId);
+    updateBookmarkQuery.bindValue(":display_order", bookmarkStructPointer->displayOrder);
+    updateBookmarkQuery.bindValue(":favorite_icon", getFavoriteIconBase64String(bookmarkStructPointer->favoriteIcon));
+    updateBookmarkQuery.bindValue(":id", bookmarkStructPointer->databaseId);
+
+    // Execute the query.
+    updateBookmarkQuery.exec();
+}
+
+void BookmarksDatabase::updateBookmarkName(const int databaseId, const QString &bookmarkName)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate an update bookmark name query.
+    QSqlQuery updateBookmarkNameQuery(bookmarksDatabase);
+
+    // Prepare the update bookmark name query.
+    updateBookmarkNameQuery.prepare("UPDATE " + BOOKMARKS_TABLE +
+                                    " SET " + BOOKMARK_NAME + " = :bookmark_name " +
+                                    "WHERE " + ID + " = :id");
+
+    // Bind the query values.
+    updateBookmarkNameQuery.bindValue(":bookmark_name", bookmarkName);
+    updateBookmarkNameQuery.bindValue(":id", databaseId);
+
+    // Execute the query.
+    updateBookmarkNameQuery.exec();
+}
+
+void BookmarksDatabase::updateBookmarkUrl(const int databaseId, const QString &bookmarkUrl)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate an update bookmark URL query.
+    QSqlQuery updateBookmarkUrlQuery(bookmarksDatabase);
+
+    // Prepare the update bookmark URL query.
+    updateBookmarkUrlQuery.prepare("UPDATE " + BOOKMARKS_TABLE +
+                                   " SET " + BOOKMARK_URL + " = :bookmark_url " +
+                                   "WHERE " + ID + " = :id");
+
+    // Bind the query values.
+    updateBookmarkUrlQuery.bindValue(":bookmark_url", bookmarkUrl);
+    updateBookmarkUrlQuery.bindValue(":id", databaseId);
+
+    // Execute the query.
+    updateBookmarkUrlQuery.exec();
+}
+
+void BookmarksDatabase::updateDisplayOrder(const int databaseId, const int displayOrder)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate an update bookmark display order query.
+    QSqlQuery updateBookmarkDisplayOrderQuery(bookmarksDatabase);
+
+    // Prepare the update bookmark display order query.
+    updateBookmarkDisplayOrderQuery.prepare("UPDATE " + BOOKMARKS_TABLE +
+                                            " SET " + DISPLAY_ORDER + " = :display_order " +
+                                            "WHERE " + ID + " = :id");
+
+    // Bind the query values.
+    updateBookmarkDisplayOrderQuery.bindValue(":display_order", displayOrder);
+    updateBookmarkDisplayOrderQuery.bindValue(":id", databaseId);
+
+    // Execute the query.
+    updateBookmarkDisplayOrderQuery.exec();
+}
+
+void BookmarksDatabase::updateFolderContentsDisplayOrder(const double folderId)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a folder contents query.
+    QSqlQuery folderContentsQuery(bookmarksDatabase);
+
+    // Set the query to be forward only, which is more performant.
+    folderContentsQuery.setForwardOnly(true);
+
+    // Prepare the folder contents query.
+    folderContentsQuery.prepare("SELECT " + ID + ", " + DISPLAY_ORDER + " FROM " + BOOKMARKS_TABLE + " WHERE " + PARENT_FOLDER_ID + " = :parent_folder_id ORDER BY " + DISPLAY_ORDER + " ASC");
+
+    // Bind the query values.
+    folderContentsQuery.bindValue(":parent_folder_id", folderId);
+
+    // Execute the query.
+    folderContentsQuery.exec();
+
+    // Define a new display order int.
+    int newDisplayOrder = 0;
+
+    // Populate the subfolder list.
+    while (folderContentsQuery.next())
+    {
+        // Update the display order if it has changed.
+        if (folderContentsQuery.value(DISPLAY_ORDER).toInt() != newDisplayOrder)
+            updateDisplayOrder(folderContentsQuery.value(ID).toInt(), newDisplayOrder);
+
+        // Increment the new display order.
+        ++newDisplayOrder;
+    }
+}
+
+void BookmarksDatabase::updateParentFolderAndDisplayOrder(const int databaseId, const double parentFolderId, const int displayOrder)
+{
+    // Get a handle for the bookmarks database.
+    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate an update bookmark display order query.
+    QSqlQuery updateBookmarkDisplayOrderQuery(bookmarksDatabase);
+
+    // Prepare the update bookmark display order query.
+    updateBookmarkDisplayOrderQuery.prepare("UPDATE " + BOOKMARKS_TABLE +
+                                            " SET " + PARENT_FOLDER_ID + " = :parent_folder_id " +
+                                            ", " + DISPLAY_ORDER + " = :display_order " +
+                                            "WHERE " + ID + " = :id");
+
+    // Bind the query values.
+    updateBookmarkDisplayOrderQuery.bindValue(":parent_folder_id", parentFolderId);
+    updateBookmarkDisplayOrderQuery.bindValue(":display_order", displayOrder);
+    updateBookmarkDisplayOrderQuery.bindValue(":id", databaseId);
+
+    // Execute the query.
+    updateBookmarkDisplayOrderQuery.exec();
+}
diff --git a/src/databases/BookmarksDatabase.h b/src/databases/BookmarksDatabase.h
new file mode 100644 (file)
index 0000000..932a1e4
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BOOKMARKSDATABASE_H
+#define BOOKMARKSDATABASE_H
+
+// Application headers.
+#include "structs/BookmarkStruct.h"
+
+// Qt framework headers.
+#include <QtSql>
+
+class BookmarksDatabase
+{
+public:
+    // The default constructor.
+    BookmarksDatabase();
+
+    // The public functions.
+    static void addBookmark(const BookmarkStruct *bookmarkStructPointer);
+    static void addDatabase();
+    static void addFolder(const BookmarkStruct *bookmarkStructPointer);
+    static void deleteBookmark(const int databaseId);
+    static void deleteBookmarks(const QString url);
+    static QList<QString>* getAllFolderUrls(const double folderId);
+    static BookmarkStruct* getBookmark(const int databaseId);
+    static std::list<BookmarkStruct>* getBookmarks();
+    static QList<BookmarkStruct>* getBookmarksInFolderExcept(const double folderId, QList<int> *exceptDatabaseIdsListPointer);
+    static QList<int>* getFolderContentsDatabaseIds(const double folderId);
+    static QList<int>* getFolderContentsDatabaseIdsRecursively(const double folderId);
+    static QList<BookmarkStruct>* getFolderContents(const double folderId);
+    static int getFolderDatabaseId(const double folderId);
+    static double getFolderId(const int databaseId);
+    static int getFolderItemCount(const double folderId);
+    static double getParentFolderId(const int databaseId);
+    static QList<BookmarkStruct>* getSubfolders(const double folderId);
+    static bool isBookmarked(const QString url);
+    static bool isFolder(const int databaseId);
+    static void updateBookmark(const BookmarkStruct *bookmarkStructPointer);
+    static void updateBookmarkName(const int databaseId, const QString &bookmarkName);
+    static void updateBookmarkUrl(const int databaseId, const QString &bookmarkUrl);
+    static void updateDisplayOrder(const int databaseId, const int displayOrder);
+    static void updateFolderContentsDisplayOrder(const double folderId);
+    static void updateParentFolderAndDisplayOrder(const int databaseId, const double parentFolderId, const int displayOrder);
+
+    // The public constants.
+    static const QString CONNECTION_NAME;
+    static const QString BOOKMARK_NAME;
+    static const QString BOOKMARKS_TABLE;
+    static const QString BOOKMARK_URL;
+    static const QString DISPLAY_ORDER;
+    static const QString FAVORITE_ICON;
+    static const QString FOLDER_ID;
+    static const QString ID;
+    static const QString IS_FOLDER;
+    static const QString PARENT_FOLDER_ID;
+
+private:
+    // The private static constants.
+    static const int SCHEMA_VERSION;
+
+    // The private functions.
+    static double generateFolderId();
+    static QString getFavoriteIconBase64String(const QIcon &favoriteIcon);
+};
+#endif
index 0abb93656f294ecac58c0e1fec06919f0a48b3fb..5f40f9938ce3aed79f8b3ac2ad4665f8e93db48c 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
@@ -17,7 +17,8 @@
 
 
 # List the sources to include in the executable.
-target_sources(privacy-browser PRIVATE
+target_sources(privacybrowser PRIVATE
+    BookmarksDatabase.cpp
     CookiesDatabase.cpp
     DomainsDatabase.cpp
 )
index 207f45b89795c3ead31d00313cb78654890056c7..e8a870b82d15b00a034e05ccf1e233efb3cc9097 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
 // Define the private static schema constants.
 const int CookiesDatabase::SCHEMA_VERSION = 0;
 
-// Define the public static database constants.
+// Define the public static constants.
 const QString CookiesDatabase::CONNECTION_NAME = "cookies_database";
 const QString CookiesDatabase::COOKIES_TABLE = "cookies";
-
-// Define the public static database field names.
-const QString CookiesDatabase::_ID = "_id";
 const QString CookiesDatabase::DOMAIN = "domain";
-const QString CookiesDatabase::NAME = "name";
-const QString CookiesDatabase::PATH = "path";
 const QString CookiesDatabase::EXPIRATION_DATE = "expiration_date";
 const QString CookiesDatabase::HTTP_ONLY = "http_only";
+const QString CookiesDatabase::ID = "_id";
+const QString CookiesDatabase::NAME = "name";
+const QString CookiesDatabase::PATH = "path";
 const QString CookiesDatabase::SECURE = "secure";
 const QString CookiesDatabase::VALUE = "value";
 
@@ -83,15 +81,14 @@ void CookiesDatabase::addDatabase()
 
             // Prepare the create table query.
             createTableQuery.prepare("CREATE TABLE " + COOKIES_TABLE + "(" +
-                _ID + " INTEGER PRIMARY KEY, " +
-                DOMAIN + " TEXT NOT NULL, " +
-                NAME + " TEXT NOT NULL, " +
-                PATH + " TEXT NOT NULL, " +
-                EXPIRATION_DATE + " TEXT, " +
-                HTTP_ONLY + " INTEGER NOT NULL DEFAULT 0, " +
-                SECURE + " INTEGER NOT NULL DEFAULT 0, " +
-                VALUE + " TEXT NOT NULL)"
-            );
+                                     ID + " INTEGER PRIMARY KEY, " +
+                                     DOMAIN + " TEXT NOT NULL, " +
+                                     NAME + " TEXT NOT NULL, " +
+                                     PATH + " TEXT NOT NULL, " +
+                                     EXPIRATION_DATE + " TEXT, " +
+                                     HTTP_ONLY + " INTEGER NOT NULL DEFAULT 0, " +
+                                     SECURE + " INTEGER NOT NULL DEFAULT 0, " +
+                                     VALUE + " TEXT NOT NULL)");
 
             // Execute the query.
             if (!createTableQuery.exec())
@@ -128,8 +125,15 @@ void CookiesDatabase::addCookie(const QNetworkCookie &cookie)
         QSqlQuery addCookieQuery(cookiesDatabase);
 
         // Prepare the add cookie query.
-        addCookieQuery.prepare("INSERT INTO " + COOKIES_TABLE + " (" + DOMAIN + ", " + NAME + ", " + PATH + ", " + EXPIRATION_DATE + ", " + HTTP_ONLY + ", " + SECURE + ", " + VALUE + ") "
-                            "VALUES (:domain, :name, :path, :expiration_date, :http_only, :secure, :value)"
+        addCookieQuery.prepare("INSERT INTO " + COOKIES_TABLE + " (" +
+                                DOMAIN + ", " +
+                                NAME + ", " +
+                                PATH + ", " +
+                                EXPIRATION_DATE + ", " +
+                                HTTP_ONLY + ", " +
+                                SECURE + ", " +
+                                VALUE + ") "
+                                "VALUES (:domain, :name, :path, :expiration_date, :http_only, :secure, :value)"
         );
 
         // Bind the values.
@@ -158,7 +162,7 @@ int CookiesDatabase::cookieCount()
     countCookiesQuery.setForwardOnly(true);
 
     // Prepare the query.
-    countCookiesQuery.prepare("SELECT " + _ID + " FROM " + COOKIES_TABLE);
+    countCookiesQuery.prepare("SELECT " + ID + " FROM " + COOKIES_TABLE);
 
     // Execute the query.
     countCookiesQuery.exec();
@@ -204,7 +208,10 @@ void CookiesDatabase::deleteCookie(const QNetworkCookie &cookie)
     QSqlQuery deleteCookieQuery(cookiesDatabase);
 
     // Prepare the delete cookie query.
-    deleteCookieQuery.prepare("DELETE FROM " + COOKIES_TABLE + " WHERE " + DOMAIN + " = :domain AND " + NAME + " = :name AND " + PATH + " = :path");
+    deleteCookieQuery.prepare("DELETE FROM " + COOKIES_TABLE + " WHERE " +
+                              DOMAIN + " = :domain AND " +
+                              NAME + " = :name AND " +
+                              PATH + " = :path");
 
     // Bind the values.
     deleteCookieQuery.bindValue(":domain", cookie.domain());
@@ -223,7 +230,7 @@ QList<QNetworkCookie*>* CookiesDatabase::getCookies()
     // Instantiate a cookies query.
     QSqlQuery cookiesQuery(cookiesDatabase);
 
-    // Set the query to be forward only.
+    // Set the query to be forward only, which is more performant.
     cookiesQuery.setForwardOnly(true);
 
     // Prepare the cookies query.
@@ -270,7 +277,7 @@ QNetworkCookie* CookiesDatabase::getCookieById(const int &id)
     cookieQuery.setForwardOnly(true);
 
     // Prepare the cookies query.
-    cookieQuery.prepare("SELECT * FROM " + COOKIES_TABLE + " WHERE " + _ID + " = :id");
+    cookieQuery.prepare("SELECT * FROM " + COOKIES_TABLE + " WHERE " + ID + " = :id");
 
     // Bind the values.
     cookieQuery.bindValue(":id", id);
@@ -309,7 +316,10 @@ bool CookiesDatabase::isDurable(const QNetworkCookie &cookie)
     isDurableQuery.setForwardOnly(true);
 
     // Prepare the is durable query.
-    isDurableQuery.prepare("SELECT " + _ID + " FROM " + COOKIES_TABLE + " WHERE " + DOMAIN + " = :domain AND " + NAME + " = :name AND " + PATH + " = :path");
+    isDurableQuery.prepare("SELECT " + ID + " FROM " + COOKIES_TABLE + " WHERE " +
+                            DOMAIN + " = :domain AND " +
+                            NAME + " = :name AND " +
+                            PATH + " = :path");
 
     // Bind the values.
     isDurableQuery.bindValue(":domain", cookie.domain());
@@ -335,8 +345,14 @@ bool CookiesDatabase::isUpdate(const QNetworkCookie &cookie)
     QSqlQuery isUpdateQuery(cookiesDatabase);
 
     // Prepare the is update query.
-    isUpdateQuery.prepare("SELECT " + EXPIRATION_DATE + " , " + HTTP_ONLY + " , " + SECURE + " , " + VALUE + " FROM " + COOKIES_TABLE + " WHERE " + DOMAIN + " = :domain AND " +
-                          NAME + " = :name AND " + PATH + " = :path");
+    isUpdateQuery.prepare("SELECT " + EXPIRATION_DATE + " , " +
+                          HTTP_ONLY + " , " +
+                          SECURE + " , " +
+                          VALUE +
+                          " FROM " + COOKIES_TABLE +
+                          " WHERE " + DOMAIN + " = :domain AND " +
+                          NAME + " = :name AND " +
+                          PATH + " = :path");
 
     // Bind the values.
     isUpdateQuery.bindValue(":domain", cookie.domain());
@@ -353,10 +369,10 @@ bool CookiesDatabase::isUpdate(const QNetworkCookie &cookie)
     if (isUpdateQuery.isValid())  // The cookie exists in the database.
     {
         // Check to see if the cookie data has changed.
-        if ((QDateTime::fromString(isUpdateQuery.value(0).toString(), Qt::ISODate) != cookie.expirationDate()) ||
-            (isUpdateQuery.value(1).toBool() != cookie.isHttpOnly()) ||
-            (isUpdateQuery.value(2).toBool() != cookie.isSecure()) ||
-            (isUpdateQuery.value(3).toString().toUtf8() != cookie.value()))  // The cookies data has changed.
+        if ((QDateTime::fromString(isUpdateQuery.value(EXPIRATION_DATE).toString(), Qt::ISODate) != cookie.expirationDate()) ||
+            (isUpdateQuery.value(HTTP_ONLY).toBool() != cookie.isHttpOnly()) ||
+            (isUpdateQuery.value(SECURE).toBool() != cookie.isSecure()) ||
+            (isUpdateQuery.value(VALUE).toString().toUtf8() != cookie.value()))  // The cookies data has changed.
         {
             //qDebug() << "The durable cookie data has changed.";
 
@@ -386,9 +402,15 @@ void CookiesDatabase::updateCookie(const QNetworkCookie &cookie)
     // Create the update cookie query.
     QSqlQuery updateCookieQuery(cookiesDatabase);
 
-    // Prepare the edit cookie query.
-    updateCookieQuery.prepare("UPDATE " + COOKIES_TABLE + " SET " + EXPIRATION_DATE + " = :expiration_date , " + HTTP_ONLY + " = :http_only , " + SECURE + " = :secure , " +
-                              VALUE + " = :value WHERE " + DOMAIN + " = :domain AND " + NAME + " = :name AND " + PATH + " = :path");
+    // Prepare the update cookie query.
+    updateCookieQuery.prepare("UPDATE " + COOKIES_TABLE +
+                              " SET " + EXPIRATION_DATE + " = :expiration_date , " +
+                              HTTP_ONLY + " = :http_only , " +
+                              SECURE + " = :secure , " +
+                              VALUE + " = :value " +
+                              "WHERE " + DOMAIN + " = :domain AND " +
+                              NAME + " = :name AND " +
+                              PATH + " = :path");
 
     // Bind the values.
     updateCookieQuery.bindValue(":domain", cookie.domain());
@@ -415,7 +437,10 @@ void CookiesDatabase::updateCookie(const QNetworkCookie &oldCookie, const QNetwo
     oldCookieQuery.setForwardOnly(true);
 
     // Prepare the old cookie query.
-    oldCookieQuery.prepare("SELECT " + _ID + " FROM " + COOKIES_TABLE + " WHERE " + DOMAIN + " = :domain AND " + NAME + " = :name AND " + PATH + " = :path");
+    oldCookieQuery.prepare("SELECT " + ID + " FROM " + COOKIES_TABLE +
+                           " WHERE " + DOMAIN + " = :domain AND " +
+                           NAME + " = :name AND " +
+                           PATH + " = :path");
 
     // Bind the values.
     oldCookieQuery.bindValue(":domain", oldCookie.domain());
@@ -432,8 +457,15 @@ void CookiesDatabase::updateCookie(const QNetworkCookie &oldCookie, const QNetwo
     QSqlQuery updateCookieQuery(cookiesDatabase);
 
     // Prepare the update cookie query.
-    updateCookieQuery.prepare("UPDATE " + COOKIES_TABLE + " SET " + DOMAIN + " = :domain , " + NAME + " = :name , " + PATH + " = :path , " + EXPIRATION_DATE + " = :expiration_date , " +
-                              HTTP_ONLY + " = :http_only , " + SECURE + " = :secure , " + VALUE + " = :value WHERE " + _ID + " = :id");
+    updateCookieQuery.prepare("UPDATE " + COOKIES_TABLE +
+                              " SET " + DOMAIN + " = :domain , " +
+                              NAME + " = :name , " +
+                              PATH + " = :path , " +
+                              EXPIRATION_DATE + " = :expiration_date , " +
+                              HTTP_ONLY + " = :http_only , " +
+                              SECURE + " = :secure , " +
+                              VALUE + " = :value " +
+                              "WHERE " + ID + " = :id");
 
     // Bind the values.
     updateCookieQuery.bindValue(":id", oldCookieQuery.value(0).toLongLong());
index 76e788ca63572bc8dadb721ba12826666b46b944..b81557b7e46b540841bddf9be1132b6187e20f97 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -44,12 +44,12 @@ public:
     static void updateCookie(const QNetworkCookie &oldCookie, const QNetworkCookie &newCookie);
 
     // The public constants.
-    static const QString _ID;
     static const QString CONNECTION_NAME;
     static const QString COOKIES_TABLE;
     static const QString DOMAIN;
     static const QString EXPIRATION_DATE;
     static const QString HTTP_ONLY;
+    static const QString ID;
     static const QString NAME;
     static const QString PATH;
     static const QString SECURE;
index 6dfc1bd69e7daad13563454dfedca826f6285f65..d52647cca107ca5163f9493f173e164ae4060530 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
 
 // Application headers.
 #include "DomainsDatabase.h"
+#include "Settings.h"
 #include "helpers/UserAgentHelper.h"
 
 // Define the private static schema constants.
-const int DomainsDatabase::SCHEMA_VERSION = 5;
+const int DomainsDatabase::SCHEMA_VERSION = 6;
 
-// Define the public static database constants.
+// Define the public static constants.
 const QString DomainsDatabase::CONNECTION_NAME = "domains_database";
-const QString DomainsDatabase::DOMAINS_TABLE = "domains";
-
-// Define the public static database field names.
-const QString DomainsDatabase::_ID = "_id";
+const QString DomainsDatabase::CUSTOM_ZOOM_FACTOR = "custom_zoom_factor";
+const QString DomainsDatabase::DOM_STORAGE = "dom_storage";
 const QString DomainsDatabase::DOMAIN_NAME = "domain_name";
+const QString DomainsDatabase::DOMAINS_TABLE = "domains";
+const QString DomainsDatabase::ID = "_id";
 const QString DomainsDatabase::JAVASCRIPT = "javascript";
 const QString DomainsDatabase::LOCAL_STORAGE = "local_storage";
-const QString DomainsDatabase::DOM_STORAGE = "dom_storage";
 const QString DomainsDatabase::USER_AGENT = "user_agent";
 const QString DomainsDatabase::ZOOM_FACTOR = "zoom_factor";
-const QString DomainsDatabase::CUSTOM_ZOOM_FACTOR = "custom_zoom_factor";
 
 // Construct the class.
 DomainsDatabase::DomainsDatabase() {}
@@ -76,7 +75,7 @@ void DomainsDatabase::addDatabase()
                         // Add the JavaScript column.
                         domainsDatabase.exec("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + JAVASCRIPT + " INTEGER DEFAULT 0");
 
-                        // Fallthrough to the next case.
+                        // Fall through to the next case.
                         [[fallthrough]];
                     }
 
@@ -86,7 +85,7 @@ void DomainsDatabase::addDatabase()
                         // Add the User Agent column.
                         domainsDatabase.exec("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + USER_AGENT + " TEXT DEFAULT '" + UserAgentHelper::SYSTEM_DEFAULT_DATABASE + "'");
 
-                        // Fallthrough to the next case.
+                        // Fall through to the next case.
                         [[fallthrough]];
                     }
 
@@ -97,7 +96,7 @@ void DomainsDatabase::addDatabase()
                         domainsDatabase.exec("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + ZOOM_FACTOR + " INTEGER DEFAULT 0");
                         domainsDatabase.exec("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + CUSTOM_ZOOM_FACTOR + " REAL DEFAULT 1.0");
 
-                        // Fallthrough to the next case.
+                        // Fall through to the next case.
                         [[fallthrough]];
                     }
 
@@ -107,7 +106,7 @@ void DomainsDatabase::addDatabase()
                         // Add the DOM Storage column.
                         domainsDatabase.exec("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + DOM_STORAGE + " INTEGER DEFAULT 0");
 
-                        // Fallthrough to the next case.
+                        // Fall through to the next case.
                         [[fallthrough]];
                     }
 
@@ -117,7 +116,120 @@ void DomainsDatabase::addDatabase()
                         // Add the Local Storage column.
                         domainsDatabase.exec("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + LOCAL_STORAGE + " INTEGER DEFAULT 0");
 
-                        // Fallthrough to the next case.
+                        // Fall through to the next case.
+                        [[fallthrough]];
+                    }
+
+                    // Upgrade from schema version 5 to schema version 6.
+                    case 5:
+                    {
+                        // Instantiate a spinner query.
+                        QSqlQuery spinnerQuery(domainsDatabase);
+
+                        // Set the query to be forward only (increases performance while iterating over the query).
+                        spinnerQuery.setForwardOnly(true);
+
+                        // Prepare the query.
+                        spinnerQuery.prepare("SELECT " + ID + "," + JAVASCRIPT + "," + LOCAL_STORAGE + "," + DOM_STORAGE + " FROM " + DOMAINS_TABLE);
+
+                        // Execute the query.
+                        spinnerQuery.exec();
+
+                        // Update the spinner values so that enabled is 1 and disabled is 2.
+                        while (spinnerQuery.next())
+                        {
+                            // Initialize the new spinner values.
+                            int newJavaScriptValue = SYSTEM_DEFAULT;
+                            int newLocalStorageValue = SYSTEM_DEFAULT;
+                            int newDomStorageValue = SYSTEM_DEFAULT;
+
+                            // Update the new JavaScript value if needed.
+                            switch (spinnerQuery.value(JAVASCRIPT).toInt())
+                            {
+                                // Disabled used to be 1.
+                                case 1:
+                                {
+                                    // Update the value to be 2.
+                                    newJavaScriptValue = DISABLED;
+
+                                    break;
+                                }
+
+                                // Enabled used to be 2.
+                                case 2:
+                                {
+                                    // Update the new value to be 1.
+                                    newJavaScriptValue = ENABLED;
+
+                                    break;
+                                }
+                            }
+
+                            // Update the new local storage value if needed.
+                            switch (spinnerQuery.value(LOCAL_STORAGE).toInt())
+                            {
+                                // Disabled used to be 1.
+                                case 1:
+                                {
+                                    // Update the new value to be 2.
+                                    newLocalStorageValue = DISABLED;
+
+                                    break;
+                                }
+
+                                // Enabled used to be 2.
+                                case 2:
+                                {
+                                    // Update the new value to be 1.
+                                    newLocalStorageValue = ENABLED;
+
+                                    break;
+                                }
+                            }
+
+                            // Update the new DOM storage value if needed.
+                            switch (spinnerQuery.value(DOM_STORAGE).toInt())
+                            {
+                                // Disabled used to be 1.
+                                case 1:
+                                {
+                                    // Update the new value to be 2.
+                                    newDomStorageValue = DISABLED;
+
+                                    break;
+                                }
+
+                                // Enabled used to be 2.
+                                case 2:
+                                {
+                                    // Update the new value to be 1.
+                                    newDomStorageValue = ENABLED;
+
+                                    break;
+                                }
+                            }
+
+                            // Create an update spinner query.
+                            QSqlQuery updateSpinnerQuery(domainsDatabase);
+
+                            // Prepare the update spinner query.
+                            updateSpinnerQuery.prepare("UPDATE " + DOMAINS_TABLE + " SET " +
+                                                 JAVASCRIPT + " = :javascript , " +
+                                                 LOCAL_STORAGE + " = :local_storage , " +
+                                                 DOM_STORAGE + " = :dom_storage " +
+                                                 " WHERE " + ID + " = :id");
+
+                            // Bind the values.
+                            updateSpinnerQuery.bindValue(":javascript", newJavaScriptValue);
+                            updateSpinnerQuery.bindValue(":local_storage", newLocalStorageValue);
+                            updateSpinnerQuery.bindValue(":dom_storage", newDomStorageValue);
+                            updateSpinnerQuery.bindValue(":id", spinnerQuery.value(ID));
+
+                            // Execute the query.
+                            updateSpinnerQuery.exec();
+                        }
+
+                        // Fall through to the next case.
                         // [[fallthrough]];
                     }
                 }
@@ -133,15 +245,14 @@ void DomainsDatabase::addDatabase()
 
             // Prepare the create table query.
             createTableQuery.prepare("CREATE TABLE " + DOMAINS_TABLE + "(" +
-                _ID + " INTEGER PRIMARY KEY, " +
-                DOMAIN_NAME + " TEXT, " +
-                JAVASCRIPT + " INTEGER DEFAULT 0, " +
-                LOCAL_STORAGE + " INTEGER DEFAULT 0, " +
-                DOM_STORAGE + " INTEGER DEFAULT 0, " +
-                USER_AGENT + " TEXT DEFAULT '" + UserAgentHelper::SYSTEM_DEFAULT_DATABASE + "', " +
-                ZOOM_FACTOR + " INTEGER DEFAULT 0, " +
-                CUSTOM_ZOOM_FACTOR + " REAL DEFAULT 1.0)"
-            );
+                                     ID + " INTEGER PRIMARY KEY, " +
+                                     DOMAIN_NAME + " TEXT, " +
+                                     JAVASCRIPT + " INTEGER DEFAULT 0, " +
+                                     LOCAL_STORAGE + " INTEGER DEFAULT 0, " +
+                                     DOM_STORAGE + " INTEGER DEFAULT 0, " +
+                                     USER_AGENT + " TEXT DEFAULT '" + UserAgentHelper::SYSTEM_DEFAULT_DATABASE + "', " +
+                                     ZOOM_FACTOR + " INTEGER DEFAULT 0, " +
+                                     CUSTOM_ZOOM_FACTOR + " REAL DEFAULT 1.0)");
 
             // Execute the query.
             if (!createTableQuery.exec())
@@ -156,11 +267,51 @@ void DomainsDatabase::addDatabase()
     }
     else  // Opening the database failed.
     {
-        // Write the last database error message to the debug output.
+        // Write the last database error message to the debug output.Settings::zoom
         qDebug().noquote().nospace() << "Error opening database:  " << domainsDatabase.lastError();
     }
 };
 
+void DomainsDatabase::addDomain(const QString &domainName)
+{
+    // Add the domain:
+    addDomain(domainName, SYSTEM_DEFAULT, SYSTEM_DEFAULT, SYSTEM_DEFAULT, UserAgentHelper::SYSTEM_DEFAULT_DATABASE, SYSTEM_DEFAULT, Settings::zoomFactor());
+}
+
+void DomainsDatabase::addDomain(const QString &domainName, const int javaScriptInt, const int localStorageInt, const int domStorageInt, const QString &userAgentDatabaseString,
+                                const int zoomFactorInt, const double currentZoomFactorDouble)
+{
+    // Get a handle for the domains database.
+    QSqlDatabase domainsDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate an add domain settings query.
+    QSqlQuery addDomainSettingsQuery(domainsDatabase);
+
+    // Prepare the query.
+    addDomainSettingsQuery.prepare("INSERT INTO " + DomainsDatabase::DOMAINS_TABLE + " (" +
+                                    DomainsDatabase::DOMAIN_NAME + ", " +
+                                    DomainsDatabase::JAVASCRIPT + ", " +
+                                    DomainsDatabase::LOCAL_STORAGE + ", " +
+                                    DomainsDatabase::DOM_STORAGE + ", " +
+                                    DomainsDatabase::USER_AGENT + ", " +
+                                    DomainsDatabase::ZOOM_FACTOR + ", " +
+                                    DomainsDatabase::CUSTOM_ZOOM_FACTOR + ") " +
+                                    "VALUES (:domain_name, :javascript, :local_storage, :dom_storage, :user_agent, :zoom_factor, :custom_zoom_factor)"
+    );
+
+    // Bind the query values.
+    addDomainSettingsQuery.bindValue(":domain_name", domainName);
+    addDomainSettingsQuery.bindValue(":javascript", javaScriptInt);
+    addDomainSettingsQuery.bindValue(":local_storage", localStorageInt);
+    addDomainSettingsQuery.bindValue(":dom_storage", domStorageInt);
+    addDomainSettingsQuery.bindValue(":user_agent", userAgentDatabaseString);
+    addDomainSettingsQuery.bindValue(":zoom_factor", zoomFactorInt);
+    addDomainSettingsQuery.bindValue(":custom_zoom_factor", currentZoomFactorDouble);
+
+    // Execute the query.
+    addDomainSettingsQuery.exec();
+}
+
 QSqlQuery DomainsDatabase::getDomainQuery(const QString &hostname)
 {
     // Get a handle for the domains database.
@@ -173,7 +324,7 @@ QSqlQuery DomainsDatabase::getDomainQuery(const QString &hostname)
     allDomainNamesQuery.setForwardOnly(true);
 
     // Prepare the query.
-    allDomainNamesQuery.prepare("SELECT " + _ID + "," + DOMAIN_NAME + " FROM " + DOMAINS_TABLE);
+    allDomainNamesQuery.prepare("SELECT " + ID + "," + DOMAIN_NAME + " FROM " + DOMAINS_TABLE);
 
     // Execute the query.
     allDomainNamesQuery.exec();
@@ -185,7 +336,7 @@ QSqlQuery DomainsDatabase::getDomainQuery(const QString &hostname)
     while (allDomainNamesQuery.next())
     {
         // Add the domain name and database ID to the map.
-        domainSettingsMap.insert(allDomainNamesQuery.record().field(DOMAIN_NAME).value().toString(), allDomainNamesQuery.record().field(_ID).value().toInt());
+        domainSettingsMap.insert(allDomainNamesQuery.value(DOMAIN_NAME).toString(), allDomainNamesQuery.value(ID).toInt());
     }
 
     // Initialize the database ID tracker.
@@ -218,7 +369,7 @@ QSqlQuery DomainsDatabase::getDomainQuery(const QString &hostname)
     QSqlQuery domainLookupQuery(domainsDatabase);
 
     // Prepare the domain lookup query.
-    domainLookupQuery.prepare("SELECT * FROM " + DOMAINS_TABLE + " WHERE " + _ID + " = " + QString::number(databaseId));
+    domainLookupQuery.prepare("SELECT * FROM " + DOMAINS_TABLE + " WHERE " + ID + " = " + QString::number(databaseId));
 
     // Execute the query.
     domainLookupQuery.exec();
index a75b831761f8b650dceb5020552b5629c896cf69..1e58d2e38057d606b7215632587e0ee67b66e4f6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -31,21 +31,24 @@ public:
 
     // The public functions.
     static void addDatabase();
+    static void addDomain(const QString &domainName);
+    static void addDomain(const QString &domainName, const int javaScriptInt, const int localStorageInt, const int domStorageInt, const QString &userAgentDatabaseString, const int zoomFactorInt,
+                          const double currentZoomFactorDouble);
     static QSqlQuery getDomainQuery(const QString &hostname);
 
     // The public int constants.
     static const int SYSTEM_DEFAULT = 0;
-    static const int DISABLED = 1;
-    static const int ENABLED = 2;
+    static const int ENABLED = 1;
+    static const int DISABLED = 2;
     static const int CUSTOM = 1;
 
     // The public constants.
-    static const QString _ID;
     static const QString CONNECTION_NAME;
     static const QString CUSTOM_ZOOM_FACTOR;
     static const QString DOM_STORAGE;
     static const QString DOMAIN_NAME;
     static const QString DOMAINS_TABLE;
+    static const QString ID;
     static const QString JAVASCRIPT;
     static const QString LOCAL_STORAGE;
     static const QString USER_AGENT;
index d56c0508061d42fab011e38deecea2a9f0bd8baa..d26efe712c25eb21d256ea048597f0515910710e 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
@@ -17,6 +17,6 @@
 
 
 # List the sources to include in the executable.
-target_sources(privacy-browser PRIVATE
+target_sources(privacybrowser PRIVATE
     ViewOnlyDelegate.cpp
 )
diff --git a/src/dialogs/AddBookmarkDialog.cpp b/src/dialogs/AddBookmarkDialog.cpp
new file mode 100644 (file)
index 0000000..041ca3d
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "AddBookmarkDialog.h"
+#include "ui_AddBookmarkDialog.h"
+#include "databases/BookmarksDatabase.h"
+
+// KDE Framework headers.
+#include <KLocalizedString>
+
+// Qt toolkit headers.
+#include <QFileDialog>
+#include <QPushButton>
+
+// Construct the class.
+AddBookmarkDialog::AddBookmarkDialog(QWidget *parentWidgetPointer, const QString &bookmarkName, const QString &bookmarkUrl, const QIcon &favoriteIcon, const double parentFolderId) :
+                                     QDialog(parentWidgetPointer)
+{
+    // Set the window title.
+    setWindowTitle(i18nc("The add bookmark dialog window title.", "Add Bookmark"));
+
+    // Set the window modality.
+    setWindowModality(Qt::WindowModality::ApplicationModal);
+
+    // Instantiate the add bookmark dialog UI.
+    Ui::AddBookmarkDialog addBookmarkDialogUi;
+
+    // Setup the UI.
+    addBookmarkDialogUi.setupUi(this);
+
+    // Get handles for the widgets.
+    websiteFavoriteIconRadioButtonPointer = addBookmarkDialogUi.websiteFavoriteIconRadioButton;
+    customFavoriteIconRadioButtonPointer = addBookmarkDialogUi.customFavoriteIconRadioButton;
+    parentFolderTreeWidgetPointer = addBookmarkDialogUi.parentFolderTreeWidget;
+    bookmarkNameLineEditPointer = addBookmarkDialogUi.bookmarkNameLineEdit;
+    bookmarkUrlLineEditPointer = addBookmarkDialogUi.bookmarkUrlLineEdit;
+    QPushButton *browseButtonPointer = addBookmarkDialogUi.browseButton;
+    QDialogButtonBox *dialogButtonBoxPointer = addBookmarkDialogUi.dialogButtonBox;
+
+    // Set the icons.
+    websiteFavoriteIconRadioButtonPointer->setIcon(favoriteIcon);
+    customFavoriteIconRadioButtonPointer->setIcon(QIcon::fromTheme(QLatin1String("globe"), QIcon::fromTheme(QLatin1String("applications-internet"))));
+
+    // Instantiate a folder helper.
+    folderHelperPointer = new FolderHelper();
+
+    // Set the parent folder tree widget column count.
+    parentFolderTreeWidgetPointer->setColumnCount(2);
+
+    // Hide the second column.
+    parentFolderTreeWidgetPointer->hideColumn(folderHelperPointer->FOLDER_ID_COLUMN);
+
+    // Set the column header.
+    parentFolderTreeWidgetPointer->setHeaderLabel(i18nc("The folder tree widget header", "Select Parent Folder"));
+
+    // Create a bookmarks tree widget item.
+    QTreeWidgetItem *bookmarksTreeWidgetItemPointer = new QTreeWidgetItem();
+
+    // Populate the bookmarks tree widget item.
+    bookmarksTreeWidgetItemPointer->setText(folderHelperPointer->FOLDER_NAME_COLUMN, i18nc("The bookmarks root tree widget name", "Bookmarks"));
+    bookmarksTreeWidgetItemPointer->setIcon(folderHelperPointer->FOLDER_NAME_COLUMN, QIcon::fromTheme(QLatin1String("bookmarks"), QIcon::fromTheme(QLatin1String("bookmark-new"))));
+    bookmarksTreeWidgetItemPointer->setText(folderHelperPointer->FOLDER_ID_COLUMN, QLatin1String("0"));
+
+    // Add the bookmarks tree widget item to the root of the tree.
+    parentFolderTreeWidgetPointer->addTopLevelItem(bookmarksTreeWidgetItemPointer);
+
+    // Select the root bookmarks folder if it is the initial parent folder.
+    if (parentFolderId == 0)
+        bookmarksTreeWidgetItemPointer->setSelected(true);
+
+    // Populate the subfolders.
+    folderHelperPointer->populateSubfolders(bookmarksTreeWidgetItemPointer, parentFolderId);
+
+    // Open all the folders.
+    parentFolderTreeWidgetPointer->expandAll();
+
+    // Populate the line edits.
+    bookmarkNameLineEditPointer->setText(bookmarkName);
+    bookmarkUrlLineEditPointer->setText(bookmarkUrl);
+
+    // Scroll to the beginning of the bookmark URL line edit.
+    bookmarkUrlLineEditPointer->setCursorPosition(0);
+
+    // Focus the bookmark name line edit.
+    bookmarkNameLineEditPointer->setFocus();
+
+    // Add buttons to the dialog button box.
+    addButtonPointer = dialogButtonBoxPointer->addButton(i18nc("The add bookmark button", "Add"), QDialogButtonBox::AcceptRole);
+
+    // Set the button icons.
+    addButtonPointer->setIcon(QIcon::fromTheme("list-add"));
+
+    // Connect the buttons.
+    connect(browseButtonPointer, SIGNAL(clicked()), this, SLOT(browse()));
+    connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(addBookmark()));
+    connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(reject()));
+
+    // Update the UI when the line edits change.
+    connect(bookmarkNameLineEditPointer, SIGNAL(textEdited(const QString&)), this, SLOT(updateUi()));
+    connect(bookmarkUrlLineEditPointer, SIGNAL(textEdited(const QString&)), this, SLOT(updateUi()));
+
+    // Set the initial UI status.
+    updateUi();
+}
+
+void AddBookmarkDialog::addBookmark()
+{
+    // Get the selected folders list.
+    QList<QTreeWidgetItem*> selectedFoldersList = parentFolderTreeWidgetPointer->selectedItems();
+
+    // Get the selected folder.
+    QTreeWidgetItem *selectedFolderPointer = selectedFoldersList.first();
+
+    // Get the favorite icon.
+    QIcon favoriteIcon = websiteFavoriteIconRadioButtonPointer->isChecked() ? websiteFavoriteIconRadioButtonPointer->icon() : customFavoriteIconRadioButtonPointer->icon();
+
+    // Create a bookmark struct.
+    BookmarkStruct *bookmarkStructPointer = new BookmarkStruct;
+
+    // Populate the bookmark struct.
+    bookmarkStructPointer->name = bookmarkNameLineEditPointer->text();
+    bookmarkStructPointer->url = bookmarkUrlLineEditPointer->text();
+    bookmarkStructPointer->parentFolderId = selectedFolderPointer->text(folderHelperPointer->FOLDER_ID_COLUMN).toDouble();
+    bookmarkStructPointer->favoriteIcon = favoriteIcon;
+
+    // Add the bookmark.
+    BookmarksDatabase::addBookmark(bookmarkStructPointer);
+
+    // Update the list of bookmarks in the menu and toolbar.
+    emit bookmarkAdded();
+
+    // Close the dialog.
+    close();
+}
+
+void AddBookmarkDialog::browse()
+{
+    // Get an image file string from the user.
+    QString imageFileString = QFileDialog::getOpenFileName(this, tr("Favorite Icon Image"), QDir::homePath(),
+                                                           tr("Image Files — *.bmp, *.gif, *.jpg, *.jpeg, *.png, *.svg (*.bmp *.gif *.jpg *.jpeg *.png *.svg);;All Files (*)"));
+
+    // Check to see if an image file string was returned.  This will be empty if the user selected cancel.
+    if (!imageFileString.isEmpty())
+    {
+        // Set the custom favorite icon.
+        customFavoriteIconRadioButtonPointer->setIcon(QIcon(imageFileString));
+
+        // Check the custom favorite icon radio button.
+        customFavoriteIconRadioButtonPointer->setChecked(true);
+    }
+}
+
+void AddBookmarkDialog::updateUi()
+{
+    // Update the add button status
+    if (bookmarkNameLineEditPointer->text().isEmpty() || bookmarkUrlLineEditPointer->text().isEmpty())  // At least one of the line edits is empty.
+    {
+        // Disable the add button.
+        addButtonPointer->setEnabled(false);
+    }
+    else  // Both of the line edits are populated.
+    {
+        // Enable the add button.
+        addButtonPointer->setEnabled(true);
+    }
+}
diff --git a/src/dialogs/AddBookmarkDialog.h b/src/dialogs/AddBookmarkDialog.h
new file mode 100644 (file)
index 0000000..7d38628
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ADDBOOKMARKDIALOG_H
+#define ADDBOOKMARKDIALOG_H
+
+// Application headers.
+#include "helpers/FolderHelper.h"
+
+// Qt toolkit headers.
+#include <QDialog>
+#include <QLineEdit>
+#include <QRadioButton>
+#include <QTreeWidget>
+
+class AddBookmarkDialog : public QDialog
+{
+    // Include the Q_OBJECT macro.
+    Q_OBJECT
+
+public:
+    // The primary constructor.
+    explicit AddBookmarkDialog(QWidget *parentWidgetPointer, const QString &bookmarkName, const QString &bookmarkUrl, const QIcon &favoriteIcon, const double parentFolderId);
+
+signals:
+    // The signals.
+    void bookmarkAdded() const;
+
+private Q_SLOTS:
+    // The private slots.
+    void addBookmark();
+    void browse();
+    void updateUi();
+
+private:
+    // The private variables.
+    FolderHelper *folderHelperPointer;
+
+    // The private widgets.
+    QPushButton *addButtonPointer;
+    QLineEdit *bookmarkNameLineEditPointer;
+    QLineEdit *bookmarkUrlLineEditPointer;
+    QRadioButton *customFavoriteIconRadioButtonPointer;
+    QTreeWidget *parentFolderTreeWidgetPointer;
+    QRadioButton *websiteFavoriteIconRadioButtonPointer;
+};
+#endif
diff --git a/src/dialogs/AddFolderDialog.cpp b/src/dialogs/AddFolderDialog.cpp
new file mode 100644 (file)
index 0000000..7462930
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "AddFolderDialog.h"
+#include "ui_AddFolderDialog.h"
+#include "databases/BookmarksDatabase.h"
+#include "helpers/FolderHelper.h"
+#include "structs/BookmarkStruct.h"
+
+// Qt toolkit headers.
+#include <QFileDialog>
+
+// Construct the class.
+AddFolderDialog::AddFolderDialog(QWidget *parentWidgetPointer, const QIcon &currentWebsiteFavoriteIcon, const double parentFolderId) : QDialog(parentWidgetPointer)
+{
+    // Set the window title.
+    setWindowTitle(i18nc("The add folder dialog window title.", "Add Folder"));
+
+    // Set the window modality.
+    setWindowModality(Qt::WindowModality::ApplicationModal);
+
+    // Instantiate the add folder dialog UI.
+    Ui::AddFolderDialog addFolderDialogUi;
+
+    // Setup the UI.
+    addFolderDialogUi.setupUi(this);
+
+    // Get handles for the widgets.
+    defaultFolderIconRadioButtonPointer = addFolderDialogUi.defaultFolderIconRadioButton;
+    currentWebsiteFavoriteIconRadioButtonPointer = addFolderDialogUi.currentWebsiteFavoriteIconRadioButton;
+    customFolderIconRadioButtonPointer = addFolderDialogUi.customFolderIconRadioButton;
+    parentFolderTreeWidgetPointer = addFolderDialogUi.parentFolderTreeWidget;
+    folderNameLineEditPointer = addFolderDialogUi.folderNameLineEdit;
+    QPushButton *browseButtonPointer = addFolderDialogUi.browseButton;
+    QDialogButtonBox *dialogButtonBoxPointer = addFolderDialogUi.dialogButtonBox;
+
+    // Set the default favorite icon.
+    currentWebsiteFavoriteIconRadioButtonPointer->setIcon(currentWebsiteFavoriteIcon);
+
+    // Instantiate a folder helper.
+    folderHelperPointer = new FolderHelper();
+
+    // Set the parent folder tree widget column count.
+    parentFolderTreeWidgetPointer->setColumnCount(2);
+
+    // Hide the second column.
+    parentFolderTreeWidgetPointer->hideColumn(folderHelperPointer->FOLDER_ID_COLUMN);
+
+    // Set the column header.
+    parentFolderTreeWidgetPointer->setHeaderLabel(i18nc("The folder tree widget header", "Select Parent Folder"));
+
+    // Create a bookmarks tree widget item.
+    QTreeWidgetItem *bookmarksTreeWidgetItemPointer = new QTreeWidgetItem();
+
+    // Populate the bookmarks tree widget item.
+    bookmarksTreeWidgetItemPointer->setText(folderHelperPointer->FOLDER_NAME_COLUMN, i18nc("The bookmarks root tree widget name", "Bookmarks"));
+    bookmarksTreeWidgetItemPointer->setIcon(folderHelperPointer->FOLDER_NAME_COLUMN, QIcon::fromTheme(QLatin1String("bookmarks"), QIcon::fromTheme(QLatin1String("bookmark-new"))));
+    bookmarksTreeWidgetItemPointer->setText(folderHelperPointer->FOLDER_ID_COLUMN, QLatin1String("0"));
+
+    // Add the bookmarks tree widget item to the root of the tree.
+    parentFolderTreeWidgetPointer->addTopLevelItem(bookmarksTreeWidgetItemPointer);
+
+    // Select the root bookmarks folder if it is the initial parent folder.
+    if (parentFolderId == 0)
+        bookmarksTreeWidgetItemPointer->setSelected(true);
+
+    // Populate the subfolders.
+    folderHelperPointer->populateSubfolders(bookmarksTreeWidgetItemPointer, parentFolderId);
+
+    // Open all the folders.
+    parentFolderTreeWidgetPointer->expandAll();
+
+    // Focus the folder name line edit.
+    folderNameLineEditPointer->setFocus();
+
+    // Add buttons to the dialog button box.
+    addButtonPointer = dialogButtonBoxPointer->addButton(i18nc("The add folder button", "Add"), QDialogButtonBox::AcceptRole);
+
+    // Set the button icons.
+    addButtonPointer->setIcon(QIcon::fromTheme("list-add"));
+
+    // Connect the buttons.
+    connect(browseButtonPointer, SIGNAL(clicked()), this, SLOT(browse()));
+    connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(addFolder()));
+    connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(reject()));
+
+    // Update the UI when the folder name changes.
+    connect(folderNameLineEditPointer, SIGNAL(textEdited(const QString&)), this, SLOT(updateUi(const QString&)));
+
+    // Set the initial UI status.
+    updateUi(folderNameLineEditPointer->text());
+}
+
+void AddFolderDialog::addFolder()
+{
+    // Get the parent folder ID.
+    QList<QTreeWidgetItem*> selectedFoldersList = parentFolderTreeWidgetPointer->selectedItems();
+
+    // Get the selected folder.
+    QTreeWidgetItem *selectedFolderPointer = selectedFoldersList.first();
+
+    // Create a favorite icon.
+    QIcon favoriteIcon;
+
+    // Get the favorite icon.
+    if (defaultFolderIconRadioButtonPointer->isChecked())  // The default folder icon is checked.
+        favoriteIcon = defaultFolderIconRadioButtonPointer->icon();
+    else if (currentWebsiteFavoriteIconRadioButtonPointer->isChecked())  // The current website favorite icon is checked.
+        favoriteIcon = currentWebsiteFavoriteIconRadioButtonPointer->icon();
+    else  // The custom folder icon is checked.
+        favoriteIcon = customFolderIconRadioButtonPointer->icon();
+
+    // Create a bookmark struct.
+    BookmarkStruct *bookmarkStructPointer = new BookmarkStruct;
+
+    // Populate the bookmark struct.
+    bookmarkStructPointer->name = folderNameLineEditPointer->text();
+    bookmarkStructPointer->parentFolderId = selectedFolderPointer->text(folderHelperPointer->FOLDER_ID_COLUMN).toDouble();
+    bookmarkStructPointer->favoriteIcon = favoriteIcon;
+
+    // Add the folder.
+    BookmarksDatabase::addFolder(bookmarkStructPointer);
+
+    // Update the list of bookmarks in the menu and toolbar.
+    emit folderAdded();
+
+    // Close the dialog.
+    close();
+}
+
+void AddFolderDialog::browse()
+{
+    // Get an image file string from the user.
+    QString imageFileString = QFileDialog::getOpenFileName(this, tr("Favorite Icon Image"), QDir::homePath(),
+                                                           tr("Image Files — *.bmp, *.gif, *.jpg, *.jpeg, *.png, *.svg (*.bmp *.gif *.jpg *.jpeg *.png *.svg);;All Files (*)"));
+
+    // Check to see if an image file string was returned.  This will be empty if the user selected cancel.
+    if (!imageFileString.isEmpty())
+    {
+        // Set the custom favorite icon.
+        customFolderIconRadioButtonPointer->setIcon(QIcon(imageFileString));
+
+        // Check the custom favorite icon radio button.
+        customFolderIconRadioButtonPointer->setChecked(true);
+    }
+}
+
+void AddFolderDialog::updateUi(const QString &newFolderName)
+{
+    // Set the status of the add button based on the
+    addButtonPointer->setEnabled(!newFolderName.isEmpty());
+}
diff --git a/src/dialogs/AddFolderDialog.h b/src/dialogs/AddFolderDialog.h
new file mode 100644 (file)
index 0000000..e0673a3
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ADDFOLDERDIALOG_H
+#define ADDFOLDERDIALOG_H
+
+// Application headers.
+#include "helpers/FolderHelper.h"
+
+// Qt toolkit headers.
+#include <QDialog>
+#include <QLineEdit>
+#include <QRadioButton>
+#include <QTreeWidget>
+
+class AddFolderDialog : public QDialog
+{
+    // Include the Q_OBJECT macro.
+    Q_OBJECT
+
+public:
+    // The primary constructor.
+    explicit AddFolderDialog(QWidget *parentWidgetPointer, const QIcon &currentWebsiteFavoriteIcon, const double parentFolderId);
+
+signals:
+    // The signals.
+    void folderAdded() const;
+
+private Q_SLOTS:
+    // The private slots.
+    void addFolder();
+    void browse();
+    void updateUi(const QString &newFolderName);
+
+private:
+    // The private variables.
+    FolderHelper *folderHelperPointer;
+
+    // The private widgets.
+    QPushButton *addButtonPointer;
+    QRadioButton *currentWebsiteFavoriteIconRadioButtonPointer;
+    QRadioButton *customFolderIconRadioButtonPointer;
+    QRadioButton *defaultFolderIconRadioButtonPointer;
+    QLineEdit *folderNameLineEditPointer;
+    QTreeWidget *parentFolderTreeWidgetPointer;
+};
+#endif
index e39303e8e7094230d63cf6db49615fcb0686e8d5..bb07c421fc4f6eab6bb4d00031be7fb3b06af2ca 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -34,7 +34,7 @@ const int AddOrEditCookieDialog::AddCookie = 0;
 const int AddOrEditCookieDialog::EditCookie = 1;
 
 // Construct the class.
-AddOrEditCookieDialog::AddOrEditCookieDialog(const int &dialogType, const QNetworkCookie *cookiePointer, const bool &isDurable) : QDialog(nullptr)
+AddOrEditCookieDialog::AddOrEditCookieDialog(QWidget *parentWidgetPointer, const int dialogType, const QNetworkCookie *cookiePointer, const bool isDurable) : QDialog(parentWidgetPointer)
 {
     // Set the dialog window title according to the dialog type.
     if (dialogType == AddCookie)
@@ -49,7 +49,7 @@ AddOrEditCookieDialog::AddOrEditCookieDialog(const int &dialogType, const QNetwo
     // Set the window modality.
     setWindowModality(Qt::WindowModality::ApplicationModal);
 
-    // Instantiate the cookie settings dialog UI.
+    // Instantiate the cookie dialog UI.
     Ui::AddOrEditCookieDialog addOrEditCookieDialogUi;
 
     // Setup the UI.
@@ -83,6 +83,12 @@ AddOrEditCookieDialog::AddOrEditCookieDialog(const int &dialogType, const QNetwo
         secureCheckBoxPointer->setChecked(originalCookie.isSecure());
         valueLineEditPointer->setText(originalCookie.value());
 
+        // Scroll to the beginning of the line edits.
+        domainLineEditPointer->setCursorPosition(0);
+        nameLineEditPointer->setCursorPosition(0);
+        pathLineEditPointer->setCursorPosition(0);
+        valueLineEditPointer->setCursorPosition(0);
+
         // Populate the expiration date if it exists.
         if (!originalCookie.isSessionCookie())
         {
@@ -167,7 +173,7 @@ void AddOrEditCookieDialog::saveCookie()
     emit addCookie(cookie, isDurable);
 
     // Close the dialog.
-    reject();
+    close();
 }
 
 void AddOrEditCookieDialog::updateExpirationDateTimeState(const int &newState) const
index d00e3f7596d06169420df8240a1baacb18f190fc..cb641c951a2759c1d4d75c29a058f5904a2d94c7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022, 2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -33,7 +33,7 @@ class AddOrEditCookieDialog : public QDialog
 
 public:
     // The primary constructor.
-    explicit AddOrEditCookieDialog(const int &dialogType, const QNetworkCookie *cookiePointer = nullptr, const bool &isDurable = false);
+    explicit AddOrEditCookieDialog(QWidget *parentWidgetPointer, const int dialogType, const QNetworkCookie *cookiePointer = nullptr, const bool isDurable = false);
 
     // The public static constants.
     static const int AddCookie;
diff --git a/src/dialogs/BookmarksDialog.cpp b/src/dialogs/BookmarksDialog.cpp
new file mode 100644 (file)
index 0000000..683d240
--- /dev/null
@@ -0,0 +1,487 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "BookmarksDialog.h"
+#include "ui_BookmarksDialog.h"
+#include "databases/BookmarksDatabase.h"
+#include "dialogs/AddBookmarkDialog.h"
+#include "dialogs/AddFolderDialog.h"
+#include "dialogs/EditBookmarkDialog.h"
+#include "dialogs/EditFolderDialog.h"
+
+// KDE Frameworks headers.
+#include <KLocalizedString>
+
+// Qt toolkit headers.
+#include <QDebug>
+#include <QStandardItemModel>
+
+// Construct the class.
+BookmarksDialog::BookmarksDialog(QWidget *parentWidgetPointer, QIcon currentWebsiteFavorieIcon, QString currentWebsiteTitle, QString currentWebsiteUrl) :
+                                 QDialog(parentWidgetPointer), websiteFavoriteIcon(currentWebsiteFavorieIcon), websiteTitle(currentWebsiteTitle), websiteUrl(currentWebsiteUrl)
+{
+    // Set the dialog window title.
+    setWindowTitle(i18nc("The bookmarks dialog window title", "Bookmarks"));
+
+    // Set the window modality.
+    setWindowModality(Qt::WindowModality::ApplicationModal);
+
+    // Instantiate the bookmarks settings dialog UI.
+    Ui::BookmarksDialog bookmarksDialogUi;
+
+    // Setup the UI.
+    bookmarksDialogUi.setupUi(this);
+
+    // Get a handle for the draggable tree view.
+    draggableTreeViewPointer = bookmarksDialogUi.draggableTreeView;
+
+    // Initialize the tree model.
+    treeModelPointer = new QStandardItemModel();
+
+    // Auto resize the headers.
+    draggableTreeViewPointer->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+
+    // Indicate that all the rows are the same height, which improves performance.
+    draggableTreeViewPointer->setUniformRowHeights(true);
+
+    // Set the selection mode to allow multiple rows to be selected at once.
+    draggableTreeViewPointer->setSelectionMode(QAbstractItemView::ExtendedSelection);
+
+    // Allow dragging of bookmarks to reorder.
+    draggableTreeViewPointer->setDragDropMode(QAbstractItemView::InternalMove);
+
+    // Set the tree model.
+    draggableTreeViewPointer->setModel(treeModelPointer);
+
+    // Get a handle for the tree selection model.
+    treeSelectionModelPointer = draggableTreeViewPointer->selectionModel();
+
+    // Listen for selection changes.
+    connect(treeSelectionModelPointer, SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(updateSelection()));
+
+    // Repopulate the bookmarks when they are moved.
+    connect(draggableTreeViewPointer, SIGNAL(bookmarksMoved()), this, SLOT(refreshBookmarks()));
+
+    // Get handles for the buttons.
+    QPushButton *addBookmarkButtonPointer = bookmarksDialogUi.addBookmarkButton;
+    QPushButton *addFolderButtonPointer = bookmarksDialogUi.addFolderButton;
+    editButtonPointer = bookmarksDialogUi.editButton;
+    deleteItemsButtonPointer = bookmarksDialogUi.deleteItemsButton;
+    QDialogButtonBox *dialogButtonBoxPointer = bookmarksDialogUi.dialogButtonBox;
+    QPushButton *closeButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::Close);
+
+    // Connect the buttons.
+    connect(addBookmarkButtonPointer, SIGNAL(clicked()), this, SLOT(showAddBookmarkDialog()));
+    connect(addFolderButtonPointer, SIGNAL(clicked()), this, SLOT(showAddFolderDialog()));
+    connect(editButtonPointer, SIGNAL(clicked()), this, SLOT(showEditDialog()));
+    connect(deleteItemsButtonPointer, SIGNAL(clicked()), this, SLOT(deleteItems()));
+    connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(reject()));
+
+    // Set the close button to be the default.
+    closeButtonPointer->setDefault(true);
+
+    // Monitor editing of data in the tree model.
+    connect(treeModelPointer, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(updateBookmarkFromTree(QStandardItem*)));
+
+    // Populate the bookmarks.
+    populateBookmarks();
+}
+
+void BookmarksDialog::deleteItems() const
+{
+    // Create a parent folder ID standard C++ list (which can be sorted and from which duplicates can be removed).
+    std::list<double> parentFolderIdList;
+
+    // Get the list of selected model indexes.
+    QList<QModelIndex> selectedModelIndexList = treeSelectionModelPointer->selectedRows(DATABASE_ID_COLUMN);
+
+    // Delete each of the bookmarks.
+    for (const QModelIndex &modelIndex : selectedModelIndexList)
+    {
+        // Add the parent folder ID to the list.
+        parentFolderIdList.push_back(modelIndex.parent().siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble());
+
+        // Delete the bookmark.
+        BookmarksDatabase::deleteBookmark(modelIndex.data().toInt());
+    }
+
+    // Sort the parent folder ID.
+    parentFolderIdList.sort();
+
+    // Remove duplicates from the parent folder ID list.
+    parentFolderIdList.unique();
+
+    // Update the folder contents display order for each folder with a deleted bookmark.
+    for (const double parentFolderId : parentFolderIdList)
+        BookmarksDatabase::updateFolderContentsDisplayOrder(parentFolderId);
+
+    // Repopulate the bookmarks in this dialog
+    populateBookmarks();
+
+    // Emit the bookmark updated signal to redraw the bookmarks in the menu and toolbar.
+    emit bookmarkUpdated();
+}
+
+void BookmarksDialog::populateBookmarks() const
+{
+    // Clear the current contents of the tree model.
+    treeModelPointer->clear();
+
+    // Set the column count.
+    treeModelPointer->setColumnCount(6);
+
+    // Set the tree header data.
+    treeModelPointer->setHeaderData(NAME_COLUMN, Qt::Horizontal, i18nc("The bookmark Name header.", "Name"));
+    treeModelPointer->setHeaderData(URL_COLUMN, Qt::Horizontal, i18nc("The bookmark URL header.", "URL"));
+
+    // Hide the backend columns.
+    draggableTreeViewPointer->setColumnHidden(DATABASE_ID_COLUMN, true);
+    draggableTreeViewPointer->setColumnHidden(DISPLAY_ORDER_COLUMN, true);
+    draggableTreeViewPointer->setColumnHidden(IS_FOLDER_COLUMN, true);
+    draggableTreeViewPointer->setColumnHidden(FOLDER_ID_COLUMN, true);
+
+    // Create a bookmarks root item list.
+    QList<QStandardItem*> bookmarksRootItemList;
+
+    // Create the root items.
+    QStandardItem *rootItemNamePointer = new QStandardItem(QIcon::fromTheme(QLatin1String("bookmarks"), QIcon::fromTheme(QLatin1String("bookmark-new"))),
+                                                           i18nc("The bookmarks root tree widget name", "Bookmarks"));
+    QStandardItem *rootItemUrlPointer = new QStandardItem(QLatin1String(""));
+    QStandardItem *rootItemDatabaseIdPonter = new QStandardItem(QLatin1String("-1"));  // The root item doesn't have a database ID.
+    QStandardItem *rootItemDisplayOrderPointer = new QStandardItem(QLatin1String("-1"));  // The root item doesn't have a display order.
+    QStandardItem *rootItemIsFolderPointer = new QStandardItem(QLatin1String("1"));
+    QStandardItem *rootItemFolderIdPointer = new QStandardItem(QLatin1String("0"));
+
+    // Enable dropping on the root name column.
+    rootItemNamePointer->setDropEnabled(true);
+
+    // Disable dropping on the URL.
+    rootItemUrlPointer->setDropEnabled(false);
+
+    // Disable dragging of the bookmarks root item.
+    rootItemNamePointer->setDragEnabled(false);
+    rootItemUrlPointer->setDragEnabled(false);
+
+    // Disable selecting the URL.
+    rootItemUrlPointer->setSelectable(false);
+
+    // Populate the bookmarks root item list.
+    bookmarksRootItemList.append(rootItemNamePointer);
+    bookmarksRootItemList.append(rootItemUrlPointer);
+    bookmarksRootItemList.append(rootItemDatabaseIdPonter);
+    bookmarksRootItemList.append(rootItemDisplayOrderPointer);
+    bookmarksRootItemList.append(rootItemIsFolderPointer);
+    bookmarksRootItemList.append(rootItemFolderIdPointer);
+
+    // Add the bookmarks root item to the tree.
+    treeModelPointer->appendRow(bookmarksRootItemList);
+
+    // Populate the subfolders, starting with the root folder ID (`0`).
+    populateSubfolders(rootItemNamePointer, 0);
+
+    // Expand all the folder.
+    draggableTreeViewPointer->expandAll();
+
+    // Update the UI.
+    updateUi();
+}
+
+void BookmarksDialog::populateSubfolders(QStandardItem *folderItemNamePointer, const double folderId) const
+{
+    // Get the folder contents.
+    QList<BookmarkStruct> *folderContentsListPointer = BookmarksDatabase::getFolderContents(folderId);
+
+    // Populate the bookmarks tree view.
+    for (const BookmarkStruct &bookmarkStruct : *folderContentsListPointer)
+    {
+        // Create a bookmark item list.
+        QList<QStandardItem*> bookmarkItemList;
+
+        // Create the bookmark items.
+        QStandardItem *nameItemPointer = new QStandardItem(bookmarkStruct.favoriteIcon, bookmarkStruct.name);
+        QStandardItem *urlItemPointer = new QStandardItem(bookmarkStruct.url);
+        QStandardItem *databaseIdItemPointer = new QStandardItem(QString::number(bookmarkStruct.databaseId));
+        QStandardItem *displayOrderItemPointer = new QStandardItem(QString::number(bookmarkStruct.displayOrder));
+        QStandardItem *isFolderItemPointer = new QStandardItem(QString::number(bookmarkStruct.isFolder));
+        QStandardItem *folderIdItemPointer = new QStandardItem(QString::number(bookmarkStruct.folderId, 'f', 0));  // Format the folder ID as a floating point with no trailing zeros.
+
+        // Enable dragging and dropping of the name column.
+        nameItemPointer->setDragEnabled(true);
+
+        // Only allow dropping on the name if this is a folder.
+        nameItemPointer->setDropEnabled(bookmarkStruct.isFolder);
+
+        // Disable dragging and dropping on the URL.
+        urlItemPointer->setDragEnabled(false);
+        urlItemPointer->setDropEnabled(false);
+
+        // Disable selecting the URL.
+        urlItemPointer->setSelectable(false);
+
+        // Populate the bookmark item list.
+        bookmarkItemList.append(nameItemPointer);
+        bookmarkItemList.append(urlItemPointer);
+        bookmarkItemList.append(databaseIdItemPointer);
+        bookmarkItemList.append(displayOrderItemPointer);
+        bookmarkItemList.append(isFolderItemPointer);
+        bookmarkItemList.append(folderIdItemPointer);
+
+        // Add the bookmark to the parent folder.
+        folderItemNamePointer->appendRow(bookmarkItemList);
+
+        // Populate subfolders if this item is a folder.
+        if (bookmarkStruct.isFolder)
+            populateSubfolders(nameItemPointer, bookmarkStruct.folderId);
+    }
+}
+
+void BookmarksDialog::refreshBookmarks() const
+{
+    // Repopulate the bookmarks in this dialog
+    populateBookmarks();
+
+    // Emit the bookmark updated signal to redraw the bookmarks in the menu and toolbar.
+    emit bookmarkUpdated();
+}
+
+void BookmarksDialog::selectSubfolderContents(const QModelIndex &parentModelIndex) const
+{
+    // Get the index model.
+    const QAbstractItemModel *modelIndexAbstractItemPointer = parentModelIndex.model();
+
+    // Get the number of items in the folder.
+    int numberOfChildrenInFolder = modelIndexAbstractItemPointer->rowCount(parentModelIndex);
+
+    // Select any child items.
+    if (numberOfChildrenInFolder > 0)
+    {
+        // Select the contents of any subfolders.
+        for (int i = 0; i < numberOfChildrenInFolder; ++i)
+        {
+            // Get the child model index.
+            QModelIndex childModelIndex = modelIndexAbstractItemPointer->index(i, 0, parentModelIndex);
+
+            // Select the subfolder contents if it is a folder.
+            if (childModelIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toBool())
+                selectSubfolderContents(childModelIndex);
+        }
+
+        // Get the first and last child model indexes.
+        QModelIndex firstChildModelIndex = modelIndexAbstractItemPointer->index(0, 0, parentModelIndex);
+        QModelIndex lastChildModelIndex = modelIndexAbstractItemPointer->index((numberOfChildrenInFolder - 1), 0, parentModelIndex);
+
+        // Create an item selection that includes all the child items.
+        QItemSelection folderChildItemsSelection = QItemSelection(firstChildModelIndex, lastChildModelIndex);
+
+        // Get the current selection.
+        QItemSelection currentSelection = treeSelectionModelPointer->selection();
+
+        // Combine the current selection and the folder child items selection.
+        currentSelection.merge(folderChildItemsSelection, QItemSelectionModel::SelectCurrent);
+
+        // Selected the updated list of items.
+        treeSelectionModelPointer->select(currentSelection, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
+    }
+}
+
+void BookmarksDialog::showAddBookmarkDialog()
+{
+    // Return the most recently selected index.
+    QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
+
+    // Instantiate a parent folder ID.
+    double parentFolderId;
+
+    // Get the parent folder ID.
+    if (currentIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toInt() == 1)  // The current index is a folder.
+    {
+        // Store the parent folder ID.
+        parentFolderId = currentIndex.siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
+    }
+    else  // The current index is not a folder.
+    {
+        // Store the parent folder ID of the folder that contains the bookmark.
+        parentFolderId = currentIndex.parent().siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
+    }
+
+    // Instantiate an add bookmark dialog.
+    AddBookmarkDialog *addBookmarkDialogPointer = new AddBookmarkDialog(this, websiteTitle, websiteUrl, websiteFavoriteIcon, parentFolderId);
+
+    // Update the displayed bookmarks when a new one is added.
+    connect(addBookmarkDialogPointer, SIGNAL(bookmarkAdded()), this, SLOT(refreshBookmarks()));
+
+    // Show the dialog.
+    addBookmarkDialogPointer->show();
+}
+
+void BookmarksDialog::showAddFolderDialog()
+{
+    // Get the most recently selected index.
+    QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
+
+    // Instantiate a parent folder ID.
+    double parentFolderId;
+
+    // Get the parent folder ID.
+    if (currentIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toInt() == 1)  // The current index is a folder.
+    {
+        // Store the parent folder ID.
+        parentFolderId = currentIndex.siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
+    }
+    else  // The current index is not a folder.
+    {
+        // Store the parent folder ID of the folder that contains the bookmark.
+        parentFolderId = currentIndex.parent().siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
+    }
+
+    // Instantiate an add folder dialog.
+    AddFolderDialog *addFolderDialogPointer = new AddFolderDialog(this, websiteFavoriteIcon, parentFolderId);
+
+    // Update the displayed bookmarks when a folder is added.
+    connect(addFolderDialogPointer, SIGNAL(folderAdded()), this, SLOT(refreshBookmarks()));
+
+    // Show the dialog.
+    addFolderDialogPointer->show();
+}
+
+void BookmarksDialog::showEditDialog()
+{
+    // Get the current model index.
+    QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
+
+    // Check to see if the selected item is a folder.
+    if (currentIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toInt() == 1)  // The selected item is a folder.
+    {
+        // Instantiate an edit folder dialog.
+        QDialog *editFolderDialogPointer = new EditFolderDialog(this, currentIndex.siblingAtColumn(DATABASE_ID_COLUMN).data().toInt(), websiteFavoriteIcon);
+
+        // Show the dialog.
+        editFolderDialogPointer->show();
+
+        // Update the bookmarks UI.
+        connect(editFolderDialogPointer, SIGNAL(folderSaved()), this, SLOT(refreshBookmarks()));
+    }
+    else  // The selected item is a bookmark.
+    {
+        // Instantiate an edit bookmark dialog.
+        QDialog *editBookmarkDialogPointer = new EditBookmarkDialog(this, currentIndex.siblingAtColumn(DATABASE_ID_COLUMN).data().toInt(), websiteFavoriteIcon);
+
+        // Show the dialog.
+        editBookmarkDialogPointer->show();
+
+        // Update the bookmarks UI.
+        connect(editBookmarkDialogPointer, SIGNAL(bookmarkSaved()), this, SLOT(refreshBookmarks()));
+    }
+}
+
+void BookmarksDialog::updateBookmarkFromTree(QStandardItem *modifiedStandardItem)
+{
+    // Get the model index of the modified item.
+    QModelIndex modifiedItemModelIndex = modifiedStandardItem->index();
+
+    // Get the model index of the database ID.
+    QModelIndex databaseIdModelIndex = modifiedItemModelIndex.siblingAtColumn(DATABASE_ID_COLUMN);
+
+    // Get the database ID.
+    int databaseId = databaseIdModelIndex.data().toInt();
+
+    // Check to see if the bookmark name or the URL was edited.
+    if (modifiedStandardItem->column() == NAME_COLUMN)  // The bookmark name was edited.
+    {
+        // Update the bookmark name.
+        BookmarksDatabase::updateBookmarkName(databaseId, modifiedStandardItem->text());
+    }
+    else  // The bookmark URL was edited.
+    {
+        // Update the bookmark URL.
+        BookmarksDatabase::updateBookmarkUrl(databaseId, modifiedStandardItem->text());
+    }
+
+    // Emit the bookmark updated signal.
+    emit bookmarkUpdated();
+}
+
+void BookmarksDialog::updateSelection() const
+{
+    // Set the status of the buttons.
+    if (treeSelectionModelPointer->hasSelection())  // A bookmark or folder is selected.
+    {
+        // Get the list of selected model indexes.
+        QModelIndexList selectedRowsModelIndexList = treeSelectionModelPointer->selectedRows();
+
+        // Check to see if each selected item is a folder.
+        for(QModelIndex modelIndex : selectedRowsModelIndexList)
+        {
+            // If it is a folder, select all the children bookmarks.
+            if (modelIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toBool())
+                selectSubfolderContents(modelIndex);
+        }
+    }
+
+    // Update the UI.
+    updateUi();
+}
+
+void BookmarksDialog::updateUi() const
+{
+    // Set the status of the buttons.
+    if (treeSelectionModelPointer->hasSelection())  // A bookmark or folder is selected.
+    {
+        // Get the currently selected index.
+        QModelIndex currentSelectedIndex = treeSelectionModelPointer->currentIndex();
+
+        // Get the list of selected model indexes.
+        QModelIndexList selectedRowsModelIndexList = treeSelectionModelPointer->selectedRows();
+
+        // Get the number of selected rows.
+        int numberOfSelectedRows = selectedRowsModelIndexList.count();
+
+        // Enable the edit button if a folder or only one bookmark is selected.
+        editButtonPointer->setEnabled(currentSelectedIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toBool() || (numberOfSelectedRows == 1));
+
+        // Check if the root folder is selected.
+        if (treeSelectionModelPointer->isRowSelected(0))
+        {
+            // Disable the edit button.
+            editButtonPointer->setEnabled(false);
+
+            // Decrease the number of selected rows by 1.
+            --numberOfSelectedRows;
+        }
+
+        // Enabled the delete button if at least one real bookmark or folder is selected.
+        deleteItemsButtonPointer->setEnabled(numberOfSelectedRows > 0);
+
+        // Update the delete items button text.
+        if (numberOfSelectedRows > 0)
+            deleteItemsButtonPointer->setText(i18ncp("Delete items button populated text.", "Delete %1 item", "Delete %1 items", numberOfSelectedRows));
+        else
+            deleteItemsButtonPointer->setText(i18nc("Delete items button default text", "Delete items"));
+    }
+    else  // Nothing is selected.
+    {
+        // Disable the buttons.
+        editButtonPointer->setEnabled(false);
+        deleteItemsButtonPointer->setEnabled(false);
+
+        // Update the delete items button text.
+        deleteItemsButtonPointer->setText(i18nc("Delete items button default text", "Delete items"));
+    }
+}
diff --git a/src/dialogs/BookmarksDialog.h b/src/dialogs/BookmarksDialog.h
new file mode 100644 (file)
index 0000000..0df0903
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BOOKMARKSDIALOG_H
+#define BOOKMARKSDIALOG_H
+
+// Application headers.
+#include "structs/BookmarkStruct.h"
+#include "widgets/DraggableTreeView.h"
+
+// Qt toolkit headers.
+#include <QDialog>
+#include <QItemSelectionModel>
+#include <QStandardItem>
+
+class BookmarksDialog : public QDialog
+{
+    // Include the Q_OBJECT macro.
+    Q_OBJECT
+
+public:
+    // The primary constructor.
+    BookmarksDialog(QWidget *parentWidgetPointer, QIcon currentWebsiteFavorieIcon, QString currentWebsiteTitle, QString currentWebsiteUrl);
+
+    // The public constants.
+    static const int NAME_COLUMN = 0;
+    static const int URL_COLUMN = 1;
+    static const int DATABASE_ID_COLUMN = 2;
+    static const int DISPLAY_ORDER_COLUMN = 3;
+    static const int IS_FOLDER_COLUMN = 4;
+    static const int FOLDER_ID_COLUMN = 5;
+
+signals:
+    // The signals.
+    void bookmarkUpdated() const;
+
+private Q_SLOTS:
+    // The private slots.
+    void deleteItems() const;
+    void refreshBookmarks() const;
+    void showAddBookmarkDialog();
+    void showAddFolderDialog();
+    void showEditDialog();
+    void updateBookmarkFromTree(QStandardItem *modifiedStandardItem);
+    void updateSelection() const;
+
+private:
+    // The private variables.
+    QPushButton *deleteItemsButtonPointer;
+    QPushButton *editButtonPointer;
+    QStandardItemModel *treeModelPointer;
+    QItemSelectionModel *treeSelectionModelPointer;
+    DraggableTreeView *draggableTreeViewPointer;
+    QIcon websiteFavoriteIcon;
+    QString websiteTitle;
+    QString websiteUrl;
+
+    // The private functions.
+    void populateBookmarks() const;
+    void populateSubfolders(QStandardItem *folderItemNamePointer, const double folderId) const;
+    void selectSubfolderContents(const QModelIndex &parentModelIndex) const;
+    void updateUi() const;
+};
+#endif
index 7120f4d053f607efa04fe7a40560169635c1cff0..94b84aa406a509b02a9f82ab75dd7bd4d1beeb49 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
 
 
 # List the sources to include in the executable.
-target_sources(privacy-browser PRIVATE
+target_sources(privacybrowser PRIVATE
+    AddBookmarkDialog.cpp
+    AddFolderDialog.cpp
     AddOrEditCookieDialog.cpp
+    BookmarksDialog.cpp
     CookiesDialog.cpp
     DomainSettingsDialog.cpp
     DurableCookiesDialog.cpp
+    EditBookmarkDialog.cpp
+    EditFolderDialog.cpp
+    HttpAuthenticationDialog.cpp
     SaveDialog.cpp
+    SettingsDialog.cpp
 )
index 4e85efe4d112f9bbf35e34121b34fd3a58af8e61..57e5ed718c770e28b8a86c18fb760945df5cd5f9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -65,7 +65,7 @@ bool cookieSortPredicate(const QNetworkCookie &leftHandCookie, const QNetworkCoo
         QString leftHandThirdLevelDomain;
         QString rightHandThirdLevelDomain;
 
-        // Get the numer of dots in the strings.
+        // Get the number of dots in the strings.
         int leftHandDots = leftHandDomain.count(QLatin1Char('.'));
         int rightHandDots = rightHandDomain.count(QLatin1Char('.'));
 
@@ -154,7 +154,7 @@ CookiesDialog::CookiesDialog(std::list<QNetworkCookie> *originalCookieListPointe
     treeModelPointer->horizontalHeaderItem(0)->setToolTip(i18nc("The cookie Name tool tip.",
                                                                         "The name identifies the cookie.  Each cookie has a unique combination of domain, name, and path."));
     treeModelPointer->horizontalHeaderItem(1)->setToolTip(i18nc("The cookie Durable tool tip",
-                                                                        "Durable cookies pursist across restarts, irrespective of the expiration date. All other cookies are deleted when Privacy Browser closes, irrespective of the expiration date."));
+                                                                        "Durable cookies persist across restarts, irrespective of the expiration date. All other cookies are deleted when Privacy Browser closes, irrespective of the expiration date."));
     treeModelPointer->horizontalHeaderItem(2)->setToolTip(i18nc("The cookie Path tool tip.", "Websites can restrict cookie access to subpath of their URL."));
     treeModelPointer->horizontalHeaderItem(3)->setToolTip(i18nc("The cookie Expiration Date tool tip.",
                                                                         "Cookies without an expiration date are known as session cookies and are expected to be deleted every time the browser closes."));
@@ -202,11 +202,11 @@ CookiesDialog::CookiesDialog(std::list<QNetworkCookie> *originalCookieListPointe
 
         // Create the cookie items.
         QStandardItem *nameItemPointer = new QStandardItem(QString(cookie.name()));
-        QStandardItem *durableItemPointer = new QStandardItem(QString(isDurable ? i18n("yes") : i18n("no")));
-        QStandardItem *pathItemPointer = new QStandardItem(QString(cookie.path()));
-        QStandardItem *expirationDateItemPointer = new QStandardItem(QString(cookie.expirationDate().toString()));
-        QStandardItem *isHttpOnlyItemPointer = new QStandardItem(QString(cookie.isHttpOnly() ? i18n("yes") : i18n("no")));
-        QStandardItem *isSecureItemPointer = new QStandardItem(QString(cookie.isSecure() ? i18n("yes") : i18n("no")));
+        QStandardItem *durableItemPointer = new QStandardItem(isDurable ? i18n("yes") : i18n("no"));
+        QStandardItem *pathItemPointer = new QStandardItem(cookie.path());
+        QStandardItem *expirationDateItemPointer = new QStandardItem(cookie.expirationDate().toString());
+        QStandardItem *isHttpOnlyItemPointer = new QStandardItem(cookie.isHttpOnly() ? i18n("yes") : i18n("no"));
+        QStandardItem *isSecureItemPointer = new QStandardItem(cookie.isSecure() ? i18n("yes") : i18n("no"));
         QStandardItem *valueItemPointer = new QStandardItem(QString(cookie.value()));
 
         // Populate the cookie standard item list.
@@ -231,7 +231,7 @@ CookiesDialog::CookiesDialog(std::list<QNetworkCookie> *originalCookieListPointe
     // Don't elide the Value field (or any other field).
     treeViewPointer->setTextElideMode(Qt::ElideNone);
 
-    // Indicate that all the rows are the same height, wich improves performance.
+    // Indicate that all the rows are the same height, which improves performance.
     treeViewPointer->setUniformRowHeights(true);
 
     // Disable editing in the tree view.
@@ -259,7 +259,7 @@ CookiesDialog::CookiesDialog(std::list<QNetworkCookie> *originalCookieListPointe
                                                                     QDialogButtonBox::ActionRole);
 
     // Set the button icons.
-    durableCookiesButtonPointer->setIcon(QIcon::fromTheme("view-visible"));
+    durableCookiesButtonPointer->setIcon(QIcon::fromTheme("view-visible", QIcon::fromTheme(QLatin1String("appointment-new"))));
 
     // Connect the buttons.
     connect(addCookieButtonPointer, SIGNAL(clicked()), this, SLOT(showAddCookieDialog()));
@@ -594,10 +594,10 @@ void CookiesDialog::deleteCookieFromDialog(const QNetworkCookie &cookie) const
     emit deleteCookie(cookie);
 }
 
-void CookiesDialog::showAddCookieDialog() const
+void CookiesDialog::showAddCookieDialog()
 {
     // Instantiate an add cookie dialog.
-    QDialog *addCookieDialogPointer = new AddOrEditCookieDialog(AddOrEditCookieDialog::AddCookie);
+    QDialog *addCookieDialogPointer = new AddOrEditCookieDialog(this, AddOrEditCookieDialog::AddCookie);
 
     // Show the dialog.
     addCookieDialogPointer->show();
@@ -744,10 +744,10 @@ void CookiesDialog::showDeleteCookieMessageBox() const
     }
 }
 
-void CookiesDialog::showDurableCookiesDialog() const
+void CookiesDialog::showDurableCookiesDialog()
 {
     // Instantiate a durable cookies dialog.
-    QDialog *durableCookiesDialogPointer = new DurableCookiesDialog();
+    QDialog *durableCookiesDialogPointer = new DurableCookiesDialog(this);
 
     // Show the dialog.
     durableCookiesDialogPointer->show();
@@ -758,7 +758,7 @@ void CookiesDialog::showDurableCookiesDialog() const
     connect(durableCookiesDialogPointer, SIGNAL(updateParentUi()), this, SLOT(updateUi()));
 }
 
-void CookiesDialog::showEditCookieDialog() const
+void CookiesDialog::showEditCookieDialog()
 {
     // Get the current model index.
     QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
@@ -783,7 +783,7 @@ void CookiesDialog::showEditCookieDialog() const
     }
 
     // Instantiate an edit cookie dialog.
-    QDialog *editCookieDialogPointer = new AddOrEditCookieDialog(AddOrEditCookieDialog::EditCookie, &cookieToEdit, currentIndex.siblingAtColumn(1).data().toString() == i18n("yes"));
+    QDialog *editCookieDialogPointer = new AddOrEditCookieDialog(this, AddOrEditCookieDialog::EditCookie, &cookieToEdit, currentIndex.siblingAtColumn(1).data().toString() == i18n("yes"));
 
     // Show the dialog.
     editCookieDialogPointer->show();
index 36a7d722fc722cc6a25bbd50eaf981d4d2e67eb6..5319897e68bd5a2cb77c91d52873941961347347 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -50,11 +50,11 @@ private Q_SLOTS:
     void addCookieFromDialog(const QNetworkCookie &cookie, const bool &isDurable) const;
     void deleteCookieFromDatabase(const QNetworkCookie &cookie) const;
     void deleteCookieFromDialog(const QNetworkCookie &cookie) const;
-    void showAddCookieDialog() const;
+    void showAddCookieDialog();
     void showDeleteAllMessageBox() const;
     void showDeleteCookieMessageBox() const;
-    void showDurableCookiesDialog() const;
-    void showEditCookieDialog() const;
+    void showDurableCookiesDialog();
+    void showEditCookieDialog();
     void updateUi() const;
 
 private:
index 34ff70e592bf988a8bdc3eb8d69f617361983ad7..0907a18c184550cc73a59722f015456bc6d04fef 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
  *
  * You should have received a copy of the GNU General Public License
  * along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>.
- */
// */
 
 // Application headers.
 #include "DomainSettingsDialog.h"
 #include "Settings.h"
 #include "ui_DomainSettingsDialog.h"
 #include "databases/DomainsDatabase.h"
-#include "helpers/UserAgentHelper.h"
 
 // Qt toolkit headers.
 #include <QInputDialog>
 #include <QMessageBox>
 #include <QPushButton>
+#include <QTimer>
 
 // Define the public static int constants.
 const int DomainSettingsDialog::SHOW_ALL_DOMAINS = 0;
-const int DomainSettingsDialog::ADD_DOMAIN = 1;
-const int DomainSettingsDialog::EDIT_DOMAIN = 2;
+const int DomainSettingsDialog::EDIT_DOMAIN = 1;
 
 // Construct the class.
-DomainSettingsDialog::DomainSettingsDialog(const int &startType, const QString &domainName) : QDialog(nullptr)
+DomainSettingsDialog::DomainSettingsDialog(QWidget *parentWidgetPointer, const int &startType, const QString &domainName) : QDialog(parentWidgetPointer)
 {
     // Set the window title.
     setWindowTitle(i18nc("The domain settings dialog window title", "Domain Settings"));
 
     // Set the window modality.
-    setWindowModality(Qt::WindowModality::ApplicationModal);;
+    setWindowModality(Qt::WindowModality::ApplicationModal);
+
+    // Instantiate the user agent helper.
+    userAgentHelperPointer = new UserAgentHelper();
 
     // Instantiate the domain settings dialog UI.
     Ui::DomainSettingsDialog domainSettingsDialogUi;
@@ -124,17 +126,6 @@ DomainSettingsDialog::DomainSettingsDialog(const int &startType, const QString &
             // Select the first entry in the list view.
             domainsListViewPointer->setCurrentIndex(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabase::DOMAIN_NAME)));
 
-            // Populate the domain settings.
-            domainSelected(domainsSelectionModelPointer->currentIndex());
-
-            break;
-        }
-
-        case ADD_DOMAIN:
-        {
-            // Add the new domain.
-            addDomain(domainName);
-
             break;
         }
 
@@ -147,13 +138,13 @@ DomainSettingsDialog::DomainSettingsDialog(const int &startType, const QString &
             // Move to the new domain.
             domainsListViewPointer->setCurrentIndex(newDomainIndex[0]);
 
-            // Populate the domain settings.
-            domainSelected(domainsSelectionModelPointer->currentIndex());
-
             break;
         }
     }
 
+    // Populate the domain settings.
+    domainSelected(domainsSelectionModelPointer->currentIndex());
+
     // Handle clicks on the domains.
     connect(domainsListViewPointer, SIGNAL(activated(QModelIndex)), this, SLOT(domainSelected(QModelIndex)));
 
@@ -174,38 +165,8 @@ DomainSettingsDialog::DomainSettingsDialog(const int &startType, const QString &
     connect(applyButtonPointer, SIGNAL(clicked()), this, SLOT(apply()));
     connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(cancel()));
 
-    // Update the UI.
-    updateUi();
-}
-
-void DomainSettingsDialog::addDomain(const QString &domainName) const
-{
-    // Create a new domain record.
-    QSqlRecord newDomainRecord = QSqlDatabase::database(DomainsDatabase::CONNECTION_NAME).record(DomainsDatabase::DOMAINS_TABLE);
-
-    // Set the values for the new domain.
-    newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabase::DOMAIN_NAME), domainName);
-    newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabase::JAVASCRIPT), DomainsDatabase::SYSTEM_DEFAULT);
-    newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabase::DOM_STORAGE), DomainsDatabase::SYSTEM_DEFAULT);
-    newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabase::USER_AGENT), UserAgentHelper::SYSTEM_DEFAULT_DATABASE);
-    newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabase::ZOOM_FACTOR), DomainsDatabase::SYSTEM_DEFAULT);
-    newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabase::CUSTOM_ZOOM_FACTOR), 1.0);
-
-    // Insert the new domain.  `-1` appends it to the end.
-    domainsTableModelPointer->insertRecord(-1, newDomainRecord);
-
-    // Submit all pending changes.
-    domainsTableModelPointer->submitAll();
-
-    // Find the index for the new domain.  `-1` allows for multiple entries to be returned.
-    QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabase::DOMAIN_NAME)),
-                                                                     Qt::DisplayRole, domainName, -1, Qt::MatchWrap);
-
-    // Move to the new domain.  If there are multiple domains with the same name, the new one should be the last in the list.
-    domainsListViewPointer->setCurrentIndex(newDomainIndex[newDomainIndex.size() - 1]);
-
-    // Populate the domain settings.
-    domainSelected(domainsSelectionModelPointer->currentIndex());
+    // Update the DOM storage status.
+    updateDomStorageStatus();
 
     // Update the UI.
     updateUi();
@@ -217,13 +178,13 @@ void DomainSettingsDialog::apply() const
     QModelIndex currentIndex = domainsListViewPointer->currentIndex();
 
     // Get the ID of the current index row.
-    QVariant currentId = currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::_ID)).data();
+    QVariant currentId = currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::ID)).data();
 
     // Submit all pending changes.
     domainsTableModelPointer->submitAll();
 
     // Find the new index for the selected id.  The `1` keeps searching after the first match.
-    QModelIndexList newIndexList = domainsTableModelPointer->match(currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::_ID)), Qt::DisplayRole, currentId,
+    QModelIndexList newIndexList = domainsTableModelPointer->match(currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::ID)), Qt::DisplayRole, currentId,
                                                                    1, Qt::MatchWrap);
 
     // Select the new index.
@@ -312,11 +273,17 @@ void DomainSettingsDialog::domainSelected(const QModelIndex &modelIndex) const
     {
         // Display the default zoom factor.
         customZoomFactorSpinBoxPointer->setValue(Settings::zoomFactor());
+
+        // Use the default palette.
+        zoomFactorWidgetPointer->setPalette(defaultPalette);
     }
     else  // Custom zoom factor is selected.
     {
         // Display the custom zoom factor from the domain settings.
         customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::CUSTOM_ZOOM_FACTOR)).data().toDouble());
+
+        // Use the highlighted palette.
+        zoomFactorWidgetPointer->setPalette(highlightedPalette);
     }
 
     // Set the initial status of the custom zoom factor spin box.
@@ -340,6 +307,9 @@ void DomainSettingsDialog::javaScriptChanged(const int &newIndex) const
     // Populate the JavaScript label.
     populateJavaScriptLabel();
 
+    // Update the DOM storage status.
+    updateDomStorageStatus();
+
     // Update the UI.
     updateUi();
 }
@@ -349,9 +319,12 @@ void DomainSettingsDialog::localStorageChanged(const int &newIndex) const
     // Update the domains table model.
     domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::LOCAL_STORAGE)), newIndex);
 
-    // Poplate the local storage label.
+    // Populate the local storage label.
     populateLocalStorageLabel();
 
+    // Update the DOM storage status.
+    updateDomStorageStatus();
+
     // Update the UI.
     updateUi();
 }
@@ -362,7 +335,7 @@ void DomainSettingsDialog::ok()
     domainsTableModelPointer->submitAll();
 
     // Emit the domain settings updated signal.
-    domainSettingsUpdated();
+    emit domainSettingsUpdated();
 
     // Close the dialog.
     accept();
@@ -387,10 +360,10 @@ void DomainSettingsDialog::populateDomStorageLabel() const
             break;
         }
 
-        case (DomainsDatabase::DISABLED):
+        case (DomainsDatabase::ENABLED):
         {
-            // Set the disabled text in bold.
-            domStorageLabelPointer->setText(i18nc("Domain settings DOM storage label.  The <b> tags should be retained.", "<b>DOM storage disabled</b>"));
+            // Set the enabled text in bold.
+            domStorageLabelPointer->setText(i18nc("Domain settings DOM storage label.  The <b> tags should be retained.", "<b>DOM storage enabled</b>"));
 
             // Set the palette.
             domStorageWidgetPointer->setPalette(highlightedPalette);
@@ -398,10 +371,10 @@ void DomainSettingsDialog::populateDomStorageLabel() const
             break;
         }
 
-        case (DomainsDatabase::ENABLED):
+        case (DomainsDatabase::DISABLED):
         {
-            // Set the enabled text in bold.
-            domStorageLabelPointer->setText(i18nc("Domain settings DOM storage label.  The <b> tags should be retained.", "<b>DOM storage enabled</b>"));
+            // Set the disabled text in bold.
+            domStorageLabelPointer->setText(i18nc("Domain settings DOM storage label.  The <b> tags should be retained.", "<b>DOM storage disabled</b>"));
 
             // Set the palette.
             domStorageWidgetPointer->setPalette(highlightedPalette);
@@ -430,10 +403,10 @@ void DomainSettingsDialog::populateJavaScriptLabel() const
             break;
         }
 
-        case (DomainsDatabase::DISABLED):
+        case (DomainsDatabase::ENABLED):
         {
-            // Set the disabled text in bold.
-            javaScriptLabelPointer->setText(i18nc("Domain settings JavaScript label.  The <b> tags should be retained.", "<b>JavaScript disabled</b>"));
+            // Set the enabled text in bold.
+            javaScriptLabelPointer->setText(i18nc("Domain settings JavaScript label.  The <b> tags should be retained.", "<b>JavaScript enabled</b>"));
 
             // Set the palette.
             javaScriptWidgetPointer->setPalette(highlightedPalette);
@@ -441,10 +414,10 @@ void DomainSettingsDialog::populateJavaScriptLabel() const
             break;
         }
 
-        case (DomainsDatabase::ENABLED):
+        case (DomainsDatabase::DISABLED):
         {
-            // Set the enabled text in bold.
-            javaScriptLabelPointer->setText(i18nc("Domain settings JavaScript label.  The <b> tags should be retained.", "<b>JavaScript enabled</b>"));
+            // Set the disabled text in bold.
+            javaScriptLabelPointer->setText(i18nc("Domain settings JavaScript label.  The <b> tags should be retained.", "<b>JavaScript disabled</b>"));
 
             // Set the palette.
             javaScriptWidgetPointer->setPalette(highlightedPalette);
@@ -473,10 +446,10 @@ void DomainSettingsDialog::populateLocalStorageLabel() const
             break;
         }
 
-        case (DomainsDatabase::DISABLED):
+        case (DomainsDatabase::ENABLED):
         {
-            // Set the disabled text in bold.
-            localStorageLabelPointer->setText(i18nc("Domain settings local storage label.  The <b> tags should be retained.", "<b>Local storage disabled</b>"));
+            // Set the enabled text in bold.
+            localStorageLabelPointer->setText(i18nc("Domain settings local storage label.  The <b> tabs should be retained.", "<b>Local storage enabled</b>"));
 
             // Set the palette.
             localStorageWidgetPointer->setPalette(highlightedPalette);
@@ -484,10 +457,10 @@ void DomainSettingsDialog::populateLocalStorageLabel() const
             break;
         }
 
-        case (DomainsDatabase::ENABLED):
+        case (DomainsDatabase::DISABLED):
         {
-            // Set the enabled text in bold.
-            localStorageLabelPointer->setText(i18nc("Domain settings local storage label.  The <b> tabs should be retained.", "<b>Local storage enabled</b>"));
+            // Set the disabled text in bold.
+            localStorageLabelPointer->setText(i18nc("Domain settings local storage label.  The <b> tags should be retained.", "<b>Local storage disabled</b>"));
 
             // Set the palette.
             localStorageWidgetPointer->setPalette(highlightedPalette);
@@ -501,10 +474,10 @@ void DomainSettingsDialog::populateLocalStorageLabel() const
 void DomainSettingsDialog::populateUserAgentLabel(const QString &userAgentName) const
 {
     // Populate the label according to the type.
-    if (userAgentName == UserAgentHelper::SYSTEM_DEFAULT_TRANSLATED)
+    if (userAgentName == userAgentHelperPointer->SYSTEM_DEFAULT_TRANSLATED)
     {
         // Display the system default user agent name.
-        userAgentLabelPointer->setText(UserAgentHelper::getTranslatedUserAgentNameFromDatabaseName(Settings::userAgent()));
+        userAgentLabelPointer->setText(userAgentHelperPointer->getTranslatedUserAgentNameFromDatabaseName(Settings::userAgent()));
 
         // Reset the palette.
         userAgentWidgetPointer->setPalette(defaultPalette);
@@ -544,7 +517,31 @@ void DomainSettingsDialog::showAddMessageBox()
                                                   QLineEdit::Normal, QString(), &okClicked);
 
     // Add the new domain if the user clicked OK.
-    if (okClicked) addDomain(newDomainName);
+    if (okClicked)
+    {
+        // Add the new domain.
+        DomainsDatabase::addDomain(newDomainName);
+
+        // Submit all pending changes.  This reloads the model from the database, getting the new domain added above.
+        domainsTableModelPointer->submitAll();
+
+
+        // Find the index for the new domain.  `-1` allows for multiple entries to be returned.
+        QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabase::DOMAIN_NAME)),
+                                                                         Qt::DisplayRole, newDomainName, -1, Qt::MatchWrap);
+
+        // Move to the new domain.  If there are multiple domains with the same name, the new one should be the last in the list.
+        domainsListViewPointer->setCurrentIndex(newDomainIndex[newDomainIndex.size() - 1]);
+
+        // Populate the domain settings.
+        domainSelected(domainsSelectionModelPointer->currentIndex());
+
+        // Update the UI.
+        updateUi();
+
+        // Emit the domain settings updated signal.
+        emit domainSettingsUpdated();
+    }
 }
 
 void DomainSettingsDialog::showDeleteMessageBox() const
@@ -606,9 +603,84 @@ void DomainSettingsDialog::showDeleteMessageBox() const
 
         // Update the Ui.
         updateUi();
+
+        // Emit the domain settings updated signal.
+        emit domainSettingsUpdated();
     }
 }
 
+void DomainSettingsDialog::updateDomStorageStatus() const
+{
+    // Instantiate tracking variables.
+    bool javaScriptEnabled;
+    bool localStorageEnabled;
+
+    // Populate the JavaScript tracker.
+    switch (javaScriptComboBoxPointer->currentIndex())
+    {
+        case (DomainsDatabase::SYSTEM_DEFAULT):
+        {
+            // Update the tracker according to the system default.
+            if (Settings::javaScriptEnabled())
+                javaScriptEnabled = true;
+            else
+                javaScriptEnabled = false;
+
+            break;
+        }
+
+        case (DomainsDatabase::ENABLED):
+        {
+            // Update the tracker.
+            javaScriptEnabled = true;
+
+            break;
+        }
+
+        case (DomainsDatabase::DISABLED):
+        {
+            // Update the tracker.
+            javaScriptEnabled = false;
+
+            break;
+        }
+    }
+
+    // Populate the local storage tracker.
+    switch (localStorageComboBoxPointer->currentIndex())
+    {
+        case (DomainsDatabase::SYSTEM_DEFAULT):
+        {
+            // Update the tracker according to the system default.
+            if (Settings::localStorageEnabled())
+                localStorageEnabled = true;
+            else
+                localStorageEnabled = false;
+
+            break;
+        }
+
+        case (DomainsDatabase::ENABLED):
+        {
+            // Update the tracker.
+            localStorageEnabled = true;
+
+            break;
+        }
+
+        case (DomainsDatabase::DISABLED):
+        {
+            // Update the tracker.
+            localStorageEnabled = false;
+
+            break;
+        }
+    }
+
+    // Only enable DOM storage if both JavaScript and local storage are enabled.
+    domStorageComboBoxPointer->setEnabled(javaScriptEnabled && localStorageEnabled);
+}
+
 void DomainSettingsDialog::updateUi() const
 {
     // Update the delete button status.
@@ -628,7 +700,7 @@ void DomainSettingsDialog::userAgentChanged(const QString &updatedUserAgent) con
 {
     // Update the domains table model.
     domainsTableModelPointer->setData(domainsSelectionModelPointer->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::USER_AGENT)),
-                                      UserAgentHelper::getDatabaseUserAgentNameFromTranslatedName(updatedUserAgent));
+                                      userAgentHelperPointer->getDatabaseUserAgentNameFromTranslatedName(updatedUserAgent));
 
     // Populate the user agent label.
     populateUserAgentLabel(updatedUserAgent);
@@ -659,7 +731,7 @@ void DomainSettingsDialog::zoomFactorComboBoxChanged(const int &newIndex) const
         // Display the custom zoom factor from the domain settings.
         customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabase::CUSTOM_ZOOM_FACTOR)).data().toDouble());
 
-        // Set the palette.
+        // Use the highlighted palette.
         zoomFactorWidgetPointer->setPalette(highlightedPalette);
     }
 
index 7680a0323252b109ed27f78369837c11e3182af4..a78d6e3d3fd8468cecbeb43cd1d3d6c05d203c0f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022,2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -20,6 +20,9 @@
 #ifndef DOMAINSETTINGSDIALOG_H
 #define DOMAINSETTINGSDIALOG_H
 
+// Application headers.
+#include "helpers/UserAgentHelper.h"
+
 // KDE Frameworks headers.
 #include <KLineEdit>
 
@@ -36,7 +39,7 @@ class DomainSettingsDialog : public QDialog
 
 public:
     // The primary constructor.
-    explicit DomainSettingsDialog(const int &startType = SHOW_ALL_DOMAINS, const QString &domainName = QStringLiteral(""));
+    explicit DomainSettingsDialog(QWidget *parentWidgetPointer, const int &startType = SHOW_ALL_DOMAINS, const QString &domainName = QStringLiteral(""));
 
     // The public static int constants.
     static const int SHOW_ALL_DOMAINS;
@@ -87,16 +90,17 @@ private:
     QPushButton *resetButtonPointer;
     QWidget *userAgentWidgetPointer;
     QComboBox *userAgentComboBoxPointer;
+    UserAgentHelper *userAgentHelperPointer;
     QLabel *userAgentLabelPointer;
     QWidget *zoomFactorWidgetPointer;
     QComboBox *zoomFactorComboBoxPointer;
 
     // The private functions.
-    void addDomain(const QString &domainName) const;
     void populateDomStorageLabel() const;
     void populateJavaScriptLabel() const;
     void populateLocalStorageLabel() const;
     void populateUserAgentLabel(const QString &userAgentName) const;
+    void updateDomStorageStatus() const;
     void updateUi() const;
 };
 #endif
index d998c20ab840cae96de877f57dde2371caba5912..f07698597e62c61f70189ae830d0ed0549dedae7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -26,7 +26,7 @@
 // KDE Frameworks headers.
 #include <KLocalizedString>
 
-DurableCookiesDialog::DurableCookiesDialog() : QDialog(nullptr)
+DurableCookiesDialog::DurableCookiesDialog(QWidget *parentWidgetPointer) : QDialog(parentWidgetPointer)
 {
     // Set the dialog window title.
     setWindowTitle(i18nc("The durable cookies dialog window title", "Durable Cookies"));
@@ -150,7 +150,7 @@ void DurableCookiesDialog::beforeUpdate(int row, QSqlRecord &sqlRecord) const
     if (sqlRecord.isGenerated(CookiesDatabase::DOMAIN) || sqlRecord.isGenerated(CookiesDatabase::NAME) || sqlRecord.isGenerated(CookiesDatabase::PATH))
     {
         // Get the ID of the cookie
-        int id = sqlRecord.value(CookiesDatabase::_ID).toInt();
+        int id = sqlRecord.value(CookiesDatabase::ID).toInt();
 
         // Get the cookie.
         QNetworkCookie *cookiePointer = CookiesDatabase::getCookieById(id);
index 61ffe5fb202a0123bd39c3ea95d1d982bb802eb6..64294f820a0defcf0081b36092f936a6c090e899 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022, 2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -33,7 +33,7 @@ class DurableCookiesDialog : public QDialog
 
 public:
     // The default constructor.
-    explicit DurableCookiesDialog();
+    explicit DurableCookiesDialog(QWidget *parentWidgetPointer);
 
 signals:
     // The signals.
@@ -60,7 +60,7 @@ private:
     QPushButton *deleteAllCookiesButtonPointer;
     QPushButton *deleteCookieButtonPointer;
     QSqlTableModel *durableCookiesTableModelPointer;
-    QItemSelectionModel *tableSelectionModelPointer;
     QPushButton *resetButtonPointer;
+    QItemSelectionModel *tableSelectionModelPointer;
 };
 #endif
diff --git a/src/dialogs/EditBookmarkDialog.cpp b/src/dialogs/EditBookmarkDialog.cpp
new file mode 100644 (file)
index 0000000..bad010e
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "EditBookmarkDialog.h"
+#include "ui_EditBookmarkDialog.h"
+#include "databases/BookmarksDatabase.h"
+
+// Qt toolkit headers.
+#include <QFileDialog>
+
+// Construct the class.
+EditBookmarkDialog::EditBookmarkDialog(QWidget *parentWidgetPointer, const int databaseId, QIcon &currentWebsiteFavoriteIcon) : QDialog(parentWidgetPointer), bookmarkDatabaseId(databaseId)
+{
+    // Set the window title.
+    setWindowTitle(i18nc("The edit bookmark dialog window title.", "Edit Bookmark"));
+
+    // Set the window modality.
+    setWindowModality(Qt::WindowModality::ApplicationModal);
+
+    // Instantiate the edit bookmark dialog UI.
+    Ui::EditBookmarkDialog editBookmarkDialogUi;
+
+    // Setup the UI.
+    editBookmarkDialogUi.setupUi(this);
+
+    // Get handles for the widgets.
+    currentFavoriteIconRadioButtonPointer = editBookmarkDialogUi.currentFavoriteIconRadioButton;
+    currentWebsiteFavoriteIconRadioButtonPointer = editBookmarkDialogUi.currentWebsiteFavoriteIconRadioButton;
+    customFavoriteIconRadioButtonPointer = editBookmarkDialogUi.customFavoriteIconRadioButton;
+    parentFolderTreeWidgetPointer = editBookmarkDialogUi.parentFolderTreeWidget;
+    bookmarkNameLineEditPointer = editBookmarkDialogUi.bookmarkNameLineEdit;
+    bookmarkUrlLineEditPointer = editBookmarkDialogUi.bookmarkUrlLineEdit;
+    QPushButton *browseButtonPointer = editBookmarkDialogUi.browseButton;
+    QDialogButtonBox *dialogButtonBoxPointer = editBookmarkDialogUi.dialogButtonBox;
+    saveButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::Save);
+
+    // Get the bookmark struct.
+    bookmarkStructPointer = BookmarksDatabase::getBookmark(databaseId);
+
+    // Set the favorite icons.
+    currentFavoriteIconRadioButtonPointer->setIcon(bookmarkStructPointer->favoriteIcon);
+    currentWebsiteFavoriteIconRadioButtonPointer->setIcon(currentWebsiteFavoriteIcon);
+    customFavoriteIconRadioButtonPointer->setIcon(QIcon::fromTheme(QLatin1String("globe"), QIcon::fromTheme(QLatin1String("applications-internet"))));
+
+    // Instantiate a folder helper.
+    folderHelperPointer = new FolderHelper();
+
+    // Set the parent folder tree widget column count.
+    parentFolderTreeWidgetPointer->setColumnCount(2);
+
+    // Hide the second column.
+    parentFolderTreeWidgetPointer->hideColumn(folderHelperPointer->FOLDER_ID_COLUMN);
+
+    // Set the column header.
+    parentFolderTreeWidgetPointer->setHeaderLabel(i18nc("The folder tree widget header", "Select Parent Folder"));
+
+    // Create a bookmarks tree widget item.
+    QTreeWidgetItem *bookmarksTreeWidgetItemPointer = new QTreeWidgetItem();
+
+    // Populate the bookmarks tree widget item.
+    bookmarksTreeWidgetItemPointer->setText(folderHelperPointer->FOLDER_NAME_COLUMN, i18nc("The bookmarks root tree widget name", "Bookmarks"));
+    bookmarksTreeWidgetItemPointer->setIcon(folderHelperPointer->FOLDER_NAME_COLUMN, QIcon::fromTheme(QLatin1String("bookmarks"), QIcon::fromTheme(QLatin1String("bookmark-new"))));
+    bookmarksTreeWidgetItemPointer->setText(folderHelperPointer->FOLDER_ID_COLUMN, QLatin1String("0"));
+
+    // Add the bookmarks tree widget item to the root of the tree.
+    parentFolderTreeWidgetPointer->addTopLevelItem(bookmarksTreeWidgetItemPointer);
+
+    // Select the root bookmarks folder if it is the initial parent folder.
+    if (bookmarkStructPointer->parentFolderId == 0)
+        bookmarksTreeWidgetItemPointer->setSelected(true);
+
+    // Populate the subfolders.
+    folderHelperPointer->populateSubfolders(bookmarksTreeWidgetItemPointer, bookmarkStructPointer->parentFolderId);
+
+    // Open all the folders.
+    parentFolderTreeWidgetPointer->expandAll();
+
+    // Populate the line edits.
+    bookmarkNameLineEditPointer->setText(bookmarkStructPointer->name);
+    bookmarkUrlLineEditPointer->setText(bookmarkStructPointer->url);
+
+    // Scroll to the beginning of the bookmark URL line edit.
+    bookmarkUrlLineEditPointer->setCursorPosition(0);
+
+    // Focus the bookmark name line edit.
+    bookmarkNameLineEditPointer->setFocus();
+
+    // Connect the buttons.
+    connect(browseButtonPointer, SIGNAL(clicked()), this, SLOT(browse()));
+    connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(save()));
+    connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(reject()));
+
+    // Update the UI when the line edits change.
+    connect(bookmarkNameLineEditPointer, SIGNAL(textEdited(const QString&)), this, SLOT(updateUi()));
+    connect(bookmarkUrlLineEditPointer, SIGNAL(textEdited(const QString&)), this, SLOT(updateUi()));
+
+    // Set the initial UI status.
+    updateUi();
+}
+
+void EditBookmarkDialog::browse()
+{
+    // Get an image file string from the user.
+    QString imageFileString = QFileDialog::getOpenFileName(this, i18nc("The browse for favorite icon dialog header", "Favorite Icon Image"), QDir::homePath(),
+                              i18nc("The browse for image files filter", "Image Files — *.bmp, *.gif, *.jpg, *.jpeg, *.png, *.svg(*.bmp *.gif *.jpg *.jpeg *.png *.svg);;All Files(*)"));
+
+
+    // Check to see if an image file string was returned.  This will be empty if the user selected cancel.
+    if (!imageFileString.isEmpty())
+    {
+        // Set the custom favorite icon.
+        customFavoriteIconRadioButtonPointer->setIcon(QIcon(imageFileString));
+
+        // Check the custom favorite icon radio button.
+        customFavoriteIconRadioButtonPointer->setChecked(true);
+    }
+}
+
+void EditBookmarkDialog::save()
+{
+    // Get the selected folders list.
+    QList<QTreeWidgetItem*> selectedFoldersList = parentFolderTreeWidgetPointer->selectedItems();
+
+    // Get the selected folder.
+    QTreeWidgetItem *selectedFolderPointer = selectedFoldersList.first();
+
+    // Get the parent folder ID.
+    double parentFolderId = selectedFolderPointer->text(folderHelperPointer->FOLDER_ID_COLUMN).toDouble();
+
+    // Get the original display order.
+    int displayOrder = bookmarkStructPointer->displayOrder;
+
+    // Get the new display order if the parent folder has changed.
+    if (parentFolderId != bookmarkStructPointer->parentFolderId)
+        displayOrder = BookmarksDatabase::getFolderItemCount(parentFolderId);
+
+    // Create a favorite icon.
+    QIcon favoriteIcon;
+
+    // Get the favorite icon.
+    if (currentFavoriteIconRadioButtonPointer->isChecked())  // The current favorite icon is checked.
+        favoriteIcon = currentFavoriteIconRadioButtonPointer->icon();
+    else if (currentWebsiteFavoriteIconRadioButtonPointer->isChecked())  // The current website favorite icon is checked.
+        favoriteIcon = currentWebsiteFavoriteIconRadioButtonPointer->icon();
+    else  // The custom favorite icon is checked.
+        favoriteIcon = customFavoriteIconRadioButtonPointer->icon();
+
+    // Create a bookmark struct.
+    BookmarkStruct *updatedBookmarkStructPointer = new BookmarkStruct;
+
+    // Populate the bookmark struct.
+    updatedBookmarkStructPointer->databaseId = bookmarkDatabaseId;
+    updatedBookmarkStructPointer->name = bookmarkNameLineEditPointer->text();
+    updatedBookmarkStructPointer->url = bookmarkUrlLineEditPointer->text();
+    updatedBookmarkStructPointer->parentFolderId = parentFolderId;
+    updatedBookmarkStructPointer->displayOrder = displayOrder;
+    updatedBookmarkStructPointer->favoriteIcon = favoriteIcon;
+
+    // Update the bookmark.
+    BookmarksDatabase::updateBookmark(updatedBookmarkStructPointer);
+
+    // Update the display order of all the items in the previous folder.
+    BookmarksDatabase::updateFolderContentsDisplayOrder(bookmarkStructPointer->parentFolderId);
+
+    // Emit the bookmark saved signal.
+    emit bookmarkSaved();
+
+    // Close the dialog.
+    close();
+}
+
+void EditBookmarkDialog::updateUi()
+{
+    // Determine if both line edits are populated.
+    if (bookmarkNameLineEditPointer->text().isEmpty() || bookmarkUrlLineEditPointer->text().isEmpty())  // At least one of the line edits is empty.
+    {
+        // Disable the save button.
+        saveButtonPointer->setEnabled(false);
+    }
+    else  // Both of the line edits are populated.
+    {
+        // Enable the save button.
+        saveButtonPointer->setEnabled(true);
+    }
+}
diff --git a/src/dialogs/EditBookmarkDialog.h b/src/dialogs/EditBookmarkDialog.h
new file mode 100644 (file)
index 0000000..f862bc0
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EDITBOOKMARKDIALOG_H
+#define EDITBOOKMARKDIALOG_H
+
+// Application headers.
+#include "helpers/FolderHelper.h"
+#include "structs/BookmarkStruct.h"
+
+// Qt toolkit headers.
+#include <QDialog>
+#include <QLineEdit>
+#include <QRadioButton>
+
+class EditBookmarkDialog : public QDialog
+{
+    // Include the Q_OBJECT macro.
+    Q_OBJECT
+
+public:
+    // The primary constructor.
+    explicit EditBookmarkDialog(QWidget *parentWidgetPointer, const int databaseId, QIcon &currentWebsiteFavoriteIcon);
+
+signals:
+    // The signals.
+    void bookmarkSaved() const;
+
+private Q_SLOTS:
+    // The private slots.
+    void browse();
+    void save();
+    void updateUi();
+
+private:
+    // The private variables.
+    FolderHelper *folderHelperPointer;
+
+    // The private widgets.
+    int bookmarkDatabaseId;
+    QLineEdit *bookmarkNameLineEditPointer;
+    BookmarkStruct *bookmarkStructPointer;
+    QLineEdit *bookmarkUrlLineEditPointer;
+    QRadioButton *currentFavoriteIconRadioButtonPointer;
+    QRadioButton *currentWebsiteFavoriteIconRadioButtonPointer;
+    QRadioButton *customFavoriteIconRadioButtonPointer;
+    QTreeWidget *parentFolderTreeWidgetPointer;
+    QPushButton *saveButtonPointer;
+};
+#endif
diff --git a/src/dialogs/EditFolderDialog.cpp b/src/dialogs/EditFolderDialog.cpp
new file mode 100644 (file)
index 0000000..bae0965
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "EditFolderDialog.h"
+#include "ui_EditFolderDialog.h"
+#include "databases/BookmarksDatabase.h"
+
+// Qt toolkit headers.
+#include <QFileDialog>
+
+// Construct the class.
+EditFolderDialog::EditFolderDialog(QWidget *parentWidgetPointer, const int databaseId, QIcon &currentWebsiteFavoriteIcon) : QDialog(parentWidgetPointer), folderDatabaseId(databaseId)
+{
+    // Set the window title.
+    setWindowTitle(i18nc("The edit folder dialog window title.", "Edit Folder"));
+
+    // Set the window modality.
+    setWindowModality(Qt::WindowModality::ApplicationModal);
+
+    // Instantiate the edit folder dialog UI.
+    Ui::EditFolderDialog editFolderDialogUi;
+
+    // Setup the UI.
+    editFolderDialogUi.setupUi(this);
+
+    // Get handles for the widgets.
+    currentFolderIconRadioButtonPointer = editFolderDialogUi.currentFolderIconRadioButton;
+    defaultFolderIconRadioButtonPointer = editFolderDialogUi.defaultFolderIconRadioButton;
+    currentWebsiteFavoriteIconRadioButtonPointer = editFolderDialogUi.currentWebsiteFavoriteIconRadioButton;
+    customFolderIconRadioButtonPointer = editFolderDialogUi.customFolderIconRadioButton;
+    parentFolderTreeWidgetPointer = editFolderDialogUi.parentFolderTreeWidget;
+    folderNameLineEditPointer = editFolderDialogUi.folderNameLineEdit;
+    QPushButton *browseButtonPointer = editFolderDialogUi.browseButton;
+    QDialogButtonBox *dialogButtonBoxPointer = editFolderDialogUi.dialogButtonBox;
+    saveButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::Save);
+
+    // Get the folder bookmark struct.
+    folderBookmarkStructPointer = BookmarksDatabase::getBookmark(databaseId);
+
+    // Set the folder icons.
+    currentFolderIconRadioButtonPointer->setIcon(folderBookmarkStructPointer->favoriteIcon);
+    currentWebsiteFavoriteIconRadioButtonPointer->setIcon(currentWebsiteFavoriteIcon);
+
+    // Instantiate a folder helper.
+    folderHelperPointer = new FolderHelper();
+
+    // Set the parent folder tree widget column count.
+    parentFolderTreeWidgetPointer->setColumnCount(2);
+
+    // Hide the second column.
+    parentFolderTreeWidgetPointer->hideColumn(folderHelperPointer->FOLDER_ID_COLUMN);
+
+    // Set the column header.
+    parentFolderTreeWidgetPointer->setHeaderLabel(i18nc("The folder tree widget header", "Select Parent Folder"));
+
+    // Create a bookmarks tree widget item.
+    QTreeWidgetItem *bookmarksTreeWidgetItemPointer = new QTreeWidgetItem();
+
+    // Populate the bookmarks tree widget item.
+    bookmarksTreeWidgetItemPointer->setText(folderHelperPointer->FOLDER_NAME_COLUMN, i18nc("The bookmarks root tree widget name", "Bookmarks"));
+    bookmarksTreeWidgetItemPointer->setIcon(folderHelperPointer->FOLDER_NAME_COLUMN, QIcon::fromTheme(QLatin1String("bookmarks"), QIcon::fromTheme(QLatin1String("bookmark-new"))));
+    bookmarksTreeWidgetItemPointer->setText(folderHelperPointer->FOLDER_ID_COLUMN, QLatin1String("0"));
+
+    // Add the bookmarks tree widget item to the root of the tree.
+    parentFolderTreeWidgetPointer->addTopLevelItem(bookmarksTreeWidgetItemPointer);
+
+    // Select the root bookmarks folder if it is the initial parent folder.
+    if (folderBookmarkStructPointer->parentFolderId == 0)
+        bookmarksTreeWidgetItemPointer->setSelected(true);
+
+    // Populate the subfolders, except for the one being edited.
+    folderHelperPointer->populateSubfoldersExcept(databaseId, bookmarksTreeWidgetItemPointer, folderBookmarkStructPointer->parentFolderId);
+
+    // Open all the folders.
+    parentFolderTreeWidgetPointer->expandAll();
+
+    // Populate the line edits.
+    folderNameLineEditPointer->setText(folderBookmarkStructPointer->name);
+
+    // Focus the folder name line edit.
+    folderNameLineEditPointer->setFocus();
+
+    // Connect the buttons.
+    connect(browseButtonPointer, SIGNAL(clicked()), this, SLOT(browse()));
+    connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(save()));
+    connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(reject()));
+
+    // Update the UI when the line edit changes.
+    connect(folderNameLineEditPointer, SIGNAL(textEdited(const QString&)), this, SLOT(updateUi()));
+
+    // Set the initial UI status.
+    updateUi();
+}
+
+void EditFolderDialog::browse()
+{
+    // Get an image file string from the user.
+    QString imageFileString = QFileDialog::getOpenFileName(this, i18nc("The browse for folder icon dialog header", "Folder Icon Image"), QDir::homePath(),
+                              i18nc("The browse for image files filter", "Image Files — *.bmp, *.gif, *.jpg, *.jpeg, *.png, *.svg(*.bmp *.gif *.jpg *.jpeg *.png *.svg);;All Files(*)"));
+
+    // Check to see if an image file string was returned.  This will be empty if the user selected cancel.
+    if (!imageFileString.isEmpty())
+    {
+        // Set the custom folder icon.
+        customFolderIconRadioButtonPointer->setIcon(QIcon(imageFileString));
+
+        // Check the custom folder icon radio button.
+        customFolderIconRadioButtonPointer->setChecked(true);
+    }
+}
+
+void EditFolderDialog::save()
+{
+    // Get the selected folders list.
+    QList<QTreeWidgetItem*> selectedFoldersList = parentFolderTreeWidgetPointer->selectedItems();
+
+    // Get the selected folder.
+    QTreeWidgetItem *selectedFolderPointer = selectedFoldersList.first();
+
+    // Get the parent folder ID.
+    double parentFolderId = selectedFolderPointer->text(folderHelperPointer->FOLDER_ID_COLUMN).toDouble();
+
+    // Determine if it has moved to a new folder.
+    bool movedToNewFolder =  parentFolderId != folderBookmarkStructPointer->parentFolderId;
+
+    // Get the original display order.
+    int displayOrder = folderBookmarkStructPointer->displayOrder;
+
+    // Get the new display order if the parent folder has changed.
+    if (movedToNewFolder)
+        displayOrder = BookmarksDatabase::getFolderItemCount(parentFolderId);
+
+    // Create a favorite icon.
+    QIcon favoriteIcon;
+
+    // Get the favorite icon.
+    if (currentFolderIconRadioButtonPointer->isChecked())  // The current folder icon is checked.
+        favoriteIcon = currentFolderIconRadioButtonPointer->icon();
+    else if (defaultFolderIconRadioButtonPointer->isChecked())  // The default folder icon is checked.
+        favoriteIcon = defaultFolderIconRadioButtonPointer->icon();
+    else if (currentWebsiteFavoriteIconRadioButtonPointer->isChecked())  // The current website favorite icon is checked.
+        favoriteIcon = currentWebsiteFavoriteIconRadioButtonPointer->icon();
+    else  // The custom favorite icon is checked.
+        favoriteIcon = customFolderIconRadioButtonPointer->icon();
+
+    // Create a bookmark struct.
+    BookmarkStruct *updatedBookmarkStructPointer = new BookmarkStruct;
+
+    // Populate the bookmark struct.
+    updatedBookmarkStructPointer->databaseId = folderDatabaseId;
+    updatedBookmarkStructPointer->name = folderNameLineEditPointer->text();
+    updatedBookmarkStructPointer->parentFolderId = parentFolderId;
+    updatedBookmarkStructPointer->displayOrder = displayOrder;
+    updatedBookmarkStructPointer->favoriteIcon = favoriteIcon;
+
+    // Update the folder.
+    BookmarksDatabase::updateBookmark(updatedBookmarkStructPointer);
+
+    // Update the display order of all the items in the previous folder if it has moved to a new folder.
+    if (movedToNewFolder)
+        BookmarksDatabase::updateFolderContentsDisplayOrder(folderBookmarkStructPointer->parentFolderId);
+
+    // Emit the folder saved signal.
+    emit folderSaved();
+
+    // Close the dialog.
+    close();
+}
+
+void EditFolderDialog::updateUi()
+{
+    // Set the status of the save button.
+    saveButtonPointer->setEnabled(!folderNameLineEditPointer->text().isEmpty());
+}
diff --git a/src/dialogs/EditFolderDialog.h b/src/dialogs/EditFolderDialog.h
new file mode 100644 (file)
index 0000000..899d5af
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EDITFOLDERDIALOG_H
+#define EDITFOLDERDIALOG_H
+
+// Application headers.
+#include "helpers/FolderHelper.h"
+#include "structs/BookmarkStruct.h"
+
+// Qt toolkit headers.
+#include <QDialog>
+#include <QRadioButton>
+
+class EditFolderDialog : public QDialog
+{
+    // Include the Q_OBJECT macro.
+    Q_OBJECT
+
+public:
+    // The primary constructor.
+    explicit EditFolderDialog(QWidget *parentWidgetPointer, const int databaseId, QIcon &currentWebsiteFavoriteIcon);
+
+signals:
+    // The signals.
+    void folderSaved() const;
+
+private Q_SLOTS:
+    // The private slots.
+    void browse();
+    void save();
+    void updateUi();
+
+private:
+    // The private variables.
+    FolderHelper *folderHelperPointer;
+
+    // The private widgets.
+    QRadioButton *currentFolderIconRadioButtonPointer;
+    QRadioButton *currentWebsiteFavoriteIconRadioButtonPointer;
+    QRadioButton *customFolderIconRadioButtonPointer;
+    QRadioButton *defaultFolderIconRadioButtonPointer;
+    BookmarkStruct *folderBookmarkStructPointer;
+    int folderDatabaseId;
+    QLineEdit *folderNameLineEditPointer;
+    QTreeWidget *parentFolderTreeWidgetPointer;
+    QPushButton *saveButtonPointer;
+};
+#endif
diff --git a/src/dialogs/HttpAuthenticationDialog.cpp b/src/dialogs/HttpAuthenticationDialog.cpp
new file mode 100644 (file)
index 0000000..650ce99
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "HttpAuthenticationDialog.h"
+#include "ui_HttpAuthenticationDialog.h"
+
+// Qt toolkit headers.
+#include <QPushButton>
+#include <QUrl>
+
+// Construct the class.
+HttpAuthenticationDialog::HttpAuthenticationDialog(QWidget *parentWidgetPointer, const QUrl &requestUrl, QAuthenticator *authenticatorPointer) :
+                                                   QDialog(parentWidgetPointer), authenticatorPointer(authenticatorPointer)
+{
+    // Set the window title.
+    setWindowTitle(i18nc("The HTTP authentication dialog window title.", "HTTP Authentication"));
+
+    // Set the window modality.
+    setWindowModality(Qt::WindowModality::ApplicationModal);
+
+    // Instantiate the HTTP authentication dialog UI.
+    Ui::HttpAuthenticationDialog httpAuthenticationDialogUi;
+
+    // Setup the UI.
+    httpAuthenticationDialogUi.setupUi(this);
+
+    // Get handles for the widgets.
+    QLabel *realmLabelPointer = httpAuthenticationDialogUi.realmLabel;
+    QLabel *hostLabelPointer = httpAuthenticationDialogUi.hostLabel;
+    usernameLineEditPointer = httpAuthenticationDialogUi.usernameLineEdit;
+    passwordLineEditPointer = httpAuthenticationDialogUi.passwordLineEdit;
+    QDialogButtonBox *dialogButtonBoxPointer = httpAuthenticationDialogUi.dialogButtonBox;
+    okButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::Ok);
+
+    // Display the labels.
+    realmLabelPointer->setText(authenticatorPointer->realm());
+    hostLabelPointer->setText(requestUrl.host());
+
+    // Connect the buttons.
+    connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(authenticate()));
+    connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(reject()));
+
+    // Update the UI when the line edits change.
+    connect(usernameLineEditPointer, SIGNAL(textEdited(const QString&)), this, SLOT(updateUi()));
+    connect(passwordLineEditPointer, SIGNAL(passwordChanged(const QString&)), this, SLOT(updateUi()));
+
+    // Initially disable the OK button.
+    okButtonPointer->setEnabled(false);
+}
+
+void HttpAuthenticationDialog::authenticate()
+{
+    // Populate the authenticator.
+    authenticatorPointer->setUser(usernameLineEditPointer->text());
+    authenticatorPointer->setPassword(passwordLineEditPointer->password());
+
+    // Close the dialog.
+    close();
+}
+
+void HttpAuthenticationDialog::updateUi()
+{
+    // Update the OK button status
+    if (usernameLineEditPointer->text().isEmpty() || passwordLineEditPointer->password().isEmpty())  // At least one of the line edits is empty.
+    {
+        // Disable the OK button.
+        okButtonPointer->setEnabled(false);
+    }
+    else  // Both of the line edits are populated.
+    {
+        // Enable the OK button.
+        okButtonPointer->setEnabled(true);
+    }
+}
diff --git a/src/dialogs/HttpAuthenticationDialog.h b/src/dialogs/HttpAuthenticationDialog.h
new file mode 100644 (file)
index 0000000..201067d
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HTTPAUTHENTICATIONDIALOG_H
+#define HTTPAUTHENTICATIONDIALOG_H
+
+// KDE framework headers.
+#include <KLineEdit>
+#include <KPasswordLineEdit>
+
+// Qt toolkit headers.
+#include <QAuthenticator>
+#include <QDialog>
+
+class HttpAuthenticationDialog : public QDialog
+{
+    // Include the Q_OBJECT macro.
+    Q_OBJECT
+
+public:
+    // The primary constructor.
+    explicit HttpAuthenticationDialog(QWidget *parentWidgetPointer, const QUrl &requestUrl, QAuthenticator *authenticatorPointer);
+
+private Q_SLOTS:
+    // The private slots.
+    void authenticate();
+    void updateUi();
+
+private:
+    // The private variables.
+    QAuthenticator *authenticatorPointer;
+
+    // The private widgets.
+    QPushButton *okButtonPointer;
+    KPasswordLineEdit *passwordLineEditPointer;
+    KLineEdit *usernameLineEditPointer;
+};
+#endif
index 281cc77c79a4c4ff97e606e4ce50402159c38242..dac011c86fe25d2a07dbb7dc2e015cb69b634202 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -30,7 +30,8 @@
 #include <QShortcut>
 #include <QStandardPaths>
 
-SaveDialog::SaveDialog(QWebEngineDownloadItem *downloadItemPointer)
+SaveDialog::SaveDialog(QWidget *parentWidgetPointer, QUrl &url, QString &mimeTypeString, int totalBytes, QString fileName, bool nativeDownloader) :
+                       QDialog(parentWidgetPointer), downloadUrl(url), suggestedFileName(fileName)
 {
     // Set the dialog window title.
     setWindowTitle(i18nc("The save dialog window title", "Save"));
@@ -53,11 +54,6 @@ SaveDialog::SaveDialog(QWebEngineDownloadItem *downloadItemPointer)
     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 mimeTypeString = downloadItemPointer->mimeType();
-
     // Get a MIME type database.
     QMimeDatabase mimeDatabase;
 
@@ -85,28 +81,40 @@ SaveDialog::SaveDialog(QWebEngineDownloadItem *downloadItemPointer)
     mimeTypeLabelPointer->setText("<b>" + mimeTypeString + "</b>");
 
     // Populate the download size label.
-    if (downloadItemPointer->totalBytes() == -1)  // The file size is unknown.
+    if (totalBytes == -1)  // The file size is unknown.
         sizeLabelPointer->setText(i18nc("Unknown download file size.  The bold style should be preserved.", "<b>unknown</b>"));
     else  // The file size is known.  Format it according to the locale.
-        sizeLabelPointer->setText(ki18nc("Download file size.  The bold style should be preserved.", "<b>%1 bytes</b>").subs(downloadItemPointer->totalBytes()).toString());
-
-    // Connect the buttons.
-    connect(saveButtonPointer, SIGNAL(clicked()), this, SLOT(showFileDialog()));
-    connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(reject()));
+        sizeLabelPointer->setText(ki18nc("Download file size.  The bold style should be preserved.", "<b>%1 bytes</b>").subs(totalBytes).toString());
 
     // 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()));
+    QShortcut *quitShortcutPointer = new QShortcut(QKeySequence::Quit, this);
+
+    // Connect the save buttons.
+    if (nativeDownloader)
+    {
+        // Show the file picker for the native download.
+        connect(saveButtonPointer, SIGNAL(clicked()), this, SLOT(showFilePicker()));
+        connect(sShortcutPointer, SIGNAL(activated()), this, SLOT(showFilePicker()));
+    }
+    else
+    {
+        // Use WebEngine's downloader.
+        connect(saveButtonPointer, SIGNAL(clicked()), this, SLOT(accept()));
+        connect(sShortcutPointer, SIGNAL(activated()), this, SLOT(accept()));
+    }
+
+    // Connect the cancel button.
+    connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(reject()));
     connect(cShortcutPointer, SIGNAL(activated()), this, SLOT(reject()));
+    connect(quitShortcutPointer, SIGNAL(activated()), this, SLOT(reject()));
 }
 
-void SaveDialog::showFileDialog()
+void SaveDialog::showFilePicker()
 {
     // Show the file picker dialog.
-    emit showSaveFilePickerDialog(downloadUrl, suggestedFileName);
+    emit useNativeKdeDownloader(downloadUrl, suggestedFileName);
 
     // Close the dialog.
     reject();
index 36354c190a4c84b4ea135629ddc76744d49d4bb2..a994d18e69d62ac0a75622ece363660e32b7c98f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022, 2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -32,15 +32,15 @@ class SaveDialog : public QDialog
 
 public:
     // The primary constructor.
-    explicit SaveDialog(QWebEngineDownloadItem *downloadItemPointer);
+    explicit SaveDialog(QWidget *parentWidgetPointer, QUrl &url, QString &mimeTypeString, int totalBytes, QString fileName = QString(), bool nativeDownloader = false);
 
 signals:
     // The signals.
-    void showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName);
+    void useNativeKdeDownloader(QUrl &downloadUrl, QString &suggestedFileName);
 
 private Q_SLOTS:
     // The private slots.
-    void showFileDialog();
+    void showFilePicker();
 
 private:
     // The private variables.
diff --git a/src/dialogs/SettingsDialog.cpp b/src/dialogs/SettingsDialog.cpp
new file mode 100644 (file)
index 0000000..1f3fb79
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "Settings.h"
+#include "SettingsDialog.h"
+#include "helpers/SearchEngineHelper.h"
+#include "helpers/UserAgentHelper.h"
+#include "ui_SettingsGeneral.h"
+#include "ui_SettingsPrivacy.h"
+#include "ui_SettingsSpellCheck.h"
+
+// Qt toolkit headers.
+#include <QDir>
+#include <QFileDialog>
+
+SettingsDialog::SettingsDialog(QWidget *parentWidgetPointer, KCoreConfigSkeleton *coreConfigSkeletonPointer) :
+                               KConfigDialog(parentWidgetPointer, QLatin1String("settings"), coreConfigSkeletonPointer)
+{
+    // Instantiate the settings UI.
+    Ui::PrivacySettings privacySettingsUi;
+    Ui::GeneralSettings generalSettingsUi;
+    Ui::SpellCheckSettings spellCheckSettingsUi;
+
+    // Create the settings widgets.
+    QWidget *privacySettingsWidgetPointer = new QWidget;
+    QWidget *generalSettingsWidgetPointer = new QWidget;
+    QWidget *spellCheckSettingsWidgetPointer = new QWidget;
+
+    // Setup the UI to display the settings widgets.
+    privacySettingsUi.setupUi(privacySettingsWidgetPointer);
+    generalSettingsUi.setupUi(generalSettingsWidgetPointer);
+    spellCheckSettingsUi.setupUi(spellCheckSettingsWidgetPointer);
+
+    // Get handles for the widgets.
+    QCheckBox *javaScriptCheckBoxPointer = privacySettingsUi.kcfg_javaScriptEnabled;
+    QCheckBox *localStorageCheckBoxPointer = privacySettingsUi.kcfg_localStorageEnabled;
+    QCheckBox *domStorageCheckBoxPointer = privacySettingsUi.kcfg_domStorageEnabled;
+    QComboBox *userAgentComboBoxPointer = privacySettingsUi.kcfg_userAgent;
+    userAgentLabelPointer = privacySettingsUi.userAgentLabel;
+    QComboBox *searchEngineComboBoxPointer = generalSettingsUi.kcfg_searchEngine;
+    searchEngineLabelPointer = generalSettingsUi.searchEngineLabel;
+    downloadDirectoryComboBoxPointer = generalSettingsUi.kcfg_downloadDirectory;
+    QPushButton *browseButtonPointer = generalSettingsUi.browseButton;
+    QListWidget *spellCheckListWidgetPointer = spellCheckSettingsUi.spellCheckListWidget;
+
+    // Create a save spell check languages lambda.
+    auto updateCheckBoxes = [javaScriptCheckBoxPointer, localStorageCheckBoxPointer, domStorageCheckBoxPointer] ()
+    {
+        // Only enable the DOM storage check box if both JavaScript and local storage are checked.
+        domStorageCheckBoxPointer->setEnabled(javaScriptCheckBoxPointer->isChecked() && localStorageCheckBoxPointer->isChecked());
+    };
+
+    // Update the status of the DOM storage check box when either JavaScript or local storage are changed.
+    connect(javaScriptCheckBoxPointer, &QCheckBox::stateChanged, this, updateCheckBoxes);
+    connect(localStorageCheckBoxPointer, &QCheckBox::stateChanged, this, updateCheckBoxes);
+
+    // Populate the combo box labels.
+    updateUserAgentLabel(userAgentComboBoxPointer->currentText());
+    updateSearchEngineLabel(searchEngineComboBoxPointer->currentText());
+
+    // Update the labels when the combo boxes change.
+    connect(userAgentComboBoxPointer, SIGNAL(currentTextChanged(const QString)), this, SLOT(updateUserAgentLabel(const QString)));
+    connect(searchEngineComboBoxPointer, SIGNAL(currentTextChanged(const QString)), this, SLOT(updateSearchEngineLabel(const QString)));
+
+    // Connect the download directory directory browse button.
+    connect(browseButtonPointer, SIGNAL(clicked()), this, SLOT(showDownloadDirectoryBrowseDialog()));
+
+    // Create a dictionaries QDir from the `QTWEBENGINE_DICTIONARIES_PATH` environment variable.
+    QDir dictionariesDir = QDir(qEnvironmentVariable("QTWEBENGINE_DICTIONARIES_PATH"));
+
+    // Get a dictionaries string list.
+    QStringList dictionariesStringList = dictionariesDir.entryList(QStringList(QLatin1String("*.bdic")), QDir::Files | QDir::NoSymLinks);
+
+    // Remove the `.bdic` file extensions from the dictionaries list.
+    dictionariesStringList.replaceInStrings(QLatin1String(".bdic"), QLatin1String(""));
+
+    // Get a list of the enabled spell check languages.
+    QStringList enabledSpellCheckLanguagesList = Settings::spellCheckLanguages();
+
+    // Add each dictionary to the spell check list widget.
+    foreach(QString dictionaryString, dictionariesStringList)
+    {
+        // Create a new list widget item pointer.
+        QListWidgetItem *listWidgetItemPointer = new QListWidgetItem();
+
+        // Create a dictionary check box widget with the name of the dictionary string.
+        QCheckBox *dictionaryCheckBoxWidget = new QCheckBox(dictionaryString);
+
+        // Check the language if it is currently enabled.
+        if (enabledSpellCheckLanguagesList.contains(dictionaryString))
+            dictionaryCheckBoxWidget->setCheckState(Qt::Checked);
+        else
+            dictionaryCheckBoxWidget->setCheckState(Qt::Unchecked);
+
+        // Add the list widget item to the spell check list widget.
+        spellCheckListWidgetPointer->addItem(listWidgetItemPointer);
+
+        // Set the list widget item check box widget.
+        spellCheckListWidgetPointer->setItemWidget(listWidgetItemPointer, dictionaryCheckBoxWidget);
+    }
+
+    // Create a settings icon string.
+    QString settingsIconString;
+
+    // Get a settings icon that matches the theme.
+    if (QIcon::hasThemeIcon("breeze-settings"))
+    {
+        // KDE uses breeze-settings.
+        settingsIconString = QLatin1String("breeze-settings");
+    }
+    else
+    {
+        // Gnome uses preferences-desktop.
+        settingsIconString = QLatin1String("preferences-desktop");
+    }
+
+    // Add the settings widgets as config dialog pages.
+    addPage(privacySettingsWidgetPointer, i18nc("Settings tab title", "Privacy"), QLatin1String("privacybrowser"));
+    addPage(generalSettingsWidgetPointer, i18nc("Settings tab title", "General"), settingsIconString);
+    addPage(spellCheckSettingsWidgetPointer, i18nc("Settings tab title", "Spell Check"), QLatin1String("tools-check-spelling"));
+
+    // Get handles for the buttons.
+    QPushButton *applyButtonPointer = button(QDialogButtonBox::Apply);
+    QPushButton *okButtonPointer = button(QDialogButtonBox::Ok);
+
+    // Prevent interaction with the parent window while the dialog is open.
+    setWindowModality(Qt::WindowModal);
+
+    // Create a save spell check languages lambda.
+    auto saveSpellCheckLanguages = [spellCheckListWidgetPointer, coreConfigSkeletonPointer, this] ()
+    {
+        // Create a list of enabled languages.
+        QStringList newSpellCheckLanguages = QStringList();
+
+        // Get a count of all the languages.
+        int allLanguagesCount = spellCheckListWidgetPointer->count();
+
+        // Get a list of all the checked languages.
+        for (int i = 0; i < allLanguagesCount; ++i) {
+            // Get the language item.
+            QListWidgetItem *languageItemPointer = spellCheckListWidgetPointer->item(i);
+
+            // Get the language check box.
+            QCheckBox *languageCheckBoxPointer = qobject_cast<QCheckBox*>(spellCheckListWidgetPointer->itemWidget(languageItemPointer));
+
+            // Add the item to the enabled languages if it is checked.
+            if (languageCheckBoxPointer->checkState() == Qt::Checked)
+            {
+                // Get the text.
+                QString languageString = languageCheckBoxPointer->text();
+
+                // Remove all instances of `&`, which may have been added automatically when creating the check box text.
+                languageString.remove(QChar('&'));
+
+                // Add the language string to the list.
+                newSpellCheckLanguages.append(languageString);
+            }
+        }
+
+        // Update the spell check languages.
+        if (Settings::spellCheckLanguages() != newSpellCheckLanguages)
+        {
+            // Update the spell check languages.
+            Settings::setSpellCheckLanguages(newSpellCheckLanguages);
+
+            // Write the settings to disk.
+            coreConfigSkeletonPointer->save();
+
+            // Emit the spell check languages updated signal.
+            emit spellCheckLanguagesUpdated();
+        }
+    };
+
+    // Process clicks on the buttons.
+    connect(applyButtonPointer, &QPushButton::clicked, this, saveSpellCheckLanguages);
+    connect(okButtonPointer, &QPushButton::clicked, this, saveSpellCheckLanguages);
+}
+
+void SettingsDialog::showDownloadDirectoryBrowseDialog()
+{
+    // Get the current download directory.
+    QString currentDownloadDirectory = downloadDirectoryComboBoxPointer->currentText();
+
+    // Resolve the system download directory if specified.
+    if (currentDownloadDirectory == QStringLiteral("System Download Directory"))
+        currentDownloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+
+    // Get the new download directory.
+    QString newDownloadDirectory = QFileDialog::getExistingDirectory(this, i18nc("Select download directory dialog caption", "Select Download Directory"), currentDownloadDirectory);
+
+    // Populate the download directory combo box according to the new download location.
+    if (newDownloadDirectory == QStandardPaths::writableLocation(QStandardPaths::DownloadLocation))  // The default download location was selected.
+    {
+        // Populate the download location with the default text.
+        downloadDirectoryComboBoxPointer->setCurrentText("System Download Directory");
+    }
+    else if (newDownloadDirectory != QStringLiteral(""))  // A different directory was selected.
+    {
+        // Populate the download location.
+        downloadDirectoryComboBoxPointer->setCurrentText(newDownloadDirectory);
+    }
+}
+
+void SettingsDialog::updateSearchEngineLabel(const QString &searchEngineString) const
+{
+    // Update the search engine label.
+    searchEngineLabelPointer->setText(SearchEngineHelper::getSearchUrl(searchEngineString));
+}
+
+void SettingsDialog::updateUserAgentLabel(const QString &userAgentDatabaseName) const
+{
+    // Update the user agent label.
+    userAgentLabelPointer->setText(UserAgentHelper::getUserAgentFromDatabaseName(userAgentDatabaseName));
+}
diff --git a/src/dialogs/SettingsDialog.h b/src/dialogs/SettingsDialog.h
new file mode 100644 (file)
index 0000000..bf56e18
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SETTINGSDIALOG_H
+#define SETTINGSDIALOG_H
+
+// KDE Framework headers.
+#include <KConfigDialog>
+
+// Qt toolkit headers.
+#include <QComboBox>
+#include <QLabel>
+
+class SettingsDialog : public KConfigDialog
+{
+    // Include the Q_OBJECT macro.
+    Q_OBJECT
+
+public:
+    // The primary constructor.
+    explicit SettingsDialog(QWidget *parentWidgetPointer, KCoreConfigSkeleton *coreConfigSkeletonPointer);
+
+signals:
+    void spellCheckLanguagesUpdated() const;
+
+private Q_SLOTS:
+    void showDownloadDirectoryBrowseDialog();
+    void updateSearchEngineLabel(const QString &searchEngineString) const;
+    void updateUserAgentLabel(const QString &userAgentDatabaseName) const;
+
+private:
+    // The private variables.
+    QComboBox *downloadDirectoryComboBoxPointer;
+    QLabel *searchEngineLabelPointer;
+    QLabel *userAgentLabelPointer;
+};
+#endif
index c57b6ee7b6f7c10c2525fc146ebf612fd77e80b3..c0d56519a3b50bcb04aea85b2d9a424eea204bb1 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
@@ -17,6 +17,6 @@
 
 
 # List the sources to include in the executable.
-target_sources(privacy-browser PRIVATE
+target_sources(privacybrowser PRIVATE
     MouseEventFilter.cpp
 )
index 042c5c37e0179a475c3175a80f49173c0c9ef336..921e0c078290ca5df6ee973847025535ccf9631e 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
@@ -17,7 +17,8 @@
 
 
 # List the sources to include in the executable.
-target_sources(privacy-browser PRIVATE
+target_sources(privacybrowser PRIVATE
+    FolderHelper.cpp
     SearchEngineHelper.cpp
     UserAgentHelper.cpp
 )
diff --git a/src/helpers/FolderHelper.cpp b/src/helpers/FolderHelper.cpp
new file mode 100644 (file)
index 0000000..01e1b22
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "FolderHelper.h"
+#include "databases/BookmarksDatabase.h"
+#include "structs/BookmarkStruct.h"
+
+// Construct the class.
+FolderHelper::FolderHelper() {}
+
+void FolderHelper::populateSubfolders(QTreeWidgetItem *treeWidgetItemPointer, const double initialParentFolderId)
+{
+    // Get the list of subfolders.
+    QList<BookmarkStruct> *subfoldersList = BookmarksDatabase::getSubfolders(treeWidgetItemPointer->text(FOLDER_ID_COLUMN).toDouble());
+
+    // Populate each subfolder.
+    for (BookmarkStruct bookmarkStruct : *subfoldersList)
+    {
+        // Create a tree widget item.
+        QTreeWidgetItem *subfolderWidgetItemPointer = new QTreeWidgetItem();
+
+        // Populate the tree widget item.
+        subfolderWidgetItemPointer->setText(FOLDER_NAME_COLUMN, bookmarkStruct.name);
+        subfolderWidgetItemPointer->setIcon(FOLDER_NAME_COLUMN, bookmarkStruct.favoriteIcon);
+        subfolderWidgetItemPointer->setText(FOLDER_ID_COLUMN, QString::number(bookmarkStruct.folderId, 'f', 0));  // Format the folder ID as a floating point with no trailing zeros.
+
+        // Add the subfolder to the tree widget item.
+        treeWidgetItemPointer->addChild(subfolderWidgetItemPointer);
+
+        // Select the folder if it is the initial parent folder.
+        if (bookmarkStruct.folderId == initialParentFolderId)
+            subfolderWidgetItemPointer->setSelected(true);
+
+        // Add any subfolders.
+        populateSubfolders(subfolderWidgetItemPointer, initialParentFolderId);
+    }
+}
+
+void FolderHelper::populateSubfoldersExcept(const double exceptSubfolderDatabaseId, QTreeWidgetItem *treeWidgetItemPointer, const double initialParentFolderId)
+{
+    // Get the list of subfolders.
+    QList<BookmarkStruct> *subfoldersList = BookmarksDatabase::getSubfolders(treeWidgetItemPointer->text(FOLDER_ID_COLUMN).toDouble());
+
+    // Populate each subfolder.
+    for (BookmarkStruct bookmarkStruct : *subfoldersList)
+    {
+        // Determine if this is an excepted folder.
+        bool exceptedFolder = bookmarkStruct.databaseId == exceptSubfolderDatabaseId;
+
+        // Create a tree widget item.
+        QTreeWidgetItem *subfolderWidgetItemPointer = new QTreeWidgetItem();
+
+        // Populate the tree widget item.
+        subfolderWidgetItemPointer->setText(FOLDER_NAME_COLUMN, bookmarkStruct.name);
+        subfolderWidgetItemPointer->setIcon(FOLDER_NAME_COLUMN, bookmarkStruct.favoriteIcon);
+        subfolderWidgetItemPointer->setText(FOLDER_ID_COLUMN, QString::number(bookmarkStruct.folderId, 'f', 0));  // Format the folder ID as a floating point with no trailing zeros.
+
+        // Disable the folder widget if it is excepted.  All subfolders will automatically be disabled.
+        if (exceptedFolder)
+            subfolderWidgetItemPointer->setDisabled(true);
+
+        // Add the subfolder to the tree widget item.
+        treeWidgetItemPointer->addChild(subfolderWidgetItemPointer);
+
+        // Select the folder if it is the initial parent folder.
+        if (bookmarkStruct.folderId == initialParentFolderId)
+            subfolderWidgetItemPointer->setSelected(true);
+
+        // Add any subfolders.
+        populateSubfoldersExcept(exceptSubfolderDatabaseId, subfolderWidgetItemPointer, initialParentFolderId);
+    }
+}
diff --git a/src/helpers/FolderHelper.h b/src/helpers/FolderHelper.h
new file mode 100644 (file)
index 0000000..9248576
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FOLDERHELPER_H
+#define FOLDERHELPER_H
+
+// Qt toolkit headers.
+#include <QTreeWidgetItem>
+
+class FolderHelper
+{
+public:
+    // The default constructor.
+    explicit FolderHelper();
+
+    // The public constants.
+    const int FOLDER_NAME_COLUMN = 0;
+    const int FOLDER_ID_COLUMN = 1;
+
+    // The public functions.
+    void populateSubfolders(QTreeWidgetItem *treeWidgetItemPointer, const double initialParentFolderId);
+    void populateSubfoldersExcept(const double exceptSubfolderDatabaseId, QTreeWidgetItem *treeWidgetItemPointer, const double initialParentFolderId);
+};
+#endif
index 14a71e2cd8720ebd8978ca291a7f1081ecff9aa6..891702d2d371e95d71e38eb35a9fc307cfe87139 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
 #include <KLocalizedString>
 
 // Define the public database constants.
-const QString UserAgentHelper::SYSTEM_DEFAULT_DATABASE = QStringLiteral("System Default");
-const QString UserAgentHelper::PRIVACY_BROWSER_DATABASE = QStringLiteral("Privacy Browser");
-const QString UserAgentHelper::WEB_ENGINE_DEFAULT_DATABASE = QStringLiteral("WebEngine Default");
-const QString UserAgentHelper::FIREFOX_LINUX_DATABASE = QStringLiteral("Firefox Linux");
-const QString UserAgentHelper::CHROMIUM_LINUX_DATABASE = QStringLiteral("Chromium Linux");
-const QString UserAgentHelper::FIREFOX_WINDOWS_DATABASE = QStringLiteral("Firefox Windows");
-const QString UserAgentHelper::CHROME_WINDOWS_DATABASE = QStringLiteral("Chrome Windows");
-const QString UserAgentHelper::EDGE_WINDOWS_DATABASE = QStringLiteral("Edge Windows");
-const QString UserAgentHelper::SAFARI_MACOS_DATABASE = QStringLiteral("Safari macOS");
-
-// Define the public translated constants.
-const QString UserAgentHelper::SYSTEM_DEFAULT_TRANSLATED = i18n("System default");
-const QString UserAgentHelper::PRIVACY_BROWSER_TRANSLATED = i18n("Privacy Browser");
-const QString UserAgentHelper::WEB_ENGINE_DEFAULT_TRANSLATED = i18n("WebEngine default");
-const QString UserAgentHelper::FIREFOX_LINUX_TRANSLATED = i18n("Firefox on Linux");
-const QString UserAgentHelper::CHROMIUM_LINUX_TRANSLATED = i18n("Chromium on Linux");
-const QString UserAgentHelper::FIREFOX_WINDOWS_TRANSLATED = i18n("Firefox on Windows");
-const QString UserAgentHelper::CHROME_WINDOWS_TRANSLATED = i18n("Chrome on Windows");
-const QString UserAgentHelper::EDGE_WINDOWS_TRANSLATED = i18n("Edge on Windows");
-const QString UserAgentHelper::SAFARI_MACOS_TRANSLATED = i18n("Safari on macOS");
+const QString UserAgentHelper::SYSTEM_DEFAULT_DATABASE = QLatin1String("System Default");
+const QString UserAgentHelper::PRIVACY_BROWSER_DATABASE = QLatin1String("Privacy Browser");
+const QString UserAgentHelper::WEB_ENGINE_DEFAULT_DATABASE = QLatin1String("WebEngine Default");
+const QString UserAgentHelper::FIREFOX_LINUX_DATABASE = QLatin1String("Firefox Linux");
+const QString UserAgentHelper::CHROMIUM_LINUX_DATABASE = QLatin1String("Chromium Linux");
+const QString UserAgentHelper::FIREFOX_WINDOWS_DATABASE = QLatin1String("Firefox Windows");
+const QString UserAgentHelper::CHROME_WINDOWS_DATABASE = QLatin1String("Chrome Windows");
+const QString UserAgentHelper::EDGE_WINDOWS_DATABASE = QLatin1String("Edge Windows");
+const QString UserAgentHelper::SAFARI_MACOS_DATABASE = QLatin1String("Safari macOS");
 
 // Define the public user agent constants.
-const QString UserAgentHelper::PRIVACY_BROWSER_USER_AGENT = QStringLiteral("PrivacyBrowser/1.0");
-const QString UserAgentHelper::FIREFOX_LINUX_USER_AGENT = QStringLiteral("Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0");
-const QString UserAgentHelper::CHROMIUM_LINUX_USER_AGENT = QStringLiteral("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36");
-const QString UserAgentHelper::FIREFOX_WINDOWS_USER_AGENT = QStringLiteral("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0");
-const QString UserAgentHelper::CHROME_WINDOWS_USER_AGENT = QStringLiteral("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36");
-const QString UserAgentHelper::EDGE_WINDOWS_USER_AGENT = QStringLiteral("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.71");
-const QString UserAgentHelper::SAFARI_MACOS_USER_AGENT = QStringLiteral("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15");
+const QString UserAgentHelper::PRIVACY_BROWSER_USER_AGENT = QLatin1String("PrivacyBrowser/1.0");
+const QString UserAgentHelper::FIREFOX_LINUX_USER_AGENT = QLatin1String("Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0");
+const QString UserAgentHelper::CHROMIUM_LINUX_USER_AGENT = QLatin1String("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36");
+const QString UserAgentHelper::FIREFOX_WINDOWS_USER_AGENT = QLatin1String("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0");
+const QString UserAgentHelper::CHROME_WINDOWS_USER_AGENT = QLatin1String("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36");
+const QString UserAgentHelper::EDGE_WINDOWS_USER_AGENT = QLatin1String("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0");
+const QString UserAgentHelper::SAFARI_MACOS_USER_AGENT = QLatin1String("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15");
 
 // Construct the class.
-UserAgentHelper::UserAgentHelper() {};
+UserAgentHelper::UserAgentHelper() {
+    // Populate the translated user agents.  Translated entries cannot be public static const.
+    SYSTEM_DEFAULT_TRANSLATED = i18nc("User agents", "System default");
+    PRIVACY_BROWSER_TRANSLATED = i18nc("User agents", "Privacy Browser");
+    WEB_ENGINE_DEFAULT_TRANSLATED = i18nc("User agents", "WebEngine default");
+    FIREFOX_LINUX_TRANSLATED = i18nc("User agents", "Firefox on Linux");
+    CHROMIUM_LINUX_TRANSLATED = i18nc("User agents", "Chromium on Linux");
+    FIREFOX_WINDOWS_TRANSLATED = i18nc("User agents", "Firefox on Windows");
+    CHROME_WINDOWS_TRANSLATED = i18nc("User agents", "Chrome on Windows");
+    EDGE_WINDOWS_TRANSLATED = i18nc("User agents", "Edge on Windows");
+    SAFARI_MACOS_TRANSLATED = i18nc("User agents", "Safari on macOS");
+};
 
 QString UserAgentHelper::getDatabaseUserAgentNameFromTranslatedName(const QString &translatedUserAgentName)
 {
@@ -74,6 +74,20 @@ QString UserAgentHelper::getDatabaseUserAgentNameFromTranslatedName(const QStrin
     else return translatedUserAgentName;  // Return the custom user agent.
 }
 
+QString UserAgentHelper::getDatabaseUserAgentNameFromUserAgent(const QString &userAgent)
+{
+    // Return the database user agent name.
+    if (userAgent == PRIVACY_BROWSER_USER_AGENT) return PRIVACY_BROWSER_DATABASE;  // Privacy Browser.
+    else if (userAgent == TabWidget::webEngineDefaultUserAgent) return WEB_ENGINE_DEFAULT_DATABASE;  // WebEngine default.
+    else if (userAgent == FIREFOX_LINUX_USER_AGENT) return FIREFOX_LINUX_DATABASE;  // Firefox Linux.
+    else if (userAgent == CHROMIUM_LINUX_USER_AGENT) return CHROMIUM_LINUX_DATABASE;  // Chromium Linux.
+    else if (userAgent == FIREFOX_WINDOWS_USER_AGENT) return FIREFOX_WINDOWS_DATABASE;  // Firefox Windows.
+    else if (userAgent == CHROME_WINDOWS_USER_AGENT) return CHROME_WINDOWS_DATABASE;  // Chrome Windows.
+    else if (userAgent == EDGE_WINDOWS_USER_AGENT) return EDGE_WINDOWS_DATABASE;  // Edge Windows.
+    else if (userAgent == SAFARI_MACOS_USER_AGENT) return SAFARI_MACOS_DATABASE;  // Safari macOS.
+    else return userAgent;  // Return the custom user agent.
+}
+
 int UserAgentHelper::getDomainSettingsUserAgentIndex(const QString &userAgentName)
 {
     // Return the domain settings user agent index.
index 5c8286afdedccbf87577b6e309140a081e74f297..60f9902812a8599348cd225f9c2a74a881f90c8e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022,2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -29,39 +29,47 @@ public:
     // The default constructor.
     UserAgentHelper();
 
+    // The destructor.
+    ~UserAgentHelper();
+
     // The public static constants.
     static const QString CHROMIUM_LINUX_DATABASE;
-    static const QString CHROMIUM_LINUX_TRANSLATED;
     static const QString CHROMIUM_LINUX_USER_AGENT;
     static const QString CHROME_WINDOWS_DATABASE;
-    static const QString CHROME_WINDOWS_TRANSLATED;
     static const QString CHROME_WINDOWS_USER_AGENT;
     static const QString EDGE_WINDOWS_DATABASE;
-    static const QString EDGE_WINDOWS_TRANSLATED;
     static const QString EDGE_WINDOWS_USER_AGENT;
     static const QString FIREFOX_LINUX_DATABASE;
-    static const QString FIREFOX_LINUX_TRANSLATED;
     static const QString FIREFOX_LINUX_USER_AGENT;
     static const QString FIREFOX_WINDOWS_DATABASE;
-    static const QString FIREFOX_WINDOWS_TRANSLATED;
     static const QString FIREFOX_WINDOWS_USER_AGENT;
     static const QString PRIVACY_BROWSER_DATABASE;
-    static const QString PRIVACY_BROWSER_TRANSLATED;
     static const QString PRIVACY_BROWSER_USER_AGENT;
     static const QString SAFARI_MACOS_DATABASE;
-    static const QString SAFARI_MACOS_TRANSLATED;
     static const QString SAFARI_MACOS_USER_AGENT;
     static const QString SYSTEM_DEFAULT_DATABASE;
-    static const QString SYSTEM_DEFAULT_TRANSLATED;
     static const QString WEB_ENGINE_DEFAULT_DATABASE;
-    static const QString WEB_ENGINE_DEFAULT_TRANSLATED;
 
     // The public static functions.
-    static QString getDatabaseUserAgentNameFromTranslatedName(const QString &translatedUserAgentName);
+    static QString getDatabaseUserAgentNameFromUserAgent(const QString &userAgent);
     static int getDomainSettingsUserAgentIndex(const QString &userAgentName);
     static QString getUserAgentFromDatabaseName(const QString &userAgentDatabaseName);
-    static QString getUserAgentFromTranslatedName(const QString &userAgentTranslatedName);
     static QString getResultingDomainSettingsUserAgent(const QString &rawUserAgent);
-    static QString getTranslatedUserAgentNameFromDatabaseName(const QString &userAgentName);
+
+    // The public variables.
+    QString SYSTEM_DEFAULT_TRANSLATED;
+    QString PRIVACY_BROWSER_TRANSLATED;
+    QString WEB_ENGINE_DEFAULT_TRANSLATED;
+    QString FIREFOX_LINUX_TRANSLATED;
+    QString CHROMIUM_LINUX_TRANSLATED;
+    QString FIREFOX_WINDOWS_TRANSLATED;
+    QString CHROME_WINDOWS_TRANSLATED;
+    QString EDGE_WINDOWS_TRANSLATED;
+    QString SAFARI_MACOS_TRANSLATED;
+
+    // The public functions.  Anything that accesses the translated user names must use an instantiated copy of the class.
+    QString getDatabaseUserAgentNameFromTranslatedName(const QString &translatedUserAgentName);
+    QString getUserAgentFromTranslatedName(const QString &userAgentTranslatedName);
+    QString getTranslatedUserAgentNameFromDatabaseName(const QString &userAgentName);
 };
 #endif
diff --git a/src/icons/1024-apps-privacybrowser.png b/src/icons/1024-apps-privacybrowser.png
new file mode 100644 (file)
index 0000000..5744367
Binary files /dev/null and b/src/icons/1024-apps-privacybrowser.png differ
diff --git a/src/icons/128-apps-privacybrowser.png b/src/icons/128-apps-privacybrowser.png
new file mode 100644 (file)
index 0000000..718d27a
Binary files /dev/null and b/src/icons/128-apps-privacybrowser.png differ
diff --git a/src/icons/16-apps-privacybrowser.png b/src/icons/16-apps-privacybrowser.png
new file mode 100644 (file)
index 0000000..108198e
Binary files /dev/null and b/src/icons/16-apps-privacybrowser.png differ
diff --git a/src/icons/22-apps-privacybrowser.png b/src/icons/22-apps-privacybrowser.png
new file mode 100644 (file)
index 0000000..100693c
Binary files /dev/null and b/src/icons/22-apps-privacybrowser.png differ
diff --git a/src/icons/24-apps-privacybrowser.png b/src/icons/24-apps-privacybrowser.png
new file mode 100644 (file)
index 0000000..7c5ffe0
Binary files /dev/null and b/src/icons/24-apps-privacybrowser.png differ
diff --git a/src/icons/256-apps-privacybrowser.png b/src/icons/256-apps-privacybrowser.png
new file mode 100644 (file)
index 0000000..cd7fca3
Binary files /dev/null and b/src/icons/256-apps-privacybrowser.png differ
diff --git a/src/icons/32-apps-privacybrowser.png b/src/icons/32-apps-privacybrowser.png
new file mode 100644 (file)
index 0000000..974f192
Binary files /dev/null and b/src/icons/32-apps-privacybrowser.png differ
diff --git a/src/icons/48-apps-privacybrowser.png b/src/icons/48-apps-privacybrowser.png
new file mode 100644 (file)
index 0000000..595c90e
Binary files /dev/null and b/src/icons/48-apps-privacybrowser.png differ
diff --git a/src/icons/512-apps-privacybrowser.png b/src/icons/512-apps-privacybrowser.png
new file mode 100644 (file)
index 0000000..0c3c0d6
Binary files /dev/null and b/src/icons/512-apps-privacybrowser.png differ
diff --git a/src/icons/64-apps-privacybrowser.png b/src/icons/64-apps-privacybrowser.png
new file mode 100644 (file)
index 0000000..c9316c4
Binary files /dev/null and b/src/icons/64-apps-privacybrowser.png differ
index 39245a7c4006fcf2368ffdcf013bd9fa478bdbf5..c9e4542595ab018b30268d7be315a2e201c6ec63 100644 (file)
@@ -5,7 +5,7 @@
 
   This file is derived from `security` and `language`, which are part of the Android Material icon set.  They are released under the Apache License 2.0.  <https://fonts.google.com/icons>
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
diff --git a/src/icons/loading.gif b/src/icons/loading.gif
new file mode 100644 (file)
index 0000000..132f217
Binary files /dev/null and b/src/icons/loading.gif differ
index 428d50771f089dd52e31307a999f5edbbb3b2d47..d799bd8fb1b6bba2512fe52598e0c1e005d27468 100644 (file)
@@ -5,7 +5,7 @@
 
   This file is derived from `security` and `language`, which are part of the Android Material icon set.  They are released under the Apache License 2.0.  <https://fonts.google.com/icons>
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
diff --git a/src/icons/privacybrowser-symbolic.svg b/src/icons/privacybrowser-symbolic.svg
new file mode 100644 (file)
index 0000000..9649f6f
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<svg
+    xmlns="http://www.w3.org/2000/svg"
+    viewBox="0 0 119.99999 119.99999" >
+
+    <path
+        style="fill:#000000"
+        d="M 59.999994,0 10.909089,21.818179 V 54.54545 c 0,30.272691 20.945481,58.58183 49.090905,65.45454 C 88.14542,113.12728 109.0909,84.818141 109.0909,54.54545 V 21.818179 Z m -0.03735,22.600527 c 20.681907,0 37.436804,16.754959 37.436804,37.399467 0,20.644505 -16.754897,37.399469 -37.436804,37.399469 -20.644505,0 -37.362132,-16.754964 -37.362132,-37.399469 0,-20.644508 16.717627,-37.399467 37.362132,-37.399467 z m 0.03735,7.629496 c -3.104155,4.487939 -5.535086,9.461971 -7.143266,14.810099 h 14.286531 c -1.608175,-5.348128 -4.03911,-10.32216 -7.143265,-14.810099 z m -9.686433,1.496031 c -6.881499,2.356163 -12.603766,7.105754 -16.194117,13.314068 h 11.033115 c 1.196783,-4.674937 2.917037,-9.16273 5.161002,-13.314068 z m 19.372866,0 c 2.243969,4.151338 3.964654,8.639131 5.161429,13.314068 H 85.880544 C 82.290198,38.869206 76.567931,34.082217 69.686427,31.726054 Z M 31.052711,52.520275 c -0.598392,2.393566 -0.972463,4.899158 -0.972463,7.479719 0,2.580565 0.37407,5.086584 0.972463,7.48015 h 12.641154 c -0.299194,-2.468364 -0.523567,-4.936987 -0.523567,-7.48015 0,-2.543159 0.224368,-5.011355 0.523567,-7.479719 z m 20.195979,0 c -0.336573,2.430962 -0.598669,4.93656 -0.598669,7.479719 0,2.543163 0.262101,5.011786 0.598669,7.48015 h 17.503039 c 0.336618,-2.468364 0.598239,-4.936987 0.598239,-7.48015 0,-2.543159 -0.261625,-5.048757 -0.598239,-7.479719 z m 25.057436,0 c 0.299197,2.468364 0.523566,4.93656 0.523566,7.479719 0,2.543163 -0.224373,5.011786 -0.523566,7.48015 h 12.641153 c 0.598393,-2.393566 0.97246,-4.899585 0.97246,-7.48015 0,-2.580561 -0.374072,-5.086153 -0.97246,-7.479719 z M 34.119444,74.959868 c 3.590351,6.208304 9.312618,10.957903 16.194117,13.314069 C 48.069596,84.122594 46.349342,79.6348 45.152559,74.959868 Z m 18.737284,0 c 1.60818,5.34812 4.039111,10.322162 7.143266,14.810095 3.104155,-4.487933 5.53509,-9.461975 7.143265,-14.810095 z m 21.991128,0 c -1.196775,4.674932 -2.91746,9.162726 -5.161429,13.314069 6.881504,-2.356166 12.603771,-7.143162 16.194117,-13.314069 z" />
+</svg>
+
+<!--
+  Copyright 2016-2017,2021-2023 Soren Stoutner <soren@stoutner.com>.
+
+  This file is derived from `security` and `language`, which are part of the Android Material icon set.  They are released under the Apache License 2.0.  <https://fonts.google.com/icons>
+
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+
+  This copyright notice is moved to the bottom of the file to make Gnome's SVG parser happy. -->
diff --git a/src/icons/sc-apps-privacy-browser.svg b/src/icons/sc-apps-privacy-browser.svg
deleted file mode 100644 (file)
index 428d507..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-
-<!--
-  Copyright © 2016-2017,2021-2022 Soren Stoutner <soren@stoutner.com>.
-
-  This file is derived from `security` and `language`, which are part of the Android Material icon set.  They are released under the Apache License 2.0.  <https://fonts.google.com/icons>
-
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
-
-  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 <http://www.gnu.org/licenses/>. -->
-
-<svg
-    xmlns="http://www.w3.org/2000/svg"
-    viewBox="0 0 256 256" >
-
-    <!-- Inner Shield. -->
-    <g transform="translate(0,160)" >
-        <path
-            style="fill:#0d47a1"
-            d="m 128,-147.2 -94.254546,41.89091 v 62.836368 c 0,58.123635 40.215273,112.477091 94.254546,125.672721 54.03928,-13.19563 94.25454,-67.549086 94.25454,-125.672721 v -62.836368 z" />
-    </g>
-
-    <!-- Outer Shield. -->
-    <g transform="translate(0,160)" >
-        <path
-            style="fill:#1976d2"
-            d="m 128,-160 -104.727273,46.54546 v 69.818182 C 23.272727,20.945459 67.956363,81.338188 128,95.999999 188.04364,81.338188 232.72727,20.945459 232.72727,-43.636358 v -69.818182 z m 0,127.883641 h 81.45454 C 203.28728,15.825464 171.28727,58.530919 128,71.912739 V -31.999996 H 46.545455 V -98.327268 L 128,-134.51636 Z" />
-    </g>
-
-    <!-- Globe. -->
-    <g transform="translate(0,232)" >
-        <path
-            style="fill:#ffffff"
-            d="m 127.92021,-183.78553 c -44.041614,0 -79.705747,35.74391 -79.705747,79.78553 0,44.041617 35.664133,79.785537 79.705747,79.785537 44.1214,0 79.86533,-35.74392 79.86533,-79.785537 0,-44.04162 -35.74393,-79.78553 -79.86533,-79.78553 z m 55.29138,47.87132 h -23.53674 c -2.55313,-9.9732 -6.22326,-19.54746 -11.0104,-28.40365 14.68055,5.02648 26.88773,15.23903 34.54714,28.40365 z M 128,-167.50929 c 6.6222,9.57427 11.80825,20.18574 15.23903,31.59508 h -30.47807 c 3.43078,-11.40934 8.61684,-22.02081 15.23904,-31.59508 z M 66.245993,-88.042889 C 64.969425,-93.149168 64.171571,-98.494798 64.171571,-104 c 0,-5.5052 0.797855,-10.85083 2.074422,-15.95711 h 26.96751 c -0.63829,5.26585 -1.117,10.5317 -1.117,15.95711 0,5.425417 0.47872,10.691263 1.117,15.957111 z m 6.542412,15.957108 H 96.32514 c 2.553137,9.973191 6.22327,19.547449 11.0104,28.403644 C 92.655006,-48.708619 80.44782,-58.841391 72.788405,-72.085781 Z M 96.32514,-135.91421 H 72.788405 c 7.659415,-13.2444 19.866601,-23.37717 34.547135,-28.40365 -4.78713,8.85619 -8.457263,18.43045 -11.0104,28.40365 z M 128,-40.49071 c -6.6222,-9.574268 -11.80826,-20.185747 -15.23904,-31.595071 h 30.47807 C 139.80825,-60.676457 134.6222,-50.064978 128,-40.49071 Z m 18.66982,-47.552179 h -37.33964 c -0.71801,-5.265848 -1.27656,-10.531694 -1.27656,-15.957111 0,-5.42541 0.55854,-10.77105 1.27656,-15.95711 h 37.33964 c 0.71811,5.18606 1.27656,10.5317 1.27656,15.95711 0,5.425417 -0.55844,10.691263 -1.27656,15.957111 z m 1.99463,44.360752 c 4.78714,-8.856195 8.45727,-18.430453 11.0104,-28.403644 h 23.53674 c -7.65941,13.164605 -19.86659,23.377162 -34.54714,28.403644 z m 14.12204,-44.360752 c 0.63828,-5.265848 1.117,-10.531694 1.117,-15.957111 0,-5.42541 -0.47871,-10.69126 -1.117,-15.95711 h 26.96752 c 1.27656,5.10628 2.07441,10.45191 2.07441,15.95711 0,5.505202 -0.79784,10.850832 -2.07441,15.957111 z" />
-    </g>
-</svg>
diff --git a/src/icons/sc-apps-privacybrowser.svg b/src/icons/sc-apps-privacybrowser.svg
new file mode 100644 (file)
index 0000000..19bf69f
--- /dev/null
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<svg
+    xmlns="http://www.w3.org/2000/svg"
+    viewBox="0 0 256 256" >
+
+    <!-- Inner Shield. -->
+    <g transform="translate(0,160)" >
+        <path
+            style="fill:#0d47a1"
+            d="m 128,-147.2 -94.254546,41.89091 v 62.836368 c 0,58.123635 40.215273,112.477091 94.254546,125.672721 54.03928,-13.19563 94.25454,-67.549086 94.25454,-125.672721 v -62.836368 z" />
+    </g>
+
+    <!-- Outer Shield. -->
+    <g transform="translate(0,160)" >
+        <path
+            style="fill:#1976d2"
+            d="m 128,-160 -104.727273,46.54546 v 69.818182 C 23.272727,20.945459 67.956363,81.338188 128,95.999999 188.04364,81.338188 232.72727,20.945459 232.72727,-43.636358 v -69.818182 z m 0,127.883641 h 81.45454 C 203.28728,15.825464 171.28727,58.530919 128,71.912739 V -31.999996 H 46.545455 V -98.327268 L 128,-134.51636 Z" />
+    </g>
+
+    <!-- Globe. -->
+    <g transform="translate(0,232)" >
+        <path
+            style="fill:#ffffff"
+            d="m 127.92021,-183.78553 c -44.041614,0 -79.705747,35.74391 -79.705747,79.78553 0,44.041617 35.664133,79.785537 79.705747,79.785537 44.1214,0 79.86533,-35.74392 79.86533,-79.785537 0,-44.04162 -35.74393,-79.78553 -79.86533,-79.78553 z m 55.29138,47.87132 h -23.53674 c -2.55313,-9.9732 -6.22326,-19.54746 -11.0104,-28.40365 14.68055,5.02648 26.88773,15.23903 34.54714,28.40365 z M 128,-167.50929 c 6.6222,9.57427 11.80825,20.18574 15.23903,31.59508 h -30.47807 c 3.43078,-11.40934 8.61684,-22.02081 15.23904,-31.59508 z M 66.245993,-88.042889 C 64.969425,-93.149168 64.171571,-98.494798 64.171571,-104 c 0,-5.5052 0.797855,-10.85083 2.074422,-15.95711 h 26.96751 c -0.63829,5.26585 -1.117,10.5317 -1.117,15.95711 0,5.425417 0.47872,10.691263 1.117,15.957111 z m 6.542412,15.957108 H 96.32514 c 2.553137,9.973191 6.22327,19.547449 11.0104,28.403644 C 92.655006,-48.708619 80.44782,-58.841391 72.788405,-72.085781 Z M 96.32514,-135.91421 H 72.788405 c 7.659415,-13.2444 19.866601,-23.37717 34.547135,-28.40365 -4.78713,8.85619 -8.457263,18.43045 -11.0104,28.40365 z M 128,-40.49071 c -6.6222,-9.574268 -11.80826,-20.185747 -15.23904,-31.595071 h 30.47807 C 139.80825,-60.676457 134.6222,-50.064978 128,-40.49071 Z m 18.66982,-47.552179 h -37.33964 c -0.71801,-5.265848 -1.27656,-10.531694 -1.27656,-15.957111 0,-5.42541 0.55854,-10.77105 1.27656,-15.95711 h 37.33964 c 0.71811,5.18606 1.27656,10.5317 1.27656,15.95711 0,5.425417 -0.55844,10.691263 -1.27656,15.957111 z m 1.99463,44.360752 c 4.78714,-8.856195 8.45727,-18.430453 11.0104,-28.403644 h 23.53674 c -7.65941,13.164605 -19.86659,23.377162 -34.54714,28.403644 z m 14.12204,-44.360752 c 0.63828,-5.265848 1.117,-10.531694 1.117,-15.957111 0,-5.42541 -0.47871,-10.69126 -1.117,-15.95711 h 26.96752 c 1.27656,5.10628 2.07441,10.45191 2.07441,15.95711 0,5.505202 -0.79784,10.850832 -2.07441,15.957111 z" />
+    </g>
+</svg>
+
+<!--
+  Copyright 2016-2017,2021-2023 Soren Stoutner <soren@stoutner.com>.
+
+  This file is derived from `security` and `language`, which are part of the Android Material icon set.  They are released under the Apache License 2.0.  <https://fonts.google.com/icons>
+
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+
+  This copyright notice is moved to the bottom of the file to make Gnome's SVG parser happy. -->
index 6e92b3290be4e2f4b2c427075b98537965bde4a8..7f044baa87fd9e8f420c92a0fe214b1220f77ab1 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
@@ -17,6 +17,6 @@
 
 
 # List the sources to include in the executable.
-target_sources(privacy-browser PRIVATE
+target_sources(privacybrowser PRIVATE
     UrlRequestInterceptor.cpp
 )
index 634889867ad386464e5472b1d01c5ff1ccca6075..5f9a8cc3a312bbc0759c5d57797edb84984acecb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
 // KDE Framework headers.
 #include <KLocalizedString>
 
-// Qt framework headers.
-#include <QMessageBox>
-
 // Construct the class.
 UrlRequestInterceptor::UrlRequestInterceptor(QObject *parentObjectPointer) : QWebEngineUrlRequestInterceptor(parentObjectPointer) {}
 
 void UrlRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo &urlRequestInfo)
 {
+    // Handle the request according to the resource type.
+    switch (urlRequestInfo.resourceType())
+    {
+        // A naughty HTTP ping request.
+        case QWebEngineUrlRequestInfo::ResourceTypePing:
+        {
+            // Block the HTTP ping request.
+            urlRequestInfo.block(true);
+
+            // Display the HTTP Ping blocked dialog.
+            emit displayHttpPingDialog(urlRequestInfo.requestUrl().toString());
+
+            break;
+        }
+
+        default:
+        {
+            // Do nothing.
+            break;
+        }
+    }
+
     // Handle the request according to the navigation type.
     switch (urlRequestInfo.navigationType())
     {
         case QWebEngineUrlRequestInfo::NavigationTypeLink:
         case QWebEngineUrlRequestInfo::NavigationTypeTyped:
         case QWebEngineUrlRequestInfo::NavigationTypeBackForward:
+        // case QWebEngineUrlRequestInfo::NavigationTypeReload:  This can be uncommented once https://redmine.stoutner.com/issues/821 has been fixed.
         case QWebEngineUrlRequestInfo::NavigationTypeRedirect:
         {
             // Only check the hosts if the main URL is changing.
@@ -58,42 +78,4 @@ void UrlRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo &urlReques
             // Do nothing.
             break;
     }
-
-    // Handle the request according to the resource type.
-    switch (urlRequestInfo.resourceType())
-    {
-        // A naughty HTTP ping request.
-        case QWebEngineUrlRequestInfo::ResourceTypePing:
-        {
-            // Block HTTP ping requests.
-            urlRequestInfo.block(true);
-
-            // Instantiate an HTTP ping blocked message box.
-            QMessageBox httpPingBlockedMessageBox;
-
-            // Set the icon.
-            httpPingBlockedMessageBox.setIcon(QMessageBox::Information);
-
-            // Set the window title.
-            httpPingBlockedMessageBox.setWindowTitle(i18nc("HTTP Ping blocked dialog title", "HTTP Ping Blocked"));
-
-            // Set the text.
-            httpPingBlockedMessageBox.setText(i18nc("HTTP Ping blocked dialog text", "This request has been blocked because it sends a naughty HTTP ping to %1.",
-                                                    urlRequestInfo.requestUrl().toString()));
-
-            // Set the standard button.
-            httpPingBlockedMessageBox.setStandardButtons(QMessageBox::Ok);
-
-            // Display the message box.
-            httpPingBlockedMessageBox.exec();
-
-            break;
-        }
-
-        default:
-        {
-            // Do nothing.
-            break;
-        }
-    }
 }
index 721d5231b3508deaea545650b29e94c77cbf5e2c..5638768529dbf5713460d88278c4aa15c4aaf0b5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -37,6 +37,7 @@ public:
 
 signals:
     // The signals.
-    void applyDomainSettings(const QString hostname) const;
+    void applyDomainSettings(const QString &hostname) const;
+    void displayHttpPingDialog(const QString &httpPingUrl) const;
 };
 #endif
index 10ce579ca39829f90c6046fcc0ca560483aaa087..0c499f2750e19f01ea294c011c6e7701542f72b4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
@@ -18,6 +18,7 @@
  */
 
 // Application headers.
+#include "databases/BookmarksDatabase.h"
 #include "databases/CookiesDatabase.h"
 #include "databases/DomainsDatabase.h"
 #include "windows/BrowserWindow.h"
@@ -25,8 +26,8 @@
 // KDE Frameworks headers.
 #include <KAboutData>
 #include <KCrash>
-#include <KLocalizedString>
 #include <KDBusService>
+#include <KLocalizedString>
 
 // Qt headers.
 #include <QApplication>
@@ -45,26 +46,27 @@ int main(int argc, char *argv[])
     KCrash::initialize();
 
     // Instantiate about data, setting the component name, the display name, and the version.
-    KAboutData aboutData(QStringLiteral("privacybrowser"), i18n("Privacy Browser"), QStringLiteral("0.1"));
+    KAboutData aboutData(QStringLiteral("privacybrowser"), i18nc("Program Name", "Privacy Browser"), QStringLiteral("0.5"));
 
     // Add the author name, job description, email address, and website.
-    aboutData.addAuthor(i18n("Soren Stoutner"),i18n("Principal developer"), QStringLiteral("soren@stoutner.com"), QStringLiteral("https://www.stoutner.com/"));
+    aboutData.addAuthor(i18nc("Developer Information", "Soren Stoutner"),i18nc("Developer Information", "Principal developer"), QStringLiteral("soren@stoutner.com"),
+                        QStringLiteral("https://www.stoutner.com/"));
 
     // Populate additional about data info.
     aboutData.setBugAddress("https://redmine.stoutner.com/projects/privacy-browser-pc/issues");
-    aboutData.setCopyrightStatement(i18n("Copyright © 2016-2017,2021-2022 Soren Stoutner <soren@stoutner.com>"));
+    aboutData.setCopyrightStatement(i18nc("Copyright", "Copyright 2016-2017,2021-2024 Soren Stoutner <soren@stoutner.com>"));
     aboutData.setDesktopFileName(QStringLiteral("com.stoutner.privacybrowser"));
     aboutData.setHomepage(QStringLiteral("https://www.stoutner.com/privacy-browser-pc/"));
     //aboutData.setLicense(KAboutLicense::GPL_V3, KAboutLicense::OrLaterVersions);  <https://redmine.stoutner.com/issues/822>
     aboutData.setLicenseTextFile(QStringLiteral(":/licenses/GPLv3+.txt"));
     aboutData.setOrganizationDomain("stoutner.com");
-    aboutData.setShortDescription(i18n("A web browser that respects your privacy."));
+    aboutData.setShortDescription(i18nc("Tagline", "A web browser that respects your privacy."));
 
     // Set the application data.
     KAboutData::setApplicationData(aboutData);
 
     // Set the window icon.
-    application.setWindowIcon(QIcon::fromTheme(QStringLiteral("privacy-browser"), QIcon(":/icons/sc-apps-privacy-browser.svg")));
+    application.setWindowIcon(QIcon::fromTheme(QStringLiteral("privacy-browser"), QIcon(QStringLiteral(":/icons/sc-apps-privacybrowser.svg"))));
 
     // Create a command line parser.
     QCommandLineParser commandLineParser;
@@ -81,9 +83,14 @@ int main(int argc, char *argv[])
     // Register with D-Bus, allowing multiple instances and allowing the program to run if for some reason the registration fails.
     KDBusService appDBusService(KDBusService::Multiple | KDBusService::NoExitOnFailure);
 
+    // Create the app data location directory if it doesn't currently exist.  This directory is used to store the databases in the subsequent commands.
+    // The first directory in the list should be the private, writable location, which on Linux should be `/home/user/.local/share/privacybrowser`.
+    QDir().mkdir(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).first());
+
     // Add the databases.
-    DomainsDatabase::addDatabase();
+    BookmarksDatabase::addDatabase();
     CookiesDatabase::addDatabase();
+    DomainsDatabase::addDatabase();
 
     // Create the main window.
     BrowserWindow *browserWindowPointer = new BrowserWindow();
diff --git a/src/privacybrowser.1 b/src/privacybrowser.1
new file mode 100644 (file)
index 0000000..828e6de
--- /dev/null
@@ -0,0 +1,95 @@
+.\" Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+.\"
+.\" This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+
+.\" Title Header.
+.TH "Privacy Browser" 1 "12 October 2023" "version 0.5" "Privacy Browser User Manual"
+
+.\" Section Header - Name.
+.SH NAME
+privacybrowser \- a web browser that respects your privacy
+
+
+.\" Section Header - Synopsis.
+.SH SYNOPSIS
+
+.\" Launching homepage.  `.B` is "Bold".
+.B privacybrowser
+.br
+
+.\" Launching a URL.  `.B` is "Bold".  `.RI` is "Roman (not italic) alternating Italic (underlined)".
+.B privacybrowser
+.RI [ url ]
+.br
+
+.\" Options.  `.B` is "Bold".  `.RI` is "Roman (not italic) alternating Italic (underlined)".
+.B privacybrowser
+.RI [ option ]
+
+
+.\" Section Header - Description.
+.SH DESCRIPTION
+Privacy Browser is a web browser focused on user privacy.  It uses Qt WebEngine to render websites, which is a modified version of Chromium’s rendering engine.
+
+
+.\" Section Header - Options.
+.SH OPTIONS
+
+.\" Help.  `.TP` is "Tag Paragraph".  `.BR` is "Bold alternating Roman (not bold)".
+.TP
+.BR -h ", " --help
+Displays help for command line options.
+
+.\" Help all.  `.TP` is "Tag Paragraph".  `.B` is "Bold".
+.TP
+.B --help-all
+Displays help, including Qt specific options.
+
+.\" Version.  `.TP` is "Tag Paragraph".  `.BR` is "Bold alternating Roman (not bold)".
+.TP
+.BR -v ", " --version
+Displays version information.
+
+.\" Author.  `.TP` is "Tag Paragraph".  `.B` is "Bold".
+.TP
+.B --author
+Show author information.
+
+.\" License.  `.TP` is "Tag Paragraph".  `.B` is "Bold".
+.TP
+.B --license
+Show license information.
+
+.\" Desktop File Name.  `.TP` is "Tag Paragraph".  `.BI` is "Bold alternating Italic (underlined)".
+.TP
+.BI --desktopfile " file"
+The base file name of the desktop entry for this application.
+
+
+.\" Section Header - Bugs.  `.TP` is "Tag Paragraph".
+.SH BUGS
+Privacy Browser is an early alpha release.  As such, many features are not yet implemented.  More information can be found at:
+.TP
+https://www.stoutner.com/privacy-browser-pc/
+.TP
+Bugs in currently implemented features may be reported at:
+.TP
+https://redmine.stoutner.com/projects/privacy-browser-pc/issues
+
+
+.\" Section Header - Author.
+.SH AUTHOR
+Soren Stoutner <soren@stoutner.com>
diff --git a/src/privacybrowser.notifyrc b/src/privacybrowser.notifyrc
new file mode 100644 (file)
index 0000000..08915de
--- /dev/null
@@ -0,0 +1,27 @@
+# Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
+#
+# This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+
+[Global]
+IconName=sc-apps-privacybrowser
+Name=Privacy Browser
+Comment=A browser that respects your privacy.
+DesktopEntry=com.stoutner.privacybrowser.desktop
+
+[Event/FileDownload]
+Name=File Download
+Comment=The file has started downloading
+Action=Popup
index cfa6f792ba5ffb28989605ecb2ed2f8579d989f4..c55f51857bdb172152aa1a18700bb23f141e490c 100644 (file)
@@ -1,7 +1,7 @@
 <!--
-  Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
@@ -20,8 +20,9 @@
 <RCC version="1.0">
     <qresource>
         <file>icons/javascript-warning.svg</file>
+        <file>icons/loading.gif</file>
         <file>icons/privacy-mode.svg</file>
-        <file>icons/sc-apps-privacy-browser.svg</file>
+        <file>icons/sc-apps-privacybrowser.svg</file>
         <file>licenses/GPLv3+.txt</file>
     </qresource>
 </RCC>
index 04a74ca534d9f62a8de0e50560fed339115ea144..3607793f4cd49bc9a1164a82551ad2c089d495d2 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
   You should have received a copy of the GNU General Public License
   along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>. -->
 
-<!-- The options are partially documented at <https://api.kde.org/frameworks/kconfig/html/classKConfigSkeleton.html>. -->
+<!-- The options are partially documented at the following URLs:
+    <https://api.kde.org/frameworks/kconfig/html/classKConfigSkeleton.html>
+    <https://api.kde.org/frameworks/kconfig/html/classKCoreConfigSkeleton.html> -->
 <kcfg
     xmlns="http://www.kde.org/standards/kcfg/1.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
 
-    <!-- This is the name of the file located in `~/.config/` where the settings are stored. -->
+    <!-- The name of the file located in `~/.config/` where the settings are stored. -->
     <kcfgfile name="privacybrowser"/>
 
     <group name="Privacy">
             <default>1.00</default>
         </entry>
 
-        <entry name="downloadLocation" type="String">
+        <entry name="downloadDirectory" type="String">
             <default>System Download Directory</default>
         </entry>
 
+        <entry name="autoUpateDownloadDirectory" type="Bool">
+            <default>true</default>
+        </entry>
+
         <entry name="tabsOnTop" type="Bool">
             <default>true</default>
         </entry>
 
+        <entry name="spatialNavigation" type="Bool">
+            <default>true</default>
+        </entry>
+
         <entry name="fullScreenHideMenuBar" type="Bool">
             <default>true</default>
         </entry>
             <default>true</default>
         </entry>
     </group>
+
+    <group name="SpellCheck">
+        <entry name="spellCheckLanguages" type="StringList">
+            <default></default>
+        </entry>
+    </group>
 </kcfg>
index 42940f7c9921c8cb1c0710c024cce03207757657..3cb234cfc330ccc5a267e125d3d0a6a8f7d5f497 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
@@ -15,6 +15,9 @@
 # You should have received a copy of the GNU General Public License
 # along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>.
 
+# The options for this file are partially specified at the following URLs:
+# <https://api.kde.org/frameworks/kconfig/html/kconfig_compiler.html>
+# <https://develop.kde.org/docs/use/configuration/kconfig_xt/>
 
 # Specify the KConfig file.
 File=Settings.kcfg
@@ -24,3 +27,6 @@ ClassName=Settings
 
 # Make the generated class a singleton.
 Singleton=true
+
+# List of variables that can be manually changed.
+Mutators=spellCheckLanguages,downloadDirectory
diff --git a/src/structs/BookmarkStruct.h b/src/structs/BookmarkStruct.h
new file mode 100644 (file)
index 0000000..d7e0a86
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BOOKMARKSTRUCT_H
+#define BOOKMARKSTRUCT_H
+
+// Qt framework headers.
+#include <QIcon>
+#include <QString>
+
+struct BookmarkStruct
+{
+    int databaseId;
+    QString name;
+    QString url;
+    double parentFolderId;
+    int displayOrder;
+    bool isFolder;
+    double folderId;
+    QIcon favoriteIcon;
+};
+#endif
index 2ac4766fd0b6b83a5b5e61dee8c102c71e4989c0..dc4dcc8926c2caf5ad2a62a05d4e7cc61e5a8076 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
@@ -18,5 +18,5 @@
 
 # Install Privacy Browser's RC (Runtime Configuration) files.
 install(FILES
-    browser_window_ui.rc
+    browserwindowui.rc
     DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/privacybrowser)
diff --git a/src/ui.rcs/browser_window_ui.rc b/src/ui.rcs/browser_window_ui.rc
deleted file mode 100644 (file)
index 6722426..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
-  Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
-
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
-
-  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 <http://www.gnu.org/licenses/>. -->
-
-<!-- Documentation at <https://techbase.kde.org/Development/Architecture/KDE4/XMLGUI_Technology>. -->
-<!-- Better documentation at <https://invent.kde.org/frameworks/kxmlgui/-/blob/master/src/kxmlgui.xsd>. -->
-<!-- This file interacts with the default template located at /etc/xdg/ui/ui_standards.rc, which is part of the libkf5xmlgui-data package. -->
-<gui
-    name="privacybrowser"
-    version="1"
-    xmlns="http://www.kde.org/standards/kxmlgui/1.0"
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0 http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd" >
-
-    <!-- The menu bar. -->
-    <MenuBar>
-        <!-- On-the-fly Settings. -->
-        <Menu name="on_the_fly_settings"> <text>On-The-Fly Settings</text>
-            <Menu name="user_agent" icon="user-group-properties">
-                <Action name="user_agent_privacy_browser" />
-                <Action name="user_agent_webengine_default" />
-                <Action name="user_agent_firefox_linux" />
-                <Action name="user_agent_chromium_linux" />
-                <Action name="user_agent_firefox_windows" />
-                <Action name="user_agent_chrome_windows" />
-                <Action name="user_agent_edge_windows" />
-                <Action name="user_agent_safari_macos" />
-                <Action name="user_agent_custom" />
-            </Menu>
-
-            <Action name="zoom_factor" />
-
-            <Separator />
-
-            <Menu name="search_engine" icon="search">
-                <Action name="search_engine_mojeek" />
-                <Action name="search_engine_monocles" />
-                <Action name="search_engine_metager" />
-                <Action name="search_engine_google" />
-                <Action name="search_engine_bing" />
-                <Action name="search_engine_yahoo" />
-                <Action name="search_engine_custom" />
-            </Menu>
-        </Menu>
-
-        <!-- Settings. -->
-        <Menu name="settings">
-            <Action name="domain_settings" />
-            <Action name="cookies" />
-        </Menu>
-    </MenuBar>
-
-    <!-- The main toolbar is removed. -->
-    <ToolBar name="mainToolBar" deleted="true" />
-
-    <!-- The navigation toolbar. -->
-    <ToolBar name="navigation_toolbar" iconText="icononly"> <text>Navigation Toolbar</text>
-        <Action name="go_back" />
-        <Action name="go_forward" />
-        <Action name="view_redisplay" />
-        <Action name="go_home" />
-    </ToolBar>
-
-    <!-- The URL toolbar. -->
-    <ToolBar name="url_toolbar" iconText="icononly"> <text>URL Toolbar</text>
-        <Action name="javascript" />
-        <Action name="local_storage" />
-        <Action name="dom_storage" />
-        <Action name="edit_find_next" />
-        <Action name="edit_find_prev" />
-        <Action name="find_case_sensitive" />
-    </ToolBar>
-</gui>
diff --git a/src/ui.rcs/browserwindowui.rc b/src/ui.rcs/browserwindowui.rc
new file mode 100644 (file)
index 0000000..9953db6
--- /dev/null
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>. -->
+
+<!-- Documentation at <https://techbase.kde.org/Development/Architecture/KDE4/XMLGUI_Technology>. -->
+<!-- Better documentation at <https://invent.kde.org/frameworks/kxmlgui/-/blob/master/src/kxmlgui.xsd>. -->
+<!-- This file interacts with the default template located at /etc/xdg/ui/ui_standards.rc, which is part of the libkf5xmlgui-data package. -->
+<gui
+    name="privacybrowser"
+    version="1"
+    xmlns="http://www.kde.org/standards/kxmlgui/1.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0 http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd" >
+
+    <!-- The menu bar. -->
+    <MenuBar>
+        <!-- File. -->
+        <Menu name="file">
+            <Action name="new_tab" append="new_merge" />
+            <Action name="new_window" append="new_merge" />
+
+            <Action name="save_archive" append="save_merge" />
+        </Menu>
+
+        <!-- View. -->
+        <Menu name="view">
+            <Action name="zoom_default" append="view_zoom_merge" />
+
+            <Action name="reload_and_bypass_cache" />
+            <Action name="stop" />
+
+            <Separator />
+
+            <Action name="view_source" />
+            <Action name="view_source_in_new_tab" />
+            <Action name="developer_tools" />
+        </Menu>
+
+        <!-- On-the-fly Settings. -->
+        <Menu name="on_the_fly_settings"> <text>On-The-Fly Settings</text>
+            <Action name="javascript" />
+            <Action name="local_storage" />
+            <Action name="dom_storage" />
+
+            <Menu name="user_agent">
+                <Action name="user_agent_privacy_browser" />
+                <Action name="user_agent_webengine_default" />
+                <Action name="user_agent_firefox_linux" />
+                <Action name="user_agent_chromium_linux" />
+                <Action name="user_agent_firefox_windows" />
+                <Action name="user_agent_chrome_windows" />
+                <Action name="user_agent_edge_windows" />
+                <Action name="user_agent_safari_macos" />
+                <Action name="user_agent_custom" />
+            </Menu>
+
+            <Action name="zoom_factor" />
+
+            <Separator />
+
+            <Menu name="search_engine">
+                <Action name="search_engine_mojeek" />
+                <Action name="search_engine_monocles" />
+                <Action name="search_engine_metager" />
+                <Action name="search_engine_google" />
+                <Action name="search_engine_bing" />
+                <Action name="search_engine_yahoo" />
+                <Action name="search_engine_custom" />
+            </Menu>
+        </Menu>
+
+        <!-- Bookmarks. -->
+        <Menu name="bookmarks">
+            <Action name="view_bookmarks_toolbar" />
+
+            <Separator />
+        </Menu>
+
+        <!-- Settings. -->
+        <Menu name="settings">
+            <Action name="domain_settings" />
+            <Action name="cookies" />
+        </Menu>
+    </MenuBar>
+
+    <!-- The main toolbar is removed. -->
+    <ToolBar name="mainToolBar" deleted="true" />
+
+    <!-- The navigation toolbar. -->
+    <ToolBar name="navigation_toolbar" iconText="icononly"> <text>Navigation Toolbar</text>
+        <Action name="go_back" />
+        <Action name="go_forward" />
+        <Action name="view_redisplay" />
+        <Action name="stop" />
+        <Action name="go_home" />
+    </ToolBar>
+
+    <!-- The URL toolbar. -->
+    <ToolBar name="url_toolbar" iconText="icononly"> <text>URL Toolbar</text>
+        <Action name="javascript" />
+        <Action name="local_storage" />
+        <Action name="dom_storage" />
+        <Action name="edit_find_next" />
+        <Action name="edit_find_prev" />
+        <Action name="find_case_sensitive" />
+        <Action name="hide_find_actions" />
+    </ToolBar>
+
+    <!-- The bookmarks toolbar. The newline aspect of this doesn't currently work. -->
+    <ToolBar name="bookmarks_toolbar" iconText="icontextright" newline="true" hidden="true"> <text>Bookmarks Toolbar</text> </ToolBar>
+</gui>
diff --git a/src/uis/AddBookmarkDialog.ui b/src/uis/AddBookmarkDialog.ui
new file mode 100644 (file)
index 0000000..d954863
--- /dev/null
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>. -->
+
+<ui version="4.0">
+    <class>AddBookmarkDialog</class>
+
+    <widget class="QWidget">
+        <layout class="QVBoxLayout">
+            <!-- Dialog body. -->
+            <item>
+                <layout class="QHBoxLayout">
+                    <!-- Favorite icon column. -->
+                    <item>
+                        <layout class="QVBoxLayout">
+                            <property name="rightMargin">
+                                <number>10</number>
+                            </property>
+
+                            <!-- First row, website favorite icon. -->
+                            <item>
+                                <widget class="QRadioButton" name="websiteFavoriteIconRadioButton">
+                                    <property name="text">
+                                        <string>Website favorite icon</string>
+                                    </property>
+
+                                    <property name="checked">
+                                        <bool>true</bool>
+                                    </property>
+
+                                    <property name="iconSize">
+                                        <size>
+                                            <height>32</height>
+                                            <width>32</width>
+                                        </size>
+                                    </property>
+                                </widget>
+                            </item>
+
+                            <!-- Second row, custom favorite icon. -->
+                            <item>
+                                <widget class="QRadioButton" name="customFavoriteIconRadioButton">
+                                    <property name="text">
+                                        <string>Custom favorite icon</string>
+                                    </property>
+
+                                    <property name="iconSize">
+                                        <size>
+                                            <height>32</height>
+                                            <width>32</width>
+                                        </size>
+                                    </property>
+                                </widget>
+                            </item>
+
+                            <!-- Spacer. -->
+                            <item>
+                                <spacer>
+                                    <property name="orientation">
+                                        <enum>Qt::Vertical</enum>
+                                    </property>
+                                </spacer>
+                            </item>
+                        </layout>
+                    </item>
+
+                    <!-- Parent folder, name and URL column. -->
+                    <item>
+                        <layout class="QVBoxLayout">
+                            <!-- First row. -->
+                            <item>
+                                <layout class="QHBoxLayout">
+                                    <property name="alignment">
+                                        <enum>Qt::AlignLeft</enum>
+                                    </property>
+
+                                    <!-- Parent folder.  -->
+                                    <item>
+                                        <widget class="QTreeWidget" name="parentFolderTreeWidget">
+                                            <property name="minimumSize">
+                                                <size>
+                                                    <width>1000</width>
+                                                    <height>700</height>
+                                                </size>
+                                            </property>
+                                        </widget>
+                                    </item>
+                                </layout>
+                            </item>
+
+                            <!-- Second row. -->
+                            <item>
+                                <layout class="QHBoxLayout">
+                                    <property name="alignment">
+                                        <enum>Qt::AlignLeft</enum>
+                                    </property>
+
+                                    <!-- Bookmark name.  -->
+                                    <item>
+                                        <widget class="QLabel">
+                                            <property name="toolTip">
+                                                <string>The name of the bookmark.</string>
+                                            </property>
+
+                                            <property name="text">
+                                                <string>Bookmark name</string>
+                                            </property>
+                                        </widget>
+                                    </item>
+
+                                    <item>
+                                        <widget class="QLineEdit" name="bookmarkNameLineEdit" />
+                                    </item>
+                                </layout>
+                            </item>
+
+                            <!-- Third row. -->
+                            <item>
+                                <layout class="QHBoxLayout">
+                                    <property name="alignment">
+                                        <enum>Qt::AlignLeft</enum>
+                                    </property>
+
+                                    <!-- Bookmark URL. -->
+                                    <item>
+                                        <widget class="QLabel">
+                                            <property name="toolTip">
+                                                <string>The URL of the bookmark.</string>
+                                            </property>
+
+                                            <property name="text">
+                                                <string>Bookmark URL</string>
+                                            </property>
+                                        </widget>
+                                    </item>
+
+                                    <item>
+                                        <widget class="QLineEdit" name="bookmarkUrlLineEdit" />
+                                    </item>
+                                </layout>
+                            </item>
+                        </layout>
+                    </item>
+                </layout>
+            </item>
+
+            <!-- Buttons. -->
+            <item>
+                <layout class="QHBoxLayout">
+                    <!-- Browse button. -->
+                    <item>
+                        <widget class="QPushButton" name="browseButton">
+                            <property name="text">
+                                <string>Browse</string>
+                            </property>
+
+                            <property name="icon">
+                                <iconset theme="insert-image" />
+                            </property>
+                        </widget>
+                    </item>
+
+                    <!-- Cancel button - dialog button box. -->
+                    <item>
+                        <widget class="QDialogButtonBox" name="dialogButtonBox">
+                            <property name="standardButtons">
+                                <set>QDialogButtonBox::Cancel</set>
+                            </property>
+                        </widget>
+                    </item>
+                </layout>
+            </item>
+        </layout>
+    </widget>
+</ui>
diff --git a/src/uis/AddFolderDialog.ui b/src/uis/AddFolderDialog.ui
new file mode 100644 (file)
index 0000000..752059e
--- /dev/null
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>. -->
+
+<ui version="4.0">
+    <class>AddFolderDialog</class>
+
+    <widget class="QWidget">
+        <layout class="QVBoxLayout">
+            <!-- Dialog body. -->
+            <item>
+                <layout class="QHBoxLayout">
+                    <!-- Favorite icon column. -->
+                    <item>
+                        <layout class="QVBoxLayout">
+                            <property name="rightMargin">
+                                <number>10</number>
+                            </property>
+
+                            <!-- First row, default folder icon. -->
+                            <item>
+                                <widget class="QRadioButton" name="defaultFolderIconRadioButton">
+                                    <property name="text">
+                                        <string>Default folder icon</string>
+                                    </property>
+
+                                    <property name="checked">
+                                        <bool>true</bool>
+                                    </property>
+
+                                    <property name="icon">
+                                        <iconset theme="folder"/>
+                                    </property>
+
+                                    <property name="iconSize">
+                                        <size>
+                                            <height>32</height>
+                                            <width>32</width>
+                                        </size>
+                                    </property>
+                                </widget>
+                            </item>
+
+                            <!-- Second row, current website favorite icon. -->
+                            <item>
+                                <widget class="QRadioButton" name="currentWebsiteFavoriteIconRadioButton">
+                                    <property name="text">
+                                        <string>Current website favorite icon</string>
+                                    </property>
+
+                                    <property name="iconSize">
+                                        <size>
+                                            <height>32</height>
+                                            <width>32</width>
+                                        </size>
+                                    </property>
+                                </widget>
+                            </item>
+
+                            <!-- Third row, custom folder icon. -->
+                            <item>
+                                <widget class="QRadioButton" name="customFolderIconRadioButton">
+                                    <property name="text">
+                                        <string>Custom folder icon</string>
+                                    </property>
+
+                                    <property name="icon">
+                                        <iconset theme="folder-symbolic"/>
+                                    </property>
+
+                                    <property name="iconSize">
+                                        <size>
+                                            <height>32</height>
+                                            <width>32</width>
+                                        </size>
+                                    </property>
+                                </widget>
+                            </item>
+
+                            <!-- Spacer. -->
+                            <item>
+                                <spacer>
+                                    <property name="orientation">
+                                        <enum>Qt::Vertical</enum>
+                                    </property>
+                                </spacer>
+                            </item>
+                        </layout>
+                    </item>
+
+                    <!-- Parent folder and name column. -->
+                    <item>
+                        <layout class="QVBoxLayout">
+                            <!-- First row. -->
+                            <item>
+                                <layout class="QHBoxLayout">
+                                    <property name="alignment">
+                                        <enum>Qt::AlignLeft</enum>
+                                    </property>
+
+                                    <!-- Parent folder.  -->
+                                    <item>
+                                        <widget class="QTreeWidget" name="parentFolderTreeWidget">
+                                            <property name="minimumSize">
+                                                <size>
+                                                    <width>1000</width>
+                                                    <height>700</height>
+                                                </size>
+                                            </property>
+                                        </widget>
+                                    </item>
+                                </layout>
+                            </item>
+
+                            <!-- Second row. -->
+                            <item>
+                                <layout class="QHBoxLayout">
+                                    <property name="alignment">
+                                        <enum>Qt::AlignLeft</enum>
+                                    </property>
+
+                                    <!-- Folder name. -->
+                                    <item>
+                                        <widget class="QLabel">
+                                            <property name="toolTip">
+                                                <string>The folder name.</string>
+                                            </property>
+
+                                            <property name="text">
+                                                <string>Folder name</string>
+                                            </property>
+                                        </widget>
+                                    </item>
+
+                                    <item>
+                                        <widget class="QLineEdit" name="folderNameLineEdit" />
+                                    </item>
+                                </layout>
+                            </item>
+                        </layout>
+                    </item>
+                </layout>
+            </item>
+
+            <!-- Buttons. -->
+            <item>
+                <layout class="QHBoxLayout">
+                    <!-- Browse button. -->
+                    <item>
+                        <widget class="QPushButton" name="browseButton">
+                            <property name="text">
+                                <string>Browse</string>
+                            </property>
+
+                            <property name="icon">
+                                <iconset theme="insert-image" />
+                            </property>
+                        </widget>
+                    </item>
+
+                    <!-- Cancel button - dialog button box. -->
+                    <item>
+                        <widget class="QDialogButtonBox" name="dialogButtonBox">
+                            <property name="standardButtons">
+                                <set>QDialogButtonBox::Cancel</set>
+                            </property>
+                        </widget>
+                    </item>
+                </layout>
+            </item>
+        </layout>
+    </widget>
+</ui>
index 3595e741af800902ad792101fba358d7a51f7ca5..779b8fb395db60a87f536238b21f2f3836e1590b 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2022 Soren Stoutner <soren@stoutner.com>.
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
index 1a7177f51788070c0bd823b2d99f05056cea42f4..74d3c5713b11fea3c5ef4011e6205619e08a31d6 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2022 Soren Stoutner <soren@stoutner.com>.
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
diff --git a/src/uis/BookmarksDialog.ui b/src/uis/BookmarksDialog.ui
new file mode 100644 (file)
index 0000000..fb7c4bb
--- /dev/null
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>. -->
+
+<ui version="4.0">
+    <class>BookmarksDialog</class>
+
+    <customwidgets>
+        <customwidget>
+            <class>DraggableTreeView</class>
+            <extends>QTreeView</extends>
+            <header>widgets/DraggableTreeView.h</header>
+        </customwidget>
+    </customwidgets>
+
+    <widget class="QWidget">
+        <property name="geometry">
+            <rect>
+                <x>0</x>
+                <y>0</y>
+                <height>1000</height>
+                <width>1500</width>
+            </rect>
+        </property>
+
+        <layout class="QVBoxLayout">
+            <!-- Tree view. -->
+            <item>
+                <widget class="DraggableTreeView" name="draggableTreeView" />
+            </item>
+
+            <!-- Buttons. -->
+            <item>
+                <layout class="QHBoxLayout">
+                    <!-- Add bookmark button. -->
+                    <item>
+                        <widget class="QPushButton" name="addBookmarkButton">
+                            <property name="text">
+                                <string>Add bookmark</string>
+                            </property>
+
+                            <property name="icon">
+                                <iconset theme="list-add" />
+                            </property>
+                        </widget>
+                    </item>
+
+                    <!-- Add folder button. -->
+                    <item>
+                        <widget class="QPushButton" name="addFolderButton">
+                            <property name="text">
+                                <string>Add folder</string>
+                            </property>
+
+                            <property name="icon">
+                                <iconset theme="folder-add" />
+                            </property>
+                        </widget>
+                    </item>
+
+                    <!-- Edit button. -->
+                    <item>
+                        <widget class="QPushButton" name="editButton">
+                            <property name="text">
+                                <string>Edit</string>
+                            </property>
+
+                            <property name="icon">
+                                <iconset theme="document-edit" />
+                            </property>
+                        </widget>
+                    </item>
+
+                    <!-- Delete button. -->
+                    <item>
+                        <widget class="QPushButton" name="deleteItemsButton">
+                            <property name="text">
+                                <string>Delete items</string>
+                            </property>
+
+                            <property name="icon">
+                                <iconset theme="list-remove" />
+                            </property>
+                        </widget>
+                    </item>
+
+                    <!-- Close button - dialog button box. -->
+                    <item>
+                        <widget class="QDialogButtonBox" name="dialogButtonBox">
+                            <property name="standardButtons">
+                                <set>QDialogButtonBox::Close</set>
+                            </property>
+                        </widget>
+                    </item>
+                </layout>
+            </item>
+        </layout>
+    </widget>
+</ui>
index bd2c24fce32202929ab759140c3708909162e67e..c5436b184fa9cdf88ec8ad0d48bbf0f3f24e13d8 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
@@ -21,7 +21,6 @@
 <ui version="4.0">
     <class>CookiesDialog</class>
 
-
     <widget class="QWidget">
         <property name="geometry">
             <rect>
@@ -62,7 +61,7 @@
                             </property>
 
                             <property name="icon">
-                                <iconset theme="edit-entry" />
+                                <iconset theme="document-edit" />
                             </property>
                         </widget>
                     </item>
@@ -88,7 +87,7 @@
                             </property>
 
                             <property name="icon">
-                                <iconset theme="delete" />
+                                <iconset theme="edit-delete" />
                             </property>
                         </widget>
                     </item>
index f0893630a760fb3c70f825efc597c04b6a166859..c432b7c240f95ed725aeef65543ce4e25ac34e32 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
 
                                                     <item>
                                                         <property name="text">
-                                                            <string>JavaScript disabled</string>
+                                                            <string>JavaScript enabled</string>
                                                         </property>
                                                     </item>
 
                                                     <item>
                                                         <property name="text">
-                                                            <string>JavaScript enabled</string>
+                                                            <string>JavaScript disabled</string>
                                                         </property>
                                                     </item>
                                                 </widget>
                                                     </property>
 
                                                     <property name="toolTip">
-                                                        <string>Local storage includes cookies, IndexedDB, DOM storage, filesystem API, and service workers.  DOM storage also requires a separate control to be enabled.  Local storage is disabled by default.</string>
+                                                        <string>Local storage includes cookies, DOM storage, IndexedDB, service workers, and the filesystem API.  DOM storage also requires a separate control to be enabled.  Local storage is disabled by default.</string>
                                                     </property>
                                                 </widget>
                                             </item>
 
                                                     <item>
                                                         <property name="text">
-                                                            <string>Local storage disabled</string>
+                                                            <string>Local storage enabled</string>
                                                         </property>
                                                     </item>
 
                                                     <item>
                                                         <property name="text">
-                                                            <string>Local storage enabled</string>
+                                                            <string>Local storage disabled</string>
                                                         </property>
                                                     </item>
                                                 </widget>
 
                                                     <item>
                                                         <property name="text">
-                                                            <string>DOM storage disabled</string>
+                                                            <string>DOM storage enabled</string>
                                                         </property>
                                                     </item>
 
                                                     <item>
                                                         <property name="text">
-                                                            <string>DOM storage enabled</string>
+                                                            <string>DOM storage disabled</string>
                                                         </property>
                                                     </item>
                                                 </widget>
index d1ee5222d91608e75855d224f812749dc23ea6ea..65fea7448039c323e3e2228b8ec036e023a03b0d 100644 (file)
@@ -3,7 +3,7 @@
 <!--
   Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
diff --git a/src/uis/EditBookmarkDialog.ui b/src/uis/EditBookmarkDialog.ui
new file mode 100644 (file)
index 0000000..1ecfd7a
--- /dev/null
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>. -->
+
+<ui version="4.0">
+    <class>EditBookmarkDialog</class>
+
+    <widget class="QWidget">
+        <layout class="QVBoxLayout">
+            <!-- Dialog body. -->
+            <item>
+                <layout class="QHBoxLayout">
+                    <!-- Favorite icon column. -->
+                    <item>
+                        <layout class="QVBoxLayout">
+                            <property name="rightMargin">
+                                <number>10</number>
+                            </property>
+
+                            <!-- First row, current favorite icon. -->
+                            <item>
+                                <widget class="QRadioButton" name="currentFavoriteIconRadioButton">
+                                    <property name="text">
+                                        <string>Current favorite icon</string>
+                                    </property>
+
+                                    <property name="checked">
+                                        <bool>true</bool>
+                                    </property>
+
+                                    <property name="iconSize">
+                                        <size>
+                                            <height>32</height>
+                                            <width>32</width>
+                                        </size>
+                                    </property>
+                                </widget>
+                            </item>
+
+                            <!-- Second row, current website favorite icon. -->
+                            <item>
+                                <widget class="QRadioButton" name="currentWebsiteFavoriteIconRadioButton">
+                                    <property name="text">
+                                        <string>Current website favorite icon</string>
+                                    </property>
+
+                                    <property name="iconSize">
+                                        <size>
+                                            <height>32</height>
+                                            <width>32</width>
+                                        </size>
+                                    </property>
+                                </widget>
+                            </item>
+
+                            <!-- Third row, custom favorite icon. -->
+                            <item>
+                                <widget class="QRadioButton" name="customFavoriteIconRadioButton">
+                                    <property name="text">
+                                        <string>Custom favorite icon</string>
+                                    </property>
+
+                                    <property name="iconSize">
+                                        <size>
+                                            <height>32</height>
+                                            <width>32</width>
+                                        </size>
+                                    </property>
+                                </widget>
+                            </item>
+
+                            <!-- Spacer. -->
+                            <item>
+                                <spacer>
+                                    <property name="orientation">
+                                        <enum>Qt::Vertical</enum>
+                                    </property>
+                                </spacer>
+                            </item>
+                        </layout>
+                    </item>
+
+                    <!-- Parent folder, name and URL column. -->
+                    <item>
+                        <layout class="QVBoxLayout">
+                            <!-- First row. -->
+                            <item>
+                                <layout class="QHBoxLayout">
+                                    <property name="alignment">
+                                        <enum>Qt::AlignLeft</enum>
+                                    </property>
+
+                                    <!-- Parent folder.  -->
+                                    <item>
+                                        <widget class="QTreeWidget" name="parentFolderTreeWidget">
+                                            <property name="minimumSize">
+                                                <size>
+                                                    <width>1000</width>
+                                                    <height>700</height>
+                                                </size>
+                                            </property>
+                                        </widget>
+                                    </item>
+                                </layout>
+                            </item>
+
+                            <!-- Second row. -->
+                            <item>
+                                <layout class="QHBoxLayout">
+                                    <property name="alignment">
+                                        <enum>Qt::AlignLeft</enum>
+                                    </property>
+
+                                    <!-- Bookmark name.  -->
+                                    <item>
+                                        <widget class="QLabel">
+                                            <property name="toolTip">
+                                                <string>The name of the bookmark.</string>
+                                            </property>
+
+                                            <property name="text">
+                                                <string>Bookmark name</string>
+                                            </property>
+                                        </widget>
+                                    </item>
+
+                                    <item>
+                                        <widget class="QLineEdit" name="bookmarkNameLineEdit" />
+                                    </item>
+                                </layout>
+                            </item>
+
+                            <!-- Second row. -->
+                            <item>
+                                <layout class="QHBoxLayout">
+                                    <property name="alignment">
+                                        <enum>Qt::AlignLeft</enum>
+                                    </property>
+
+                                    <!-- Bookmark URL. -->
+                                    <item>
+                                        <widget class="QLabel">
+                                            <property name="toolTip">
+                                                <string>The URL of the bookmark.</string>
+                                            </property>
+
+                                            <property name="text">
+                                                <string>Bookmark URL</string>
+                                            </property>
+                                        </widget>
+                                    </item>
+
+                                    <item>
+                                        <widget class="QLineEdit" name="bookmarkUrlLineEdit">
+                                            <property name="sizePolicy">
+                                                <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+                                                    <horstretch>0</horstretch>
+                                                    <verstretch>0</verstretch>
+                                                </sizepolicy>
+                                            </property>
+
+                                            <property name="minimumSize">
+                                                <size>
+                                                    <width>700</width>
+                                                    <height>0</height>
+                                                </size>
+                                            </property>
+                                        </widget>
+                                    </item>
+                                </layout>
+                            </item>
+                        </layout>
+                    </item>
+                </layout>
+            </item>
+
+            <!-- Buttons. -->
+            <item>
+                <layout class="QHBoxLayout">
+                    <!-- Browse button. -->
+                    <item>
+                        <widget class="QPushButton" name="browseButton">
+                            <property name="text">
+                                <string>Browse</string>
+                            </property>
+
+                            <property name="icon">
+                                <iconset theme="insert-image" />
+                            </property>
+                        </widget>
+                    </item>
+
+                    <!-- Cancel button - dialog button box. -->
+                    <item>
+                        <widget class="QDialogButtonBox" name="dialogButtonBox">
+                            <property name="standardButtons">
+                                <set>QDialogButtonBox::Save | QDialogButtonBox::Cancel</set>
+                            </property>
+                        </widget>
+                    </item>
+                </layout>
+            </item>
+        </layout>
+    </widget>
+</ui>
diff --git a/src/uis/EditFolderDialog.ui b/src/uis/EditFolderDialog.ui
new file mode 100644 (file)
index 0000000..efebd2a
--- /dev/null
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>. -->
+
+<ui version="4.0">
+    <class>EditFolderDialog</class>
+
+    <widget class="QWidget">
+        <layout class="QVBoxLayout">
+            <!-- Dialog body. -->
+            <item>
+                <layout class="QHBoxLayout">
+                    <!-- Favorite icon column. -->
+                    <item>
+                        <layout class="QVBoxLayout">
+                            <property name="rightMargin">
+                                <number>10</number>
+                            </property>
+
+                            <!-- First row, current folder icon. -->
+                            <item>
+                                <widget class="QRadioButton" name="currentFolderIconRadioButton">
+                                    <property name="text">
+                                        <string>Current folder icon</string>
+                                    </property>
+
+                                    <property name="checked">
+                                        <bool>true</bool>
+                                    </property>
+
+                                    <property name="iconSize">
+                                        <size>
+                                            <height>32</height>
+                                            <width>32</width>
+                                        </size>
+                                    </property>
+                                </widget>
+                            </item>
+
+                            <!-- Second row, current folder icon. -->
+                            <item>
+                                <widget class="QRadioButton" name="defaultFolderIconRadioButton">
+                                    <property name="text">
+                                        <string>Default folder icon</string>
+                                    </property>
+
+                                    <property name="icon">
+                                        <iconset theme="folder"/>
+                                    </property>
+
+                                    <property name="iconSize">
+                                        <size>
+                                            <height>32</height>
+                                            <width>32</width>
+                                        </size>
+                                    </property>
+                                </widget>
+                            </item>
+
+                            <!-- Third row, current website favorite icon. -->
+                            <item>
+                                <widget class="QRadioButton" name="currentWebsiteFavoriteIconRadioButton">
+                                    <property name="text">
+                                        <string>Current website favorite icon</string>
+                                    </property>
+
+                                    <property name="iconSize">
+                                        <size>
+                                            <height>32</height>
+                                            <width>32</width>
+                                        </size>
+                                    </property>
+                                </widget>
+                            </item>
+
+                            <!-- Fourth row, custom folder icon. -->
+                            <item>
+                                <widget class="QRadioButton" name="customFolderIconRadioButton">
+                                    <property name="text">
+                                        <string>Custom folder icon</string>
+                                    </property>
+
+                                    <property name="icon">
+                                        <iconset theme="folder-symbolic"/>
+                                    </property>
+
+                                    <property name="iconSize">
+                                        <size>
+                                            <height>32</height>
+                                            <width>32</width>
+                                        </size>
+                                    </property>
+                                </widget>
+                            </item>
+
+                            <!-- Spacer. -->
+                            <item>
+                                <spacer>
+                                    <property name="orientation">
+                                        <enum>Qt::Vertical</enum>
+                                    </property>
+                                </spacer>
+                            </item>
+                        </layout>
+                    </item>
+
+                    <!-- Parent folder and name column. -->
+                    <item>
+                        <layout class="QVBoxLayout">
+                            <!-- First row. -->
+                            <item>
+                                <layout class="QHBoxLayout">
+                                    <property name="alignment">
+                                        <enum>Qt::AlignLeft</enum>
+                                    </property>
+
+                                    <!-- Parent folder.  -->
+                                    <item>
+                                        <widget class="QTreeWidget" name="parentFolderTreeWidget">
+                                            <property name="minimumSize">
+                                                <size>
+                                                    <width>1000</width>
+                                                    <height>700</height>
+                                                </size>
+                                            </property>
+                                        </widget>
+                                    </item>
+                                </layout>
+                            </item>
+
+                            <!-- Second row. -->
+                            <item>
+                                <layout class="QHBoxLayout">
+                                    <property name="alignment">
+                                        <enum>Qt::AlignLeft</enum>
+                                    </property>
+
+                                    <!-- Folder name. -->
+                                    <item>
+                                        <widget class="QLabel">
+                                            <property name="toolTip">
+                                                <string>The folder name.</string>
+                                            </property>
+
+                                            <property name="text">
+                                                <string>Folder name</string>
+                                            </property>
+                                        </widget>
+                                    </item>
+
+                                    <item>
+                                        <widget class="QLineEdit" name="folderNameLineEdit" />
+                                    </item>
+                                </layout>
+                            </item>
+                        </layout>
+                    </item>
+                </layout>
+            </item>
+
+            <!-- Buttons. -->
+            <item>
+                <layout class="QHBoxLayout">
+                    <!-- Browse button. -->
+                    <item>
+                        <widget class="QPushButton" name="browseButton">
+                            <property name="text">
+                                <string>Browse</string>
+                            </property>
+
+                            <property name="icon">
+                                <iconset theme="insert-image" />
+                            </property>
+                        </widget>
+                    </item>
+
+                    <!-- Cancel button - dialog button box. -->
+                    <item>
+                        <widget class="QDialogButtonBox" name="dialogButtonBox">
+                            <property name="standardButtons">
+                                <set>QDialogButtonBox::Save | QDialogButtonBox::Cancel</set>
+                            </property>
+                        </widget>
+                    </item>
+                </layout>
+            </item>
+        </layout>
+    </widget>
+</ui>
diff --git a/src/uis/HttpAuthenticationDialog.ui b/src/uis/HttpAuthenticationDialog.ui
new file mode 100644 (file)
index 0000000..d5d9de0
--- /dev/null
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Copyright 2024 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>. -->
+
+<ui version="4.0">
+    <class>HttpAuthenticationDialog</class>
+
+    <widget class="QWidget">
+        <!-- Dialog body. -->
+        <layout class="QVBoxLayout">
+            <item>
+                <widget class="QLabel" name="realmLabel">
+                    <property name="font">
+                        <font>
+                            <pointsize>24</pointsize>
+                            <bold>true</bold>
+                        </font>
+                    </property>
+
+                    <property name="alignment">
+                        <set>Qt::AlignCenter</set>
+                    </property>
+                </widget>
+            </item>
+
+            <item>
+                <!-- Host. -->
+                <layout class="QFormLayout">
+                    <property name="formAlignment">
+                        <set>Qt::AlignHCenter|Qt::AlignTop</set>
+                    </property>
+
+                    <item row="0" column="0">
+                        <widget class="QLabel">
+                            <property name="text">
+                                <string>Host:</string>
+                            </property>
+                        </widget>
+                    </item>
+
+                    <item row="0" column ="1">
+                        <widget class="QLabel" name="hostLabel" />
+                    </item>
+                </layout>
+            </item>
+
+            <item>
+                <layout class="QFormLayout">
+                    <!-- Username. -->
+                    <item row="0" column="0">
+                        <widget class="QLabel">
+                            <property name="text">
+                                <string>Username</string>
+                            </property>
+                        </widget>
+                    </item>
+
+                    <item row="0" column="1">
+                        <widget class="KLineEdit" name="usernameLineEdit">
+                            <property name="minimumSize">
+                                <size>
+                                    <width>300</width>
+                                    <height>0</height>
+                                </size>
+                            </property>
+                        </widget>
+                    </item>
+
+                    <!-- Password. -->
+                    <item row="1" column="0">
+                        <widget class="QLabel">
+                            <property name="text">
+                                <string>Password</string>
+                            </property>
+                        </widget>
+                    </item>
+
+                    <item row="1" column="1">
+                        <widget class="KPasswordLineEdit" name="passwordLineEdit" />
+                    </item>
+                </layout>
+            </item>
+
+            <!-- Spacer. -->
+            <item>
+                <spacer>
+                    <property name="orientation">
+                        <enum>Qt::Vertical</enum>
+                    </property>
+                </spacer>
+            </item>
+
+            <!-- Buttons. -->
+            <item>
+                <widget class="QDialogButtonBox" name="dialogButtonBox">
+                    <property name="standardButtons">
+                        <set>QDialogButtonBox::Ok | QDialogButtonBox::Cancel</set>
+                    </property>
+                </widget>
+            </item>
+        </layout>
+    </widget>
+</ui>
index b8e3dfa4b0ae95dcd1fece7f104bf1884ea7a7ef..d9d2ceff1f3fa043e88e4481e533655523858093 100644 (file)
@@ -3,7 +3,7 @@
 <!--
   Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
index 223ec5b355781170960ff04b55b5221be1e69da4..15f776fa8b392b08b3f9fe37eb68e906560be53a 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
                 </layout>
             </item>
 
-            <!-- Download location. -->
-            <item>
-                <layout class="QHBoxLayout">
-                    <item>
-                        <widget class="QLabel">
-                            <property name="text">
-                                <string>Download Location</string>
-                            </property>
-
-                            <property name="toolTip">
-                                <string>The default is System Download Directory.</string>
-                            </property>
-                        </widget>
-                    </item>
-
-                    <item>
-                        <widget class="QComboBox" name="kcfg_downloadLocation">
-                            <property name="sizePolicy">
-                                <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-                                    <horstretch>0</horstretch>
-                                    <verstretch>0</verstretch>
-                                </sizepolicy>
-                            </property>
-
-                            <property name="editable">
-                                <bool>true</bool>
-                            </property>
-
-                            <item>
-                                <property name="text">
-                                    <string>System Download Directory</string>
-                                </property>
-                            </item>
-                        </widget>
-                    </item>
-
-                    <item>
-                        <widget class="QPushButton" name="browseButton">
-                            <property name="text">
-                                <string>Browse</string>
-                            </property>
-                        </widget>
-                    </item>
-                </layout>
-            </item>
-
             <!-- Tabs on top. -->
             <item>
                 <widget class="QCheckBox" name="kcfg_tabsOnTop">
                 </widget>
             </item>
 
+            <!-- Spatial navigation. -->
+            <item>
+                <widget class="QCheckBox" name="kcfg_spatialNavigation">
+                    <property name="text">
+                        <string>Spatial navigation</string>
+                    </property>
+
+                    <property name="toolTip">
+                        <string>Allow moving between links and input fields using the keyboard arrow keys.  The default is enabled.</string>
+                    </property>
+                </widget>
+            </item>
+
+            <!-- Download location. -->
+            <item>
+                <widget class="QGroupBox">
+                    <property name="title">
+                        <string>Download Location</string>
+                    </property>
+
+                    <layout class="QVBoxLayout">
+                        <!-- Download directory. -->
+                        <item>
+                            <layout class="QHBoxLayout">
+                                <item>
+                                    <widget class="QComboBox" name="kcfg_downloadDirectory">
+                                        <property name="sizePolicy">
+                                            <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+                                                <horstretch>0</horstretch>
+                                                <verstretch>0</verstretch>
+                                            </sizepolicy>
+                                        </property>
+
+                                        <property name="editable">
+                                            <bool>true</bool>
+                                        </property>
+
+                                        <item>
+                                            <property name="text">
+                                                <string>System Download Directory</string>
+                                            </property>
+                                        </item>
+                                    </widget>
+                                </item>
+
+                                <item>
+                                    <widget class="QPushButton" name="browseButton">
+                                        <property name="text">
+                                            <string>Browse</string>
+                                        </property>
+                                    </widget>
+                                </item>
+                            </layout>
+                        </item>
+
+                        <!-- Auto update download directory. -->
+                        <item>
+                            <widget class="QCheckBox" name="kcfg_autoUpateDownloadDirectory">
+                                <property name="text">
+                                    <string>Auto update the download directory</string>
+                                </property>
+
+                                <property name="toolTip">
+                                    <string>Automatically update the download directory to be whatever was used for the last download. The default is enabled.</string>
+                                </property>
+                            </widget>
+                        </item>
+                    </layout>
+                </widget>
+            </item>
+
             <!-- Full screen browsing. -->
             <item>
                 <widget class="QGroupBox">
index 0affb07f524cd43c1c216cd5acf3235aa6c831da..16c616e26a88200be6809ffd30ce3cacde5add89 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
@@ -45,7 +45,7 @@
                     </property>
 
                     <property name="toolTip">
-                        <string>Local storage includes cookies, IndexedDB, DOM storage, filesystem API, and service workers.  DOM storage also requires a separate control to be enabled.  Local storage is disabled by default.</string>
+                        <string>Local storage includes cookies, DOM storage, IndexedDB, service workers, and the filesystem API.  DOM storage also requires a separate control to be enabled.  Local storage is disabled by default.</string>
                     </property>
                 </widget>
             </item>
                     <property name="toolTip">
                         <string>DOM storage, sometimes called web storage, is like cookies on steroids.  To function, it requires that both JavaScript and local storage be enabled.  It is disabled by default.</string>
                     </property>
+
+                    <!-- Initially disable DOM storage. -->
+                    <property name="enabled">
+                        <bool>false</bool>
+                    </property>
                 </widget>
             </item>
 
diff --git a/src/uis/SettingsSpellCheck.ui b/src/uis/SettingsSpellCheck.ui
new file mode 100644 (file)
index 0000000..9375f0b
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>. -->
+
+<ui version="4.0">
+    <!-- The name of the generated class. -->
+    <class>SpellCheckSettings</class>
+
+    <widget class="QWidget">
+        <layout class="QVBoxLayout">
+            <!-- Instructions. -->
+            <item>
+                <widget class="QLabel" name="spellCheckInstructions">
+                    <property name="text">
+                        <string>Spell checking languages can be added by installing the Hunspell language packages. One or more languages can be selected. All selected languages will be checked simultaneously.</string>
+                    </property>
+
+                    <property name="wordWrap">
+                        <bool>true</bool>
+                    </property>
+                </widget>
+            </item>
+
+            <!-- Selection list. -->
+            <item>
+                <widget class="QListWidget" name="spellCheckListWidget">
+                    <property name="sizePolicy">
+                        <sizepolicy hsizetype="Fixed" vsizetype="Expanding">
+                            <horstretch>0</horstretch>
+                            <verstretch>0</verstretch>
+                        </sizepolicy>
+                    </property>
+                </widget>
+            </item>
+        </layout>
+    </widget>
+</ui>
index 2aad7efd4d8b1e9bd860285b043e76a60f73d6ba..521526db433df80ee3ee78098b659ae49898d611 100644 (file)
@@ -3,7 +3,7 @@
 <!--
   Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
 
-  This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser PC <https://www.stoutner.com/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
index afbc0de8bf0f95ae0b64f4029708ec2c4f70ea4c..9fe904f0761dcfec36dac8c3b4827d8ed298dc40 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
 
 
 # List the sources to include in the executable.
-target_sources(privacy-browser PRIVATE
-    TabWidget.cpp
+target_sources(privacybrowser PRIVATE
+    DevToolsWebEngineView.cpp
+    DraggableTreeView.cpp
     PrivacyWebEngineView.cpp
+    TabWidget.cpp
+    UrlLineEdit.cpp
 )
diff --git a/src/widgets/DevToolsWebEngineView.cpp b/src/widgets/DevToolsWebEngineView.cpp
new file mode 100644 (file)
index 0000000..a409584
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "DevToolsWebEngineView.h"
+
+// Qt toolkit headers.
+#include <QWebEngineProfile>
+
+// Construct the class.
+DevToolsWebEngineView::DevToolsWebEngineView(QWidget *parentWidgetPointer) : QWebEngineView(parentWidgetPointer)
+{
+    // Create an off-the-record profile (the default when no profile name is specified).
+    QWebEngineProfile *webEngineProfilePointer = new QWebEngineProfile(QLatin1String(""));
+
+    // Create a WebEngine page.
+    QWebEnginePage *webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
+
+    // Set the WebEngine page.
+    setPage(webEnginePagePointer);
+}
diff --git a/src/widgets/DevToolsWebEngineView.h b/src/widgets/DevToolsWebEngineView.h
new file mode 100644 (file)
index 0000000..a694bd3
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DEVTOOLSWEBENGINEVIEW_H
+#define DEVTOOLSWEBENGINEVIEW_H
+
+// Qt toolkit headers.
+#include <QWebEngineView>
+
+class DevToolsWebEngineView : public QWebEngineView
+{
+    // Include the Q_OBJECT macro.
+    Q_OBJECT
+
+public:
+    // The default constructor.
+    explicit DevToolsWebEngineView(QWidget *parentWidgetPointer = nullptr);
+};
+#endif
diff --git a/src/widgets/DraggableTreeView.cpp b/src/widgets/DraggableTreeView.cpp
new file mode 100644 (file)
index 0000000..bbe0868
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "DraggableTreeView.h"
+#include "databases/BookmarksDatabase.h"
+#include "dialogs/BookmarksDialog.h"
+
+// Qt toolkit headers.
+#include <QDebug>
+#include <QDropEvent>
+#include <QList>
+
+// Construct the class.
+DraggableTreeView::DraggableTreeView(QWidget *parentWidget) : QTreeView(parentWidget) {}
+
+// Handle drop events.
+void DraggableTreeView::dropEvent(QDropEvent *dropEvent)
+{
+    // Get the list of currently selected items that are moving.
+    QList<QModelIndex> indexesMovingList = selectedIndexes();
+
+    // Create a list of selected database IDs.
+    QList<int> *selectedDatabaseIdsListPointer = new QList<int>;
+
+    // Populate the list of selected database IDs.
+    for (const QModelIndex &modelIndex : indexesMovingList)
+    {
+        // Only process model indexes from the bookmark name column (by default, there will be model indexes from all the visible columns in the list).
+        if (modelIndex.column() == BookmarksDialog::NAME_COLUMN)
+            selectedDatabaseIdsListPointer->append(modelIndex.siblingAtColumn(BookmarksDialog::DATABASE_ID_COLUMN).data().toInt());
+    }
+
+    // Get a list of root selected database IDs.
+    QList<int> *rootSelectedDatabaseIdsListPointer = getRootSelectedDatabaseIds(selectedDatabaseIdsListPointer);
+
+    // Get the drop position.
+    int dropPosition = dropIndicatorPosition();
+
+    // Get the drop model index.
+    QModelIndex dropModelIndex = indexAt(dropEvent->pos());
+
+    // Create a previous parent folder ID standard C++ list (which can be sorted and from which duplicates can be removed).
+    std::list<double> *previousParentFolderIdListPointer = new std::list<double>;
+
+    // Process the move according to the drop position.
+    switch (dropPosition)
+    {
+        case QAbstractItemView::OnItem:  // This will only ever be called for folders as `BookmarksDialog::populateSubfolders` creates bookmarks without `setDropEnabled`.
+        {
+            // Move everything to the beginning of the folder.
+            moveToBeginningOfFolder(dropModelIndex, selectedDatabaseIdsListPointer, rootSelectedDatabaseIdsListPointer, previousParentFolderIdListPointer);
+
+            break;
+        }
+
+        case QAbstractItemView::AboveItem:
+        {
+            // Get the drop display order.
+            int dropDisplayOrder = dropModelIndex.siblingAtColumn(BookmarksDialog::DISPLAY_ORDER_COLUMN).data().toInt();
+
+            // Get the drop parent folder ID.
+            double dropParentFolderId = dropModelIndex.parent().siblingAtColumn(BookmarksDialog::FOLDER_ID_COLUMN).data().toDouble();
+
+            // Get a list of all the items in the target folder except those selected.
+            QList<BookmarkStruct> *itemsInFolderExceptSelectedListPointer = BookmarksDatabase::getBookmarksInFolderExcept(dropParentFolderId, selectedDatabaseIdsListPointer);
+
+            // Initialize a new display order tracker.
+            int newDisplayOrder = 0;
+
+            // Process all the items in the target folder, moving in the new ones.
+            for (const BookmarkStruct &bookmarkStruct : *itemsInFolderExceptSelectedListPointer)
+            {
+                // Check to see if this is the drop display order.
+                if (bookmarkStruct.displayOrder == dropDisplayOrder)
+                {
+                    // Add all of the bookmarks being moved to this point.
+                    for (const int databaseId : *rootSelectedDatabaseIdsListPointer)
+                    {
+                        // Get the item's current parent folder ID.
+                        double currentParentFolderId = BookmarksDatabase::getParentFolderId(databaseId);
+
+                        // Add the parent folder ID to the list of previous parent folders if the item is from a different folder.
+                        if (currentParentFolderId != dropParentFolderId)
+                            previousParentFolderIdListPointer->push_back(currentParentFolderId);
+
+                        // Update the parent folder and display order for each bookmark.
+                        BookmarksDatabase::updateParentFolderAndDisplayOrder(databaseId, dropParentFolderId, newDisplayOrder);
+
+                        // Increment the new display order.
+                        ++newDisplayOrder;
+                    }
+                }
+
+                // Set the bookmark's display order if it has changed.
+                if (bookmarkStruct.displayOrder != newDisplayOrder)
+                    BookmarksDatabase::updateDisplayOrder(bookmarkStruct.databaseId, newDisplayOrder);
+
+                // Increment the new display order.
+                ++newDisplayOrder;
+            }
+            break;
+        }
+
+        case QAbstractItemView::BelowItem:
+        {
+            // Check to see if the drop model index is an expanded folder.
+            if (dropModelIndex.siblingAtColumn(BookmarksDialog::IS_FOLDER_COLUMN).data().toBool() && isExpanded(dropModelIndex))  // The drop model index is an expanded folder.
+            {
+                // Move everything to the beginning of the folder.
+                moveToBeginningOfFolder(dropModelIndex, selectedDatabaseIdsListPointer, rootSelectedDatabaseIdsListPointer, previousParentFolderIdListPointer);
+            }
+            else  // The drop model index is not an expanded folder.
+            {
+                // Get the drop display order.
+                int dropDisplayOrder = dropModelIndex.siblingAtColumn(BookmarksDialog::DISPLAY_ORDER_COLUMN).data().toInt();
+
+                // Get the drop parent folder ID.
+                double dropParentFolderId = dropModelIndex.parent().siblingAtColumn(BookmarksDialog::FOLDER_ID_COLUMN).data().toDouble();
+
+                // Get a list of all the items in the target folder except those selected.
+                QList<BookmarkStruct> *itemsInFolderExceptSelectedListPointer = BookmarksDatabase::getBookmarksInFolderExcept(dropParentFolderId, selectedDatabaseIdsListPointer);
+
+                // Initialize a new display order tracker.
+                int newDisplayOrder = 0;
+
+                // Process all the items in the target folder, moving in the new ones.
+                for (const BookmarkStruct &bookmarkStruct : *itemsInFolderExceptSelectedListPointer)
+                {
+                    // Set the bookmark's display order if it has changed.
+                    if (bookmarkStruct.displayOrder != newDisplayOrder)
+                        BookmarksDatabase::updateDisplayOrder(bookmarkStruct.databaseId, newDisplayOrder);
+
+                    // Increment the new display order.
+                    ++newDisplayOrder;
+
+                    // Check to see if this is the drop display order.
+                    if (bookmarkStruct.displayOrder == dropDisplayOrder)
+                    {
+                        // Add all of the bookmarks being moved to this point.
+                        for (const int databaseId : *rootSelectedDatabaseIdsListPointer)
+                        {
+                            // Get the item's current parent folder ID.
+                            double currentParentFolderId = BookmarksDatabase::getParentFolderId(databaseId);
+
+                            // Add the parent folder ID to the list of previous parent folders if the item is from a different folder.
+                            if (currentParentFolderId != dropParentFolderId)
+                                previousParentFolderIdListPointer->push_back(currentParentFolderId);
+
+                            // Update the parent folder and display order for each bookmark.
+                            BookmarksDatabase::updateParentFolderAndDisplayOrder(databaseId, dropParentFolderId, newDisplayOrder);
+
+                            // Increment the new display order.
+                            ++newDisplayOrder;
+                        }
+                    }
+                }
+            }
+            break;
+        }
+
+        case QAbstractItemView::OnViewport:
+        {
+            // Get the drop parent folder ID.
+            double dropParentFolderId = 0;
+
+            // Get a list of all the items in the root folder except those selected.
+            QList<BookmarkStruct> *itemsInFolderExceptSelectedListPointer = BookmarksDatabase::getBookmarksInFolderExcept(dropParentFolderId, selectedDatabaseIdsListPointer);
+
+            // Initialize a new display order tracker.
+            int newDisplayOrder = 0;
+
+            // Update the display order of the existing items in the folder.
+            for (const BookmarkStruct &bookmarkStruct : *itemsInFolderExceptSelectedListPointer)
+            {
+                // Set the bookmark's display order if it has changed.
+                if (bookmarkStruct.displayOrder != newDisplayOrder)
+                    BookmarksDatabase::updateDisplayOrder(bookmarkStruct.databaseId, newDisplayOrder);
+
+                // Increment the new display order.
+                ++newDisplayOrder;
+            }
+
+            // Add all of the bookmarks being moved to the end of the list.
+            for (const int databaseId : *rootSelectedDatabaseIdsListPointer)
+            {
+                // Get the item's current parent folder ID.
+                double currentParentFolderId = BookmarksDatabase::getParentFolderId(databaseId);
+
+                // Add the parent folder ID to the list of previous parent folders if the item is from a different folder.
+                if (currentParentFolderId != dropParentFolderId)
+                    previousParentFolderIdListPointer->push_back(currentParentFolderId);
+
+                // Update the parent folder and display order for each bookmark.
+                BookmarksDatabase::updateParentFolderAndDisplayOrder(databaseId, dropParentFolderId, newDisplayOrder);
+
+                // Increment the new display order.
+                ++newDisplayOrder;
+            }
+            break;
+        }
+    }
+
+    // Sort the previous parent folder ID list.
+    previousParentFolderIdListPointer->sort();
+
+    // Remove duplicates from the parent folder ID list.
+    previousParentFolderIdListPointer->unique();
+
+    // Update the folder contents display order for each previous parent folder.
+    for (const double parentFolderId : *previousParentFolderIdListPointer)
+        BookmarksDatabase::updateFolderContentsDisplayOrder(parentFolderId);
+
+    // Emit the bookmarks moved signal.
+    emit bookmarksMoved();
+}
+
+QList<int>* DraggableTreeView::getRootSelectedDatabaseIds(QList<int> *selectedDatabaseIdsPointer) const
+{
+    // Create a list of the database IDs of the contents of each selected folder.
+    QList<int> selectedFoldersContentsDatabaseIds;
+
+    // Populate the list of the database IDs of the contents of each selected folder.
+    for (const int databaseId : *selectedDatabaseIdsPointer)
+    {
+        // If this is not the root item and is a folder, get the database IDs of the contents.
+        if ((databaseId != -1) && BookmarksDatabase::isFolder(databaseId))
+            selectedFoldersContentsDatabaseIds.append(*BookmarksDatabase::getFolderContentsDatabaseIds(BookmarksDatabase::getFolderId(databaseId)));
+    }
+
+    // Create a root selected database IDs list.
+    QList<int>* rootSelectedDatabaseIdsListPointer = new QList<int>;
+
+    // Populate the root selected database IDs list.
+    for (const int databaseId : *selectedDatabaseIdsPointer)
+    {
+        // Add the database ID to the root selected database IDs list if it isn't the root item and it isn't contained in the selected folder contents database IDs list.
+        if ((databaseId != -1) && !selectedFoldersContentsDatabaseIds.contains(databaseId))
+            rootSelectedDatabaseIdsListPointer->append(databaseId);
+    }
+
+    // Return the root selected database IDs list.
+    return rootSelectedDatabaseIdsListPointer;
+}
+
+void DraggableTreeView::moveToBeginningOfFolder(const QModelIndex &dropModelIndex, QList<int> *selectedDatabaseIdsListPointer, QList<int> *rootSelectedDatabaseIdsListPointer,
+                                                std::list<double> *previousParentFolderIdListPointer) const
+{
+    // Get the new parent folder ID.
+    double newParentFolderId = dropModelIndex.siblingAtColumn(BookmarksDialog::FOLDER_ID_COLUMN).data().toDouble();
+
+    // Get a list of all the items in the target folder except those selected.
+    QList<BookmarkStruct> *itemsInFolderExceptSelectedListPointer = BookmarksDatabase::getBookmarksInFolderExcept(newParentFolderId, selectedDatabaseIdsListPointer);
+
+    // Initialize a new display order tracker.
+    int newDisplayOrder = 0;
+
+    // Move all the items to the top of the target folder.
+    for (const int databaseId : *rootSelectedDatabaseIdsListPointer)
+    {
+        // Get the item's current parent folder ID.
+        double currentParentFolderId = BookmarksDatabase::getParentFolderId(databaseId);
+
+        // Add the parent folder ID to the list of previous parent folders if the item is from a different folder.
+        if (currentParentFolderId != newParentFolderId)
+            previousParentFolderIdListPointer->push_back(currentParentFolderId);
+
+                // Update the parent folder and display order for each bookmark.
+                BookmarksDatabase::updateParentFolderAndDisplayOrder(databaseId, newParentFolderId, newDisplayOrder);
+
+                // Increment the new display order.
+                ++newDisplayOrder;
+            }
+
+            // Update the display order of the existing items in the folder.
+            for (const BookmarkStruct &bookmarkStruct : *itemsInFolderExceptSelectedListPointer)
+            {
+                // Set the bookmark's display order if it has changed.
+                if (bookmarkStruct.displayOrder != newDisplayOrder)
+                    BookmarksDatabase::updateDisplayOrder(bookmarkStruct.databaseId, newDisplayOrder);
+
+                // Increment the new display order.
+                ++newDisplayOrder;
+            }
+}
diff --git a/src/widgets/DraggableTreeView.h b/src/widgets/DraggableTreeView.h
new file mode 100644 (file)
index 0000000..961d2fa
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DRAGGABLETREEVIEW_H
+#define DRAGGABLETREEVIEW_H
+
+// Qt toolkit headers.
+#include <QTreeView>
+
+class DraggableTreeView : public QTreeView
+{
+    // Include the Q_OBJECT macro.
+    Q_OBJECT
+
+public:
+    // The default contructor.
+    explicit DraggableTreeView(QWidget *parentWidget = nullptr);
+
+signals:
+    // The signals.
+    void bookmarksMoved() const;
+
+protected:
+    void dropEvent(QDropEvent *event) override;
+
+private:
+    // The private functions
+    QList<int>* getRootSelectedDatabaseIds(QList<int> *selectedDatabaseIdsPointer) const;
+    void moveToBeginningOfFolder(const QModelIndex &dropModelIndex, QList<int> *selectedDatabaseIdsListPointer, QList<int> *rootSelectedDatabaseIdsListPointer,
+                                 std::list<double> *previousParentFolderIdListPointer) const;
+};
+#endif
index 10a3a47fcf3f7124a194ce4c62f31b273c5eeaac..42c331fec5340447ed05da6433a6475db30f7de4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
 
 // Application headers.
 #include "PrivacyWebEngineView.h"
+#include "Settings.h"
+#include "ui_HttpAuthenticationDialog.h"
 #include "databases/CookiesDatabase.h"
+#include "databases/DomainsDatabase.h"
+#include "dialogs/HttpAuthenticationDialog.h"
+#include "interceptors/UrlRequestInterceptor.h"
 #include "windows/BrowserWindow.h"
 
+// Qt toolkit headers.
+#include <QContextMenuEvent>
+#include <QMenu>
+
 // Construct the class.
-PrivacyWebEngineView::PrivacyWebEngineView() : QWebEngineView(nullptr) {}
+PrivacyWebEngineView::PrivacyWebEngineView(QWidget *parentWidgetPointer) : QWebEngineView(parentWidgetPointer)
+{
+    // Create an off-the-record profile (the default when no profile name is specified).
+    webEngineProfilePointer = new QWebEngineProfile(QLatin1String(""));
+
+    // Create a WebEngine page.
+    QWebEnginePage *webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
+
+    // Set the WebEngine page.
+    setPage(webEnginePagePointer);
+
+    // Get handles for the various aspects of the WebEngine.
+    webEngineSettingsPointer = webEnginePagePointer->settings();
+
+    // Instantiate the URL request interceptor.
+    UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor(this);
+
+    // Set the URL request interceptor.
+    webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
+
+    // Reapply the domain settings when the host changes.
+    connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(const QString&)), this, SLOT(applyDomainSettingsWithoutReloading(const QString&)));
+
+    // Display HTTP Ping blocked dialogs.
+    connect(urlRequestInterceptorPointer, SIGNAL(displayHttpPingDialog(const QString&)), this, SLOT(displayHttpPingDialog(const QString&)));
+
+    // Handle HTTP authentication requests.
+    connect(webEnginePagePointer, SIGNAL(authenticationRequired(const QUrl&, QAuthenticator*)), this, SLOT(handleAuthenticationRequest(const QUrl&, QAuthenticator*)));
+}
 
 void PrivacyWebEngineView::addCookieToList(const QNetworkCookie &cookie) const
 {
@@ -40,19 +77,203 @@ void PrivacyWebEngineView::addCookieToList(const QNetworkCookie &cookie) const
     emit updateCookiesAction(cookieListPointer->size());
 }
 
+void PrivacyWebEngineView::applyDomainSettingsWithoutReloading(const QString &hostname)
+{
+    // Apply the domain settings  `false` does not reload the website.
+    applyDomainSettings(hostname, false);
+}
+
+void PrivacyWebEngineView::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
+{
+    // Get the record for the hostname.
+    QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
+
+    // Check if the hostname has domain settings.
+    if (domainQuery.isValid())  // The hostname has domain settings.
+    {
+        // Store the domain settings name.
+        domainSettingsName = domainQuery.value(DomainsDatabase::DOMAIN_NAME).toString();
+
+        // Set the JavaScript status.
+        switch (domainQuery.value(DomainsDatabase::JAVASCRIPT).toInt())
+        {
+            // Set the default JavaScript status.
+            case (DomainsDatabase::SYSTEM_DEFAULT):
+            {
+                webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
+
+                break;
+            }
+
+            // Enable JavaScript.
+            case (DomainsDatabase::ENABLED):
+            {
+                webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+
+                break;
+            }
+
+            // Disable JavaScript.
+            case (DomainsDatabase::DISABLED):
+            {
+                webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
+
+                break;
+            }
+        }
+
+        // Set the local storage status.
+        switch (domainQuery.value(DomainsDatabase::LOCAL_STORAGE).toInt())
+        {
+            // Set the default local storage status.
+            case (DomainsDatabase::SYSTEM_DEFAULT):
+            {
+                localStorageEnabled = Settings::localStorageEnabled();
+
+                break;
+            }
+
+            // Enable local storage.
+            case (DomainsDatabase::ENABLED):
+            {
+                localStorageEnabled = true;
+
+                break;
+            }
+
+            // Disable local storage.
+            case (DomainsDatabase::DISABLED):
+            {
+                localStorageEnabled = false;
+
+                break;
+            }
+        }
+
+        // Set the DOM storage status.
+        switch (domainQuery.value(DomainsDatabase::DOM_STORAGE).toInt())
+        {
+            // Set the default DOM storage status.  QWebEngineSettings confusingly calls this local storage.
+            case (DomainsDatabase::SYSTEM_DEFAULT):
+            {
+                webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
+
+                break;
+            }
+
+            // Enable DOM storage.  QWebEngineSettings confusingly calls this local storage.
+            case (DomainsDatabase::ENABLED):
+            {
+                webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
+
+                break;
+            }
+
+            // Disable DOM storage.  QWebEngineSettings confusingly calls this local storage.
+            case (DomainsDatabase::DISABLED):
+            {
+                webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
+
+                break;
+            }
+        }
+
+        // Set the user agent.
+        webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainQuery.value(DomainsDatabase::USER_AGENT).toString()));
+
+        // Check if a custom zoom factor is set.
+        if (domainQuery.value(DomainsDatabase::ZOOM_FACTOR).toInt())
+        {
+            // Store the current zoom factor.
+            defaultZoomFactor = domainQuery.value(DomainsDatabase::CUSTOM_ZOOM_FACTOR).toDouble();
+        }
+        else
+        {
+            // Store the current zoom factor.
+            defaultZoomFactor = Settings::zoomFactor();
+        }
+    }
+    else  // The hostname does not have domain settings.
+    {
+        // Reset the domain settings name.
+        domainSettingsName = QLatin1String("");
+
+        // Set the JavaScript status.
+        webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
+
+        // Set the local storage status.
+        localStorageEnabled = Settings::localStorageEnabled();
+
+        // Set DOM storage.  In QWebEngineSettings it is called Local Storage.
+        webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
+
+        // Set the user agent.
+        webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
+
+        // Store the zoom factor.
+        defaultZoomFactor = Settings::zoomFactor();
+    }
+
+    // Set the current zoom factor.
+    setZoomFactor(defaultZoomFactor);
+
+    // Reload the website if requested.
+    if (reloadWebsite)
+        reload();
+
+    // Reset the HTTP authentication dialog counter.
+    httpAuthenticationDialogsDisplayed = 0;
+
+    // Update the UI.
+    emit updateUi(this);
+}
+
+void PrivacyWebEngineView::contextMenuEvent(QContextMenuEvent *contextMenuEvent) {
+    // Get a handle for the
+    QWebEnginePage *webEnginePagePointer = page();
+
+    // Get a handle for the menu.
+    QMenu *contextMenu = webEnginePagePointer->createStandardContextMenu();
+
+    // Get the list of context menu actions.
+    const QList<QAction *> contextMenuActionsList = contextMenu->actions();
+
+    // Add the open link in new background tab action if the context menu already contains the open link in new window action.
+    if (contextMenuActionsList.contains(webEnginePagePointer->action(QWebEnginePage::OpenLinkInNewWindow)))
+    {
+        // Move the open in new tab action to the top of the list.
+        contextMenu->insertAction(webEnginePagePointer->action(QWebEnginePage::Back), webEnginePagePointer->action(QWebEnginePage::OpenLinkInNewTab));
+
+        // Add the open link in background tab action below the open in new tab action.
+        contextMenu->insertAction(webEnginePagePointer->action(QWebEnginePage::Back), webEnginePagePointer->action(QWebEnginePage::OpenLinkInNewBackgroundTab));
+
+        // Move the open in new window action below the open in background tab action.
+        contextMenu->insertAction(webEnginePagePointer->action(QWebEnginePage::Back), webEnginePagePointer->action(QWebEnginePage::OpenLinkInNewWindow));
+
+        // Add a separator below the open in new window action.
+        contextMenu->insertSeparator(webEnginePagePointer->action(QWebEnginePage::Back));
+    }
+
+    // Display the menu using the location in the context menu event.
+    contextMenu->popup(contextMenuEvent->globalPos());
+}
+
 QWebEngineView* PrivacyWebEngineView::createWindow(QWebEnginePage::WebWindowType webWindowType) {
     // Get a handle for the browser window.
     BrowserWindow *browserWindowPointer = qobject_cast<BrowserWindow*>(window());
 
-    // Create the requsted window type.
-    switch (webWindowType) {
-        case QWebEnginePage::WebBrowserTab: {
-            // Create the new tab and return the privacy WebEngine view pointer.  `true` removes the focus from the blank URL line edit.
+    // Create the requested window type.
+    switch (webWindowType)
+    {
+        case QWebEnginePage::WebBrowserTab:
+        {
+            // Create the new tab and return the privacy WebEngine view pointer.  `true` removes the focus from the blank URL line edit.  `true` adds the new tab adjacent to the current tab.
             // The new privacy WebEngine view pointer is returned so it can be populated with the link from the context menu.
-            return browserWindowPointer->tabWidgetPointer->addTab(true);
+            return browserWindowPointer->tabWidgetPointer->addTab(true, true);
         }
 
-        case QWebEnginePage::WebBrowserWindow: {
+        case QWebEnginePage::WebBrowserWindow:
+        {
             // Create a new browser window.
             BrowserWindow *newBrowserWindowPointer = new BrowserWindow();
 
@@ -63,13 +284,43 @@ QWebEngineView* PrivacyWebEngineView::createWindow(QWebEnginePage::WebWindowType
             return newBrowserWindowPointer->tabWidgetPointer->loadBlankInitialWebsite();
         }
 
-        default: {
-            // Return an null pointer for opening a background tab and opening a web dialog.
+        case QWebEnginePage::WebBrowserBackgroundTab:
+        {
+            // Create the new tab and return the privacy WebEngine view pointer.  `false` does not clear the URL line edit.  `true` adds the new tab adjacent to the current tab.
+            // `true` creates a background tab.
+            // The new privacy WebEngine view pointer is returned so it can be populated with the link from the context menu.
+            return browserWindowPointer->tabWidgetPointer->addTab(false, true, true);
+        }
+
+        default:
+        {
+            // Return a null pointer for opening a web dialog.
             return nullptr;
         }
     }
 }
 
+void PrivacyWebEngineView::displayHttpPingDialog(const QString &httpPingUrl) const
+{
+    // Display the HTTP Ping blocked dialog.
+    emit displayHttpPingBlockedDialog(httpPingUrl);
+}
+
+void PrivacyWebEngineView::handleAuthenticationRequest(const QUrl &requestUrl, QAuthenticator *authenticatorPointer)
+{
+    // Only display the HTTP authentication dialog if it hasn't already been displayed three times for this URL.
+    if (httpAuthenticationDialogsDisplayed < 3) {
+        // Increment the HTTP authentication dialog display counter.
+        ++httpAuthenticationDialogsDisplayed;
+
+        // Instantiate an HTTP authentication dialog.
+        HttpAuthenticationDialog *httpAuthenticationDialogPointer = new HttpAuthenticationDialog(parentWidget(), requestUrl, authenticatorPointer);
+
+        // Display the dialog.  This must be `exec()` instead of `show()` so that the website doesn't proceed before populating the authentication pointer.
+        httpAuthenticationDialogPointer->exec();
+    }
+}
+
 void PrivacyWebEngineView::removeCookieFromList(const QNetworkCookie &cookie) const
 {
     //qDebug() << "Remove cookie:  " << cookie.toRawForm();
index c227f428ee2bd1a0aab6a0b89a8708f2a6335519..95cc69f5b67d8397b030c27a6f987c5d8e7fc715 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
 #ifndef PRIVACYWEBENGINEVIEW_H
 #define PRIVACYWEBENGINEVIEW_H
 
+// KDE framework headers.
+#include <KLineEdit>
+
 // Qt toolkit headers.
+#include <QIcon>
 #include <QNetworkCookie>
 #include <QWebEngineFindTextResult>
 #include <QWebEngineView>
@@ -32,28 +36,51 @@ class PrivacyWebEngineView : public QWebEngineView
 
 public:
     // The default constructor.
-    explicit PrivacyWebEngineView();
+    explicit PrivacyWebEngineView(QWidget *parentWidgetPointer = nullptr);
 
     // The public variables.
     std::list<QNetworkCookie> *cookieListPointer = new std::list<QNetworkCookie>;
-    QString domainSettingsName = QStringLiteral("");
+    double defaultZoomFactor = 1.00;
+    QString domainSettingsName = QLatin1String("");
+    QIcon favoriteIcon = QIcon::fromTheme(QLatin1String("globe"), QIcon::fromTheme(QLatin1String("applications-internet")));
     bool findCaseSensitive = false;
-    QString findString = QStringLiteral("");
+    QString findString = QLatin1String("");
     QWebEngineFindTextResult findTextResult = QWebEngineFindTextResult();
+    int httpAuthenticationDialogsDisplayed = 0;
+    bool isLoading = false;
     int loadProgressInt = -1;
     bool localStorageEnabled = false;
 
+    // The public functions.
+    void applyDomainSettings(const QString &hostname, const bool reloadWebsite);
+
 signals:
     // The signals.
+    void displayHttpPingBlockedDialog(const QString &httpPingUrl) const;
     void updateCookiesAction(const int numberOfCookies) const;
+    void updateUi(const PrivacyWebEngineView *privacyWebEngineViewPointer) const;
 
 public Q_SLOTS:
     // The public slots.
     void addCookieToList(const QNetworkCookie &cookie) const;
     void removeCookieFromList(const QNetworkCookie &cookie) const;
 
+private Q_SLOTS:
+    // The private slots.
+    void applyDomainSettingsWithoutReloading(const QString &hostname);
+    void displayHttpPingDialog(const QString &httpPingUrl) const;
+    void handleAuthenticationRequest(const QUrl &requestUrl, QAuthenticator *authenticatorPointer);
+
+private:
+    // The private variables.
+    KLineEdit *passwordLineEditPointer;
+    KLineEdit *usernameLineEditPointer;
+    QWebEngineProfile *webEngineProfilePointer;
+    QWebEngineSettings *webEngineSettingsPointer;
+
 protected:
     // The protected functions.
+    void contextMenuEvent(QContextMenuEvent *contextMenuEvent) override;
     QWebEngineView* createWindow(QWebEnginePage::WebWindowType webWindowType) override;
 };
 #endif
index 3a1b54f33f2028f411b5eefadb14d346f794bb13..f65c79ea2177b404eef157f441245dab7175ed73 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
  */
 
 // Application headers.
+#include "DevToolsWebEngineView.h"
 #include "TabWidget.h"
 #include "Settings.h"
 #include "ui_AddTabWidget.h"
+#include "ui_Tab.h"
 #include "ui_TabWidget.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"
 
 // KDE Framework headers.
 #include <KIO/FileCopyJob>
 #include <KIO/JobUiDelegate>
+#include <KNotification>
 
 // Qt toolkit headers.
 #include <QAction>
 #include <QFileDialog>
 #include <QGraphicsScene>
 #include <QGraphicsView>
+#include <QMessageBox>
 #include <QPrintDialog>
 #include <QPrintPreviewDialog>
 #include <QPrinter>
 
 // Initialize the public static variables.
-QString TabWidget::webEngineDefaultUserAgent = QStringLiteral("");
+QString TabWidget::webEngineDefaultUserAgent = QLatin1String("");
 
 // Construct the class.
-TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
+TabWidget::TabWidget(QWidget *windowPointer) : QWidget(windowPointer)
 {
+    // Create a QProcess to check if KDE is running.
+    QProcess *checkIfRunningKdeQProcessPointer = new QProcess();
+
+    // Create an argument string list that contains `ksmserver` (KDE Session Manager).
+    QStringList argument = QStringList(QLatin1String("ksmserver"));
+
+    // Run `pidof` to check for the presence of `ksmserver`.
+    checkIfRunningKdeQProcessPointer->start(QLatin1String("pidof"), argument);
+
+    // Monitor any standard output.
+    connect(checkIfRunningKdeQProcessPointer, &QProcess::readyReadStandardOutput, [this]
+    {
+        // If there is any standard output, `ksmserver` is running.
+        isRunningKde = true;
+    });
+
+    // Instantiate the user agent helper.
+    userAgentHelperPointer = new UserAgentHelper();
+
     // Instantiate the UIs.
     Ui::TabWidget tabWidgetUi;
     Ui::AddTabWidget addTabWidgetUi;
@@ -58,25 +78,34 @@ TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
     tabWidgetUi.setupUi(this);
 
     // Get a handle for the tab widget.
-    tabWidgetPointer = tabWidgetUi.tabWidget;
+    qTabWidgetPointer = tabWidgetUi.tabWidget;
 
     // Setup the add tab UI.
-    addTabWidgetUi.setupUi(tabWidgetPointer);
+    addTabWidgetUi.setupUi(qTabWidgetPointer);
 
     // Get handles for the add tab widgets.
     QWidget *addTabWidgetPointer = addTabWidgetUi.addTabQWidget;
     QPushButton *addTabButtonPointer = addTabWidgetUi.addTabButton;
 
     // Display the add tab widget.
-    tabWidgetPointer->setCornerWidget(addTabWidgetPointer);
+    qTabWidgetPointer->setCornerWidget(addTabWidgetPointer);
+
+    // Create the loading favorite icon movie.
+    loadingFavoriteIconMoviePointer = new QMovie();
+
+    // Set the loading favorite icon movie file name.
+    loadingFavoriteIconMoviePointer->setFileName(QStringLiteral(":/icons/loading.gif"));
+
+    // Stop the loading favorite icon movie if the window is destroyed.  Otherwise, the app will crash if there is more than one window open and a window is closed while at tab is loading.
+    connect(windowPointer, SIGNAL(destroyed()), this, SLOT(stopLoadingFavoriteIconMovie()));
 
     // Add the first tab.
     addFirstTab();
 
     // Process tab events.
-    connect(tabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
+    connect(qTabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
     connect(addTabButtonPointer, SIGNAL(clicked()), this, SLOT(addTab()));
-    connect(tabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));
+    connect(qTabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));
 
     // Store a copy of the WebEngine default user agent.
     webEngineDefaultUserAgent = currentWebEngineProfilePointer->httpUserAgent();
@@ -94,32 +123,40 @@ TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
 
 TabWidget::~TabWidget()
 {
+    // Get the number of tabs.
+    int numberOfTabs = qTabWidgetPointer->count();
+
     // Manually delete each WebEngine page.
-    for (int i = 0; i < tabWidgetPointer->count(); ++i)
+    for (int i = 0; i < numberOfTabs; ++i)
     {
-        // Get the privacy WebEngine view.
-        PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(i));
+        // Get the tab splitter widget.
+        QWidget *tabSplitterWidgetPointer = qTabWidgetPointer->widget(i);
+
+        // Get the WebEngine views.
+        PrivacyWebEngineView *privacyWebEngineViewPointer = tabSplitterWidgetPointer->findChild<PrivacyWebEngineView *>();
+        DevToolsWebEngineView *devToolsWebEngineViewPointer = tabSplitterWidgetPointer->findChild<DevToolsWebEngineView *>();
 
-        // Deletion the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
+        // Deletion the WebEngine pages to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
         delete privacyWebEngineViewPointer->page();
+        delete devToolsWebEngineViewPointer->page();
     }
 }
 
 // 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.
 void TabWidget::addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *webEngineCookieStorePointer) const
 {
-    // Create a url.
+    // Create a URL.
     QUrl url;
 
     // 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>
-    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.
@@ -138,35 +175,60 @@ void TabWidget::addFirstTab()
     updateUiWithTabSettings();
 
     // 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.
-    tabWidgetPointer->currentWidget()->setFocus();
+    qTabWidgetPointer->currentWidget()->setFocus();
 }
 
-PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
+PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool adjacent, const bool backgroundTab, const QString urlString)
 {
-    // Create a privacy WebEngine view.
+    // Create a splitter widget.
+    QSplitter *splitterPointer = new QSplitter();
+
+    // Set the splitter to be vertical.
+    splitterPointer->setOrientation(Qt::Vertical);
+
+    // Set the splitter handle size.
+    splitterPointer->setHandleWidth(5);
+
+    // Create the WebEngines.
     PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView();
+    DevToolsWebEngineView *devToolsWebEngineViewPointer = new DevToolsWebEngineView();
+
+    // Add the WebEngines to the splitter.
+    splitterPointer->addWidget(privacyWebEngineViewPointer);
+    splitterPointer->addWidget(devToolsWebEngineViewPointer);
+
+    // Initialize the new tab index.
+    int newTabIndex = 0;
 
     // Add a new tab.
-    int newTabIndex = tabWidgetPointer->addTab(privacyWebEngineViewPointer, i18nc("New tab label.", "New Tab"));
+    if (adjacent)  // Add the new tab adjacent to the current tab.
+        newTabIndex = qTabWidgetPointer->insertTab((qTabWidgetPointer->currentIndex() + 1), splitterPointer, i18nc("New tab label.", "New Tab"));
+    else  // Add the new tab at the end of the list.
+        newTabIndex = qTabWidgetPointer->addTab(splitterPointer, i18nc("New tab label.", "New Tab"));
 
     // Set the default tab icon.
-    tabWidgetPointer->setTabIcon(newTabIndex, defaultTabIcon);
+    qTabWidgetPointer->setTabIcon(newTabIndex, defaultFavoriteIcon);
 
-    // Create an off-the-record profile (the default when no profile name is specified).
-    QWebEngineProfile *webEngineProfilePointer = new QWebEngineProfile(QStringLiteral(""));
+    // Get handles for the WebEngine components.
+    QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
+    QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
+    QWebEngineCookieStore *webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
+    QWebEngineSettings *webEngineSettingsPointer = webEnginePagePointer->settings();
 
-    // Create a WebEngine page.
-    QWebEnginePage *webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
+    // Set the development tools WebEngine.  This must be done here to preserve the bottom half of the window as the initial development tools size.
+    webEnginePagePointer->setDevToolsPage(devToolsWebEngineViewPointer->page());
 
-    // Set the WebEngine page.
-    privacyWebEngineViewPointer->setPage(webEnginePagePointer);
+    // Initially hide the development tools WebEngine.
+    devToolsWebEngineViewPointer->setVisible(false);
 
-    // Get handles for the web engine elements.
-    QWebEngineCookieStore *webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
-    QWebEngineSettings *webEngineSettingsPointer = webEnginePagePointer->settings();
+    // Initially disable the development tools WebEngine.
+    webEnginePagePointer->setDevToolsPage(nullptr);
+
+    // Disable JavaScript on the development tools WebEngine to prevent error messages from being written to the console.
+    devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
 
     // Update the URL line edit when the URL changes.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::urlChanged, [privacyWebEngineViewPointer, this] (const QUrl &newUrl)
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::urlChanged, [this, privacyWebEngineViewPointer] (const QUrl &newUrl)
     {
         // Only update the UI if this is the current tab.
         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
@@ -178,24 +240,71 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
             emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
             emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
         }
+    });
+
+    // Update the title when it changes.
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, splitterPointer] (const QString &title)
+    {
+        // Get the index for this tab.
+        int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);
+
+        // Update the title for this tab.
+        qTabWidgetPointer->setTabText(tabIndex, title);
+
+        // Update the window title if this is the current tab.
+        if (tabIndex == qTabWidgetPointer->currentIndex())
+            emit updateWindowTitle(title);
+    });
+
+    // Connect the loading favorite icon movie to the tab icon.
+    connect(loadingFavoriteIconMoviePointer, &QMovie::frameChanged, [this, splitterPointer, privacyWebEngineViewPointer]
+    {
+        // Get the index for this tab.
+        int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);
+
+        // Display the loading favorite icon if this tab is loading.
+        if (privacyWebEngineViewPointer->isLoading)
+            qTabWidgetPointer->setTabIcon(tabIndex, loadingFavoriteIconMoviePointer->currentPixmap());
+    });
+
+    // Update the icon when it changes.
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [this, splitterPointer, privacyWebEngineViewPointer] (const QIcon &newFavoriteIcon)
+    {
+        // Store the favorite icon in the privacy web engine view.
+        if (newFavoriteIcon.isNull())
+            privacyWebEngineViewPointer->favoriteIcon = defaultFavoriteIcon;
+        else
+            privacyWebEngineViewPointer->favoriteIcon = newFavoriteIcon;
 
-        // Reapply the zoom factor.  This is a bug in QWebEngineView that resets the zoom with every load.  It can be removed once <https://redmine.stoutner.com/issues/799> is fixed.
-        privacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
+        // Get the index for this tab.
+        int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);
+
+        // Update the icon for this tab.
+        if (newFavoriteIcon.isNull())
+            qTabWidgetPointer->setTabIcon(tabIndex, defaultFavoriteIcon);
+        else
+            qTabWidgetPointer->setTabIcon(tabIndex, newFavoriteIcon);
     });
 
-    // Update the progress bar when a load is started.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadStarted, [privacyWebEngineViewPointer, this] ()
+    // Update the progress bar and the favorite icon when a load is started.
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadStarted, [this, privacyWebEngineViewPointer] ()
     {
+        // Set the privacy web engine view to be loading.
+        privacyWebEngineViewPointer->isLoading = true;
+
         // Store the load progress.
         privacyWebEngineViewPointer->loadProgressInt = 0;
 
         // Show the progress bar if this is the current tab.
         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
             emit showProgressBar(0);
+
+        // Start the loading favorite icon movie.
+        loadingFavoriteIconMoviePointer->start();
     });
 
     // Update the progress bar when a load progresses.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadProgress, [privacyWebEngineViewPointer, this] (const int progress)
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadProgress, [this, privacyWebEngineViewPointer] (const int progress)
     {
         // Store the load progress.
         privacyWebEngineViewPointer->loadProgressInt = progress;
@@ -206,14 +315,78 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
     });
 
     // Update the progress bar when a load finishes.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [privacyWebEngineViewPointer, this] ()
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [this, splitterPointer, privacyWebEngineViewPointer] ()
     {
+        // Set the privacy web engine view to be not loading.
+        privacyWebEngineViewPointer->isLoading = false;
+
         // Store the load progress.
         privacyWebEngineViewPointer->loadProgressInt = -1;
 
         // Hide the progress bar if this is the current tab.
         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
             emit hideProgressBar();
+
+        // Get the index for this tab.
+        int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);
+
+        // Display the current favorite icon
+        qTabWidgetPointer->setTabIcon(tabIndex, privacyWebEngineViewPointer->favoriteIcon);
+
+        // Create a no tabs loading variable.
+        bool noTabsLoading = true;
+
+        // Get the number of tabs.
+        int numberOfTabs = qTabWidgetPointer->count();
+
+        // Check to see if any other tabs are loading.
+        for (int i = 0; i < numberOfTabs; i++)
+        {
+            // Get the privacy WebEngine view for the tab.
+            PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
+
+            // Check to see if it is currently loading.  If at least one tab is loading, this flag will end up being marked `false` when the for loop has finished.
+            if (privacyWebEngineViewPointer->isLoading)
+                noTabsLoading = false;
+        }
+
+        // Stop the loading favorite icon movie if there are no loading tabs.
+        if (noTabsLoading)
+            loadingFavoriteIconMoviePointer->stop();
+    });
+
+    // Display HTTP Ping blocked dialogs.
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::displayHttpPingBlockedDialog, [this, privacyWebEngineViewPointer] (const QString &httpPingUrl)
+    {
+        // Only display the HTTP Ping blocked dialog if this is the current tab.
+        if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
+        {
+            // Instantiate an HTTP ping blocked message box.
+            QMessageBox httpPingBlockedMessageBox;
+
+            // Set the icon.
+            httpPingBlockedMessageBox.setIcon(QMessageBox::Information);
+
+            // Set the window title.
+            httpPingBlockedMessageBox.setWindowTitle(i18nc("HTTP Ping blocked dialog title", "HTTP Ping Blocked"));
+
+            // Set the text.
+            httpPingBlockedMessageBox.setText(i18nc("HTTP Ping blocked dialog text", "This request has been blocked because it sends a naughty HTTP ping to %1.", httpPingUrl));
+
+            // Set the standard button.
+            httpPingBlockedMessageBox.setStandardButtons(QMessageBox::Ok);
+
+            // Display the message box.
+            httpPingBlockedMessageBox.exec();
+        }
+    });
+
+    // Update the zoom actions when changed by CTRL-Scrolling.  This can be modified when <https://redmine.stoutner.com/issues/845> is fixed.
+    connect(webEnginePagePointer, &QWebEnginePage::contentsSizeChanged, [webEnginePagePointer, this] ()
+    {
+        // Only update the zoom actions if this is the current tab.
+        if (webEnginePagePointer == currentWebEnginePagePointer)
+            emit updateZoomActions(webEnginePagePointer->zoomFactor());
     });
 
     // Display find text results.
@@ -228,15 +401,6 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
     // Handle file downloads.
     connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
 
-    // Instantiate the URL request interceptor.
-    UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
-
-    // Set the URL request interceptor.
-    webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
-
-    // Reapply the domain settings when the host changes.
-    connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
-
     // Set the local storage filter.
     webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
     {
@@ -264,14 +428,14 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
         return false;
     });
 
-    // Disable JavaScript by default (this prevetns JavaScript from being enabled on a new tab before domain settings are loaded).
+    // Disable JavaScript by default (this prevents JavaScript from being enabled on a new tab before domain settings are loaded).
     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
 
     // Don't allow JavaScript to open windows.
     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
 
-    // Allow keyboard navigation.
-    webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
+    // Allow keyboard navigation between links and input fields.
+    webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, Settings::spatialNavigation());
 
     // Enable full screen support.
     webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
@@ -282,8 +446,14 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
     // Limit WebRTC to public IP addresses.
     webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
 
+    // Enable the PDF viewer (it should be enabled by default, but it is nice to be explicit in case the defaults change).
+    webEngineSettingsPointer->setAttribute(QWebEngineSettings::PdfViewerEnabled, true);
+
+    // Plugins must be enabled for the PDF viewer to work.  <https://doc.qt.io/qt-5/qtwebengine-features.html#pdf-file-viewing>
+    webEngineSettingsPointer->setAttribute(QWebEngineSettings::PluginsEnabled, true);
+
     // Update the cookies action.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [privacyWebEngineViewPointer, this] (const int numberOfCookies)
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [this, privacyWebEngineViewPointer] (const int numberOfCookies)
     {
         // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
@@ -301,46 +471,29 @@ PrivacyWebEngineView* TabWidget::addTab(const bool focusNewWebEngineView)
     for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
         addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
 
-    // Update the title when it changes.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, privacyWebEngineViewPointer] (const QString &title)
-    {
-        // Get the index for this tab.
-        int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
-
-        // Update the title for this tab.
-        tabWidgetPointer->setTabText(tabIndex, title);
-
-        // Update the window title if this is the current tab.
-        if (tabIndex == tabWidgetPointer->currentIndex())
-            emit updateWindowTitle(title);
-    });
-
-    // Update the icon when it changes.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [privacyWebEngineViewPointer, this] (const QIcon &icon)
-    {
-        // Get the index for this tab.
-        int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
-
-        // Update the icon for this tab.
-        if (icon.isNull())
-            tabWidgetPointer->setTabIcon(tabIndex, defaultTabIcon);
-        else
-            tabWidgetPointer->setTabIcon(tabIndex, icon);
-    });
-
     // Enable spell checking.
     webEngineProfilePointer->setSpellCheckEnabled(true);
 
     // Set the spell check language.
-    webEngineProfilePointer->setSpellCheckLanguages({QStringLiteral("en_US")});
+    webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
+
+    // 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());
+
+    // Update the UI when domain settings are applied.
+    connect(privacyWebEngineViewPointer, SIGNAL(updateUi(const PrivacyWebEngineView*)), this, SLOT(updateUiFromWebEngineView(const PrivacyWebEngineView*)));
 
-    // Move to the new tab.
-    tabWidgetPointer->setCurrentIndex(newTabIndex);
+    // Move to the new tab if it is not a background tab.
+    if (!backgroundTab)
+        qTabWidgetPointer->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();
 
+    if (urlString != nullptr)
+        privacyWebEngineViewPointer->load(QUrl::fromUserInput(urlString));
+
     // Return the privacy WebEngine view pointer.
     return privacyWebEngineViewPointer;
 }
@@ -349,9 +502,21 @@ void TabWidget::applyApplicationSettings()
 {
     // Set the tab position.
     if (Settings::tabsOnTop())
-        tabWidgetPointer->setTabPosition(QTabWidget::North);
+        qTabWidgetPointer->setTabPosition(QTabWidget::North);
     else
-        tabWidgetPointer->setTabPosition(QTabWidget::South);
+        qTabWidgetPointer->setTabPosition(QTabWidget::South);
+
+    // Get the number of tabs.
+    int numberOfTabs = qTabWidgetPointer->count();
+
+    // Apply the spatial navigation settings to each WebEngine.
+    for (int i = 0; i < numberOfTabs; ++i) {
+        // Get the WebEngine view pointer.
+        PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
+
+        // Apply the spatial navigation settings to each page.
+        privacyWebEngineViewPointer->page()->settings()->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, Settings::spatialNavigation());
+    }
 
     // Set the search engine URL.
     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
@@ -360,174 +525,19 @@ void TabWidget::applyApplicationSettings()
     emit updateSearchEngineActions(Settings::searchEngine(), true);
 }
 
-// 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.
-// Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
 void TabWidget::applyDomainSettingsAndReload()
 {
-    // Apply the domain settings.  `true` reloads the website.
-    applyDomainSettings(currentPrivacyWebEngineViewPointer->url().host(), true);
-}
-
-// 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.
-// Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
-void TabWidget::applyDomainSettingsWithoutReloading(const QString &hostname)
-{
-    // Apply the domain settings  `false` does not reload the website.
-    applyDomainSettings(hostname, false);
-}
-
-// Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
-void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
-{
-    // Get the record for the hostname.
-    QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
-
-    // Check if the hostname has domain settings.
-    if (domainQuery.isValid())  // The hostname has domain settings.
-    {
-        // Get the domain record.
-        QSqlRecord domainRecord = domainQuery.record();
-
-        // Store the domain settings name.
-        currentPrivacyWebEngineViewPointer->domainSettingsName = domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString();
-
-        // Set the JavaScript status.
-        switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
-        {
-            // Set the default JavaScript status.
-            case (DomainsDatabase::SYSTEM_DEFAULT):
-            {
-                currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
-
-                break;
-            }
-
-            // Disable JavaScript.
-            case (DomainsDatabase::DISABLED):
-            {
-                currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
-
-                break;
-            }
-
-            // Enable JavaScript.
-            case (DomainsDatabase::ENABLED):
-            {
-                currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
-
-                break;
-            }
-        }
-
-        // Set the local storage status.
-        switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
-        {
-            // Set the default local storage status.
-            case (DomainsDatabase::SYSTEM_DEFAULT):
-            {
-                currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
-
-                break;
-            }
-
-            // Disable local storage.
-            case (DomainsDatabase::DISABLED):
-            {
-                currentPrivacyWebEngineViewPointer->localStorageEnabled = false;
-
-                break;
-            }
-
-            // Enable local storage.
-            case (DomainsDatabase::ENABLED):
-            {
-                currentPrivacyWebEngineViewPointer->localStorageEnabled = true;
-
-                break;
-            }
-        }
-
-        // Set the DOM storage status.
-        switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
-        {
-            // Set the default DOM storage status.
-            case (DomainsDatabase::SYSTEM_DEFAULT):
-            {
-                currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
-
-                break;
-            }
-
-            // Disable DOM storage.
-            case (DomainsDatabase::DISABLED):
-            {
-                currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
-
-                break;
-            }
-
-            // Enable DOM storage.
-            case (DomainsDatabase::ENABLED):
-            {
-                currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
-
-                break;
-            }
-        }
-
-        // Set the user agent.
-        currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));
-
-        // Check if a custom zoom factor is set.
-        if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
-        {
-            // Store the current zoom factor.
-            currentZoomFactor = domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble();
-        }
-        else
-        {
-            // Reset the current zoom factor.
-            currentZoomFactor = Settings::zoomFactor();
-        }
-
-        // Set the zoom factor.    The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
-        currentPrivacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
-    }
-    else  // The hostname does not have domain settings.
-    {
-        // Reset the domain settings name.
-        currentPrivacyWebEngineViewPointer->domainSettingsName = QStringLiteral("");
+    // Get the number of tabs.
+    int numberOfTabs = qTabWidgetPointer->count();
 
-        // Set the JavaScript status.
-        currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
+    // Apply the domain settings to each WebEngine.
+    for (int i = 0; i < numberOfTabs; ++i) {
+        // Get the WebEngine view pointer.
+        PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
 
-        // Set the local storage status.
-        currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
-
-        // Set DOM storage.  In QWebEngineSettings it is called Local Storage.
-        currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
-
-        // Set the user agent.
-        currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
-
-        // Store the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
-        currentZoomFactor = Settings::zoomFactor();
-
-        // Set the zoom factor.
-        currentPrivacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
+        // Apply the spatial navigation settings to each page.
+        privacyWebEngineViewPointer->applyDomainSettings(privacyWebEngineViewPointer->url().host(), true);
     }
-
-    // Update the UI.
-    emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
-    emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
-    emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
-    emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
-    emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
-    emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
-
-    // Reload the website if requested.
-    if (reloadWebsite)
-        currentPrivacyWebEngineViewPointer->reload();
 }
 
 void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
@@ -541,7 +551,7 @@ void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
     // Store the search engine string.
     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
 
-    // Update the search engine actionas.
+    // Update the search engine actions.
     emit updateSearchEngineActions(searchEngineName, false);
 }
 
@@ -554,7 +564,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);
@@ -563,14 +573,32 @@ void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
     currentPrivacyWebEngineViewPointer->reload();
 }
 
-// This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
-void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor)
+void TabWidget::applyOnTheFlyZoomFactor(const double zoomFactorDouble) const
 {
-    // Update the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
-    currentZoomFactor = zoomFactor;
-
     // Set the zoom factor.
-    currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactor);
+    currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactorDouble);
+}
+
+void TabWidget::applySpellCheckLanguages() const
+{
+    // Get the number of tab.
+    int numberOfTabs = qTabWidgetPointer->count();
+
+    // Set the spell check languages for each tab.
+    for (int i = 0; i < numberOfTabs; ++i)
+    {
+        // Get the WebEngine view pointer.
+        PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
+
+        // Get the WebEngine page pointer.
+        QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
+
+        // Get the WebEngine profile pointer.
+        QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
+
+        // Set the spell check languages.
+        webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
+    }
 }
 
 void TabWidget::back() const
@@ -593,20 +621,29 @@ void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
 
 void TabWidget::deleteTab(const int tabIndex)
 {
-    // Get the privacy WebEngine view.
-    PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(tabIndex));
+    // Get the tab splitter widget.
+    QWidget *tabSplitterWidgetPointer = qTabWidgetPointer->widget(tabIndex);
 
-    // Proccess the tab delete according to the number of tabs.
-    if (tabWidgetPointer->count() > 1)  // There is more than one tab.
+    // Get the WebEngine views.
+    PrivacyWebEngineView *privacyWebEngineViewPointer = tabSplitterWidgetPointer->findChild<PrivacyWebEngineView *>();
+    DevToolsWebEngineView *devToolsWebEngineViewPointer = tabSplitterWidgetPointer->findChild<DevToolsWebEngineView *>();
+
+    // Process the tab delete according to the number of tabs.
+    if (qTabWidgetPointer->count() > 1)  // There is more than one tab.
     {
-        // Delete the tab.
-        tabWidgetPointer->removeTab(tabIndex);
+        // Remove the tab.
+        qTabWidgetPointer->removeTab(tabIndex);
 
-        // Delete the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
+        // Delete the WebEngine pages to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
         delete privacyWebEngineViewPointer->page();
+        delete devToolsWebEngineViewPointer->page();
 
-        // Delete the privacy WebEngine view.
+        // Delete the WebEngine views.
         delete privacyWebEngineViewPointer;
+        delete devToolsWebEngineViewPointer;
+
+        // Delete the tab splitter widget.
+        delete tabSplitterWidgetPointer;
     }
     else  // There is only one tab.
     {
@@ -645,7 +682,7 @@ void TabWidget::findText(const QString &text) const
 
 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`.
+    // Update the find text UI if it wasn't simply wiping the current find text selection.  Otherwise the UI temporarily flashes `0/0`.
     if (wipingCurrentFindTextSelection)  // The current selection is being wiped.
     {
         // Reset the flag.
@@ -682,6 +719,30 @@ std::list<QNetworkCookie>* TabWidget::getCookieList() const
     return currentPrivacyWebEngineViewPointer->cookieListPointer;
 }
 
+QIcon TabWidget::getCurrentTabFavoritIcon() const
+{
+    // Return the current Privacy WebEngine favorite icon.
+    return currentPrivacyWebEngineViewPointer->favoriteIcon;
+}
+
+QString TabWidget::getCurrentTabTitle() const
+{
+    // Return the current Privacy WebEngine title.
+    return currentPrivacyWebEngineViewPointer->title();
+}
+
+QString TabWidget::getCurrentTabUrl() const
+{
+    // Return the current Privacy WebEngine URL as a string.
+    return currentPrivacyWebEngineViewPointer->url().toString();
+}
+
+QString TabWidget::getCurrentUserAgent() const
+{
+    // Return the current WebEngine user agent.
+    return currentWebEngineProfilePointer->httpUserAgent();
+}
+
 QString& TabWidget::getDomainSettingsName() const
 {
     // Return the domain settings name.
@@ -727,7 +788,7 @@ void TabWidget::loadInitialWebsite()
 void TabWidget::loadUrlFromLineEdit(QString url) const
 {
     // Decide if the text is more likely to be a URL or a search.
-    if (url.startsWith("file://"))  // The text is likely a file URL.
+    if (url.startsWith("file://") || url.startsWith("view-source:"))  // The text is likely a file or view source URL.
     {
         // Load the URL.
         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
@@ -783,6 +844,12 @@ void TabWidget::pageLinkHovered(const QString &linkUrl) const
     emit linkHovered(linkUrl);
 }
 
+void TabWidget::stopLoadingFavoriteIconMovie() const
+{
+    // Stop the loading favorite icon movie.  Otherwise, the browser will crash if a second window is closed while a tab in it is loading.  <https://redmine.stoutner.com/issues/1010>
+    loadingFavoriteIconMoviePointer->stop();
+}
+
 void TabWidget::print() const
 {
     // Create a printer.
@@ -839,69 +906,263 @@ void TabWidget::printWebpage(QPrinter *printerPointer) const
 
 void TabWidget::refresh() const
 {
+    // Reset the HTTP authentication dialog counter.
+    currentPrivacyWebEngineViewPointer->httpAuthenticationDialogsDisplayed = 0;
+
     // Reload the website.
     currentPrivacyWebEngineViewPointer->reload();
 }
 
-void TabWidget::setTabBarVisible(const bool visible) const
+void TabWidget::reloadAndBypassCache() const
 {
-    // Set the tab bar visibility.
-    tabWidgetPointer->tabBar()->setVisible(visible);
+    // Reload the website, bypassing the cache.
+    currentWebEnginePagePointer->triggerAction(QWebEnginePage::ReloadAndBypassCache);
 }
 
-void TabWidget::showSaveDialog(QWebEngineDownloadItem *downloadItemPointer) const
+void TabWidget::saveArchive()
 {
-    // Instantiate the save dialog.
-    SaveDialog *saveDialogPointer = new SaveDialog(downloadItemPointer);
+    // Get the suggested file name.
+    QString suggestedFileName = currentPrivacyWebEngineViewPointer->title() + ".mht";
 
-    // Connect the save button.
-    connect(saveDialogPointer, SIGNAL(showSaveFilePickerDialog(QUrl &, QString &)), this, SLOT(showSaveFilePickerDialog(QUrl &, QString &)));
+    // Get the download directory.
+    QString downloadDirectory = Settings::downloadDirectory();
 
-    // Show the dialog.
-    saveDialogPointer->show();
+    // Resolve the system download directory if specified.
+    if (downloadDirectory == QLatin1String("System Download Directory"))
+        downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+
+    // Get a file path from the file picker.
+    QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
+
+    // Save the webpage as an archive if the file save path is populated.
+    if (!saveFilePath.isEmpty())
+    {
+        // Update the download directory if specified.
+        if (Settings::autoUpateDownloadDirectory())
+            updateDownloadDirectory(saveFilePath);
+
+        // Set the saving archive flag.  Otherwise, a second download tries to run.
+        savingArchive = true;
+
+        // Save the archive.
+        currentWebEnginePagePointer->save(saveFilePath);
+    }
 }
 
-void TabWidget::showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName)
+void TabWidget::setTabBarVisible(const bool visible) const
 {
-    // Get the download location.
-    QString downloadDirectory = Settings::downloadLocation();
+    // Set the tab bar visibility.
+    qTabWidgetPointer->tabBar()->setVisible(visible);
+}
 
-    // Resolve the system download directory if specified.
-    if (downloadDirectory == QStringLiteral("System Download Directory"))
-        downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+void TabWidget::showSaveDialog(QWebEngineDownloadItem *webEngineDownloadItemPointer)
+{
+    // Only show the save dialog if an archive is not currently being saved.  Otherwise, two save dialogs will be shown.
+    if (!savingArchive)
+    {
+        // Get the download attributes.
+        QUrl downloadUrl = webEngineDownloadItemPointer->url();
+        QString mimeTypeString = webEngineDownloadItemPointer->mimeType();
+        QString suggestedFileName = webEngineDownloadItemPointer->suggestedFileName();
+        int totalBytes = webEngineDownloadItemPointer->totalBytes();
+
+        // Check to see if Privacy Browser is not running KDE or if local storage (cookies) is enabled.
+        if (!isRunningKde || currentPrivacyWebEngineViewPointer->localStorageEnabled)  // KDE is not running or local storage (cookies) is enabled.  Use WebEngine's downloader.
+        {
+            // Instantiate the save dialog.
+            SaveDialog *saveDialogPointer = new SaveDialog(this, downloadUrl, mimeTypeString, totalBytes);
 
-    // Create a save file dialog.
-    QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
+            // Display the save dialog.
+            int saveDialogResult = saveDialogPointer->exec();
 
-    // Tell the dialog to use a save button.
-    saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
+            // Process the save dialog results.
+            if (saveDialogResult == QDialog::Accepted)  // Save was selected.
+            {
+                // Get the download directory.
+                QString downloadDirectory = Settings::downloadDirectory();
 
-    // Populate the file name from the download item pointer.
-    saveFileDialogPointer->selectFile(suggestedFileName);
+                // Resolve the system download directory if specified.
+                if (downloadDirectory == QLatin1String("System Download Directory"))
+                    downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
 
-    // Prevent interaction with the parent window while the dialog is open.
-    saveFileDialogPointer->setWindowModality(Qt::WindowModal);
+                // Get a file path from the file picker.
+                QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
 
-    // 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);
+                // Process the save file path.
+                if (!saveFilePath.isEmpty())  // The file save path is populated.
+                {
+                    // Update the download directory if specified.
+                    if (Settings::autoUpateDownloadDirectory())
+                        updateDownloadDirectory(saveFilePath);
 
-        // Create a file copy job.  `-1` creates the file with default permissions.
-        KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
+                    // Create a save file path file info.
+                    QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath);
+
+                    // Get the canonical save path and file name.
+                    QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
+                    QString saveFileName = saveFilePathFileInfo.fileName();
+
+                    // Set the download directory and file name.
+                    webEngineDownloadItemPointer->setDownloadDirectory(absoluteSavePath);
+                    webEngineDownloadItemPointer->setDownloadFileName(saveFileName);
+
+                    // Create a file download notification.
+                    KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
+
+                    // Set the notification title.
+                    fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
+
+                    // Set the notification text.
+                    fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));
+
+                    // Get the download icon from the theme.
+                    QIcon downloadIcon = QIcon::fromTheme(QLatin1String("download"), QIcon::fromTheme(QLatin1String("document-save")));
+
+                    // Set the notification icon.
+                    fileDownloadNotificationPointer->setIconName(downloadIcon.name());
+
+                    // Set the action list cancel button.
+                    fileDownloadNotificationPointer->setActions(QStringList({i18nc("Download notification action","Cancel")}));
+
+                    // Prevent the notification from being autodeleted if it is closed.  Otherwise, the updates to the notification below cause a crash.
+                    fileDownloadNotificationPointer->setAutoDelete(false);
+
+                    // 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)
+                    {
+                        // Set the new text.  Total bytes will be 0 if the download size is unknown.
+                        if (totalBytes > 0)
+                        {
+                            // Calculate the download percentage.
+                            int downloadPercentage = 100 * bytesReceived / totalBytes;
+
+                            // Set the file download notification text.
+                            fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1\% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
+                                                                        bytesReceived, totalBytes));
+                        }
+                        else
+                        {
+                            // Set the file download notification text.
+                            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> {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();
+                    });
+
+                    // Display the notification.
+                    fileDownloadNotificationPointer->sendEvent();
+
+                    // 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  // KDE is running and 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(this, downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true);
 
-        // Set the download job to display any error messages.
-        fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
+            // Connect the save button.
+            connect(saveDialogPointer, SIGNAL(useNativeKdeDownloader(QUrl &, QString &)), this, SLOT(useNativeKdeDownloader(QUrl &, QString &)));
 
-        // Start the download.
-        fileCopyJobPointer->start();
-    };
+            // Show the dialog.
+            saveDialogPointer->show();
+        }
+    }
 
-    // Handle clicks on the save button.
-    connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
+    // Reset the saving archive flag.
+    savingArchive = false;
+}
 
-    // Show the dialog.
-    saveFileDialogPointer->show();
+void TabWidget::stop() const
+{
+    // Stop the loading of the current privacy WebEngine.
+    currentPrivacyWebEngineViewPointer->stop();
+}
+
+void TabWidget::toggleDeveloperTools(const bool enabled) const
+{
+    // Get a handle for the current developer tools WebEngine.
+    DevToolsWebEngineView *devToolsWebEngineViewPointer = qTabWidgetPointer->currentWidget()->findChild<DevToolsWebEngineView *>();
+
+    if (enabled)
+    {
+        // Set the zoom factor on the development tools WebEngine.
+        devToolsWebEngineViewPointer->setZoomFactor(currentWebEnginePagePointer->zoomFactor());
+
+        // Enable the development tools.
+        currentWebEnginePagePointer->setDevToolsPage(devToolsWebEngineViewPointer->page());
+
+        // Enable JavaScript on the development tools WebEngine.
+        devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+
+        // Display the developer tools.
+        devToolsWebEngineViewPointer->setVisible(true);
+    }
+    else
+    {
+        // Disable JavaScript on the development tools WebEngine to prevent error messages from being written to the console.
+        devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
+
+        // Disable the development tools.
+        currentWebEnginePagePointer->setDevToolsPage(nullptr);
+
+        // Hide the developer tools.
+        devToolsWebEngineViewPointer->setVisible(false);
+    }
 }
 
 void TabWidget::toggleDomStorage() const
@@ -925,7 +1186,7 @@ void TabWidget::toggleFindCaseSensitive(const QString &text)
     wipingCurrentFindTextSelection = true;
 
     // Wipe the previous search.  Otherwise currently highlighted words will remain highlighted.
-    findText(QStringLiteral(""));
+    findText(QLatin1String(""));
 
     // Update the find text.
     findText(text);
@@ -945,7 +1206,7 @@ void TabWidget::toggleJavaScript() const
 
 void TabWidget::toggleLocalStorage()
 {
-    // Toggle local storeage.
+    // Toggle local storage.
     currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
 
     // Update the local storage action.
@@ -955,10 +1216,41 @@ void TabWidget::toggleLocalStorage()
     currentPrivacyWebEngineViewPointer->reload();
 }
 
+void TabWidget::updateDownloadDirectory(QString newDownloadDirectory) const
+{
+    // Remove the file name from the save file path.
+    newDownloadDirectory.truncate(newDownloadDirectory.lastIndexOf(QLatin1Char('/')));
+
+    // Update the download location.
+    Settings::setDownloadDirectory(newDownloadDirectory);
+
+    // Get a handle for the KConfig skeleton.
+    KConfigSkeleton *kConfigSkeletonPointer = Settings::self();
+
+    // Write the settings to disk.
+    kConfigSkeletonPointer->save();
+}
+
+void TabWidget::updateUiFromWebEngineView(const PrivacyWebEngineView *privacyWebEngineViewPointer) const
+{
+    // Only update the UI if the signal was emitted from the current privacy WebEngine.
+    if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
+    {
+        // Update the UI.
+        emit updateDefaultZoomFactor(currentPrivacyWebEngineViewPointer->defaultZoomFactor);
+        emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
+        emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
+        emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
+        emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
+        emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
+        emit updateZoomActions(currentPrivacyWebEngineViewPointer->zoomFactor());
+    }
+}
+
 void TabWidget::updateUiWithTabSettings()
 {
     // Update the current WebEngine pointers.
-    currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->currentWidget());
+    currentPrivacyWebEngineViewPointer = qTabWidgetPointer->currentWidget()->findChild<PrivacyWebEngineView *>();
     currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
     currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
     currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
@@ -968,19 +1260,24 @@ void TabWidget::updateUiWithTabSettings()
     // Clear the URL line edit focus.
     emit clearUrlLineEditFocus();
 
+    // Get a handle for the development tools WebEngine view.
+    DevToolsWebEngineView *devToolsWebEngineViewPointer = qTabWidgetPointer->currentWidget()->findChild<DevToolsWebEngineView *>();
+
     // Update the actions.
+    emit updateDefaultZoomFactor(currentPrivacyWebEngineViewPointer->defaultZoomFactor);
     emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
     emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
+    emit updateDeveloperToolsAction(devToolsWebEngineViewPointer->isVisible());
     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
     emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
-    emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
+    emit updateZoomActions(currentPrivacyWebEngineViewPointer->zoomFactor());
 
     // Update the URL.
     emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
-    emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
+    emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
     emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
 
     // Update the find text.
@@ -993,3 +1290,52 @@ void TabWidget::updateUiWithTabSettings()
     else
         emit hideProgressBar();
 }
+
+void TabWidget::useNativeKdeDownloader(QUrl &downloadUrl, QString &suggestedFileName)
+{
+    // Get the download directory.
+    QString downloadDirectory = Settings::downloadDirectory();
+
+    // 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, this] ()
+    {
+        // Get the save location.  The dialog box should only allow the selecting of one file location.
+        QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
+
+        // Update the download directory if specified.
+        if (Settings::autoUpateDownloadDirectory())
+            updateDownloadDirectory(saveLocation.toLocalFile());
+
+        // 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 warning and error messages.
+        fileCopyJobPointer->uiDelegate()->setAutoWarningHandlingEnabled(true);
+        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();
+}
index bd857b0d65a8b431c8a7dd08779cde91d057ad30..4b06c640f5f1080e795b5567ba8ff58446e04985 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
 
 // Application headers.
 #include "PrivacyWebEngineView.h"
+#include "helpers/UserAgentHelper.h"
 
 // KDE Framework headers.
 #include <KLineEdit>
 
 // Qt toolkit headers.
+#include <QMovie>
 #include <QPushButton>
 #include <QTabWidget>
 #include <QWebEngineCookieStore>
@@ -43,19 +45,24 @@ class TabWidget : public QWidget
 
 public:
     // The primary contructor.
-    explicit TabWidget(QWidget *parent);
+    explicit TabWidget(QWidget *windowPointer);
 
     // The destructor.
     ~TabWidget();
 
     // The public functions.
-    void applyOnTheFlyZoomFactor(const double &zoomFactor);
+    void applyOnTheFlyZoomFactor(const double zoomFactorDouble) const;
     PrivacyWebEngineView* loadBlankInitialWebsite();
     void loadInitialWebsite();
     void findPrevious(const QString &text) const;
     std::list<QNetworkCookie>* getCookieList() const;
+    QIcon getCurrentTabFavoritIcon() const;
+    QString getCurrentTabTitle() const;
+    QString getCurrentTabUrl() const;
+    QString getCurrentUserAgent() const;
     QString& getDomainSettingsName() const;
     void setTabBarVisible(const bool visible) const;
+    void toggleDeveloperTools(const bool enabled) const;
     void toggleDomStorage() const;
     void toggleFindCaseSensitive(const QString &text);
     void toggleJavaScript() const;
@@ -75,6 +82,8 @@ signals:
     void showProgressBar(const int &progress) const;
     void updateBackAction(const bool &isEnabled) const;
     void updateCookiesAction(const int numberOfCookies) const;
+    void updateDefaultZoomFactor(const double newDefaultZoomFactorDouble) const;
+    void updateDeveloperToolsAction(const bool &isEnabled) const;
     void updateDomStorageAction(const bool &isEnabled) const;
     void updateDomainSettingsIndicator(const bool status) const;
     void updateFindText(const QString &text, const bool findCaseSensitive) const;
@@ -86,17 +95,17 @@ signals:
     void updateUrlLineEdit(const QUrl &newUrl) const;
     void updateUserAgentActions(const QString &userAgent, const bool &updateCustomUserAgentStatus) const;
     void updateWindowTitle(const QString &title) const;
-    void updateZoomFactorAction(const double &zoomFactor) const;
+    void updateZoomActions(const double zoomFactorDouble) const;
 
 public Q_SLOTS:
     // The public slots.
     void addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *webEngineCookieStorePointer = nullptr) const;
-    PrivacyWebEngineView* addTab(const bool focusNewWebEngineView=false);
+    PrivacyWebEngineView* addTab(const bool removeUrlLineEditFocus = false, const bool adjacent = false, const bool backgroundTab = false, const QString urlString = nullptr);
     void applyApplicationSettings();
     void applyDomainSettingsAndReload();
-    void applyDomainSettingsWithoutReloading(const QString &hostname);
     void applyOnTheFlySearchEngine(QAction *searchEngineActionPointer);
     void applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const;
+    void applySpellCheckLanguages() const;
     void back() const;
     void deleteAllCookies() const;
     void deleteCookieFromStore(const QNetworkCookie &cookie) const;
@@ -109,6 +118,9 @@ public Q_SLOTS:
     void print() const;
     void printPreview() const;
     void refresh() const;
+    void reloadAndBypassCache() const;
+    void saveArchive();
+    void stop() const;
 
 private Q_SLOTS:
     // The private slots.
@@ -118,25 +130,30 @@ private Q_SLOTS:
     void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const;
     void pageLinkHovered(const QString &linkUrl) const;
     void printWebpage(QPrinter *printerPointer) const;
-    void showSaveDialog(QWebEngineDownloadItem *downloadItemPointer) const;
-    void showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName);
+    void showSaveDialog(QWebEngineDownloadItem *downloadItemPointer);
+    void stopLoadingFavoriteIconMovie() const;
+    void updateUiFromWebEngineView(const PrivacyWebEngineView *privacyWebEngineViewPointer) const;
     void updateUiWithTabSettings();
+    void useNativeKdeDownloader(QUrl &downloadUrl, QString &suggestedFileName);
 
 private:
+    // The private functions.
+    void updateDownloadDirectory(QString newDownloadDirectory) const;
+
     // The private variables.
-    double currentZoomFactor;  // This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
     PrivacyWebEngineView *currentPrivacyWebEngineViewPointer;
     QWebEngineCookieStore *currentWebEngineCookieStorePointer;
     QWebEngineHistory *currentWebEngineHistoryPointer;
     QWebEnginePage *currentWebEnginePagePointer;
     QWebEngineProfile *currentWebEngineProfilePointer;
     QWebEngineSettings *currentWebEngineSettingsPointer;
-    QIcon defaultTabIcon = QIcon::fromTheme(QStringLiteral("globe"));
+    QIcon defaultFavoriteIcon = QIcon::fromTheme(QLatin1String("globe"), QIcon::fromTheme(QLatin1String("applications-internet")));
+    bool isRunningKde = false;
+    QMovie *loadingFavoriteIconMoviePointer;
+    QTabWidget *qTabWidgetPointer;
+    bool savingArchive;
     QString searchEngineUrl;
-    QTabWidget *tabWidgetPointer;
+    UserAgentHelper *userAgentHelperPointer;
     bool wipingCurrentFindTextSelection = false;
-
-    // The private functions.
-    void applyDomainSettings(const QString &hostname, const bool reloadWebsite);
 };
 #endif
diff --git a/src/widgets/UrlLineEdit.cpp b/src/widgets/UrlLineEdit.cpp
new file mode 100644 (file)
index 0000000..04de23b
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "UrlLineEdit.h"
+
+// Qt toolkit headers.
+#include <QInputMethodEvent>
+
+// Construct the class.
+UrlLineEdit::UrlLineEdit(QWidget *parentWidgetPointer) : KLineEdit(parentWidgetPointer) {}
+
+void UrlLineEdit::focusInEvent(QFocusEvent *focusEventPointer)
+{
+    // Create an input method event attribute list.
+    QList<QInputMethodEvent::Attribute> inputMethodEventAttributeList;
+
+    // Create an input method event with no formatting.
+    QInputMethodEvent inputMethodEvent(QString(QLatin1String("")), inputMethodEventAttributeList);
+
+    // Apply the empty formatting to remove the URL highlighting.
+    event(&inputMethodEvent);
+
+    // Run the default commands.
+    KLineEdit::focusInEvent(focusEventPointer);
+}
+
+void UrlLineEdit::focusOutEvent(QFocusEvent *focusEventPointer)
+{
+    // Reapply the highlight to the current text.
+    setText(text());
+
+    // Run the default commands.
+    KLineEdit::focusOutEvent(focusEventPointer);
+}
+
+void UrlLineEdit::setText(const QString &urlString)
+{
+    // Wipe the currently displayed text.
+    KLineEdit::setText(QLatin1String(""));
+
+    // Only highlight the URL if it starts with a known schema.
+    if (urlString.startsWith(QLatin1String("https://")) || urlString.startsWith(QLatin1String("http://")) || urlString.startsWith(QLatin1String("view-source:")))
+    {
+        // Create an input method event attribute list.
+        QList<QInputMethodEvent::Attribute> inputMethodEventAttributeList;
+
+        // Calculate the URL string length.
+        int urlStringLength = urlString.length();
+
+        // Get the index of end of the schema.
+        int endOfSchemaIndex = urlString.indexOf(QLatin1String("//")) + 2;
+
+        // Get the index of the `/` immediately after the domain name.  If this doesn't exit it will be set to `-1`.
+        int endOfDomainNameIndex = urlString.indexOf(QLatin1Char('/'), endOfSchemaIndex);
+
+        // Get the index of the last `.` in the domain.
+        int lastDotIndex = urlString.lastIndexOf(QLatin1Char('.'), endOfDomainNameIndex - urlStringLength);
+
+        // Get the index of the penultimate `.` in the domain.  If this doesn't exist it will be set to `-1`.
+        int penultimateDotIndex = urlString.lastIndexOf(QLatin1Char('.'), lastDotIndex - urlStringLength - 1);
+
+        // Create the text character formats.
+        QTextCharFormat grayTextCharFormat;
+        QTextCharFormat schemaTextCharFormat;
+
+        // Set the text character format colors.
+        grayTextCharFormat.setForeground(Qt::darkGray);
+
+        // Set the schema text character format color.
+        if (urlString.startsWith(QLatin1String("http://")) || urlString.startsWith(QLatin1String("view-source:http://")))
+            schemaTextCharFormat.setForeground(Qt::red);
+        else
+            schemaTextCharFormat = grayTextCharFormat;
+
+        // Append the schema text format format.
+        inputMethodEventAttributeList.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, endOfSchemaIndex, schemaTextCharFormat));
+
+        // Append the subdomain format if the subdomain exists.  If it doesn't, the penultimate dot index will be `-1`.
+        if (penultimateDotIndex > 0)
+            inputMethodEventAttributeList.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, endOfSchemaIndex, penultimateDotIndex + 1 - endOfSchemaIndex, grayTextCharFormat));
+
+        // Append the post domain formatting if text exists after the domain name.  If it doesn't, the end of domain name index will be `-1`.
+        if (endOfDomainNameIndex > 0)
+            inputMethodEventAttributeList.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, endOfDomainNameIndex, urlStringLength - endOfDomainNameIndex, grayTextCharFormat));
+
+        // Create an input method event with an empty pre-edit string.
+        QInputMethodEvent inputMethodEvent(QString(""), inputMethodEventAttributeList);
+
+        // Apply the URL highlighting.
+        event(&inputMethodEvent);
+    }
+
+    // Run the default commands.
+    KLineEdit::setText(urlString);
+}
diff --git a/src/widgets/UrlLineEdit.h b/src/widgets/UrlLineEdit.h
new file mode 100644 (file)
index 0000000..f57232a
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef URLLINEEDIT_H
+#define URLLINEEDIT_H
+
+// KDE Framework headers.
+#include <KLineEdit>
+
+class UrlLineEdit : public KLineEdit
+{
+    // Include the Q_OBJECT macro.
+    Q_OBJECT
+
+public:
+    // The default constructor.
+    explicit UrlLineEdit(QWidget *parentWidgetPointer = nullptr);
+
+    // The public functions.
+    void setText(const QString &urlString) override;
+
+protected:
+    // The protected functions.
+    void focusInEvent(QFocusEvent *focusEventPointer) override;
+    void focusOutEvent(QFocusEvent *focusEventPointer) override;
+};
+#endif
index 716f2e0c58f57dea8d676a00c660ebf697ac58c1..fe3b5939e86ea73d157d4ee87adf7165c59e839d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
 // Application headers.
 #include "BrowserWindow.h"
 #include "Settings.h"
-#include "ui_SettingsPrivacy.h"
-#include "ui_SettingsGeneral.h"
+#include "databases/BookmarksDatabase.h"
+#include "databases/DomainsDatabase.h"
+#include "dialogs/AddBookmarkDialog.h"
+#include "dialogs/AddFolderDialog.h"
+#include "dialogs/BookmarksDialog.h"
 #include "dialogs/CookiesDialog.h"
 #include "dialogs/DomainSettingsDialog.h"
+#include "dialogs/EditBookmarkDialog.h"
+#include "dialogs/EditFolderDialog.h"
+#include "dialogs/SettingsDialog.h"
 #include "helpers/SearchEngineHelper.h"
 #include "helpers/UserAgentHelper.h"
+#include "structs/BookmarkStruct.h"
 
 // KDE Frameworks headers.
 #include <KActionCollection>
 #include <KColorScheme>
-#include <KToolBar>
+#include <KLocalizedString>
+#include <KXMLGUIFactory>
 
 // Qt toolkit headers.
-#include <QFileDialog>
+#include <QClipboard>
+#include <QContextMenuEvent>
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+#include <QDBusMessage>
+#include <QGuiApplication>
 #include <QInputDialog>
+#include <QLayout>
 #include <QNetworkCookie>
 #include <QMenuBar>
+#include <QMessageBox>
 #include <QShortcut>
 #include <QStatusBar>
 #include <QWebEngineFindTextResult>
 
 // Construct the class.
-BrowserWindow::BrowserWindow(bool firstWindow) : KXmlGuiWindow()
+BrowserWindow::BrowserWindow(bool firstWindow, QString *initialUrlStringPointer) : KXmlGuiWindow()
 {
     // Initialize the variables.
     javaScriptEnabled = false;
@@ -55,47 +70,60 @@ BrowserWindow::BrowserWindow(bool firstWindow) : KXmlGuiWindow()
     setCentralWidget(tabWidgetPointer);
 
     // Get a handle for the action collection.
-    KActionCollection *actionCollectionPointer = this->actionCollection();
+    actionCollectionPointer = this->actionCollection();
 
     // Add the standard actions.
-    KStandardAction::openNew(this, SLOT(fileNew()), actionCollectionPointer);
     KStandardAction::print(tabWidgetPointer, SLOT(print()), actionCollectionPointer);
-    KStandardAction::printPreview(tabWidgetPointer, SLOT(printPreview()), actionCollectionPointer);
+    QAction *printPreviewActionPointer = KStandardAction::printPreview(tabWidgetPointer, SLOT(printPreview()), actionCollectionPointer);
     KStandardAction::quit(qApp, SLOT(closeAllWindows()), actionCollectionPointer);
-    KStandardAction::redisplay(this, SLOT(refresh()), actionCollectionPointer);
+    zoomInActionPointer = KStandardAction::zoomIn(this, SLOT(incrementZoom()), actionCollectionPointer);
+    zoomOutActionPointer = KStandardAction::zoomOut(this, SLOT(decrementZoom()), actionCollectionPointer);
+    refreshActionPointer = KStandardAction::redisplay(this, SLOT(refresh()), actionCollectionPointer);
     fullScreenActionPointer = KStandardAction::fullScreen(this, SLOT(toggleFullScreen()), this, actionCollectionPointer);
     QAction *backActionPointer = KStandardAction::back(this, SLOT(back()), actionCollectionPointer);
     QAction *forwardActionPointer = KStandardAction::forward(this, SLOT(forward()), actionCollectionPointer);
     KStandardAction::home(this, SLOT(home()), actionCollectionPointer);
+    QAction *editBookmarksActionPointer = KStandardAction::editBookmarks(this, SLOT(editBookmarks()), actionCollectionPointer);
     KStandardAction::preferences(this, SLOT(showSettingsDialog()), actionCollectionPointer);
-    KStandardAction::find(this, SLOT(focusFindLineEdit()), actionCollectionPointer);
-    QAction *findNextActionPointer = KStandardAction::findNext(this, SLOT(findNext()), actionCollectionPointer);
-    KStandardAction::findPrev(this, SLOT(findPrevious()), actionCollectionPointer);
+    KStandardAction::find(this, SLOT(showFindTextActions()), actionCollectionPointer);
+    findNextActionPointer = KStandardAction::findNext(this, SLOT(findNext()), actionCollectionPointer);
+    findPreviousActionPointer = KStandardAction::findPrev(this, SLOT(findPrevious()), actionCollectionPointer);
 
     // Add the custom actions.
-    userAgentPrivacyBrowserActionPointer = actionCollectionPointer->addAction(QStringLiteral("user_agent_privacy_browser"));
-    userAgentWebEngineDefaultActionPointer = actionCollectionPointer->addAction(QStringLiteral("user_agent_webengine_default"));
-    userAgentFirefoxLinuxActionPointer = actionCollectionPointer->addAction(QStringLiteral("user_agent_firefox_linux"));
-    userAgentChromiumLinuxActionPointer = actionCollectionPointer->addAction(QStringLiteral("user_agent_chromium_linux"));
-    userAgentFirefoxWindowsActionPointer = actionCollectionPointer->addAction(QStringLiteral("user_agent_firefox_windows"));
-    userAgentChromeWindowsActionPointer = actionCollectionPointer->addAction(QStringLiteral("user_agent_chrome_windows"));
-    userAgentEdgeWindowsActionPointer = actionCollectionPointer->addAction(QStringLiteral("user_agent_edge_windows"));
-    userAgentSafariMacosActionPointer = actionCollectionPointer->addAction(QStringLiteral("user_agent_safari_macos"));
-    userAgentCustomActionPointer = actionCollectionPointer->addAction(QStringLiteral("user_agent_custom"));
-    zoomFactorActionPointer = actionCollectionPointer->addAction(QStringLiteral("zoom_factor"));
-    searchEngineMojeekActionPointer = actionCollectionPointer->addAction(QStringLiteral("search_engine_mojeek"));
-    searchEngineMonoclesActionPointer = actionCollectionPointer->addAction(QStringLiteral("search_engine_monocles"));
-    searchEngineMetagerActionPointer = actionCollectionPointer->addAction(QStringLiteral("search_engine_metager"));
-    searchEngineGoogleActionPointer = actionCollectionPointer->addAction(QStringLiteral("search_engine_google"));
-    searchEngineBingActionPointer = actionCollectionPointer->addAction(QStringLiteral("search_engine_bing"));
-    searchEngineYahooActionPointer = actionCollectionPointer->addAction(QStringLiteral("search_engine_yahoo"));
-    searchEngineCustomActionPointer = actionCollectionPointer->addAction(QStringLiteral("search_engine_custom"));
-    QAction *domainSettingsActionPointer = actionCollectionPointer->addAction(QStringLiteral("domain_settings"));
-    cookiesActionPointer = actionCollectionPointer->addAction(QStringLiteral("cookies"));
-    javaScriptActionPointer = actionCollectionPointer->addAction(QStringLiteral("javascript"));
-    localStorageActionPointer = actionCollectionPointer->addAction(QStringLiteral("local_storage"));
-    domStorageActionPointer = actionCollectionPointer->addAction(QStringLiteral("dom_storage"));
-    findCaseSensitiveActionPointer = actionCollectionPointer->addAction(QStringLiteral("find_case_sensitive"));
+    QAction *newTabActionPointer = actionCollectionPointer->addAction(QLatin1String("new_tab"));
+    QAction *newWindowActionPointer = actionCollectionPointer->addAction(QLatin1String("new_window"));
+    QAction *saveArchiveActionPointer = actionCollectionPointer->addAction(QLatin1String("save_archive"));
+    zoomDefaultActionPointer = actionCollectionPointer->addAction(QLatin1String("zoom_default"));
+    QAction *reloadAndBypassCacheActionPointer = actionCollectionPointer->addAction(QLatin1String("reload_and_bypass_cache"));
+    stopActionPointer = actionCollectionPointer->addAction(QLatin1String("stop"));
+    viewSourceActionPointer = actionCollectionPointer->addAction(QLatin1String("view_source"));
+    viewSourceInNewTabActionPointer = actionCollectionPointer->addAction(QLatin1String("view_source_in_new_tab"));
+    developerToolsActionPointer = actionCollectionPointer->addAction(QLatin1String("developer_tools"));
+    javaScriptActionPointer = actionCollectionPointer->addAction(QLatin1String("javascript"));
+    localStorageActionPointer = actionCollectionPointer->addAction(QLatin1String("local_storage"));
+    domStorageActionPointer = actionCollectionPointer->addAction(QLatin1String("dom_storage"));
+    userAgentPrivacyBrowserActionPointer = actionCollectionPointer->addAction(QLatin1String("user_agent_privacy_browser"));
+    userAgentWebEngineDefaultActionPointer = actionCollectionPointer->addAction(QLatin1String("user_agent_webengine_default"));
+    userAgentFirefoxLinuxActionPointer = actionCollectionPointer->addAction(QLatin1String("user_agent_firefox_linux"));
+    userAgentChromiumLinuxActionPointer = actionCollectionPointer->addAction(QLatin1String("user_agent_chromium_linux"));
+    userAgentFirefoxWindowsActionPointer = actionCollectionPointer->addAction(QLatin1String("user_agent_firefox_windows"));
+    userAgentChromeWindowsActionPointer = actionCollectionPointer->addAction(QLatin1String("user_agent_chrome_windows"));
+    userAgentEdgeWindowsActionPointer = actionCollectionPointer->addAction(QLatin1String("user_agent_edge_windows"));
+    userAgentSafariMacosActionPointer = actionCollectionPointer->addAction(QLatin1String("user_agent_safari_macos"));
+    userAgentCustomActionPointer = actionCollectionPointer->addAction(QLatin1String("user_agent_custom"));
+    zoomFactorActionPointer = actionCollectionPointer->addAction(QLatin1String("zoom_factor"));
+    searchEngineMojeekActionPointer = actionCollectionPointer->addAction(QLatin1String("search_engine_mojeek"));
+    searchEngineMonoclesActionPointer = actionCollectionPointer->addAction(QLatin1String("search_engine_monocles"));
+    searchEngineMetagerActionPointer = actionCollectionPointer->addAction(QLatin1String("search_engine_metager"));
+    searchEngineGoogleActionPointer = actionCollectionPointer->addAction(QLatin1String("search_engine_google"));
+    searchEngineBingActionPointer = actionCollectionPointer->addAction(QLatin1String("search_engine_bing"));
+    searchEngineYahooActionPointer = actionCollectionPointer->addAction(QLatin1String("search_engine_yahoo"));
+    searchEngineCustomActionPointer = actionCollectionPointer->addAction(QLatin1String("search_engine_custom"));
+    viewBookmarksToolBarActionPointer = actionCollectionPointer->addAction(QLatin1String("view_bookmarks_toolbar"));
+    QAction *domainSettingsActionPointer = actionCollectionPointer->addAction(QLatin1String("domain_settings"));
+    cookiesActionPointer = actionCollectionPointer->addAction(QLatin1String("cookies"));
+    findCaseSensitiveActionPointer = actionCollectionPointer->addAction(QLatin1String("find_case_sensitive"));
+    hideFindTextActionPointer = actionCollectionPointer->addAction(QLatin1String("hide_find_actions"));
 
     // Create the action groups
     QActionGroup *userAgentActionGroupPointer = new QActionGroup(this);
@@ -124,6 +152,8 @@ BrowserWindow::BrowserWindow(bool firstWindow) : KXmlGuiWindow()
     localStorageActionPointer->setCheckable(true);
     domStorageActionPointer->setCheckable(true);
     findCaseSensitiveActionPointer->setCheckable(true);
+    viewSourceActionPointer->setCheckable(true);
+    developerToolsActionPointer->setCheckable(true);
     userAgentPrivacyBrowserActionPointer->setCheckable(true);
     userAgentWebEngineDefaultActionPointer->setCheckable(true);
     userAgentFirefoxLinuxActionPointer->setCheckable(true);
@@ -140,55 +170,175 @@ BrowserWindow::BrowserWindow(bool firstWindow) : KXmlGuiWindow()
     searchEngineBingActionPointer->setCheckable(true);
     searchEngineYahooActionPointer->setCheckable(true);
     searchEngineCustomActionPointer->setCheckable(true);
+    viewBookmarksToolBarActionPointer->setCheckable(true);
+
+    // Instantiate the user agent helper.
+    UserAgentHelper *userAgentHelperPointer = new UserAgentHelper();
 
     // Set the action text.
-    userAgentPrivacyBrowserActionPointer->setText(UserAgentHelper::PRIVACY_BROWSER_TRANSLATED);
-    userAgentWebEngineDefaultActionPointer->setText(UserAgentHelper::WEB_ENGINE_DEFAULT_TRANSLATED);
-    userAgentFirefoxLinuxActionPointer->setText(UserAgentHelper::FIREFOX_LINUX_TRANSLATED);
-    userAgentChromiumLinuxActionPointer->setText(UserAgentHelper::CHROMIUM_LINUX_TRANSLATED);
-    userAgentFirefoxWindowsActionPointer->setText(UserAgentHelper::FIREFOX_WINDOWS_TRANSLATED);
-    userAgentChromeWindowsActionPointer->setText(UserAgentHelper::CHROME_WINDOWS_TRANSLATED);
-    userAgentEdgeWindowsActionPointer->setText(UserAgentHelper::EDGE_WINDOWS_TRANSLATED);
-    userAgentSafariMacosActionPointer->setText(UserAgentHelper::SAFARI_MACOS_TRANSLATED);
+    newTabActionPointer->setText(i18nc("New tab action", "New Tab"));
+    newWindowActionPointer->setText(i18nc("New window action", "New Window"));
+    saveArchiveActionPointer->setText(i18nc("Save archive action", "Save Archive"));
+    zoomDefaultActionPointer->setText(i18nc("Zoom default action", "Zoom Default"));
+    reloadAndBypassCacheActionPointer->setText(i18nc("Reload and bypass cache action", "Reload and Bypass Cache"));
+    stopActionPointer->setText(i18nc("Stop action", "Stop"));
+    viewSourceActionPointer->setText(i18nc("View source action", "View Source"));
+    viewSourceInNewTabActionPointer->setText(i18nc("View source in new tab action", "View Source in New Tab"));
+    developerToolsActionPointer->setText(i18nc("Developer tools action", "Developer Tools"));
+    javaScriptActionPointer->setText(i18nc("JavaScript action", "JavaScript"));
+    localStorageActionPointer->setText(i18nc("The Local Storage action", "Local Storage"));
+    domStorageActionPointer->setText(i18nc("DOM Storage action", "DOM Storage"));
+    userAgentPrivacyBrowserActionPointer->setText(userAgentHelperPointer->PRIVACY_BROWSER_TRANSLATED);
+    userAgentWebEngineDefaultActionPointer->setText(userAgentHelperPointer->WEB_ENGINE_DEFAULT_TRANSLATED);
+    userAgentFirefoxLinuxActionPointer->setText(userAgentHelperPointer->FIREFOX_LINUX_TRANSLATED);
+    userAgentChromiumLinuxActionPointer->setText(userAgentHelperPointer->CHROMIUM_LINUX_TRANSLATED);
+    userAgentFirefoxWindowsActionPointer->setText(userAgentHelperPointer->FIREFOX_WINDOWS_TRANSLATED);
+    userAgentChromeWindowsActionPointer->setText(userAgentHelperPointer->CHROME_WINDOWS_TRANSLATED);
+    userAgentEdgeWindowsActionPointer->setText(userAgentHelperPointer->EDGE_WINDOWS_TRANSLATED);
+    userAgentSafariMacosActionPointer->setText(userAgentHelperPointer->SAFARI_MACOS_TRANSLATED);
     searchEngineMojeekActionPointer->setText(i18nc("Search engine", "Mojeek"));
     searchEngineMonoclesActionPointer->setText(i18nc("Search engine", "Monocles"));
     searchEngineMetagerActionPointer->setText(i18nc("Search engine", "MetaGer"));
     searchEngineGoogleActionPointer->setText(i18nc("Search engine", "Google"));
     searchEngineBingActionPointer->setText(i18nc("Search engine", "Bing"));
     searchEngineYahooActionPointer->setText(i18nc("Search engine", "Yahoo"));
+    viewBookmarksToolBarActionPointer->setText(i18nc("View bookmarks toolbar", "View Bookmarks Toolbar"));
     domainSettingsActionPointer->setText(i18nc("Domain Settings action", "Domain Settings"));
     cookiesActionPointer->setText(i18nc("The Cookies action, which also displays the number of cookies", "Cookies - %1", 0));
-    javaScriptActionPointer->setText(i18nc("JavaScript action", "JavaScript"));
-    localStorageActionPointer->setText(i18nc("The Local Storage action", "Local Storage"));
-    domStorageActionPointer->setText(i18nc("DOM Storage action", "DOM Storage"));
     findCaseSensitiveActionPointer->setText(i18nc("Find Case Sensitive action", "Find Case Sensitive"));
-
-    // Set the action icons.
-    userAgentPrivacyBrowserActionPointer->setIcon(QIcon(":/icons/privacy-mode"));
-    userAgentWebEngineDefaultActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("user-group-properties")));
-    userAgentFirefoxLinuxActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("firefox-esr")));
-    userAgentChromiumLinuxActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("chromium")));
-    userAgentFirefoxWindowsActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("firefox-esr")));
-    userAgentChromeWindowsActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("chromium")));
-    userAgentEdgeWindowsActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("user-group-properties")));
-    userAgentSafariMacosActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("user-group-properties")));
-    userAgentCustomActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("user-group-properties")));
-    searchEngineMojeekActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("search")));
-    searchEngineMonoclesActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("search")));
-    searchEngineMetagerActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("search")));
-    searchEngineGoogleActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("im-google")));
-    searchEngineBingActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("search")));
-    searchEngineYahooActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("im-yahoo")));
-    searchEngineCustomActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("search")));
-    zoomFactorActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("zoom")));
-    domainSettingsActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("settings-configure")));
-    cookiesActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("preferences-web-browser-cookies")));
-    domStorageActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("code-class")));
-    findCaseSensitiveActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("format-text-lowercase")));
+    hideFindTextActionPointer->setText(i18nc("Hide Find Text action (the text should include the language-specific escape keyboard shortcut).", "Hide Find Text (Esc)"));
+
+    // Set the action icons.  Gnome doesn't contain some of the icons that KDE has.
+    // The toolbar icons don't pick up unless the size is explicit, probably because the toolbar ends up being an intermediate size.
+    newTabActionPointer->setIcon(QIcon::fromTheme(QLatin1String("tab-new")));
+    newWindowActionPointer->setIcon(QIcon::fromTheme(QLatin1String("window-new")));
+    saveArchiveActionPointer->setIcon(QIcon::fromTheme(QLatin1String("document-save")));
+    zoomDefaultActionPointer->setIcon(QIcon::fromTheme(QLatin1String("zoom-fit-best")));
+    reloadAndBypassCacheActionPointer->setIcon(QIcon::fromTheme(QLatin1String("view-refresh")));
+    stopActionPointer->setIcon(QIcon::fromTheme(QLatin1String("process-stop")));
+    viewSourceActionPointer->setIcon(QIcon::fromTheme(QLatin1String("view-choose"), QIcon::fromTheme(QLatin1String("accessories-text-editor"))));
+    viewSourceInNewTabActionPointer->setIcon(QIcon::fromTheme(QLatin1String("view-choose"), QIcon::fromTheme(QLatin1String("accessories-text-editor"))));
+    developerToolsActionPointer->setIcon(QIcon::fromTheme(QLatin1String("add-subtitle"), QIcon::fromTheme("system-run")));
+    domStorageActionPointer->setIcon(QIcon::fromTheme(QLatin1String("code-class"), QIcon(QLatin1String("/usr/share/icons/gnome/32x32/actions/gtk-unindent-ltr.png"))));
+    userAgentPrivacyBrowserActionPointer->setIcon(QIcon(QLatin1String(":/icons/privacy-mode.svg")));
+    userAgentWebEngineDefaultActionPointer->setIcon(QIcon::fromTheme(QLatin1String("qtlogo"), QIcon::fromTheme(QLatin1String("user-group-properties"),
+                                                                                                               QIcon::fromTheme(QLatin1String("contact-new")))));
+    userAgentFirefoxLinuxActionPointer->setIcon(QIcon::fromTheme(QLatin1String("firefox-esr"), QIcon::fromTheme(QLatin1String("user-group-properties"),
+                                                                                                                QIcon::fromTheme(QLatin1String("contact-new")))));
+    userAgentChromiumLinuxActionPointer->setIcon(QIcon::fromTheme(QLatin1String("chromium"), QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new")))));
+    userAgentFirefoxWindowsActionPointer->setIcon(QIcon::fromTheme(QLatin1String("firefox-esr"), QIcon::fromTheme(QLatin1String("user-group-properties"),
+                                                                                                                  QIcon::fromTheme(QLatin1String("contact-new")))));
+    userAgentChromeWindowsActionPointer->setIcon(QIcon::fromTheme(QLatin1String("chromium"), QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new")))));
+    userAgentEdgeWindowsActionPointer->setIcon(QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new"))));
+    userAgentSafariMacosActionPointer->setIcon(QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new"))));
+    userAgentCustomActionPointer->setIcon(QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new"))));
+    searchEngineMojeekActionPointer->setIcon(QIcon::fromTheme(QLatin1String("edit-find")));
+    searchEngineMonoclesActionPointer->setIcon(QIcon::fromTheme(QLatin1String("edit-find")));
+    searchEngineMetagerActionPointer->setIcon(QIcon::fromTheme(QLatin1String("edit-find")));
+    searchEngineGoogleActionPointer->setIcon(QIcon::fromTheme(QLatin1String("im-google"), QIcon::fromTheme(QLatin1String("edit-find"))));
+    searchEngineBingActionPointer->setIcon(QIcon::fromTheme(QLatin1String("edit-find")));
+    searchEngineYahooActionPointer->setIcon(QIcon::fromTheme(QLatin1String("im-yahoo"), QIcon::fromTheme(QLatin1String("edit-find"))));
+    searchEngineCustomActionPointer->setIcon(QIcon::fromTheme(QLatin1String("edit-find")));
+    zoomFactorActionPointer->setIcon(QIcon::fromTheme(QLatin1String("zoom-fit-best")));
+    editBookmarksActionPointer->setIcon(QIcon::fromTheme(QLatin1String("bookmark-edit"), QIcon::fromTheme(QLatin1String("bookmark-new"))));
+    viewBookmarksToolBarActionPointer->setIcon(QIcon::fromTheme(QLatin1String("bookmarks"), QIcon::fromTheme(QLatin1String("bookmark-new"))));
+    domainSettingsActionPointer->setIcon(QIcon::fromTheme(QLatin1String("settings-configure"), QIcon::fromTheme(QLatin1String("preferences-desktop"))));
+    cookiesActionPointer->setIcon(QIcon::fromTheme(QLatin1String("preferences-web-browser-cookies"), QIcon::fromTheme(QLatin1String("appointment-new"))));
+    findCaseSensitiveActionPointer->setIcon(QIcon::fromTheme(QLatin1String("format-text-lowercase"), QIcon::fromTheme(QLatin1String("/usr/share/icons/gnome/32x32/apps/fonts.png"))));
+    hideFindTextActionPointer->setIcon(QIcon::fromTheme(QLatin1String("window-close-symbolic")));
+
+    // Create the key sequences.
+    QKeySequence ctrlTKeySequence = QKeySequence(i18nc("The open new tab key sequence.", "Ctrl+T"));
+    QKeySequence ctrlNKeySequence = QKeySequence(i18nc("The open new window key sequence.", "Ctrl+N"));
+    QKeySequence ctrlAKeySequence = QKeySequence(i18nc("The save archive key sequence.", "Ctrl+A"));
+    QKeySequence ctrl0KeySequence = QKeySequence(i18nc("The zoom default key sequence.", "Ctrl+0"));
+    QKeySequence ctrlF5KeySequence = QKeySequence(i18nc("The reload and bypass cache key sequence.", "Ctrl+F5"));
+    QKeySequence ctrlShiftXKeySequence = QKeySequence(i18nc("The stop key sequence.", "Ctrl+Shift+X"));
+    QKeySequence ctrlUKeySequence = QKeySequence(i18nc("The view source key sequence.", "Ctrl+U"));
+    QKeySequence ctrlShiftUKeySequence = QKeySequence(i18nc("The view source in new tab key sequence.", "Ctrl+Shift+U"));
+    QKeySequence f12KeySequence = QKeySequence(i18nc("The developer tools key sequence.", "F12"));
+    QKeySequence ctrlShiftPKeySequence = QKeySequence(i18nc("The print preview key sequence.", "Ctrl+Shift+P"));
+    QKeySequence ctrlJKeySequence = QKeySequence(i18nc("The JavaScript key sequence.", "Ctrl+J"));
+    QKeySequence ctrlLKeySequence = QKeySequence(i18nc("The local storage key sequence.", "Ctrl+L"));
+    QKeySequence ctrlDKeySequence = QKeySequence(i18nc("The DOM storage key sequence.", "Ctrl+D"));
+    QKeySequence ctrlSKeySequence = QKeySequence(i18nc("The find case sensitive key sequence.", "Ctrl+S"));
+    QKeySequence ctrlAltPKeySequence = QKeySequence(i18nc("The Privacy Browser user agent key sequence.", "Ctrl+Alt+P"));
+    QKeySequence ctrlAltWKeySequence = QKeySequence(i18nc("The WebEngine Default user agent key sequence.", "Ctrl+Alt+W"));
+    QKeySequence ctrlAltFKeySequence = QKeySequence(i18nc("The Firefox on Linux user agent key sequence.", "Ctrl+Alt+F"));
+    QKeySequence ctrlAltCKeySequence = QKeySequence(i18nc("The Chromium on Linux user agent key sequence.", "Ctrl+Alt+C"));
+    QKeySequence ctrlAltShiftFKeySequence = QKeySequence(i18nc("The Firefox on Windows user agent key sequence.", "Ctrl+Alt+Shift+F"));
+    QKeySequence ctrlAltShiftCKeySequence = QKeySequence(i18nc("The Chrome on Windows user agent key sequence.", "Ctrl+Alt+Shift+C"));
+    QKeySequence ctrlAltEKeySequence = QKeySequence(i18nc("The Edge on Windows user agent key sequence.", "Ctrl+Alt+E"));
+    QKeySequence ctrlAltSKeySequence = QKeySequence(i18nc("The Safari on macOS user agent key sequence.", "Ctrl+Alt+S"));
+    QKeySequence altShiftCKeySequence = QKeySequence(i18nc("The custom user agent key sequence.", "Alt+Shift+C"));
+    QKeySequence ctrlAltZKeySequence = QKeySequence(i18nc("The zoom factor key sequence.", "Ctrl+Alt+Z"));
+    QKeySequence ctrlShiftMKeySequence = QKeySequence(i18nc("The Mojeek search engine key sequence.", "Ctrl+Shift+M"));
+    QKeySequence ctrlShiftOKeySequence = QKeySequence(i18nc("The Monocles search engine key sequence.", "Ctrl+Shift+O"));
+    QKeySequence ctrlShiftEKeySequence = QKeySequence(i18nc("The MetaGer search engine key sequence.", "Ctrl+Shift+E"));
+    QKeySequence ctrlShiftGKeySequence = QKeySequence(i18nc("The Google search engine key sequence.", "Ctrl+Shift+G"));
+    QKeySequence ctrlShiftBKeySequence = QKeySequence(i18nc("The Bing search engine key sequence.", "Ctrl+Shift+B"));
+    QKeySequence ctrlShiftYKeySequence = QKeySequence(i18nc("The Yahoo search engine key sequence.", "Ctrl+Shift+Y"));
+    QKeySequence ctrlShiftCKeySequence = QKeySequence(i18nc("The custom search engine key sequence.", "Ctrl+Shift+C"));
+    QKeySequence ctrlAltShiftBKeySequence = QKeySequence(i18nc("The edit bookmarks key sequence.", "Ctrl+Alt+Shift+B"));
+    QKeySequence ctrlAltBKeySequence = QKeySequence(i18nc("The view bookmarks toolbar key sequence.", "Ctrl+Alt+B"));
+    QKeySequence ctrlShiftDKeySequence = QKeySequence(i18nc("The domain settings key sequence.", "Ctrl+Shift+D"));
+    QKeySequence ctrlSemicolonKeySequence = QKeySequence(i18nc("The cookies dialog key sequence.", "Ctrl+;"));
+
+    // Set the action key sequences.
+    actionCollectionPointer->setDefaultShortcut(newTabActionPointer, ctrlTKeySequence);
+    actionCollectionPointer->setDefaultShortcut(newWindowActionPointer, ctrlNKeySequence);
+    actionCollectionPointer->setDefaultShortcut(saveArchiveActionPointer, ctrlAKeySequence);
+    actionCollectionPointer->setDefaultShortcut(zoomDefaultActionPointer, ctrl0KeySequence);
+    actionCollectionPointer->setDefaultShortcut(reloadAndBypassCacheActionPointer, ctrlF5KeySequence);
+    actionCollectionPointer->setDefaultShortcut(stopActionPointer, ctrlShiftXKeySequence);
+    actionCollectionPointer->setDefaultShortcut(viewSourceActionPointer, ctrlUKeySequence);
+    actionCollectionPointer->setDefaultShortcut(viewSourceInNewTabActionPointer, ctrlShiftUKeySequence);
+    actionCollectionPointer->setDefaultShortcut(developerToolsActionPointer, f12KeySequence);
+    actionCollectionPointer->setDefaultShortcut(printPreviewActionPointer, ctrlShiftPKeySequence);
+    actionCollectionPointer->setDefaultShortcut(javaScriptActionPointer, ctrlJKeySequence);
+    actionCollectionPointer->setDefaultShortcut(localStorageActionPointer, ctrlLKeySequence);
+    actionCollectionPointer->setDefaultShortcut(domStorageActionPointer, ctrlDKeySequence);
+    actionCollectionPointer->setDefaultShortcut(findCaseSensitiveActionPointer, ctrlSKeySequence);
+    actionCollectionPointer->setDefaultShortcut(userAgentPrivacyBrowserActionPointer, ctrlAltPKeySequence);
+    actionCollectionPointer->setDefaultShortcut(userAgentWebEngineDefaultActionPointer, ctrlAltWKeySequence);
+    actionCollectionPointer->setDefaultShortcut(userAgentFirefoxLinuxActionPointer, ctrlAltFKeySequence);
+    actionCollectionPointer->setDefaultShortcut(userAgentChromiumLinuxActionPointer, ctrlAltCKeySequence);
+    actionCollectionPointer->setDefaultShortcut(userAgentFirefoxWindowsActionPointer, ctrlAltShiftFKeySequence);
+    actionCollectionPointer->setDefaultShortcut(userAgentChromeWindowsActionPointer, ctrlAltShiftCKeySequence);
+    actionCollectionPointer->setDefaultShortcut(userAgentEdgeWindowsActionPointer, ctrlAltEKeySequence);
+    actionCollectionPointer->setDefaultShortcut(userAgentSafariMacosActionPointer, ctrlAltSKeySequence);
+    actionCollectionPointer->setDefaultShortcut(userAgentCustomActionPointer, altShiftCKeySequence);
+    actionCollectionPointer->setDefaultShortcut(zoomFactorActionPointer, ctrlAltZKeySequence);
+    actionCollectionPointer->setDefaultShortcut(searchEngineMojeekActionPointer, ctrlShiftMKeySequence);
+    actionCollectionPointer->setDefaultShortcut(searchEngineMonoclesActionPointer, ctrlShiftOKeySequence);
+    actionCollectionPointer->setDefaultShortcut(searchEngineMetagerActionPointer, ctrlShiftEKeySequence);
+    actionCollectionPointer->setDefaultShortcut(searchEngineGoogleActionPointer, ctrlShiftGKeySequence);
+    actionCollectionPointer->setDefaultShortcut(searchEngineBingActionPointer, ctrlShiftBKeySequence);
+    actionCollectionPointer->setDefaultShortcut(searchEngineYahooActionPointer, ctrlShiftYKeySequence);
+    actionCollectionPointer->setDefaultShortcut(searchEngineCustomActionPointer, ctrlShiftCKeySequence);
+    actionCollectionPointer->setDefaultShortcut(editBookmarksActionPointer, ctrlAltShiftBKeySequence);
+    actionCollectionPointer->setDefaultShortcut(viewBookmarksToolBarActionPointer, ctrlAltBKeySequence);
+    actionCollectionPointer->setDefaultShortcut(domainSettingsActionPointer, ctrlShiftDKeySequence);
+    actionCollectionPointer->setDefaultShortcut(cookiesActionPointer, ctrlSemicolonKeySequence);
+
+    // Execute the actions.
+    connect(newTabActionPointer, SIGNAL(triggered()), tabWidgetPointer, SLOT(addTab()));
+    connect(newWindowActionPointer, SIGNAL(triggered()), this, SLOT(newWindow()));
+    connect(saveArchiveActionPointer, SIGNAL(triggered()), tabWidgetPointer, SLOT(saveArchive()));
+    connect(zoomDefaultActionPointer, SIGNAL(triggered()), this, SLOT(zoomDefault()));
+    connect(reloadAndBypassCacheActionPointer, SIGNAL(triggered()), this, SLOT(reloadAndBypassCache()));
+    connect(stopActionPointer, SIGNAL(triggered()), tabWidgetPointer, SLOT(stop()));
+    connect(viewSourceActionPointer, SIGNAL(triggered()), this, SLOT(toggleViewSource()));
+    connect(viewSourceInNewTabActionPointer, SIGNAL(triggered()), this, SLOT(toggleViewSourceInNewTab()));
+    connect(developerToolsActionPointer, SIGNAL(triggered()), this, SLOT(toggleDeveloperTools()));
+    connect(zoomFactorActionPointer, SIGNAL(triggered()), this, SLOT(getZoomFactorFromUser()));
+    connect(viewBookmarksToolBarActionPointer, SIGNAL(triggered()), this, SLOT(toggleViewBookmarksToolBar()));
+    connect(cookiesActionPointer, SIGNAL(triggered()), this, SLOT(showCookiesDialog()));
+    connect(domainSettingsActionPointer, SIGNAL(triggered()), this, SLOT(showDomainSettingsDialog()));
 
     // Update the on-the-fly menus.
     connect(tabWidgetPointer, SIGNAL(updateUserAgentActions(QString, bool)), this, SLOT(updateUserAgentActions(QString, bool)));
-    connect(tabWidgetPointer, SIGNAL(updateZoomFactorAction(double)), this, SLOT(updateZoomFactorAction(double)));
+    connect(tabWidgetPointer, SIGNAL(updateZoomActions(double)), this, SLOT(updateZoomActions(double)));
     connect(tabWidgetPointer, SIGNAL(updateSearchEngineActions(QString, bool)), this, SLOT(updateSearchEngineActions(QString, bool)));
 
     // Apply the on-the-fly settings when selected.
@@ -198,17 +348,16 @@ BrowserWindow::BrowserWindow(bool firstWindow) : KXmlGuiWindow()
     // Process cookie changes.
     connect(tabWidgetPointer, SIGNAL(updateCookiesAction(int)), this, SLOT(updateCookiesAction(int)));
 
-    // Display dialogs.
-    connect(zoomFactorActionPointer, SIGNAL(triggered()), this, SLOT(getZoomFactorFromUser()));
-    connect(cookiesActionPointer, SIGNAL(triggered()), this, SLOT(showCookiesDialog()));
-    connect(domainSettingsActionPointer, SIGNAL(triggered()), this, SLOT(showDomainSettingsDialog()));
+    // Store the default zoom factor.
+    connect(tabWidgetPointer, SIGNAL(updateDefaultZoomFactor(double)), this, SLOT(updateDefaultZoomFactor(double)));
 
     // Connect the URL toolbar actions.
     connect(javaScriptActionPointer, SIGNAL(triggered()), this, SLOT(toggleJavaScript()));
     connect(localStorageActionPointer, SIGNAL(triggered()), this, SLOT(toggleLocalStorage()));
     connect(domStorageActionPointer, SIGNAL(triggered()), this, SLOT(toggleDomStorage()));
 
-    // Update the URL toolbar actions.
+    // Update the actions from the tab widget.
+    connect(tabWidgetPointer, SIGNAL(updateDeveloperToolsAction(bool)), developerToolsActionPointer, SLOT(setChecked(bool)));
     connect(tabWidgetPointer, SIGNAL(updateBackAction(bool)), backActionPointer, SLOT(setEnabled(bool)));
     connect(tabWidgetPointer, SIGNAL(updateForwardAction(bool)), forwardActionPointer, SLOT(setEnabled(bool)));
     connect(tabWidgetPointer, SIGNAL(updateJavaScriptAction(bool)), this, SLOT(updateJavaScriptAction(bool)));
@@ -217,9 +366,12 @@ BrowserWindow::BrowserWindow(bool firstWindow) : KXmlGuiWindow()
 
     // Connect the find text actions.
     connect(findCaseSensitiveActionPointer, SIGNAL(triggered()), this, SLOT(toggleFindCaseSensitive()));
+    connect(hideFindTextActionPointer, SIGNAL(triggered()), this, SLOT(hideFindTextActions()));
+
 
-    // Setup the GUI based on the browser_window_ui.rc file.
-    setupGUI(StandardWindowOption::Default, ("browser_window_ui.rc"));
+
+    // Setup the GUI based on the browserwindowui.rc file.
+    setupGUI(StandardWindowOption::Default, ("browserwindowui.rc"));
 
     // Get lists of the actions' associated widgets.
     QList<QWidget*> userAgentAssociatedWidgetsPointerList = userAgentPrivacyBrowserActionPointer->associatedWidgets();
@@ -238,11 +390,15 @@ BrowserWindow::BrowserWindow(bool firstWindow) : KXmlGuiWindow()
     searchEngineMenuActionPointer = searchEngineMenuPointer->menuAction();
 
     // Get handles for the toolbars.
-    navigationToolBarPointer = toolBar(QStringLiteral("navigation_toolbar"));
-    urlToolBarPointer = toolBar(QStringLiteral("url_toolbar"));
+    navigationToolBarPointer = toolBar(QLatin1String("navigation_toolbar"));
+    urlToolBarPointer = toolBar(QLatin1String("url_toolbar"));
+    bookmarksToolBarPointer = toolBar(QLatin1String("bookmarks_toolbar"));
+
+    // Populate the view bookmarks toolbar checkbox.
+    connect(bookmarksToolBarPointer, SIGNAL(visibilityChanged(bool)), this, SLOT(updateViewBookmarksToolBarCheckbox(bool)));
 
     // Create the line edits.
-    urlLineEditPointer = new KLineEdit();
+    urlLineEditPointer = new UrlLineEdit();
     findTextLineEditPointer = new KLineEdit();
 
     // Get the line edit size policies.
@@ -262,29 +418,38 @@ BrowserWindow::BrowserWindow(bool firstWindow) : KXmlGuiWindow()
     findTextLineEditPointer->setMinimumWidth(200);
     findTextLineEditPointer->setMaximumWidth(350);
 
-    // Set the placehold text.
+    // Set the place holder text.
     urlLineEditPointer->setPlaceholderText(i18nc("The URL line edit placeholder text", "URL or Search Terms"));
     findTextLineEditPointer->setPlaceholderText(i18nc("The find line edit placeholder text", "Find Text"));
 
     // Show the clear button on the find line edit.
     findTextLineEditPointer->setClearButtonEnabled(true);
 
-    // Add an edit or add domain settings action to the URL line edit.
-    QAction *addOrEditDomainSettingsActionPointer = urlLineEditPointer->addAction(QIcon::fromTheme("settings-configure"), QLineEdit::TrailingPosition);
+    // Add the actions to the URL line edit.
+    bookmarkedActionPointer = urlLineEditPointer->addAction(QIcon::fromTheme("non-starred-symbolic"), QLineEdit::LeadingPosition);
+    QAction *addOrEditDomainSettingsActionPointer = urlLineEditPointer->addAction(QIcon::fromTheme("settings-configure", QIcon::fromTheme(QLatin1String("preferences-desktop"))),
+                                                                                  QLineEdit::TrailingPosition);
 
-    // Add or edit the current domain settings.
+    // Set the bookmarked action pointer to be checkable.
+    bookmarkedActionPointer->setCheckable(true);
+
+    // Connect the URL line edit actions.
+    connect(bookmarkedActionPointer, SIGNAL(triggered()), this, SLOT(toggleBookmark()));
     connect(addOrEditDomainSettingsActionPointer, SIGNAL(triggered()), this, SLOT(addOrEditDomainSettings()));
 
     // Create a find text label pointer.
     findTextLabelPointer = new QLabel();
 
     // Set the default label text.
-    findTextLabelPointer->setText(QStringLiteral("  ") + i18nc("Default find results.", "0/0") + QStringLiteral("  "));
+    findTextLabelPointer->setText(QLatin1String("  ") + i18nc("Default find results.", "0/0") + QLatin1String("  "));
 
     // Insert the widgets into the toolbars.
     urlToolBarPointer->insertWidget(javaScriptActionPointer, urlLineEditPointer);
-    urlToolBarPointer->insertWidget(findNextActionPointer, findTextLineEditPointer);
-    urlToolBarPointer->insertWidget(findNextActionPointer, findTextLabelPointer);
+    findTextLineEditActionPointer = urlToolBarPointer->insertWidget(findNextActionPointer, findTextLineEditPointer);
+    findTextLabelActionPointer = urlToolBarPointer->insertWidget(findNextActionPointer, findTextLabelPointer);
+
+    // Initially hide the find text actions.
+    hideFindTextActions();
 
     // Load a new URL from the URL line edit.
     connect(urlLineEditPointer, SIGNAL(returnKeyPressed(const QString)), this, SLOT(loadUrlFromLineEdit(const QString)));
@@ -310,18 +475,41 @@ BrowserWindow::BrowserWindow(bool firstWindow) : KXmlGuiWindow()
     // Get a handle for the status bar.
     QStatusBar *statusBarPointer = statusBar();
 
-    // Create a progress bar.
+    // Create the status bar widgets.
     progressBarPointer = new QProgressBar();
+    zoomMinusButtonPointer = new QPushButton();
+    currentZoomButtonPointer = new QPushButton();
+    zoomPlusButtonPointer = new QPushButton();
+
+    // Set the button icons.
+    zoomMinusButtonPointer->setIcon(QIcon::fromTheme(QStringLiteral("list-remove-symbolic")));
+    zoomPlusButtonPointer->setIcon(QIcon::fromTheme(QStringLiteral("list-add-symbolic")));
+
+    // Set the button icons to be flat (no borders).
+    zoomMinusButtonPointer->setFlat(true);
+    currentZoomButtonPointer->setFlat(true);
+    zoomPlusButtonPointer->setFlat(true);
+
+    // Handle clicks on the zoom buttons.
+    connect(zoomMinusButtonPointer, SIGNAL(clicked()), this, SLOT(decrementZoom()));
+    connect(currentZoomButtonPointer, SIGNAL(clicked()), this, SLOT(getZoomFactorFromUser()));
+    connect(zoomPlusButtonPointer, SIGNAL(clicked()), this, SLOT(incrementZoom()));
+
+    // Remove the padding around the current zoom button text.
+    currentZoomButtonPointer->setStyleSheet("padding: 0px;");
 
-    // Add the progress bar to to the status bar.
+    // Add the widgets to the far right of the status bar.
     statusBarPointer->addPermanentWidget(progressBarPointer);
+    statusBarPointer->addPermanentWidget(zoomMinusButtonPointer);
+    statusBarPointer->addPermanentWidget(currentZoomButtonPointer);
+    statusBarPointer->addPermanentWidget(zoomPlusButtonPointer);
 
     // Update the status bar with the URL when a link is hovered.
     connect(tabWidgetPointer, SIGNAL(linkHovered(QString)), statusBarPointer, SLOT(showMessage(QString)));
 
     // Update the progress bar.
     connect(tabWidgetPointer, SIGNAL(showProgressBar(const int)), this, SLOT(showProgressBar(const int)));
-    connect(tabWidgetPointer, SIGNAL(hideProgressBar()), progressBarPointer, SLOT(hide()));
+    connect(tabWidgetPointer, SIGNAL(hideProgressBar()), this, SLOT(hideProgressBar()));
 
     // Update the URL line edit focus.
     connect(tabWidgetPointer, SIGNAL(clearUrlLineEditFocus()), this, SLOT(clearUrlLineEditFocus()));
@@ -342,54 +530,300 @@ BrowserWindow::BrowserWindow(bool firstWindow) : KXmlGuiWindow()
     connect(tabWidgetPointer, SIGNAL(fullScreenRequested(bool)), this, SLOT(fullScreenRequested(bool)));
 
     // Create keyboard shortcuts.
-    QShortcut *ctrlTShortcutPointer = new QShortcut(QKeySequence(i18nc("The open new tab shortcut.", "Ctrl+t")), this);
     QShortcut *f11ShortcutPointer = new QShortcut(QKeySequence(i18nc("The toggle full screen shortcut.", "F11")), this);
     QShortcut *escapeShortcutPointer = new QShortcut(QKeySequence::Cancel, this);
 
-    // Connect the keyboard shortcuts to the actions.
-    connect(ctrlTShortcutPointer, SIGNAL(activated()), tabWidgetPointer, SLOT(addTab()));
+    // Connect the keyboard shortcuts.
     connect(f11ShortcutPointer, SIGNAL(activated()), fullScreenActionPointer, SLOT(trigger()));
     connect(escapeShortcutPointer, SIGNAL(activated()), this, SLOT(escape()));
 
-    // Load the initial website if this is the first window.
-    if (firstWindow)
+    // Get a handle for the Bookmarks menu.
+    bookmarksMenuPointer = qobject_cast<QMenu*>(guiFactory()->container("bookmarks", this));
+
+    // Add a separator to the bookmarks menu.
+    bookmarksMenuPointer->addSeparator();
+
+    // Initialize the bookmark action lists.
+    bookmarkFolderFinalActionList = QList<QPair<QMenu *, QAction *> *>();
+    bookmarksMenuActionList = QList<QPair<QMenu *, QAction *> *>();
+    bookmarksMenuSubmenuList = QList<QPair<QMenu *, QMenu *> *>();
+    bookmarksToolBarActionList = QList<QAction*>();
+    bookmarksToolBarSubfolderActionList = QList<QPair<QMenu *, QAction *> *>();
+
+    // Set the bookmarks toolbar context menu policy.
+    bookmarksToolBarPointer->setContextMenuPolicy(Qt::CustomContextMenu);
+
+    // Show the custom bookmark context menu when requested.
+    connect(bookmarksToolBarPointer, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showBookmarkContextMenu(const QPoint&)));
+
+    // Populate the bookmarks in this window.
+    populateBookmarksInThisWindow();
+
+    // Populate the UI.
+    // This must be done here, because otherwise, if a URL is loaded, like a local file, that does not trigger a call to TabWidget::applyDomainSettings, the UI will not be fully populated.
+    updateJavaScriptAction(Settings::javaScriptEnabled());
+    updateLocalStorageAction(Settings::localStorageEnabled());
+    updateDomStorageAction(Settings::domStorageEnabled());
+    updateUserAgentActions(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()), true);
+    updateZoomActions(Settings::zoomFactor());
+
+    // Populate the first tab.
+    if (firstWindow)  // This is the first window.
+    {
+        // Load the initial website.
         tabWidgetPointer->loadInitialWebsite();
+    }
+    else if (initialUrlStringPointer)  // An initial URL was specified.
+    {
+        // Load the initial URL.
+        tabWidgetPointer->loadUrlFromLineEdit(*initialUrlStringPointer);
+    }
+}
+
+// If actions are part of a context menu they do not need to be added to the list as they will be deleted automatically when the context menu closes.
+void BrowserWindow::addBookmarkFolderFinalActions(QMenu *menuPointer, const double folderId, const bool addToList)
+{
+    // Get the database ID.
+    int folderDatabaseId = BookmarksDatabase::getFolderDatabaseId(folderId);
+
+    // Add a separator.
+    menuPointer->addSeparator();
+
+    // Add the add bookmark action to the menu.
+    QAction *addBookmarkActionPointer = menuPointer->addAction(QIcon::fromTheme(QLatin1String("bookmark-new")), i18nc("The add bookmark action", "Add Bookmark"), [=]
+        {
+            // Instantiate an add bookmark dialog.
+            AddBookmarkDialog *addBookmarkDialogPointer = new AddBookmarkDialog(this, tabWidgetPointer->getCurrentTabTitle(), tabWidgetPointer->getCurrentTabUrl(),
+                                                                                tabWidgetPointer->getCurrentTabFavoritIcon(), folderId);
+
+            // Update the displayed bookmarks when a new one is added.
+            connect(addBookmarkDialogPointer, SIGNAL(bookmarkAdded()), this, SLOT(populateBookmarksInAllWindows()));
+
+            // Show the dialog.
+            addBookmarkDialogPointer->show();
+        }
+    );
+
+    // Add the add folder action to the menu.
+    QAction *addFolderActionPointer = menuPointer->addAction(QIcon::fromTheme(QLatin1String("folder-add")), i18nc("The add folder action", "Add Folder"), [=]
+        {
+            // Instantiate an add folder dialog.
+            AddFolderDialog *addFolderDialogPointer = new AddFolderDialog(this, tabWidgetPointer->getCurrentTabFavoritIcon(), folderId);
+
+            // Update the displayed bookmarks when a folder is added.
+            connect(addFolderDialogPointer, SIGNAL(folderAdded()), this, SLOT(populateBookmarksInAllWindows()));
+
+            // Show the dialog.
+            addFolderDialogPointer->show();
+        }
+    );
+
+    // Add a separator.
+    menuPointer->addSeparator();
+
+    // Add the open folder in new tabs action to the menu.
+    QAction *openFolderInNewTabsActionPointer = menuPointer->addAction(QIcon::fromTheme(QLatin1String("tab-new")), i18nc("The open folder in new tabs action", "Open Folder in New Tabs"), [=]
+        {
+            // Get all the folder URLs.
+            QList<QString> *folderUrlsListPointer = BookmarksDatabase::getAllFolderUrls(folderId);
+
+            // Open the URLs in new tabs.  `true` removes the URL line edit focus, `true` opens the new tabs in an adjacent tab.  `false` does not load a background tab.
+            for (QString url : *folderUrlsListPointer)
+                tabWidgetPointer->addTab(true, true, false, url);
+        }
+    );
+
+    // Add the open folder in background tabs action to the menu.
+    QAction *openFolderInBackgroundTabsActionPointer = menuPointer->addAction(QIcon::fromTheme(QLatin1String("tab-new")),
+                                                                              i18nc("The open folder in background tabs action", "Open Folder in Background Tabs"), [=]
+        {
+            // Get all the folder URLs.
+            QList<QString> *folderUrlsListPointer = BookmarksDatabase::getAllFolderUrls(folderId);
+
+            // Open the URLs in new tabs.  `true` removes the URL line edit focus, `true` opens the new tabs in an adjacent tab.  `true` loads a background tab.
+            for (QString url : *folderUrlsListPointer)
+                tabWidgetPointer->addTab(true, true, true, url);
+        }
+    );
+
+    // Add the open folder in new window action to the menu.
+    QAction *openFolderInNewWindowActionPointer = menuPointer->addAction(QIcon::fromTheme(QLatin1String("window-new")), i18nc("The open folder in new window action", "Open Folder in New Window"), [=]
+        {
+            // Get all the folder URLs.
+            QList<QString> *folderUrlsListPointer = BookmarksDatabase::getAllFolderUrls(folderId);
+
+            // Create a new browser window.
+            BrowserWindow *browserWindowPointer = new BrowserWindow(false, &folderUrlsListPointer->first());
+
+            // Get a count of the folder URLs.
+            const int folderUrls = folderUrlsListPointer->count();
+
+            // Load all the other URLs.  `true` removes the URL line edit focus, `false` does not load the new tabs in adjacent tabs.  `true` loads a background tab.
+            for (int i = 1; i < folderUrls; ++i)
+                browserWindowPointer->tabWidgetPointer->addTab(true, false, true, folderUrlsListPointer->value(i));
+
+            // Show the new browser window.
+            browserWindowPointer->show();
+        }
+    );
+
+    // Add a separator.
+    menuPointer->addSeparator();
+
+    // Add the edit folder action to the menu if this is not the root bookmark menu.
+    if (folderId != 0)
+    {
+        QAction *editFolderActionPointer = menuPointer->addAction(QIcon::fromTheme(QLatin1String("document-edit")), i18nc("The edit folder action", "Edit Folder"), [=]
+            {
+                // Get the current tab favorite icon.
+                QIcon currentTabFavoriteIcon = tabWidgetPointer->getCurrentTabFavoritIcon();
+
+                // Instantiate an edit folder dialog.
+                QDialog *editFolderDialogPointer = new EditFolderDialog(this, folderDatabaseId, currentTabFavoriteIcon);
+
+                // Show the dialog.
+                editFolderDialogPointer->show();
+
+                // Process bookmark events.
+                connect(editFolderDialogPointer, SIGNAL(folderSaved()), this, SLOT(populateBookmarksInAllWindows()));
+            }
+        );
+
+        // Add the action to the beginning of the bookmark folder final action list if requsted.
+        if (addToList)
+            bookmarkFolderFinalActionList.prepend(new QPair<QMenu *, QAction *>(menuPointer, editFolderActionPointer));
+    }
+
+    // Add the delete folder action to the menu.
+    QAction *deleteFolderActionPointer = menuPointer->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18nc("Delete folder context menu entry", "Delete Folder"), [=]
+        {
+            // Create an items to delete list.
+            QList<int>* itemsToDeleteListPointer = new QList<int>;
+
+            // Add the folder to the list of items to delete if it is not the root folder.
+            if (folderId != 0)
+                itemsToDeleteListPointer->append(folderDatabaseId);
+
+            // Add the folder contents to the list of items to delete.
+            itemsToDeleteListPointer->append(*BookmarksDatabase::getFolderContentsDatabaseIdsRecursively(folderId));
+
+            // Instantiate a delete dialog message box.
+            QMessageBox deleteDialogMessageBox;
+
+            // Set the icon.
+            deleteDialogMessageBox.setIcon(QMessageBox::Warning);
+
+            // Set the window title.
+            deleteDialogMessageBox.setWindowTitle(i18nc("Delete bookmarks dialog title", "Delete Bookmarks"));
+
+            // Set the text.
+            deleteDialogMessageBox.setText(i18ncp("Delete bookmarks dialog main message", "Delete %1 bookmark item?", "Delete %1 bookmark items?", itemsToDeleteListPointer->count()));
+
+            // Set the informative text.
+            deleteDialogMessageBox.setInformativeText(i18nc("Delete bookmarks dialog secondary message", "This cannot be undone."));
+
+            // Set the standard buttons.
+            deleteDialogMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+
+            // Set the default button.
+            deleteDialogMessageBox.setDefaultButton(QMessageBox::No);
+
+            // Display the dialog and capture the return value.
+            int returnValue = deleteDialogMessageBox.exec();
+
+            // Delete the domain if instructed.
+            if (returnValue == QMessageBox::Yes)
+            {
+                // Get the parent folder ID.
+                double parentFolderId = BookmarksDatabase::getParentFolderId(folderDatabaseId);
+
+                // Delete the folder and its contents.
+                for (const int databaseId : *itemsToDeleteListPointer)
+                    BookmarksDatabase::deleteBookmark(databaseId);
+
+                // Update the display order of the bookmarks in the parent folder.
+                BookmarksDatabase::updateFolderContentsDisplayOrder(parentFolderId);
+
+                // Repopulate the bookmarks.
+                populateBookmarksInAllWindows();
+            }
+        }
+    );
+
+    // Add the key sequences if this is the root bookmarks menu.
+    if (folderId == 0)
+    {
+        // Create the key sequences.
+        QKeySequence ctrlBKeySequence = QKeySequence(i18nc("The add bookmark key sequence.", "Ctrl+B"));
+        QKeySequence metaFKeySequence = QKeySequence(i18nc("The add folder key sequence.", "Meta+F"));
+
+        // Set the action key sequences.
+        actionCollectionPointer->setDefaultShortcut(addBookmarkActionPointer, ctrlBKeySequence);
+        actionCollectionPointer->setDefaultShortcut(addFolderActionPointer, metaFKeySequence);
+    }
+
+    // Add the actions to the beginning of the bookmark folder final action list if requested.
+    if (addToList) {
+        bookmarkFolderFinalActionList.prepend(new QPair<QMenu *, QAction *>(menuPointer, addBookmarkActionPointer));
+        bookmarkFolderFinalActionList.prepend(new QPair<QMenu *, QAction *>(menuPointer, addFolderActionPointer));
+        bookmarkFolderFinalActionList.prepend(new QPair<QMenu *, QAction *>(menuPointer, openFolderInNewTabsActionPointer));
+        bookmarkFolderFinalActionList.prepend(new QPair<QMenu *, QAction *>(menuPointer, openFolderInBackgroundTabsActionPointer));
+        bookmarkFolderFinalActionList.prepend(new QPair<QMenu *, QAction *>(menuPointer, openFolderInNewWindowActionPointer));
+        bookmarkFolderFinalActionList.prepend(new QPair<QMenu *, QAction *>(menuPointer, deleteFolderActionPointer));
+    }
 }
 
-void BrowserWindow::addOrEditDomainSettings() const
+void BrowserWindow::addOrEditDomainSettings()
 {
     // Remove the focus from the URL line edit.
     urlLineEditPointer->clearFocus();
 
-    // Create the domain settings dialog pointer.
-    DomainSettingsDialog *domainSettingsDialogPointer;
-
     // Get the current domain settings name.
     QString &currentDomainSettingsName = tabWidgetPointer->getDomainSettingsName();
 
     // Run the commands according to the current domain settings status.
     if (currentDomainSettingsName == QStringLiteral(""))  // Domain settings are not currently applied.
     {
-        // Instruct the domain settings dialog to add a new domain.
-        domainSettingsDialogPointer = new DomainSettingsDialog(DomainSettingsDialog::ADD_DOMAIN, currentUrl.host());
-    }
-    else  // Domain settings are currently applied.
-    {
-        // Instruct the domain settings dialog to edit the current domain.
-        domainSettingsDialogPointer = new DomainSettingsDialog(DomainSettingsDialog::EDIT_DOMAIN, currentDomainSettingsName);
-    }
+        // Get the current settings status.
+        int javaScriptInt = calculateSettingsInt(javaScriptEnabled, Settings::javaScriptEnabled());
+        int localStorageInt = calculateSettingsInt(localStorageActionPointer->isChecked(), Settings::localStorageEnabled());
+        int domStorageInt = calculateSettingsInt(domStorageActionPointer->isChecked(), Settings::domStorageEnabled());
 
-    // Set the dialog window title.
-    domainSettingsDialogPointer->setWindowTitle(i18nc("The domain settings dialog title", "Domain Settings"));
+        // Get the current user agent string.
+        QString currentUserAgentString = tabWidgetPointer->getCurrentUserAgent();
 
-    // Set the modality.
-    domainSettingsDialogPointer->setWindowModality(Qt::WindowModality::WindowModal);;
+        // Get the current user agent database string.
+        QString currentUserAgentDatabaseString = UserAgentHelper::getDatabaseUserAgentNameFromUserAgent(currentUserAgentString);
 
-    // Show the dialog.
-    domainSettingsDialogPointer->show();
+        // Initialize the user agent database string.
+        QString userAgentDatabaseString = UserAgentHelper::SYSTEM_DEFAULT_DATABASE;
+
+        // Replace the user agent database string if the current user agent is not the default.
+        if (currentUserAgentDatabaseString != Settings::userAgent())
+            userAgentDatabaseString = currentUserAgentDatabaseString;
+
+        // Initialize the zoom factor variables.
+        int zoomFactorInt = DomainsDatabase::SYSTEM_DEFAULT;
+
+        // Use a custom zoom factor if currently applied.  Doubles cannot be reliably compared using `==`, so a mathematical workaround is used.
+        if (abs(currentZoomFactorDouble - defaultZoomFactorDouble ) > 0.01)
+            zoomFactorInt = DomainsDatabase::CUSTOM;
+
+        // Add the domain.
+        DomainsDatabase::addDomain(currentUrl.host(), javaScriptInt, localStorageInt, domStorageInt, userAgentDatabaseString, zoomFactorInt, currentZoomFactorDouble);
+
+        // Apply the domain settings.
+        tabWidgetPointer->applyDomainSettingsAndReload();
+    }
+
+    // Create the domain settings dialog pointer.
+    DomainSettingsDialog *domainSettingsDialogPointer = new DomainSettingsDialog(this, DomainSettingsDialog::EDIT_DOMAIN, currentDomainSettingsName);
 
     // Reload the tabs when domain settings are updated.
     connect(domainSettingsDialogPointer, SIGNAL(domainSettingsUpdated()), tabWidgetPointer, SLOT(applyDomainSettingsAndReload()));
+
+    // Show the dialog.
+    domainSettingsDialogPointer->show();
 }
 
 void BrowserWindow::back() const
@@ -401,34 +835,72 @@ void BrowserWindow::back() const
     tabWidgetPointer->back();
 }
 
+int BrowserWindow::calculateSettingsInt(const bool settingCurrentlyEnabled, const bool settingEnabledByDefault) const
+{
+    // Return the int that matches the current state.
+    if (settingCurrentlyEnabled == settingEnabledByDefault)  // The current system default is used.
+        return DomainsDatabase::SYSTEM_DEFAULT;
+    else if (settingCurrentlyEnabled)  // The setting is enabled, which is different from the system default.
+        return DomainsDatabase::ENABLED;
+    else  // The setting is disabled, which is different from the system default.
+        return DomainsDatabase::DISABLED;
+}
+
 void BrowserWindow::clearUrlLineEditFocus() const
 {
     // Remove the focus from the URL line edit.
     urlLineEditPointer->clearFocus();
 }
 
+void BrowserWindow::decrementZoom()
+{
+    // Update the current zoom factor.
+    currentZoomFactorDouble = currentZoomFactorDouble - 0.25;
+
+    // Check to make sure the zoom factor is in the valid range (0.25 to 5.00).
+    if (currentZoomFactorDouble < 0.25)
+        currentZoomFactorDouble = 0.25;
+
+    // Set the new zoom factor.
+    tabWidgetPointer->applyOnTheFlyZoomFactor(currentZoomFactorDouble);
+
+    // Update the on-the-fly action text.
+    updateZoomActions(currentZoomFactorDouble);
+}
+
+void BrowserWindow::editBookmarks()
+{
+    // Instantiate an edit bookmarks dialog.
+    BookmarksDialog *bookmarksDialogPointer = new BookmarksDialog(this, tabWidgetPointer->getCurrentTabFavoritIcon(), tabWidgetPointer->getCurrentTabTitle(), tabWidgetPointer->getCurrentTabUrl());
+
+    // Update the displayed bookmarks when edited.
+    connect(bookmarksDialogPointer, SIGNAL(bookmarkUpdated()), this, SLOT(populateBookmarksInAllWindows()));
+
+    // Show the dialog.
+    bookmarksDialogPointer->show();
+}
+
 void BrowserWindow::escape() const
 {
-    // Process the excape according to the status of the browser.
+    // Process the escape according to the status of the browser.
     if (fullScreenActionPointer->isChecked())  // Full screen browsing is enabled.
     {
         // Exit full screen browsing.
         fullScreenActionPointer->trigger();
     }
-    else if (!findTextLineEditPointer->text().isEmpty())  // Find text is activated.
+    else if (!findTextLineEditPointer->text().isEmpty())  // Find text is populated.
     {
-        // Clear the text in the line edit.
+        // Clear the find text line edit.
         findTextLineEditPointer->clear();
 
         // Clear the search in the WebEngine.
         tabWidgetPointer->findText(QStringLiteral(""));
     }
-}
-
-void BrowserWindow::fileNew() const
-{
-    // Display a new instance of Privacy Browser.
-    (new BrowserWindow)->show();
+    else if (findTextLineEditActionPointer->isVisible())  // Find text actions are visible.
+    {
+        // Hide the find text actions.
+        hideFindTextActions();
+    }
 }
 
 void BrowserWindow::findNext() const
@@ -451,15 +923,6 @@ void BrowserWindow::findPrevious() const
         tabWidgetPointer->findPrevious(findString);
 }
 
-void BrowserWindow::focusFindLineEdit() const
-{
-    // Set the focus on the find line edit.
-    findTextLineEditPointer->setFocus();
-
-    // Select all the text in the find line edit.
-    findTextLineEditPointer->selectAll();
-}
-
 void BrowserWindow::forward() const
 {
     // Remove the focus from the URL line edit.
@@ -474,26 +937,49 @@ void BrowserWindow::fullScreenRequested(const bool toggleOn)
     // Toggle full screen mode.
     if (toggleOn)  // Turn full screen mode on.
     {
-        // Set the window to be full screen.
+        // Enable full screen mode.
         fullScreenActionPointer->setFullScreen(window(), true);
 
-        // Hide all the bars.
-        menuBar()->setVisible(false);
-        navigationToolBarPointer->setVisible(false);
-        urlToolBarPointer->setVisible(false);
-        tabWidgetPointer->setTabBarVisible(false);
-        statusBar()->setVisible(false);
+        // Hide the menu bar if specified.
+        if (Settings::fullScreenHideMenuBar())
+            menuBar()->setVisible(false);
+
+        // Hide the toolbars if specified.
+        if (Settings::fullScreenHideToolBars())
+        {
+            navigationToolBarPointer->setVisible(false);
+            urlToolBarPointer->setVisible(false);
+            bookmarksToolBarPointer->setVisible(false);
+        }
+
+        // Hide the tab bar if specified.
+        if (Settings::fullScreenHideTabBar())
+            tabWidgetPointer->setTabBarVisible(false);
+
+        // Hide the status bar if specified.
+        if (Settings::fullScreenHideStatusBar())
+            statusBar()->setVisible(false);
     }
-    else  // Turn full screen mode off.
+    else  // Disable full screen browsing mode.
     {
-        // Set the window to not be full screen.
+        // Disable full screen mode.
         fullScreenActionPointer->setFullScreen(window(), false);
 
-        // Show all the bars.
+        // Show the menu bar.
         menuBar()->setVisible(true);
+
+        // Show the toolbars.
         navigationToolBarPointer->setVisible(true);
         urlToolBarPointer->setVisible(true);
+
+        // Only show the bookmarks toolbar if it was previously visible.
+        if (bookmarksToolBarIsVisible)
+            bookmarksToolBarPointer->setVisible(true);
+
+        // Show the tab bar.
         tabWidgetPointer->setTabBarVisible(true);
+
+        // Show the status bar.
         statusBar()->setVisible(true);
     }
 }
@@ -504,24 +990,32 @@ void BrowserWindow::getZoomFactorFromUser()
     bool okClicked;
 
     // Display a dialog to get the new zoom factor from the user.  Format the double to display two decimals and have a 0.25 step.
-    double newZoomFactor = QInputDialog::getDouble(this, i18nc("The on-the-fly zoom factor dialog title", "On-The-Fly Zoom Factor"),
+    double newZoomFactorDouble = QInputDialog::getDouble(this, i18nc("The on-the-fly zoom factor dialog title", "On-The-Fly Zoom Factor"),
                                                    i18nc("The instruction text of the on-the-fly zoom factor dialog", "Enter a zoom factor between 0.25 and 5.00"),
-                                                   currentZoomFactor, .025, 5.00, 2, &okClicked, Qt::WindowFlags(), 0.25);
+                                                   currentZoomFactorDouble, .025, 5.00, 2, &okClicked, Qt::WindowFlags(), 0.25);
 
     // Update the zoom factor if the user clicked OK.
     if (okClicked)
     {
-        // Update the current zoom factor.
-        currentZoomFactor = newZoomFactor;
-
         // Set the new zoom factor.
-        tabWidgetPointer->applyOnTheFlyZoomFactor(newZoomFactor);
+        tabWidgetPointer->applyOnTheFlyZoomFactor(newZoomFactorDouble);
 
         // Update the on-the-fly action text.
-        updateZoomFactorAction(newZoomFactor);
+        updateZoomActions(newZoomFactorDouble);
     }
 }
 
+void BrowserWindow::hideFindTextActions() const
+{
+    // Hide the find text actions.
+    findTextLineEditActionPointer->setVisible(false);
+    findTextLabelActionPointer->setVisible(false);
+    findNextActionPointer->setVisible(false);
+    findPreviousActionPointer->setVisible(false);
+    findCaseSensitiveActionPointer->setVisible(false);
+    hideFindTextActionPointer->setVisible(false);
+}
+
 void BrowserWindow::home() const
 {
     // Remove the focus from the URL line edit.
@@ -531,6 +1025,36 @@ void BrowserWindow::home() const
     tabWidgetPointer->home();
 }
 
+void BrowserWindow::hideProgressBar() const
+{
+    // Hide the progress bar.
+    progressBarPointer->hide();
+
+    // Disable and hide the stop action.
+    stopActionPointer->setEnabled(false);
+    stopActionPointer->setVisible(false);
+
+    // Enable and show the refresh action.
+    refreshActionPointer->setEnabled(true);
+    refreshActionPointer->setVisible(true);
+}
+
+void BrowserWindow::incrementZoom()
+{
+    // Update the current zoom factor.
+    currentZoomFactorDouble = currentZoomFactorDouble + 0.25;
+
+    // Check to make sure the zoom factor is in the valid range (0.25 to 5.00).
+    if (currentZoomFactorDouble > 5.0)
+        currentZoomFactorDouble = 5.0;
+
+    // Set the new zoom factor.
+    tabWidgetPointer->applyOnTheFlyZoomFactor(currentZoomFactorDouble);
+
+    // Update the on-the-fly action text.
+    updateZoomActions(currentZoomFactorDouble);
+}
+
 void BrowserWindow::loadUrlFromLineEdit(const QString &url) const
 {
     // Remove the focus from the URL line edit.
@@ -540,6 +1064,258 @@ void BrowserWindow::loadUrlFromLineEdit(const QString &url) const
     tabWidgetPointer->loadUrlFromLineEdit(url);
 }
 
+void BrowserWindow::newWindow() const
+{
+    // Create a new browser window.
+    BrowserWindow *browserWindowPointer = new BrowserWindow();
+
+    // Show the new browser window.
+    browserWindowPointer->show();
+}
+
+void BrowserWindow::populateBookmarksInAllWindows() const
+{
+    // Get a list of all the registered service names.
+    QStringList registeredServiceNames = QDBusConnection::sessionBus().interface()->registeredServiceNames().value();
+
+    // Get a list of all the Privacy Browser windows, which will be `com.stoutner.privacybrowser-` with the PID appended.
+    QStringList privacyBrowserServiceNames = registeredServiceNames.filter("com.stoutner.privacybrowser");
+
+    // Repopulate the bookmarks in each window.
+    for (QString privacyBrowserServiceName : privacyBrowserServiceNames)
+    {
+        // Prepare the D-Bus message.
+        QDBusMessage dBusMessage = QDBusMessage::createMethodCall(privacyBrowserServiceName, "/privacybrowser/MainWindow_1", "com.stoutner.privacybrowser.BrowserWindow", "populateBookmarksInThisWindow");
+
+        // Make it so.
+        QDBusConnection::sessionBus().send(dBusMessage);
+    }
+}
+
+void BrowserWindow::populateBookmarksInThisWindow()
+{
+    // Remove all the final bookmark folder menu actions.
+    for (QPair<QMenu *, QAction *> *bookmarkFolderFinalActionPairPointer : bookmarkFolderFinalActionList)
+    {
+        // Remove the action.
+        bookmarkFolderFinalActionPairPointer->first->removeAction(bookmarkFolderFinalActionPairPointer->second);
+    }
+
+    // Remove all the current menu bookmarks.
+    for (QPair<QMenu *, QAction *> *bookmarkMenuActionPairPointer : bookmarksMenuActionList)
+    {
+        // Remove the bookmark.
+        bookmarkMenuActionPairPointer->first->removeAction(bookmarkMenuActionPairPointer->second);
+    }
+
+    // Remove all the current menu subfolders.
+    for (QPair<QMenu *, QMenu *> *submenuPairPointer : bookmarksMenuSubmenuList)
+    {
+        // Remove the submenu from the parent menu.
+        submenuPairPointer->first->removeAction(submenuPairPointer->second->menuAction());
+    }
+
+    // Remove all the current toolbar subfolders.
+    for (QPair<QMenu *, QAction *> *subfolderActionPairPointer : bookmarksToolBarSubfolderActionList)
+    {
+        // Remove the action from the subfolder.
+        subfolderActionPairPointer->first->removeAction(subfolderActionPairPointer->second);
+    }
+
+    // Remove all the current toolbar bookmarks.
+    for (QAction *bookmarkAction : bookmarksToolBarActionList)
+    {
+        // Remove the bookmark.
+        bookmarksToolBarPointer->removeAction(bookmarkAction);
+    }
+
+    // Clear the action lists.
+    bookmarkFolderFinalActionList.clear();
+    bookmarksMenuActionList.clear();
+    bookmarksMenuSubmenuList.clear();
+    bookmarksToolBarSubfolderActionList.clear();
+    bookmarksToolBarActionList.clear();
+
+    // Populate the bookmarks subfolders, beginning with the root folder (`0`);
+    populateBookmarksMenuSubfolders(0, bookmarksMenuPointer);
+
+    // Populate the bookmarks toolbar.
+    populateBookmarksToolBar();
+
+    // Get a handle for the bookmark toolbar layout.
+    QLayout *bookmarksToolBarLayoutPointer = bookmarksToolBarPointer->layout();
+
+    // Get the count of the bookmarks.
+    int bookmarkCount = bookmarksToolBarLayoutPointer->count();
+
+    // Set the layout of each bookmark to be left aligned.
+    for(int i = 0; i < bookmarkCount; ++i)
+        bookmarksToolBarLayoutPointer->itemAt(i)->setAlignment(Qt::AlignLeft);
+
+    // Update the bookmarked action.
+    updateBookmarkedAction();
+}
+
+void BrowserWindow::populateBookmarksMenuSubfolders(const double folderId, QMenu *menuPointer)
+{
+    // Get the folder contents.
+    QList<BookmarkStruct> *folderContentsListPointer = BookmarksDatabase::getFolderContents(folderId);
+
+    // Populate the bookmarks menu and toolbar.
+    for (BookmarkStruct bookmarkStruct : *folderContentsListPointer)
+    {
+        // Process the item according to the type.
+        if (bookmarkStruct.isFolder)  // This item is a folder.
+        {
+            // Add a submenu to the menu.
+            QMenu *submenuPointer = menuPointer->addMenu(bookmarkStruct.favoriteIcon, bookmarkStruct.name);
+
+            // Add the submenu to the beginning of the list of menus to be deleted on repopulate.
+            bookmarksMenuSubmenuList.prepend(new QPair<QMenu *, QMenu *>(menuPointer, submenuPointer));
+
+            // Populate any subfolders.
+            populateBookmarksMenuSubfolders(bookmarkStruct.folderId, submenuPointer);
+        }
+        else  // This item is a bookmark.
+        {
+            // Add the bookmark to the menu.
+            QAction *menuBookmarkActionPointer = menuPointer->addAction(bookmarkStruct.favoriteIcon, bookmarkStruct.name, [=]
+                {
+                    // Remove the focus from the URL line edit.
+                    urlLineEditPointer->clearFocus();
+
+                    // Load the URL.
+                    tabWidgetPointer->loadUrlFromLineEdit(bookmarkStruct.url);
+                }
+            );
+
+            // Add the actions to the beginning of the list of bookmarks to be deleted on repopulate.
+            bookmarksMenuActionList.prepend(new QPair<QMenu *, QAction *>(menuPointer, menuBookmarkActionPointer));
+        }
+    }
+
+    // Add the extra items at the bottom of the menu.  `true` adds them to the list of actions to be deleted on repopulate.
+    addBookmarkFolderFinalActions(menuPointer, folderId, true);
+}
+
+void BrowserWindow::populateBookmarksToolBar()
+{
+    // Get the root folder contents (which has a folder ID of `0`).
+    QList<BookmarkStruct> *folderContentsListPointer = BookmarksDatabase::getFolderContents(0);
+
+    // Populate the bookmarks toolbar.
+    for (BookmarkStruct bookmarkStruct : *folderContentsListPointer)
+    {
+        // Process the item according to the type.
+        if (bookmarkStruct.isFolder)  // This item is a folder.
+        {
+            // Add the subfolder action.
+            QAction *toolBarSubfolderActionPointer = bookmarksToolBarPointer->addAction(bookmarkStruct.favoriteIcon, bookmarkStruct.name);
+
+            // Add the bookmark database ID to the toolbar action.
+            toolBarSubfolderActionPointer->setData(bookmarkStruct.databaseId);
+
+            // Add the action to the beginning of the list of actions to be deleted on repopulate.
+            bookmarksToolBarActionList.prepend(toolBarSubfolderActionPointer);
+
+            // Create a subfolder menu.
+            QMenu *subfolderMenuPointer = new QMenu();
+
+            // Add the menu to the action.
+            toolBarSubfolderActionPointer->setMenu(subfolderMenuPointer);
+
+            // Add the submenu to the toolbar menu list.
+            bookmarksToolBarMenuList.append(new QPair<QMenu *, const double>(subfolderMenuPointer, bookmarkStruct.folderId));
+
+            // Set the popup mode for the menu.
+            dynamic_cast<QToolButton *>(bookmarksToolBarPointer->widgetForAction(toolBarSubfolderActionPointer))->setPopupMode(QToolButton::InstantPopup);
+
+            // Populate the subfolder.
+            populateBookmarksToolBarSubfolders(bookmarkStruct.folderId, subfolderMenuPointer);
+        }
+        else  // This item is a bookmark.
+        {
+            // Add the bookmark to the toolbar.
+            QAction *toolBarBookmarkActionPointer = bookmarksToolBarPointer->addAction(bookmarkStruct.favoriteIcon, bookmarkStruct.name, [=]
+                {
+                    // Remove the focus from the URL line edit.
+                    urlLineEditPointer->clearFocus();
+
+                    // Load the URL.
+                    tabWidgetPointer->loadUrlFromLineEdit(bookmarkStruct.url);
+                }
+            );
+
+            // Add the bookmark database ID to the toolbar action.
+            toolBarBookmarkActionPointer->setData(bookmarkStruct.databaseId);
+
+            // Add the actions to the beginning of the current bookmarks lists.
+            bookmarksToolBarActionList.prepend(toolBarBookmarkActionPointer);
+        }
+    }
+
+    // Add the extra items to the toolbar folder menus.  The first item in the pair is the menu pointer.  The second is the folder ID.
+    for (QPair<QMenu *, const double> *menuAndFolderIdPairPointer : bookmarksToolBarMenuList)
+    {
+        // Populate the final bookmarks menu entries.  `true` adds them to the list of actions to be deleted on repopulate.
+        addBookmarkFolderFinalActions(menuAndFolderIdPairPointer->first, menuAndFolderIdPairPointer->second, true);
+    }
+}
+
+void BrowserWindow::populateBookmarksToolBarSubfolders(const double folderId, QMenu *menuPointer)
+{
+    // Get the folder contents.
+    QList<BookmarkStruct> *folderContentsListPointer = BookmarksDatabase::getFolderContents(folderId);
+
+    // Populate the bookmarks folder.
+    for (BookmarkStruct bookmarkStruct : *folderContentsListPointer)
+    {
+        // Get the bookmark URL.
+        QString bookmarkUrl = bookmarkStruct.url;
+
+        // Process the item according to the type.
+        if (bookmarkStruct.isFolder)  // This item is a folder.
+        {
+            // Add the subfolder action.
+            QAction *toolBarSubfolderActionPointer = menuPointer->addAction(bookmarkStruct.favoriteIcon, bookmarkStruct.name);
+
+            // Add the action to the beginning of the list of actions to be deleted on repopulate.
+            bookmarksToolBarSubfolderActionList.prepend(new QPair<QMenu *, QAction *>(menuPointer, toolBarSubfolderActionPointer));
+
+            // Create a subfolder menu.
+            QMenu *subfolderMenuPointer = new QMenu();
+
+            // Add the submenu to the action.
+            toolBarSubfolderActionPointer->setMenu(subfolderMenuPointer);
+
+            // Add the submenu to the toolbar menu list.
+            bookmarksToolBarMenuList.append(new QPair<QMenu *, const double>(subfolderMenuPointer, bookmarkStruct.folderId));
+
+            // Populate the subfolder menu.
+            populateBookmarksToolBarSubfolders(bookmarkStruct.folderId, subfolderMenuPointer);
+        }
+        else  // This item is a bookmark.
+        {
+            // Add the bookmark to the folder.
+            QAction *toolBarBookmarkActionPointer = menuPointer->addAction(bookmarkStruct.favoriteIcon, bookmarkStruct.name, [=]
+                {
+                    // Remove the focus from the URL line edit.
+                    urlLineEditPointer->clearFocus();
+
+                    // Load the URL.
+                    tabWidgetPointer->loadUrlFromLineEdit(bookmarkUrl);
+                }
+            );
+
+            // Add the bookmark database ID to the toolbar action.
+            toolBarBookmarkActionPointer->setData(bookmarkStruct.databaseId);
+
+            // Add the action to the beginning of the list of actions to be deleted on repopulate.
+            bookmarksToolBarSubfolderActionList.prepend(new QPair<QMenu *, QAction *>(menuPointer, toolBarBookmarkActionPointer));
+        }
+    }
+}
+
 void BrowserWindow::refresh() const
 {
     // Remove the focus from the URL line edit.
@@ -549,6 +1325,150 @@ void BrowserWindow::refresh() const
     tabWidgetPointer->refresh();
 }
 
+void BrowserWindow::reloadAndBypassCache() const
+{
+    // Remove the focus from the URL line edit.
+    urlLineEditPointer->clearFocus();
+
+    // Refresh the web page.
+    tabWidgetPointer->refresh();
+}
+
+void BrowserWindow::showBookmarkContextMenu(const QPoint &point)
+{
+    // Get the bookmark action.
+    QAction *bookmarkActionPointer = bookmarksToolBarPointer->actionAt(point);
+
+    // Check to see if an action was clicked.
+    if (bookmarkActionPointer)  // An action was clicked.
+    {
+        // Create a bookmark context menu.
+        QMenu *bookmarkContextMenuPointer = new QMenu();
+
+        // Get the database ID from the action.
+        int databaseId = bookmarkActionPointer->data().toInt();
+
+        // Create the menu according to the type.
+        if (BookmarksDatabase::isFolder(databaseId))  // A folder was clicked.
+        {
+            // Populate the final bookmarks menu entries.  `false` does not add the actions to the delete list, as they will be automatically deleted when the menu closes.
+            addBookmarkFolderFinalActions(bookmarkContextMenuPointer, BookmarksDatabase::getFolderId(databaseId), false);
+        }
+        else  // A bookmark was clicked.
+        {
+            // Add the open in new tab action to the menu.
+            bookmarkContextMenuPointer->addAction(QIcon::fromTheme(QLatin1String("tab-new")), i18nc("Open bookmark in new tab context menu entry", "Open in New Tab"), [=]
+                {
+                    // Get the bookmark.
+                    BookmarkStruct *bookmarkStructPointer = BookmarksDatabase::getBookmark(databaseId);
+
+                    // Open the bookmark in a new tab.  `true` removes the URL line edit focus, `true` opens the new tab in an adjacent tab.  `false` does not load a background tab.
+                    tabWidgetPointer->addTab(true, true, false, bookmarkStructPointer->url);
+                }
+            );
+
+            // Add the open in background tab action to the menu.
+            bookmarkContextMenuPointer->addAction(QIcon::fromTheme(QLatin1String("tab-new")), i18nc("Open bookmark in background tab context menu entry", "Open in Background Tab"), [=]
+                {
+                    // Get the bookmark.
+                    BookmarkStruct *bookmarkStructPointer = BookmarksDatabase::getBookmark(databaseId);
+
+                    // Open the bookmark in a new tab.  `true` removes the URL line edit focus, `true` opens the new tab in an adjacent tab.  `true` loads a background tab.
+                    tabWidgetPointer->addTab(true, true, true, bookmarkStructPointer->url);
+                }
+            );
+
+            // Add the open in new window action to the menu.
+            bookmarkContextMenuPointer->addAction(QIcon::fromTheme(QLatin1String("window-new")), i18nc("Open bookmark in new window context menu entry", "Open in New Window"), [=]
+                {
+                    // Get the bookmark.
+                    BookmarkStruct *bookmarkStructPointer = BookmarksDatabase::getBookmark(databaseId);
+
+                    // Create a new browser window and load the first URL.  `false` indicates it is not the first browser window.
+                    BrowserWindow *browserWindowPointer = new BrowserWindow(false, &bookmarkStructPointer->url);
+
+                    // Show the new browser window.
+                    browserWindowPointer->show();
+                }
+            );
+
+            // Add a separator.
+            bookmarkContextMenuPointer->addSeparator();
+
+            // Add the edit action to the menu.
+            bookmarkContextMenuPointer->addAction(QIcon::fromTheme(QLatin1String("document-edit")), i18nc("Edit bookmark context menu entry", "Edit"), [=]
+                {
+                    // Get the current tab favorite icon.
+                    QIcon currentTabFavoriteIcon = tabWidgetPointer->getCurrentTabFavoritIcon();
+
+                    // Instantiate an edit bookmark dialog.
+                    QDialog *editBookmarkDialogPointer = new EditBookmarkDialog(this, databaseId, currentTabFavoriteIcon);
+
+                    // Show the dialog.
+                    editBookmarkDialogPointer->show();
+
+                    // Process bookmark events.
+                    connect(editBookmarkDialogPointer, SIGNAL(bookmarkSaved()), this, SLOT(populateBookmarksInAllWindows()));
+                }
+            );
+
+            // Add the copy URL action to the menu.
+            bookmarkContextMenuPointer->addAction(QIcon::fromTheme(QLatin1String("edit-copy")), i18nc("Copy bookmark URL context menu entry", "Copy URL"), [=]
+                {
+                    // Get the bookmark.
+                    BookmarkStruct *bookmarkStructPointer = BookmarksDatabase::getBookmark(databaseId);
+
+                    // Get a handle for the clipboard.
+                    QClipboard *clipboard = QGuiApplication::clipboard();
+
+                    // Place the URL on the keyboard.
+                    clipboard->setText(bookmarkStructPointer->url);
+                }
+            );
+
+            // Add a separator.
+            bookmarkContextMenuPointer->addSeparator();
+
+            // Add the delete action to the menu.
+            bookmarkContextMenuPointer->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18nc("Delete bookmark context menu entry", "Delete"), [=]
+                {
+                    // Get the parent folder ID.
+                    double parentFolderId = BookmarksDatabase::getParentFolderId(databaseId);
+
+                    // Delete the bookmark.
+                    BookmarksDatabase::deleteBookmark(databaseId);
+
+                    // Update the display order of the bookmarks in the parent folder.
+                    BookmarksDatabase::updateFolderContentsDisplayOrder(parentFolderId);
+
+                    // Repopulate the bookmarks.
+                    populateBookmarksInAllWindows();
+                }
+            );
+        }
+
+        // Delete the menu from memory when it is closed.
+        bookmarkContextMenuPointer->setAttribute(Qt::WA_DeleteOnClose);
+
+        // Display the context menu.
+        bookmarkContextMenuPointer->popup(bookmarksToolBarPointer->mapToGlobal(point));
+    }
+    else  // The toolbar background was clicked.
+    {
+        // Temporarily set the context menu policy to the default.
+        bookmarksToolBarPointer->setContextMenuPolicy(Qt::DefaultContextMenu);
+
+        // Create a context menu event with the same position.
+        QContextMenuEvent *contextMenuEventPointer = new QContextMenuEvent(QContextMenuEvent::Mouse, point);
+
+        // Send the context menu event to the toolbar.
+        QCoreApplication::sendEvent(bookmarksToolBarPointer, contextMenuEventPointer);
+
+        // Reset the context menu policy.
+        bookmarksToolBarPointer->setContextMenuPolicy(Qt::CustomContextMenu);
+    }
+}
+
 void BrowserWindow::showCookiesDialog()
 {
     // Remove the focus from the URL line edit.
@@ -566,44 +1486,36 @@ void BrowserWindow::showCookiesDialog()
     connect(cookiesDialogPointer, SIGNAL(deleteCookie(QNetworkCookie)), tabWidgetPointer, SLOT(deleteCookieFromStore(QNetworkCookie)));
 }
 
-void BrowserWindow::showDownloadLocationBrowseDialog() const
-{
-    // Get the current download location.
-    QString currentDownloadLocation = downloadLocationComboBoxPointer->currentText();
-
-    // Resolve the system download directory if specified.
-    if (currentDownloadLocation == QStringLiteral("System Download Directory"))
-        currentDownloadLocation = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
-
-    // Get the new download location.
-    QString newDownloadLocation = QFileDialog::getExistingDirectory(configDialogPointer, i18nc("Select download location dialog caption", "Select Download Location"), currentDownloadLocation);
-
-    // Populate the download location combo box according to the new download location.
-    if (newDownloadLocation == QStandardPaths::writableLocation(QStandardPaths::DownloadLocation))  // The default download location was selected.
-    {
-        // Populate the download location with the default text.
-        downloadLocationComboBoxPointer->setCurrentText("System Download Directory");
-    }
-    else if (newDownloadLocation != QStringLiteral(""))  // A different directory was selected.
-    {
-        // Populate the download location.
-        downloadLocationComboBoxPointer->setCurrentText(newDownloadLocation);
-    }
-}
-
-void BrowserWindow::showDomainSettingsDialog() const
+void BrowserWindow::showDomainSettingsDialog()
 {
     // Remove the focus from the URL line edit.
     urlLineEditPointer->clearFocus();
 
     // Instantiate the domain settings dialog.
-    DomainSettingsDialog *domainSettingsDialogPointer = new DomainSettingsDialog();
+    DomainSettingsDialog *domainSettingsDialogPointer = new DomainSettingsDialog(this);
+
+    // Reload the tabs when domain settings are updated.
+    connect(domainSettingsDialogPointer, SIGNAL(domainSettingsUpdated()), tabWidgetPointer, SLOT(applyDomainSettingsAndReload()));
 
     // Show the dialog.
     domainSettingsDialogPointer->show();
+}
 
-    // Reload the tabs when domain settings are updated.
-    connect(domainSettingsDialogPointer, SIGNAL(domainSettingsUpdated()), tabWidgetPointer, SLOT(applyDomainSettingsAndReload()));
+void BrowserWindow::showFindTextActions() const
+{
+    // Show the find text actions.
+    findTextLineEditActionPointer->setVisible(true);
+    findTextLabelActionPointer->setVisible(true);
+    findNextActionPointer->setVisible(true);
+    findPreviousActionPointer->setVisible(true);
+    findCaseSensitiveActionPointer->setVisible(true);
+    hideFindTextActionPointer->setVisible(true);
+
+    // Set the focus on the find line edit.
+    findTextLineEditPointer->setFocus();
+
+    // Select all the text in the find line edit.
+    findTextLineEditPointer->selectAll();
 }
 
 void BrowserWindow::showProgressBar(const int &progress) const
@@ -613,72 +1525,84 @@ void BrowserWindow::showProgressBar(const int &progress) const
 
     // Show the progress bar.
     progressBarPointer->show();
+
+    // Disable and hide the refresh action.
+    refreshActionPointer->setEnabled(false);
+    refreshActionPointer->setVisible(false);
+
+    // Enable and show the stop action.
+    stopActionPointer->setEnabled(true);
+    stopActionPointer->setVisible(true);
 }
 
 void BrowserWindow::showSettingsDialog()
 {
-    // Create the settings widgets.
-    QWidget *privacySettingsWidgetPointer = new QWidget;
-    QWidget *generalSettingsWidgetPointer = new QWidget;
+    // Get a handle for the KConfig skeleton.
+    KConfigSkeleton *kConfigSkeletonPointer = Settings::self();
 
-    // Instantiate the settings UI.
-    Ui::PrivacySettings privacySettingsUi;
-    Ui::GeneralSettings generalSettingsUi;
+    // Instantiate a settings dialog.
+    SettingsDialog *settingsDialogPointer = new SettingsDialog(this, kConfigSkeletonPointer);
 
-    // Setup the UI to display the settings widgets.
-    privacySettingsUi.setupUi(privacySettingsWidgetPointer);
-    generalSettingsUi.setupUi(generalSettingsWidgetPointer);
+    // Show the dialog
+    settingsDialogPointer->show();
 
-    // Get handles for the widgets.
-    QComboBox *userAgentComboBoxPointer = privacySettingsUi.kcfg_userAgent;
-    userAgentLabelPointer = privacySettingsUi.userAgentLabel;
-    QComboBox *searchEngineComboBoxPointer = generalSettingsUi.kcfg_searchEngine;
-    searchEngineLabelPointer = generalSettingsUi.searchEngineLabel;
-    downloadLocationComboBoxPointer = generalSettingsUi.kcfg_downloadLocation;
-    QPushButton *browseButtonPointer = generalSettingsUi.browseButton;
+    // TODO.  KConfigDialog does not respect expanding size policies.  <https://redmine.stoutner.com/issues/823>
+    //settingsDialogPointer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+    //privacySettingsWidgetPointer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+    //generalSettingsWidgetPointer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+    //settingsDialogPointer->adjustSize();
 
-    // Populate the combo box labels.
-    updateUserAgentLabel(userAgentComboBoxPointer->currentText());
-    updateSearchEngineLabel(searchEngineComboBoxPointer->currentText());
+    // Expand the config dialog.
+    settingsDialogPointer->resize(1000, 500);
 
-    // Update the labels when the combo boxes change.
-    connect(userAgentComboBoxPointer, SIGNAL(currentTextChanged(const QString)), this, SLOT(updateUserAgentLabel(const QString)));
-    connect(searchEngineComboBoxPointer, SIGNAL(currentTextChanged(const QString)), this, SLOT(updateSearchEngineLabel(const QString)));
+    // Apply the settings handled by KConfig.
+    connect(settingsDialogPointer, SIGNAL(spellCheckLanguagesUpdated()), tabWidgetPointer, SLOT(applySpellCheckLanguages()));
+    connect(settingsDialogPointer, SIGNAL(settingsChanged(QString)), tabWidgetPointer, SLOT(applyApplicationSettings()));
+    connect(settingsDialogPointer, SIGNAL(settingsChanged(QString)), tabWidgetPointer, SLOT(applyDomainSettingsAndReload()));
+}
 
-    // Connect the download location directory browse button.
-    connect(browseButtonPointer, SIGNAL(clicked()), this, SLOT(showDownloadLocationBrowseDialog()));
+QSize BrowserWindow::sizeHint() const
+{
+    // Return the default window size.
+    return QSize(1500, 1200);
+}
 
-    // Instantiate a settings config dialog from the settings.kcfg file.
-    configDialogPointer = new KConfigDialog(this, QStringLiteral("settings"), Settings::self());
+void BrowserWindow::toggleBookmark()
+{
+    // Remove the focus from the URL line edit, which will have been focused when the user clicked on the bookmarked icon.
+    urlLineEditPointer->clearFocus();
 
-    // Add the settings widgets as config dialog pages.
-    configDialogPointer->addPage(privacySettingsWidgetPointer, i18nc("@title:tab", "Privacy"), QStringLiteral("privacy-browser"));
-    configDialogPointer->addPage(generalSettingsWidgetPointer, i18nc("@title:tab", "General"), QStringLiteral("breeze-settings"));
+    // Create or delete the bookmark
+    if (bookmarkedActionPointer->isChecked())  // The user checked the toggle.  Create a bookmark.
+    {
+        // Create a bookmark struct.
+        BookmarkStruct *bookmarkStructPointer = new BookmarkStruct;
 
-    // Prevent interaction with the parent window while the dialog is open.
-    configDialogPointer->setWindowModality(Qt::WindowModal);
+        // Populate the bookmark struct.
+        bookmarkStructPointer->name = tabWidgetPointer->getCurrentTabTitle();
+        bookmarkStructPointer->url = tabWidgetPointer->getCurrentTabUrl();
+        bookmarkStructPointer->parentFolderId = 0;
+        bookmarkStructPointer->favoriteIcon = tabWidgetPointer->getCurrentTabFavoritIcon();
 
-    // Make it so.
-    configDialogPointer->show();
+        // Add the bookmark.
+        BookmarksDatabase::addBookmark(bookmarkStructPointer);
+    }
+    else  // The user unchecked the toggle.  Delete all related bookmarks.
+    {
+        // Delete the bookmarks.
+        BookmarksDatabase::deleteBookmarks(urlLineEditPointer->text());
 
-    // TODO.  KConfigDialog does not respect expanding size policies.  <https://redmine.stoutner.com/issues/823>
-    //configDialogPointer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
-    //privacySettingsWidgetPointer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
-    //generalSettingsWidgetPointer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
-    //configDialogPointer->adjustSize();
 
-    // Expand the config dialog.
-    configDialogPointer->resize(1000, 500);
+    }
 
-    // Apply the settings when they are updated.
-    connect(configDialogPointer, SIGNAL(settingsChanged(QString)), tabWidgetPointer, SLOT(applyApplicationSettings()));
-    connect(configDialogPointer, SIGNAL(settingsChanged(QString)), tabWidgetPointer, SLOT(applyDomainSettingsAndReload()));
+    // Repopulate the bookmarks.
+    populateBookmarksInAllWindows();
 }
 
-QSize BrowserWindow::sizeHint() const
+void BrowserWindow::toggleDeveloperTools() const
 {
-    // Return the default window size.
-    return QSize(1500, 1200);
+    // Toggle the developer tools.
+    tabWidgetPointer->toggleDeveloperTools(developerToolsActionPointer->isChecked());
 }
 
 void BrowserWindow::toggleDomStorage() const
@@ -720,48 +1644,67 @@ void BrowserWindow::toggleLocalStorage() const
 void BrowserWindow::toggleFullScreen()
 {
     // Toggle the full screen status.
-    if (fullScreenActionPointer->isChecked())  // Enable full screen browsing mode.
-    {
-        // Enable full screen mode.
-        fullScreenActionPointer->setFullScreen(window(), true);
-
-        // Hide the menu bar if specified.
-        if (Settings::fullScreenHideMenuBar())
-            menuBar()->setVisible(false);
-
-        // Hide the toolbars if specified.
-        if (Settings::fullScreenHideToolBars())
-        {
-            navigationToolBarPointer->setVisible(false);
-            urlToolBarPointer->setVisible(false);
-        }
+    fullScreenRequested(fullScreenActionPointer->isChecked());
+}
 
-        // Hide the tab bar if specified.
-        if (Settings::fullScreenHideTabBar())
-            tabWidgetPointer->setTabBarVisible(false);
+void BrowserWindow::toggleViewSource() const
+{
+    // Get the current URL.
+    QString url = urlLineEditPointer->text();
 
-        // Hide the status bar if specified.
-        if (Settings::fullScreenHideStatusBar())
-            statusBar()->setVisible(false);
+    // Toggle the URL.
+    if (url.startsWith(QLatin1String("view-source:")))  // The source is currently being viewed.
+    {
+        // Remove `view-source:` from the URL.
+        url = url.remove(0, 12);
     }
-    else  // Disable full screen browsing mode.
+    else  // The source is not currently being viewed.
     {
-        // Disable full screen mode.
-        fullScreenActionPointer->setFullScreen(window(), false);
+        // Prepend `view-source:` from the URL.
+        url = url.prepend(QLatin1String("view-source:"));
+    }
 
-        // Show the menu bar.
-        menuBar()->setVisible(true);
+    // Make it so.
+    loadUrlFromLineEdit(url);
+}
 
-        // Show the toolbars.
-        navigationToolBarPointer->setVisible(true);
-        urlToolBarPointer->setVisible(true);
+void BrowserWindow::toggleViewBookmarksToolBar()
+{
+    // Store the current status of the bookmarks toolbar, which is used when exiting full screen browsing.
+    bookmarksToolBarIsVisible = viewBookmarksToolBarActionPointer->isChecked();
 
-        // Show the tab bar.
-        tabWidgetPointer->setTabBarVisible(true);
+    // Update the visibility of the bookmarks toolbar.
+    bookmarksToolBarPointer->setVisible(bookmarksToolBarIsVisible);
+}
 
-        // Show the status bar.
-        statusBar()->setVisible(true);
+void BrowserWindow::toggleViewSourceInNewTab() const
+{
+    // Get the current URL.
+    QString url = urlLineEditPointer->text();
+
+    // Toggle the URL.
+    if (url.startsWith(QLatin1String("view-source:")))  // The source is currently being viewed.
+    {
+        // Remove `view-source:` from the URL.
+        url = url.remove(0, 12);
+    }
+    else  // The source is not currently being viewed.
+    {
+        // Prepend `view-source:` from the URL.
+        url = url.prepend(QLatin1String("view-source:"));
     }
+
+    // Add the new tab.  `true` removes the URL line edit focus, `true` opens the new tab in an adjacent tab.  `false` does not open a background tab.
+    tabWidgetPointer->addTab(true, true, false, url);
+}
+
+void BrowserWindow::updateBookmarkedAction() const
+{
+    // Update the bookmarked action to reflect the current state.
+    if (bookmarkedActionPointer->isChecked())
+        bookmarkedActionPointer->setIcon(QIcon::fromTheme("starred-symbolic"));
+    else
+        bookmarkedActionPointer->setIcon(QIcon::fromTheme("non-starred-symbolic"));
 }
 
 void BrowserWindow::updateCookiesAction(const int numberOfCookies) const
@@ -770,6 +1713,12 @@ void BrowserWindow::updateCookiesAction(const int numberOfCookies) const
     cookiesActionPointer->setText(i18nc("The Cookies action, which also displays the number of cookies", "Cookies - %1", numberOfCookies));
 }
 
+void BrowserWindow::updateDefaultZoomFactor(const double newDefaultZoomFactorDouble)
+{
+    // Store the new default zoom factor.
+    defaultZoomFactorDouble = newDefaultZoomFactorDouble;
+}
+
 void BrowserWindow::updateDomStorageAction(const bool &isEnabled) const
 {
     // Set the action checked status.
@@ -815,9 +1764,9 @@ void BrowserWindow::updateJavaScriptAction(const bool &isEnabled)
 
     // Set the icon according to the status.
     if (javaScriptEnabled)
-        javaScriptActionPointer->setIcon(QIcon(QStringLiteral(":/icons/javascript-warning")));
+        javaScriptActionPointer->setIcon(QIcon(QLatin1String(":/icons/javascript-warning.svg")));
     else
-        javaScriptActionPointer->setIcon(QIcon(QStringLiteral(":/icons/privacy-mode")));
+        javaScriptActionPointer->setIcon(QIcon(QLatin1String(":/icons/privacy-mode.svg")));
 
     // Set the action checked status.
     javaScriptActionPointer->setChecked(javaScriptEnabled);
@@ -831,11 +1780,11 @@ void BrowserWindow::updateLocalStorageAction(const bool &isEnabled)
     // Update the local storage status.
     localStorageEnabled = isEnabled;
 
-    // Update the icon.
+    // Update the icon.  On Gnome, the toolbar icons don't pick up unless the size is explicit, probably because the toolbar ends up being an intermediate size.
     if (localStorageEnabled)
-        localStorageActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("disk-quota-high")));
+        localStorageActionPointer->setIcon(QIcon::fromTheme(QLatin1String("disk-quota-high"), QIcon(QLatin1String("/usr/share/icons/gnome/32x32/actions/document-save-as.png"))));
     else
-        localStorageActionPointer->setIcon(QIcon::fromTheme(QStringLiteral("disk-quota")));
+        localStorageActionPointer->setIcon(QIcon::fromTheme(QLatin1String("disk-quota"), QIcon(QLatin1String("/usr/share/icons/gnome/32x32/apps/kfm.png"))));
 
     // Set the action checked status.
     localStorageActionPointer->setChecked(localStorageEnabled);
@@ -854,6 +1803,9 @@ void BrowserWindow::updateSearchEngineActions(const QString &searchEngine, const
         // Check the Mojeek user agent action.
         searchEngineMojeekActionPointer->setChecked(true);
 
+        // Update the search engine menu action icon.
+        searchEngineMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("edit-find")));
+
         // Update the search engine menu action text.
         searchEngineMenuActionPointer->setText(i18nc("The main search engine menu action", "Search Engine - Mojeek"));
     }
@@ -862,6 +1814,9 @@ void BrowserWindow::updateSearchEngineActions(const QString &searchEngine, const
         // Check the Monocles user agent action.
         searchEngineMonoclesActionPointer->setChecked(true);
 
+        // Update the search engine menu action icon.
+        searchEngineMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("edit-find")));
+
         // Update the search engine menu action text.
         searchEngineMenuActionPointer->setText(i18nc("The main search engine menu action", "Search Engine - Monocles"));
     }
@@ -870,6 +1825,9 @@ void BrowserWindow::updateSearchEngineActions(const QString &searchEngine, const
         // Check the MetaGer user agent action.
         searchEngineMetagerActionPointer->setChecked(true);
 
+        // Update the search engine menu action icon.
+        searchEngineMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("edit-find")));
+
         // Update the search engine menu action text.
         searchEngineMenuActionPointer->setText(i18nc("The main search engine menu action", "Search Engine - MetaGer"));
     }
@@ -878,6 +1836,9 @@ void BrowserWindow::updateSearchEngineActions(const QString &searchEngine, const
         // Check the Google user agent action.
         searchEngineGoogleActionPointer->setChecked(true);
 
+        // Update the search engine menu action icon.
+        searchEngineMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("im-google"), QIcon::fromTheme(QLatin1String("edit-find"))));
+
         // Update the search engine menu action text.
         searchEngineMenuActionPointer->setText(i18nc("The main search engine menu action", "Search Engine - Google"));
     }
@@ -886,6 +1847,9 @@ void BrowserWindow::updateSearchEngineActions(const QString &searchEngine, const
         // Check the Bing user agent action.
         searchEngineBingActionPointer->setChecked(true);
 
+        // Update the search engine menu action icon.
+        searchEngineMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("edit-find")));
+
         // Update the search engine menu action text.
         searchEngineMenuActionPointer->setText(i18nc("The main search engine menu action", "Search Engine - Bing"));
     }
@@ -894,6 +1858,9 @@ void BrowserWindow::updateSearchEngineActions(const QString &searchEngine, const
         // Check the Yahoo user agent action.
         searchEngineYahooActionPointer->setChecked(true);
 
+        // Update the search engine menu action icon.
+        searchEngineMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("im-yahoo"), QIcon::fromTheme(QLatin1String("edit-find"))));
+
         // Update the search engine menu action text.
         searchEngineMenuActionPointer->setText(i18nc("The main search engine menu action", "Search Engine - Yahoo"));
     }
@@ -902,6 +1869,9 @@ void BrowserWindow::updateSearchEngineActions(const QString &searchEngine, const
         // Check the user agent.
         searchEngineCustomActionPointer->setChecked(true);
 
+        // Update the search engine menu action icon.
+        searchEngineMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("edit-find")));
+
         // Update the search engine menu action text.
         searchEngineMenuActionPointer->setText(i18nc("The main search engine menu action", "Search Engine - Custom"));
 
@@ -932,6 +1902,50 @@ void BrowserWindow::updateSearchEngineActions(const QString &searchEngine, const
     }
 }
 
+void BrowserWindow::updateUrlLineEdit(const QUrl &newUrl)
+{
+    // Get the new URL string in encoded form, which displays punycode.
+    QString newUrlString = newUrl.toEncoded();
+
+    // Update the view source actions.
+    if (newUrlString.startsWith(QLatin1String("view-source:")))  // The source is currently being viewed.
+    {
+        // Mark the view source checkbox.
+        viewSourceActionPointer->setChecked(true);
+
+        // Update the view in new tab action text.
+        viewSourceInNewTabActionPointer->setText(i18nc("View rendered website in new tab action", "View Rendered Website in New Tab"));
+    }
+    else  // The source is not currently being viewed.
+    {
+        // Unmark the view source checkbox.
+        viewSourceActionPointer->setChecked(false);
+
+        // Update the view in new tab action text.
+        viewSourceInNewTabActionPointer->setText(i18nc("View source in new tab action", "View Source in New Tab"));
+    }
+
+    // Update the URL line edit if it does not have focus.
+    if (!urlLineEditPointer->hasFocus())
+    {
+        // Update the URL line edit.
+        urlLineEditPointer->setText(newUrlString);
+
+        // Set the bookmarked action status.
+        bookmarkedActionPointer->setChecked(BookmarksDatabase::isBookmarked(newUrlString));
+
+        // Update the bookmarked action.
+        updateBookmarkedAction();
+
+        // Set the focus if the new URL is blank.
+        if (newUrlString == QStringLiteral(""))
+            urlLineEditPointer->setFocus();
+    }
+
+    // Store the current URL.
+    currentUrl = newUrl;
+}
+
 void BrowserWindow::updateUserAgentActions(const QString &userAgent, const bool &updateCustomUserAgentStatus)
 {
     // Initialize the custom user agent flag.
@@ -943,6 +1957,9 @@ void BrowserWindow::updateUserAgentActions(const QString &userAgent, const bool
         // Check the Privacy Browser user agent action.
         userAgentPrivacyBrowserActionPointer->setChecked(true);
 
+        // Update the user agent menu action icon.
+        userAgentMenuActionPointer->setIcon(QIcon(":/icons/privacy-mode.svg"));
+
         // Update the user agent menu action text.
         userAgentMenuActionPointer->setText(i18nc("The main user agent menu action", "User Agent - Privacy Browser"));
     }
@@ -951,6 +1968,9 @@ void BrowserWindow::updateUserAgentActions(const QString &userAgent, const bool
         // check the WebEngine default user agent action.
         userAgentWebEngineDefaultActionPointer->setChecked(true);
 
+        // Update the user agent menu action icon.
+        userAgentMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("qtlogo"), QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new")))));
+
         // Update the user agent menu action text.
         userAgentMenuActionPointer->setText(i18nc("The main user agent menu action", "User Agent - WebEngine default"));
     }
@@ -959,6 +1979,9 @@ void BrowserWindow::updateUserAgentActions(const QString &userAgent, const bool
         // Check the Firefox on Linux user agent action.
         userAgentFirefoxLinuxActionPointer->setChecked(true);
 
+        // Update the user agent menu action icon.
+        userAgentMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("firefox-esr"), QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new")))));
+
         // Update the user agent menu action text.
         userAgentMenuActionPointer->setText(i18nc("The main user agent menu action", "User Agent - Firefox on Linux"));
     }
@@ -967,6 +1990,9 @@ void BrowserWindow::updateUserAgentActions(const QString &userAgent, const bool
         // Check the Chromium on Linux user agent action.
         userAgentChromiumLinuxActionPointer->setChecked(true);
 
+        // Update the user agent menu action icon.
+        userAgentMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("chromium"), QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new")))));
+
         // Update the user agent menu action text.
         userAgentMenuActionPointer->setText(i18nc("The main user agent menu action", "User Agent - Chromium on Linux"));
     }
@@ -975,6 +2001,9 @@ void BrowserWindow::updateUserAgentActions(const QString &userAgent, const bool
         // Check the Firefox on Windows user agent action.
         userAgentFirefoxWindowsActionPointer->setChecked(true);
 
+        // Update the user agent menu action icon.
+        userAgentMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("firefox-esr"), QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new")))));
+
         // Update the user agent menu action text.
         userAgentMenuActionPointer->setText(i18nc("The main user agent menu action", "User Agent - Firefox on Windows"));
     }
@@ -983,6 +2012,9 @@ void BrowserWindow::updateUserAgentActions(const QString &userAgent, const bool
         // Check the Chrome on Windows user agent action.
         userAgentChromeWindowsActionPointer->setChecked(true);
 
+        // Update the user agent menu action icon.
+        userAgentMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("chromium"), QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new")))));
+
         // Update the user agent menu action text.
         userAgentMenuActionPointer->setText(i18nc("The main user agent menu action", "User Agent - Chrome on Windows"));
     }
@@ -991,6 +2023,9 @@ void BrowserWindow::updateUserAgentActions(const QString &userAgent, const bool
         // Check the Edge on Windows user agent action.
         userAgentEdgeWindowsActionPointer->setChecked(true);
 
+        // Update the user agent menu action icon.
+        userAgentMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new"))));
+
         // Update the user agent menu action text.
         userAgentMenuActionPointer->setText(i18nc("The main user agent menu action", "User Agent - Edge on Windows"));
     }
@@ -999,6 +2034,9 @@ void BrowserWindow::updateUserAgentActions(const QString &userAgent, const bool
         // Check the Safari on macOS user agent action.
         userAgentSafariMacosActionPointer->setChecked(true);
 
+        // Update the user agent menu action icon.
+        userAgentMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new"))));
+
         // Update the user agent menu action text.
         userAgentMenuActionPointer->setText(i18nc("The main user agent menu action", "User Agent - Safari on macOS"));
     }
@@ -1007,6 +2045,9 @@ void BrowserWindow::updateUserAgentActions(const QString &userAgent, const bool
         // Check the user agent.
         userAgentCustomActionPointer->setChecked(true);
 
+        // Update the user agent menu action icon.
+        userAgentMenuActionPointer->setIcon(QIcon::fromTheme(QLatin1String("user-group-properties"), QIcon::fromTheme(QLatin1String("contact-new"))));
+
         // Update the user agent menu action text.
         userAgentMenuActionPointer->setText(i18nc("The main user agent menu action", "User Agent - Custom"));
 
@@ -1018,6 +2059,7 @@ void BrowserWindow::updateUserAgentActions(const QString &userAgent, const bool
     }
 
     // Update the custom user agent enabled boolean.
+    // This is not done when the current user agent is a custom one but it is temporarially being changed via on-the-fly settings so that the user can switch back to the custom user agent.
     if (updateCustomUserAgentStatus)
         customUserAgentEnabled = customUserAgent;
 
@@ -1038,49 +2080,57 @@ void BrowserWindow::updateUserAgentActions(const QString &userAgent, const bool
     }
 }
 
-void BrowserWindow::updateZoomFactorAction(const double &zoomFactor)
+void BrowserWindow::updateViewBookmarksToolBarCheckbox(const bool visible)
 {
-    // Set the current zoom factor.
-    currentZoomFactor = zoomFactor;
+    // Update the view bookmarks toolbar checkbox.
+    viewBookmarksToolBarActionPointer->setChecked(visible);
+
+    // Initialize the bookmarks toolbar visibility tracker if Privacy Browser has just launched.
+    if (bookmarksToolBarUninitialized)
+    {
+        // Set the initialization flag.
+        bookmarksToolBarUninitialized = false;
 
-    // Update the zoom factor action text, formatting the double with 2 decimal places.
-    zoomFactorActionPointer->setText(ki18nc("@action", "Zoom Factor - %1").subs(zoomFactor, 0, '0', 2).toString());
+        // Store the current status of the bookmarks toolbar, which is used when exiting full screen browsing.
+        bookmarksToolBarIsVisible = visible;
+    }
 }
 
-void BrowserWindow::updateSearchEngineLabel(const QString &searchEngineString) const
+void BrowserWindow::updateWindowTitle(const QString &title)
 {
-    // Update the search engine label.
-    searchEngineLabelPointer->setText(SearchEngineHelper::getSearchUrl(searchEngineString));
+    // Update the window title.
+    setWindowTitle(title);
 }
 
-void BrowserWindow::updateUrlLineEdit(const QUrl &newUrl)
+void BrowserWindow::updateZoomActions(const double zoomFactorDouble)
 {
-    // Update the URL line edit if it does not have focus.
-    if (!urlLineEditPointer->hasFocus())
-    {
-        // Get the new URL string.
-        QString newUrlString = newUrl.toString();
+    // Set the current zoom factor.
+    currentZoomFactorDouble = zoomFactorDouble;
 
-        // Update the URL line edit.
-        urlLineEditPointer->setText(newUrlString);
+    // Set the status of the default zoom action.
+    zoomDefaultActionPointer->setEnabled(currentZoomFactorDouble != defaultZoomFactorDouble);
 
-        // Set the focus if the new URL is blank.
-        if (newUrlString == QStringLiteral(""))
-            urlLineEditPointer->setFocus();
-    }
+    // Set the status of the zoom in action and button.
+    zoomInActionPointer->setEnabled(currentZoomFactorDouble <= 4.99);
+    zoomPlusButtonPointer->setEnabled(currentZoomFactorDouble <= 4.99);
 
-    // Store the current URL.
-    currentUrl = newUrl;
-}
+    // Set the status of the zoom out action and button.
+    zoomMinusButtonPointer->setEnabled(currentZoomFactorDouble > 0.25);
+    zoomOutActionPointer->setEnabled(currentZoomFactorDouble > 0.25);
 
-void BrowserWindow::updateUserAgentLabel(const QString &userAgentDatabaseName) const
-{
-    // Update the user agent label.
-    userAgentLabelPointer->setText(UserAgentHelper::getUserAgentFromDatabaseName(userAgentDatabaseName));
+
+    // Update the zoom factor action text, formatting the double with 2 decimal places.  `0` specifies no extra field width.  `'0'` sets the format to not use scientific notation.
+    zoomFactorActionPointer->setText(ki18nc("The zoom factor action", "Zoom Factor - %1").subs(zoomFactorDouble, 0, '0', 2).toString());
+
+    // Update the status bar zoom factor label.
+    currentZoomButtonPointer->setText(ki18nc("The status bar zoom, which is just the formatted zoom factor", "%1").subs(zoomFactorDouble, 0, '0', 2).toString());
 }
 
-void BrowserWindow::updateWindowTitle(const QString &title)
+void BrowserWindow::zoomDefault()
 {
-    // Update the window title.
-    setWindowTitle(title);
+    // Set the new zoom factor.
+    tabWidgetPointer->applyOnTheFlyZoomFactor(defaultZoomFactorDouble);
+
+    // Update the on-the-fly action text.
+    updateZoomActions(defaultZoomFactorDouble);
 }
index 4c043d2e8f4717b13e4dba0193ba8e7225d3064c..85af5a2930d3bb6681bd22c9325a65f295b33048 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
 
 // Application headers.
 #include "widgets/TabWidget.h"
+#include "widgets/UrlLineEdit.h"
 
 // KDE Frameworks headers.
 #include <KConfigDialog>
 #include <KToggleFullScreenAction>
+#include <KToolBar>
 #include <KXmlGuiWindow>
 
 // Qt toolkit headers.
@@ -40,7 +42,7 @@ class BrowserWindow : public KXmlGuiWindow
 
 public:
     // The default constructor.
-    BrowserWindow(bool firstWindow=true);
+    BrowserWindow(bool firstWindow=true, QString *initialUrlStringPointer = nullptr);
 
     // The public functions.
     QSize sizeHint() const override;
@@ -48,33 +50,50 @@ public:
     // The public variables.
     TabWidget *tabWidgetPointer;
 
+public Q_SLOTS:
+    // The public slots.
+    void populateBookmarksInThisWindow();  // This is public so that the bookmarks can be repopulated over D-Bus when changed by another instance of Privacy Browser.
+
 private Q_SLOTS:
     // The private slots.
-    void addOrEditDomainSettings() const;
+    void addOrEditDomainSettings();
     void back() const;
     void clearUrlLineEditFocus() const;
+    void decrementZoom();
+    void editBookmarks();
     void escape() const;
-    void fileNew() const;
     void findNext() const;
     void findPrevious() const;
-    void focusFindLineEdit() const;
     void forward() const;
     void fullScreenRequested(const bool toggleOn);
     void getZoomFactorFromUser();
+    void hideFindTextActions() const;
+    void hideProgressBar() const;
     void home() const;
+    void incrementZoom();
     void loadUrlFromLineEdit(const QString &url) const;
+    void newWindow() const;
+    void populateBookmarksInAllWindows() const;
     void refresh() const;
+    void reloadAndBypassCache() const;
+    void showBookmarkContextMenu(const QPoint &point);
     void showCookiesDialog();
-    void showDownloadLocationBrowseDialog() const;
-    void showDomainSettingsDialog() const;
+    void showDomainSettingsDialog();
+    void showFindTextActions() const;
     void showProgressBar(const int &progress) const;
     void showSettingsDialog();
+    void toggleBookmark();
+    void toggleDeveloperTools() const;
     void toggleDomStorage() const;
     void toggleFindCaseSensitive() const;
     void toggleJavaScript() const;
     void toggleLocalStorage() const;
     void toggleFullScreen();
+    void toggleViewBookmarksToolBar();
+    void toggleViewSource() const;
+    void toggleViewSourceInNewTab() const;
     void updateCookiesAction(const int numberOfCookies) const;
+    void updateDefaultZoomFactor(const double newDefaultZoomFactorDouble);
     void updateDomStorageAction(const bool &isEnabled) const;
     void updateDomainSettingsIndicator(const bool status);
     void updateFindText(const QString &text, const bool findCaseSensitive) const;
@@ -83,26 +102,44 @@ private Q_SLOTS:
     void updateLocalStorageAction(const bool &isEnabled);
     void updateSearchEngineActions(const QString &searchEngine, const bool &updateCustomSearchEngineStatus);
     void updateUserAgentActions(const QString &userAgent, const bool &updateCustomUserAgentStatus);
-    void updateZoomFactorAction(const double &zoomFactor);
-    void updateSearchEngineLabel(const QString &searchEngineString) const;
+    void updateZoomActions(const double zoomFactorDouble);
     void updateUrlLineEdit(const QUrl &newUrl);
-    void updateUserAgentLabel(const QString &userAgentDatabaseName) const;
+    void updateViewBookmarksToolBarCheckbox(const bool visible);
     void updateWindowTitle(const QString &title);
+    void zoomDefault();
 
 private:
     // The private variables.
-    KConfigDialog *configDialogPointer;
+    KActionCollection *actionCollectionPointer;
+    QAction *bookmarkedActionPointer;
+    QList<QPair<QMenu *, QAction *> *> bookmarkFolderFinalActionList;
+    QList<QPair<QMenu *, QAction *> *> bookmarksMenuActionList;
+    QMenu *bookmarksMenuPointer;
+    QList<QPair<QMenu *, QMenu *> *> bookmarksMenuSubmenuList;
+    QList<QAction*> bookmarksToolBarActionList;
+    QList<QPair<QMenu *, const double> *> bookmarksToolBarMenuList;
+    KToolBar *bookmarksToolBarPointer;
+    QList<QPair<QMenu *, QAction *> *> bookmarksToolBarSubfolderActionList;
+    bool bookmarksToolBarIsVisible = false;
+    bool bookmarksToolBarUninitialized = true;
     QAction *cookiesActionPointer;
     QUrl currentUrl;
-    double currentZoomFactor;
+    QPushButton *currentZoomButtonPointer;
+    double currentZoomFactorDouble;
     bool customSearchEngineEnabled;
     bool customUserAgentEnabled;
+    double defaultZoomFactorDouble;
+    QAction *developerToolsActionPointer;
     QAction *domStorageActionPointer;
-    QComboBox *downloadLocationComboBoxPointer;
     QAction *findCaseSensitiveActionPointer;
+    QAction *findNextActionPointer;
+    QAction *findPreviousActionPointer;
+    QAction *findTextLabelActionPointer;
     QLabel *findTextLabelPointer;
+    QAction *findTextLineEditActionPointer;
     KLineEdit *findTextLineEditPointer;
     KToggleFullScreenAction *fullScreenActionPointer;
+    QAction *hideFindTextActionPointer;
     QAction *javaScriptActionPointer;
     bool javaScriptEnabled;
     QAction *localStorageActionPointer;
@@ -112,7 +149,7 @@ private:
     QPalette normalBackgroundPalette;
     QPalette positiveBackgroundPalette;
     QProgressBar *progressBarPointer;
-    QLabel *searchEngineLabelPointer;
+    QAction *refreshActionPointer;
     QAction *searchEngineMenuActionPointer;
     QAction *searchEngineMojeekActionPointer;
     QAction *searchEngineMonoclesActionPointer;
@@ -121,7 +158,7 @@ private:
     QAction *searchEngineBingActionPointer;
     QAction *searchEngineYahooActionPointer;
     QAction *searchEngineCustomActionPointer;
-    QLabel *userAgentLabelPointer;
+    QAction *stopActionPointer;
     QAction *userAgentMenuActionPointer;
     QAction *userAgentPrivacyBrowserActionPointer;
     QAction *userAgentWebEngineDefaultActionPointer;
@@ -132,8 +169,24 @@ private:
     QAction *userAgentEdgeWindowsActionPointer;
     QAction *userAgentSafariMacosActionPointer;
     QAction *userAgentCustomActionPointer;
-    KLineEdit *urlLineEditPointer;
+    UrlLineEdit *urlLineEditPointer;
     KToolBar *urlToolBarPointer;
+    QAction *viewBookmarksToolBarActionPointer;
+    QAction *viewSourceActionPointer;
+    QAction *viewSourceInNewTabActionPointer;
+    QAction *zoomDefaultActionPointer;
     QAction *zoomFactorActionPointer;
+    QAction *zoomInActionPointer;
+    QPushButton *zoomMinusButtonPointer;
+    QAction *zoomOutActionPointer;
+    QPushButton *zoomPlusButtonPointer;
+
+    // The private functions.
+    void addBookmarkFolderFinalActions(QMenu *menuPointer, const double folderId, const bool addToList);
+    int calculateSettingsInt(const bool settingCurrentlyEnabled, const bool settingEnabledByDefault) const;
+    void populateBookmarksMenuSubfolders(const double folderId, QMenu *menuPointer);
+    void populateBookmarksToolBar();
+    void populateBookmarksToolBarSubfolders(const double folderId, QMenu *menuPointer);
+    void updateBookmarkedAction() const;
 };
 #endif
index 5fd76c49af06e2f8fde8138c8fab3cf280daeb8f..5bf1d72a540ed82435d84b885746e66b99e0fffe 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+# Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 #
 # This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 #
@@ -17,6 +17,6 @@
 
 
 # List the sources to include in the executable.
-target_sources(privacy-browser PRIVATE
+target_sources(privacybrowser PRIVATE
     BrowserWindow.cpp
 )