]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blobdiff - src/dialogs/CookiesDialog.cpp
Add a default folder icon to the edit folder dialog. https://redmine.stoutner.com...
[PrivacyBrowserPC.git] / src / dialogs / CookiesDialog.cpp
index 972bc67d1d49b132a9fb4634f9631ff22804012c..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>.
  *
  */
 
 // Application headers.
-#include "AddCookieDialog.h"
+#include "AddOrEditCookieDialog.h"
 #include "CookiesDialog.h"
-#include "ui_CookieDisplayWidget.h"
+#include "DurableCookiesDialog.h"
 #include "ui_CookiesDialog.h"
+#include "databases/CookiesDatabase.h"
 
 // KDE Frameworks headers.
 #include <KLocalizedString>
 // Qt toolkit headers.
 #include <QDateTime>
 #include <QMessageBox>
+#include <QShortcut>
 #include <QUrl>
 
-CookiesDialog::CookiesDialog(QList<QNetworkCookie> *originalCookieListPointer) : QDialog(nullptr), cookieListPointer(originalCookieListPointer)
+// Define the cookie sort predicate.
+bool cookieSortPredicate(const QNetworkCookie &leftHandCookie, const QNetworkCookie &rightHandCookie)
+{
+    // Check to see if the domains are identical.
+    if (leftHandCookie.domain() == rightHandCookie.domain())
+    {
+        // Check to see if the names are identical.
+        if (leftHandCookie.name() == rightHandCookie.name())
+        {
+            // Sort the cookies by the path.
+            return (leftHandCookie.path() < rightHandCookie.path());
+        }
+        else  // The name are not identical.
+        {
+            // Sort the cookies by the name.
+            return (leftHandCookie.name() < rightHandCookie.name());
+        }
+    }
+    else  // The domains are not identical.
+    {
+        // Get copies of the domains.
+        QString leftHandDomain = leftHandCookie.domain();
+        QString rightHandDomain = rightHandCookie.domain();
+
+        // Create the strings.
+        QString leftHandTopLevelDomain;
+        QString rightHandTopLevelDomain;
+        QString leftHandSecondLevelDomain;
+        QString rightHandSecondLevelDomain;
+        QString leftHandThirdLevelDomain;
+        QString rightHandThirdLevelDomain;
+
+        // Get the number of dots in the strings.
+        int leftHandDots = leftHandDomain.count(QLatin1Char('.'));
+        int rightHandDots = rightHandDomain.count(QLatin1Char('.'));
+
+        // Get the top level domains.
+        leftHandTopLevelDomain = leftHandDomain.section('.', -1);
+        rightHandTopLevelDomain = rightHandDomain.section('.', -1);
+
+        // Get the second level domains if they contain at least one dot.
+        if (leftHandDots >= 1)
+            leftHandSecondLevelDomain = leftHandDomain.section('.', -2);
+        if (rightHandDots >= 1)
+            rightHandSecondLevelDomain = rightHandDomain.section('.', -2);
+
+        // Get the third level domains if they contain at least two dots.
+        if (leftHandDots >= 2)
+            leftHandThirdLevelDomain = leftHandDomain.section('.', -3);
+        if (rightHandDots >= 2)
+            rightHandThirdLevelDomain = rightHandDomain.section('.', -3);
+
+        // Check to see if the top level domains are the same.  Segments, like third level domains, that don't exist will be blank, which will cause the sorting to group similar domains.
+        if (leftHandTopLevelDomain == rightHandTopLevelDomain)
+        {
+            // Check to see if the second level domains are the same.
+            if (leftHandSecondLevelDomain == rightHandSecondLevelDomain)
+            {
+                // Check to see if the third level domains are the same.
+                if (leftHandThirdLevelDomain == rightHandThirdLevelDomain)
+                {
+                    // Sort the cookies by the full domain because they share the same third level domain.
+                    return (leftHandDomain < rightHandDomain);
+                }
+                else  // The second level domains are the same, but the third level domains are different.
+                {
+                    // Sort the cookies by the third level domains.
+                    return (leftHandThirdLevelDomain < rightHandThirdLevelDomain);
+                }
+            }
+            else  // The top level domains are the same, but the second level domains are diferent.
+            {
+                // Sort the cookies by the second level domain.
+                return (leftHandSecondLevelDomain < rightHandSecondLevelDomain);
+            }
+        }
+        else  // The top level domains are different.
+        {
+            // Sort the cookies by the top level domain.
+            return (leftHandTopLevelDomain < rightHandTopLevelDomain);
+        }
+    }
+}
+
+// Construct the class.
+CookiesDialog::CookiesDialog(std::list<QNetworkCookie> *originalCookieListPointer) : QDialog(nullptr), cookieListPointer(originalCookieListPointer)
 {
     // Set the dialog window title.
     setWindowTitle(i18nc("The cookies dialog window title", "Cookies"));
@@ -45,120 +132,478 @@ CookiesDialog::CookiesDialog(QList<QNetworkCookie> *originalCookieListPointer) :
     // Setup the UI.
     cookiesDialogUi.setupUi(this);
 
-    // Get a handle for the scroll area.
-    QScrollArea *scrollAreaPointer = cookiesDialogUi.scrollArea;
+    // Get a handle for the tree view.
+    treeViewPointer = cookiesDialogUi.treeView;
+
+    // Initialize the tree model.
+    treeModelPointer = new QStandardItemModel();
+
+    // Set the column count.
+    treeModelPointer->setColumnCount(7);
+
+    // Set the tree header data.
+    treeModelPointer->setHeaderData(0, Qt::Horizontal, i18nc("The cookie Name header.", "Name"));
+    treeModelPointer->setHeaderData(1, Qt::Horizontal, i18nc("The cookie Durable header.", "Durable"));
+    treeModelPointer->setHeaderData(2, Qt::Horizontal, i18nc("The cookie Path header.", "Path"));
+    treeModelPointer->setHeaderData(3, Qt::Horizontal, i18nc("The cookie Expiration Date header.", "Expiration Date"));
+    treeModelPointer->setHeaderData(4, Qt::Horizontal, i18nc("The cookie HTTP Only header.", "HTTP Only"));
+    treeModelPointer->setHeaderData(5, Qt::Horizontal, i18nc("The cookie Secure header.", "Secure"));
+    treeModelPointer->setHeaderData(6, Qt::Horizontal, i18nc("The cookie Value header.", "Value"));
+
+    // Set the tree header tool tips.
+    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 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."));
+    treeModelPointer->horizontalHeaderItem(4)->setToolTip(i18nc("The cookie HTTP Only tool tip.",
+                                                                        "Restrict cookie access to HTTP (and HTTPS). This prevents JavaScript from accessing the cookie, which hardens it against cross-site scripting attacks."));
+    treeModelPointer->horizontalHeaderItem(5)->setToolTip(i18nc("The cookie Secure tool tip.", "Only allow the cookie to be transferred across HTTPS (as opposed to HTTP)."));
+    treeModelPointer->horizontalHeaderItem(6)->setToolTip(i18nc("The cookie Value tool tip.", "The value contains the cookie data."));
+
+    // Sort the cookie list.
+    cookieListPointer->sort(cookieSortPredicate);
+
+    // Create the current domain string.
+    QString currentDomainString = "";
+
+    // Create the current domain item pointer.
+    QStandardItem *currentDomainItemPointer;
+
+    // Populate the cookie tree view.
+    for (QNetworkCookie cookie : *cookieListPointer)
+    {
+        // Get the cookie domain.
+        QString cookieDomain = cookie.domain();
+
+        // Check to see if the cookie is a member of the current domain.
+        if (cookieDomain != currentDomainString)  // Create a new domain in the tree.
+        {
+            // Create the domain name item.
+            QStandardItem *domainNameItemPointer = new QStandardItem(cookieDomain);
+
+            // Add the domain to the tree.
+            treeModelPointer->invisibleRootItem()->appendRow(domainNameItemPointer);
 
-    // Create the scroll area widget.
-    QWidget *scrollAreaWidgetPointer = new QWidget();
+            // Update the current domain string.
+            currentDomainString = cookieDomain;
 
-    // Set the scroll area widget.
-    scrollAreaPointer->setWidget(scrollAreaWidgetPointer);
+            // Update the current domain item pointer.
+            currentDomainItemPointer = domainNameItemPointer;
+        }
 
-    // Create a scroll area VBox layout.
-    QVBoxLayout *scrollAreaVBoxLayoutPointer = new QVBoxLayout();
+        // Check to see if the cookie is durable.
+        bool isDurable = CookiesDatabase::isDurable(cookie);
+
+        // Create a list for the cookie items.
+        QList<QStandardItem*> cookieItemList;
+
+        // Create the cookie items.
+        QStandardItem *nameItemPointer = new QStandardItem(QString(cookie.name()));
+        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.
+        cookieItemList.append(nameItemPointer);
+        cookieItemList.append(durableItemPointer);
+        cookieItemList.append(pathItemPointer);
+        cookieItemList.append(expirationDateItemPointer);
+        cookieItemList.append(isHttpOnlyItemPointer);
+        cookieItemList.append(isSecureItemPointer);
+        cookieItemList.append(valueItemPointer);
+
+        // Add the cookie to the tree.
+        currentDomainItemPointer->appendRow(cookieItemList);
+    }
 
-    // Set the scroll area widget layout.
-    scrollAreaWidgetPointer->setLayout(scrollAreaVBoxLayoutPointer);
+    // Auto resize the headers.
+    treeViewPointer->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
 
-    // Create the cookies VBox layout.
-    cookiesVBoxLayoutPointer = new QVBoxLayout();
+    // Disable stretching the last section.  Otherwise, the Value field will be truncated to the width of the window when a row is expanded.
+    treeViewPointer->header()->setStretchLastSection(false);
 
-    // Populate the scroll area VBox layout.  The stretch prevents the cookies from expanding vertically if they are smaller than the dialog.
-    scrollAreaVBoxLayoutPointer->addLayout(cookiesVBoxLayoutPointer);
-    scrollAreaVBoxLayoutPointer->addStretch();
+    // Don't elide the Value field (or any other field).
+    treeViewPointer->setTextElideMode(Qt::ElideNone);
 
-    // Populate the VBoxLayout.
-    for (QNetworkCookie cookie : *cookieListPointer)
-    {
-        // Add the cookie to the layout.
-        addCookieToLayout(cookie);
-    }
+    // Indicate that all the rows are the same height, which improves performance.
+    treeViewPointer->setUniformRowHeights(true);
+
+    // Disable editing in the tree view.
+    treeViewPointer->setEditTriggers(QAbstractItemView::NoEditTriggers);
+
+    // Set the tree model.
+    treeViewPointer->setModel(treeModelPointer);
+
+    // Get a handle for the tree selection model.
+    treeSelectionModelPointer = treeViewPointer->selectionModel();
+
+    // Listen for selection changes.
+    connect(treeSelectionModelPointer, SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(updateUi()));
 
     // Get handles for the buttons.
     addCookieButtonPointer = cookiesDialogUi.addCookieButton;
+    editCookieButtonPointer = cookiesDialogUi.editCookieButton;
+    deleteCookieButtonPointer = cookiesDialogUi.deleteCookieButton;
+    deleteAllButtonPointer = cookiesDialogUi.deleteAllCookiesButton;
     QDialogButtonBox *dialogButtonBoxPointer = cookiesDialogUi.dialogButtonBox;
-    QPushButton *cancelButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::Close);
+    QPushButton *closeButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::Close);
 
