File indexing completed on 2024-04-28 05:11:32
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 "incidencealarm.h" 0009 #include "alarmdialog.h" 0010 #include "alarmpresets.h" 0011 #include "incidencedatetime.h" 0012 #include "ui_dialogdesktop.h" 0013 0014 #include <CalendarSupport/KCalPrefs> 0015 0016 using namespace IncidenceEditorNG; 0017 using namespace CalendarSupport; 0018 0019 IncidenceAlarm::IncidenceAlarm(IncidenceDateTime *dateTime, Ui::EventOrTodoDesktop *ui) 0020 : mUi(ui) 0021 , mDateTime(dateTime) 0022 { 0023 setObjectName(QLatin1StringView("IncidenceAlarm")); 0024 0025 mUi->mAlarmPresetCombo->insertItems(0, AlarmPresets::availablePresets()); 0026 mUi->mAlarmPresetCombo->setCurrentIndex(AlarmPresets::defaultPresetIndex()); 0027 updateButtons(); 0028 0029 connect(mDateTime, &IncidenceDateTime::startDateTimeToggled, this, &IncidenceAlarm::handleDateTimeToggle); 0030 connect(mDateTime, &IncidenceDateTime::endDateTimeToggled, this, &IncidenceAlarm::handleDateTimeToggle); 0031 connect(mUi->mAlarmAddPresetButton, &QPushButton::clicked, this, &IncidenceAlarm::newAlarmFromPreset); 0032 connect(mUi->mAlarmList, &QListWidget::itemSelectionChanged, this, &IncidenceAlarm::updateButtons); 0033 connect(mUi->mAlarmList, &QListWidget::itemDoubleClicked, this, &IncidenceAlarm::editCurrentAlarm); 0034 connect(mUi->mAlarmNewButton, &QPushButton::clicked, this, &IncidenceAlarm::newAlarm); 0035 connect(mUi->mAlarmConfigureButton, &QPushButton::clicked, this, &IncidenceAlarm::editCurrentAlarm); 0036 connect(mUi->mAlarmToggleButton, &QPushButton::clicked, this, &IncidenceAlarm::toggleCurrentAlarm); 0037 connect(mUi->mAlarmRemoveButton, &QPushButton::clicked, this, &IncidenceAlarm::removeCurrentAlarm); 0038 } 0039 0040 void IncidenceAlarm::load(const KCalendarCore::Incidence::Ptr &incidence) 0041 { 0042 mLoadedIncidence = incidence; 0043 // We must be sure that the date/time in mDateTime is the correct date time. 0044 // So don't depend on CombinedIncidenceEditor or whatever external factor to 0045 // load the date/time before loading the recurrence 0046 mDateTime->load(incidence); 0047 0048 mAlarms.clear(); 0049 const auto lstAlarms = incidence->alarms(); 0050 for (const KCalendarCore::Alarm::Ptr &alarm : lstAlarms) { 0051 mAlarms.append(KCalendarCore::Alarm::Ptr(new KCalendarCore::Alarm(*alarm.data()))); 0052 } 0053 0054 mIsTodo = incidence->type() == KCalendarCore::Incidence::TypeTodo; 0055 if (mIsTodo) { 0056 mUi->mAlarmPresetCombo->clear(); 0057 mUi->mAlarmPresetCombo->addItems(AlarmPresets::availablePresets(AlarmPresets::BeforeEnd)); 0058 } else { 0059 mUi->mAlarmPresetCombo->clear(); 0060 mUi->mAlarmPresetCombo->addItems(AlarmPresets::availablePresets(AlarmPresets::BeforeStart)); 0061 } 0062 mUi->mAlarmPresetCombo->setCurrentIndex(AlarmPresets::defaultPresetIndex()); 0063 0064 handleDateTimeToggle(); 0065 mWasDirty = false; 0066 0067 updateAlarmList(); 0068 } 0069 0070 void IncidenceAlarm::save(const KCalendarCore::Incidence::Ptr &incidence) 0071 { 0072 incidence->clearAlarms(); 0073 const KCalendarCore::Alarm::List::ConstIterator end(mAlarms.constEnd()); 0074 for (KCalendarCore::Alarm::List::ConstIterator it = mAlarms.constBegin(); it != end; ++it) { 0075 KCalendarCore::Alarm::Ptr al(new KCalendarCore::Alarm(*(*it))); 0076 al->setParent(incidence.data()); 0077 // We need to make sure that both lists are the same in the end for isDirty. 0078 Q_ASSERT(*al == *(*it)); 0079 incidence->addAlarm(al); 0080 } 0081 } 0082 0083 bool IncidenceAlarm::isDirty() const 0084 { 0085 if (mLoadedIncidence->alarms().count() != mAlarms.count()) { 0086 return true; 0087 } 0088 0089 if (!mLoadedIncidence->alarms().isEmpty()) { 0090 const KCalendarCore::Alarm::List initialAlarms = mLoadedIncidence->alarms(); 0091 0092 if (initialAlarms.count() != mAlarms.count()) { 0093 return true; // The number of alarms has changed 0094 } 0095 0096 // Note: Not the most efficient algorithm but I'm assuming that we're only 0097 // dealing with a couple, at most tens of alarms. The idea is we check 0098 // if all currently enabled alarms are also in the incidence. The 0099 // disabled alarms are not changed by our code at all, so we assume that 0100 // they're still there. 0101 for (const KCalendarCore::Alarm::Ptr &alarm : std::as_const(mAlarms)) { 0102 bool found = false; 0103 for (const KCalendarCore::Alarm::Ptr &initialAlarm : std::as_const(initialAlarms)) { 0104 if (*alarm == *initialAlarm) { 0105 found = true; 0106 break; 0107 } 0108 } 0109 0110 if (!found) { 0111 // There was an alarm in the mLoadedIncidence->alarms() that wasn't found 0112 // in mLastAlarms. This means that one of the alarms was modified. 0113 return true; 0114 } 0115 } 0116 } 0117 0118 return false; 0119 } 0120 0121 void IncidenceAlarm::editCurrentAlarm() 0122 { 0123 KCalendarCore::Alarm::Ptr currentAlarm = mAlarms.at(mUi->mAlarmList->currentRow()); 0124 0125 QPointer<AlarmDialog> dialog(new AlarmDialog(mLoadedIncidence->type(), mUi->mTabWidget)); 0126 dialog->load(currentAlarm); 0127 0128 dialog->setAllowBeginReminders(mDateTime->startDateTimeEnabled()); 0129 dialog->setAllowEndReminders(mDateTime->endDateTimeEnabled()); 0130 0131 if (dialog->exec() == QDialog::Accepted) { 0132 dialog->save(currentAlarm); 0133 updateAlarmList(); 0134 checkDirtyStatus(); 0135 } 0136 delete dialog; 0137 } 0138 0139 void IncidenceAlarm::handleDateTimeToggle() 0140 { 0141 QWidget *parent = mUi->mAlarmPresetCombo->parentWidget(); // the parent of a toplevel widget 0142 if (parent) { 0143 parent->setEnabled(mDateTime->startDateTimeEnabled() || mDateTime->endDateTimeEnabled()); 0144 } 0145 0146 mUi->mAlarmPresetCombo->setEnabled(mDateTime->endDateTimeEnabled()); 0147 mUi->mAlarmAddPresetButton->setEnabled(mDateTime->endDateTimeEnabled()); 0148 0149 mUi->mQuickAddReminderLabel->setEnabled(mDateTime->endDateTimeEnabled()); 0150 } 0151 0152 void IncidenceAlarm::newAlarm() 0153 { 0154 QPointer<AlarmDialog> dialog(new AlarmDialog(mLoadedIncidence->type(), mUi->mTabWidget)); 0155 const int reminderOffset = KCalPrefs::instance()->reminderTime(); 0156 0157 if (reminderOffset >= 0) { 0158 dialog->setOffset(reminderOffset); 0159 } else { 0160 dialog->setOffset(DEFAULT_REMINDER_OFFSET); 0161 } 0162 dialog->setUnit(AlarmDialog::Minutes); 0163 if (mIsTodo && mDateTime->endDateTimeEnabled()) { 0164 dialog->setWhen(AlarmDialog::BeforeEnd); 0165 } else { 0166 dialog->setWhen(AlarmDialog::BeforeStart); 0167 } 0168 0169 dialog->setAllowBeginReminders(mDateTime->startDateTimeEnabled()); 0170 dialog->setAllowEndReminders(mDateTime->endDateTimeEnabled()); 0171 0172 if (dialog->exec() == QDialog::Accepted) { 0173 KCalendarCore::Alarm::Ptr newAlarm(new KCalendarCore::Alarm(nullptr)); 0174 dialog->save(newAlarm); 0175 newAlarm->setEnabled(true); 0176 mAlarms.append(newAlarm); 0177 updateAlarmList(); 0178 checkDirtyStatus(); 0179 } 0180 delete dialog; 0181 } 0182 0183 void IncidenceAlarm::newAlarmFromPreset() 0184 { 0185 if (mIsTodo) { 0186 mAlarms.append(AlarmPresets::preset(AlarmPresets::BeforeEnd, mUi->mAlarmPresetCombo->currentText())); 0187 } else { 0188 mAlarms.append(AlarmPresets::preset(AlarmPresets::BeforeStart, mUi->mAlarmPresetCombo->currentText())); 0189 } 0190 0191 updateAlarmList(); 0192 checkDirtyStatus(); 0193 } 0194 0195 void IncidenceAlarm::removeCurrentAlarm() 0196 { 0197 Q_ASSERT(mUi->mAlarmList->selectedItems().size() == 1); 0198 const int curAlarmIndex = mUi->mAlarmList->currentRow(); 0199 delete mUi->mAlarmList->takeItem(curAlarmIndex); 0200 mAlarms.remove(curAlarmIndex); 0201 0202 updateAlarmList(); 0203 updateButtons(); 0204 checkDirtyStatus(); 0205 } 0206 0207 void IncidenceAlarm::toggleCurrentAlarm() 0208 { 0209 Q_ASSERT(mUi->mAlarmList->selectedItems().size() == 1); 0210 const int curAlarmIndex = mUi->mAlarmList->currentRow(); 0211 KCalendarCore::Alarm::Ptr alarm = mAlarms.at(curAlarmIndex); 0212 alarm->setEnabled(!alarm->enabled()); 0213 0214 updateButtons(); 0215 updateAlarmList(); 0216 checkDirtyStatus(); 0217 } 0218 0219 void IncidenceAlarm::updateAlarmList() 0220 { 0221 const int prevEnabledAlarmCount = mEnabledAlarmCount; 0222 mEnabledAlarmCount = 0; 0223 0224 const QModelIndex currentIndex = mUi->mAlarmList->currentIndex(); 0225 mUi->mAlarmList->clear(); 0226 for (const KCalendarCore::Alarm::Ptr &alarm : std::as_const(mAlarms)) { 0227 mUi->mAlarmList->addItem(stringForAlarm(alarm)); 0228 if (alarm->enabled()) { 0229 ++mEnabledAlarmCount; 0230 } 0231 } 0232 0233 mUi->mAlarmList->setCurrentIndex(currentIndex); 0234 if (prevEnabledAlarmCount != mEnabledAlarmCount) { 0235 Q_EMIT alarmCountChanged(mEnabledAlarmCount); 0236 } 0237 } 0238 0239 void IncidenceAlarm::updateButtons() 0240 { 0241 if (mUi->mAlarmList->count() > 0 && !mUi->mAlarmList->selectedItems().isEmpty()) { 0242 mUi->mAlarmConfigureButton->setEnabled(true); 0243 mUi->mAlarmRemoveButton->setEnabled(true); 0244 mUi->mAlarmToggleButton->setEnabled(true); 0245 KCalendarCore::Alarm::Ptr selAlarm; 0246 if (mUi->mAlarmList->currentIndex().isValid()) { 0247 selAlarm = mAlarms.at(mUi->mAlarmList->currentIndex().row()); 0248 } 0249 if (selAlarm && selAlarm->enabled()) { 0250 mUi->mAlarmToggleButton->setText(i18nc("Disable currently selected reminder", "Disable")); 0251 } else { 0252 mUi->mAlarmToggleButton->setText(i18nc("Enable currently selected reminder", "Enable")); 0253 } 0254 } else { 0255 mUi->mAlarmConfigureButton->setEnabled(false); 0256 mUi->mAlarmRemoveButton->setEnabled(false); 0257 mUi->mAlarmToggleButton->setEnabled(false); 0258 } 0259 } 0260 0261 QString IncidenceAlarm::stringForAlarm(const KCalendarCore::Alarm::Ptr &alarm) 0262 { 0263 Q_ASSERT(alarm); 0264 0265 QString action; 0266 switch (alarm->type()) { 0267 case KCalendarCore::Alarm::Procedure: 0268 case KCalendarCore::Alarm::Display: 0269 case KCalendarCore::Alarm::Email: 0270 action = i18nc("Alarm action", "Display a dialog"); 0271 break; 0272 case KCalendarCore::Alarm::Audio: 0273 action = i18nc("Alarm action", "Play an audio file"); 0274 break; 0275 default: 0276 action = i18nc("Alarm action", "Invalid Reminder."); 0277 return action; 0278 } 0279 0280 const int offset = alarm->hasStartOffset() ? alarm->startOffset().asSeconds() / 60 : alarm->endOffset().asSeconds() / 60; // make minutes 0281 0282 QString offsetUnitTranslated = i18ncp("The reminder is set to X minutes before/after the event", "1 minute", "%1 minutes", qAbs(offset)); 0283 0284 int useoffset = offset; 0285 if (offset % (24 * 60) == 0 && offset != 0) { // divides evenly into days? 0286 useoffset = offset / 60 / 24; 0287 offsetUnitTranslated = i18ncp("The reminder is set to X days before/after the event", "1 day", "%1 days", qAbs(useoffset)); 0288 } else if (offset % 60 == 0 && offset != 0) { // divides evenly into hours? 0289 useoffset = offset / 60; 0290 offsetUnitTranslated = i18ncp("The reminder is set to X hours before/after the event", "1 hour", "%1 hours", qAbs(useoffset)); 0291 } 0292 0293 QString repeatStr; 0294 if (alarm->repeatCount() > 0) { 0295 repeatStr = i18nc("The reminder is configured to repeat after snooze", "(Repeats)"); 0296 } 0297 0298 if (alarm->enabled()) { 0299 if (useoffset > 0 && alarm->hasStartOffset()) { 0300 if (mIsTodo) { 0301 // i18n: These series of strings are used to show the user a description of 0302 // the alarm. %1 is replaced by one of the actions above, %2 is replaced by 0303 // one of the time units above, %3 is the (Repeats) part that will be used 0304 // in case of repetition of the alarm. 0305 return i18n("%1 %2 after the to-do started %3", action, offsetUnitTranslated, repeatStr); 0306 } else { 0307 return i18n("%1 %2 after the event started %3", action, offsetUnitTranslated, repeatStr); 0308 } 0309 } else if (useoffset < 0 && alarm->hasStartOffset()) { 0310 if (mIsTodo) { 0311 return i18n("%1 %2 before the to-do starts %3", action, offsetUnitTranslated, repeatStr); 0312 } else { 0313 return i18n("%1 %2 before the event starts %3", action, offsetUnitTranslated, repeatStr); 0314 } 0315 } else if (useoffset > 0 && alarm->hasEndOffset()) { 0316 if (mIsTodo) { 0317 return i18n("%1 %2 after the to-do is due %3", action, offsetUnitTranslated, repeatStr); 0318 } else { 0319 return i18n("%1 %2 after the event ends %3", action, offsetUnitTranslated, repeatStr); 0320 } 0321 } else if (useoffset < 0 && alarm->hasEndOffset()) { 0322 if (mIsTodo) { 0323 return i18n("%1 %2 before the to-do is due %3", action, offsetUnitTranslated, repeatStr); 0324 } else { 0325 return i18n("%1 %2 before the event ends %3", action, offsetUnitTranslated, repeatStr); 0326 } 0327 } 0328 } else { 0329 if (useoffset > 0 && alarm->hasStartOffset()) { 0330 if (mIsTodo) { 0331 return i18n("%1 %2 after the to-do started %3 (Disabled)", action, offsetUnitTranslated, repeatStr); 0332 } else { 0333 return i18n("%1 %2 after the event started %3 (Disabled)", action, offsetUnitTranslated, repeatStr); 0334 } 0335 } else if (useoffset < 0 && alarm->hasStartOffset()) { 0336 if (mIsTodo) { 0337 return i18n("%1 %2 before the to-do starts %3 (Disabled)", action, offsetUnitTranslated, repeatStr); 0338 } else { 0339 return i18n("%1 %2 before the event starts %3 (Disabled)", action, offsetUnitTranslated, repeatStr); 0340 } 0341 } else if (useoffset > 0 && alarm->hasEndOffset()) { 0342 if (mIsTodo) { 0343 return i18n("%1 %2 after the to-do is due %3 (Disabled)", action, offsetUnitTranslated, repeatStr); 0344 } else { 0345 return i18n("%1 %2 after the event ends %3 (Disabled)", action, offsetUnitTranslated, repeatStr); 0346 } 0347 } else if (useoffset < 0 && alarm->hasEndOffset()) { 0348 if (mIsTodo) { 0349 return i18n("%1 %2 before the to-do is due %3 (Disabled)", action, offsetUnitTranslated, repeatStr); 0350 } else { 0351 return i18n("%1 %2 before the event ends %3 (Disabled)", action, offsetUnitTranslated, repeatStr); 0352 } 0353 } 0354 } 0355 0356 // useoffset == 0 0357 if (alarm->enabled()) { 0358 if (mIsTodo && alarm->hasStartOffset()) { 0359 return i18n("%1 when the to-do starts", action); 0360 } else if (alarm->hasStartOffset()) { 0361 return i18n("%1 when the event starts", action); 0362 } else if (mIsTodo && alarm->hasEndOffset()) { 0363 return i18n("%1 when the to-do is due", action); 0364 } else { 0365 return i18n("%1 when the event ends", action); 0366 } 0367 } else { 0368 if (mIsTodo && alarm->hasStartOffset()) { 0369 return i18n("%1 when the to-do starts (Disabled)", action); 0370 } else if (alarm->hasStartOffset()) { 0371 return i18n("%1 when the event starts (Disabled)", action); 0372 } else if (mIsTodo && alarm->hasEndOffset()) { 0373 return i18n("%1 when the to-do is due (Disabled)", action); 0374 } else { 0375 return i18n("%1 when the event ends (Disabled)", action); 0376 } 0377 } 0378 } 0379 0380 #include "moc_incidencealarm.cpp"