2 * Copyright 2023 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
6 * Privacy Browser PC is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser PC is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser PC. If not, see <http://www.gnu.org/licenses/>.
20 // Application headers.
21 #include "BookmarksDialog.h"
22 #include "ui_BookmarksDialog.h"
23 #include "databases/BookmarksDatabase.h"
24 #include "dialogs/AddBookmarkDialog.h"
25 #include "dialogs/AddFolderDialog.h"
26 #include "dialogs/EditBookmarkDialog.h"
27 #include "dialogs/EditFolderDialog.h"
29 // KDE Frameworks headers.
30 #include <KLocalizedString>
32 // Qt toolkit headers.
34 #include <QStandardItemModel>
36 // Construct the class.
37 BookmarksDialog::BookmarksDialog(QString currentWebsiteTitle, QString currentWebsiteUrl, QIcon currentWebsiteFavorieIcon) :
38 QDialog(nullptr), websiteFavoriteIcon(currentWebsiteFavorieIcon), websiteTitle(currentWebsiteTitle), websiteUrl(currentWebsiteUrl)
40 // Set the dialog window title.
41 setWindowTitle(i18nc("The bookmarks dialog window title", "Bookmarks"));
43 // Set the window modality.
44 setWindowModality(Qt::WindowModality::ApplicationModal);
46 // Instantiate the bookmarks settings dialog UI.
47 Ui::BookmarksDialog bookmarksDialogUi;
50 bookmarksDialogUi.setupUi(this);
52 // Get a handle for the draggable tree view.
53 draggableTreeViewPointer = bookmarksDialogUi.draggableTreeView;
55 // Initialize the tree model.
56 treeModelPointer = new QStandardItemModel();
58 // Auto resize the headers.
59 draggableTreeViewPointer->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
61 // Indicate that all the rows are the same height, which improves performance.
62 draggableTreeViewPointer->setUniformRowHeights(true);
64 // Set the selection mode to allow multiple rows to be selected at once.
65 draggableTreeViewPointer->setSelectionMode(QAbstractItemView::ExtendedSelection);
67 // Allow dragging of bookmarks to reorder.
68 draggableTreeViewPointer->setDragDropMode(QAbstractItemView::InternalMove);
70 // Set the tree model.
71 draggableTreeViewPointer->setModel(treeModelPointer);
73 // Get a handle for the tree selection model.
74 treeSelectionModelPointer = draggableTreeViewPointer->selectionModel();
76 // Listen for selection changes.
77 connect(treeSelectionModelPointer, SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(updateSelection()));
79 // Repopulate the bookmarks when they are moved.
80 connect(draggableTreeViewPointer, SIGNAL(bookmarksMoved()), this, SLOT(refreshBookmarks()));
82 // Get handles for the buttons.
83 QPushButton *addBookmarkButtonPointer = bookmarksDialogUi.addBookmarkButton;
84 QPushButton *addFolderButtonPointer = bookmarksDialogUi.addFolderButton;
85 editButtonPointer = bookmarksDialogUi.editButton;
86 deleteItemsButtonPointer = bookmarksDialogUi.deleteItemsButton;
87 QDialogButtonBox *dialogButtonBoxPointer = bookmarksDialogUi.dialogButtonBox;
88 QPushButton *closeButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::Close);
90 // Connect the buttons.
91 connect(addBookmarkButtonPointer, SIGNAL(clicked()), this, SLOT(showAddBookmarkDialog()));
92 connect(addFolderButtonPointer, SIGNAL(clicked()), this, SLOT(showAddFolderDialog()));
93 connect(editButtonPointer, SIGNAL(clicked()), this, SLOT(showEditDialog()));
94 connect(deleteItemsButtonPointer, SIGNAL(clicked()), this, SLOT(deleteItems()));
95 connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(reject()));
97 // Set the close button to be the default.
98 closeButtonPointer->setDefault(true);
100 // Monitor editing of data in the tree model.
101 connect(treeModelPointer, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(updateBookmarkFromTree(QStandardItem*)));
103 // Populate the bookmarks.
107 void BookmarksDialog::deleteItems() const
109 // Create a parent folder ID standard C++ list (which can be sorted and from which duplicates can be removed).
110 std::list<double> parentFolderIdList;
112 // Get the list of selected model indexes.
113 QList<QModelIndex> selectedModelIndexList = treeSelectionModelPointer->selectedRows(DATABASE_ID_COLUMN);
115 // Delete each of the bookmarks.
116 for (const QModelIndex &modelIndex : selectedModelIndexList)
118 // Add the parent folder ID to the list.
119 parentFolderIdList.push_back(modelIndex.parent().siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble());
121 // Delete the bookmark.
122 BookmarksDatabase::deleteBookmark(modelIndex.data().toInt());
125 // Sort the parent folder ID.
126 parentFolderIdList.sort();
128 // Remove duplicates from the parent folder ID list.
129 parentFolderIdList.unique();
131 // Update the folder contents display order for each folder with a deleted bookmark.
132 for (const double parentFolderId : parentFolderIdList)
133 BookmarksDatabase::updateFolderContentsDisplayOrder(parentFolderId);
135 // Repopulate the bookmarks in this dialog
138 // Emit the bookmark updated signal to redraw the bookmarks in the menu and toolbar.
139 emit bookmarkUpdated();
142 void BookmarksDialog::populateBookmarks() const
144 // Clear the current contents of the tree model.
145 treeModelPointer->clear();
147 // Set the column count.
148 treeModelPointer->setColumnCount(6);
150 // Set the tree header data.
151 treeModelPointer->setHeaderData(NAME_COLUMN, Qt::Horizontal, i18nc("The bookmark Name header.", "Name"));
152 treeModelPointer->setHeaderData(URL_COLUMN, Qt::Horizontal, i18nc("The bookmark URL header.", "URL"));
154 // Hide the backend columns.
155 draggableTreeViewPointer->setColumnHidden(DATABASE_ID_COLUMN, true);
156 draggableTreeViewPointer->setColumnHidden(DISPLAY_ORDER_COLUMN, true);
157 draggableTreeViewPointer->setColumnHidden(IS_FOLDER_COLUMN, true);
158 draggableTreeViewPointer->setColumnHidden(FOLDER_ID_COLUMN, true);
160 // Create a bookmarks root item list.
161 QList<QStandardItem*> bookmarksRootItemList;
163 // Create the root items.
164 QStandardItem *rootItemNamePointer = new QStandardItem(QIcon::fromTheme("bookmarks"), i18nc("The bookmarks root tree widget name", "Bookmarks"));
165 QStandardItem *rootItemUrlPointer = new QStandardItem(QLatin1String(""));
166 QStandardItem *rootItemDatabaseIdPonter = new QStandardItem(QLatin1String("-1")); // The root item doesn't have a database ID.
167 QStandardItem *rootItemDisplayOrderPointer = new QStandardItem(QLatin1String("-1")); // The root item doesn't have a display order.
168 QStandardItem *rootItemIsFolderPointer = new QStandardItem(QLatin1String("1"));
169 QStandardItem *rootItemFolderIdPointer = new QStandardItem(QLatin1String("0"));
171 // Enable dropping on the root name column.
172 rootItemNamePointer->setDropEnabled(true);
174 // Disable dropping on the URL.
175 rootItemUrlPointer->setDropEnabled(false);
177 // Disable dragging of the bookmarks root item.
178 rootItemNamePointer->setDragEnabled(false);
179 rootItemUrlPointer->setDragEnabled(false);
181 // Disable selecting the URL.
182 rootItemUrlPointer->setSelectable(false);
184 // Populate the bookmarks root item list.
185 bookmarksRootItemList.append(rootItemNamePointer);
186 bookmarksRootItemList.append(rootItemUrlPointer);
187 bookmarksRootItemList.append(rootItemDatabaseIdPonter);
188 bookmarksRootItemList.append(rootItemDisplayOrderPointer);
189 bookmarksRootItemList.append(rootItemIsFolderPointer);
190 bookmarksRootItemList.append(rootItemFolderIdPointer);
192 // Add the bookmarks root item to the tree.
193 treeModelPointer->appendRow(bookmarksRootItemList);
195 // Populate the subfolders, starting with the root folder ID (`0`).
196 populateSubfolders(rootItemNamePointer, 0);
198 // Expand all the folder.
199 draggableTreeViewPointer->expandAll();
205 void BookmarksDialog::populateSubfolders(QStandardItem *folderItemNamePointer, const double folderId) const
207 // Get the folder contents.
208 QList<BookmarkStruct> *folderContentsListPointer = BookmarksDatabase::getFolderContents(folderId);
210 // Populate the bookmarks tree view.
211 for (const BookmarkStruct &bookmarkStruct : *folderContentsListPointer)
213 // Create a bookmark item list.
214 QList<QStandardItem*> bookmarkItemList;
216 // Create the bookmark items.
217 QStandardItem *nameItemPointer = new QStandardItem(bookmarkStruct.favoriteIcon, bookmarkStruct.name);
218 QStandardItem *urlItemPointer = new QStandardItem(bookmarkStruct.url);
219 QStandardItem *databaseIdItemPointer = new QStandardItem(QString::number(bookmarkStruct.databaseId));
220 QStandardItem *displayOrderItemPointer = new QStandardItem(QString::number(bookmarkStruct.displayOrder));
221 QStandardItem *isFolderItemPointer = new QStandardItem(QString::number(bookmarkStruct.isFolder));
222 QStandardItem *folderIdItemPointer = new QStandardItem(QString::number(bookmarkStruct.folderId, 'f', 0)); // Format the folder ID as a floating point with no trailing zeros.
224 // Enable dragging and dropping of the name column.
225 nameItemPointer->setDragEnabled(true);
227 // Only allow dropping on the name if this is a folder.
228 nameItemPointer->setDropEnabled(bookmarkStruct.isFolder);
230 // Disable dragging and dropping on the URL.
231 urlItemPointer->setDragEnabled(false);
232 urlItemPointer->setDropEnabled(false);
234 // Disable selecting the URL.
235 urlItemPointer->setSelectable(false);
237 // Populate the bookmark item list.
238 bookmarkItemList.append(nameItemPointer);
239 bookmarkItemList.append(urlItemPointer);
240 bookmarkItemList.append(databaseIdItemPointer);
241 bookmarkItemList.append(displayOrderItemPointer);
242 bookmarkItemList.append(isFolderItemPointer);
243 bookmarkItemList.append(folderIdItemPointer);
245 // Add the bookmark to the parent folder.
246 folderItemNamePointer->appendRow(bookmarkItemList);
248 // Populate subfolders if this item is a folder.
249 if (bookmarkStruct.isFolder)
250 populateSubfolders(nameItemPointer, bookmarkStruct.folderId);
254 void BookmarksDialog::refreshBookmarks() const
256 // Repopulate the bookmarks in this dialog
259 // Emit the bookmark updated signal to redraw the bookmarks in the menu and toolbar.
260 emit bookmarkUpdated();
263 void BookmarksDialog::selectSubfolderContents(const QModelIndex &parentModelIndex) const
265 // Get the index model.
266 const QAbstractItemModel *modelIndexAbstractItemPointer = parentModelIndex.model();
268 // Get the number of items in the folder.
269 int numberOfChildrenInFolder = modelIndexAbstractItemPointer->rowCount(parentModelIndex);
271 // Select any child items.
272 if (numberOfChildrenInFolder > 0)
274 // Select the contents of any subfolders.
275 for (int i = 0; i < numberOfChildrenInFolder; ++i)
277 // Get the child model index.
278 QModelIndex childModelIndex = modelIndexAbstractItemPointer->index(i, 0, parentModelIndex);
280 // Select the subfolder contents if it is a folder.
281 if (childModelIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toBool())
282 selectSubfolderContents(childModelIndex);
285 // Get the first and last child model indexes.
286 QModelIndex firstChildModelIndex = modelIndexAbstractItemPointer->index(0, 0, parentModelIndex);
287 QModelIndex lastChildModelIndex = modelIndexAbstractItemPointer->index((numberOfChildrenInFolder - 1), 0, parentModelIndex);
289 // Create an item selection that includes all the child items.
290 QItemSelection folderChildItemsSelection = QItemSelection(firstChildModelIndex, lastChildModelIndex);
292 // Get the current selection.
293 QItemSelection currentSelection = treeSelectionModelPointer->selection();
295 // Combine the current selection and the folder child items selection.
296 currentSelection.merge(folderChildItemsSelection, QItemSelectionModel::SelectCurrent);
298 // Selected the updated list of items.
299 treeSelectionModelPointer->select(currentSelection, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
303 void BookmarksDialog::showAddBookmarkDialog() const
305 // Return the most recently selected index.
306 QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
308 // Instantiate a parent folder ID.
309 double parentFolderId;
311 // Get the parent folder ID.
312 if (currentIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toInt() == 1) // The current index is a folder.
314 // Store the parent folder ID.
315 parentFolderId = currentIndex.siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
317 else // The current index is not a folder.
319 // Store the parent folder ID of the folder that contains the bookmark.
320 parentFolderId = currentIndex.parent().siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
323 // Instantiate an add bookmark dialog.
324 AddBookmarkDialog *addBookmarkDialogPointer = new AddBookmarkDialog(websiteTitle, websiteUrl, websiteFavoriteIcon, parentFolderId);
326 // Update the displayed bookmarks when a new one is added.
327 connect(addBookmarkDialogPointer, SIGNAL(bookmarkAdded()), this, SLOT(refreshBookmarks()));
330 addBookmarkDialogPointer->show();
333 void BookmarksDialog::showAddFolderDialog() const
335 // Get the most recently selected index.
336 QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
338 // Instantiate a parent folder ID.
339 double parentFolderId;
341 // Get the parent folder ID.
342 if (currentIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toInt() == 1) // The current index is a folder.
344 // Store the parent folder ID.
345 parentFolderId = currentIndex.siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
347 else // The current index is not a folder.
349 // Store the parent folder ID of the folder that contains the bookmark.
350 parentFolderId = currentIndex.parent().siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
353 // Instantiate an add folder dialog.
354 AddFolderDialog *addFolderDialogPointer = new AddFolderDialog(websiteFavoriteIcon, parentFolderId);
356 // Update the displayed bookmarks when a folder is added.
357 connect(addFolderDialogPointer, SIGNAL(folderAdded()), this, SLOT(refreshBookmarks()));
360 addFolderDialogPointer->show();
363 void BookmarksDialog::showEditDialog()
365 // Get the current model index.
366 QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
368 // Check to see if the selected item is a folder.
369 if (currentIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toInt() == 1) // The selected item is a folder.
371 // Instantiate an edit folder dialog.
372 QDialog *editFolderDialogPointer = new EditFolderDialog(currentIndex.siblingAtColumn(DATABASE_ID_COLUMN).data().toInt(), websiteFavoriteIcon);
375 editFolderDialogPointer->show();
377 // Update the bookmarks UI.
378 connect(editFolderDialogPointer, SIGNAL(folderSaved()), this, SLOT(refreshBookmarks()));
380 else // The selected item is a bookmark.
382 // Instantiate an edit bookmark dialog.
383 QDialog *editBookmarkDialogPointer = new EditBookmarkDialog(currentIndex.siblingAtColumn(DATABASE_ID_COLUMN).data().toInt(), websiteFavoriteIcon);
386 editBookmarkDialogPointer->show();
388 // Update the bookmarks UI.
389 connect(editBookmarkDialogPointer, SIGNAL(bookmarkSaved()), this, SLOT(refreshBookmarks()));
393 void BookmarksDialog::updateBookmarkFromTree(QStandardItem *modifiedStandardItem)
395 // Get the model index of the modified item.
396 QModelIndex modifiedItemModelIndex = modifiedStandardItem->index();
398 // Get the model index of the database ID.
399 QModelIndex databaseIdModelIndex = modifiedItemModelIndex.siblingAtColumn(DATABASE_ID_COLUMN);
401 // Get the database ID.
402 int databaseId = databaseIdModelIndex.data().toInt();
404 // Check to see if the bookmark name or the URL was edited.
405 if (modifiedStandardItem->column() == NAME_COLUMN) // The bookmark name was edited.
407 // Update the bookmark name.
408 BookmarksDatabase::updateBookmarkName(databaseId, modifiedStandardItem->text());
410 else // The bookmark URL was edited.
412 // Update the bookmark URL.
413 BookmarksDatabase::updateBookmarkUrl(databaseId, modifiedStandardItem->text());
416 // Emit the bookmark updated signal.
417 emit bookmarkUpdated();
420 void BookmarksDialog::updateSelection() const
422 // Set the status of the buttons.
423 if (treeSelectionModelPointer->hasSelection()) // A bookmark or folder is selected.
425 // Get the list of selected model indexes.
426 QModelIndexList selectedRowsModelIndexList = treeSelectionModelPointer->selectedRows();
428 // Check to see if each selected item is a folder.
429 for(QModelIndex modelIndex : selectedRowsModelIndexList)
431 // If it is a folder, select all the children bookmarks.
432 if (modelIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toBool())
433 selectSubfolderContents(modelIndex);
441 void BookmarksDialog::updateUi() const
443 // Set the status of the buttons.
444 if (treeSelectionModelPointer->hasSelection()) // A bookmark or folder is selected.
446 // Get the currently selected index.
447 QModelIndex currentSelectedIndex = treeSelectionModelPointer->currentIndex();
449 // Get the list of selected model indexes.
450 QModelIndexList selectedRowsModelIndexList = treeSelectionModelPointer->selectedRows();
452 // Get the number of selected rows.
453 int numberOfSelectedRows = selectedRowsModelIndexList.count();
455 // Enable the edit button if a folder or only one bookmark is selected.
456 editButtonPointer->setEnabled(currentSelectedIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toBool() || (numberOfSelectedRows == 1));
458 // Check if the root folder is selected.
459 if (treeSelectionModelPointer->isRowSelected(0))
461 // Disable the edit button.
462 editButtonPointer->setEnabled(false);
464 // Decrease the number of selected rows by 1.
465 --numberOfSelectedRows;
468 // Enabled the delete button if at least one real bookmark or folder is selected.
469 deleteItemsButtonPointer->setEnabled(numberOfSelectedRows > 0);
471 // Update the delete items button text.
472 if (numberOfSelectedRows > 0)
473 deleteItemsButtonPointer->setText(i18ncp("Delete items button populated text.", "Delete %1 item", "Delete %1 items", numberOfSelectedRows));
475 deleteItemsButtonPointer->setText(i18nc("Delete items button default text", "Delete items"));
477 else // Nothing is selected.
479 // Disable the buttons.
480 editButtonPointer->setEnabled(false);
481 deleteItemsButtonPointer->setEnabled(false);
483 // Update the delete items button text.
484 deleteItemsButtonPointer->setText(i18nc("Delete items button default text", "Delete items"));