-    // Add a delete all button to the dialog button box.
-    deleteAllButtonPointer = dialogButtonBoxPointer->addButton(i18nc("Delete all cookies button", "Delete all"), QDialogButtonBox::ActionRole);
+    // Add buttons to the dialog button box.
+    durableCookiesButtonPointer = dialogButtonBoxPointer->addButton(i18nc("View the durable cookies button", "Durable cookies - %1", CookiesDatabase::cookieCount()),
+                                                                    QDialogButtonBox::ActionRole);
 
-    // Set the delete all button icon.
-    deleteAllButtonPointer->setIcon(QIcon::fromTheme("delete"));
+    // Set the button icons.
+    durableCookiesButtonPointer->setIcon(QIcon::fromTheme("view-visible", QIcon::fromTheme(QLatin1String("appointment-new"))));
 
     // Connect the buttons.
-    connect(addCookieButtonPointer, SIGNAL(clicked()), this, SLOT(showAddCookieMessageBox()));
+    connect(addCookieButtonPointer, SIGNAL(clicked()), this, SLOT(showAddCookieDialog()));
+    connect(editCookieButtonPointer, SIGNAL(clicked()), this, SLOT(showEditCookieDialog()));
+    connect(deleteCookieButtonPointer, SIGNAL(clicked()), this, SLOT(showDeleteCookieMessageBox()));
+    connect(durableCookiesButtonPointer, SIGNAL(clicked()), this, SLOT(showDurableCookiesDialog()));
     connect(deleteAllButtonPointer, SIGNAL(clicked()), this, SLOT(showDeleteAllMessageBox()));
     connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(reject()));
 
