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"