File indexing completed on 2024-04-28 05:11:34

0001 /*
0002   SPDX-FileCopyrightText: 2010 Bertjan Broeksema <broeksema@kde.org>
0003   SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
0004 
0005   SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "incidencedatetime.h"
0009 #include "incidenceeditor_debug.h"
0010 #include "ui_dialogdesktop.h"
0011 
0012 #include <CalendarSupport/KCalPrefs>
0013 
0014 #include <KCalUtils/IncidenceFormatter>
0015 
0016 #include <QTimeZone>
0017 
0018 using namespace IncidenceEditorNG;
0019 
0020 static bool identical(QDateTime dt1, QDateTime dt2)
0021 {
0022     return dt1 == dt2 && dt1.timeSpec() == dt2.timeSpec() && dt1.timeZone() == dt2.timeZone();
0023 }
0024 
0025 /**
0026  * Returns true if the incidence's dates are equal to the default ones specified in config.
0027  */
0028 static bool incidenceHasDefaultTimes(const KCalendarCore::Incidence::Ptr &incidence)
0029 {
0030     if (!incidence || incidence->allDay()) {
0031         return false;
0032     }
0033 
0034     QTime defaultDuration = CalendarSupport::KCalPrefs::instance()->defaultDuration().time();
0035     if (!defaultDuration.isValid()) {
0036         return false;
0037     }
0038 
0039     QTime defaultStart = CalendarSupport::KCalPrefs::instance()->mStartTime.time();
0040     if (!defaultStart.isValid()) {
0041         return false;
0042     }
0043 
0044     if (incidence->dtStart().time() == defaultStart) {
0045         if (incidence->type() == KCalendarCore::Incidence::TypeJournal) {
0046             return true; // no duration to compare with
0047         }
0048 
0049         const QDateTime start = incidence->dtStart();
0050         const QDateTime end = incidence->dateTime(KCalendarCore::Incidence::RoleEnd);
0051         if (!end.isValid() || !start.isValid()) {
0052             return false;
0053         }
0054 
0055         const int durationInSeconds = defaultDuration.hour() * 3600 + defaultDuration.minute() * 60;
0056         return start.secsTo(end) == durationInSeconds;
0057     }
0058 
0059     return false;
0060 }
0061 
0062 IncidenceDateTime::IncidenceDateTime(Ui::EventOrTodoDesktop *ui)
0063     : IncidenceEditor(nullptr)
0064     , mUi(ui)
0065     , mTimezoneCombosWereVisibile(false)
0066 {
0067     setTimeZonesVisibility(false);
0068     setObjectName(QLatin1StringView("IncidenceDateTime"));
0069 
0070     mUi->mTimeZoneLabel->setVisible(!mUi->mWholeDayCheck->isChecked());
0071     connect(mUi->mTimeZoneLabel, &QLabel::linkActivated, this, &IncidenceDateTime::toggleTimeZoneVisibility);
0072     mUi->mTimeZoneLabel->setContextMenuPolicy(Qt::NoContextMenu);
0073 
0074     const QList<QLineEdit *> lineEdits{mUi->mStartDateEdit->lineEdit(),
0075                                        mUi->mEndDateEdit->lineEdit(),
0076                                        mUi->mStartTimeEdit->lineEdit(),
0077                                        mUi->mEndTimeEdit->lineEdit()};
0078     for (QLineEdit *lineEdit : lineEdits) {
0079         if (lineEdit) {
0080             lineEdit->setClearButtonEnabled(false);
0081         }
0082     }
0083 
0084     connect(mUi->mFreeBusyCheck, &QCheckBox::toggled, this, &IncidenceDateTime::checkDirtyStatus);
0085     connect(mUi->mWholeDayCheck, &QCheckBox::toggled, this, &IncidenceDateTime::enableTimeEdits);
0086     connect(mUi->mWholeDayCheck, &QCheckBox::toggled, this, &IncidenceDateTime::checkDirtyStatus);
0087 
0088     connect(this, &IncidenceDateTime::startDateChanged, this, &IncidenceDateTime::updateStartToolTips);
0089     connect(this, &IncidenceDateTime::startTimeChanged, this, &IncidenceDateTime::updateStartToolTips);
0090     connect(this, &IncidenceDateTime::endDateChanged, this, &IncidenceDateTime::updateEndToolTips);
0091     connect(this, &IncidenceDateTime::endTimeChanged, this, &IncidenceDateTime::updateEndToolTips);
0092     connect(mUi->mWholeDayCheck, &QCheckBox::toggled, this, &IncidenceDateTime::updateStartToolTips);
0093     connect(mUi->mWholeDayCheck, &QCheckBox::toggled, this, &IncidenceDateTime::updateEndToolTips);
0094     connect(mUi->mStartCheck, &QCheckBox::toggled, this, &IncidenceDateTime::updateStartToolTips);
0095     connect(mUi->mEndCheck, &QCheckBox::toggled, this, &IncidenceDateTime::updateEndToolTips);
0096 }
0097 
0098 IncidenceDateTime::~IncidenceDateTime() = default;
0099 
0100 bool IncidenceDateTime::eventFilter(QObject *obj, QEvent *event)
0101 {
0102     if (event->type() == QEvent::FocusIn) {
0103         if (obj == mUi->mStartDateEdit) {
0104             qCDebug(INCIDENCEEDITOR_LOG) << "emitting startDateTime: " << mUi->mStartDateEdit;
0105             Q_EMIT startDateFocus(obj);
0106         } else if (obj == mUi->mEndDateEdit) {
0107             qCDebug(INCIDENCEEDITOR_LOG) << "emitting endDateTime: " << mUi->mEndDateEdit;
0108             Q_EMIT endDateFocus(obj);
0109         } else if (obj == mUi->mStartTimeEdit) {
0110             qCDebug(INCIDENCEEDITOR_LOG) << "emitting startTimeTime: " << mUi->mStartTimeEdit;
0111             Q_EMIT startTimeFocus(obj);
0112         } else if (obj == mUi->mEndTimeEdit) {
0113             qCDebug(INCIDENCEEDITOR_LOG) << "emitting endTimeTime: " << mUi->mEndTimeEdit;
0114             Q_EMIT endTimeFocus(obj);
0115         }
0116 
0117         return true;
0118     } else {
0119         // standard event processing
0120         return QObject::eventFilter(obj, event);
0121     }
0122 }
0123 
0124 void IncidenceDateTime::load(const KCalendarCore::Incidence::Ptr &incidence)
0125 {
0126     if (mLoadedIncidence && *mLoadedIncidence == *incidence) {
0127         return;
0128     }
0129 
0130     const bool isTemplate = incidence->customProperty("kdepim", "isTemplate") == QLatin1StringView("true");
0131     incidence->removeCustomProperty("kdepim", "isTemplate");
0132     const bool templateOverridesTimes = incidenceHasDefaultTimes(mLoadedIncidence);
0133 
0134     mLoadedIncidence = incidence;
0135     mLoadingIncidence = true;
0136 
0137     // We can only handle events or todos.
0138     if (KCalendarCore::Todo::Ptr todo = IncidenceDateTime::incidence<KCalendarCore::Todo>()) {
0139         load(todo, isTemplate, templateOverridesTimes);
0140     } else if (KCalendarCore::Event::Ptr event = IncidenceDateTime::incidence<KCalendarCore::Event>()) {
0141         load(event, isTemplate, templateOverridesTimes);
0142     } else if (KCalendarCore::Journal::Ptr journal = IncidenceDateTime::incidence<KCalendarCore::Journal>()) {
0143         load(journal, isTemplate, templateOverridesTimes);
0144     } else {
0145         qCDebug(INCIDENCEEDITOR_LOG) << "Not an Incidence.";
0146     }
0147 
0148     // Set the initial times before calling enableTimeEdits, as enableTimeEdits
0149     // assumes that the initial times are initialized.
0150     mInitialStartDT = currentStartDateTime();
0151     mInitialEndDT = currentEndDateTime();
0152 
0153     enableTimeEdits();
0154 
0155     mWasDirty = false;
0156     mLoadingIncidence = false;
0157 }
0158 
0159 void IncidenceDateTime::save(const KCalendarCore::Incidence::Ptr &incidence)
0160 {
0161     if (KCalendarCore::Todo::Ptr todo = IncidenceDateTime::incidence<KCalendarCore::Todo>(incidence)) {
0162         save(todo);
0163     } else if (KCalendarCore::Event::Ptr event = IncidenceDateTime::incidence<KCalendarCore::Event>(incidence)) {
0164         save(event);
0165     } else if (KCalendarCore::Journal::Ptr journal = IncidenceDateTime::incidence<KCalendarCore::Journal>(incidence)) {
0166         save(journal);
0167     } else {
0168         Q_ASSERT_X(false, "IncidenceDateTimeEditor::save", "Only implemented for todos, events and journals");
0169     }
0170 }
0171 
0172 bool IncidenceDateTime::isDirty() const
0173 {
0174     if (KCalendarCore::Todo::Ptr todo = IncidenceDateTime::incidence<KCalendarCore::Todo>()) {
0175         return isDirty(todo);
0176     } else if (KCalendarCore::Event::Ptr event = IncidenceDateTime::incidence<KCalendarCore::Event>()) {
0177         return isDirty(event);
0178     } else if (KCalendarCore::Journal::Ptr journal = IncidenceDateTime::incidence<KCalendarCore::Journal>()) {
0179         return isDirty(journal);
0180     } else {
0181         Q_ASSERT_X(false, "IncidenceDateTimeEditor::isDirty", "Only implemented for todos and events");
0182         return false;
0183     }
0184 }
0185 
0186 void IncidenceDateTime::setActiveDate(const QDate &activeDate)
0187 {
0188     mActiveDate = activeDate;
0189 }
0190 
0191 QDate IncidenceDateTime::startDate() const
0192 {
0193     return currentStartDateTime().date();
0194 }
0195 
0196 QDate IncidenceDateTime::endDate() const
0197 {
0198     return currentEndDateTime().date();
0199 }
0200 
0201 QTime IncidenceDateTime::startTime() const
0202 {
0203     return currentStartDateTime().time();
0204 }
0205 
0206 QTime IncidenceDateTime::endTime() const
0207 {
0208     return currentEndDateTime().time();
0209 }
0210 
0211 /// private slots for General
0212 
0213 void IncidenceDateTime::setTimeZonesVisibility(bool visible)
0214 {
0215     static const QString tz(i18nc("@action show or hide the time zone widgets", "Time zones"));
0216     QString placeholder(QStringLiteral("<a href=\"hide\">&lt;&lt; %1</a>"));
0217     if (visible) {
0218         placeholder = placeholder.arg(tz);
0219     } else {
0220         placeholder = QStringLiteral("<a href=\"show\">%1 &gt;&gt;</a>");
0221         placeholder = placeholder.arg(tz);
0222     }
0223     mUi->mTimeZoneLabel->setText(placeholder);
0224 
0225     mUi->mTimeZoneComboStart->setVisible(visible);
0226     mUi->mTimeZoneComboEnd->setVisible(visible && type() != KCalendarCore::Incidence::TypeJournal);
0227 }
0228 
0229 void IncidenceDateTime::toggleTimeZoneVisibility()
0230 {
0231     setTimeZonesVisibility(!mUi->mTimeZoneComboStart->isVisible());
0232 }
0233 
0234 void IncidenceDateTime::updateStartTime(const QTime &newTime)
0235 {
0236     if (!newTime.isValid()) {
0237         return;
0238     }
0239 
0240     QDateTime endDateTime = currentEndDateTime();
0241     const int secsep = mCurrentStartDateTime.secsTo(endDateTime);
0242     mCurrentStartDateTime.setTime(newTime);
0243     if (mUi->mEndCheck->isChecked()) {
0244         // Only update the end time when it is actually enabled, adjust end time so
0245         // that the event/todo has the same duration as before.
0246         endDateTime = mCurrentStartDateTime.addSecs(secsep);
0247         mUi->mEndTimeEdit->setTime(endDateTime.time());
0248         mUi->mEndDateEdit->setDate(endDateTime.date());
0249     }
0250 
0251     Q_EMIT startTimeChanged(mCurrentStartDateTime.time());
0252     checkDirtyStatus();
0253 }
0254 
0255 void IncidenceDateTime::updateStartDate(const QDate &newDate)
0256 {
0257     if (!newDate.isValid()) {
0258         return;
0259     }
0260 
0261     const bool dateChanged = mCurrentStartDateTime.date() != newDate;
0262 
0263     QDateTime endDateTime = currentEndDateTime();
0264     int daysep = mCurrentStartDateTime.daysTo(endDateTime);
0265     mCurrentStartDateTime.setDate(newDate);
0266     if (mUi->mEndCheck->isChecked()) {
0267         // Only update the end time when it is actually enabled, adjust end time so
0268         // that the event/todo has the same duration as before.
0269         endDateTime.setDate(mCurrentStartDateTime.date().addDays(daysep));
0270         mUi->mEndDateEdit->setDate(endDateTime.date());
0271     }
0272 
0273     checkDirtyStatus();
0274 
0275     if (dateChanged) {
0276         Q_EMIT startDateChanged(mCurrentStartDateTime.date());
0277     }
0278 }
0279 
0280 void IncidenceDateTime::updateStartSpec()
0281 {
0282     const QDate prevDate = mCurrentStartDateTime.date();
0283 
0284     // RFC 5545 states that both date-times must be "floating" if either is.
0285     // Otherwise, for the user's convenience, if both combos used to be the same
0286     // then keep them the same.
0287     if ((mUi->mTimeZoneComboStart->isFloating() != mUi->mTimeZoneComboEnd->isFloating())
0288         || currentEndDateTime().timeZone() == mCurrentStartDateTime.timeZone()) {
0289         mUi->mTimeZoneComboEnd->setCurrentIndex(mUi->mTimeZoneComboStart->currentIndex());
0290     }
0291 
0292     mUi->mTimeZoneComboStart->applyTimeZoneTo(mCurrentStartDateTime);
0293 
0294     const bool dateChanged = mCurrentStartDateTime.date() != prevDate;
0295 
0296     if (dateChanged) {
0297         Q_EMIT startDateChanged(mCurrentStartDateTime.date());
0298     }
0299 
0300     if (type() == KCalendarCore::Incidence::TypeJournal) {
0301         checkDirtyStatus();
0302     }
0303 }
0304 
0305 void IncidenceDateTime::updateEndSpec()
0306 {
0307     // RFC 5545 states that both date-times must be "floating" if either is.
0308     if (mUi->mTimeZoneComboStart->isFloating() != mUi->mTimeZoneComboEnd->isFloating()) {
0309         mUi->mTimeZoneComboStart->setCurrentIndex(mUi->mTimeZoneComboEnd->currentIndex());
0310     }
0311     checkDirtyStatus();
0312 }
0313 
0314 /// private slots for Todo
0315 
0316 void IncidenceDateTime::enableStartEdit(bool enable)
0317 {
0318     mUi->mStartDateEdit->setEnabled(enable);
0319 
0320     if (mUi->mEndCheck->isChecked() || mUi->mStartCheck->isChecked()) {
0321         mUi->mWholeDayCheck->setEnabled(true);
0322         setTimeZoneLabelEnabled(!mUi->mWholeDayCheck->isChecked());
0323     } else {
0324         mUi->mWholeDayCheck->setEnabled(false);
0325         mUi->mWholeDayCheck->setChecked(false);
0326         setTimeZoneLabelEnabled(false);
0327     }
0328 
0329     if (enable) {
0330         mUi->mStartTimeEdit->setEnabled(!mUi->mWholeDayCheck->isChecked());
0331         mUi->mTimeZoneComboStart->setEnabled(!mUi->mWholeDayCheck->isChecked());
0332     } else {
0333         mUi->mStartTimeEdit->setEnabled(false);
0334         mUi->mTimeZoneComboStart->setEnabled(false);
0335     }
0336 
0337     checkDirtyStatus();
0338 }
0339 
0340 void IncidenceDateTime::enableEndEdit(bool enable)
0341 {
0342     mUi->mEndDateEdit->setEnabled(enable);
0343 
0344     if (mUi->mEndCheck->isChecked() || mUi->mStartCheck->isChecked()) {
0345         mUi->mWholeDayCheck->setEnabled(true);
0346         setTimeZoneLabelEnabled(!mUi->mWholeDayCheck->isChecked());
0347     } else {
0348         mUi->mWholeDayCheck->setEnabled(false);
0349         mUi->mWholeDayCheck->setChecked(false);
0350         setTimeZoneLabelEnabled(false);
0351     }
0352 
0353     if (enable) {
0354         mUi->mEndTimeEdit->setEnabled(!mUi->mWholeDayCheck->isChecked());
0355         mUi->mTimeZoneComboEnd->setEnabled(!mUi->mWholeDayCheck->isChecked());
0356     } else {
0357         mUi->mEndTimeEdit->setEnabled(false);
0358         mUi->mTimeZoneComboEnd->setEnabled(false);
0359     }
0360 
0361     checkDirtyStatus();
0362 }
0363 
0364 bool IncidenceDateTime::timeZonesAreLocal(const QDateTime &start, const QDateTime &end)
0365 {
0366     // Returns false if the incidence start or end timezone is not the local zone.
0367 
0368     if ((start.isValid() && start.timeZone() != QTimeZone::systemTimeZone()) || (end.isValid() && end.timeZone() != QTimeZone::systemTimeZone())) {
0369         return false;
0370     } else {
0371         return true;
0372     }
0373 }
0374 
0375 void IncidenceDateTime::enableTimeEdits()
0376 {
0377     // NOTE: assumes that the initial times are initialized.
0378     const bool wholeDayChecked = mUi->mWholeDayCheck->isChecked();
0379 
0380     setTimeZoneLabelEnabled(!wholeDayChecked);
0381 
0382     if (mUi->mStartCheck->isChecked()) {
0383         mUi->mStartTimeEdit->setEnabled(!wholeDayChecked);
0384         mUi->mTimeZoneComboStart->setEnabled(!wholeDayChecked);
0385         if (wholeDayChecked)
0386             mUi->mTimeZoneComboStart->setFloating(true);
0387         else
0388             mUi->mTimeZoneComboStart->selectTimeZoneFor(mInitialStartDT);
0389     }
0390     if (mUi->mEndCheck->isChecked()) {
0391         mUi->mEndTimeEdit->setEnabled(!wholeDayChecked);
0392         mUi->mTimeZoneComboEnd->setEnabled(!wholeDayChecked);
0393         if (wholeDayChecked)
0394             mUi->mTimeZoneComboEnd->setFloating(true);
0395         else
0396             mUi->mTimeZoneComboEnd->selectTimeZoneFor(mInitialEndDT);
0397     }
0398 
0399     /**
0400        When editing a whole-day event, unchecking mWholeDayCheck shouldn't set both
0401        times to 00:00. DTSTART must always be smaller than DTEND
0402      */
0403     if (sender() == mUi->mWholeDayCheck && !wholeDayChecked // Somebody unchecked it, the incidence will now have time.
0404         && mUi->mStartCheck->isChecked() && mUi->mEndCheck->isChecked() // The incidence has both start and end/due dates
0405         && currentStartDateTime() == currentEndDateTime()) { // DTSTART == DTEND. This is illegal, lets correct it.
0406         // Not sure about the best time here... doesn't really matter, when someone unchecks mWholeDayCheck, she will
0407         // always want to set a time.
0408         mUi->mStartTimeEdit->setTime(QTime(0, 0));
0409         mUi->mEndTimeEdit->setTime(QTime(1, 0));
0410     }
0411 
0412     const bool currentlyVisible = mUi->mTimeZoneLabel->text().contains(QLatin1StringView("&lt;&lt;"));
0413     setTimeZonesVisibility(!wholeDayChecked && mTimezoneCombosWereVisibile);
0414     mTimezoneCombosWereVisibile = currentlyVisible;
0415     if (!wholeDayChecked && !timeZonesAreLocal(currentStartDateTime(), currentEndDateTime())) {
0416         setTimeZonesVisibility(true);
0417         mTimezoneCombosWereVisibile = true;
0418     }
0419 }
0420 
0421 bool IncidenceDateTime::isDirty(const KCalendarCore::Todo::Ptr &todo) const
0422 {
0423     Q_ASSERT(todo);
0424 
0425     const bool hasDateTimes = mUi->mStartCheck->isChecked() || mUi->mEndCheck->isChecked();
0426 
0427     // First check the start time/date of the todo
0428     if (todo->hasStartDate() != mUi->mStartCheck->isChecked()) {
0429         return true;
0430     }
0431 
0432     if ((hasDateTimes && todo->allDay()) != mUi->mWholeDayCheck->isChecked()) {
0433         return true;
0434     }
0435 
0436     if (todo->hasDueDate() != mUi->mEndCheck->isChecked()) {
0437         return true;
0438     }
0439 
0440     if (todo->allDay()) {
0441         if ((mUi->mStartCheck->isChecked() && mUi->mStartDateEdit->date() != mInitialStartDT.date())
0442             || (mUi->mEndCheck->isChecked() && mUi->mEndDateEdit->date() != mInitialEndDT.date())) {
0443             return true;
0444         }
0445     } else {
0446         if ((mUi->mStartCheck->isChecked() && !identical(currentStartDateTime(), mInitialStartDT))
0447             || (mUi->mEndCheck->isChecked() && !identical(currentEndDateTime(), mInitialEndDT))) {
0448             return true;
0449         }
0450     }
0451 
0452     return false;
0453 }
0454 
0455 /// Event specific methods
0456 
0457 bool IncidenceDateTime::isDirty(const KCalendarCore::Event::Ptr &event) const
0458 {
0459     if (event->allDay() != mUi->mWholeDayCheck->isChecked()) {
0460         return true;
0461     }
0462 
0463     if (mUi->mFreeBusyCheck->isChecked() && event->transparency() != KCalendarCore::Event::Opaque) {
0464         return true;
0465     }
0466 
0467     if (!mUi->mFreeBusyCheck->isChecked() && event->transparency() != KCalendarCore::Event::Transparent) {
0468         return true;
0469     }
0470 
0471     if (event->allDay()) {
0472         if (mUi->mStartDateEdit->date() != mInitialStartDT.date() || mUi->mEndDateEdit->date() != mInitialEndDT.date()) {
0473             return true;
0474         }
0475     } else {
0476         if (!identical(currentStartDateTime(), mInitialStartDT) || !identical(currentEndDateTime(), mInitialEndDT)) {
0477             return true;
0478         }
0479     }
0480 
0481     return false;
0482 }
0483 
0484 bool IncidenceDateTime::isDirty(const KCalendarCore::Journal::Ptr &journal) const
0485 {
0486     if (journal->allDay() != mUi->mWholeDayCheck->isChecked()) {
0487         return true;
0488     }
0489 
0490     if (journal->allDay()) {
0491         if (mUi->mStartDateEdit->date() != mInitialStartDT.date()) {
0492             return true;
0493         }
0494     } else {
0495         if (!identical(currentStartDateTime(), mInitialStartDT)) {
0496             return true;
0497         }
0498     }
0499 
0500     return false;
0501 }
0502 
0503 /// Private methods
0504 
0505 QDateTime IncidenceDateTime::currentStartDateTime() const
0506 {
0507     QDateTime dt(mUi->mStartDateEdit->date(), mUi->mStartTimeEdit->time());
0508     mUi->mTimeZoneComboStart->applyTimeZoneTo(dt);
0509     return dt;
0510 }
0511 
0512 QDateTime IncidenceDateTime::currentEndDateTime() const
0513 {
0514     QDateTime dt(mUi->mEndDateEdit->date(), mUi->mEndTimeEdit->time());
0515     mUi->mTimeZoneComboEnd->applyTimeZoneTo(dt);
0516     return dt;
0517 }
0518 
0519 void IncidenceDateTime::load(const KCalendarCore::Event::Ptr &event, bool isTemplate, bool templateOverridesTimes)
0520 {
0521     // First en/disable the necessary ui bits and pieces
0522     mUi->mStartCheck->setVisible(false);
0523     mUi->mStartCheck->setChecked(true); // Set to checked so we can reuse enableTimeEdits.
0524     mUi->mEndCheck->setVisible(false);
0525     mUi->mEndCheck->setChecked(true); // Set to checked so we can reuse enableTimeEdits.
0526 
0527     // Start time
0528     connect(mUi->mStartTimeEdit, &KTimeComboBox::timeChanged, this,
0529             &IncidenceDateTime::updateStartTime); // when editing with mouse, or up/down arrows
0530     connect(mUi->mStartTimeEdit, &KTimeComboBox::timeEdited, this,
0531             &IncidenceDateTime::updateStartTime); // When editing with any key except up/down
0532     connect(mUi->mStartDateEdit, &KDateComboBox::dateChanged, this, &IncidenceDateTime::updateStartDate);
0533     connect(mUi->mTimeZoneComboStart,
0534             static_cast<void (IncidenceEditorNG::KTimeZoneComboBox::*)(int)>(&IncidenceEditorNG::KTimeZoneComboBox::currentIndexChanged),
0535             this,
0536             &IncidenceDateTime::updateStartSpec);
0537 
0538     // End time
0539     connect(mUi->mEndTimeEdit, &KTimeComboBox::timeChanged, this, &IncidenceDateTime::checkDirtyStatus);
0540     connect(mUi->mEndTimeEdit, &KTimeComboBox::timeEdited, this, &IncidenceDateTime::checkDirtyStatus);
0541     connect(mUi->mEndDateEdit, &KDateComboBox::dateChanged, this, &IncidenceDateTime::checkDirtyStatus);
0542     connect(mUi->mEndTimeEdit, &KTimeComboBox::timeChanged, this, &IncidenceDateTime::endTimeChanged);
0543     connect(mUi->mEndTimeEdit, &KTimeComboBox::timeEdited, this, &IncidenceDateTime::endTimeChanged);
0544     connect(mUi->mEndDateEdit, &KDateComboBox::dateChanged, this, &IncidenceDateTime::endDateChanged);
0545     connect(mUi->mTimeZoneComboEnd,
0546             static_cast<void (IncidenceEditorNG::KTimeZoneComboBox::*)(int)>(&IncidenceEditorNG::KTimeZoneComboBox::currentIndexChanged),
0547             this,
0548             &IncidenceDateTime::updateEndSpec);
0549     mUi->mWholeDayCheck->setChecked(event->allDay());
0550     enableTimeEdits();
0551 
0552     if (isTemplate) {
0553         if (templateOverridesTimes) {
0554             // We only use the template times if the user didn't override them.
0555             setTimes(event->dtStart(), event->dtEnd());
0556         }
0557     } else {
0558         QDateTime startDT = event->dtStart();
0559         QDateTime endDT = event->dtEnd();
0560         setDateTimes(startDT, endDT);
0561     }
0562 
0563     switch (event->transparency()) {
0564     case KCalendarCore::Event::Transparent:
0565         mUi->mFreeBusyCheck->setChecked(false);
0566         break;
0567     case KCalendarCore::Event::Opaque:
0568         mUi->mFreeBusyCheck->setChecked(true);
0569         break;
0570     }
0571 }
0572 
0573 void IncidenceDateTime::load(const KCalendarCore::Journal::Ptr &journal, bool isTemplate, bool templateOverridesTimes)
0574 {
0575     // First en/disable the necessary ui bits and pieces
0576     mUi->mStartCheck->setVisible(false);
0577     mUi->mStartCheck->setChecked(true); // Set to checked so we can reuse enableTimeEdits.
0578     mUi->mEndCheck->setVisible(false);
0579     mUi->mEndCheck->setChecked(true); // Set to checked so we can reuse enableTimeEdits.
0580     mUi->mEndDateEdit->setVisible(false);
0581     mUi->mEndTimeEdit->setVisible(false);
0582     mUi->mTimeZoneComboEnd->setVisible(false);
0583     mUi->mEndLabel->setVisible(false);
0584     mUi->mFreeBusyCheck->setVisible(false);
0585 
0586     // Start time
0587     connect(mUi->mStartTimeEdit, &KTimeComboBox::timeChanged, this, &IncidenceDateTime::updateStartTime);
0588     connect(mUi->mStartDateEdit, &KDateComboBox::dateChanged, this, &IncidenceDateTime::updateStartDate);
0589     connect(mUi->mTimeZoneComboStart,
0590             static_cast<void (IncidenceEditorNG::KTimeZoneComboBox::*)(int)>(&IncidenceEditorNG::KTimeZoneComboBox::currentIndexChanged),
0591             this,
0592             &IncidenceDateTime::updateStartSpec);
0593     mUi->mWholeDayCheck->setChecked(journal->allDay());
0594     enableTimeEdits();
0595 
0596     if (isTemplate) {
0597         if (templateOverridesTimes) {
0598             // We only use the template times if the user didn't override them.
0599             setTimes(journal->dtStart(), QDateTime());
0600         }
0601     } else {
0602         QDateTime startDT = journal->dtStart();
0603         // Journals do not have end dates, so pick an arbitrary suitable date.
0604         setDateTimes(startDT, startDT);
0605     }
0606 }
0607 
0608 void IncidenceDateTime::load(const KCalendarCore::Todo::Ptr &todo, bool isTemplate, bool templateOverridesTimes)
0609 {
0610     // First en/disable the necessary ui bits and pieces
0611     mUi->mStartCheck->setVisible(true);
0612     mUi->mStartCheck->setChecked(todo->hasStartDate());
0613     mUi->mStartDateEdit->setEnabled(todo->hasStartDate());
0614     mUi->mStartTimeEdit->setEnabled(todo->hasStartDate());
0615     mUi->mTimeZoneComboStart->setEnabled(todo->hasStartDate());
0616 
0617     mUi->mEndLabel->setText(i18nc("@label The due date/time of a to-do", "Due:"));
0618     mUi->mEndCheck->setVisible(true);
0619     mUi->mEndCheck->setChecked(todo->hasDueDate());
0620     mUi->mEndDateEdit->setEnabled(todo->hasDueDate());
0621     mUi->mEndTimeEdit->setEnabled(todo->hasDueDate());
0622     mUi->mTimeZoneComboEnd->setEnabled(todo->hasDueDate());
0623 
0624     // These fields where not enabled in the old code either:
0625     mUi->mFreeBusyCheck->setVisible(false);
0626 
0627     const bool hasDateTimes = mUi->mEndCheck->isChecked() || mUi->mStartCheck->isChecked();
0628     mUi->mWholeDayCheck->setChecked(hasDateTimes && todo->allDay());
0629     mUi->mWholeDayCheck->setEnabled(hasDateTimes);
0630 
0631     // Connect to the right logic
0632     connect(mUi->mStartCheck, &QCheckBox::toggled, this, &IncidenceDateTime::enableStartEdit);
0633     connect(mUi->mStartCheck, &QCheckBox::toggled, this, &IncidenceDateTime::startDateTimeToggled);
0634     connect(mUi->mStartDateEdit, &KDateComboBox::dateChanged, this, &IncidenceDateTime::updateStartDate);
0635     connect(mUi->mStartTimeEdit, &KTimeComboBox::timeChanged, this, &IncidenceDateTime::updateStartTime);
0636     connect(mUi->mStartTimeEdit, &KTimeComboBox::timeEdited, this, &IncidenceDateTime::updateStartTime);
0637     connect(mUi->mTimeZoneComboStart,
0638             static_cast<void (IncidenceEditorNG::KTimeZoneComboBox::*)(int)>(&IncidenceEditorNG::KTimeZoneComboBox::currentIndexChanged),
0639             this,
0640             &IncidenceDateTime::updateStartSpec);
0641 
0642     connect(mUi->mEndCheck, &QCheckBox::toggled, this, &IncidenceDateTime::enableEndEdit);
0643     connect(mUi->mEndCheck, &QCheckBox::toggled, this, &IncidenceDateTime::endDateTimeToggled);
0644     connect(mUi->mEndDateEdit, &KDateComboBox::dateChanged, this, &IncidenceDateTime::checkDirtyStatus);
0645     connect(mUi->mEndTimeEdit, &KTimeComboBox::timeChanged, this, &IncidenceDateTime::checkDirtyStatus);
0646     connect(mUi->mEndTimeEdit, &KTimeComboBox::timeEdited, this, &IncidenceDateTime::checkDirtyStatus);
0647     connect(mUi->mEndDateEdit, &KDateComboBox::dateChanged, this, &IncidenceDateTime::endDateChanged);
0648     connect(mUi->mEndTimeEdit, &KTimeComboBox::timeChanged, this, &IncidenceDateTime::endTimeChanged);
0649     connect(mUi->mEndTimeEdit, &KTimeComboBox::timeEdited, this, &IncidenceDateTime::endTimeChanged);
0650     connect(mUi->mTimeZoneComboEnd,
0651             static_cast<void (IncidenceEditorNG::KTimeZoneComboBox::*)(int)>(&IncidenceEditorNG::KTimeZoneComboBox::currentIndexChanged),
0652             this,
0653             &IncidenceDateTime::updateEndSpec);
0654     const QDateTime rightNow = QDateTime::currentDateTime();
0655 
0656     if (isTemplate) {
0657         if (templateOverridesTimes) {
0658             // We only use the template times if the user didn't override them.
0659             setTimes(todo->dtStart(), todo->dateTime(KCalendarCore::Incidence::RoleEnd));
0660         }
0661     } else {
0662         const QDateTime endDT = todo->hasDueDate() ? todo->dtDue(true /** first */) : rightNow;
0663         const QDateTime startDT = todo->hasStartDate() ? todo->dtStart(true /** first */) : rightNow;
0664         setDateTimes(startDT, endDT);
0665     }
0666 }
0667 
0668 void IncidenceDateTime::save(const KCalendarCore::Event::Ptr &event)
0669 {
0670     event->setAllDay(mUi->mWholeDayCheck->isChecked());
0671     event->setDtStart(currentStartDateTime());
0672     event->setDtEnd(currentEndDateTime());
0673 
0674     // Free == Event::Transparent
0675     // Busy == Event::Opaque
0676     event->setTransparency(mUi->mFreeBusyCheck->isChecked() ? KCalendarCore::Event::Opaque : KCalendarCore::Event::Transparent);
0677 }
0678 
0679 void IncidenceDateTime::save(const KCalendarCore::Todo::Ptr &todo)
0680 {
0681     if (mUi->mStartCheck->isChecked()) {
0682         todo->setDtStart(currentStartDateTime());
0683         todo->setAllDay(mUi->mWholeDayCheck->isChecked());
0684         if (currentStartDateTime() != mInitialStartDT) {
0685             // We don't offer any way to edit the current completed occurrence.
0686             // So, if the start date changes, reset the dtRecurrence
0687             todo->setDtRecurrence(currentStartDateTime());
0688         }
0689     } else {
0690         todo->setDtStart(QDateTime());
0691     }
0692 
0693     if (mUi->mEndCheck->isChecked()) {
0694         todo->setDtDue(currentEndDateTime(), true /* first */);
0695         todo->setAllDay(mUi->mWholeDayCheck->isChecked());
0696     } else {
0697         todo->setDtDue(QDateTime(), true /* first */);
0698     }
0699 }
0700 
0701 void IncidenceDateTime::save(const KCalendarCore::Journal::Ptr &journal)
0702 {
0703     journal->setAllDay(mUi->mWholeDayCheck->isChecked());
0704     journal->setDtStart(currentStartDateTime());
0705 }
0706 
0707 void IncidenceDateTime::setDateTimes(const QDateTime &start, const QDateTime &end)
0708 {
0709     if (start.isValid()) {
0710         mUi->mStartDateEdit->setDate(start.date());
0711         mUi->mStartTimeEdit->setTime(start.time());
0712         mUi->mTimeZoneComboStart->selectTimeZoneFor(start);
0713     } else {
0714         QDateTime dt = QDateTime::currentDateTime();
0715         mUi->mStartDateEdit->setDate(dt.date());
0716         mUi->mStartTimeEdit->setTime(dt.time());
0717         mUi->mTimeZoneComboStart->selectTimeZoneFor(dt);
0718     }
0719 
0720     if (end.isValid()) {
0721         mUi->mEndDateEdit->setDate(end.date());
0722         mUi->mEndTimeEdit->setTime(end.time());
0723         mUi->mTimeZoneComboEnd->selectTimeZoneFor(end);
0724     } else {
0725         QDateTime dt(QDate::currentDate(), QTime::currentTime().addSecs(60 * 60));
0726         mUi->mEndDateEdit->setDate(dt.date());
0727         mUi->mEndTimeEdit->setTime(dt.time());
0728         mUi->mTimeZoneComboEnd->selectTimeZoneFor(dt);
0729     }
0730 
0731     mCurrentStartDateTime = currentStartDateTime();
0732     Q_EMIT startDateChanged(start.date());
0733     Q_EMIT startTimeChanged(start.time());
0734     Q_EMIT endDateChanged(end.date());
0735     Q_EMIT endTimeChanged(end.time());
0736 
0737     updateStartToolTips();
0738     updateEndToolTips();
0739 }
0740 
0741 void IncidenceDateTime::updateStartToolTips()
0742 {
0743     if (mUi->mStartCheck->isChecked()) {
0744         QString datetimeStr = KCalUtils::IncidenceFormatter::dateTimeToString(currentStartDateTime(), mUi->mWholeDayCheck->isChecked(), false);
0745         mUi->mStartDateEdit->setToolTip(i18n("Starts: %1", datetimeStr));
0746         mUi->mStartTimeEdit->setToolTip(i18n("Starts: %1", datetimeStr));
0747     } else {
0748         mUi->mStartDateEdit->setToolTip(i18n("Starting Date"));
0749         mUi->mStartTimeEdit->setToolTip(i18n("Starting Time"));
0750     }
0751 }
0752 
0753 void IncidenceDateTime::updateEndToolTips()
0754 {
0755     if (mUi->mStartCheck->isChecked()) {
0756         QString datetimeStr = KCalUtils::IncidenceFormatter::dateTimeToString(currentEndDateTime(), mUi->mWholeDayCheck->isChecked(), false);
0757         if (mLoadedIncidence->type() == KCalendarCore::Incidence::TypeTodo) {
0758             mUi->mEndDateEdit->setToolTip(i18n("Due on: %1", datetimeStr));
0759             mUi->mEndTimeEdit->setToolTip(i18n("Due on: %1", datetimeStr));
0760         } else {
0761             mUi->mEndDateEdit->setToolTip(i18n("Ends: %1", datetimeStr));
0762             mUi->mEndTimeEdit->setToolTip(i18n("Ends: %1", datetimeStr));
0763         }
0764     } else {
0765         if (mLoadedIncidence->type() == KCalendarCore::Incidence::TypeTodo) {
0766             mUi->mEndDateEdit->setToolTip(i18n("Due Date"));
0767             mUi->mEndTimeEdit->setToolTip(i18n("Due Time"));
0768         } else {
0769             mUi->mEndDateEdit->setToolTip(i18n("Ending Date"));
0770             mUi->mEndTimeEdit->setToolTip(i18n("Ending Time"));
0771         }
0772     }
0773 }
0774 
0775 void IncidenceDateTime::setTimes(const QDateTime &start, const QDateTime &end)
0776 {
0777     // like setDateTimes(), but it set only the start/end time, not the date
0778     // it is used while applying a template to an event.
0779     mUi->mStartTimeEdit->blockSignals(true);
0780     mUi->mStartTimeEdit->setTime(start.time());
0781     mUi->mStartTimeEdit->blockSignals(false);
0782 
0783     mUi->mEndTimeEdit->setTime(end.time());
0784 
0785     mUi->mTimeZoneComboStart->selectTimeZoneFor(start);
0786     mUi->mTimeZoneComboEnd->selectTimeZoneFor(end);
0787 
0788     //   emitDateTimeStr();
0789 }
0790 
0791 void IncidenceDateTime::setStartDate(const QDate &newDate)
0792 {
0793     mUi->mStartDateEdit->setDate(newDate);
0794     updateStartDate(newDate);
0795 }
0796 
0797 void IncidenceDateTime::setStartTime(const QTime &newTime)
0798 {
0799     mUi->mStartTimeEdit->setTime(newTime);
0800     updateStartTime(newTime);
0801 }
0802 
0803 bool IncidenceDateTime::startDateTimeEnabled() const
0804 {
0805     return mUi->mStartCheck->isChecked();
0806 }
0807 
0808 bool IncidenceDateTime::endDateTimeEnabled() const
0809 {
0810     return mUi->mEndCheck->isChecked();
0811 }
0812 
0813 void IncidenceDateTime::focusInvalidField()
0814 {
0815     if (startDateTimeEnabled()) {
0816         if (!mUi->mStartDateEdit->isValid()) {
0817             mUi->mStartDateEdit->setFocus();
0818             return;
0819         }
0820         if (!mUi->mWholeDayCheck->isChecked() && !mUi->mStartTimeEdit->isValid()) {
0821             mUi->mStartTimeEdit->setFocus();
0822             return;
0823         }
0824     }
0825     if (endDateTimeEnabled()) {
0826         if (!mUi->mEndDateEdit->isValid()) {
0827             mUi->mEndDateEdit->setFocus();
0828             return;
0829         }
0830         if (!mUi->mWholeDayCheck->isChecked() && !mUi->mEndTimeEdit->isValid()) {
0831             mUi->mEndTimeEdit->setFocus();
0832             return;
0833         }
0834     }
0835     if (startDateTimeEnabled() && endDateTimeEnabled() && currentStartDateTime() > currentEndDateTime()) {
0836         if (mUi->mEndDateEdit->date() < mUi->mStartDateEdit->date()) {
0837             mUi->mEndDateEdit->setFocus();
0838         } else {
0839             mUi->mEndTimeEdit->setFocus();
0840         }
0841     }
0842 }
0843 
0844 bool IncidenceDateTime::isValid() const
0845 {
0846     if (startDateTimeEnabled()) {
0847         if (!mUi->mStartDateEdit->isValid()) {
0848             mLastErrorString = i18nc("@info", "Invalid start date.");
0849             qCDebug(INCIDENCEEDITOR_LOG) << mLastErrorString;
0850             return false;
0851         }
0852         if (!mUi->mWholeDayCheck->isChecked() && !mUi->mStartTimeEdit->isValid()) {
0853             mLastErrorString = i18nc("@info", "Invalid start time.");
0854             qCDebug(INCIDENCEEDITOR_LOG) << mLastErrorString;
0855             return false;
0856         }
0857     }
0858 
0859     if (endDateTimeEnabled()) {
0860         if (!mUi->mEndDateEdit->isValid()) {
0861             mLastErrorString = i18nc("@info", "Invalid end date.");
0862             qCDebug(INCIDENCEEDITOR_LOG) << mLastErrorString;
0863             return false;
0864         }
0865         if (!mUi->mWholeDayCheck->isChecked() && !mUi->mEndTimeEdit->isValid()) {
0866             mLastErrorString = i18nc("@info", "Invalid end time.");
0867             qCDebug(INCIDENCEEDITOR_LOG) << mLastErrorString;
0868             return false;
0869         }
0870     }
0871 
0872     if (startDateTimeEnabled() && endDateTimeEnabled() && currentStartDateTime() > currentEndDateTime()) {
0873         if (mLoadedIncidence->type() == KCalendarCore::Incidence::TypeEvent) {
0874             mLastErrorString = i18nc("@info",
0875                                      "The event ends before it starts.\n"
0876                                      "Please correct dates and times.");
0877         } else if (mLoadedIncidence->type() == KCalendarCore::Incidence::TypeTodo) {
0878             mLastErrorString = i18nc("@info",
0879                                      "The to-do is due before it starts.\n"
0880                                      "Please correct dates and times.");
0881         } else if (mLoadedIncidence->type() == KCalendarCore::Incidence::TypeJournal) {
0882             return true;
0883         }
0884 
0885         qCDebug(INCIDENCEEDITOR_LOG) << mLastErrorString;
0886         return false;
0887     } else {
0888         mLastErrorString.clear();
0889         return true;
0890     }
0891 }
0892 
0893 void IncidenceDateTime::printDebugInfo() const
0894 {
0895     qCDebug(INCIDENCEEDITOR_LOG) << "startDateTimeEnabled()          : " << startDateTimeEnabled();
0896     qCDebug(INCIDENCEEDITOR_LOG) << "endDateTimeEnabled()            : " << endDateTimeEnabled();
0897     qCDebug(INCIDENCEEDITOR_LOG) << "currentStartDateTime().isValid(): " << currentStartDateTime().isValid();
0898     qCDebug(INCIDENCEEDITOR_LOG) << "currentEndDateTime().isValid()  : " << currentEndDateTime().isValid();
0899     qCDebug(INCIDENCEEDITOR_LOG) << "currentStartDateTime()          : " << currentStartDateTime().toString();
0900     qCDebug(INCIDENCEEDITOR_LOG) << "currentEndDateTime()            : " << currentEndDateTime().toString();
0901     qCDebug(INCIDENCEEDITOR_LOG) << "Incidence type                  : " << mLoadedIncidence->type();
0902     qCDebug(INCIDENCEEDITOR_LOG) << "allday                          : " << mLoadedIncidence->allDay();
0903     qCDebug(INCIDENCEEDITOR_LOG) << "mInitialStartDT                 : " << mInitialStartDT.toString();
0904     qCDebug(INCIDENCEEDITOR_LOG) << "mInitialEndDT                   : " << mInitialEndDT.toString();
0905 
0906     qCDebug(INCIDENCEEDITOR_LOG) << "currentStartDateTime().timeZone(): " << currentStartDateTime().timeZone().id();
0907     qCDebug(INCIDENCEEDITOR_LOG) << "currentEndDateTime().timeZone()  : " << currentEndDateTime().timeZone().id();
0908     qCDebug(INCIDENCEEDITOR_LOG) << "mInitialStartDT.timeZone()       : " << mInitialStartDT.timeZone().id();
0909     qCDebug(INCIDENCEEDITOR_LOG) << "mInitialEndDT.timeZone()         : " << mInitialEndDT.timeZone().id();
0910 
0911     qCDebug(INCIDENCEEDITOR_LOG) << "dirty test1: " << (mLoadedIncidence->allDay() != mUi->mWholeDayCheck->isChecked());
0912     if (mLoadedIncidence->type() == KCalendarCore::Incidence::TypeEvent) {
0913         KCalendarCore::Event::Ptr event = mLoadedIncidence.staticCast<KCalendarCore::Event>();
0914         qCDebug(INCIDENCEEDITOR_LOG) << "dirty test2: " << (mUi->mFreeBusyCheck->isChecked() && event->transparency() != KCalendarCore::Event::Opaque);
0915         qCDebug(INCIDENCEEDITOR_LOG) << "dirty test3: " << (!mUi->mFreeBusyCheck->isChecked() && event->transparency() != KCalendarCore::Event::Transparent);
0916     }
0917 
0918     if (mLoadedIncidence->allDay()) {
0919         qCDebug(INCIDENCEEDITOR_LOG) << "dirty test4: "
0920                                      << (mUi->mStartDateEdit->date() != mInitialStartDT.date() || mUi->mEndDateEdit->date() != mInitialEndDT.date());
0921     } else {
0922         qCDebug(INCIDENCEEDITOR_LOG) << "dirty test4.1: " << (currentStartDateTime() != mInitialStartDT);
0923         qCDebug(INCIDENCEEDITOR_LOG) << "dirty test4.2: " << (currentEndDateTime() != mInitialEndDT);
0924         qCDebug(INCIDENCEEDITOR_LOG) << "dirty test4.3: " << (currentStartDateTime().timeZone() != mInitialStartDT.timeZone());
0925         qCDebug(INCIDENCEEDITOR_LOG) << "dirty test4.4: " << (currentEndDateTime().timeZone() != mInitialEndDT.timeZone());
0926     }
0927 }
0928 
0929 void IncidenceDateTime::setTimeZoneLabelEnabled(bool enable)
0930 {
0931     mUi->mTimeZoneLabel->setVisible(enable);
0932 }
0933 
0934 #include "moc_incidencedatetime.cpp"