-    // Set the cancel button to be the default.
-    cancelButtonPointer->setDefault(true);
+    // Set the close button to be the default.
+    closeButtonPointer->setDefault(true);
+
+    // Create the keyboard shortcuts.
+    QShortcut *aShortcutPointer = new QShortcut(QKeySequence(i18nc("The add cookie key shortcut.", "a")), this);
+    QShortcut *eShortcutPointer = new QShortcut(QKeySequence(i18nc("The edit cookie key shortcut.", "e")), this);
+    QShortcut *dShortcutPointer = new QShortcut(QKeySequence(i18nc("The delete cookie key shortcut.", "d")), this);
+    QShortcut *deleteShortcutPointer = new QShortcut(QKeySequence::Delete, this);
+    QShortcut *lShortcutPointer = new QShortcut(QKeySequence(i18nc("The delete all key shortcut.", "l")), this);
+    QShortcut *cShortcutPointer = new QShortcut(QKeySequence(i18nc("The close key shortcut.", "c")), this);
+    QShortcut *quitShortcutPointer = new QShortcut(QKeySequence::Quit, this);
+
+    // Connect the keyboard shortcuts to the buttons.
+    connect(aShortcutPointer, SIGNAL(activated()), addCookieButtonPointer, SLOT(click()));
+    connect(eShortcutPointer, SIGNAL(activated()), editCookieButtonPointer, SLOT(click()));
+    connect(dShortcutPointer, SIGNAL(activated()), deleteCookieButtonPointer, SLOT(click()));
+    connect(deleteShortcutPointer, SIGNAL(activated()), deleteCookieButtonPointer, SLOT(click()));
+    connect(lShortcutPointer, SIGNAL(activated()), deleteAllButtonPointer, SLOT(click()));
+    connect(cShortcutPointer, SIGNAL(activated()), closeButtonPointer, SLOT(click()));
+    connect(quitShortcutPointer, SIGNAL(activated()), closeButtonPointer, SLOT(click()));
+
+    // Edit a cookie when it is double clicked.
+    connect(treeViewPointer, SIGNAL(doubleClicked(QModelIndex)), editCookieButtonPointer, SLOT(click()));
 
     // Update the UI.
     updateUi();
 };
 
