Incidenceeditor

incidencedatetime.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Bertjan Broeksema <broeksema@kde.org>
3 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "incidencedatetime.h"
9using namespace Qt::Literals::StringLiterals;
10
11#include "incidenceeditor_debug.h"
12#include "ui_dialogdesktop.h"
13
14#include <CalendarSupport/KCalPrefs>
15
16#include <KCalUtils/IncidenceFormatter>
17
18#include <QTimeZone>
19
20using namespace IncidenceEditorNG;
21
22static bool identical(QDateTime dt1, QDateTime dt2)
23{
24 return dt1 == dt2 && dt1.timeSpec() == dt2.timeSpec() && dt1.timeZone() == dt2.timeZone();
25}
26
27/**
28 * Returns true if the incidence's dates are equal to the default ones specified in config.
29 */
30static bool incidenceHasDefaultTimes(const KCalendarCore::Incidence::Ptr &incidence)
31{
32 if (!incidence || incidence->allDay()) {
33 return false;
34 }
35
36 QTime defaultDuration = CalendarSupport::KCalPrefs::instance()->defaultDuration().time();
37 if (!defaultDuration.isValid()) {
38 return false;
39 }
40
41 QTime defaultStart = CalendarSupport::KCalPrefs::instance()->mStartTime.time();
42 if (!defaultStart.isValid()) {
43 return false;
44 }
45
46 if (incidence->dtStart().time() == defaultStart) {
48 return true; // no duration to compare with
49 }
50
51 const QDateTime start = incidence->dtStart();
53 if (!end.isValid() || !start.isValid()) {
54 return false;
55 }
56
57 const int durationInSeconds = defaultDuration.hour() * 3600 + defaultDuration.minute() * 60;
58 return start.secsTo(end) == durationInSeconds;
59 }
60
61 return false;
62}
63
64IncidenceDateTime::IncidenceDateTime(Ui::EventOrTodoDesktop *ui)
65 : IncidenceEditor(nullptr)
66 , mUi(ui)
67 , mTimezoneCombosWereVisibile(false)
68{
69 setTimeZonesVisibility(false);
70 setObjectName("IncidenceDateTime"_L1);
71
72 mUi->mTimeZoneLabel->setVisible(!mUi->mWholeDayCheck->isChecked());
73 connect(mUi->mTimeZoneLabel, &QLabel::linkActivated, this, &IncidenceDateTime::toggleTimeZoneVisibility);
74 mUi->mTimeZoneLabel->setContextMenuPolicy(Qt::NoContextMenu);
75
76 const QList<QLineEdit *> lineEdits{mUi->mStartDateEdit->lineEdit(),
77 mUi->mEndDateEdit->lineEdit(),
78 mUi->mStartTimeEdit->lineEdit(),
79 mUi->mEndTimeEdit->lineEdit()};
80 for (QLineEdit *lineEdit : lineEdits) {
81 if (lineEdit) {
82 lineEdit->setClearButtonEnabled(false);
83 }
84 }
85
86 connect(mUi->mFreeBusyCheck, &QCheckBox::toggled, this, &IncidenceDateTime::checkDirtyStatus);
87 connect(mUi->mWholeDayCheck, &QCheckBox::toggled, this, &IncidenceDateTime::enableTimeEdits);
88 connect(mUi->mWholeDayCheck, &QCheckBox::toggled, this, &IncidenceDateTime::checkDirtyStatus);
89
90 connect(this, &IncidenceDateTime::startDateChanged, this, &IncidenceDateTime::updateStartToolTips);
91 connect(this, &IncidenceDateTime::startTimeChanged, this, &IncidenceDateTime::updateStartToolTips);
92 connect(this, &IncidenceDateTime::endDateChanged, this, &IncidenceDateTime::updateEndToolTips);
93 connect(this, &IncidenceDateTime::endTimeChanged, this, &IncidenceDateTime::updateEndToolTips);
94 connect(mUi->mWholeDayCheck, &QCheckBox::toggled, this, &IncidenceDateTime::updateStartToolTips);
95 connect(mUi->mWholeDayCheck, &QCheckBox::toggled, this, &IncidenceDateTime::updateEndToolTips);
96 connect(mUi->mStartCheck, &QCheckBox::toggled, this, &IncidenceDateTime::updateStartToolTips);
97 connect(mUi->mEndCheck, &QCheckBox::toggled, this, &IncidenceDateTime::updateEndToolTips);
98}
99
100IncidenceDateTime::~IncidenceDateTime() = default;
101
102bool IncidenceDateTime::eventFilter(QObject *obj, QEvent *event)
103{
104 if (event->type() == QEvent::FocusIn) {
105 if (obj == mUi->mStartDateEdit) {
106 qCDebug(INCIDENCEEDITOR_LOG) << "emitting startDateTime: " << mUi->mStartDateEdit;
107 Q_EMIT startDateFocus(obj);
108 } else if (obj == mUi->mEndDateEdit) {
109 qCDebug(INCIDENCEEDITOR_LOG) << "emitting endDateTime: " << mUi->mEndDateEdit;
110 Q_EMIT endDateFocus(obj);
111 } else if (obj == mUi->mStartTimeEdit) {
112 qCDebug(INCIDENCEEDITOR_LOG) << "emitting startTimeTime: " << mUi->mStartTimeEdit;
113 Q_EMIT startTimeFocus(obj);
114 } else if (obj == mUi->mEndTimeEdit) {
115 qCDebug(INCIDENCEEDITOR_LOG) << "emitting endTimeTime: " << mUi->mEndTimeEdit;
116 Q_EMIT endTimeFocus(obj);
117 }
118
119 return true;
120 } else {
121 // standard event processing
122 return QObject::eventFilter(obj, event);
123 }
124}
125
126void IncidenceDateTime::load(const KCalendarCore::Incidence::Ptr &incidence)
127{
128 if (mLoadedIncidence && *mLoadedIncidence == *incidence) {
129 return;
130 }
131
132 const bool isTemplate = incidence->customProperty("kdepim", "isTemplate") == "true"_L1;
133 incidence->removeCustomProperty("kdepim", "isTemplate");
134 const bool templateOverridesTimes = incidenceHasDefaultTimes(mLoadedIncidence);
135
136 mLoadedIncidence = incidence;
137 mLoadingIncidence = true;
138
139 // We can only handle events or todos.
141 load(todo, isTemplate, templateOverridesTimes);
143 load(event, isTemplate, templateOverridesTimes);
145 load(journal, isTemplate, templateOverridesTimes);
146 } else {
147 qCDebug(INCIDENCEEDITOR_LOG) << "Not an Incidence.";
148 }
149
150 // Set the initial times before calling enableTimeEdits, as enableTimeEdits
151 // assumes that the initial times are initialized.
152 mInitialStartDT = currentStartDateTime();
153 mInitialEndDT = currentEndDateTime();
154
155 enableTimeEdits();
156
157 mWasDirty = false;
158 mLoadingIncidence = false;
159}
160
161void IncidenceDateTime::save(const KCalendarCore::Incidence::Ptr &incidence)
162{
164 save(todo);
166 save(event);
168 save(journal);
169 } else {
170 Q_ASSERT_X(false, "IncidenceDateTimeEditor::save", "Only implemented for todos, events and journals");
171 }
172}
173
174bool IncidenceDateTime::isDirty() const
175{
177 return isDirty(todo);
179 return isDirty(event);
181 return isDirty(journal);
182 } else {
183 Q_ASSERT_X(false, "IncidenceDateTimeEditor::isDirty", "Only implemented for todos and events");
184 return false;
185 }
186}
187
188void IncidenceDateTime::setActiveDate(const QDate &activeDate)
189{
190 mActiveDate = activeDate;
191}
192
193QDate IncidenceDateTime::startDate() const
194{
195 return currentStartDateTime().date();
196}
197
198QDate IncidenceDateTime::endDate() const
199{
200 return currentEndDateTime().date();
201}
202
203QTime IncidenceDateTime::startTime() const
204{
205 return currentStartDateTime().time();
206}
207
208QTime IncidenceDateTime::endTime() const
209{
210 return currentEndDateTime().time();
211}
212
213/// private slots for General
214
215void IncidenceDateTime::setTimeZonesVisibility(bool visible)
216{
217 static const QString tz(i18nc("@action show or hide the time zone widgets", "Time zones"));
218 QString placeholder(QStringLiteral("<a href=\"hide\">&lt;&lt; %1</a>"));
219 if (visible) {
220 placeholder = placeholder.arg(tz);
221 } else {
222 placeholder = QStringLiteral("<a href=\"show\">%1 &gt;&gt;</a>");
223 placeholder = placeholder.arg(tz);
224 }
225 mUi->mTimeZoneLabel->setText(placeholder);
226
227 mUi->mTimeZoneComboStart->setVisible(visible);
228 mUi->mTimeZoneComboEnd->setVisible(visible && type() != KCalendarCore::Incidence::TypeJournal);
229}
230
231void IncidenceDateTime::toggleTimeZoneVisibility()
232{
233 setTimeZonesVisibility(!mUi->mTimeZoneComboStart->isVisible());
234}
235
236void IncidenceDateTime::updateStartTime(const QTime &newTime)
237{
238 if (!newTime.isValid()) {
239 return;
240 }
241
242 QDateTime endDateTime = currentEndDateTime();
243 const int secsep = mCurrentStartDateTime.secsTo(endDateTime);
244 mCurrentStartDateTime.setTime(newTime);
245 if (mUi->mEndCheck->isChecked()) {
246 // Only update the end time when it is actually enabled, adjust end time so
247 // that the event/todo has the same duration as before.
248 endDateTime = mCurrentStartDateTime.addSecs(secsep);
249 mUi->mEndTimeEdit->setTime(endDateTime.time());
250 mUi->mEndDateEdit->setDate(endDateTime.date());
251 }
252
253 Q_EMIT startTimeChanged(mCurrentStartDateTime.time());
255}
256
257void IncidenceDateTime::updateStartDate(const QDate &newDate)
258{
259 if (!newDate.isValid()) {
260 return;
261 }
262
263 const bool dateChanged = mCurrentStartDateTime.date() != newDate;
264
265 QDateTime endDateTime = currentEndDateTime();
266 int daysep = mCurrentStartDateTime.daysTo(endDateTime);
267 mCurrentStartDateTime.setDate(newDate);
268 if (mUi->mEndCheck->isChecked()) {
269 // Only update the end time when it is actually enabled, adjust end time so
270 // that the event/todo has the same duration as before.
271 endDateTime.setDate(mCurrentStartDateTime.date().addDays(daysep));
272 mUi->mEndDateEdit->setDate(endDateTime.date());
273 }
274
276
277 if (dateChanged) {
278 Q_EMIT startDateChanged(mCurrentStartDateTime.date());
279 }
280}
281
282void IncidenceDateTime::updateStartSpec()
283{
284 const QDate prevDate = mCurrentStartDateTime.date();
285
286 // RFC 5545 states that both date-times must be "floating" if either is.
287 // Otherwise, for the user's convenience, if both combos used to be the same
288 // then keep them the same.
289 if ((mUi->mTimeZoneComboStart->isFloating() != mUi->mTimeZoneComboEnd->isFloating())
290 || currentEndDateTime().timeZone() == mCurrentStartDateTime.timeZone()) {
291 mUi->mTimeZoneComboEnd->setCurrentIndex(mUi->mTimeZoneComboStart->currentIndex());
292 }
293
294 mUi->mTimeZoneComboStart->applyTimeZoneTo(mCurrentStartDateTime);
295
296 const bool dateChanged = mCurrentStartDateTime.date() != prevDate;
297
298 if (dateChanged) {
299 Q_EMIT startDateChanged(mCurrentStartDateTime.date());
300 }
301
304 }
305}
306
307void IncidenceDateTime::updateEndSpec()
308{
309 // RFC 5545 states that both date-times must be "floating" if either is.
310 if (mUi->mTimeZoneComboStart->isFloating() != mUi->mTimeZoneComboEnd->isFloating()) {
311 mUi->mTimeZoneComboStart->setCurrentIndex(mUi->mTimeZoneComboEnd->currentIndex());
312 }
314}
315
316/// private slots for Todo
317
318void IncidenceDateTime::enableStartEdit(bool enable)
319{
320 mUi->mStartDateEdit->setEnabled(enable);
321
322 if (mUi->mEndCheck->isChecked() || mUi->mStartCheck->isChecked()) {
323 mUi->mWholeDayCheck->setEnabled(true);
324 setTimeZoneLabelEnabled(!mUi->mWholeDayCheck->isChecked());
325 } else {
326 mUi->mWholeDayCheck->setEnabled(false);
327 mUi->mWholeDayCheck->setChecked(false);
328 setTimeZoneLabelEnabled(false);
329 }
330
331 if (enable) {
332 mUi->mStartTimeEdit->setEnabled(!mUi->mWholeDayCheck->isChecked());
333 mUi->mTimeZoneComboStart->setEnabled(!mUi->mWholeDayCheck->isChecked());
334 } else {
335 mUi->mStartTimeEdit->setEnabled(false);
336 mUi->mTimeZoneComboStart->setEnabled(false);
337 }
338
340}
341
342void IncidenceDateTime::enableEndEdit(bool enable)
343{
344 mUi->mEndDateEdit->setEnabled(enable);
345
346 if (mUi->mEndCheck->isChecked() || mUi->mStartCheck->isChecked()) {
347 mUi->mWholeDayCheck->setEnabled(true);
348 setTimeZoneLabelEnabled(!mUi->mWholeDayCheck->isChecked());
349 } else {
350 mUi->mWholeDayCheck->setEnabled(false);
351 mUi->mWholeDayCheck->setChecked(false);
352 setTimeZoneLabelEnabled(false);
353 }
354
355 if (enable) {
356 mUi->mEndTimeEdit->setEnabled(!mUi->mWholeDayCheck->isChecked());
357 mUi->mTimeZoneComboEnd->setEnabled(!mUi->mWholeDayCheck->isChecked());
358 } else {
359 mUi->mEndTimeEdit->setEnabled(false);
360 mUi->mTimeZoneComboEnd->setEnabled(false);
361 }
362
364}
365
366bool IncidenceDateTime::timeZonesAreLocal(const QDateTime &start, const QDateTime &end)
367{
368 // Returns false if the incidence start or end timezone is not the local zone.
369
370 if ((start.isValid() && start.timeZone() != QTimeZone::systemTimeZone()) || (end.isValid() && end.timeZone() != QTimeZone::systemTimeZone())) {
371 return false;
372 } else {
373 return true;
374 }
375}
376
377void IncidenceDateTime::enableTimeEdits()
378{
379 // NOTE: assumes that the initial times are initialized.
380 const bool wholeDayChecked = mUi->mWholeDayCheck->isChecked();
381
382 setTimeZoneLabelEnabled(!wholeDayChecked);
383
384 if (mUi->mStartCheck->isChecked()) {
385 mUi->mStartTimeEdit->setEnabled(!wholeDayChecked);
386 mUi->mTimeZoneComboStart->setEnabled(!wholeDayChecked);
387 if (wholeDayChecked)
388 mUi->mTimeZoneComboStart->setFloating(true);
389 else
390 mUi->mTimeZoneComboStart->selectTimeZoneFor(mInitialStartDT);
391 }
392 if (mUi->mEndCheck->isChecked()) {
393 mUi->mEndTimeEdit->setEnabled(!wholeDayChecked);
394 mUi->mTimeZoneComboEnd->setEnabled(!wholeDayChecked);
395 if (wholeDayChecked)
396 mUi->mTimeZoneComboEnd->setFloating(true);
397 else
398 mUi->mTimeZoneComboEnd->selectTimeZoneFor(mInitialEndDT);
399 }
400
401 /**
402 When editing a whole-day event, unchecking mWholeDayCheck shouldn't set both
403 times to 00:00. DTSTART must always be smaller than DTEND
404 */
405 if (sender() == mUi->mWholeDayCheck && !wholeDayChecked // Somebody unchecked it, the incidence will now have time.
406 && mUi->mStartCheck->isChecked() && mUi->mEndCheck->isChecked() // The incidence has both start and end/due dates
407 && currentStartDateTime() == currentEndDateTime()) { // DTSTART == DTEND. This is illegal, lets correct it.
408 // Not sure about the best time here... doesn't really matter, when someone unchecks mWholeDayCheck, she will
409 // always want to set a time.
410 mUi->mStartTimeEdit->setTime(QTime(0, 0));
411 mUi->mEndTimeEdit->setTime(QTime(1, 0));
412 }
413
414 const bool currentlyVisible = mUi->mTimeZoneLabel->text().contains("&lt;&lt;"_L1);
415 setTimeZonesVisibility(!wholeDayChecked && mTimezoneCombosWereVisibile);
416 mTimezoneCombosWereVisibile = currentlyVisible;
417 if (!wholeDayChecked && !timeZonesAreLocal(currentStartDateTime(), currentEndDateTime())) {
418 setTimeZonesVisibility(true);
419 mTimezoneCombosWereVisibile = true;
420 }
421}
422
423bool IncidenceDateTime::isDirty(const KCalendarCore::Todo::Ptr &todo) const
424{
425 Q_ASSERT(todo);
426
427 const bool hasDateTimes = mUi->mStartCheck->isChecked() || mUi->mEndCheck->isChecked();
428
429 // First check the start time/date of the todo
430 if (todo->hasStartDate() != mUi->mStartCheck->isChecked()) {
431 return true;
432 }
433
434 if ((hasDateTimes && todo->allDay()) != mUi->mWholeDayCheck->isChecked()) {
435 return true;
436 }
437
438 if (todo->hasDueDate() != mUi->mEndCheck->isChecked()) {
439 return true;
440 }
441
442 if (todo->allDay()) {
443 if ((mUi->mStartCheck->isChecked() && mUi->mStartDateEdit->date() != mInitialStartDT.date())
444 || (mUi->mEndCheck->isChecked() && mUi->mEndDateEdit->date() != mInitialEndDT.date())) {
445 return true;
446 }
447 } else {
448 if ((mUi->mStartCheck->isChecked() && !identical(currentStartDateTime(), mInitialStartDT))
449 || (mUi->mEndCheck->isChecked() && !identical(currentEndDateTime(), mInitialEndDT))) {
450 return true;
451 }
452 }
453
454 return false;
455}
456
457/// Event specific methods
458
459bool IncidenceDateTime::isDirty(const KCalendarCore::Event::Ptr &event) const
460{
461 if (event->allDay() != mUi->mWholeDayCheck->isChecked()) {
462 return true;
463 }
464
465 if (mUi->mFreeBusyCheck->isChecked() && event->transparency() != KCalendarCore::Event::Opaque) {
466 return true;
467 }
468
469 if (!mUi->mFreeBusyCheck->isChecked() && event->transparency() != KCalendarCore::Event::Transparent) {
470 return true;
471 }
472
473 if (event->allDay()) {
474 if (mUi->mStartDateEdit->date() != mInitialStartDT.date() || mUi->mEndDateEdit->date() != mInitialEndDT.date()) {
475 return true;
476 }
477 } else {
478 if (!identical(currentStartDateTime(), mInitialStartDT) || !identical(currentEndDateTime(), mInitialEndDT)) {
479 return true;
480 }
481 }
482
483 return false;
484}
485
486bool IncidenceDateTime::isDirty(const KCalendarCore::Journal::Ptr &journal) const
487{
488 if (journal->allDay() != mUi->mWholeDayCheck->isChecked()) {
489 return true;
490 }
491
492 if (journal->allDay()) {
493 if (mUi->mStartDateEdit->date() != mInitialStartDT.date()) {
494 return true;
495 }
496 } else {
497 if (!identical(currentStartDateTime(), mInitialStartDT)) {
498 return true;
499 }
500 }
501
502 return false;
503}
504
505/// Private methods
506
507QDateTime IncidenceDateTime::currentStartDateTime() const
508{
509 QDateTime dt(mUi->mStartDateEdit->date(), mUi->mStartTimeEdit->time());
510 mUi->mTimeZoneComboStart->applyTimeZoneTo(dt);
511 return dt;
512}
513
514QDateTime IncidenceDateTime::currentEndDateTime() const
515{
516 QDateTime dt(mUi->mEndDateEdit->date(), mUi->mEndTimeEdit->time());
517 mUi->mTimeZoneComboEnd->applyTimeZoneTo(dt);
518 return dt;
519}
520
521void IncidenceDateTime::load(const KCalendarCore::Event::Ptr &event, bool isTemplate, bool templateOverridesTimes)
522{
523 // First en/disable the necessary ui bits and pieces
524 mUi->mStartCheck->setVisible(false);
525 mUi->mStartCheck->setChecked(true); // Set to checked so we can reuse enableTimeEdits.
526 mUi->mEndCheck->setVisible(false);
527 mUi->mEndCheck->setChecked(true); // Set to checked so we can reuse enableTimeEdits.
528
529 // Start time
530 connect(mUi->mStartTimeEdit, &KTimeComboBox::timeChanged, this,
531 &IncidenceDateTime::updateStartTime); // when editing with mouse, or up/down arrows
532 connect(mUi->mStartTimeEdit, &KTimeComboBox::timeEdited, this,
533 &IncidenceDateTime::updateStartTime); // When editing with any key except up/down
534 connect(mUi->mStartDateEdit, &KDateComboBox::dateChanged, this, &IncidenceDateTime::updateStartDate);
535 connect(mUi->mTimeZoneComboStart,
537 this,
538 &IncidenceDateTime::updateStartSpec);
539
540 // End time
544 connect(mUi->mEndTimeEdit, &KTimeComboBox::timeChanged, this, &IncidenceDateTime::endTimeChanged);
545 connect(mUi->mEndTimeEdit, &KTimeComboBox::timeEdited, this, &IncidenceDateTime::endTimeChanged);
546 connect(mUi->mEndDateEdit, &KDateComboBox::dateChanged, this, &IncidenceDateTime::endDateChanged);
547 connect(mUi->mTimeZoneComboEnd,
549 this,
550 &IncidenceDateTime::updateEndSpec);
551 mUi->mWholeDayCheck->setChecked(event->allDay());
552 enableTimeEdits();
553
554 if (isTemplate) {
555 if (templateOverridesTimes) {
556 // We only use the template times if the user didn't override them.
557 setTimes(event->dtStart(), event->dtEnd());
558 }
559 } else {
560 QDateTime startDT = event->dtStart();
561 QDateTime endDT = event->dtEnd();
562 setDateTimes(startDT, endDT);
563 }
564
565 switch (event->transparency()) {
567 mUi->mFreeBusyCheck->setChecked(false);
568 break;
570 mUi->mFreeBusyCheck->setChecked(true);
571 break;
572 }
573}
574
575void IncidenceDateTime::load(const KCalendarCore::Journal::Ptr &journal, bool isTemplate, bool templateOverridesTimes)
576{
577 // First en/disable the necessary ui bits and pieces
578 mUi->mStartCheck->setVisible(false);
579 mUi->mStartCheck->setChecked(true); // Set to checked so we can reuse enableTimeEdits.
580 mUi->mEndCheck->setVisible(false);
581 mUi->mEndCheck->setChecked(true); // Set to checked so we can reuse enableTimeEdits.
582 mUi->mEndDateEdit->setVisible(false);
583 mUi->mEndTimeEdit->setVisible(false);
584 mUi->mTimeZoneComboEnd->setVisible(false);
585 mUi->mEndLabel->setVisible(false);
586 mUi->mFreeBusyCheck->setVisible(false);
587
588 // Start time
589 connect(mUi->mStartTimeEdit, &KTimeComboBox::timeChanged, this, &IncidenceDateTime::updateStartTime);
590 connect(mUi->mStartDateEdit, &KDateComboBox::dateChanged, this, &IncidenceDateTime::updateStartDate);
591 connect(mUi->mTimeZoneComboStart,
593 this,
594 &IncidenceDateTime::updateStartSpec);
595 mUi->mWholeDayCheck->setChecked(journal->allDay());
596 enableTimeEdits();
597
598 if (isTemplate) {
599 if (templateOverridesTimes) {
600 // We only use the template times if the user didn't override them.
601 setTimes(journal->dtStart(), QDateTime());
602 }
603 } else {
604 QDateTime startDT = journal->dtStart();
605 // Journals do not have end dates, so pick an arbitrary suitable date.
606 setDateTimes(startDT, startDT);
607 }
608}
609
610void IncidenceDateTime::load(const KCalendarCore::Todo::Ptr &todo, bool isTemplate, bool templateOverridesTimes)
611{
612 // First en/disable the necessary ui bits and pieces
613 mUi->mStartCheck->setVisible(true);
614 mUi->mStartCheck->setChecked(todo->hasStartDate());
615 mUi->mStartDateEdit->setEnabled(todo->hasStartDate());
616 mUi->mStartTimeEdit->setEnabled(todo->hasStartDate());
617 mUi->mTimeZoneComboStart->setEnabled(todo->hasStartDate());
618
619 mUi->mEndLabel->setText(i18nc("@label The due date/time of a to-do", "Due:"));
620 mUi->mEndCheck->setVisible(true);
621 mUi->mEndCheck->setChecked(todo->hasDueDate());
622 mUi->mEndDateEdit->setEnabled(todo->hasDueDate());
623 mUi->mEndTimeEdit->setEnabled(todo->hasDueDate());
624 mUi->mTimeZoneComboEnd->setEnabled(todo->hasDueDate());
625
626 // These fields where not enabled in the old code either:
627 mUi->mFreeBusyCheck->setVisible(false);
628
629 const bool hasDateTimes = mUi->mEndCheck->isChecked() || mUi->mStartCheck->isChecked();
630 mUi->mWholeDayCheck->setChecked(hasDateTimes && todo->allDay());
631 mUi->mWholeDayCheck->setEnabled(hasDateTimes);
632
633 // Connect to the right logic
634 connect(mUi->mStartCheck, &QCheckBox::toggled, this, &IncidenceDateTime::enableStartEdit);
635 connect(mUi->mStartCheck, &QCheckBox::toggled, this, &IncidenceDateTime::startDateTimeToggled);
636 connect(mUi->mStartDateEdit, &KDateComboBox::dateChanged, this, &IncidenceDateTime::updateStartDate);
637 connect(mUi->mStartTimeEdit, &KTimeComboBox::timeChanged, this, &IncidenceDateTime::updateStartTime);
638 connect(mUi->mStartTimeEdit, &KTimeComboBox::timeEdited, this, &IncidenceDateTime::updateStartTime);
639 connect(mUi->mTimeZoneComboStart,
641 this,
642 &IncidenceDateTime::updateStartSpec);
643
644 connect(mUi->mEndCheck, &QCheckBox::toggled, this, &IncidenceDateTime::enableEndEdit);
645 connect(mUi->mEndCheck, &QCheckBox::toggled, this, &IncidenceDateTime::endDateTimeToggled);
649 connect(mUi->mEndDateEdit, &KDateComboBox::dateChanged, this, &IncidenceDateTime::endDateChanged);
650 connect(mUi->mEndTimeEdit, &KTimeComboBox::timeChanged, this, &IncidenceDateTime::endTimeChanged);
651 connect(mUi->mEndTimeEdit, &KTimeComboBox::timeEdited, this, &IncidenceDateTime::endTimeChanged);
652 connect(mUi->mTimeZoneComboEnd,
654 this,
655 &IncidenceDateTime::updateEndSpec);
656 const QDateTime rightNow = QDateTime::currentDateTime();
657
658 if (isTemplate) {
659 if (templateOverridesTimes) {
660 // We only use the template times if the user didn't override them.
661 setTimes(todo->dtStart(), todo->dateTime(KCalendarCore::Incidence::RoleEnd));
662 }
663 } else {
664 const QDateTime endDT = todo->hasDueDate() ? todo->dtDue(true /** first */) : rightNow;
665 const QDateTime startDT = todo->hasStartDate() ? todo->dtStart(true /** first */) : rightNow;
666 setDateTimes(startDT, endDT);
667 }
668}
669
670void IncidenceDateTime::save(const KCalendarCore::Event::Ptr &event)
671{
672 event->setAllDay(mUi->mWholeDayCheck->isChecked());
673 event->setDtStart(currentStartDateTime());
674 event->setDtEnd(currentEndDateTime());
675
676 // Free == Event::Transparent
677 // Busy == Event::Opaque
678 event->setTransparency(mUi->mFreeBusyCheck->isChecked() ? KCalendarCore::Event::Opaque : KCalendarCore::Event::Transparent);
679}
680
681void IncidenceDateTime::save(const KCalendarCore::Todo::Ptr &todo)
682{
683 if (mUi->mStartCheck->isChecked()) {
684 todo->setDtStart(currentStartDateTime());
685 todo->setAllDay(mUi->mWholeDayCheck->isChecked());
686 if (currentStartDateTime() != mInitialStartDT) {
687 // We don't offer any way to edit the current completed occurrence.
688 // So, if the start date changes, reset the dtRecurrence
689 todo->setDtRecurrence(currentStartDateTime());
690 }
691 } else {
692 todo->setDtStart(QDateTime());
693 }
694
695 if (mUi->mEndCheck->isChecked()) {
696 todo->setDtDue(currentEndDateTime(), true /* first */);
697 todo->setAllDay(mUi->mWholeDayCheck->isChecked());
698 } else {
699 todo->setDtDue(QDateTime(), true /* first */);
700 }
701}
702
703void IncidenceDateTime::save(const KCalendarCore::Journal::Ptr &journal)
704{
705 journal->setAllDay(mUi->mWholeDayCheck->isChecked());
706 journal->setDtStart(currentStartDateTime());
707}
708
709void IncidenceDateTime::setDateTimes(const QDateTime &start, const QDateTime &end)
710{
711 if (start.isValid()) {
712 mUi->mStartDateEdit->setDate(start.date());
713 mUi->mStartTimeEdit->setTime(start.time());
714 mUi->mTimeZoneComboStart->selectTimeZoneFor(start);
715 } else {
717 mUi->mStartDateEdit->setDate(dt.date());
718 mUi->mStartTimeEdit->setTime(dt.time());
719 mUi->mTimeZoneComboStart->selectTimeZoneFor(dt);
720 }
721
722 if (end.isValid()) {
723 mUi->mEndDateEdit->setDate(end.date());
724 mUi->mEndTimeEdit->setTime(end.time());
725 mUi->mTimeZoneComboEnd->selectTimeZoneFor(end);
726 } else {
727 QDateTime dt(QDate::currentDate(), QTime::currentTime().addSecs(60 * 60));
728 mUi->mEndDateEdit->setDate(dt.date());
729 mUi->mEndTimeEdit->setTime(dt.time());
730 mUi->mTimeZoneComboEnd->selectTimeZoneFor(dt);
731 }
732
733 mCurrentStartDateTime = currentStartDateTime();
734 Q_EMIT startDateChanged(start.date());
735 Q_EMIT startTimeChanged(start.time());
736 Q_EMIT endDateChanged(end.date());
737 Q_EMIT endTimeChanged(end.time());
738
739 updateStartToolTips();
740 updateEndToolTips();
741}
742
743void IncidenceDateTime::updateStartToolTips()
744{
745 if (mUi->mStartCheck->isChecked()) {
746 QString datetimeStr = KCalUtils::IncidenceFormatter::dateTimeToString(currentStartDateTime(), mUi->mWholeDayCheck->isChecked(), false);
747 mUi->mStartDateEdit->setToolTip(i18nc("@info:tooltip", "Starts: %1", datetimeStr));
748 mUi->mStartTimeEdit->setToolTip(i18nc("@info:tooltip", "Starts: %1", datetimeStr));
749 } else {
750 mUi->mStartDateEdit->setToolTip(i18nc("@info:tooltip", "Starting Date"));
751 mUi->mStartTimeEdit->setToolTip(i18nc("@info:tooltip", "Starting Time"));
752 }
753}
754
755void IncidenceDateTime::updateEndToolTips()
756{
757 if (mUi->mStartCheck->isChecked()) {
758 QString datetimeStr = KCalUtils::IncidenceFormatter::dateTimeToString(currentEndDateTime(), mUi->mWholeDayCheck->isChecked(), false);
759 if (mLoadedIncidence->type() == KCalendarCore::Incidence::TypeTodo) {
760 mUi->mEndDateEdit->setToolTip(i18nc("@info:tooltip", "Due on: %1", datetimeStr));
761 mUi->mEndTimeEdit->setToolTip(i18nc("@info:tooltip", "Due on: %1", datetimeStr));
762 } else {
763 mUi->mEndDateEdit->setToolTip(i18nc("@info:tooltip", "Ends: %1", datetimeStr));
764 mUi->mEndTimeEdit->setToolTip(i18nc("@info:tooltip", "Ends: %1", datetimeStr));
765 }
766 } else {
767 if (mLoadedIncidence->type() == KCalendarCore::Incidence::TypeTodo) {
768 mUi->mEndDateEdit->setToolTip(i18nc("@info:tooltip", "Due Date"));
769 mUi->mEndTimeEdit->setToolTip(i18nc("@info:tooltip", "Due Time"));
770 } else {
771 mUi->mEndDateEdit->setToolTip(i18nc("@info:tooltip", "Ending Date"));
772 mUi->mEndTimeEdit->setToolTip(i18nc("@info:tooltip", "Ending Time"));
773 }
774 }
775}
776
777void IncidenceDateTime::setTimes(const QDateTime &start, const QDateTime &end)
778{
779 // like setDateTimes(), but it set only the start/end time, not the date
780 // it is used while applying a template to an event.
781 mUi->mStartTimeEdit->blockSignals(true);
782 mUi->mStartTimeEdit->setTime(start.time());
783 mUi->mStartTimeEdit->blockSignals(false);
784
785 mUi->mEndTimeEdit->setTime(end.time());
786
787 mUi->mTimeZoneComboStart->selectTimeZoneFor(start);
788 mUi->mTimeZoneComboEnd->selectTimeZoneFor(end);
789
790 // emitDateTimeStr();
791}
792
793void IncidenceDateTime::setStartDate(const QDate &newDate)
794{
795 mUi->mStartDateEdit->setDate(newDate);
796 updateStartDate(newDate);
797}
798
799void IncidenceDateTime::setStartTime(const QTime &newTime)
800{
801 mUi->mStartTimeEdit->setTime(newTime);
802 updateStartTime(newTime);
803}
804
805bool IncidenceDateTime::startDateTimeEnabled() const
806{
807 return mUi->mStartCheck->isChecked();
808}
809
810bool IncidenceDateTime::endDateTimeEnabled() const
811{
812 return mUi->mEndCheck->isChecked();
813}
814
815void IncidenceDateTime::focusInvalidField()
816{
817 if (startDateTimeEnabled()) {
818 if (!mUi->mStartDateEdit->isValid()) {
819 mUi->mStartDateEdit->setFocus();
820 return;
821 }
822 if (!mUi->mWholeDayCheck->isChecked() && !mUi->mStartTimeEdit->isValid()) {
823 mUi->mStartTimeEdit->setFocus();
824 return;
825 }
826 }
827 if (endDateTimeEnabled()) {
828 if (!mUi->mEndDateEdit->isValid()) {
829 mUi->mEndDateEdit->setFocus();
830 return;
831 }
832 if (!mUi->mWholeDayCheck->isChecked() && !mUi->mEndTimeEdit->isValid()) {
833 mUi->mEndTimeEdit->setFocus();
834 return;
835 }
836 }
837 if (startDateTimeEnabled() && endDateTimeEnabled() && currentStartDateTime() > currentEndDateTime()) {
838 if (mUi->mEndDateEdit->date() < mUi->mStartDateEdit->date()) {
839 mUi->mEndDateEdit->setFocus();
840 } else {
841 mUi->mEndTimeEdit->setFocus();
842 }
843 }
844}
845
846bool IncidenceDateTime::isValid() const
847{
848 if (startDateTimeEnabled()) {
849 if (!mUi->mStartDateEdit->isValid()) {
850 mLastErrorString = i18nc("@info", "Invalid start date.");
851 qCDebug(INCIDENCEEDITOR_LOG) << mLastErrorString;
852 return false;
853 }
854 if (!mUi->mWholeDayCheck->isChecked() && !mUi->mStartTimeEdit->isValid()) {
855 mLastErrorString = i18nc("@info", "Invalid start time.");
856 qCDebug(INCIDENCEEDITOR_LOG) << mLastErrorString;
857 return false;
858 }
859 }
860
861 if (endDateTimeEnabled()) {
862 if (!mUi->mEndDateEdit->isValid()) {
863 mLastErrorString = i18nc("@info", "Invalid end date.");
864 qCDebug(INCIDENCEEDITOR_LOG) << mLastErrorString;
865 return false;
866 }
867 if (!mUi->mWholeDayCheck->isChecked() && !mUi->mEndTimeEdit->isValid()) {
868 mLastErrorString = i18nc("@info", "Invalid end time.");
869 qCDebug(INCIDENCEEDITOR_LOG) << mLastErrorString;
870 return false;
871 }
872 }
873
874 if (startDateTimeEnabled() && endDateTimeEnabled() && currentStartDateTime() > currentEndDateTime()) {
875 if (mLoadedIncidence->type() == KCalendarCore::Incidence::TypeEvent) {
876 mLastErrorString = i18nc("@info",
877 "The event ends before it starts.\n"
878 "Please correct dates and times.");
879 } else if (mLoadedIncidence->type() == KCalendarCore::Incidence::TypeTodo) {
880 mLastErrorString = i18nc("@info",
881 "The to-do is due before it starts.\n"
882 "Please correct dates and times.");
883 } else if (mLoadedIncidence->type() == KCalendarCore::Incidence::TypeJournal) {
884 return true;
885 }
886
887 qCDebug(INCIDENCEEDITOR_LOG) << mLastErrorString;
888 return false;
889 } else {
890 mLastErrorString.clear();
891 return true;
892 }
893}
894
895void IncidenceDateTime::printDebugInfo() const
896{
897 qCDebug(INCIDENCEEDITOR_LOG) << "startDateTimeEnabled() : " << startDateTimeEnabled();
898 qCDebug(INCIDENCEEDITOR_LOG) << "endDateTimeEnabled() : " << endDateTimeEnabled();
899 qCDebug(INCIDENCEEDITOR_LOG) << "currentStartDateTime().isValid(): " << currentStartDateTime().isValid();
900 qCDebug(INCIDENCEEDITOR_LOG) << "currentEndDateTime().isValid() : " << currentEndDateTime().isValid();
901 qCDebug(INCIDENCEEDITOR_LOG) << "currentStartDateTime() : " << currentStartDateTime().toString();
902 qCDebug(INCIDENCEEDITOR_LOG) << "currentEndDateTime() : " << currentEndDateTime().toString();
903 qCDebug(INCIDENCEEDITOR_LOG) << "Incidence type : " << mLoadedIncidence->type();
904 qCDebug(INCIDENCEEDITOR_LOG) << "allday : " << mLoadedIncidence->allDay();
905 qCDebug(INCIDENCEEDITOR_LOG) << "mInitialStartDT : " << mInitialStartDT.toString();
906 qCDebug(INCIDENCEEDITOR_LOG) << "mInitialEndDT : " << mInitialEndDT.toString();
907
908 qCDebug(INCIDENCEEDITOR_LOG) << "currentStartDateTime().timeZone(): " << currentStartDateTime().timeZone().id();
909 qCDebug(INCIDENCEEDITOR_LOG) << "currentEndDateTime().timeZone() : " << currentEndDateTime().timeZone().id();
910 qCDebug(INCIDENCEEDITOR_LOG) << "mInitialStartDT.timeZone() : " << mInitialStartDT.timeZone().id();
911 qCDebug(INCIDENCEEDITOR_LOG) << "mInitialEndDT.timeZone() : " << mInitialEndDT.timeZone().id();
912
913 qCDebug(INCIDENCEEDITOR_LOG) << "dirty test1: " << (mLoadedIncidence->allDay() != mUi->mWholeDayCheck->isChecked());
914 if (mLoadedIncidence->type() == KCalendarCore::Incidence::TypeEvent) {
916 qCDebug(INCIDENCEEDITOR_LOG) << "dirty test2: " << (mUi->mFreeBusyCheck->isChecked() && event->transparency() != KCalendarCore::Event::Opaque);
917 qCDebug(INCIDENCEEDITOR_LOG) << "dirty test3: " << (!mUi->mFreeBusyCheck->isChecked() && event->transparency() != KCalendarCore::Event::Transparent);
918 }
919
920 if (mLoadedIncidence->allDay()) {
921 qCDebug(INCIDENCEEDITOR_LOG) << "dirty test4: "
922 << (mUi->mStartDateEdit->date() != mInitialStartDT.date() || mUi->mEndDateEdit->date() != mInitialEndDT.date());
923 } else {
924 qCDebug(INCIDENCEEDITOR_LOG) << "dirty test4.1: " << (currentStartDateTime() != mInitialStartDT);
925 qCDebug(INCIDENCEEDITOR_LOG) << "dirty test4.2: " << (currentEndDateTime() != mInitialEndDT);
926 qCDebug(INCIDENCEEDITOR_LOG) << "dirty test4.3: " << (currentStartDateTime().timeZone() != mInitialStartDT.timeZone());
927 qCDebug(INCIDENCEEDITOR_LOG) << "dirty test4.4: " << (currentEndDateTime().timeZone() != mInitialEndDT.timeZone());
928 }
929}
930
931void IncidenceDateTime::setTimeZoneLabelEnabled(bool enable)
932{
933 mUi->mTimeZoneLabel->setVisible(enable);
934}
935
936#include "moc_incidencedatetime.cpp"
KCal Incidences are complicated objects.
void checkDirtyStatus()
Checks if the dirty status has changed until last check and emits the dirtyStatusChanged signal if ne...
KCalendarCore::IncidenceBase::IncidenceType type() const
Returns the type of the Incidence that is currently loaded.
QSharedPointer< IncidenceT > incidence() const
Convenience method to get a pointer for a specific const Incidence Type.
A combobox that shows the system timezones available in QTimeZone and provides methods to easily sele...
void dateChanged(const QDate &date)
void timeChanged(const QTime &time)
void timeEdited(const QTime &time)
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
AKONADI_CALENDAR_EXPORT KCalendarCore::Journal::Ptr journal(const Akonadi::Item &item)
AKONADI_CALENDAR_EXPORT KCalendarCore::Todo::Ptr todo(const Akonadi::Item &item)
KCALUTILS_EXPORT QString dateTimeToString(const QDateTime &date, bool dateOnly=false, bool shortfmt=true)
KCALENDARCORE_EXPORT bool identical(const QDateTime &dt1, const QDateTime &dt2)
QDateTime endDateTime(const QVariant &res)
const QList< QKeySequence > & end()
void toggled(bool checked)
void currentIndexChanged(int index)
QDate addDays(qint64 ndays) const const
QDate currentDate()
bool isValid(int year, int month, int day)
QDateTime addSecs(qint64 s) const const
QDateTime currentDateTime()
QDate date() const const
qint64 daysTo(const QDateTime &other) const const
bool isValid() const const
qint64 secsTo(const QDateTime &other) const const
void setDate(QDate date)
void setTime(QTime time)
QTime time() const const
Qt::TimeSpec timeSpec() const const
QTimeZone timeZone() const const
QString toString(QStringView format, QCalendar cal) const const
void linkActivated(const QString &link)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool event(QEvent *e)
virtual bool eventFilter(QObject *watched, QEvent *event)
QObject * sender() const const
QSharedPointer< X > staticCast() const const
void clear()
NoContextMenu
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QTime currentTime()
int hour() const const
bool isValid(int h, int m, int s, int ms)
int minute() const const
QByteArray id() const const
QTimeZone systemTimeZone()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:58:15 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.