Branch data Line data Source code
1 : : #include "EventEditDialog.h"
2 : :
3 : : #include <QAbstractItemView>
4 : : #include <QCalendarWidget>
5 : : #include <QCheckBox>
6 : : #include <QComboBox>
7 : : #include <QDateEdit>
8 : : #include <QDialogButtonBox>
9 : : #include <QFormLayout>
10 : : #include <QFrame>
11 : : #include <QLabel>
12 : : #include <QLineEdit>
13 : : #include <QPlainTextEdit>
14 : : #include <QPushButton>
15 : : #include <QSettings>
16 : : #include <QStyle>
17 : : #include <QTimeEdit>
18 : : #include <QUuid>
19 : : #include <QVBoxLayout>
20 : :
21 [ + - ]: 12 : EventEditDialog::EventEditDialog(QWidget *parent) : QDialog(parent) {
22 [ + - ]: 24 : setObjectName(QStringLiteral("eventEditDialog"));
23 [ + - ]: 12 : setAttribute(Qt::WA_StyledBackground, true);
24 [ + - ]: 12 : setupUi();
25 : 12 : }
26 : :
27 : : // T-76.B3: Runtime language switching
28 : 30 : void EventEditDialog::changeEvent(QEvent *event) {
29 : 30 : QDialog::changeEvent(event);
30 [ + + ]: 30 : if (event->type() == QEvent::LanguageChange)
31 : 1 : retranslateUi();
32 : 30 : }
33 : :
34 : 1 : void EventEditDialog::retranslateUi() {
35 [ + - + - : 1 : setWindowTitle(m_isNew ? tr("New event") : tr("Edit event"));
- - + - ]
36 [ + - + - ]: 1 : m_headerLabel->setText(windowTitle());
37 [ + - + - ]: 1 : m_titleLabel->setText(tr("Title:"));
38 [ + - + - ]: 1 : m_calendarLabel->setText(tr("Calendar:"));
39 [ + - + - ]: 1 : m_allDayCheck->setText(tr("All day"));
40 [ + - + - ]: 1 : m_startLabel->setText(tr("Start:"));
41 [ + - + - ]: 1 : m_endLabel->setText(tr("End:"));
42 [ + - + - ]: 1 : m_locationLabel->setText(tr("Location:"));
43 [ + - + - ]: 1 : m_descriptionLabel->setText(tr("Description:"));
44 [ + - + - ]: 1 : m_recurrenceLabel->setText(tr("Recurrence:"));
45 [ + - + - ]: 1 : m_titleEdit->setPlaceholderText(tr("Enter title..."));
46 [ + - + - ]: 1 : m_locationEdit->setPlaceholderText(tr("Enter location..."));
47 [ + - + - ]: 1 : m_descriptionEdit->setPlaceholderText(tr("Description..."));
48 [ + - + - ]: 1 : m_repeatCombo->setItemText(0, tr("None"));
49 [ + - + - ]: 1 : m_repeatCombo->setItemText(1, tr("Daily"));
50 [ + - + - ]: 1 : m_repeatCombo->setItemText(2, tr("Weekly"));
51 [ + - + - ]: 1 : m_repeatCombo->setItemText(3, tr("Monthly"));
52 [ + - + - ]: 1 : m_repeatCombo->setItemText(4, tr("Yearly"));
53 [ + - + - ]: 1 : m_cancelBtn->setText(tr("Cancel"));
54 [ + - + - ]: 1 : m_saveBtn->setText(tr("Save"));
55 : 1 : }
56 : :
57 : 12 : void EventEditDialog::setupUi() {
58 [ + - ]: 12 : setMinimumWidth(440);
59 [ + - + - ]: 12 : setWindowTitle(tr("Edit event"));
60 : :
61 [ + - + - : 12 : auto *mainLayout = new QVBoxLayout(this);
- + - - ]
62 [ + - ]: 12 : mainLayout->setSpacing(14);
63 [ + - ]: 12 : mainLayout->setContentsMargins(20, 18, 20, 18);
64 : :
65 : : // Header
66 [ + - + - : 12 : m_headerLabel = new QLabel(windowTitle(), this);
+ - - + -
- ]
67 [ + - ]: 24 : m_headerLabel->setObjectName(QStringLiteral("eventDialogHeader"));
68 [ + - ]: 12 : QFont hf = m_headerLabel->font();
69 [ + - ]: 12 : hf.setPointSize(15);
70 [ + - ]: 12 : hf.setBold(true);
71 [ + - ]: 12 : m_headerLabel->setFont(hf);
72 [ + - ]: 12 : mainLayout->addWidget(m_headerLabel);
73 : :
74 : : // Subtle separator
75 [ + - + - : 12 : auto *sep = new QFrame(this);
- + - - ]
76 [ + - ]: 24 : sep->setObjectName(QStringLiteral("eventDialogSeparator"));
77 [ + - ]: 12 : sep->setFrameShape(QFrame::HLine);
78 [ + - ]: 12 : mainLayout->addWidget(sep);
79 : :
80 : : // Form
81 [ + - + - : 12 : auto *form = new QFormLayout();
- + - - ]
82 [ + - ]: 12 : form->setLabelAlignment(Qt::AlignRight);
83 [ + - ]: 12 : form->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
84 [ + - ]: 12 : form->setVerticalSpacing(10);
85 : :
86 : : // Title
87 [ + - + - : 12 : m_titleEdit = new QLineEdit(this);
- + - - ]
88 [ + - ]: 24 : m_titleEdit->setObjectName(QStringLiteral("eventTitleEdit"));
89 [ + - + - ]: 12 : m_titleEdit->setPlaceholderText(tr("Enter title..."));
90 [ + - + - : 12 : m_titleLabel = new QLabel(tr("Title:"), this);
+ - - + -
- ]
91 [ + - ]: 12 : form->addRow(m_titleLabel, m_titleEdit);
92 : :
93 : : // Calendar selection
94 [ + - + - : 12 : m_calendarCombo = new QComboBox(this);
- + - - ]
95 [ + - ]: 24 : m_calendarCombo->setObjectName(QStringLiteral("eventCalendarCombo"));
96 [ + - + - ]: 24 : m_calendarCombo->view()->setObjectName(
97 : 24 : QStringLiteral("eventCalendarComboPopup"));
98 [ + - + - : 12 : m_calendarLabel = new QLabel(tr("Calendar:"), this);
+ - - + -
- ]
99 [ + - ]: 12 : form->addRow(m_calendarLabel, m_calendarCombo);
100 : :
101 : : // All-day toggle
102 [ + - + - : 12 : m_allDayCheck = new QCheckBox(tr("All day"), this);
+ - - + -
- ]
103 [ + - ]: 24 : m_allDayCheck->setObjectName(QStringLiteral("eventAllDayCheck"));
104 [ + - ]: 12 : form->addRow(QString(), m_allDayCheck);
105 : :
106 : : // Start date/time
107 [ + - + - : 12 : auto *startRow = new QHBoxLayout();
- + - - ]
108 [ + - + - : 12 : m_startDate = new QDateEdit(QDate::currentDate(), this);
+ - - + -
- ]
109 [ + - ]: 24 : m_startDate->setObjectName(QStringLiteral("eventStartDateEdit"));
110 [ + - ]: 12 : m_startDate->setCalendarPopup(true);
111 [ + - ]: 24 : m_startDate->setDisplayFormat(QStringLiteral("dd.MM.yyyy"));
112 [ + - + - : 12 : m_startTime = new QTimeEdit(QTime(9, 0), this);
+ - - + -
- ]
113 [ + - ]: 24 : m_startTime->setObjectName(QStringLiteral("eventStartTimeEdit"));
114 [ + - ]: 24 : m_startTime->setDisplayFormat(QStringLiteral("HH:mm"));
115 [ + - ]: 12 : startRow->addWidget(m_startDate);
116 [ + - ]: 12 : startRow->addWidget(m_startTime);
117 [ + - + - : 12 : m_startLabel = new QLabel(tr("Start:"), this);
+ - - + -
- ]
118 [ + - ]: 12 : form->addRow(m_startLabel, startRow);
119 : :
120 : : // End date/time
121 [ + - + - : 12 : auto *endRow = new QHBoxLayout();
- + - - ]
122 [ + - + - : 12 : m_endDate = new QDateEdit(QDate::currentDate(), this);
+ - - + -
- ]
123 [ + - ]: 24 : m_endDate->setObjectName(QStringLiteral("eventEndDateEdit"));
124 [ + - ]: 12 : m_endDate->setCalendarPopup(true);
125 [ + - ]: 24 : m_endDate->setDisplayFormat(QStringLiteral("dd.MM.yyyy"));
126 [ + - + - : 12 : m_endTime = new QTimeEdit(QTime(10, 0), this);
+ - - + -
- ]
127 [ + - ]: 24 : m_endTime->setObjectName(QStringLiteral("eventEndTimeEdit"));
128 [ + - ]: 24 : m_endTime->setDisplayFormat(QStringLiteral("HH:mm"));
129 [ + - ]: 12 : endRow->addWidget(m_endDate);
130 [ + - ]: 12 : endRow->addWidget(m_endTime);
131 [ + - + - : 12 : m_endLabel = new QLabel(tr("End:"), this);
+ - - + -
- ]
132 [ + - ]: 12 : form->addRow(m_endLabel, endRow);
133 : :
134 : : // Location
135 [ + - + - : 12 : m_locationEdit = new QLineEdit(this);
- + - - ]
136 [ + - ]: 24 : m_locationEdit->setObjectName(QStringLiteral("eventLocationEdit"));
137 [ + - + - ]: 12 : m_locationEdit->setPlaceholderText(tr("Enter location..."));
138 [ + - + - : 12 : m_locationLabel = new QLabel(tr("Location:"), this);
+ - - + -
- ]
139 [ + - ]: 12 : form->addRow(m_locationLabel, m_locationEdit);
140 : :
141 : : // Description
142 [ + - + - : 12 : m_descriptionEdit = new QPlainTextEdit(this);
- + - - ]
143 [ + - ]: 24 : m_descriptionEdit->setObjectName(QStringLiteral("eventDescriptionEdit"));
144 [ + - ]: 12 : m_descriptionEdit->setMaximumHeight(90);
145 [ + - + - ]: 12 : m_descriptionEdit->setPlaceholderText(tr("Description..."));
146 [ + - + - : 12 : m_descriptionLabel = new QLabel(tr("Description:"), this);
+ - - + -
- ]
147 [ + - ]: 12 : form->addRow(m_descriptionLabel, m_descriptionEdit);
148 : :
149 : : // Recurrence
150 [ + - + - : 12 : m_repeatCombo = new QComboBox(this);
- + - - ]
151 [ + - ]: 24 : m_repeatCombo->setObjectName(QStringLiteral("eventRepeatCombo"));
152 [ + - + - ]: 24 : m_repeatCombo->view()->setObjectName(
153 : 24 : QStringLiteral("eventRepeatComboPopup"));
154 [ + - + - ]: 12 : m_repeatCombo->addItem(tr("None"), QString());
155 [ + - + - ]: 24 : m_repeatCombo->addItem(tr("Daily"), QStringLiteral("FREQ=DAILY"));
156 [ + - + - ]: 24 : m_repeatCombo->addItem(tr("Weekly"), QStringLiteral("FREQ=WEEKLY"));
157 [ + - + - ]: 24 : m_repeatCombo->addItem(tr("Monthly"), QStringLiteral("FREQ=MONTHLY"));
158 [ + - + - ]: 24 : m_repeatCombo->addItem(tr("Yearly"), QStringLiteral("FREQ=YEARLY"));
159 [ + - + - : 12 : m_recurrenceLabel = new QLabel(tr("Recurrence:"), this);
+ - - + -
- ]
160 [ + - ]: 12 : form->addRow(m_recurrenceLabel, m_repeatCombo);
161 : :
162 [ + - ]: 12 : mainLayout->addLayout(form);
163 : :
164 : : // Buttons
165 [ + - + - : 12 : auto *btnRow = new QHBoxLayout();
- + - - ]
166 [ + - ]: 12 : btnRow->addStretch();
167 [ + - + - : 12 : m_cancelBtn = new QPushButton(tr("Cancel"), this);
+ - - + -
- ]
168 [ + - + - ]: 12 : m_cancelBtn->setCursor(Qt::PointingHandCursor);
169 [ + - + - : 12 : m_saveBtn = new QPushButton(tr("Save"), this);
+ - - + -
- ]
170 [ + - ]: 24 : m_saveBtn->setObjectName(QStringLiteral("eventSaveButton"));
171 [ + - + - ]: 12 : m_saveBtn->setCursor(Qt::PointingHandCursor);
172 [ + - ]: 12 : m_saveBtn->setDefault(true);
173 [ + - ]: 12 : btnRow->addWidget(m_cancelBtn);
174 [ + - ]: 12 : btnRow->addWidget(m_saveBtn);
175 [ + - ]: 12 : mainLayout->addLayout(btnRow);
176 : :
177 : : // Connections
178 [ + - ]: 12 : connect(m_cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
179 [ + - ]: 12 : connect(m_saveBtn, &QPushButton::clicked, this, [this]() {
180 [ + - + - : 2 : if (m_titleEdit->text().trimmed().isEmpty()) {
+ + ]
181 : 1 : m_titleEdit->setFocus();
182 : 1 : setTitleInvalid(true);
183 : 1 : return;
184 : : }
185 : : // Save last-used calendar to QSettings. The combo's item data is the
186 : : // calendar INDEX — store the path, which is what setCalendars()
187 : : // matches on restore (storing the index made the restore never hit).
188 [ + - + - ]: 1 : const int calIdx = m_calendarCombo->currentData().toInt();
189 [ + - + - : 1 : if (m_isNew && calIdx >= 0 && calIdx < m_calendars.size()) {
+ - + - ]
190 [ + - ]: 1 : QSettings s;
191 [ + - ]: 1 : s.setValue(QStringLiteral("calendar/lastUsedCalendar"),
192 : 1 : m_calendars.at(calIdx).path);
193 : 1 : }
194 : 1 : accept();
195 : : });
196 : 12 : connect(m_allDayCheck, &QCheckBox::toggled, this,
197 [ + - ]: 12 : &EventEditDialog::onAllDayToggled);
198 [ + - ]: 12 : connect(m_titleEdit, &QLineEdit::textChanged, this, [this]() {
199 : 8 : setTitleInvalid(false);
200 : 8 : });
201 : :
202 : : // 67.B3: calendar popups + dialog inputs are styled by the global
203 : : // theme (main.qss generic rules); no dialog-local stylesheets.
204 : :
205 : : // Dialog styling lives in main.qss (67.B3): #eventDialogHeader,
206 : : // #eventDialogSeparator, #eventTitleEdit[invalid], #eventSaveButton.
207 : 12 : }
208 : :
209 : 4 : void EventEditDialog::onAllDayToggled(bool checked) {
210 : 4 : m_startTime->setVisible(!checked);
211 : 4 : m_endTime->setVisible(!checked);
212 : 4 : }
213 : :
214 : 9 : void EventEditDialog::setTitleInvalid(bool invalid) {
215 [ + - + - : 9 : if (m_titleEdit->property("invalid").toBool() == invalid)
+ + ]
216 : 7 : return;
217 : :
218 [ + - ]: 2 : m_titleEdit->setProperty("invalid", invalid);
219 : 2 : m_titleEdit->style()->unpolish(m_titleEdit);
220 : 2 : m_titleEdit->style()->polish(m_titleEdit);
221 : 2 : m_titleEdit->update();
222 : : }
223 : :
224 : 8 : void EventEditDialog::setCalendars(const QList<CalendarInfo> &calendars) {
225 : 8 : m_calendars = calendars;
226 [ + - ]: 8 : m_calendarCombo->clear();
227 [ + + ]: 20 : for (int i = 0; i < calendars.size(); ++i) {
228 : 12 : const auto &cal = calendars.at(i);
229 [ - + ]: 12 : QString name = cal.displayName.isEmpty() ? cal.path : cal.displayName;
230 [ + - ]: 12 : m_calendarCombo->addItem(name, i);
231 : 12 : }
232 : : // Restore last-used calendar
233 [ + - ]: 8 : QSettings s;
234 [ + - + - ]: 8 : QString lastCal = s.value(QStringLiteral("calendar/lastUsedCalendar")).toString();
235 [ + + ]: 8 : if (!lastCal.isEmpty()) {
236 [ + - ]: 2 : for (int i = 0; i < m_calendars.size(); ++i) {
237 [ + + ]: 2 : if (m_calendars.at(i).path == lastCal) {
238 [ + - ]: 1 : m_calendarCombo->setCurrentIndex(i);
239 : 1 : break;
240 : : }
241 : : }
242 : : }
243 : 8 : }
244 : :
245 : 5 : void EventEditDialog::setEvent(const CalendarEvent &event) {
246 : 5 : m_originalEvent = event;
247 : 5 : m_isNew = false;
248 [ + - + - ]: 5 : setWindowTitle(tr("Edit event"));
249 : :
250 : 5 : m_titleEdit->setText(event.summary);
251 : 5 : m_allDayCheck->setChecked(event.allDay);
252 : :
253 [ + + ]: 5 : if (event.dtStart.isValid()) {
254 [ + - + - : 4 : m_startDate->setDate(event.dtStart.toLocalTime().date());
+ - ]
255 [ + - + - : 4 : m_startTime->setTime(event.dtStart.toLocalTime().time());
+ - ]
256 : : }
257 [ + + ]: 5 : if (event.dtEnd.isValid()) {
258 : : // All-day events use the iCal convention: DTEND is EXCLUSIVE (the day
259 : : // after the last day). Show the inclusive last day in the date field.
260 [ + + ]: 4 : if (event.allDay)
261 [ + - + - : 1 : m_endDate->setDate(event.dtEnd.toLocalTime().date().addDays(-1));
+ - + - ]
262 : : else
263 [ + - + - : 3 : m_endDate->setDate(event.dtEnd.toLocalTime().date());
+ - ]
264 [ + - + - : 4 : m_endTime->setTime(event.dtEnd.toLocalTime().time());
+ - ]
265 : : }
266 : :
267 : 5 : m_locationEdit->setText(event.location);
268 : 5 : m_descriptionEdit->setPlainText(event.description);
269 : :
270 : : // Match RRULE to combo. The "Keine" entry carries empty data and
271 : : // startsWith("") is always true — skip it, or every recurring event
272 : : // displayed as non-recurring when reopened.
273 [ + + ]: 5 : if (!event.rrule.isEmpty()) {
274 [ + - ]: 3 : for (int i = 0; i < m_repeatCombo->count(); ++i) {
275 [ + - + - ]: 3 : const QString prefix = m_repeatCombo->itemData(i).toString();
276 [ + + + - : 3 : if (!prefix.isEmpty() && event.rrule.startsWith(prefix)) {
+ + + + ]
277 [ + - ]: 1 : m_repeatCombo->setCurrentIndex(i);
278 : 1 : break;
279 : : }
280 [ + + ]: 3 : }
281 : : }
282 : :
283 : : // Select calendar in combo
284 [ + + ]: 7 : for (int i = 0; i < m_calendars.size(); ++i) {
285 : 5 : const auto &cal = m_calendars.at(i);
286 [ + + - + : 8 : if (cal.path == event.calendarPath &&
+ + ]
287 [ - - ]: 3 : (event.accountId.isEmpty() || cal.accountId == event.accountId)) {
288 : 3 : m_calendarCombo->setCurrentIndex(i);
289 : 3 : break;
290 : : }
291 : : }
292 : 5 : m_calendarCombo->setEnabled(false); // Can't change calendar for existing events
293 : 5 : }
294 : :
295 : 4 : void EventEditDialog::setNewEventDefaults(const QDate &date,
296 : : const QTime &startTime,
297 : : const QTime &endTime) {
298 : 4 : m_isNew = true;
299 [ + - + - ]: 4 : setWindowTitle(tr("New event"));
300 : 4 : m_calendarCombo->setEnabled(true);
301 : :
302 : 4 : m_startDate->setDate(date);
303 : 4 : m_endDate->setDate(date);
304 : :
305 [ + + ]: 4 : if (startTime.isValid()) {
306 : 3 : m_startTime->setTime(startTime);
307 [ + - ]: 3 : m_endTime->setTime(endTime.isValid() ? endTime : startTime.addSecs(3600));
308 : : } else {
309 [ + - + - ]: 1 : m_startTime->setTime(QTime(9, 0));
310 [ + - + - ]: 1 : m_endTime->setTime(QTime(10, 0));
311 : : }
312 : 4 : }
313 : :
314 : 4 : CalendarEvent EventEditDialog::result() const {
315 : 4 : CalendarEvent ev = m_originalEvent;
316 : :
317 [ + - + - ]: 4 : ev.summary = m_titleEdit->text().trimmed();
318 [ + - ]: 4 : ev.allDay = m_allDayCheck->isChecked();
319 : :
320 [ + - ]: 4 : QDate sd = m_startDate->date();
321 [ + - ]: 4 : QDate ed = m_endDate->date();
322 : :
323 [ + + ]: 4 : if (ev.allDay) {
324 [ + - + - ]: 2 : ev.dtStart = QDateTime(sd, QTime(0, 0));
325 : : // iCal convention: all-day DTEND is exclusive. Storing the inclusive
326 : : // end date made every single-day all-day event invisible
327 : : // (displayEndDate subtracts one day) and synced a one-day-short DTEND
328 : : // to the CalDAV server.
329 [ + - + - : 2 : ev.dtEnd = QDateTime(ed.addDays(1), QTime(0, 0));
+ - ]
330 : : } else {
331 [ + - + - ]: 2 : ev.dtStart = QDateTime(sd, m_startTime->time());
332 [ + - + - ]: 2 : ev.dtEnd = QDateTime(ed, m_endTime->time());
333 : : }
334 : :
335 [ + - + - ]: 4 : ev.location = m_locationEdit->text().trimmed();
336 [ + - ]: 4 : ev.description = m_descriptionEdit->toPlainText();
337 [ + - + - ]: 4 : ev.rrule = m_repeatCombo->currentData().toString();
338 : :
339 : : // Calendar path from combo (only if creating)
340 [ + + + - : 4 : if (m_isNew && m_calendarCombo->currentIndex() >= 0) {
+ + + + ]
341 [ + - + - ]: 1 : const int calIndex = m_calendarCombo->currentData().toInt();
342 [ + - + - : 1 : if (calIndex >= 0 && calIndex < m_calendars.size()) {
+ - ]
343 : 1 : ev.calendarPath = m_calendars.at(calIndex).path;
344 : 1 : ev.accountId = m_calendars.at(calIndex).accountId;
345 : : }
346 : : }
347 : :
348 : : // Generate UID for new events
349 [ + + + - : 4 : if (m_isNew && ev.uid.isEmpty()) {
+ + ]
350 [ + - + - ]: 2 : ev.uid = QUuid::createUuid().toString(QUuid::WithoutBraces);
351 : : }
352 : :
353 : 4 : return ev;
354 : 0 : }
355 : :
|