summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-24 17:21:57 +0200
committerDanilo M. <danix@danix.xyz>2026-09-24 17:21:57 +0200
commitbf1d7ecf687adeadc98d60395a4efc7cb306e13f (patch)
tree1ea9db1bdbb459bbf61b8537181d15d0ff1f2e36
parente6a96c8b966fd060090805d9a689187e1ed28468 (diff)
downloadqtmaildir-bf1d7ecf687adeadc98d60395a4efc7cb306e13f.tar.gz
qtmaildir-bf1d7ecf687adeadc98d60395a4efc7cb306e13f.zip
fix: add the toolbar New button, sync the month controls, and let a stale save be retried
-rw-r--r--src/calendarwindow.cpp33
-rw-r--r--src/calendarwindow.h3
-rw-r--r--tests/test_calendarwindow.cpp49
3 files changed, 81 insertions, 4 deletions
diff --git a/src/calendarwindow.cpp b/src/calendarwindow.cpp
index 10caac9..7079c62 100644
--- a/src/calendarwindow.cpp
+++ b/src/calendarwindow.cpp
@@ -140,6 +140,7 @@ CalendarWindow::CalendarWindow(const Config &config, const QStringList &ownAddre
state.endGroup();
reload();
+ refreshToolbar();
const int index = m_collectionBox->findData(collectionDir);
if (index >= 0)
m_collectionBox->setCurrentIndex(index);
@@ -180,10 +181,12 @@ void CalendarWindow::buildUi()
})->setToolTip(tr("Next month"));
m_monthBox = new QComboBox(this);
+ m_monthBox->setObjectName(QStringLiteral("monthBox"));
for (int m = 1; m <= 12; ++m)
m_monthBox->addItem(QLocale().standaloneMonthName(m), m);
toolbar->addWidget(m_monthBox);
m_yearSpin = new QSpinBox(this);
+ m_yearSpin->setObjectName(QStringLiteral("yearSpin"));
m_yearSpin->setRange(1900, 2200);
toolbar->addWidget(m_yearSpin);
connect(m_monthBox, &QComboBox::activated, this,
@@ -260,6 +263,11 @@ void CalendarWindow::buildActions()
QAction *newEvent = make(QStringLiteral("newEvent"), tr("&New event"),
QKeySequence(Qt::CTRL | Qt::Key_N), nullptr,
[this]() { startNew(defaultNewDay()); });
+ // The spec's toolbar ends with + New, after the Month | Agenda toggle.
+ // buildUi() has already placed every widget there, so appending now lands
+ // it last.
+ if (auto *bar = findChild<QToolBar *>(QStringLiteral("calendarToolbar")))
+ bar->addAction(newEvent);
QAction *close = make(QStringLiteral("closeCalendar"), tr("&Close"),
QKeySequence(Qt::CTRL | Qt::Key_W), nullptr, [this]() { this->close(); });
QAction *edit = make(QStringLiteral("editEvent"), tr("&Edit event"),
@@ -589,8 +597,16 @@ bool CalendarWindow::save()
label = tr("Edit event");
}
- if (!writeAndRecord(label, changes))
+ if (!writeAndRecord(label, changes)) {
+ // The spec promises a stale save can be checked and tried again. The
+ // write refused because the file moved under the form, so rebase the
+ // form's bytes on what is on disk NOW; a deliberate second Save then
+ // proceeds and overwrites with the user's values. A file that is gone
+ // is left alone, so Save keeps refusing and Cancel is the way out.
+ if (m_lastStale && !m_editPath.isEmpty() && QFile::exists(m_editPath))
+ m_editBase = readFile(m_editPath);
return false; // the form stays open with the user's values
+ }
m_pane->stopEdit();
bool ignored = false;
m_selectedUid = CalendarStore::parseEvent(*changes.first().after, {}, {}, &ignored).uid;
@@ -627,10 +643,18 @@ void CalendarWindow::deleteSelected()
QPushButton *one = box.addButton(tr("This occurrence"), QMessageBox::AcceptRole);
QPushButton *all = box.addButton(tr("All occurrences"), QMessageBox::AcceptRole);
box.exec();
- if (box.clickedButton() == one)
- after = CalendarStore::deleteOccurrence(e.rawText, o.recurrenceId);
- else if (box.clickedButton() != all)
+ if (box.clickedButton() == one) {
+ const QByteArray removed = CalendarStore::deleteOccurrence(e.rawText, o.recurrenceId);
+ // A parse failure returns empty; writing it would TRUNCATE the file
+ // instead of removing one occurrence. Same guard as save().
+ if (removed.isEmpty()) {
+ status(tr("Could not remove this occurrence."));
+ return;
+ }
+ after = removed;
+ } else if (box.clickedButton() != all) {
return;
+ }
}
if (writeAndRecord(tr("Delete event"), { { e.filePath, e.rawText, after } })) {
m_selectedUid.clear();
@@ -648,6 +672,7 @@ bool CalendarWindow::applyChanges(const QList<Change> &changes, bool reverse)
QString error;
const CalendarWriter::Result r = CalendarWriter::replace(c.path, from, to, &error);
if (r != CalendarWriter::Result::Ok) {
+ m_lastStale = r == CalendarWriter::Result::Stale;
// Roll back what this call already did, newest first.
// ponytail: best effort; a rollback that itself fails is reported
// by the file being wrong on the next reload, not handled here.
diff --git a/src/calendarwindow.h b/src/calendarwindow.h
index 5b74578..133dff8 100644
--- a/src/calendarwindow.h
+++ b/src/calendarwindow.h
@@ -104,6 +104,9 @@ private:
int m_selected = -1;
QString m_selectedUid; ///< survives a reload
QDateTime m_selectedStart;
+ /// Set by applyChanges: the last refusal was a stale check, so a retry can
+ /// rebase the form's bytes on what is on disk now.
+ bool m_lastStale = false;
// The open edit. m_editPath empty with m_editing: a new event.
QString m_editPath;
diff --git a/tests/test_calendarwindow.cpp b/tests/test_calendarwindow.cpp
index 26f7237..c52f245 100644
--- a/tests/test_calendarwindow.cpp
+++ b/tests/test_calendarwindow.cpp
@@ -19,14 +19,17 @@
#include <QtTest>
#include <QAction>
#include <QApplication>
+#include <QComboBox>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
+#include <QSpinBox>
#include <QTemporaryDir>
#include <QTimer>
+#include <QToolBar>
#include <QUndoStack>
#include <memory>
@@ -120,6 +123,9 @@ private slots:
void saveWritesAndUndoRestoresTheOriginalBytes();
void aNewEventGoesToTheDefaultCalendar();
void aStaleSaveKeepsTheFormAndTheSyncedFile();
+ void theToolbarCarriesTheNewEventAction();
+ void aFreshWindowShowsTheCurrentMonth();
+ void aStaleSaveCanBeRetried();
};
void TestCalendarWindow::loadsAndSelects()
@@ -218,5 +224,48 @@ void TestCalendarWindow::aStaleSaveKeepsTheFormAndTheSyncedFile()
QCOMPARE(w->findChild<QLineEdit *>(QStringLiteral("eventTitle"))->text(), QStringLiteral("Mine"));
}
+void TestCalendarWindow::theToolbarCarriesTheNewEventAction()
+{
+ Fixture f;
+ std::unique_ptr<CalendarWindow> w(f.window());
+ auto *bar = w->findChild<QToolBar *>(QStringLiteral("calendarToolbar"));
+ QVERIFY(bar);
+ auto *newEvent = w->findChild<QAction *>(QStringLiteral("newEvent"));
+ QVERIFY(newEvent);
+ QVERIFY(bar->actions().contains(newEvent));
+}
+
+void TestCalendarWindow::aFreshWindowShowsTheCurrentMonth()
+{
+ Fixture f;
+ std::unique_ptr<CalendarWindow> w(
+ new CalendarWindow(f.config, { QStringLiteral("me@example.org") },
+ f.dir.filePath(QStringLiteral("uistate.conf"))));
+ const QDate today = QDate::currentDate();
+ QCOMPARE(w->findChild<QComboBox *>(QStringLiteral("monthBox"))->currentData().toInt(),
+ today.month());
+ QCOMPARE(w->findChild<QSpinBox *>(QStringLiteral("yearSpin"))->value(), today.year());
+}
+
+void TestCalendarWindow::aStaleSaveCanBeRetried()
+{
+ Fixture f;
+ std::unique_ptr<CalendarWindow> w(f.window());
+ w->selectEvent(QStringLiteral("one@example.org"));
+ editTitle(w.get(), QStringLiteral("Mine"));
+ // A sync pulls a server change while the form is open.
+ const QByteArray synced = eventText(QStringLiteral("one@example.org"), QStringLiteral("Theirs"), 22);
+ writeFile(f.a(), synced);
+ w->findChild<QPushButton *>(QStringLiteral("saveEvent"))->click();
+ QCOMPARE(read(f.a()), synced); // first Save refuses, nothing clobbered
+ QVERIFY(w->isEditing());
+
+ // The user checks what arrived and saves again deliberately: the form's
+ // bytes were rebased on the synced file, so this one lands.
+ w->findChild<QPushButton *>(QStringLiteral("saveEvent"))->click();
+ QVERIFY(read(f.a()).contains("SUMMARY:Mine"));
+ QVERIFY(!w->isEditing());
+}
+
QTEST_MAIN(TestCalendarWindow)
#include "test_calendarwindow.moc"