-void CookiesDialog::addCookieFromDialog(const QNetworkCookie &cookie) const
+void CookiesDialog::addCookieFromDialog(const QNetworkCookie &cookie, const bool &isDurable) const
 {
     // Add the cookie to the cookie list and the cookie store.
     emit addCookie(cookie);
 
-    // Add the cookie to the VBox layout.
-    addCookieToLayout(cookie);
+    // Get the new domain string.
+    QString newDomain = cookie.domain();
+
+    // Check to see if the domain already exists in the model.
+    QList<QStandardItem*> currentDomainItemList = treeModelPointer->findItems(newDomain);
+
+    // Create a domain item pointer.
+    QStandardItem *domainNameItemPointer;
+
+    // Prepare the domain item pointer.
+    if (currentDomainItemList.isEmpty())  // The domain doesn't currently exist in the tree.
+    {
+        // Create the domain name item.
+        domainNameItemPointer = new QStandardItem(newDomain);
+
+        // Get the number of domains in the tree.
+        int numberOfDomains = treeModelPointer->invisibleRootItem()->rowCount();
+
+        // Create the insert domain row number and initially set it to be at the end of the list.
+        int insertDomainRowNumber = numberOfDomains;
+
+        // Create the new domain strings.
+        QString newDomainTopLevelDomain;
+        QString newDomainSecondLevelDomain;
+        QString newDomainThirdLevelDomain;
+
+        // Get the number of dots in the new domain string.
+        int newDomainDots = newDomain.count(QLatin1Char('.'));
+
+        // Get the new top level domain.
+        newDomainTopLevelDomain = newDomain.section('.', -1);
+
+        // Get the new second level domain if it contains at least one dot.
+        if (newDomainDots >= 1)
+            newDomainSecondLevelDomain = newDomain.section('.', -2);
+
+        // Get the new third level domain if it contains at least two dots.
+        if (newDomainDots >= 2)
+            newDomainThirdLevelDomain = newDomain.section('.', -3);
+
+        // Create while loop trackers.
+        bool locationFound = false;
+        int currentRow = 0;
+
+        // Check to see if the new domain should be inserted after an existing domain.
+        while (!locationFound && (currentRow < numberOfDomains)) {
+            // Get the current domain string.
+            QString currentDomain = treeModelPointer->invisibleRootItem()->child(currentRow, 0)->index().data().toString();
+
+            // Create the current domain strings.
+            QString currentDomainTopLevelDomain;
+            QString currentDomainSecondLevelDomain;
+            QString currentDomainThirdLevelDomain;
+
+            // Get the number of dots in the current domain string.
+            int currentDomainDots = currentDomain.count(QLatin1Char('.'));
+
+            // Get the current top level domain.
+            currentDomainTopLevelDomain = currentDomain.section('.', -1);
+
+            // Get the current second level domain if it contains at least one dot.
+            if (currentDomainDots >= 1)
+                currentDomainSecondLevelDomain = currentDomain.section('.', -2);
+
+            // Get the current third level domain if it contains at least two dots.
+            if (currentDomainDots >= 2)
+                currentDomainThirdLevelDomain = currentDomain.section('.', -3);
+
+            // Check to see if the new domain should be inserted after the current domain.
+            // Segments, like third level domains, that do not exist will be blank, which will cause the sorting to group similar domains.
+            if ((newDomainTopLevelDomain < currentDomainTopLevelDomain) ||  // The new top level domain `.com` is less than the current top level domain `.org`.
+                ((newDomainTopLevelDomain == currentDomainTopLevelDomain) && ((newDomainSecondLevelDomain < currentDomainSecondLevelDomain) ||  // `jessica.com` is less than `stoutner.com`.
+                ((newDomainSecondLevelDomain == currentDomainSecondLevelDomain) && ((newDomainThirdLevelDomain < currentDomainThirdLevelDomain) ||  // `apache.stoutner.com` < `www.stoutner.com`.
+                ((newDomainThirdLevelDomain == currentDomainThirdLevelDomain) && (newDomain < currentDomain)))))))  // `first.www.stoutner.com` < `second.www.stoutner.com`.
+            {
+                // Insert the domain at the current row (because the rows are 0 based, this will insert it before the first domain where all the above checks fail).
+                insertDomainRowNumber = currentRow;
+
+                // Mark the location as found.
+                locationFound = true;
+            }
+
+            // Move to the next row.
+            ++currentRow;
+        }
+
+        // Add the domain to the tree.
+        treeModelPointer->invisibleRootItem()->insertRow(insertDomainRowNumber, domainNameItemPointer);
+    }
+    else  // The domain already exists in the tree.
+    {
+        // Use the current domain standard item.
+        domainNameItemPointer = currentDomainItemList[0];
+    }
+
+    // Get strings for the new cookie name and path (used later in the placement of the row).
+    QString newCookieName = QString(cookie.name());
+    QString newCookiePath = QString(cookie.path());
+
+    // Create a cookie item list.
+    QList<QStandardItem*> cookieItemList;
+
+    // Create the cookie items.
+    QStandardItem *nameItemPointer = new QStandardItem(newCookieName);
+    QStandardItem *durableItemPointer = new QStandardItem(QString(isDurable ? i18n("yes") : i18n("no")));
+    QStandardItem *pathItemPointer = new QStandardItem(newCookiePath);
+    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 *valueItemPointer = new QStandardItem(QString(cookie.value()));
+
+    // Populate the cookie item list.
+    cookieItemList.append(nameItemPointer);
+    cookieItemList.append(durableItemPointer);
+    cookieItemList.append(pathItemPointer);
+    cookieItemList.append(expirationDateItemPointer);
+    cookieItemList.append(isHttpOnlyItemPointer);
+    cookieItemList.append(isSecureItemPointer);
+    cookieItemList.append(valueItemPointer);
+
+    // Get the number of cookies in the domain.
+    int numberOfCookies = domainNameItemPointer->rowCount();
+
+    // Create the insert cookie row number and initially set it to be at the end of the list.
+    int insertCookieRowNumber = numberOfCookies;
+
+    // Create the trackers.
+    bool removeExistingRow = false;
+    bool rowFound = false;
+    int currentRow = 0;
+
+    while (!rowFound && currentRow < numberOfCookies)
+    {
+        // Get the current cookie name and path at the indicated row.
+        QString currentCookieName = domainNameItemPointer->child(currentRow, 0)->index().data().toString();
+        QString currentCookiePath = domainNameItemPointer->child(currentRow, 2)->index().data().toString();
+
+        // Check to see if the new cookie should be inserted after the current cookie.
+        if ((newCookieName < currentCookieName) ||  // The new cookie name comes before the current cookie name.
+            ((newCookieName == currentCookieName) && ((newCookiePath < currentCookiePath) ||  // The names are the same, but the new cookie path comes before the current cookie path.
+            (newCookiePath == currentCookiePath))))  // The core attributes of the cookies are the same.
+        {
+            // Remove the existing cookie if the core attributes are the same.
+            if ((newCookieName == currentCookieName) && (newCookiePath == currentCookiePath))
+                removeExistingRow = true;
+
+            // Insert the new cookie at this row.
+            insertCookieRowNumber = currentRow;
+
+            // Mark the row as found.
+            rowFound = true;
+        }
+
+        // Move to the next row.
+        ++currentRow;
+    }
+
+    // Remove the existing row if it is being edited.
+    if (removeExistingRow)
+        domainNameItemPointer->removeRow(insertCookieRowNumber);
+
+    // Add the cookie to the tree model.
+    domainNameItemPointer->insertRow(insertCookieRowNumber, cookieItemList);
+
+    // Get the new cookie model index.
+    QModelIndex newCookieIndex = nameItemPointer->index();
+
+    // Set the new cookie to be the current index.
+    treeViewPointer->setCurrentIndex(newCookieIndex);
+
+    // Expand the parent of the new cookie.
+    treeViewPointer->expand(newCookieIndex.parent());
 }
 
