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\"><< %1</a>")); 0217 if (visible) { 0218 placeholder = placeholder.arg(tz); 0219 } else { 0220 placeholder = QStringLiteral("<a href=\"show\">%1 >></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("<<")); 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"