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(QLatin1String("bookmarks"), QIcon::fromTheme(QLatin1String("bookmark-new"))),
165 i18nc("The bookmarks root tree widget name", "Bookmarks"));
166 QStandardItem *rootItemUrlPointer = new QStandardItem(QLatin1String(""));
167 QStandardItem *rootItemDatabaseIdPonter = new QStandardItem(QLatin1String("-1")); // The root item doesn't have a database ID.
168 QStandardItem *rootItemDisplayOrderPointer = new QStandardItem(QLatin1String("-1")); // The root item doesn't have a display order.
169 QStandardItem *rootItemIsFolderPointer = new QStandardItem(QLatin1String("1"));
170 QStandardItem *rootItemFolderIdPointer = new QStandardItem(QLatin1String("0"));
172 // Enable dropping on the root name column.
173 rootItemNamePointer->setDropEnabled(true);
175 // Disable dropping on the URL.
176 rootItemUrlPointer->setDropEnabled(false);
178 // Disable dragging of the bookmarks root item.
179 rootItemNamePointer->setDragEnabled(false);
180 rootItemUrlPointer->setDragEnabled(false);
182 // Disable selecting the URL.
183 rootItemUrlPointer->setSelectable(false);
185 // Populate the bookmarks root item list.
186 bookmarksRootItemList.append(rootItemNamePointer);
187 bookmarksRootItemList.append(rootItemUrlPointer);
188 bookmarksRootItemList.append(rootItemDatabaseIdPonter);
189 bookmarksRootItemList.append(rootItemDisplayOrderPointer);
190 bookmarksRootItemList.append(rootItemIsFolderPointer);
191 bookmarksRootItemList.append(rootItemFolderIdPointer);
193 // Add the bookmarks root item to the tree.
194 treeModelPointer->appendRow(bookmarksRootItemList);
196 // Populate the subfolders, starting with the root folder ID (`0`).
197 populateSubfolders(rootItemNamePointer, 0);
199 // Expand all the folder.
200 draggableTreeViewPointer->expandAll();
206 void BookmarksDialog::populateSubfolders(QStandardItem *folderItemNamePointer, const double folderId) const
208 // Get the folder contents.
209 QList<BookmarkStruct> *folderContentsListPointer = BookmarksDatabase::getFolderContents(folderId);
211 // Populate the bookmarks tree view.
212 for (const BookmarkStruct &bookmarkStruct : *folderContentsListPointer)
214 // Create a bookmark item list.
215 QList<QStandardItem*> bookmarkItemList;
217 // Create the bookmark items.
218 QStandardItem *nameItemPointer = new QStandardItem(bookmarkStruct.favoriteIcon, bookmarkStruct.name);
219 QStandardItem *urlItemPointer = new QStandardItem(bookmarkStruct.url);
220 QStandardItem *databaseIdItemPointer = new QStandardItem(QString::number(bookmarkStruct.databaseId));
221 QStandardItem *displayOrderItemPointer = new QStandardItem(QString::number(bookmarkStruct.displayOrder));
222 QStandardItem *isFolderItemPointer = new QStandardItem(QString::number(bookmarkStruct.isFolder));
223 QStandardItem *folderIdItemPointer = new QStandardItem(QString::number(bookmarkStruct.folderId, 'f', 0)); // Format the folder ID as a floating point with no trailing zeros.
225 // Enable dragging and dropping of the name column.
226 nameItemPointer->setDragEnabled(true);
228 // Only allow dropping on the name if this is a folder.
229 nameItemPointer->setDropEnabled(bookmarkStruct.isFolder);
231 // Disable dragging and dropping on the URL.
232 urlItemPointer->setDragEnabled(false);
233 urlItemPointer->setDropEnabled(false);
235 // Disable selecting the URL.
236 urlItemPointer->setSelectable(false);
238 // Populate the bookmark item list.
239 bookmarkItemList.append(nameItemPointer);
240 bookmarkItemList.append(urlItemPointer);
241 bookmarkItemList.append(databaseIdItemPointer);
242 bookmarkItemList.append(displayOrderItemPointer);
243 bookmarkItemList.append(isFolderItemPointer);
244 bookmarkItemList.append(folderIdItemPointer);
246 // Add the bookmark to the parent folder.
247 folderItemNamePointer->appendRow(bookmarkItemList);
249 // Populate subfolders if this item is a folder.
250 if (bookmarkStruct.isFolder)
251 populateSubfolders(nameItemPointer, bookmarkStruct.folderId);
255 void BookmarksDialog::refreshBookmarks() const
257 // Repopulate the bookmarks in this dialog
260 // Emit the bookmark updated signal to redraw the bookmarks in the menu and toolbar.
261 emit bookmarkUpdated();
264 void BookmarksDialog::selectSubfolderContents(const QModelIndex &parentModelIndex) const
266 // Get the index model.
267 const QAbstractItemModel *modelIndexAbstractItemPointer = parentModelIndex.model();
269 // Get the number of items in the folder.
270 int numberOfChildrenInFolder = modelIndexAbstractItemPointer->rowCount(parentModelIndex);
272 // Select any child items.
273 if (numberOfChildrenInFolder > 0)
275 // Select the contents of any subfolders.
276 for (int i = 0; i < numberOfChildrenInFolder; ++i)
278 // Get the child model index.
279 QModelIndex childModelIndex = modelIndexAbstractItemPointer->index(i, 0, parentModelIndex);
281 // Select the subfolder contents if it is a folder.
282 if (childModelIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toBool())
283 selectSubfolderContents(childModelIndex);
286 // Get the first and last child model indexes.
287 QModelIndex firstChildModelIndex = modelIndexAbstractItemPointer->index(0, 0, parentModelIndex);
288 QModelIndex lastChildModelIndex = modelIndexAbstractItemPointer->index((numberOfChildrenInFolder - 1), 0, parentModelIndex);
290 // Create an item selection that includes all the child items.
291 QItemSelection folderChildItemsSelection = QItemSelection(firstChildModelIndex, lastChildModelIndex);
293 // Get the current selection.
294 QItemSelection currentSelection = treeSelectionModelPointer->selection();
296 // Combine the current selection and the folder child items selection.
297 currentSelection.merge(folderChildItemsSelection, QItemSelectionModel::SelectCurrent);
299 // Selected the updated list of items.
300 treeSelectionModelPointer->select(currentSelection, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
304 void BookmarksDialog::showAddBookmarkDialog() const
306 // Return the most recently selected index.
307 QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
309 // Instantiate a parent folder ID.
310 double parentFolderId;
312 // Get the parent folder ID.
313 if (currentIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toInt() == 1) // The current index is a folder.
315 // Store the parent folder ID.
316 parentFolderId = currentIndex.siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
318 else // The current index is not a folder.
320 // Store the parent folder ID of the folder that contains the bookmark.
321 parentFolderId = currentIndex.parent().siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
324 // Instantiate an add bookmark dialog.
325 AddBookmarkDialog *addBookmarkDialogPointer = new AddBookmarkDialog(websiteTitle, websiteUrl, websiteFavoriteIcon, parentFolderId);
327 // Update the displayed bookmarks when a new one is added.
328 connect(addBookmarkDialogPointer, SIGNAL(bookmarkAdded()), this, SLOT(refreshBookmarks()));
331 addBookmarkDialogPointer->show();
334 void BookmarksDialog::showAddFolderDialog() const
336 // Get the most recently selected index.
337 QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
339 // Instantiate a parent folder ID.
340 double parentFolderId;
342 // Get the parent folder ID.
343 if (currentIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toInt() == 1) // The current index is a folder.
345 // Store the parent folder ID.
346 parentFolderId = currentIndex.siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
348 else // The current index is not a folder.
350 // Store the parent folder ID of the folder that contains the bookmark.
351 parentFolderId = currentIndex.parent().siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
354 // Instantiate an add folder dialog.
355 AddFolderDialog *addFolderDialogPointer = new AddFolderDialog(websiteFavoriteIcon, parentFolderId);
357 // Update the displayed bookmarks when a folder is added.
358 connect(addFolderDialogPointer, SIGNAL(folderAdded()), this, SLOT(refreshBookmarks()));
361 addFolderDialogPointer->show();
364 void BookmarksDialog::showEditDialog()
366 // Get the current model index.
367 QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
369 // Check to see if the selected item is a folder.
370 if (currentIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toInt() == 1) // The selected item is a folder.
372 // Instantiate an edit folder dialog.
373 QDialog *editFolderDialogPointer = new EditFolderDialog(currentIndex.siblingAtColumn(DATABASE_ID_COLUMN).data().toInt(), websiteFavoriteIcon);
376 editFolderDialogPointer->show();
378 // Update the bookmarks UI.
379 connect(editFolderDialogPointer, SIGNAL(folderSaved()), this, SLOT(refreshBookmarks()));
381 else // The selected item is a bookmark.
383 // Instantiate an edit bookmark dialog.
384 QDialog *editBookmarkDialogPointer = new EditBookmarkDialog(currentIndex.siblingAtColumn(DATABASE_ID_COLUMN).data().toInt(), websiteFavoriteIcon);
387 editBookmarkDialogPointer->show();
389 // Update the bookmarks UI.
390 connect(editBookmarkDialogPointer, SIGNAL(bookmarkSaved()), this, SLOT(refreshBookmarks()));
394 void BookmarksDialog::updateBookmarkFromTree(QStandardItem *modifiedStandardItem)
396 // Get the model index of the modified item.
397 QModelIndex modifiedItemModelIndex = modifiedStandardItem->index();
399 // Get the model index of the database ID.
400 QModelIndex databaseIdModelIndex = modifiedItemModelIndex.siblingAtColumn(DATABASE_ID_COLUMN);
402 // Get the database ID.
403 int databaseId = databaseIdModelIndex.data().toInt();
405 // Check to see if the bookmark name or the URL was edited.
406 if (modifiedStandardItem->column() == NAME_COLUMN) // The bookmark name was edited.
408 // Update the bookmark name.
409 BookmarksDatabase::updateBookmarkName(databaseId, modifiedStandardItem->text());
411 else // The bookmark URL was edited.
413 // Update the bookmark URL.
414 BookmarksDatabase::updateBookmarkUrl(databaseId, modifiedStandardItem->text());
417 // Emit the bookmark updated signal.
418 emit bookmarkUpdated();
421 void BookmarksDialog::updateSelection() const
423 // Set the status of the buttons.
424 if (treeSelectionModelPointer->hasSelection()) // A bookmark or folder is selected.
426 // Get the list of selected model indexes.
427 QModelIndexList selectedRowsModelIndexList = treeSelectionModelPointer->selectedRows();
429 // Check to see if each selected item is a folder.
430 for(QModelIndex modelIndex : selectedRowsModelIndexList)
432 // If it is a folder, select all the children bookmarks.
433 if (modelIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toBool())
434 selectSubfolderContents(modelIndex);
442 void BookmarksDialog::updateUi() const
444 // Set the status of the buttons.
445 if (treeSelectionModelPointer->hasSelection()) // A bookmark or folder is selected.
447 // Get the currently selected index.
448 QModelIndex currentSelectedIndex = treeSelectionModelPointer->currentIndex();
450 // Get the list of selected model indexes.
451 QModelIndexList selectedRowsModelIndexList = treeSelectionModelPointer->selectedRows();
453 // Get the number of selected rows.
454 int numberOfSelectedRows = selectedRowsModelIndexList.count();
456 // Enable the edit button if a folder or only one bookmark is selected.
457 editButtonPointer->setEnabled(currentSelectedIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toBool() || (numberOfSelectedRows == 1));
459 // Check if the root folder is selected.
460 if (treeSelectionModelPointer->isRowSelected(0))
462 // Disable the edit button.
463 editButtonPointer->setEnabled(false);
465 // Decrease the number of selected rows by 1.
466 --numberOfSelectedRows;
469 // Enabled the delete button if at least one real bookmark or folder is selected.
470 deleteItemsButtonPointer->setEnabled(numberOfSelectedRows > 0);
472 // Update the delete items button text.
473 if (numberOfSelectedRows > 0)
474 deleteItemsButtonPointer->setText(i18ncp("Delete items button populated text.", "Delete %1 item", "Delete %1 items", numberOfSelectedRows));
476 deleteItemsButtonPointer->setText(i18nc("Delete items button default text", "Delete items"));
478 else // Nothing is selected.
480 // Disable the buttons.
481 editButtonPointer->setEnabled(false);
482 deleteItemsButtonPointer->setEnabled(false);
484 // Update the delete items button text.
485 deleteItemsButtonPointer->setText(i18nc("Delete items button default text", "Delete items"));