-void CookiesDialog::addCookieToLayout(const QNetworkCookie &cookie) const
+void CookiesDialog::deleteCookie(const QModelIndex &modelIndex, const bool &deleteDurableCookies) const
 {
-    // Create a cookie display widget.
-    QWidget *cookieDisplayWidgetPointer = new QWidget();
+    // Create a partial cookie.
+    QNetworkCookie partialCookie;
 
-    // Instantiate the cookie widget dialog UI.
-    Ui::CookieDisplayWidget cookieDisplayWidgetUi;
+    // Populate the partial cookie from the current model index.
+    partialCookie.setDomain(modelIndex.parent().siblingAtColumn(0).data().toString());
+    partialCookie.setName(modelIndex.siblingAtColumn(0).data().toString().toUtf8());
+    partialCookie.setPath(modelIndex.siblingAtColumn(2).data().toString());
 
-    // Setup the UI.
-    cookieDisplayWidgetUi.setupUi(cookieDisplayWidgetPointer);
-
-    // Get handles for the views.
-    QLabel *domainLabelPointer = cookieDisplayWidgetUi.domainLabel;
-    QLabel *nameLabelPointer = cookieDisplayWidgetUi.nameLabel;
-    QLabel *expirationDateLabelPointer = cookieDisplayWidgetUi.expirationDateLabel;
-    QLabel *pathLabelPointer = cookieDisplayWidgetUi.pathLabel;
-    QCheckBox *httpOnlyCheckBoxPointer = cookieDisplayWidgetUi.httpOnlyCheckBox;
-    QCheckBox *secureCheckBoxPointer = cookieDisplayWidgetUi.secureCheckBox;
-    QLabel *valueLabelPointer = cookieDisplayWidgetUi.valueLabel;
-
-    // Populate the views.
-    domainLabelPointer->setText("<font size=\"+1\"><b>" + cookie.domain() + "</b></font>");
-    nameLabelPointer->setText("<font size=\"+1\"><b>" + cookie.name() + "</b></font>");
-    expirationDateLabelPointer->setText("<b>" + cookie.expirationDate().toString() + "</b>");
-    pathLabelPointer->setText("<b>" + cookie.path() + "</b>");
-    httpOnlyCheckBoxPointer->setChecked(cookie.isHttpOnly());
-    secureCheckBoxPointer->setChecked(cookie.isSecure());
-    valueLabelPointer->setText("<b>" + cookie.value() + "</b>");
-
-    // Add the cookie display widget to the cookies VBox layout.
-    cookiesVBoxLayoutPointer->addWidget(cookieDisplayWidgetPointer);
-
-    // Create a line.
-    QFrame *lineFrame = new QFrame();
-
-    // Format the line.
-    lineFrame->setFrameShape(QFrame::HLine);
-    lineFrame->setFrameShadow(QFrame::Sunken);
-
-    // Add the line to the cookies VBox layout.
-    cookiesVBoxLayoutPointer->addWidget(lineFrame);
+    // Create a cookie to delete.
+    QNetworkCookie cookieToDelete;
+
+    // Check if the cookie is durable.
+    bool isDurable = CookiesDatabase::isDurable(partialCookie);
+
+    // Only delete durable cookies if directed.
+    if (deleteDurableCookies || !isDurable)
+    {
+        // Search for the partial cookie in the cookie list.
+        for (QNetworkCookie cookie : *cookieListPointer)
+        {
+            // Store the cookie to delete if it has the same identifier as the partial cookie.
+            if (cookie.hasSameIdentifier(partialCookie))
+                cookieToDelete = cookie;
+        }
+
+        // Remove the cookie from the tree model.
+        treeModelPointer->removeRow(modelIndex.row(), modelIndex.parent());
+
+        // Delete the cookie from the cookie list and cookie store.
+        emit deleteCookie(cookieToDelete);
+
+        // Delete the cookie from the durable cookies database.
+        if (isDurable)
+            CookiesDatabase::deleteCookie(cookieToDelete);
+    }
+}
+
+void CookiesDialog::deleteDomain(const QModelIndex &modelIndex, const bool &deleteDurableCookies) const
+{
+    // Get the parent index.
+    QModelIndex parentIndex = modelIndex.parent();
+
+    // Get the number of cookies in the domain.
+    int numberOfCookies = treeModelPointer->rowCount(modelIndex);
+
+    // Delete each child cookie, starting from the bottom.
+    for (int i = numberOfCookies; i > 0; --i)
+        deleteCookie(treeModelPointer->index(i-1, 0, modelIndex), deleteDurableCookies);
+
+    // Remove the domain if all the cookies have been deleted.
+    if (treeModelPointer->rowCount(modelIndex) == 0)
+        treeModelPointer->removeRow(modelIndex.row(), parentIndex);
+}
+
+void CookiesDialog::deleteCookieFromDatabase(const QNetworkCookie &cookie) const
+{
+    // Get a list of the matching domains.  There should only be one item in this list
+    QList<QStandardItem *> domainList = treeModelPointer->findItems(cookie.domain());
+
+    // Find any matching cookies.
+    for (QStandardItem *domainItemPointer : domainList)
+    {
+        // Get the number of cookies in the domain.
+        int numberOfCookies = domainItemPointer->rowCount();
+
+        // Initialize the tracking variables.
+        bool cookieFound = false;
+        int currentRow = 0;
+
+        // Find the cookie in the tree model.
+        while (!cookieFound && (currentRow < numberOfCookies))
+        {
+            // Get the name item.
+            QStandardItem *nameItemPointer = domainItemPointer->child(currentRow);
+
+            // Get the name model index.
+            QModelIndex nameModelIndex = nameItemPointer->index();
+
+            // Check to see if the name and the path match.
+            if ((nameModelIndex.data().toString() == cookie.name()) && (nameModelIndex.siblingAtColumn(2).data().toString() == cookie.path()))
+            {
+                // Set the current index.
+                treeSelectionModelPointer->setCurrentIndex(nameModelIndex, QItemSelectionModel::ClearAndSelect);
+
+                // Delete the cookie.
+                deleteCookieFromDialog(cookie);
+
+                // Mark the cookie as found.
+                cookieFound = true;
+            }
+
+            // Move to the next row.
+            ++currentRow;
+        }
+    }
+}
+
+void CookiesDialog::deleteCookieFromDialog(const QNetworkCookie &cookie) const
+{
+    // Get the current model index.
+    QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
+
+    // Get the parent index.
+    QModelIndex parentIndex = currentIndex.parent();
+
+    // Remove the cookie from the tree model.
+    treeModelPointer->removeRow(currentIndex.row(), parentIndex);
+
+    // Remove the domain from the tree model if its only cookie has been deleted.
+    if (treeModelPointer->rowCount(parentIndex) == 0)
+        treeModelPointer->removeRow(parentIndex.row(), parentIndex.parent());
+
+    // Delete the cookie from the cookie list and cookie store.
+    emit deleteCookie(cookie);
 }
 
