diff options
| -rw-r--r-- | AGENTS.md | 4 | ||||
| -rw-r--r-- | docs/superpowers/specs/2026-09-24-calendar-design.md | 4 | ||||
| -rw-r--r-- | src/calendarwindow.cpp | 22 | ||||
| -rw-r--r-- | src/monthview.cpp | 3 | ||||
| -rw-r--r-- | tests/test_calendarwindow.cpp | 19 | ||||
| -rw-r--r-- | translations/qtmaildir_it_IT.ts | 4 |
6 files changed, 47 insertions, 9 deletions
@@ -1059,7 +1059,9 @@ scoped to the views with `Qt::WidgetWithChildrenShortcut`.** They are window shortcuts otherwise, and a bare `Delete` or `Ctrl+Z` then fires while the user is typing in the edit form's Title field, deleting the event or undoing the calendar instead of editing text. `EventPane` and the stacked views are the -scopes; Escape is scoped to the pane. +scopes. Escape is the exception and is a WINDOW shortcut, so it closes the +details pane from the grid; with an edit open it cancels only when focus is +inside the pane, so a stray Escape from the grid cannot discard the form. **`calendar_sync_command` is read through a `QStringList` join.** With no quote around the value, QSettings splits a comma-holding string into a `QStringList` diff --git a/docs/superpowers/specs/2026-09-24-calendar-design.md b/docs/superpowers/specs/2026-09-24-calendar-design.md index a57bb26..f58f372 100644 --- a/docs/superpowers/specs/2026-09-24-calendar-design.md +++ b/docs/superpowers/specs/2026-09-24-calendar-design.md @@ -312,7 +312,9 @@ Edit and View holding every action; `Ctrl+N` New, `Ctrl+E` Edit, `Delete`, `Home` would otherwise fire while the cursor is in the edit form's Title field, deleting the event or moving the month instead of editing text. `Delete`, `PgUp`/`PgDn` and `Home` sit on the stacked views; Undo and Redo sit there too; -Escape is scoped to `EventPane`. `Ctrl+N`, `Ctrl+E` and `Ctrl+W` stay window +Escape is a window shortcut: it closes the details pane (the pane is hidden +while nothing is selected) and cancels an edit only when focus is inside +`EventPane`. `Ctrl+N`, `Ctrl+E` and `Ctrl+W` stay window shortcuts, since no text field wants them. **i18n.** Every string in `tr()`; the Italian `.ts` refreshed, `lrelease` diff --git a/src/calendarwindow.cpp b/src/calendarwindow.cpp index d9c1da2..4a07b89 100644 --- a/src/calendarwindow.cpp +++ b/src/calendarwindow.cpp @@ -25,6 +25,7 @@ #include "monthview.h" #include <QAction> +#include <QApplication> #include <QButtonGroup> #include <QCloseEvent> #include <QComboBox> @@ -276,8 +277,10 @@ void CalendarWindow::buildActions() QKeySequence(Qt::CTRL | Qt::Key_E), nullptr, &CalendarWindow::startEdit); QAction *del = make(QStringLiteral("deleteEvent"), tr("&Delete event"), QKeySequence(Qt::Key_Delete), m_views, &CalendarWindow::deleteSelected); - QAction *cancel = make(QStringLiteral("cancelEdit"), tr("Cancel &editing"), - QKeySequence(Qt::Key_Escape), m_pane, &CalendarWindow::cancelEdit); + // Window-wide, so Escape closes the details pane from the grid too; the + // combo and spin box popups take their own Escape first. + QAction *cancel = make(QStringLiteral("cancelEdit"), tr("C&lose event"), + QKeySequence(Qt::Key_Escape), nullptr, &CalendarWindow::cancelEdit); QAction *undo = m_undo.createUndoAction(this, tr("&Undo")); undo->setShortcut(QKeySequence::Undo); @@ -619,9 +622,18 @@ bool CalendarWindow::save() void CalendarWindow::cancelEdit() { - if (!m_pane->isEditing()) - return; - m_pane->stopEdit(); + // Cancelling an edit returns to the details; with no edit, closing the + // details clears the selection, which hides the pane. + if (m_pane->isEditing()) { + // Escape used to be scoped to the pane; keep a stray one from the + // grid from throwing away an edit. + if (!m_pane->isAncestorOf(QApplication::focusWidget())) + return; + m_pane->stopEdit(); + } else { + m_selectedUid.clear(); + m_selectedStart = {}; + } rebuildItems(); } diff --git a/src/monthview.cpp b/src/monthview.cpp index 202ef4a..5b56ee4 100644 --- a/src/monthview.cpp +++ b/src/monthview.cpp @@ -28,6 +28,9 @@ MonthView::MonthView(QWidget *parent) m_year(QDate::currentDate().year()), m_month(QDate::currentDate().month()) { setMinimumSize(420, 320); + // A click must move focus here, or it stays in the edit form and the + // view-scoped shortcuts and the Escape guard read the wrong widget. + setFocusPolicy(Qt::StrongFocus); } void MonthView::setMonth(int year, int month) diff --git a/tests/test_calendarwindow.cpp b/tests/test_calendarwindow.cpp index 5591b2b..ea83f12 100644 --- a/tests/test_calendarwindow.cpp +++ b/tests/test_calendarwindow.cpp @@ -128,6 +128,7 @@ private slots: void aFreshWindowShowsTheCurrentMonth(); void aStaleSaveCanBeRetried(); void aRolledBackWriteIsNotReportedLostAfterSync(); + void escapeClosesTheDetailsButNotAnEditFromOutsideThePane(); }; void TestCalendarWindow::loadsAndSelects() @@ -320,5 +321,23 @@ void TestCalendarWindow::aRolledBackWriteIsNotReportedLostAfterSync() 5000); } + +void TestCalendarWindow::escapeClosesTheDetailsButNotAnEditFromOutsideThePane() +{ + Fixture f; + std::unique_ptr<CalendarWindow> w(f.window()); + auto *close = w->findChild<QAction *>(QStringLiteral("cancelEdit")); + QVERIFY(close); + QVERIFY(w->selectEvent(QStringLiteral("one@example.org"))); + close->trigger(); + QVERIFY(w->selectedUid().isEmpty()); + + // Focus is nowhere inside the pane, as after a click on the grid. + QVERIFY(w->selectEvent(QStringLiteral("one@example.org"))); + editTitle(w.get(), QStringLiteral("Changed")); + close->trigger(); + QVERIFY(w->isEditing()); +} + QTEST_MAIN(TestCalendarWindow) #include "test_calendarwindow.moc" diff --git a/translations/qtmaildir_it_IT.ts b/translations/qtmaildir_it_IT.ts index 35702ae..eff6754 100644 --- a/translations/qtmaildir_it_IT.ts +++ b/translations/qtmaildir_it_IT.ts @@ -71,8 +71,8 @@ <translation>&Elimina evento</translation> </message> <message> - <source>Cancel &editing</source> - <translation>Annulla &modifica</translation> + <source>C&lose event</source> + <translation>C&hiudi evento</translation> </message> <message> <source>&Undo</source> |
