Line data Source code
1 : // SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2 : // SPDX-License-Identifier: GPL-3.0-or-later
3 : /**
4 : * @file
5 : * @brief Implements FieldLabel; startEdit() and finishEdit() swap the label
6 : * and its editor in and out of the parent's QFormLayout cell.
7 : */
8 : #include "fieldlabel.h"
9 :
10 : #include <QContextMenuEvent>
11 : #include <QFormLayout>
12 : #include <QKeyEvent>
13 : #include <QLineEdit>
14 : #include <QMenu>
15 :
16 34 : FieldLabel::FieldLabel(const QString &name, QWidget *parent) : QLabel(parent) {
17 : // A field name comes from the entry, which a shared store's other writers
18 : // control: as rich text, <img src="/dev/zero"> would have Qt read that
19 : // file without end. Plain text before the name is set.
20 34 : setTextFormat(Qt::PlainText);
21 34 : setText(name);
22 34 : setToolTip(tr("Double-click to rename this field"));
23 34 : }
24 :
25 2 : void FieldLabel::mouseDoubleClickEvent(QMouseEvent *event) {
26 2 : if (event->button() == Qt::LeftButton) {
27 1 : startEdit();
28 1 : return;
29 : }
30 1 : QLabel::mouseDoubleClickEvent(event);
31 : }
32 :
33 2 : void FieldLabel::contextMenuEvent(QContextMenuEvent *event) {
34 2 : QMenu menu(this);
35 2 : menu.addAction(tr("Rename field…"), this, &FieldLabel::startEdit);
36 4 : menu.addAction(tr("Remove field"), this, &FieldLabel::removeRequested);
37 2 : menu.exec(event->globalPos());
38 2 : }
39 :
40 102 : FieldLabel::~FieldLabel() { delete m_editor.data(); }
41 :
42 12 : void FieldLabel::startEdit() {
43 12 : if (m_editor != nullptr) {
44 1 : m_editor->setFocus();
45 1 : return;
46 : }
47 22 : m_editor = new QLineEdit(text(), parentWidget());
48 22 : m_editor->setObjectName(QStringLiteral("fieldNameEditor"));
49 11 : m_editor->installEventFilter(this);
50 11 : connect(m_editor, &QLineEdit::editingFinished, this,
51 30 : [this] { finishEdit(true); });
52 : // Take the label's cell in the form, so the editor gets the label column
53 : // (which widens for it) instead of floating over the value next to it.
54 11 : auto *form = qobject_cast<QFormLayout *>(parentWidget()->layout());
55 11 : int row = -1;
56 11 : QFormLayout::ItemRole role{};
57 11 : if (form != nullptr) {
58 1 : form->getWidgetPosition(this, &row, &role);
59 : }
60 11 : if (row >= 0) {
61 1 : form->removeWidget(this);
62 1 : hide();
63 1 : form->setWidget(row, role, m_editor);
64 : } else {
65 10 : m_editor->setGeometry(geometry());
66 : }
67 11 : m_editor->show();
68 11 : m_editor->selectAll();
69 11 : m_editor->setFocus();
70 : }
71 :
72 188 : auto FieldLabel::eventFilter(QObject *watched, QEvent *event) -> bool {
73 188 : if (watched != m_editor || event->type() != QEvent::KeyPress) {
74 176 : return QLabel::eventFilter(watched, event);
75 : }
76 12 : switch (static_cast<QKeyEvent *>(event)->key()) {
77 1 : case Qt::Key_Escape:
78 1 : finishEdit(false);
79 1 : return true;
80 6 : case Qt::Key_Return:
81 : case Qt::Key_Enter:
82 : // Enter confirms the name only; left alone it would also reach the
83 : // dialog's default button and save and close the whole entry.
84 6 : finishEdit(true);
85 6 : return true;
86 5 : default:
87 5 : return QLabel::eventFilter(watched, event);
88 : }
89 : }
90 :
91 15 : void FieldLabel::finishEdit(bool commit) {
92 15 : if (m_editor == nullptr) {
93 6 : return;
94 : }
95 : // editingFinished fires again when the editor loses focus on hide; take
96 : // the pointer first so the second call finds nothing to do.
97 : QLineEdit *editor = m_editor;
98 : m_editor = nullptr;
99 : // A colon would start the value early when the line is written back.
100 18 : const QString name = editor->text().remove(u':').trimmed();
101 9 : auto *form = qobject_cast<QFormLayout *>(parentWidget()->layout());
102 9 : int row = -1;
103 9 : QFormLayout::ItemRole role{};
104 9 : if (form != nullptr) {
105 1 : form->getWidgetPosition(editor, &row, &role);
106 : }
107 9 : editor->hide();
108 9 : if (row >= 0) {
109 1 : form->removeWidget(editor);
110 1 : form->setWidget(row, role, this);
111 1 : show();
112 : }
113 9 : editor->deleteLater();
114 17 : if (commit && !name.isEmpty() && name != text()) {
115 16 : emit renamed(text(), name);
116 : }
117 : }
|