-void CookiesDialog::showAddCookieMessageBox() const
+void CookiesDialog::showAddCookieDialog()
 {
     // Instantiate an add cookie dialog.
-    QDialog *addCookieDialogPointer = new AddCookieDialog();
+    QDialog *addCookieDialogPointer = new AddOrEditCookieDialog(this, AddOrEditCookieDialog::AddCookie);
 
     // Show the dialog.
     addCookieDialogPointer->show();
 
     // Add the cookie if directed.
-    connect(addCookieDialogPointer, SIGNAL(addCookie(QNetworkCookie)), this, SLOT(addCookieFromDialog(QNetworkCookie)));
+    connect(addCookieDialogPointer, SIGNAL(addCookie(QNetworkCookie, bool)), this, SLOT(addCookieFromDialog(QNetworkCookie, bool)));
 }
 
 void CookiesDialog::showDeleteAllMessageBox() const
@@ -175,6 +620,12 @@ void CookiesDialog::showDeleteAllMessageBox() const
     // Set the text.
     deleteAllCookiesMessageBox.setText(i18nc("Delete all cookies dialog text", "Delete all cookies?"));
 
+    // Create a delete durable cookies check box.
+    QCheckBox deleteDurableCookiesCheckBox(i18nc("Delete durable cookies check box", "Delete even if durable"));
+
+    // Add the check box to the dialog.
+    deleteAllCookiesMessageBox.setCheckBox(&deleteDurableCookiesCheckBox);
+
     // Set the standard buttons.
     deleteAllCookiesMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
 
