Skip to content

Commit 0e222b3

Browse files
committed
#3459 navigationwidget: allow renaming of note headings
Signed-off-by: Patrizio Bekerle <patrizio@bekerle.com>
1 parent f3958ee commit 0e222b3

File tree

5 files changed

+93
-2
lines changed

5 files changed

+93
-2
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# QOwnNotes Changelog
22

3+
## 26.2.1
4+
5+
- Added ability to rename headings directly in the Navigation panel
6+
(for [#3459](https://github.com/pbek/QOwnNotes/issues/3459))
7+
- You can now double-click or press `F2` on any heading in the "Headings" tab of the Navigation panel to rename it
8+
- The heading text in your note will update automatically while preserving the heading level
9+
- This provides a quick way to reorganize your note structure without manually editing the text
10+
311
## 26.2.0
412

513
- Empty note files are now allowed again and will only result in a warning in the log

src/mainwindow.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
433433
// act on position clicks in the navigation widget
434434
connect(ui->navigationWidget, &NavigationWidget::positionClicked, this,
435435
&MainWindow::onNavigationWidgetPositionClicked);
436+
// act on heading renames in the navigation widget
437+
connect(ui->navigationWidget, &NavigationWidget::headingRenamed, this,
438+
&MainWindow::onNavigationWidgetHeadingRenamed);
436439
connect(ui->backlinkWidget, &BacklinkWidget::noteClicked, this,
437440
&MainWindow::onBacklinkWidgetNoteClicked);
438441

@@ -9630,6 +9633,48 @@ void MainWindow::onNavigationWidgetPositionClicked(int position) {
96309633
ui->navigationWidget->setFocus();
96319634
}
96329635

9636+
/**
9637+
* Handles renaming of headings from the navigation widget
9638+
* Updates the heading text in the note
9639+
*/
9640+
void MainWindow::onNavigationWidgetHeadingRenamed(int position, const QString &oldText,
9641+
const QString &newText) {
9642+
Q_UNUSED(oldText)
9643+
9644+
QOwnNotesMarkdownTextEdit *textEdit = activeNoteTextEdit();
9645+
QTextDocument *doc = textEdit->document();
9646+
9647+
// Find the block at the given position
9648+
QTextBlock block = doc->findBlock(position);
9649+
if (!block.isValid()) {
9650+
return;
9651+
}
9652+
9653+
// Get the current text of the block
9654+
QString blockText = block.text();
9655+
9656+
// Extract the heading level (number of # symbols)
9657+
static const QRegularExpression headingRegex(QStringLiteral("^(#+)\\s+"));
9658+
QRegularExpressionMatch match = headingRegex.match(blockText);
9659+
9660+
if (!match.hasMatch()) {
9661+
return;
9662+
}
9663+
9664+
QString headingPrefix = match.captured(1); // The "###" part
9665+
QString newBlockText = headingPrefix + " " + newText;
9666+
9667+
// Update the text in the document
9668+
// We need to be careful to only replace the text content, not the block delimiters
9669+
QTextCursor cursor(block);
9670+
cursor.movePosition(QTextCursor::StartOfBlock);
9671+
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
9672+
cursor.insertText(newBlockText);
9673+
9674+
// Reparse the navigation to reflect the change
9675+
// This will be triggered automatically by the text change event
9676+
}
9677+
96339678
/**
96349679
* Jumps to the note that was clicked in the backlink widget
96359680
*/

src/mainwindow.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ class MainWindow : public QMainWindow {
272272

273273
void onNavigationWidgetPositionClicked(int position);
274274

275+
void onNavigationWidgetHeadingRenamed(int position, const QString &oldText,
276+
const QString &newText);
277+
275278
private slots:
276279

277280
void on_noteTextEdit_textChanged();

src/widgets/navigationwidget.cpp

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222
#include <QTreeWidgetItem>
2323

2424
NavigationWidget::NavigationWidget(QWidget *parent) : QTreeWidget(parent) {
25-
// we want to handle currentItemChanged because it also works with the keyboard
25+
// We want to handle currentItemChanged because it also works with the keyboard
2626
QObject::connect(this, &NavigationWidget::currentItemChanged, this,
2727
&NavigationWidget::onCurrentItemChanged);
28-
// we want to handle itemClicked because it allows to click on an item a 2nd time
28+
// We want to handle itemClicked because it allows to click on an item a 2nd time
2929
QObject::connect(this, &NavigationWidget::itemClicked, this, &NavigationWidget::onItemClicked);
30+
// We want to handle itemChanged to allow renaming headings
31+
QObject::connect(this, &NavigationWidget::itemChanged, this, &NavigationWidget::onItemChanged);
3032
}
3133

3234
/**
@@ -57,6 +59,35 @@ void NavigationWidget::onItemClicked(QTreeWidgetItem *current, int column) {
5759
emit positionClicked(current->data(0, Qt::UserRole).toInt());
5860
}
5961

62+
/**
63+
* Handles renaming of heading items
64+
* Emits the headingRenamed signal when a heading is renamed
65+
*/
66+
void NavigationWidget::onItemChanged(QTreeWidgetItem *item, int column) {
67+
Q_UNUSED(column)
68+
69+
if (item == nullptr) {
70+
return;
71+
}
72+
73+
// Get the position from the item
74+
int position = item->data(0, Qt::UserRole).toInt();
75+
76+
// Find the corresponding node in our cache
77+
for (const auto &node : _navigationTreeNodes) {
78+
if (node.pos == position) {
79+
QString oldText = node.text;
80+
QString newText = item->text(0);
81+
82+
// Only emit if the text actually changed
83+
if (oldText != newText && !newText.isEmpty()) {
84+
emit headingRenamed(position, oldText, newText);
85+
}
86+
break;
87+
}
88+
}
89+
}
90+
6091
/**
6192
* Parses a text document and builds the navigation tree for it
6293
*/
@@ -164,6 +195,8 @@ void NavigationWidget::buildNavTree(const QVector<Node> &nodes) {
164195
item->setText(0, stripMarkdown(node.text));
165196
item->setData(0, Qt::UserRole, pos);
166197
item->setToolTip(0, tr("headline %1").arg(elementType - MarkdownHighlighter::H1 + 1));
198+
// Make the item editable to allow renaming headings
199+
item->setFlags(item->flags() | Qt::ItemIsEditable);
167200

168201
// attempt to find a suitable parent item for the element type
169202
QTreeWidgetItem *lastHigherItem = findSuitableParentItem(elementType);

src/widgets/navigationwidget.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@ class NavigationWidget : public QTreeWidget {
4343
private slots:
4444
void onCurrentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
4545
void onItemClicked(QTreeWidgetItem *current, int column);
46+
void onItemChanged(QTreeWidgetItem *item, int column);
4647

4748
private:
4849
void buildNavTree(const QVector<Node> &nodes);
4950
void doParse();
5051

5152
signals:
5253
void positionClicked(int position);
54+
void headingRenamed(int position, const QString &oldText, const QString &newText);
5355

5456
private:
5557
const QTextDocument *_doc;

0 commit comments

Comments
 (0)