From 29dbafaca706ea6a34cd881060ebf680378f39b4 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Fri, 6 Oct 2023 15:56:13 -0700 Subject: [PATCH] Add bookmark folders. --- src/CMakeLists.txt | 2 + src/databases/BookmarksDatabase.cpp | 642 +++++++++++++++++++++---- src/databases/BookmarksDatabase.h | 26 +- src/dialogs/AddBookmarkDialog.cpp | 87 +++- src/dialogs/AddBookmarkDialog.h | 16 +- src/dialogs/AddFolderDialog.cpp | 169 +++++++ src/dialogs/AddFolderDialog.h | 63 +++ src/dialogs/BookmarksDialog.cpp | 302 ++++++++++-- src/dialogs/BookmarksDialog.h | 24 +- src/dialogs/CMakeLists.txt | 2 + src/dialogs/EditBookmarkDialog.cpp | 124 ++++- src/dialogs/EditBookmarkDialog.h | 15 +- src/dialogs/EditFolderDialog.cpp | 184 +++++++ src/dialogs/EditFolderDialog.h | 64 +++ src/helpers/CMakeLists.txt | 1 + src/helpers/FolderHelper.cpp | 86 ++++ src/helpers/FolderHelper.h | 40 ++ src/main.cpp | 2 +- src/structs/BookmarkStruct.h | 9 +- src/ui.rcs/browserwindowui.rc | 4 + src/uis/AddBookmarkDialog.ui | 61 ++- src/uis/AddFolderDialog.ui | 189 ++++++++ src/uis/BookmarksDialog.ui | 13 + src/uis/EditBookmarkDialog.ui | 43 +- src/uis/EditFolderDialog.ui | 185 +++++++ src/widgets/DraggableTreeView.cpp | 181 +++++-- src/widgets/DraggableTreeView.h | 3 + src/windows/BrowserWindow.cpp | 714 +++++++++++++++++++++++----- src/windows/BrowserWindow.h | 13 +- 29 files changed, 2879 insertions(+), 385 deletions(-) create mode 100644 src/dialogs/AddFolderDialog.cpp create mode 100644 src/dialogs/AddFolderDialog.h create mode 100644 src/dialogs/EditFolderDialog.cpp create mode 100644 src/dialogs/EditFolderDialog.h create mode 100644 src/helpers/FolderHelper.cpp create mode 100644 src/helpers/FolderHelper.h create mode 100644 src/uis/AddFolderDialog.ui create mode 100644 src/uis/EditFolderDialog.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3ecd3c8..16bf76c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,6 +36,7 @@ kconfig_add_kcfg_files(privacybrowser settings/Settings.kcfgc) # Use KDE Frameworks to handle internationalization of the following UI files. ki18n_wrap_ui(privacybrowser uis/AddBookmarkDialog.ui + uis/AddFolderDialog.ui uis/AddOrEditCookieDialog.ui uis/AddTabWidget.ui uis/BookmarksDialog.ui @@ -43,6 +44,7 @@ ki18n_wrap_ui(privacybrowser uis/DomainSettingsDialog.ui uis/DurableCookiesDialog.ui uis/EditBookmarkDialog.ui + uis/EditFolderDialog.ui uis/SaveDialog.ui uis/SettingsGeneral.ui uis/SettingsPrivacy.ui diff --git a/src/databases/BookmarksDatabase.cpp b/src/databases/BookmarksDatabase.cpp index fdcb0fd..e786eb6 100644 --- a/src/databases/BookmarksDatabase.cpp +++ b/src/databases/BookmarksDatabase.cpp @@ -109,30 +109,8 @@ void BookmarksDatabase::addBookmark(const BookmarkStruct *bookmarkStructPointer) // Get a handle for the bookmarks database. QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME); - // Instantiate a count bookmarks query. TODO: This needs to be updated to only count the bookmarks in the current folder. - QSqlQuery countBookmarksQuery(bookmarksDatabase); - - // Set the query to be forward only, which is more performant. - countBookmarksQuery.setForwardOnly(true); - - // Prepare the count bookmarks query. - countBookmarksQuery.prepare("SELECT " + DISPLAY_ORDER + " FROM " + BOOKMARKS_TABLE); - - // Execute the count bookmarks query. - countBookmarksQuery.exec(); - - // Move to the last row. - countBookmarksQuery.last(); - - // Initialize a bookmarks count variable. - int bookmarksCount = 0; - - // Check to see if the query is valid (there is at least one bookmark). - if (countBookmarksQuery.isValid()) - { - // Get the number of rows (which is zero based) and add one to calculate the number of bookmarks. - bookmarksCount = countBookmarksQuery.at() + 1; - } + // Get the folder item count. + int folderItemCount = getFolderItemCount(bookmarkStructPointer->parentFolderId); // Instantiate an add bookmark query. QSqlQuery addBookmarkQuery(bookmarksDatabase); @@ -141,22 +119,58 @@ void BookmarksDatabase::addBookmark(const BookmarkStruct *bookmarkStructPointer) addBookmarkQuery.prepare("INSERT INTO " + BOOKMARKS_TABLE + " (" + BOOKMARK_NAME + ", " + BOOKMARK_URL + ", " + - FAVORITE_ICON + ", " + - DISPLAY_ORDER + ") " + - "VALUES (:bookmark_name, :bookmark_url, :favorite_icon, :display_order)" + 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->bookmarkName); - addBookmarkQuery.bindValue(":bookmark_url", bookmarkStructPointer->bookmarkUrl); + 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)); - addBookmarkQuery.bindValue(":display_order", bookmarksCount); // Execute the add bookmark query. addBookmarkQuery.exec(); } -void BookmarksDatabase::deleteBookmark(const int bookmarkId) +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); @@ -168,55 +182,83 @@ void BookmarksDatabase::deleteBookmark(const int bookmarkId) deleteBookmarkQuery.prepare("DELETE FROM " + BOOKMARKS_TABLE + " WHERE " + ID + " = :id"); // Bind the query values. - deleteBookmarkQuery.bindValue(":id", bookmarkId); + deleteBookmarkQuery.bindValue(":id", databaseId); // Execute the query. deleteBookmarkQuery.exec(); +} - // Reset the display order for the other items in the folder. TODO: make this folder aware. - // TODO: Perhaps, for performance reasons, this shouldn't run each time a bookmarks is deleted, but batched at the end. +double BookmarksDatabase::generateFolderId() +{ + // Get the current time in epoch format (milliseconds). + double possibleFolderId = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - // Instantiate a bookmarks query. - QSqlQuery bookmarksQuery(bookmarksDatabase); + // Get a handle for the bookmarks database. + QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME); - // Set the query to be forward only, which is more performant. - bookmarksQuery.setForwardOnly(true); + // Instantiate a existing folder query. + QSqlQuery existingFolderQuery(bookmarksDatabase); - // Prepare the bookmarks query. - bookmarksQuery.prepare("SELECT " + ID + ", " + DISPLAY_ORDER + " FROM " + BOOKMARKS_TABLE + " ORDER BY " + DISPLAY_ORDER + " ASC"); + // 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. - bookmarksQuery.exec(); + existingFolderQuery.exec(); - // Create a new display order int. - int newDisplayOrder = 0; + // 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(); - // Update the display order for each bookmark. - while (bookmarksQuery.next()) - { - // Check if the new display order is different than the current display order. - if (bookmarksQuery.value(DISPLAY_ORDER).toInt() != newDisplayOrder) - { - // Instantiate an update display order query. - QSqlQuery updateDisplayOrderQuery(bookmarksDatabase); + return possibleFolderId; +} - // Prepare the update display order query. - updateDisplayOrderQuery.prepare("UPDATE " + BOOKMARKS_TABLE + " SET " + DISPLAY_ORDER + " = :display_order WHERE " + ID + " = :id"); +QList* BookmarksDatabase::getAllFolderUrls(const double folderId) +{ + // Get a handle for the bookmarks database. + QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME); - // Bind the query values. - updateDisplayOrderQuery.bindValue(":display_order", newDisplayOrder); - updateDisplayOrderQuery.bindValue(":id", bookmarksQuery.value(ID).toInt()); + // Instantiate a folder URLs query. + QSqlQuery folderUrlsQuery(bookmarksDatabase); - // Execute the query. - updateDisplayOrderQuery.exec(); - } + // Set the query to be forward only, which is more performant. + folderUrlsQuery.setForwardOnly(true); - // Increment the new display order. - ++newDisplayOrder; + // 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 *folderUrlsListPointer = new QList; + + // 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(int bookmarkId) +BookmarkStruct* BookmarksDatabase::getBookmark(const int databaseId) { // Get a handle for the bookmarks database. QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME); @@ -231,7 +273,7 @@ BookmarkStruct *BookmarksDatabase::getBookmark(int bookmarkId) bookmarkQuery.prepare("SELECT * FROM " + BOOKMARKS_TABLE + " WHERE " + ID + " = :id"); // Bind the query values. - bookmarkQuery.bindValue(":id", bookmarkId); + bookmarkQuery.bindValue(":id", databaseId); // Execute the query. bookmarkQuery.exec(); @@ -252,10 +294,13 @@ BookmarkStruct *BookmarksDatabase::getBookmark(int bookmarkId) favoriteIconPixmap.loadFromData(favoriteIconByteArray); // Populate the bookmark struct. - bookmarkStructPointer->id = bookmarkQuery.value(ID).toInt(); - bookmarkStructPointer->bookmarkName = bookmarkQuery.value(BOOKMARK_NAME).toString(); - bookmarkStructPointer->bookmarkUrl = bookmarkQuery.value(BOOKMARK_URL).toString(); + 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. @@ -298,10 +343,13 @@ std::list* BookmarksDatabase::getBookmarks() favoriteIconPixmap.loadFromData(favoriteIconByteArray); // Populate the bookmark struct. - bookmarkStruct.id = bookmarksQuery.value(ID).toInt(); - bookmarkStruct.bookmarkName = bookmarksQuery.value(BOOKMARK_NAME).toString(); - bookmarkStruct.bookmarkUrl = bookmarksQuery.value(BOOKMARK_URL).toString(); + 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. @@ -312,7 +360,7 @@ std::list* BookmarksDatabase::getBookmarks() return bookmarkListPointer; } -QList* BookmarksDatabase::getBookmarksExcept(QList *exceptDatabaseIdsListPointer) +QList* BookmarksDatabase::getBookmarksInFolderExcept(const double folderId, QList *exceptDatabaseIdsListPointer) { // Get a handle for the bookmarks database. QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME); @@ -340,7 +388,10 @@ QList* BookmarksDatabase::getBookmarksExcept(QList *exceptD } // Prepare the bookmarks query. - bookmarksQuery.prepare("SELECT * FROM " + BOOKMARKS_TABLE + " WHERE " + ID + " NOT IN (" + idsNotToGetString + ") ORDER BY " + DISPLAY_ORDER + " ASC"); + 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(); @@ -364,10 +415,13 @@ QList* BookmarksDatabase::getBookmarksExcept(QList *exceptD favoriteIconPixmap.loadFromData(favoriteIconByteArray); // Populate the bookmark struct. - bookmarkStruct.id = bookmarksQuery.value(ID).toInt(); - bookmarkStruct.bookmarkName = bookmarksQuery.value(BOOKMARK_NAME).toString(); - bookmarkStruct.bookmarkUrl = bookmarksQuery.value(BOOKMARK_URL).toString(); + 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. @@ -405,34 +459,406 @@ QString BookmarksDatabase::getFavoriteIconBase64String(const QIcon &favoriteIcon return favoriteIconBase64String; } +QList* 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 *folderContentsListPointer = new QList; + + // 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* 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 *folderContentsDatabaseIdsListPointer = new QList; + + // 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 *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 *folderContentsDatabaseIdsListPointer = new QList; + + // 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* 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 *subfoldersListPointer = new QList; + + // 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::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 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->bookmarkName); - updateBookmarkQuery.bindValue(":bookmark_url", bookmarkStructPointer->bookmarkUrl); + 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->id); + updateBookmarkQuery.bindValue(":id", bookmarkStructPointer->databaseId); // Execute the query. updateBookmarkQuery.exec(); } -void BookmarksDatabase::updateDisplayOrder(const int bookmarkId, const int displayOrder) +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); @@ -447,50 +873,66 @@ void BookmarksDatabase::updateDisplayOrder(const int bookmarkId, const int displ // Bind the query values. updateBookmarkDisplayOrderQuery.bindValue(":display_order", displayOrder); - updateBookmarkDisplayOrderQuery.bindValue(":id", bookmarkId); + updateBookmarkDisplayOrderQuery.bindValue(":id", databaseId); // Execute the query. updateBookmarkDisplayOrderQuery.exec(); } -void BookmarksDatabase::updateBookmarkName(const int bookmarkId, const QString &bookmarkName) +void BookmarksDatabase::updateFolderContentsDisplayOrder(const double folderId) { // Get a handle for the bookmarks database. QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME); - // Instantiate an update bookmark name query. - QSqlQuery updateBookmarkNameQuery(bookmarksDatabase); + // Instantiate a folder contents query. + QSqlQuery folderContentsQuery(bookmarksDatabase); - // Prepare the update bookmark name query. - updateBookmarkNameQuery.prepare("UPDATE " + BOOKMARKS_TABLE + - " SET " + BOOKMARK_NAME + " = :bookmark_name " + - "WHERE " + ID + " = :id"); + // 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. - updateBookmarkNameQuery.bindValue(":bookmark_name", bookmarkName); - updateBookmarkNameQuery.bindValue(":id", bookmarkId); + folderContentsQuery.bindValue(":parent_folder_id", folderId); // Execute the query. - updateBookmarkNameQuery.exec(); + 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::updateBookmarkUrl(const int bookmarkId, const QString &bookmarkUrl) +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 URL query. - QSqlQuery updateBookmarkUrlQuery(bookmarksDatabase); + // Instantiate an update bookmark display order query. + QSqlQuery updateBookmarkDisplayOrderQuery(bookmarksDatabase); - // Prepare the update bookmark URL query. - updateBookmarkUrlQuery.prepare("UPDATE " + BOOKMARKS_TABLE + - " SET " + BOOKMARK_URL + " = :bookmark_url " + - "WHERE " + ID + " = :id"); + // 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. - updateBookmarkUrlQuery.bindValue(":bookmark_url", bookmarkUrl); - updateBookmarkUrlQuery.bindValue(":id", bookmarkId); + updateBookmarkDisplayOrderQuery.bindValue(":parent_folder_id", parentFolderId); + updateBookmarkDisplayOrderQuery.bindValue(":display_order", displayOrder); + updateBookmarkDisplayOrderQuery.bindValue(":id", databaseId); // Execute the query. - updateBookmarkUrlQuery.exec(); + updateBookmarkDisplayOrderQuery.exec(); } diff --git a/src/databases/BookmarksDatabase.h b/src/databases/BookmarksDatabase.h index 35f94bd..1e88140 100644 --- a/src/databases/BookmarksDatabase.h +++ b/src/databases/BookmarksDatabase.h @@ -35,14 +35,27 @@ public: // The public functions. static void addBookmark(const BookmarkStruct *bookmarkStructPointer); static void addDatabase(); - static void deleteBookmark(const int bookmarkId); - static BookmarkStruct* getBookmark(int bookmarkId); + static void addFolder(const BookmarkStruct *bookmarkStructPointer); + static void deleteBookmark(const int databaseId); + static QList* getAllFolderUrls(const double folderId); + static BookmarkStruct* getBookmark(const int databaseId); static std::list* getBookmarks(); - static QList* getBookmarksExcept(QList *exceptDatabaseIdsListPointer); + static QList* getBookmarksInFolderExcept(const double folderId, QList *exceptDatabaseIdsListPointer); + static QList* getFolderContentsDatabaseIds(const double folderId); + static QList* getFolderContentsDatabaseIdsRecursively(const double folderId); + static QList* 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* getSubfolders(const double folderId); + static bool isFolder(const int databaseId); static void updateBookmark(const BookmarkStruct *bookmarkStructPointer); - static void updateDisplayOrder(const int bookmarkId, const int displayOrder); - static void updateBookmarkName(const int bookmarkId, const QString &bookmarkName); - static void updateBookmarkUrl(const int bookmarkId, const QString &bookmarkUrl); + 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; @@ -61,6 +74,7 @@ private: static const int SCHEMA_VERSION; // The private functions. + static double generateFolderId(); static QString getFavoriteIconBase64String(const QIcon &favoriteIcon); }; #endif diff --git a/src/dialogs/AddBookmarkDialog.cpp b/src/dialogs/AddBookmarkDialog.cpp index 222a69a..e69543e 100644 --- a/src/dialogs/AddBookmarkDialog.cpp +++ b/src/dialogs/AddBookmarkDialog.cpp @@ -30,7 +30,7 @@ #include // Construct the class. -AddBookmarkDialog::AddBookmarkDialog(const QString &bookmarkName, const QString &bookmarkUrl, const QIcon &favoriteIcon) : QDialog(nullptr) +AddBookmarkDialog::AddBookmarkDialog(const QString &bookmarkName, const QString &bookmarkUrl, const QIcon &favoriteIcon, const double parentFolderId) : QDialog(nullptr) { // Set the window title. setWindowTitle(i18nc("The add bookmark dialog window title.", "Add Bookmark")); @@ -38,7 +38,7 @@ AddBookmarkDialog::AddBookmarkDialog(const QString &bookmarkName, const QString // Set the window modality. setWindowModality(Qt::WindowModality::ApplicationModal); - // Instantiate the bookmarks dialog UI. + // Instantiate the add bookmark dialog UI. Ui::AddBookmarkDialog addBookmarkDialogUi; // Setup the UI. @@ -47,36 +47,83 @@ AddBookmarkDialog::AddBookmarkDialog(const QString &bookmarkName, const QString // Get handles for the widgets. defaultFavoriteIconRadioButtonPointer = addBookmarkDialogUi.defaultFavoriteIconRadioButton; customFavoriteIconRadioButtonPointer = addBookmarkDialogUi.customFavoriteIconRadioButton; - bookmarkNamePointer = addBookmarkDialogUi.bookmarkNameLineEdit; - bookmarkUrlPointer = addBookmarkDialogUi.bookmarkUrlLineEdit; + parentFolderTreeWidgetPointer = addBookmarkDialogUi.parentFolderTreeWidget; + bookmarkNameLineEditPointer = addBookmarkDialogUi.bookmarkNameLineEdit; + bookmarkUrlLineEditPointer = addBookmarkDialogUi.bookmarkUrlLineEdit; QPushButton *browseButtonPointer = addBookmarkDialogUi.browseButton; QDialogButtonBox *dialogButtonBoxPointer = addBookmarkDialogUi.dialogButtonBox; // Set the default favorite icon. defaultFavoriteIconRadioButtonPointer->setIcon(favoriteIcon); + // 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("bookmarks")); + 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. - bookmarkNamePointer->setText(bookmarkName); - bookmarkUrlPointer->setText(bookmarkUrl); + bookmarkNameLineEditPointer->setText(bookmarkName); + bookmarkUrlLineEditPointer->setText(bookmarkUrl); // Scroll to the beginning of the line edits. - bookmarkNamePointer->setCursorPosition(0); - bookmarkUrlPointer->setCursorPosition(0); + bookmarkNameLineEditPointer->setCursorPosition(0); + bookmarkUrlLineEditPointer->setCursorPosition(0); // Add buttons to the dialog button box. - QPushButton *addBookmarkButtonPointer = dialogButtonBoxPointer->addButton(i18nc("The add bookmark button", "Add"), QDialogButtonBox::AcceptRole); + addButtonPointer = dialogButtonBoxPointer->addButton(i18nc("The add bookmark button", "Add"), QDialogButtonBox::AcceptRole); // Set the button icons. - addBookmarkButtonPointer->setIcon(QIcon::fromTheme("list-add")); + 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 selectedFoldersList = parentFolderTreeWidgetPointer->selectedItems(); + + // Get the selected folder. + QTreeWidgetItem *selectedFolderPointer = selectedFoldersList.first(); + // Get the favorite icon. QIcon favoriteIcon = defaultFavoriteIconRadioButtonPointer->isChecked() ? defaultFavoriteIconRadioButtonPointer->icon() : customFavoriteIconRadioButtonPointer->icon(); @@ -84,8 +131,9 @@ void AddBookmarkDialog::addBookmark() BookmarkStruct *bookmarkStructPointer = new BookmarkStruct; // Populate the bookmark struct. - bookmarkStructPointer->bookmarkName = bookmarkNamePointer->text(); - bookmarkStructPointer->bookmarkUrl = bookmarkUrlPointer->text(); + bookmarkStructPointer->name = bookmarkNameLineEditPointer->text(); + bookmarkStructPointer->url = bookmarkUrlLineEditPointer->text(); + bookmarkStructPointer->parentFolderId = selectedFolderPointer->text(folderHelperPointer->FOLDER_ID_COLUMN).toDouble(); bookmarkStructPointer->favoriteIcon = favoriteIcon; // Add the bookmark. @@ -114,3 +162,18 @@ void AddBookmarkDialog::browse() customFavoriteIconRadioButtonPointer->setChecked(true); } } + +void AddBookmarkDialog::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 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 index c054876..18c3ca1 100644 --- a/src/dialogs/AddBookmarkDialog.h +++ b/src/dialogs/AddBookmarkDialog.h @@ -20,10 +20,14 @@ #ifndef ADDBOOKMARKDIALOG_H #define ADDBOOKMARKDIALOG_H +// Application headers. +#include "helpers/FolderHelper.h" + // Qt toolkit headers. #include #include #include +#include class AddBookmarkDialog : public QDialog { @@ -32,7 +36,7 @@ class AddBookmarkDialog : public QDialog public: // The primary constructor. - explicit AddBookmarkDialog(const QString &bookmarkName, const QString &bookmarkUrl, const QIcon &favoriteIcon); + explicit AddBookmarkDialog(const QString &bookmarkName, const QString &bookmarkUrl, const QIcon &favoriteIcon, const double parentFolderId = 0); signals: // The signals. @@ -42,12 +46,18 @@ private Q_SLOTS: // The private slots. void addBookmark(); void browse(); + void updateUi(); private: + // The private variables. + FolderHelper *folderHelperPointer; + // The private widgets. - QLineEdit *bookmarkNamePointer; - QLineEdit *bookmarkUrlPointer; + QPushButton *addButtonPointer; + QLineEdit *bookmarkNameLineEditPointer; + QLineEdit *bookmarkUrlLineEditPointer; QRadioButton *customFavoriteIconRadioButtonPointer; QRadioButton *defaultFavoriteIconRadioButtonPointer; + QTreeWidget *parentFolderTreeWidgetPointer; }; #endif diff --git a/src/dialogs/AddFolderDialog.cpp b/src/dialogs/AddFolderDialog.cpp new file mode 100644 index 0000000..37b8845 --- /dev/null +++ b/src/dialogs/AddFolderDialog.cpp @@ -0,0 +1,169 @@ +/* + * Copyright 2023 Soren Stoutner . + * + * This file is part of Privacy Browser PC . + * + * Privacy Browser PC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser PC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser PC. If not, see . + */ + +// Application headers. +#include "AddFolderDialog.h" +#include "ui_AddFolderDialog.h" +#include "databases/BookmarksDatabase.h" +#include "helpers/FolderHelper.h" +#include "structs/BookmarkStruct.h" + +// Qt toolkit headers. +#include + +// Construct the class. +AddFolderDialog::AddFolderDialog(const QIcon ¤tWebsiteFavoriteIcon, const double parentFolderId) : QDialog(nullptr) +{ + // 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("bookmarks")); + 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 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 index 0000000..2dc05df --- /dev/null +++ b/src/dialogs/AddFolderDialog.h @@ -0,0 +1,63 @@ +/* + * Copyright 2023 Soren Stoutner . + * + * This file is part of Privacy Browser PC . + * + * Privacy Browser PC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser PC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser PC. If not, see . + */ + +#ifndef ADDFOLDERDIALOG_H +#define ADDFOLDERDIALOG_H + +// Application headers. +#include "helpers/FolderHelper.h" + +// Qt toolkit headers. +#include +#include +#include +#include + +class AddFolderDialog : public QDialog +{ + // Include the Q_OBJECT macro. + Q_OBJECT + +public: + // The primary constructor. + explicit AddFolderDialog(const QIcon ¤tWebsiteFavoriteIcon, const double parentFolderId = 0); + +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 diff --git a/src/dialogs/BookmarksDialog.cpp b/src/dialogs/BookmarksDialog.cpp index 4dc66ee..be150cc 100644 --- a/src/dialogs/BookmarksDialog.cpp +++ b/src/dialogs/BookmarksDialog.cpp @@ -22,7 +22,9 @@ #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 @@ -32,7 +34,8 @@ #include // Construct the class. -BookmarksDialog::BookmarksDialog(QIcon currentWebsiteFavorieIcon) : QDialog(nullptr), websiteFavoriteIcon(currentWebsiteFavorieIcon) +BookmarksDialog::BookmarksDialog(QString currentWebsiteTitle, QString currentWebsiteUrl, QIcon currentWebsiteFavorieIcon) : + QDialog(nullptr), websiteFavoriteIcon(currentWebsiteFavorieIcon), websiteTitle(currentWebsiteTitle), websiteUrl(currentWebsiteUrl) { // Set the dialog window title. setWindowTitle(i18nc("The bookmarks dialog window title", "Bookmarks")); @@ -71,13 +74,14 @@ BookmarksDialog::BookmarksDialog(QIcon currentWebsiteFavorieIcon) : QDialog(null 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; @@ -85,6 +89,7 @@ BookmarksDialog::BookmarksDialog(QIcon currentWebsiteFavorieIcon) : QDialog(null // 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())); @@ -101,16 +106,32 @@ BookmarksDialog::BookmarksDialog(QIcon currentWebsiteFavorieIcon) : QDialog(null 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 parentFolderIdList; + // Get the list of selected model indexes. QList 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(); @@ -124,38 +145,90 @@ void BookmarksDialog::populateBookmarks() const 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 bookmarksRootItemList; + + // Create the root items. + QStandardItem *rootItemNamePointer = new QStandardItem(QIcon::fromTheme("bookmarks"), 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 *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 *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 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. @@ -164,15 +237,18 @@ void BookmarksDialog::populateBookmarks() const // 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 @@ -184,10 +260,68 @@ void BookmarksDialog::refreshBookmarks() const 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() const { + // 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(websiteTitle, websiteUrl, websiteFavoriteIcon, parentFolderId); // Update the displayed bookmarks when a new one is added. connect(addBookmarkDialogPointer, SIGNAL(bookmarkAdded()), this, SLOT(refreshBookmarks())); @@ -196,19 +330,64 @@ void BookmarksDialog::showAddBookmarkDialog() const addBookmarkDialogPointer->show(); } +void BookmarksDialog::showAddFolderDialog() const +{ + // 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(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(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(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) @@ -223,7 +402,7 @@ 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()); @@ -238,17 +417,62 @@ void BookmarksDialog::updateBookmarkFromTree(QStandardItem *modifiedStandardItem 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. { diff --git a/src/dialogs/BookmarksDialog.h b/src/dialogs/BookmarksDialog.h index e81d0b4..78a4896 100644 --- a/src/dialogs/BookmarksDialog.h +++ b/src/dialogs/BookmarksDialog.h @@ -36,13 +36,15 @@ class BookmarksDialog : public QDialog public: // The primary constructor. - explicit BookmarksDialog(QIcon currentWebsiteFavoriteIcon); + explicit BookmarksDialog(QString currentWebsiteTitle, QString currentWebsiteUrl, QIcon currentWebsiteFavoriteIcon); // The public constants. - static const int BOOKMARK_NAME_COLUMN = 0; - static const int BOOKMARK_URL_COLUMN = 1; + static const int NAME_COLUMN = 0; + static const int URL_COLUMN = 1; static const int DATABASE_ID_COLUMN = 2; - static const int DISPLAY_ORDER = 3; + static const int DISPLAY_ORDER_COLUMN = 3; + static const int IS_FOLDER_COLUMN = 4; + static const int FOLDER_ID_COLUMN = 5; signals: // The signals. @@ -53,14 +55,12 @@ private Q_SLOTS: void deleteItems() const; void refreshBookmarks() const; void showAddBookmarkDialog() const; + void showAddFolderDialog() const; void showEditDialog(); void updateBookmarkFromTree(QStandardItem *modifiedStandardItem); - void updateUi() const; + void updateSelection() const; private: - // The private functions. - void populateBookmarks() const; - // The private variables. QPushButton *deleteItemsButtonPointer; QPushButton *editButtonPointer; @@ -68,5 +68,13 @@ private: 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 diff --git a/src/dialogs/CMakeLists.txt b/src/dialogs/CMakeLists.txt index a1cc4d0..4b72f5c 100644 --- a/src/dialogs/CMakeLists.txt +++ b/src/dialogs/CMakeLists.txt @@ -19,11 +19,13 @@ # List the sources to include in the executable. target_sources(privacybrowser PRIVATE AddBookmarkDialog.cpp + AddFolderDialog.cpp AddOrEditCookieDialog.cpp BookmarksDialog.cpp CookiesDialog.cpp DomainSettingsDialog.cpp DurableCookiesDialog.cpp EditBookmarkDialog.cpp + EditFolderDialog.cpp SaveDialog.cpp ) diff --git a/src/dialogs/EditBookmarkDialog.cpp b/src/dialogs/EditBookmarkDialog.cpp index 35582f1..6dd3f71 100644 --- a/src/dialogs/EditBookmarkDialog.cpp +++ b/src/dialogs/EditBookmarkDialog.cpp @@ -26,7 +26,7 @@ #include // Construct the class. -EditBookmarkDialog::EditBookmarkDialog(const int bookmarkId, QIcon ¤tWebsiteFavoriteIcon) : QDialog(nullptr), bookmarkDatabaseId(bookmarkId) +EditBookmarkDialog::EditBookmarkDialog(const int databaseId, QIcon ¤tWebsiteFavoriteIcon) : QDialog(nullptr), bookmarkDatabaseId(databaseId) { // Set the window title. setWindowTitle(i18nc("The edit bookmark dialog window title.", "Edit Bookmark")); @@ -34,7 +34,7 @@ EditBookmarkDialog::EditBookmarkDialog(const int bookmarkId, QIcon ¤tWebsi // Set the window modality. setWindowModality(Qt::WindowModality::ApplicationModal); - // Instantiate the bookmarks dialog UI. + // Instantiate the edit bookmark dialog UI. Ui::EditBookmarkDialog editBookmarkDialogUi; // Setup the UI. @@ -42,39 +42,82 @@ EditBookmarkDialog::EditBookmarkDialog(const int bookmarkId, QIcon ¤tWebsi // Get handles for the widgets. currentFavoriteIconRadioButtonPointer = editBookmarkDialogUi.currentFavoriteIconRadioButton; - currentWebsiteFavoritIconRadioButtonPointer = editBookmarkDialogUi.currentWebsiteFavoriteIconRadioButton; + currentWebsiteFavoriteIconRadioButtonPointer = editBookmarkDialogUi.currentWebsiteFavoriteIconRadioButton; customFavoriteIconRadioButtonPointer = editBookmarkDialogUi.customFavoriteIconRadioButton; - bookmarkNamePointer = editBookmarkDialogUi.bookmarkNameLineEdit; - bookmarkUrlPointer = editBookmarkDialogUi.bookmarkUrlLineEdit; + 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(bookmarkId); + bookmarkStructPointer = BookmarksDatabase::getBookmark(databaseId); // Set the favorite icons. currentFavoriteIconRadioButtonPointer->setIcon(bookmarkStructPointer->favoriteIcon); - currentWebsiteFavoritIconRadioButtonPointer->setIcon(currentWebsiteFavoriteIcon); + 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("bookmarks")); + 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. - bookmarkNamePointer->setText(bookmarkStructPointer->bookmarkName); - bookmarkUrlPointer->setText(bookmarkStructPointer->bookmarkUrl); + bookmarkNameLineEditPointer->setText(bookmarkStructPointer->name); + bookmarkUrlLineEditPointer->setText(bookmarkStructPointer->url); // Scroll to the beginning of the line edits. - bookmarkNamePointer->setCursorPosition(0); - bookmarkUrlPointer->setCursorPosition(0); + bookmarkNameLineEditPointer->setCursorPosition(0); + bookmarkUrlLineEditPointer->setCursorPosition(0); // 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, tr("Favorite Icon Image"), QDir::homePath(), - tr("Image Files — *.bmp, *.gif, *.jpg, *.jpeg, *.png, *.svg (*.bmp *.gif *.jpg *.jpeg *.png *.svg);;All Files (*)")); + 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()) @@ -89,31 +132,49 @@ void EditBookmarkDialog::browse() void EditBookmarkDialog::save() { + // Get the selected folders list. + QList 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 (currentWebsiteFavoritIconRadioButtonPointer->isChecked()) // The current website favorite icon is checked. - favoriteIcon = currentWebsiteFavoritIconRadioButtonPointer->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(); - qDebug() << "Favorite icon: " << favoriteIcon; - // Create a bookmark struct. - BookmarkStruct *bookmarkStructPointer = new BookmarkStruct; + BookmarkStruct *updatedBookmarkStructPointer = new BookmarkStruct; // Populate the bookmark struct. - bookmarkStructPointer->id = bookmarkDatabaseId; - bookmarkStructPointer->bookmarkName = bookmarkNamePointer->text(); - bookmarkStructPointer->bookmarkUrl = bookmarkUrlPointer->text(); - bookmarkStructPointer->displayOrder = bookmarkStructPointer->displayOrder; - bookmarkStructPointer->favoriteIcon = favoriteIcon; + 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(bookmarkStructPointer); + 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(); @@ -121,3 +182,18 @@ void EditBookmarkDialog::save() // 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 index c065581..bbba43e 100644 --- a/src/dialogs/EditBookmarkDialog.h +++ b/src/dialogs/EditBookmarkDialog.h @@ -21,6 +21,7 @@ #define EDITBOOKMARKDIALOG_H // Application headers. +#include "helpers/FolderHelper.h" #include "structs/BookmarkStruct.h" // Qt toolkit headers. @@ -35,7 +36,7 @@ class EditBookmarkDialog : public QDialog public: // The primary constructor. - explicit EditBookmarkDialog(const int bookmarkId, QIcon ¤tWebsiteFavoriteIcon); + explicit EditBookmarkDialog(const int databaseId, QIcon ¤tWebsiteFavoriteIcon); signals: // The signals. @@ -45,15 +46,21 @@ private Q_SLOTS: // The private slots. void browse(); void save(); + void updateUi(); private: + // The private variables. + FolderHelper *folderHelperPointer; + // The private widgets. int bookmarkDatabaseId; - QLineEdit *bookmarkNamePointer; + QLineEdit *bookmarkNameLineEditPointer; BookmarkStruct *bookmarkStructPointer; - QLineEdit *bookmarkUrlPointer; + QLineEdit *bookmarkUrlLineEditPointer; QRadioButton *currentFavoriteIconRadioButtonPointer; - QRadioButton *currentWebsiteFavoritIconRadioButtonPointer; + 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 index 0000000..4d332ce --- /dev/null +++ b/src/dialogs/EditFolderDialog.cpp @@ -0,0 +1,184 @@ +/* + * Copyright 2023 Soren Stoutner . + * + * This file is part of Privacy Browser PC . + * + * Privacy Browser PC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser PC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser PC. If not, see . + */ + +// Application headers. +#include "EditFolderDialog.h" +#include "ui_EditFolderDialog.h" +#include "databases/BookmarksDatabase.h" + +// Qt toolkit headers. +#include + +// Construct the class. +EditFolderDialog::EditFolderDialog(const int databaseId, QIcon ¤tWebsiteFavoriteIcon) : QDialog(nullptr), 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; + 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 bookmark struct. + bookmarkStructPointer = BookmarksDatabase::getBookmark(databaseId); + + // Set the folder icons. + currentFolderIconRadioButtonPointer->setIcon(bookmarkStructPointer->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("bookmarks")); + 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, except for the one being edited. + folderHelperPointer->populateSubfoldersExcept(databaseId, bookmarksTreeWidgetItemPointer, bookmarkStructPointer->parentFolderId); + + // Open all the folders. + parentFolderTreeWidgetPointer->expandAll(); + + // Populate the line edits. + folderNameLineEditPointer->setText(bookmarkStructPointer->name); + + // Scroll to the beginning of the line edits. + folderNameLineEditPointer->setCursorPosition(0); + + // 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 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 (currentFolderIconRadioButtonPointer->isChecked()) // The current folder icon is checked. + favoriteIcon = currentFolderIconRadioButtonPointer->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. + BookmarksDatabase::updateFolderContentsDisplayOrder(bookmarkStructPointer->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 index 0000000..ac203d8 --- /dev/null +++ b/src/dialogs/EditFolderDialog.h @@ -0,0 +1,64 @@ +/* + * Copyright 2023 Soren Stoutner . + * + * This file is part of Privacy Browser PC . + * + * Privacy Browser PC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser PC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser PC. If not, see . + */ + +#ifndef EDITFOLDERDIALOG_H +#define EDITFOLDERDIALOG_H + +// Application headers. +#include "helpers/FolderHelper.h" +#include "structs/BookmarkStruct.h" + +// Qt toolkit headers. +#include +#include + +class EditFolderDialog : public QDialog +{ + // Include the Q_OBJECT macro. + Q_OBJECT + +public: + // The primary constructor. + explicit EditFolderDialog(const int databaseId, QIcon ¤tWebsiteFavoriteIcon); + +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. + BookmarkStruct *bookmarkStructPointer; + QRadioButton *currentFolderIconRadioButtonPointer; + QRadioButton *currentWebsiteFavoriteIconRadioButtonPointer; + QRadioButton *customFolderIconRadioButtonPointer; + int folderDatabaseId; + QLineEdit *folderNameLineEditPointer; + QTreeWidget *parentFolderTreeWidgetPointer; + QPushButton *saveButtonPointer; +}; +#endif diff --git a/src/helpers/CMakeLists.txt b/src/helpers/CMakeLists.txt index a1a19de..921e0c0 100644 --- a/src/helpers/CMakeLists.txt +++ b/src/helpers/CMakeLists.txt @@ -18,6 +18,7 @@ # List the sources to include in the executable. 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 index 0000000..01a45da --- /dev/null +++ b/src/helpers/FolderHelper.cpp @@ -0,0 +1,86 @@ +/* + * Copyright 2023 Soren Stoutner . + * + * This file is part of Privacy Browser PC . + * + * Privacy Browser PC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser PC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser PC. If not, see . + */ + +// Application headers. +#include "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 *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 *subfoldersList = BookmarksDatabase::getSubfolders(treeWidgetItemPointer->text(FOLDER_ID_COLUMN).toDouble()); + + // Populate each subfolder. + for (BookmarkStruct bookmarkStruct : *subfoldersList) + { + // Only populate the subfolder if it is not excepted. + if (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. + + // 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 index 0000000..9248576 --- /dev/null +++ b/src/helpers/FolderHelper.h @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Soren Stoutner . + * + * This file is part of Privacy Browser PC . + * + * Privacy Browser PC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser PC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser PC. If not, see . + */ + +#ifndef FOLDERHELPER_H +#define FOLDERHELPER_H + +// Qt toolkit headers. +#include + +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 diff --git a/src/main.cpp b/src/main.cpp index c39eb88..726d3fb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,8 +26,8 @@ // KDE Frameworks headers. #include #include -#include #include +#include // Qt headers. #include diff --git a/src/structs/BookmarkStruct.h b/src/structs/BookmarkStruct.h index 98ac69a..d7e0a86 100644 --- a/src/structs/BookmarkStruct.h +++ b/src/structs/BookmarkStruct.h @@ -26,10 +26,13 @@ struct BookmarkStruct { - int id; - QString bookmarkName; - QString bookmarkUrl; + int databaseId; + QString name; + QString url; + double parentFolderId; int displayOrder; + bool isFolder; + double folderId; QIcon favoriteIcon; }; #endif diff --git a/src/ui.rcs/browserwindowui.rc b/src/ui.rcs/browserwindowui.rc index c0e5a19..1d61322 100644 --- a/src/ui.rcs/browserwindowui.rc +++ b/src/ui.rcs/browserwindowui.rc @@ -83,6 +83,10 @@ + + + + diff --git a/src/uis/AddBookmarkDialog.ui b/src/uis/AddBookmarkDialog.ui index 01f143c..0fdaf33 100644 --- a/src/uis/AddBookmarkDialog.ui +++ b/src/uis/AddBookmarkDialog.ui @@ -72,10 +72,19 @@ + + + + + + Qt::Vertical + + + - + @@ -84,6 +93,28 @@ Qt::AlignLeft + + + + + + + 1000 + 700 + + + + + + + + + + + + Qt::AlignLeft + + @@ -103,12 +134,13 @@ - + Qt::AlignLeft + @@ -123,21 +155,7 @@ - - - - 0 - 0 - - - - - - 700 - 0 - - - + @@ -146,15 +164,6 @@ - - - - - Qt::Vertical - - - - diff --git a/src/uis/AddFolderDialog.ui b/src/uis/AddFolderDialog.ui new file mode 100644 index 0000000..752059e --- /dev/null +++ b/src/uis/AddFolderDialog.ui @@ -0,0 +1,189 @@ + + + + + + AddFolderDialog + + + + + + + + + + + 10 + + + + + + + Default folder icon + + + + true + + + + + + + + + 32 + 32 + + + + + + + + + + Current website favorite icon + + + + + 32 + 32 + + + + + + + + + + Custom folder icon + + + + + + + + + 32 + 32 + + + + + + + + + + Qt::Vertical + + + + + + + + + + + + + + Qt::AlignLeft + + + + + + + + 1000 + 700 + + + + + + + + + + + + Qt::AlignLeft + + + + + + + The folder name. + + + + Folder name + + + + + + + + + + + + + + + + + + + + + + Browse + + + + + + + + + + + + + QDialogButtonBox::Cancel + + + + + + + + diff --git a/src/uis/BookmarksDialog.ui b/src/uis/BookmarksDialog.ui index b9355bb..cce8b2a 100644 --- a/src/uis/BookmarksDialog.ui +++ b/src/uis/BookmarksDialog.ui @@ -61,6 +61,19 @@ + + + + + Add folder + + + + + + + + diff --git a/src/uis/EditBookmarkDialog.ui b/src/uis/EditBookmarkDialog.ui index 9dab0e8..68a8413 100644 --- a/src/uis/EditBookmarkDialog.ui +++ b/src/uis/EditBookmarkDialog.ui @@ -88,10 +88,19 @@ + + + + + + Qt::Vertical + + + - + @@ -100,6 +109,28 @@ Qt::AlignLeft + + + + + + + 1000 + 700 + + + + + + + + + + + + Qt::AlignLeft + + @@ -125,6 +156,7 @@ Qt::AlignLeft + @@ -162,15 +194,6 @@ - - - - - Qt::Vertical - - - - diff --git a/src/uis/EditFolderDialog.ui b/src/uis/EditFolderDialog.ui new file mode 100644 index 0000000..545ed3c --- /dev/null +++ b/src/uis/EditFolderDialog.ui @@ -0,0 +1,185 @@ + + + + + + EditFolderDialog + + + + + + + + + + + 10 + + + + + + + Default folder icon + + + + true + + + + + 32 + 32 + + + + + + + + + + Current website favorite icon + + + + + 32 + 32 + + + + + + + + + + Custom folder icon + + + + + + + + + 32 + 32 + + + + + + + + + + Qt::Vertical + + + + + + + + + + + + + + Qt::AlignLeft + + + + + + + + 1000 + 700 + + + + + + + + + + + + Qt::AlignLeft + + + + + + + The folder name. + + + + Folder name + + + + + + + + + + + + + + + + + + + + + + Browse + + + + + + + + + + + + + QDialogButtonBox::Save | QDialogButtonBox::Cancel + + + + + + + + diff --git a/src/widgets/DraggableTreeView.cpp b/src/widgets/DraggableTreeView.cpp index 1fa07a5..6471b08 100644 --- a/src/widgets/DraggableTreeView.cpp +++ b/src/widgets/DraggableTreeView.cpp @@ -36,52 +36,105 @@ void DraggableTreeView::dropEvent(QDropEvent *dropEvent) // Get the list of currently selected items that are moving. QList indexesMovingList = selectedIndexes(); - // Create a list of database IDs that are moving. - QList *databaseIdsMovingListPointer = new QList; + // Create a list of selected database IDs. + QList *selectedDatabaseIdsListPointer = new QList; - // Populate the list of moving database IDs. + // 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::BOOKMARK_NAME_COLUMN) - databaseIdsMovingListPointer->append(modelIndex.siblingAtColumn(BookmarksDialog::DATABASE_ID_COLUMN).data().toInt()); + 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 *rootSelectedDatabaseIdsListPointer = getRootSelectedDatabaseIds(selectedDatabaseIdsListPointer); + // Get the drop position. int dropPosition = dropIndicatorPosition(); // Get the drop model index. QModelIndex dropModelIndex = indexAt(dropEvent->pos()); - // Get the drop display order. - int dropDisplayOrder = dropModelIndex.siblingAtColumn(BookmarksDialog::DISPLAY_ORDER).data().toInt(); + // Create a previous parent folder ID standard C++ list (which can be sorted and from which duplicates can be removed). + std::list previousParentFolderIdList; // Process the move according to the drop position. switch (dropPosition) { case QAbstractItemView::OnItem: - // TODO Implement for moving into a folder. + { + // 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 *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) + previousParentFolderIdList.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; + } break; + } case QAbstractItemView::AboveItem: { - // Get a list of all the bookmarks except those selected. - QList *bookmarksExceptSelectedListPointer = BookmarksDatabase::getBookmarksExcept(databaseIdsMovingListPointer); + // 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().toInt(); + + // Get a list of all the items in the target folder except those selected. + QList *itemsInFolderExceptSelectedListPointer = BookmarksDatabase::getBookmarksInFolderExcept(dropParentFolderId, selectedDatabaseIdsListPointer); // Initialize a new display order tracker. int newDisplayOrder = 0; - // Move the bookmarks. - for (const BookmarkStruct &bookmarkStruct : *bookmarksExceptSelectedListPointer) + // 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 : *databaseIdsMovingListPointer) + for (const int databaseId : *rootSelectedDatabaseIdsListPointer) { - // Update the display order for each bookmark. - BookmarksDatabase::updateDisplayOrder(databaseId, newDisplayOrder); + // 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) + previousParentFolderIdList.push_back(currentParentFolderId); + + // Update the parent folder and display order for each bookmark. + BookmarksDatabase::updateParentFolderAndDisplayOrder(databaseId, dropParentFolderId, newDisplayOrder); // Increment the new display order. ++newDisplayOrder; @@ -90,29 +143,34 @@ void DraggableTreeView::dropEvent(QDropEvent *dropEvent) // Set the bookmark's display order if it has changed. if (bookmarkStruct.displayOrder != newDisplayOrder) - BookmarksDatabase::updateDisplayOrder(bookmarkStruct.id, newDisplayOrder); + BookmarksDatabase::updateDisplayOrder(bookmarkStruct.databaseId, newDisplayOrder); // Increment the new display order. ++newDisplayOrder; } - break; } case QAbstractItemView::BelowItem: { - // Get a list of all the bookmarks except those selected. - QList *bookmarksExceptSelectedListPointer = BookmarksDatabase::getBookmarksExcept(databaseIdsMovingListPointer); + // 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().toInt(); + + // Get a list of all the items in the target folder except those selected. + QList *itemsInFolderExceptSelectedListPointer = BookmarksDatabase::getBookmarksInFolderExcept(dropParentFolderId, selectedDatabaseIdsListPointer); // Initialize a new display order tracker. int newDisplayOrder = 0; - // Move the bookmarks. - for (const BookmarkStruct &bookmarkStruct : *bookmarksExceptSelectedListPointer) + // 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.id, newDisplayOrder); + BookmarksDatabase::updateDisplayOrder(bookmarkStruct.databaseId, newDisplayOrder); // Increment the new display order. ++newDisplayOrder; @@ -121,51 +179,106 @@ void DraggableTreeView::dropEvent(QDropEvent *dropEvent) if (bookmarkStruct.displayOrder == dropDisplayOrder) { // Add all of the bookmarks being moved to this point. - for (const int databaseId : *databaseIdsMovingListPointer) + for (const int databaseId : *rootSelectedDatabaseIdsListPointer) { - // Update the display order for each bookmark. - BookmarksDatabase::updateDisplayOrder(databaseId, newDisplayOrder); + // 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) + previousParentFolderIdList.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 bookmarks except those selected. - QList *bookmarksExceptSelectedListPointer = BookmarksDatabase::getBookmarksExcept(databaseIdsMovingListPointer); + // Get a list of all the items in the root folder except those selected. + QList *itemsInFolderExceptSelectedListPointer = BookmarksDatabase::getBookmarksInFolderExcept(dropParentFolderId, selectedDatabaseIdsListPointer); // Initialize a new display order tracker. int newDisplayOrder = 0; - // Move the bookmarks. - for (const BookmarkStruct &bookmarkStruct : *bookmarksExceptSelectedListPointer) + // 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.id, 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 : *databaseIdsMovingListPointer) + for (const int databaseId : *rootSelectedDatabaseIdsListPointer) { - // Update the display order for each bookmark. - BookmarksDatabase::updateDisplayOrder(databaseId, newDisplayOrder); + // 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) + previousParentFolderIdList.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. + previousParentFolderIdList.sort(); + + // Remove duplicates from the parent folder ID list. + previousParentFolderIdList.unique(); + + // Update the folder contents display order for each previous parent folder. + for (const double parentFolderId : previousParentFolderIdList) + BookmarksDatabase::updateFolderContentsDisplayOrder(parentFolderId); + // Emit the bookmarks moved signal. emit bookmarksMoved(); } + +QList* DraggableTreeView::getRootSelectedDatabaseIds(QList *selectedDatabaseIdsPointer) const +{ + // Create a list of the database IDs of the contents of each selected folder. + QList 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* rootSelectedDatabaseIdsListPointer = new QList; + + // 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; +} diff --git a/src/widgets/DraggableTreeView.h b/src/widgets/DraggableTreeView.h index f520ed3..983ff1c 100644 --- a/src/widgets/DraggableTreeView.h +++ b/src/widgets/DraggableTreeView.h @@ -38,5 +38,8 @@ signals: protected: void dropEvent(QDropEvent *event) override; + +private: + QList* getRootSelectedDatabaseIds(QList *selectedDatabaseIdsPointer) const; }; #endif diff --git a/src/windows/BrowserWindow.cpp b/src/windows/BrowserWindow.cpp index 0fdfe26..f85726d 100644 --- a/src/windows/BrowserWindow.cpp +++ b/src/windows/BrowserWindow.cpp @@ -25,10 +25,12 @@ #include "ui_SettingsSpellCheck.h" #include "databases/BookmarksDatabase.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 "helpers/SearchEngineHelper.h" #include "helpers/UserAgentHelper.h" #include "structs/BookmarkStruct.h" @@ -45,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -110,6 +113,7 @@ BrowserWindow::BrowserWindow(bool firstWindow, QString *initialUrlStringPointer) searchEngineBingActionPointer = actionCollectionPointer->addAction(QLatin1String("search_engine_bing")); searchEngineYahooActionPointer = actionCollectionPointer->addAction(QLatin1String("search_engine_yahoo")); searchEngineCustomActionPointer = actionCollectionPointer->addAction(QLatin1String("search_engine_custom")); + QAction *addFolderPointer = actionCollectionPointer->addAction(QLatin1String("add_folder")); viewBookmarksToolBarActionPointer = actionCollectionPointer->addAction(QLatin1String("view_bookmarks_toolbar")); QAction *domainSettingsActionPointer = actionCollectionPointer->addAction(QLatin1String("domain_settings")); cookiesActionPointer = actionCollectionPointer->addAction(QLatin1String("cookies")); @@ -189,6 +193,7 @@ BrowserWindow::BrowserWindow(bool firstWindow, QString *initialUrlStringPointer) searchEngineGoogleActionPointer->setText(i18nc("Search engine", "Google")); searchEngineBingActionPointer->setText(i18nc("Search engine", "Bing")); searchEngineYahooActionPointer->setText(i18nc("Search engine", "Yahoo")); + addFolderPointer->setText(i18nc("Add folder", "Add Folder")); 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)); @@ -223,6 +228,7 @@ BrowserWindow::BrowserWindow(bool firstWindow, QString *initialUrlStringPointer) 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"))); + addFolderPointer->setIcon(QIcon::fromTheme(QLatin1String("folder-add"))); viewBookmarksToolBarActionPointer->setIcon(QIcon::fromTheme(QLatin1String("bookmarks"))); 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")))); @@ -259,6 +265,7 @@ BrowserWindow::BrowserWindow(bool firstWindow, QString *initialUrlStringPointer) 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 altFKeySequence = QKeySequence(i18nc("The add folder key sequence.", "Alt+F")); 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+;")); @@ -293,6 +300,7 @@ BrowserWindow::BrowserWindow(bool firstWindow, QString *initialUrlStringPointer) actionCollectionPointer->setDefaultShortcut(searchEngineYahooActionPointer, ctrlShiftYKeySequence); actionCollectionPointer->setDefaultShortcut(searchEngineCustomActionPointer, ctrlShiftCKeySequence); actionCollectionPointer->setDefaultShortcut(editBookmarksActionPointer, ctrlAltShiftBKeySequence); + actionCollectionPointer->setDefaultShortcut(addFolderPointer, altFKeySequence); actionCollectionPointer->setDefaultShortcut(viewBookmarksToolBarActionPointer, ctrlAltBKeySequence); actionCollectionPointer->setDefaultShortcut(domainSettingsActionPointer, ctrlShiftDKeySequence); actionCollectionPointer->setDefaultShortcut(cookiesActionPointer, ctrlSemicolonKeySequence); @@ -305,6 +313,7 @@ BrowserWindow::BrowserWindow(bool firstWindow, QString *initialUrlStringPointer) connect(viewSourceActionPointer, SIGNAL(triggered()), this, SLOT(toggleViewSource())); connect(viewSourceInNewTabActionPointer, SIGNAL(triggered()), this, SLOT(toggleViewSourceInNewTab())); connect(zoomFactorActionPointer, SIGNAL(triggered()), this, SLOT(getZoomFactorFromUser())); + connect(addFolderPointer, SIGNAL(triggered()), this, SLOT(showAddFolderDialog())); connect(viewBookmarksToolBarActionPointer, SIGNAL(triggered()), this, SLOT(toggleViewBookmarksToolBar())); connect(cookiesActionPointer, SIGNAL(triggered()), this, SLOT(showCookiesDialog())); connect(domainSettingsActionPointer, SIGNAL(triggered()), this, SLOT(showDomainSettingsDialog())); @@ -509,8 +518,10 @@ BrowserWindow::BrowserWindow(bool firstWindow, QString *initialUrlStringPointer) bookmarksMenuPointer->addSeparator(); // Initialize the current bookmarks lists. - bookmarksMenuCurrentActionList = QList(); - bookmarksToolBarCurrentActionList = QList(); + bookmarksMenuActionList = QList *>(); + bookmarksMenuSubmenuList = QList *>(); + bookmarksToolBarActionList = QList(); + bookmarksToolBarSubfolderActionList = QList *>(); // Set the bookmarks toolbar context menu policy. bookmarksToolBarPointer->setContextMenuPolicy(Qt::CustomContextMenu); @@ -606,7 +617,7 @@ void BrowserWindow::decrementZoom() void BrowserWindow::editBookmarks() const { // Instantiate an edit bookmarks dialog. - BookmarksDialog *bookmarksDialogPointer = new BookmarksDialog(tabWidgetPointer->getCurrentTabFavoritIcon()); + BookmarksDialog *bookmarksDialogPointer = new BookmarksDialog(tabWidgetPointer->getCurrentTabTitle(), tabWidgetPointer->getCurrentTabUrl(), tabWidgetPointer->getCurrentTabFavoritIcon()); // Update the displayed bookmarks when edited. connect(bookmarksDialogPointer, SIGNAL(bookmarkUpdated()), this, SLOT(populateBookmarks())); @@ -797,71 +808,366 @@ void BrowserWindow::newWindow() const void BrowserWindow::populateBookmarks() { // Remove all the current menu bookmarks. - for (QAction *bookmarkAction : bookmarksMenuCurrentActionList) + for (QPair *bookmarkPairPointer : bookmarksMenuActionList) { // Remove the bookmark. - bookmarksMenuPointer->removeAction(bookmarkAction); + bookmarkPairPointer->first->removeAction(bookmarkPairPointer->second); + } + + // Remove all the current menu subfolders. + for (QPair *submenuPairPointer : bookmarksMenuSubmenuList) + { + // Remove the submenu from the parent menu. + submenuPairPointer->first->removeAction(submenuPairPointer->second->menuAction()); + } + + // Remove all the current toolbar subfolders. + for (QPair *subfolderPairPointer : bookmarksToolBarSubfolderActionList) + { + // Remove the action from the subfolder. + subfolderPairPointer->first->removeAction(subfolderPairPointer->second); } // Remove all the current toolbar bookmarks. - for (QAction *bookmarkAction : bookmarksToolBarCurrentActionList) + for (QAction *bookmarkAction : bookmarksToolBarActionList) { // Remove the bookmark. bookmarksToolBarPointer->removeAction(bookmarkAction); } // Clear the current bookmark lists. - bookmarksMenuCurrentActionList.clear(); - bookmarksToolBarCurrentActionList.clear(); + bookmarksMenuActionList.clear(); + bookmarksMenuSubmenuList.clear(); + bookmarksToolBarActionList.clear(); + bookmarksToolBarSubfolderActionList.clear(); - // Get a list of the bookmarks. - std::list *bookmarkListPointer = BookmarksDatabase::getBookmarks(); + // Populate the bookmarks subfolders, beginning with the root folder (`0`); + populateBookmarksMenuSubfolders(0, bookmarksMenuPointer); - // Populate the bookmarks menu. - for (BookmarkStruct bookmarkStruct : *bookmarkListPointer) + // 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); +} + +void BrowserWindow::populateBookmarksMenuSubfolders(const double folderId, QMenu *menuPointer) +{ + // Get the folder contents. + QList *folderContentsListPointer = BookmarksDatabase::getFolderContents(folderId); + + // Populate the bookmarks menu and toolbar. + for (BookmarkStruct bookmarkStruct : *folderContentsListPointer) { - // Get the bookmark URL. - QString bookmarkUrl = bookmarkStruct.bookmarkUrl; + // 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(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(menuPointer, menuBookmarkActionPointer)); + } + } +} + +void BrowserWindow::populateBookmarksToolBar() +{ + // Get the root folder contents (which has a folder ID of `0`). + QList *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(subfolderMenuPointer, bookmarkStruct.folderId)); + + // Set the popup mode for the menu. + dynamic_cast(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 *menuAndFolderIdPairPointer : bookmarksToolBarMenuList) + { + // Add a separator. + menuAndFolderIdPairPointer->first->addSeparator(); + + // Add the open folder in new tabs action to the menu. + menuAndFolderIdPairPointer->first->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 *folderUrlsListPointer = BookmarksDatabase::getAllFolderUrls(menuAndFolderIdPairPointer->second); + + // Open the URLs in new tabs. `true` removes the URL line edit focus, `false` does not load a background tab. + for (QString url : *folderUrlsListPointer) + tabWidgetPointer->addTab(true, false, url); + } + ); + + // Add the open folder in background tabs action to the menu. + menuAndFolderIdPairPointer->first->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 *folderUrlsListPointer = BookmarksDatabase::getAllFolderUrls(menuAndFolderIdPairPointer->second); + + // Open the URLs in new tabs. `true` removes the URL line edit focus, `true` loads a background tab. + for (QString url : *folderUrlsListPointer) + tabWidgetPointer->addTab(true, true, url); + } + ); + + // Add the open folder in new window action to the menu. + menuAndFolderIdPairPointer->first->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 *folderUrlsListPointer = BookmarksDatabase::getAllFolderUrls(menuAndFolderIdPairPointer->second); + + // Create a new browser window. + BrowserWindow *browserWindowPointer = new BrowserWindow(false, &folderUrlsListPointer->first()); + + // Get a count of the folder URLs. + const int folderUrls = folderUrlsListPointer->count(); - // Add the bookmark to the menu. - QAction *menuBookmarkActionPointer = bookmarksMenuPointer->addAction(bookmarkStruct.favoriteIcon, bookmarkStruct.bookmarkName, [=] + // Load all the other URLs. `true` removes the URL line edit focus, `true` loads a background tab. + for (int i = 1; i < folderUrls; ++i) + browserWindowPointer->tabWidgetPointer->addTab(true, true, folderUrlsListPointer->value(i)); + + // Show the new browser window. + browserWindowPointer->show(); + } + ); + + // Add a separator. + menuAndFolderIdPairPointer->first->addSeparator(); + + // Add the add bookmark action to the menu. + menuAndFolderIdPairPointer->first->addAction(QIcon::fromTheme(QLatin1String("bookmark-new")), i18nc("The add bookmark action", "Add Bookmark"), [=] { - // Remove the focus from the URL line edit. - urlLineEditPointer->clearFocus(); + // Instantiate an add bookmark dialog. + AddBookmarkDialog *addBookmarkDialogPointer = new AddBookmarkDialog(tabWidgetPointer->getCurrentTabTitle(), tabWidgetPointer->getCurrentTabUrl(), + tabWidgetPointer->getCurrentTabFavoritIcon(), menuAndFolderIdPairPointer->second); + + // Update the displayed bookmarks when a new one is added. + connect(addBookmarkDialogPointer, SIGNAL(bookmarkAdded()), this, SLOT(populateBookmarks())); - // Load the URL. - tabWidgetPointer->loadUrlFromLineEdit(bookmarkUrl); + // Show the dialog. + addBookmarkDialogPointer->show(); } ); - // Add the bookmark to the toolbar. - QAction *toolBarBookmarkActionPointer = bookmarksToolBarPointer->addAction(bookmarkStruct.favoriteIcon, bookmarkStruct.bookmarkName, [=] + // Add the add folder action to the menu. + menuAndFolderIdPairPointer->first->addAction(QIcon::fromTheme(QLatin1String("folder-add")), i18nc("The add folder action", "Add Folder"), [=] { - // Remove the focus from the URL line edit. - urlLineEditPointer->clearFocus(); + // Instantiate an add folder dialog. + AddFolderDialog *addFolderDialogPointer = new AddFolderDialog(tabWidgetPointer->getCurrentTabFavoritIcon(), menuAndFolderIdPairPointer->second); + + // Update the displayed bookmarks when a folder is added. + connect(addFolderDialogPointer, SIGNAL(folderAdded()), this, SLOT(populateBookmarks())); + + // Show the dialog. + addFolderDialogPointer->show(); + } + ); + + // Add a separator. + menuAndFolderIdPairPointer->first->addSeparator(); + + // Add the edit folder action to the menu. + menuAndFolderIdPairPointer->first->addAction(QIcon::fromTheme(QLatin1String("edit-entry")), i18nc("The edit folder action", "Edit Folder"), [=] + { + // Get the current tab favorite icon. + QIcon currentTabFavoriteIcon = tabWidgetPointer->getCurrentTabFavoritIcon(); - // Load the URL. - tabWidgetPointer->loadUrlFromLineEdit(bookmarkUrl); + // Instantiate an edit folder dialog. + QDialog *editFolderDialogPointer = new EditFolderDialog(BookmarksDatabase::getFolderDatabaseId(menuAndFolderIdPairPointer->second), currentTabFavoriteIcon); + + // Show the dialog. + editFolderDialogPointer->show(); + + // Process bookmark events. + connect(editFolderDialogPointer, SIGNAL(folderSaved()), this, SLOT(populateBookmarks())); } ); - // Add the bookmark database ID to the toolbar action. - toolBarBookmarkActionPointer->setData(bookmarkStruct.id); + // Add the delete folder action to the menu. + menuAndFolderIdPairPointer->first->addAction(QIcon::fromTheme(QLatin1String("delete")), i18nc("Delete folder context menu entry", "Delete Folder"), [=] + { + // Get the folder database ID. + int folderDatabaseId = BookmarksDatabase::getFolderDatabaseId(menuAndFolderIdPairPointer->second); + + // Create an items to delete list. + QList* itemsToDeleteListPointer = new QList; + + // Add the folder to the list of items to delete. + itemsToDeleteListPointer->append(folderDatabaseId); + + // Add the folder contents to the list of items to delete. + itemsToDeleteListPointer->append(*BookmarksDatabase::getFolderContentsDatabaseIdsRecursively(menuAndFolderIdPairPointer->second)); + + // 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")); - // Add the actions to the current bookmarks lists. - bookmarksMenuCurrentActionList.append(menuBookmarkActionPointer); - bookmarksToolBarCurrentActionList.append(toolBarBookmarkActionPointer); + // 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. + populateBookmarks(); + } + } + ); } +} - // Get a handle for the bookmark toolbar layout. - QLayout *bookmarksToolBarLayoutPointer = bookmarksToolBarPointer->layout(); +void BrowserWindow::populateBookmarksToolBarSubfolders(const double folderId, QMenu *menuPointer) +{ + // Get the folder contents. + QList *folderContentsListPointer = BookmarksDatabase::getFolderContents(folderId); - // Get the count of the bookmarks. - int bookmarkCount = bookmarksToolBarLayoutPointer->count(); + // Populate the bookmarks folder. + for (BookmarkStruct bookmarkStruct : *folderContentsListPointer) + { + // Get the bookmark URL. + QString bookmarkUrl = bookmarkStruct.url; - // Set the layout of each bookmark to be left aligned. - for(int i = 0; i < bookmarkCount; ++i) - bookmarksToolBarLayoutPointer->itemAt(i)->setAlignment(Qt::AlignLeft); + // 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(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(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(menuPointer, toolBarBookmarkActionPointer)); + } + } } void BrowserWindow::refresh() const @@ -894,103 +1200,285 @@ void BrowserWindow::showAddBookmarkDialog() const addBookmarkDialogPointer->show(); } +void BrowserWindow::showAddFolderDialog() const +{ + // Instantiate an add folder dialog. + AddFolderDialog *addFolderDialogPointer = new AddFolderDialog(tabWidgetPointer->getCurrentTabFavoritIcon()); + + // Update the displayed bookmarks when a folder is added. + connect(addFolderDialogPointer, SIGNAL(folderAdded()), this, SLOT(populateBookmarks())); + + // Show the dialog. + addFolderDialogPointer->show(); +} + void BrowserWindow::showBookmarkContextMenu(const QPoint &point) { // Get the bookmark action. QAction *bookmarkActionPointer = bookmarksToolBarPointer->actionAt(point); - // Check to see if an bookmark was clicked. - if (bookmarkActionPointer) // A bookmark was clicked. + // 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 bookmark ID from the action. - int bookmarkId = bookmarkActionPointer->data().toInt(); - - // 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(bookmarkId); - - // Open the bookmark in a new tab. `true` removes the URL line edit focus, `false` does not load a background tab. - tabWidgetPointer->addTab(true, false, bookmarkStructPointer->bookmarkUrl); - } - ); + // Get the database ID from the action. + int databaseId = bookmarkActionPointer->data().toInt(); - // 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(bookmarkId); - - // Open the bookmark in a new tab. `true` removes the URL line edit focus, `true` loads a background tab. - tabWidgetPointer->addTab(true, true, bookmarkStructPointer->bookmarkUrl); - } - ); - - // 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(bookmarkId); - - // Create a new browser window. - BrowserWindow *browserWindowPointer = new BrowserWindow(false, &bookmarkStructPointer->bookmarkUrl); + // Create the menu according to the type. + if (BookmarksDatabase::isFolder(databaseId)) // A folder was clicked. + { + // Get the folder ID. + double folderId = BookmarksDatabase::getFolderId(databaseId); + + // Add the open folder in new tabs action to the menu. + bookmarkContextMenuPointer->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 *folderUrlsListPointer = BookmarksDatabase::getAllFolderUrls(folderId); + + // Open the URLs in new tabs. `true` removes the URL line edit focus, `false` does not load a background tab. + for (QString url : *folderUrlsListPointer) + tabWidgetPointer->addTab(true, false, url); + } + ); + + // Add the open folder in background tabs action to the menu. + bookmarkContextMenuPointer->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 *folderUrlsListPointer = BookmarksDatabase::getAllFolderUrls(folderId); + + // Open the URLs in new tabs. `true` removes the URL line edit focus, `true` loads a background tab. + for (QString url : *folderUrlsListPointer) + tabWidgetPointer->addTab(true, true, url); + } + ); + + // Add the open folder in new window action to the menu. + bookmarkContextMenuPointer->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 *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(); - // Show the new browser window. - browserWindowPointer->show(); - } - ); + // Load all the other URLs. `true` removes the URL line edit focus, `true` loads a background tab. + for (int i = 1; i < folderUrls; ++i) + browserWindowPointer->tabWidgetPointer->addTab(true, true, folderUrlsListPointer->value(i)); + + // Show the new browser window. + browserWindowPointer->show(); + } + ); + + // Add a separator. + bookmarkContextMenuPointer->addSeparator(); + + // Add the add bookmark action to the menu. + bookmarkContextMenuPointer->addAction(QIcon::fromTheme(QLatin1String("bookmark-new")), i18nc("The add bookmark action", "Add Bookmark"), [=] + { + // Instantiate an add bookmark dialog. + AddBookmarkDialog *addBookmarkDialogPointer = new AddBookmarkDialog(tabWidgetPointer->getCurrentTabTitle(), tabWidgetPointer->getCurrentTabUrl(), + tabWidgetPointer->getCurrentTabFavoritIcon(), folderId); + + // Update the displayed bookmarks when a new one is added. + connect(addBookmarkDialogPointer, SIGNAL(bookmarkAdded()), this, SLOT(populateBookmarks())); + + // Show the dialog. + addBookmarkDialogPointer->show(); + } + ); + + // Add the add folder action to the menu. + bookmarkContextMenuPointer->addAction(QIcon::fromTheme(QLatin1String("folder-add")), i18nc("The add folder action", "Add Folder"), [=] + { + // Instantiate an add folder dialog. + AddFolderDialog *addFolderDialogPointer = new AddFolderDialog(tabWidgetPointer->getCurrentTabFavoritIcon(), folderId); + + // Update the displayed bookmarks when a folder is added. + connect(addFolderDialogPointer, SIGNAL(folderAdded()), this, SLOT(populateBookmarks())); + + // Show the dialog. + addFolderDialogPointer->show(); + } + ); + + // Add a separator. + bookmarkContextMenuPointer->addSeparator(); + + // Add the edit folder action to the menu. + bookmarkContextMenuPointer->addAction(QIcon::fromTheme(QLatin1String("edit-entry")), 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(BookmarksDatabase::getFolderDatabaseId(folderId), currentTabFavoriteIcon); + + // Show the dialog. + editFolderDialogPointer->show(); + + // Process bookmark events. + connect(editFolderDialogPointer, SIGNAL(folderSaved()), this, SLOT(populateBookmarks())); + } + ); + + // Add the delete folder action to the menu. + bookmarkContextMenuPointer->addAction(QIcon::fromTheme(QLatin1String("delete")), i18nc("Delete folder context menu entry", "Delete Folder"), [=] + { + // Get the folder database ID. + int folderDatabaseId = BookmarksDatabase::getFolderDatabaseId(folderId); + + // Create an items to delete list. + QList* itemsToDeleteListPointer = new QList; + + // Add the folder to the list of items to delete. + itemsToDeleteListPointer->append(folderDatabaseId); + + // Add the folder contents to the list of items to delete. + itemsToDeleteListPointer->append(*BookmarksDatabase::getFolderContentsDatabaseIdsRecursively(folderId)); - // Add a separator. - bookmarkContextMenuPointer->addSeparator(); + // Instantiate a delete dialog message box. + QMessageBox deleteDialogMessageBox; + + // Set the icon. + deleteDialogMessageBox.setIcon(QMessageBox::Warning); - // Add the edit action to the menu. - bookmarkContextMenuPointer->addAction(QIcon::fromTheme(QLatin1String("edit-entry")), i18nc("Edit bookmark context menu entry", "Edit"), [=] - { - // Get the current tab favorite icon. - QIcon currentTabFavoriteIcon = tabWidgetPointer->getCurrentTabFavoritIcon(); + // Set the window title. + deleteDialogMessageBox.setWindowTitle(i18nc("Delete bookmarks dialog title", "Delete Bookmarks")); - // Instantiate an edit bookmark dialog. - QDialog *editBookmarkDialogPointer = new EditBookmarkDialog(bookmarkId, currentTabFavoriteIcon); + // Set the text. + deleteDialogMessageBox.setText(i18ncp("Delete bookmarks dialog main message", "Delete %1 bookmark item?", "Delete %1 bookmark items?", itemsToDeleteListPointer->count())); - // Show the dialog. - editBookmarkDialogPointer->show(); + // Set the informative text. + deleteDialogMessageBox.setInformativeText(i18nc("Delete bookmarks dialog secondary message", "This cannot be undone.")); - // Process bookmark events. - connect(editBookmarkDialogPointer, SIGNAL(bookmarkSaved()), this, SLOT(populateBookmarks())); - } - ); + // Set the standard buttons. + deleteDialogMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - // 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(bookmarkId); + // Set the default button. + deleteDialogMessageBox.setDefaultButton(QMessageBox::No); - // Get a handle for the clipboard. - QClipboard *clipboard = QGuiApplication::clipboard(); + // Display the dialog and capture the return value. + int returnValue = deleteDialogMessageBox.exec(); - // Place the URL on the keyboard. - clipboard->setText(bookmarkStructPointer->bookmarkUrl); - } - ); + // Delete the domain if instructed. + if (returnValue == QMessageBox::Yes) + { + // Get the parent folder ID. + double parentFolderId = BookmarksDatabase::getParentFolderId(folderDatabaseId); - // Add a separator. - bookmarkContextMenuPointer->addSeparator(); + // Delete the folder and its contents. + for (const int databaseId : *itemsToDeleteListPointer) + BookmarksDatabase::deleteBookmark(databaseId); - // Add the delete action to the menu. - bookmarkContextMenuPointer->addAction(QIcon::fromTheme(QLatin1String("delete")), i18nc("Delete bookmark context menu entry", "Delete"), [=] - { - // Delete the bookmark. - BookmarksDatabase::deleteBookmark(bookmarkId); + // Update the display order of the bookmarks in the parent folder. + BookmarksDatabase::updateFolderContentsDisplayOrder(parentFolderId); - // Repopulate the bookmarks. - populateBookmarks(); - } - ); + // Repopulate the bookmarks. + populateBookmarks(); + } + } + ); + } + 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, `false` does not load a background tab. + tabWidgetPointer->addTab(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` loads a background tab. + tabWidgetPointer->addTab(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("edit-entry")), 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(databaseId, currentTabFavoriteIcon); + + // Show the dialog. + editBookmarkDialogPointer->show(); + + // Process bookmark events. + connect(editBookmarkDialogPointer, SIGNAL(bookmarkSaved()), this, SLOT(populateBookmarks())); + } + ); + + // 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("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. + populateBookmarks(); + } + ); + } // Delete the menu from memory when it is closed. bookmarkContextMenuPointer->setAttribute(Qt::WA_DeleteOnClose); diff --git a/src/windows/BrowserWindow.h b/src/windows/BrowserWindow.h index 73ac519..eb6f82e 100644 --- a/src/windows/BrowserWindow.h +++ b/src/windows/BrowserWindow.h @@ -71,6 +71,7 @@ private Q_SLOTS: void refresh() const; void reloadAndBypassCache() const; void showAddBookmarkDialog() const; + void showAddFolderDialog() const; void showBookmarkContextMenu(const QPoint &point); void showCookiesDialog(); void showDownloadLocationBrowseDialog() const; @@ -106,10 +107,13 @@ private Q_SLOTS: private: // The private variables. - QList bookmarksMenuCurrentActionList; + QList *> bookmarksMenuActionList; QMenu *bookmarksMenuPointer; - QList bookmarksToolBarCurrentActionList; + QList *> bookmarksMenuSubmenuList; + QList bookmarksToolBarActionList; + QList *> bookmarksToolBarMenuList; KToolBar *bookmarksToolBarPointer; + QList *> bookmarksToolBarSubfolderActionList; bool bookmarksToolBarIsVisible = false; bool bookmarksToolBarUninitialized = true; KConfigDialog *configDialogPointer; @@ -171,5 +175,10 @@ private: QPushButton *zoomMinusButtonPointer; QAction *zoomOutActionPointer; QPushButton *zoomPlusButtonPointer; + + // The private functions. + void populateBookmarksMenuSubfolders(const double folderId, QMenu *menuPointer); + void populateBookmarksToolBar(); + void populateBookmarksToolBarSubfolders(const double folderId, QMenu *menuPointer); }; #endif -- 2.45.2