@@ -187,32 +638,191 @@ void CookiesDialog::showDeleteAllMessageBox() const
     // Delete all cookies if instructed.
     if (returnValue == QMessageBox::Yes)
     {
-        // Delete all the cookies.
-        emit deleteAllCookies();
+        // Only delete durable cookies if requested.
+        if (deleteDurableCookiesCheckBox.isChecked())  // Delete everything.
+        {
+            // Delete all the cookies.
+            emit deleteAllCookies();
 
-        // Clear the cookie list.
-        cookieListPointer->clear();
+            // Clear the tree model.
+            treeModelPointer->clear();
 
-        // Create a layout item pointer.
-        QLayoutItem *layoutItemPointer;
+            // Delete the durable cookies from the database.
+            CookiesDatabase::deleteAllCookies();
 
-        // Delete each cookie widget.
-        while ((layoutItemPointer = cookiesVBoxLayoutPointer->takeAt(0)) != nullptr)
+            // Update the UI.
+            updateUi();
+        }
+        else  // Only delete cookies that are not durable.
         {
-            // Delete the widget.
-            delete layoutItemPointer->widget();
+            // Get the root model index.
+            QModelIndex rootIndex = treeModelPointer->invisibleRootItem()->index();
+
+            // Get the number of domains.
+            int numberOfDomains = treeModelPointer->rowCount(rootIndex);
 
-            // Delete the layout.
-            delete layoutItemPointer;
+            // Delete each domain, starting from the bottom.
+            for (int i = numberOfDomains; i > 0; --i)
+                deleteDomain(treeModelPointer->index(i - 1, 0, rootIndex), deleteDurableCookiesCheckBox.isChecked());
         }
+    }
+}
+
+void CookiesDialog::showDeleteCookieMessageBox() const
+{
+    // Get the current model index.
+    QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
+
+    // Determine if a domain is selected.
+    bool isDomain = treeModelPointer->hasChildren(currentIndex);
 
-        // Update the UI.
-        updateUi();
+    // Instantiate a delete cookie message box.
+    QMessageBox deleteCookieMessageBox;
+
+    // Set the icon.
+    deleteCookieMessageBox.setIcon(QMessageBox::Warning);
+
+    // Create a delete durable cookies check box.
+    QCheckBox deleteDurableCookiesCheckBox(i18nc("Delete durable cookies check box", "Delete even if durable"));
+
+    if (isDomain)  // A domain is selected.
+    {
+        // Get the number of cookies.
+        int numberOfCookiesToDelete = treeModelPointer->rowCount(currentIndex);
+
+        // Set the window title.
+        deleteCookieMessageBox.setWindowTitle(i18ncp("Delete cookies dialog title", "Delete %1 Cookie", "Delete 1% Cookies", numberOfCookiesToDelete));
+
+        // Set the text.
+        deleteCookieMessageBox.setText(i18ncp("Delete cookies dialog text", "Delete %1 cookie?", "Delete %1 cookies?", numberOfCookiesToDelete));
+    }
+    else  // A single cookie is selected.
+    {
+        // Set the window title.
+        deleteCookieMessageBox.setWindowTitle(i18nc("Delete cookie dialog title", "Delete 1 Cookie"));
+
+        // Set the text.
+        deleteCookieMessageBox.setText(i18nc("Delete cookie dialog text", "Delete 1 cookie?"));
+
+        // Check the box.
+        deleteDurableCookiesCheckBox.setChecked(true);
     }
+
+    // Add the check box to the dialog.
+    deleteCookieMessageBox.setCheckBox(&deleteDurableCookiesCheckBox);
+
+    // Set the standard buttons.
+    deleteCookieMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+
+    // Set the default button.
+    deleteCookieMessageBox.setDefaultButton(QMessageBox::No);
+
+    // Display the dialog and capture the return value.
+    int returnValue = deleteCookieMessageBox.exec();
+
+    // Delete the cookie if instructed.
+    if (returnValue == QMessageBox::Yes)
+    {
+        // Delete the cookies according to the selection.
+        if (isDomain)  // A domain is selected.
+        {
+            // Delete the domain.
+            deleteDomain(currentIndex, deleteDurableCookiesCheckBox.isChecked());
+        }
+        else  // A single cookie is selected.
+        {
+            // Get the parent model index.
+            QModelIndex parentIndex = currentIndex.parent();
+
+            // Delete the cookie.
+            deleteCookie(currentIndex, deleteDurableCookiesCheckBox.isChecked());
+
+            // Remove the domain row if its only cookie has been deleted.
+            if (treeModelPointer->rowCount(parentIndex) == 0)
+                treeModelPointer->removeRow(parentIndex.row(), parentIndex.parent());
+        }
+    }
+}
+
+void CookiesDialog::showDurableCookiesDialog()
+{
+    // Instantiate a durable cookies dialog.
+    QDialog *durableCookiesDialogPointer = new DurableCookiesDialog(this);
+
+    // Show the dialog.
+    durableCookiesDialogPointer->show();
+
+    // Process cookie changes.
+    connect(durableCookiesDialogPointer, SIGNAL(addingCookie(QNetworkCookie, bool)), this, SLOT(addCookieFromDialog(QNetworkCookie, bool)));
+    connect(durableCookiesDialogPointer, SIGNAL(deletingCookie(QNetworkCookie)), this, SLOT(deleteCookieFromDatabase(QNetworkCookie)));
+    connect(durableCookiesDialogPointer, SIGNAL(updateParentUi()), this, SLOT(updateUi()));
+}
+
+void CookiesDialog::showEditCookieDialog()
+{
+    // Get the current model index.
+    QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
+
+    // Create a partial cookie.
+    QNetworkCookie partialCookie;
+
+    // Populate the partial cookie from the current model index.
+    partialCookie.setDomain(currentIndex.parent().siblingAtColumn(0).data().toString());
+    partialCookie.setName(currentIndex.siblingAtColumn(0).data().toString().toUtf8());
+    partialCookie.setPath(currentIndex.siblingAtColumn(2).data().toString());
+
+    // Create a cookie to edit.
+    QNetworkCookie cookieToEdit;
+
+    // Search for the partial cookie in the cookie list.
+    for (QNetworkCookie cookie : *cookieListPointer)
+    {
+        // Store the cookie to edit if it has the same identifier as the partial cookie.
+        if (cookie.hasSameIdentifier(partialCookie))
+            cookieToEdit = cookie;
+    }
+
+    // Instantiate an edit cookie dialog.
+    QDialog *editCookieDialogPointer = new AddOrEditCookieDialog(this, AddOrEditCookieDialog::EditCookie, &cookieToEdit, currentIndex.siblingAtColumn(1).data().toString() == i18n("yes"));
+
+    // Show the dialog.
+    editCookieDialogPointer->show();
+
+    // Process cookie events.
+    connect(editCookieDialogPointer, SIGNAL(addCookie(QNetworkCookie, bool)), this, SLOT(addCookieFromDialog(QNetworkCookie, bool)));
+    connect(editCookieDialogPointer, SIGNAL(deleteCookie(QNetworkCookie)), this, SLOT(deleteCookieFromDialog(QNetworkCookie)));
 }
 
 void CookiesDialog::updateUi() const
 {
+    // Get the current index of the first column.
+    QModelIndex currentIndex = treeSelectionModelPointer->currentIndex().siblingAtColumn(0);
+
     // Set the status of the buttons.
-    deleteAllButtonPointer->setEnabled(cookieListPointer->count() > 0);
+    editCookieButtonPointer->setEnabled(treeSelectionModelPointer->hasSelection() && !treeModelPointer->hasChildren(currentIndex));
+    deleteCookieButtonPointer->setEnabled(treeSelectionModelPointer->hasSelection());;
+    deleteAllButtonPointer->setEnabled(treeModelPointer->hasChildren(treeModelPointer->invisibleRootItem()->index()));
+
+    // Update the delete cookie button text.
+    if (deleteCookieButtonPointer->isEnabled())  // The button is enabled.
+    {
+        if (treeModelPointer->hasChildren(currentIndex))  // A domain is selected.
+        {
+            // Update the button text.
+            deleteCookieButtonPointer->setText(i18ncp("Delete cookies button.", "&Delete %1 cookie", "&Delete %1 cookies", treeModelPointer->rowCount(currentIndex)));
+        }
+        else  // A single cookie is selected.
+        {
+            // Update the button text.
+            deleteCookieButtonPointer->setText(i18nc("Delete cookies button.", "&Delete 1 cookie"));
+        }
+    }
+    else  // The button is disabled.
+    {
+        // Reset the button text.
+        deleteCookieButtonPointer->setText(i18nc("Delete cookie button.", "&Delete cookie"));
+    }
+
+    // Update the text of the durable cookies button.
+    durableCookiesButtonPointer->setText(i18nc("View the durable cookies button", "Durable cookies - %1", CookiesDatabase::cookieCount()));
 }