Skip to content

Commit 1d2b87d

Browse files
committed
#3589 notebookmarks: add main menu entries and new dialog
Signed-off-by: Patrizio Bekerle <patrizio@bekerle.com>
1 parent 436d64c commit 1d2b87d

9 files changed

Lines changed: 574 additions & 42 deletions

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
## 26.5.0
44

5+
- Added **Note Bookmarks** sub-menus to the _Note / Navigation_ menu
6+
(for [#3589](https://github.com/pbek/QOwnNotes/issues/3589))
7+
- New **Store note bookmark** sub-menu with slots 1–9 (`Ctrl+Shift+1`–`Ctrl+Shift+9`)
8+
- New **Go to note bookmark** sub-menu with slots 1–9 (`Ctrl+1`–`Ctrl+9`)
9+
- All bookmark shortcuts are now proper `QAction`s in the menu and can be
10+
customised in the _Shortcut settings_, superseding the old hardcoded
11+
`QShortcut`-based approach
12+
- Added a new **Note bookmarks** entry in the _Note / Navigation_ menu that opens
13+
a non-modal **Note Bookmarks** dialog listing all currently stored bookmarks with
14+
the ability to jump to a bookmark or delete it (for [#3589](https://github.com/pbek/QOwnNotes/issues/3589))
515
- Fixed a bug where searching for a multi-word text like "Heading 1" in the
616
**Note search panel** would not correctly use all terms in the in-note regexp
717
search, because the search mode was set after the search text causing

src/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@ set(SOURCE_FILES
273273
dialogs/notedialog.cpp
274274
dialogs/notedialog.h
275275
dialogs/notedialog.ui
276+
dialogs/notebookmarkdialog.cpp
277+
dialogs/notebookmarkdialog.h
278+
dialogs/notebookmarkdialog.ui
276279
dialogs/websockettokendialog.cpp
277280
dialogs/websockettokendialog.h
278281
dialogs/websockettokendialog.ui

src/QOwnNotes.pro

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ SOURCES += main.cpp\
220220
dialogs/tabledialog.cpp \
221221
libraries/qtcsv/src/sources/reader.cpp \
222222
dialogs/notedialog.cpp \
223+
dialogs/notebookmarkdialog.cpp \
223224
dialogs/filedialog.cpp \
224225
dialogs/scriptrepositorydialog.cpp \
225226
dialogs/dictionarymanagerdialog.cpp \
@@ -390,6 +391,7 @@ HEADERS += mainwindow.h \
390391
libraries/qtcsv/src/sources/filechecker.h \
391392
libraries/qtcsv/src/sources/symbols.h \
392393
dialogs/notedialog.h \
394+
dialogs/notebookmarkdialog.h \
393395
dialogs/filedialog.h \
394396
dialogs/scriptrepositorydialog.h \
395397
dialogs/dictionarymanagerdialog.h \
@@ -486,6 +488,7 @@ FORMS += mainwindow.ui \
486488
dialogs/actiondialog.ui \
487489
dialogs/tabledialog.ui \
488490
dialogs/notedialog.ui \
491+
dialogs/notebookmarkdialog.ui \
489492
dialogs/scriptrepositorydialog.ui \
490493
dialogs/dictionarymanagerdialog.ui \
491494
widgets/qtexteditsearchwidget.ui \

src/dialogs/notebookmarkdialog.cpp

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#include "notebookmarkdialog.h"
2+
3+
#include <QDialogButtonBox>
4+
#include <QKeyEvent>
5+
#include <QMessageBox>
6+
#include <QPushButton>
7+
#include <QTableWidgetItem>
8+
#include <algorithm>
9+
10+
#include "entities/note.h"
11+
#include "ui_notebookmarkdialog.h"
12+
#include "utils/gui.h"
13+
14+
NoteBookmarkDialog::NoteBookmarkDialog(QWidget *parent)
15+
: MasterDialog(parent), ui(new Ui::NoteBookmarkDialog) {
16+
ui->setupUi(this);
17+
afterSetupUI();
18+
19+
// Set up table column widths
20+
ui->bookmarkTableWidget->horizontalHeader()->setSectionResizeMode(
21+
0, QHeaderView::ResizeToContents);
22+
ui->bookmarkTableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
23+
ui->bookmarkTableWidget->horizontalHeader()->setSectionResizeMode(
24+
2, QHeaderView::ResizeToContents);
25+
26+
// Add Jump and Delete action buttons
27+
QPushButton *jumpButton =
28+
ui->buttonBox->addButton(tr("Jump to bookmark"), QDialogButtonBox::ActionRole);
29+
jumpButton->setIcon(
30+
QIcon::fromTheme(QStringLiteral("go-next"),
31+
QIcon(QStringLiteral(":/icons/breeze-qownnotes/16x16/go-next.svg"))));
32+
jumpButton->setToolTip(tr("Jump to the selected bookmark in the main window"));
33+
34+
QPushButton *deleteButton =
35+
ui->buttonBox->addButton(tr("Delete bookmark"), QDialogButtonBox::ActionRole);
36+
deleteButton->setIcon(
37+
QIcon::fromTheme(QStringLiteral("edit-delete"),
38+
QIcon(QStringLiteral(":/icons/breeze-qownnotes/16x16/edit-delete.svg"))));
39+
deleteButton->setToolTip(tr("Delete the selected bookmark"));
40+
41+
connect(jumpButton, &QPushButton::clicked, this, &NoteBookmarkDialog::onJumpButtonClicked);
42+
connect(deleteButton, &QPushButton::clicked, this, &NoteBookmarkDialog::onDeleteButtonClicked);
43+
connect(ui->bookmarkTableWidget, &QTableWidget::cellDoubleClicked, this,
44+
&NoteBookmarkDialog::onBookmarkTableDoubleClicked);
45+
46+
// Install event filter on the table to handle the Del key
47+
ui->bookmarkTableWidget->installEventFilter(this);
48+
}
49+
50+
NoteBookmarkDialog::~NoteBookmarkDialog() { delete ui; }
51+
52+
/**
53+
* Populates the bookmark table from the given bookmarks hash
54+
*/
55+
void NoteBookmarkDialog::setBookmarks(const QHash<int, NoteHistoryItem> &bookmarks) {
56+
// Collect and sort by slot number for a predictable display order
57+
QList<int> slotNumbers = bookmarks.keys();
58+
std::sort(slotNumbers.begin(), slotNumbers.end());
59+
60+
ui->bookmarkTableWidget->setRowCount(0);
61+
62+
for (int slot : slotNumbers) {
63+
const NoteHistoryItem &item = bookmarks[slot];
64+
const Note note = item.getNote();
65+
66+
if (!note.isFetched()) {
67+
// Skip bookmarks whose note no longer exists
68+
continue;
69+
}
70+
71+
const int row = ui->bookmarkTableWidget->rowCount();
72+
ui->bookmarkTableWidget->insertRow(row);
73+
74+
auto *slotItem = new QTableWidgetItem(QString::number(slot));
75+
slotItem->setData(Qt::UserRole, slot);
76+
slotItem->setTextAlignment(Qt::AlignCenter);
77+
78+
auto *noteItem = new QTableWidgetItem(item.getNoteName());
79+
auto *posItem = new QTableWidgetItem(QString::number(item.getCursorPosition()));
80+
posItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
81+
82+
ui->bookmarkTableWidget->setItem(row, 0, slotItem);
83+
ui->bookmarkTableWidget->setItem(row, 1, noteItem);
84+
ui->bookmarkTableWidget->setItem(row, 2, posItem);
85+
}
86+
}
87+
88+
/**
89+
* Returns the slot number of the currently selected row, or -1 if none
90+
*/
91+
int NoteBookmarkDialog::selectedSlot() const {
92+
const QList<QTableWidgetItem *> selected = ui->bookmarkTableWidget->selectedItems();
93+
94+
if (selected.isEmpty()) {
95+
return -1;
96+
}
97+
98+
// The slot is stored in column 0 via UserRole
99+
const int row = ui->bookmarkTableWidget->row(selected.first());
100+
QTableWidgetItem *slotItem = ui->bookmarkTableWidget->item(row, 0);
101+
102+
if (!slotItem) {
103+
return -1;
104+
}
105+
106+
return slotItem->data(Qt::UserRole).toInt();
107+
}
108+
109+
void NoteBookmarkDialog::onJumpButtonClicked() {
110+
const int slot = selectedSlot();
111+
112+
if (slot >= 0) {
113+
emit jumpToBookmarkRequested(slot);
114+
}
115+
}
116+
117+
void NoteBookmarkDialog::onDeleteButtonClicked() {
118+
const int slot = selectedSlot();
119+
120+
if (slot < 0) {
121+
return;
122+
}
123+
124+
// Ask the user to confirm before deleting the bookmark, with a "Don't ask again!" option
125+
const auto answer = Utils::Gui::question(
126+
this, tr("Delete bookmark"),
127+
tr("Are you sure you want to delete the bookmark at slot %1?").arg(slot),
128+
QStringLiteral("delete-note-bookmark"));
129+
130+
if (answer == QMessageBox::Yes) {
131+
emit deleteBookmarkRequested(slot);
132+
}
133+
}
134+
135+
void NoteBookmarkDialog::onBookmarkTableDoubleClicked(int /*row*/, int /*column*/) {
136+
onJumpButtonClicked();
137+
}
138+
139+
/**
140+
* Intercepts key events on the bookmark table to allow deletion with the Del key
141+
*/
142+
bool NoteBookmarkDialog::eventFilter(QObject *watched, QEvent *event) {
143+
if (watched == ui->bookmarkTableWidget && event->type() == QEvent::KeyPress) {
144+
auto *keyEvent = static_cast<QKeyEvent *>(event);
145+
146+
if (keyEvent->key() == Qt::Key_Delete) {
147+
onDeleteButtonClicked();
148+
return true;
149+
}
150+
}
151+
152+
return MasterDialog::eventFilter(watched, event);
153+
}

src/dialogs/notebookmarkdialog.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#ifndef NOTEBOOKMARKDIALOG_H
2+
#define NOTEBOOKMARKDIALOG_H
3+
4+
#include <QHash>
5+
6+
#include "entities/notehistory.h"
7+
#include "masterdialog.h"
8+
9+
namespace Ui {
10+
class NoteBookmarkDialog;
11+
}
12+
13+
class NoteBookmarkDialog : public MasterDialog {
14+
Q_OBJECT
15+
16+
public:
17+
explicit NoteBookmarkDialog(QWidget *parent = nullptr);
18+
~NoteBookmarkDialog();
19+
20+
void setBookmarks(const QHash<int, NoteHistoryItem> &bookmarks);
21+
22+
signals:
23+
void jumpToBookmarkRequested(int slot);
24+
void deleteBookmarkRequested(int slot);
25+
26+
protected:
27+
bool eventFilter(QObject *watched, QEvent *event) override;
28+
29+
private slots:
30+
void onJumpButtonClicked();
31+
void onDeleteButtonClicked();
32+
void onBookmarkTableDoubleClicked(int row, int column);
33+
34+
private:
35+
Ui::NoteBookmarkDialog *ui;
36+
37+
void reloadTable();
38+
int selectedSlot() const;
39+
};
40+
41+
#endif // NOTEBOOKMARKDIALOG_H

src/dialogs/notebookmarkdialog.ui

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>NoteBookmarkDialog</class>
4+
<widget class="QDialog" name="NoteBookmarkDialog">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>520</width>
10+
<height>320</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Note Bookmarks</string>
15+
</property>
16+
<layout class="QVBoxLayout" name="verticalLayout">
17+
<item>
18+
<widget class="QTableWidget" name="bookmarkTableWidget">
19+
<property name="selectionBehavior">
20+
<enum>QAbstractItemView::SelectRows</enum>
21+
</property>
22+
<property name="selectionMode">
23+
<enum>QAbstractItemView::SingleSelection</enum>
24+
</property>
25+
<property name="editTriggers">
26+
<set>QAbstractItemView::NoEditTriggers</set>
27+
</property>
28+
<property name="alternatingRowColors">
29+
<bool>true</bool>
30+
</property>
31+
<property name="sortingEnabled">
32+
<bool>false</bool>
33+
</property>
34+
<column>
35+
<property name="text">
36+
<string>Slot</string>
37+
</property>
38+
</column>
39+
<column>
40+
<property name="text">
41+
<string>Note</string>
42+
</property>
43+
</column>
44+
<column>
45+
<property name="text">
46+
<string>Position</string>
47+
</property>
48+
</column>
49+
</widget>
50+
</item>
51+
<item>
52+
<widget class="QDialogButtonBox" name="buttonBox">
53+
<property name="orientation">
54+
<enum>Qt::Horizontal</enum>
55+
</property>
56+
<property name="standardButtons">
57+
<set>QDialogButtonBox::Close</set>
58+
</property>
59+
</widget>
60+
</item>
61+
</layout>
62+
</widget>
63+
<resources/>
64+
<connections>
65+
<connection>
66+
<sender>buttonBox</sender>
67+
<signal>rejected()</signal>
68+
<receiver>NoteBookmarkDialog</receiver>
69+
<slot>reject()</slot>
70+
<hints>
71+
<hint type="sourcelabel">
72+
<x>316</x>
73+
<y>295</y>
74+
</hint>
75+
<hint type="destinationlabel">
76+
<x>286</x>
77+
<y>313</y>
78+
</hint>
79+
</hints>
80+
</connection>
81+
</connections>
82+
</ui>

0 commit comments

Comments
 (0)