]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blob - src/dialogs/BookmarksDialog.cpp
Add a default folder icon to the edit folder dialog. https://redmine.stoutner.com...
[PrivacyBrowserPC.git] / src / dialogs / BookmarksDialog.cpp
1 /*
2  * Copyright 2023-2024 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
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"
28
29 // KDE Frameworks headers.
30 #include <KLocalizedString>
31
32 // Qt toolkit headers.
33 #include <QDebug>
34 #include <QStandardItemModel>
35
36 // Construct the class.
37 BookmarksDialog::BookmarksDialog(QWidget *parentWidgetPointer, QIcon currentWebsiteFavorieIcon, QString currentWebsiteTitle, QString currentWebsiteUrl) :
38                                  QDialog(parentWidgetPointer), websiteFavoriteIcon(currentWebsiteFavorieIcon), websiteTitle(currentWebsiteTitle), websiteUrl(currentWebsiteUrl)
39 {
40     // Set the dialog window title.
41     setWindowTitle(i18nc("The bookmarks dialog window title", "Bookmarks"));
42
43     // Set the window modality.
44     setWindowModality(Qt::WindowModality::ApplicationModal);
45
46     // Instantiate the bookmarks settings dialog UI.
47     Ui::BookmarksDialog bookmarksDialogUi;
48
49     // Setup the UI.
50     bookmarksDialogUi.setupUi(this);
51
52     // Get a handle for the draggable tree view.
53     draggableTreeViewPointer = bookmarksDialogUi.draggableTreeView;
54
55     // Initialize the tree model.
56     treeModelPointer = new QStandardItemModel();
57
58     // Auto resize the headers.
59     draggableTreeViewPointer->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
60
61     // Indicate that all the rows are the same height, which improves performance.
62     draggableTreeViewPointer->setUniformRowHeights(true);
63
64     // Set the selection mode to allow multiple rows to be selected at once.
65     draggableTreeViewPointer->setSelectionMode(QAbstractItemView::ExtendedSelection);
66
67     // Allow dragging of bookmarks to reorder.
68     draggableTreeViewPointer->setDragDropMode(QAbstractItemView::InternalMove);
69
70     // Set the tree model.
71     draggableTreeViewPointer->setModel(treeModelPointer);
72
73     // Get a handle for the tree selection model.
74     treeSelectionModelPointer = draggableTreeViewPointer->selectionModel();
75
76     // Listen for selection changes.
77     connect(treeSelectionModelPointer, SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(updateSelection()));
78
79     // Repopulate the bookmarks when they are moved.
80     connect(draggableTreeViewPointer, SIGNAL(bookmarksMoved()), this, SLOT(refreshBookmarks()));
81
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);
89
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()));
96
97     // Set the close button to be the default.
98     closeButtonPointer->setDefault(true);
99
100     // Monitor editing of data in the tree model.
101     connect(treeModelPointer, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(updateBookmarkFromTree(QStandardItem*)));
102
103     // Populate the bookmarks.
104     populateBookmarks();
105 }
106
107 void BookmarksDialog::deleteItems() const
108 {
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;
111
112     // Get the list of selected model indexes.
113     QList<QModelIndex> selectedModelIndexList = treeSelectionModelPointer->selectedRows(DATABASE_ID_COLUMN);
114
115     // Delete each of the bookmarks.
116     for (const QModelIndex &modelIndex : selectedModelIndexList)
117     {
118         // Add the parent folder ID to the list.
119         parentFolderIdList.push_back(modelIndex.parent().siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble());
120
121         // Delete the bookmark.
122         BookmarksDatabase::deleteBookmark(modelIndex.data().toInt());
123     }
124
125     // Sort the parent folder ID.
126     parentFolderIdList.sort();
127
128     // Remove duplicates from the parent folder ID list.
129     parentFolderIdList.unique();
130
131     // Update the folder contents display order for each folder with a deleted bookmark.
132     for (const double parentFolderId : parentFolderIdList)
133         BookmarksDatabase::updateFolderContentsDisplayOrder(parentFolderId);
134
135     // Repopulate the bookmarks in this dialog
136     populateBookmarks();
137
138     // Emit the bookmark updated signal to redraw the bookmarks in the menu and toolbar.
139     emit bookmarkUpdated();
140 }
141
142 void BookmarksDialog::populateBookmarks() const
143 {
144     // Clear the current contents of the tree model.
145     treeModelPointer->clear();
146
147     // Set the column count.
148     treeModelPointer->setColumnCount(6);
149
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"));
153
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);
159
160     // Create a bookmarks root item list.
161     QList<QStandardItem*> bookmarksRootItemList;
162
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"));
171
172     // Enable dropping on the root name column.
173     rootItemNamePointer->setDropEnabled(true);
174
175     // Disable dropping on the URL.
176     rootItemUrlPointer->setDropEnabled(false);
177
178     // Disable dragging of the bookmarks root item.
179     rootItemNamePointer->setDragEnabled(false);
180     rootItemUrlPointer->setDragEnabled(false);
181
182     // Disable selecting the URL.
183     rootItemUrlPointer->setSelectable(false);
184
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);
192
193     // Add the bookmarks root item to the tree.
194     treeModelPointer->appendRow(bookmarksRootItemList);
195
196     // Populate the subfolders, starting with the root folder ID (`0`).
197     populateSubfolders(rootItemNamePointer, 0);
198
199     // Expand all the folder.
200     draggableTreeViewPointer->expandAll();
201
202     // Update the UI.
203     updateUi();
204 }
205
206 void BookmarksDialog::populateSubfolders(QStandardItem *folderItemNamePointer, const double folderId) const
207 {
208     // Get the folder contents.
209     QList<BookmarkStruct> *folderContentsListPointer = BookmarksDatabase::getFolderContents(folderId);
210
211     // Populate the bookmarks tree view.
212     for (const BookmarkStruct &bookmarkStruct : *folderContentsListPointer)
213     {
214         // Create a bookmark item list.
215         QList<QStandardItem*> bookmarkItemList;
216
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.
224
225         // Enable dragging and dropping of the name column.
226         nameItemPointer->setDragEnabled(true);
227
228         // Only allow dropping on the name if this is a folder.
229         nameItemPointer->setDropEnabled(bookmarkStruct.isFolder);
230
231         // Disable dragging and dropping on the URL.
232         urlItemPointer->setDragEnabled(false);
233         urlItemPointer->setDropEnabled(false);
234
235         // Disable selecting the URL.
236         urlItemPointer->setSelectable(false);
237
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);
245
246         // Add the bookmark to the parent folder.
247         folderItemNamePointer->appendRow(bookmarkItemList);
248
249         // Populate subfolders if this item is a folder.
250         if (bookmarkStruct.isFolder)
251             populateSubfolders(nameItemPointer, bookmarkStruct.folderId);
252     }
253 }
254
255 void BookmarksDialog::refreshBookmarks() const
256 {
257     // Repopulate the bookmarks in this dialog
258     populateBookmarks();
259
260     // Emit the bookmark updated signal to redraw the bookmarks in the menu and toolbar.
261     emit bookmarkUpdated();
262 }
263
264 void BookmarksDialog::selectSubfolderContents(const QModelIndex &parentModelIndex) const
265 {
266     // Get the index model.
267     const QAbstractItemModel *modelIndexAbstractItemPointer = parentModelIndex.model();
268
269     // Get the number of items in the folder.
270     int numberOfChildrenInFolder = modelIndexAbstractItemPointer->rowCount(parentModelIndex);
271
272     // Select any child items.
273     if (numberOfChildrenInFolder > 0)
274     {
275         // Select the contents of any subfolders.
276         for (int i = 0; i < numberOfChildrenInFolder; ++i)
277         {
278             // Get the child model index.
279             QModelIndex childModelIndex = modelIndexAbstractItemPointer->index(i, 0, parentModelIndex);
280
281             // Select the subfolder contents if it is a folder.
282             if (childModelIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toBool())
283                 selectSubfolderContents(childModelIndex);
284         }
285
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);
289
290         // Create an item selection that includes all the child items.
291         QItemSelection folderChildItemsSelection = QItemSelection(firstChildModelIndex, lastChildModelIndex);
292
293         // Get the current selection.
294         QItemSelection currentSelection = treeSelectionModelPointer->selection();
295
296         // Combine the current selection and the folder child items selection.
297         currentSelection.merge(folderChildItemsSelection, QItemSelectionModel::SelectCurrent);
298
299         // Selected the updated list of items.
300         treeSelectionModelPointer->select(currentSelection, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
301     }
302 }
303
304 void BookmarksDialog::showAddBookmarkDialog()
305 {
306     // Return the most recently selected index.
307     QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
308
309     // Instantiate a parent folder ID.
310     double parentFolderId;
311
312     // Get the parent folder ID.
313     if (currentIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toInt() == 1)  // The current index is a folder.
314     {
315         // Store the parent folder ID.
316         parentFolderId = currentIndex.siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
317     }
318     else  // The current index is not a folder.
319     {
320         // Store the parent folder ID of the folder that contains the bookmark.
321         parentFolderId = currentIndex.parent().siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
322     }
323
324     // Instantiate an add bookmark dialog.
325     AddBookmarkDialog *addBookmarkDialogPointer = new AddBookmarkDialog(this, websiteTitle, websiteUrl, websiteFavoriteIcon, parentFolderId);
326
327     // Update the displayed bookmarks when a new one is added.
328     connect(addBookmarkDialogPointer, SIGNAL(bookmarkAdded()), this, SLOT(refreshBookmarks()));
329
330     // Show the dialog.
331     addBookmarkDialogPointer->show();
332 }
333
334 void BookmarksDialog::showAddFolderDialog()
335 {
336     // Get the most recently selected index.
337     QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
338
339     // Instantiate a parent folder ID.
340     double parentFolderId;
341
342     // Get the parent folder ID.
343     if (currentIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toInt() == 1)  // The current index is a folder.
344     {
345         // Store the parent folder ID.
346         parentFolderId = currentIndex.siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
347     }
348     else  // The current index is not a folder.
349     {
350         // Store the parent folder ID of the folder that contains the bookmark.
351         parentFolderId = currentIndex.parent().siblingAtColumn(FOLDER_ID_COLUMN).data().toDouble();
352     }
353
354     // Instantiate an add folder dialog.
355     AddFolderDialog *addFolderDialogPointer = new AddFolderDialog(this, websiteFavoriteIcon, parentFolderId);
356
357     // Update the displayed bookmarks when a folder is added.
358     connect(addFolderDialogPointer, SIGNAL(folderAdded()), this, SLOT(refreshBookmarks()));
359
360     // Show the dialog.
361     addFolderDialogPointer->show();
362 }
363
364 void BookmarksDialog::showEditDialog()
365 {
366     // Get the current model index.
367     QModelIndex currentIndex = treeSelectionModelPointer->currentIndex();
368
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.
371     {
372         // Instantiate an edit folder dialog.
373         QDialog *editFolderDialogPointer = new EditFolderDialog(this, currentIndex.siblingAtColumn(DATABASE_ID_COLUMN).data().toInt(), websiteFavoriteIcon);
374
375         // Show the dialog.
376         editFolderDialogPointer->show();
377
378         // Update the bookmarks UI.
379         connect(editFolderDialogPointer, SIGNAL(folderSaved()), this, SLOT(refreshBookmarks()));
380     }
381     else  // The selected item is a bookmark.
382     {
383         // Instantiate an edit bookmark dialog.
384         QDialog *editBookmarkDialogPointer = new EditBookmarkDialog(this, currentIndex.siblingAtColumn(DATABASE_ID_COLUMN).data().toInt(), websiteFavoriteIcon);
385
386         // Show the dialog.
387         editBookmarkDialogPointer->show();
388
389         // Update the bookmarks UI.
390         connect(editBookmarkDialogPointer, SIGNAL(bookmarkSaved()), this, SLOT(refreshBookmarks()));
391     }
392 }
393
394 void BookmarksDialog::updateBookmarkFromTree(QStandardItem *modifiedStandardItem)
395 {
396     // Get the model index of the modified item.
397     QModelIndex modifiedItemModelIndex = modifiedStandardItem->index();
398
399     // Get the model index of the database ID.
400     QModelIndex databaseIdModelIndex = modifiedItemModelIndex.siblingAtColumn(DATABASE_ID_COLUMN);
401
402     // Get the database ID.
403     int databaseId = databaseIdModelIndex.data().toInt();
404
405     // Check to see if the bookmark name or the URL was edited.
406     if (modifiedStandardItem->column() == NAME_COLUMN)  // The bookmark name was edited.
407     {
408         // Update the bookmark name.
409         BookmarksDatabase::updateBookmarkName(databaseId, modifiedStandardItem->text());
410     }
411     else  // The bookmark URL was edited.
412     {
413         // Update the bookmark URL.
414         BookmarksDatabase::updateBookmarkUrl(databaseId, modifiedStandardItem->text());
415     }
416
417     // Emit the bookmark updated signal.
418     emit bookmarkUpdated();
419 }
420
421 void BookmarksDialog::updateSelection() const
422 {
423     // Set the status of the buttons.
424     if (treeSelectionModelPointer->hasSelection())  // A bookmark or folder is selected.
425     {
426         // Get the list of selected model indexes.
427         QModelIndexList selectedRowsModelIndexList = treeSelectionModelPointer->selectedRows();
428
429         // Check to see if each selected item is a folder.
430         for(QModelIndex modelIndex : selectedRowsModelIndexList)
431         {
432             // If it is a folder, select all the children bookmarks.
433             if (modelIndex.siblingAtColumn(IS_FOLDER_COLUMN).data().toBool())
434                 selectSubfolderContents(modelIndex);
435         }
436     }
437
438     // Update the UI.
439     updateUi();
440 }
441
442 void BookmarksDialog::updateUi() const
443 {
444     // Set the status of the buttons.
445     if (treeSelectionModelPointer->hasSelection())  // A bookmark or folder is selected.
446     {
447         // Get the currently selected index.
448         QModelIndex currentSelectedIndex = treeSelectionModelPointer->currentIndex();
449
450         // Get the list of selected model indexes.
451         QModelIndexList selectedRowsModelIndexList = treeSelectionModelPointer->selectedRows();
452
453         // Get the number of selected rows.
454         int numberOfSelectedRows = selectedRowsModelIndexList.count();
455
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));
458
459         // Check if the root folder is selected.
460         if (treeSelectionModelPointer->isRowSelected(0))
461         {
462             // Disable the edit button.
463             editButtonPointer->setEnabled(false);
464
465             // Decrease the number of selected rows by 1.
466             --numberOfSelectedRows;
467         }
468
469         // Enabled the delete button if at least one real bookmark or folder is selected.
470         deleteItemsButtonPointer->setEnabled(numberOfSelectedRows > 0);
471
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));
475         else
476             deleteItemsButtonPointer->setText(i18nc("Delete items button default text", "Delete items"));
477     }
478     else  // Nothing is selected.
479     {
480         // Disable the buttons.
481         editButtonPointer->setEnabled(false);
482         deleteItemsButtonPointer->setEnabled(false);
483
484         // Update the delete items button text.
485         deleteItemsButtonPointer->setText(i18nc("Delete items button default text", "Delete items"));
486     }
487 }