diff options
| -rw-r--r-- | src/calendarwindow.cpp | 33 | ||||
| -rw-r--r-- | src/calendarwindow.h | 3 | ||||
| -rw-r--r-- | tests/test_calendarwindow.cpp | 49 |
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" |
