/*
- * Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
*
#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>
#include <QStandardItemModel>
// Construct the class.
-BookmarksDialog::BookmarksDialog(QIcon currentWebsiteFavorieIcon) : QDialog(nullptr), websiteFavoriteIcon(currentWebsiteFavorieIcon)
+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"));
treeSelectionModelPointer = draggableTreeViewPointer->selectionModel();
// Listen for selection changes.
- connect(treeSelectionModelPointer, SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(updateUi()));
+ 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;
// 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()));
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();
treeModelPointer->clear();
// Set the column count.
- treeModelPointer->setColumnCount(4);
+ treeModelPointer->setColumnCount(6);
- // Set the tree header data. The last column is the database ID, which is not displayed.
- treeModelPointer->setHeaderData(BOOKMARK_NAME_COLUMN, Qt::Horizontal, i18nc("The bookmark Name header.", "Name"));
- treeModelPointer->setHeaderData(BOOKMARK_URL_COLUMN, Qt::Horizontal, i18nc("The bookmark URL header.", "URL"));
+ // 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, 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);
- // Get the list of bookmarks.
- std::list<BookmarkStruct> *bookmarksListPointer = BookmarksDatabase::getBookmarks();
+ // 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 : *bookmarksListPointer)
+ for (const BookmarkStruct &bookmarkStruct : *folderContentsListPointer)
{
- // Create a list for the bookmark items.
+ // Create a bookmark item list.
QList<QStandardItem*> bookmarkItemList;
// Create the bookmark items.
- QStandardItem *nameItemPointer = new QStandardItem(bookmarkStruct.favoriteIcon, bookmarkStruct.bookmarkName);
- QStandardItem *urlItemPointer = new QStandardItem(bookmarkStruct.bookmarkUrl);
- QStandardItem *idItemPointer = new QStandardItem(QString::number(bookmarkStruct.id));
- QStandardItem *displayOrderPointer = new QStandardItem(QString::number(bookmarkStruct.displayOrder));
-
+ 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);
- nameItemPointer->setDropEnabled(true);
- // Disable dragging the URL.
- urlItemPointer->setDragEnabled(false);
+ // Only allow dropping on the name if this is a folder.
+ nameItemPointer->setDropEnabled(bookmarkStruct.isFolder);
- // Disable dropping on the URL. For some reason this doesn't work.
+ // Disable dragging and dropping on the URL.
+ urlItemPointer->setDragEnabled(false);
urlItemPointer->setDropEnabled(false);
// Disable selecting the URL.
// Populate the bookmark item list.
bookmarkItemList.append(nameItemPointer);
bookmarkItemList.append(urlItemPointer);
- bookmarkItemList.append(idItemPointer);
- bookmarkItemList.append(displayOrderPointer);
+ bookmarkItemList.append(databaseIdItemPointer);
+ bookmarkItemList.append(displayOrderItemPointer);
+ bookmarkItemList.append(isFolderItemPointer);
+ bookmarkItemList.append(folderIdItemPointer);
- // Add the bookmark to the tree.
- treeModelPointer->appendRow(bookmarkItemList);
- }
+ // Add the bookmark to the parent folder.
+ folderItemNamePointer->appendRow(bookmarkItemList);
- // Update the UI.
- updateUi();
+ // Populate subfolders if this item is a folder.
+ if (bookmarkStruct.isFolder)
+ populateSubfolders(nameItemPointer, bookmarkStruct.folderId);
+ }
}
void BookmarksDialog::refreshBookmarks() const
emit bookmarkUpdated();
}
-void BookmarksDialog::showAddBookmarkDialog() const
+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(QLatin1String(""), QLatin1String(""), websiteFavoriteIcon);
+ 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()));
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();
- // Instantiate an edit bookmark dialog.
- QDialog *editBookmarkDialogPointer = new EditBookmarkDialog(currentIndex.siblingAtColumn(DATABASE_ID_COLUMN).data().toInt(), websiteFavoriteIcon);
+ // 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.
- editBookmarkDialogPointer->show();
+ // Show the dialog.
+ editFolderDialogPointer->show();
- // Process bookmark events.
- connect(editBookmarkDialogPointer, SIGNAL(bookmarkSaved()), this, SLOT(refreshBookmarks()));
+ // 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)
int databaseId = databaseIdModelIndex.data().toInt();
// Check to see if the bookmark name or the URL was edited.
- if (modifiedStandardItem->column() == BOOKMARK_NAME_COLUMN) // The bookmark name was edited.
+ if (modifiedStandardItem->column() == NAME_COLUMN) // The bookmark name was edited.
{
// Update the bookmark name.
BookmarksDatabase::updateBookmarkName(databaseId, modifiedStandardItem->text());
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.
{
- // Enabled the buttons.
- editButtonPointer->setEnabled(true);
- deleteItemsButtonPointer->setEnabled(true);
+ // 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.
- deleteItemsButtonPointer->setText(i18ncp("Delete items button populated text.", "Delete %1 item", "Delete %1 items", treeSelectionModelPointer->selectedRows().count()));
+ 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.
{