Warning, file /pim/kalarm/src/birthdaydlg.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  birthdaydlg.cpp  -  dialog to pick birthdays from address book
0003  *  Program:  kalarm
0004  *  SPDX-FileCopyrightText: 2002-2023 David Jarvie <djarvie@kde.org>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "birthdaydlg.h"
0010 
0011 #include "editdlgtypes.h"
0012 #include "fontcolourbutton.h"
0013 #include "latecancel.h"
0014 #include "preferences.h"
0015 #include "reminder.h"
0016 #include "repetitionbutton.h"
0017 #include "resourcescalendar.h"
0018 #include "soundpicker.h"
0019 #include "specialactions.h"
0020 #include "lib/checkbox.h"
0021 #include "lib/shellprocess.h"
0022 #include "akonadiplugin/akonadiplugin.h"
0023 
0024 #include <KLocalizedString>
0025 #include <KConfigGroup>
0026 #include <KStandardAction>
0027 #include <KActionCollection>
0028 #include <KSharedConfig>
0029 
0030 #include <QAction>
0031 #include <QGroupBox>
0032 #include <QLabel>
0033 #include <QTreeView>
0034 #include <QHeaderView>
0035 #include <QHBoxLayout>
0036 #include <QVBoxLayout>
0037 #include <QDialogButtonBox>
0038 #include <QSortFilterProxyModel>
0039 
0040 namespace
0041 {
0042 const QLatin1String GENERAL_GROUP("General");
0043 }
0044 
0045 BirthdayDlg::BirthdayDlg(QWidget* parent)
0046     : QDialog(parent)
0047 {
0048     setObjectName(QLatin1StringView("BirthdayDlg"));    // used by LikeBack
0049     setWindowTitle(i18nc("@title:window", "Import Birthdays From KAddressBook"));
0050 
0051     auto mainLayout = new QVBoxLayout(this);
0052     QWidget* mainWidget = new QWidget;
0053     mainLayout->addWidget(mainWidget);
0054 
0055     auto topLayout = new QVBoxLayout(mainWidget);
0056     topLayout->setContentsMargins(0, 0, 0, 0);
0057 
0058     if (Preferences::useAlarmName())
0059     {
0060         auto hlayout = new QHBoxLayout();
0061         hlayout->setContentsMargins(0, 0, 0, 0);
0062         topLayout->addLayout(hlayout);
0063         QLabel* label = new QLabel(i18nc("@label:textbox", "Alarm name:"), mainWidget);
0064         hlayout->addWidget(label);
0065         mName = new KLineEdit(mainWidget);
0066         mName->setMinimumSize(mName->sizeHint());
0067         label->setBuddy(mName);
0068         mName->setWhatsThis(i18nc("@info:whatsthis", "Enter a name to help you identify this alarm. This is optional and need not be unique."));
0069         hlayout->addWidget(mName);
0070     }
0071 
0072     // Prefix and suffix to the name in the alarm text
0073     // Get default prefix and suffix texts from config file
0074     KConfigGroup config(KSharedConfig::openConfig(), GENERAL_GROUP);
0075     mPrefixText = config.readEntry("BirthdayPrefix", i18nc("@info", "Birthday: "));
0076     mSuffixText = config.readEntry("BirthdaySuffix");
0077 
0078     QGroupBox* textGroup = new QGroupBox(i18nc("@title:group", "Alarm Text"), mainWidget);
0079     topLayout->addWidget(textGroup);
0080     auto grid = new QGridLayout(textGroup);
0081     QLabel* label = new QLabel(i18nc("@label:textbox", "Prefix:"), textGroup);
0082     grid->addWidget(label, 0, 0);
0083     mPrefix = new BLineEdit(mPrefixText, textGroup);
0084     mPrefix->setMinimumSize(mPrefix->sizeHint());
0085     label->setBuddy(mPrefix);
0086     connect(mPrefix, &BLineEdit::focusLost, this, &BirthdayDlg::slotTextLostFocus);
0087     mPrefix->setWhatsThis(i18nc("@info:whatsthis",
0088           "Enter text to appear before the person's name in the alarm message, "
0089           "including any necessary trailing spaces."));
0090     grid->addWidget(mPrefix, 0, 1);
0091 
0092     label = new QLabel(i18nc("@label:textbox", "Suffix:"), textGroup);
0093     grid->addWidget(label, 1, 0);
0094     mSuffix = new BLineEdit(mSuffixText, textGroup);
0095     mSuffix->setMinimumSize(mSuffix->sizeHint());
0096     label->setBuddy(mSuffix);
0097     connect(mSuffix, &BLineEdit::focusLost, this, &BirthdayDlg::slotTextLostFocus);
0098     mSuffix->setWhatsThis(i18nc("@info:whatsthis",
0099           "Enter text to appear after the person's name in the alarm message, "
0100           "including any necessary leading spaces."));
0101     grid->addWidget(mSuffix, 1, 1);
0102 
0103     QGroupBox* group = new QGroupBox(i18nc("@title:group", "Select Birthdays"), mainWidget);
0104     topLayout->addWidget(group);
0105     auto layout = new QVBoxLayout(group);
0106     layout->setContentsMargins(0, 0, 0, 0);
0107 
0108     AkonadiPlugin* akonadiPlugin = Preferences::akonadiPlugin();
0109     if (!akonadiPlugin)
0110         return;   // no error message - this constructor should only be called if using Akonadi plugin
0111     mBirthdaySortModel = akonadiPlugin->createBirthdayModels(mainWidget, this);
0112     connect(akonadiPlugin, &AkonadiPlugin::birthdayModelDataChanged, this, &BirthdayDlg::resizeViewColumns);
0113     setSortModelSelectionList();
0114 
0115     mBirthdayModel_NameColumn = akonadiPlugin->birthdayModelEnum(AkonadiPlugin::BirthdayModelValue::NameColumn);
0116     mBirthdayModel_DateColumn = akonadiPlugin->birthdayModelEnum(AkonadiPlugin::BirthdayModelValue::DateColumn);
0117     mBirthdayModel_DateRole   = akonadiPlugin->birthdayModelEnum(AkonadiPlugin::BirthdayModelValue::DateRole);
0118 
0119     mListView = new QTreeView(group);
0120     mListView->setEditTriggers(QAbstractItemView::NoEditTriggers);
0121     mListView->setModel(mBirthdaySortModel);
0122     mListView->setRootIsDecorated(false);    // don't show expander icons
0123     mListView->setSortingEnabled(true);
0124     mListView->sortByColumn(mBirthdayModel_NameColumn, mListView->header()->sortIndicatorOrder());
0125     mListView->setAllColumnsShowFocus(true);
0126     mListView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0127     mListView->setSelectionBehavior(QAbstractItemView::SelectRows);
0128     mListView->setTextElideMode(Qt::ElideRight);
0129     mListView->header()->setSectionResizeMode(mBirthdayModel_NameColumn, QHeaderView::Stretch);
0130     mListView->header()->setSectionResizeMode(mBirthdayModel_DateColumn, QHeaderView::ResizeToContents);
0131     connect(mListView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &BirthdayDlg::slotSelectionChanged);
0132     mListView->setWhatsThis(xi18nc("@info:whatsthis",
0133           "<para>Select birthdays to set alarms for.<nl/>"
0134           "This list shows all birthdays in <application>KAddressBook</application> except those for which alarms already exist.</para>"
0135           "<para>You can select multiple birthdays at one time by dragging the mouse over the list, "
0136           "or by clicking the mouse while pressing Ctrl or Shift.</para>"));
0137     layout->addWidget(mListView);
0138 
0139     group = new QGroupBox(i18nc("@title:group", "Alarm Configuration"), mainWidget);
0140     topLayout->addWidget(group);
0141     auto groupLayout = new QVBoxLayout(group);
0142 
0143     // Sound checkbox and file selector
0144     auto hlayout = new QHBoxLayout();
0145     hlayout->setContentsMargins(0, 0, 0, 0);
0146     groupLayout->addLayout(hlayout);
0147     mSoundPicker = new SoundPicker(group);
0148     hlayout->addWidget(mSoundPicker);
0149     hlayout->addSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0150     hlayout->addStretch();
0151 
0152     // Font and colour choice button and sample text
0153     mFontColourButton = new FontColourButton(group);
0154     mFontColourButton->setMaximumHeight(mFontColourButton->sizeHint().height() * 3/2);
0155     hlayout->addWidget(mFontColourButton);
0156     connect(mFontColourButton, &FontColourButton::selected, this, &BirthdayDlg::setColours);
0157 
0158     // How much advance warning to give
0159     mReminder = new Reminder(i18nc("@info:whatsthis", "Check to display a reminder in advance of or after the birthday."),
0160                              i18nc("@info:whatsthis", "Enter the number of days before or after each birthday to display a reminder. "
0161                                   "This is in addition to the alarm which is displayed on the birthday."),
0162                              i18nc("@info:whatsthis", "Select whether the reminder should be triggered before or after the birthday."),
0163                              false, false, group);
0164     mReminder->setMaximum(0, 364);
0165     mReminder->setMinutes(0, true);
0166     groupLayout->addWidget(mReminder, 0, Qt::AlignLeft);
0167 
0168     // Acknowledgement confirmation required - default = no confirmation
0169     hlayout = new QHBoxLayout();
0170     hlayout->setContentsMargins(0, 0, 0, 0);
0171     hlayout->setSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0172     groupLayout->addLayout(hlayout);
0173     mConfirmAck = EditDisplayAlarmDlg::createConfirmAckCheckbox(group);
0174     hlayout->addWidget(mConfirmAck);
0175     hlayout->addSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0176     hlayout->addStretch();
0177 
0178     if (ShellProcess::authorised())    // don't display if shell commands not allowed (e.g. kiosk mode)
0179     {
0180         // Special actions button
0181         mSpecialActionsButton = new SpecialActionsButton(false, group);
0182         hlayout->addWidget(mSpecialActionsButton);
0183     }
0184 
0185     // Late display checkbox - default = allow late display
0186     hlayout = new QHBoxLayout();
0187     hlayout->setContentsMargins(0, 0, 0, 0);
0188     hlayout->setSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0189     groupLayout->addLayout(hlayout);
0190     mLateCancel = new LateCancelSelector(false, group);
0191     hlayout->addWidget(mLateCancel);
0192     hlayout->addStretch();
0193 
0194     // Sub-repetition button
0195     mSubRepetition = new RepetitionButton(i18nc("@action:button", "Sub-Repetition"), false, group);
0196     mSubRepetition->set(Repetition(), true, 364*24*60);
0197     mSubRepetition->setWhatsThis(i18nc("@info:whatsthis", "Set up an additional alarm repetition"));
0198     hlayout->addWidget(mSubRepetition);
0199 
0200     // Set the values to their defaults
0201     setColours(Preferences::defaultFgColour(), Preferences::defaultBgColour());
0202     mFontColourButton->setFont(Preferences::messageFont(), true);
0203     mFontColourButton->setBgColour(Preferences::defaultBgColour());
0204     mFontColourButton->setFgColour(Preferences::defaultFgColour());
0205     mLateCancel->setMinutes(Preferences::defaultLateCancel(), true, TimePeriod::Days);
0206     mConfirmAck->setChecked(Preferences::defaultConfirmAck());
0207     mSoundPicker->set(Preferences::defaultSoundType(), Preferences::defaultSoundFile(),
0208                       Preferences::defaultSoundVolume(), -1, 0, Preferences::defaultSoundRepeat());
0209     if (mSpecialActionsButton)
0210     {
0211         KAEvent::ExtraActionOptions opts{};
0212         if (Preferences::defaultExecPreActionOnDeferral())
0213             opts |= KAEvent::ExecPreActOnDeferral;
0214         if (Preferences::defaultCancelOnPreActionError())
0215             opts |= KAEvent::CancelOnPreActError;
0216         if (Preferences::defaultDontShowPreActionError())
0217             opts |= KAEvent::DontShowPreActError;
0218         mSpecialActionsButton->setActions(Preferences::defaultPreAction(), Preferences::defaultPostAction(), opts);
0219     }
0220 
0221 
0222     mButtonBox = new QDialogButtonBox(this);
0223     mButtonBox->addButton(QDialogButtonBox::Ok);
0224     mButtonBox->addButton(QDialogButtonBox::Cancel);
0225     connect(mButtonBox, &QDialogButtonBox::accepted,
0226             this, &BirthdayDlg::slotOk);
0227     connect(mButtonBox, &QDialogButtonBox::rejected,
0228             this, &QDialog::reject);
0229     mainLayout->addWidget(mButtonBox);
0230 
0231 
0232     KActionCollection* actions = new KActionCollection(this);
0233     KStandardAction::selectAll(mListView, &QTreeView::selectAll, actions);
0234     KStandardAction::deselect(mListView, &QTreeView::clearSelection, actions);
0235     actions->addAssociatedWidget(mListView);
0236     const auto lstActions = actions->actions();
0237     for (QAction* action : lstActions)
0238         action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0239 
0240     mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(false); // only enable OK button when something is selected
0241 }
0242 
0243 /******************************************************************************
0244 * Return a list of events for birthdays chosen.
0245 */
0246 QList<KAEvent> BirthdayDlg::events() const
0247 {
0248     QList<KAEvent> list;
0249     const QModelIndexList indexes = mListView->selectionModel()->selectedRows();
0250     const int count = indexes.count();
0251     if (!count)
0252         return list;
0253     list.reserve(count);
0254     const QDate today = KADateTime::currentLocalDate();
0255     const KADateTime todayStart(today, KADateTime::LocalZone);
0256     const int thisYear = today.year();
0257     const int reminder = mReminder->minutes();
0258     for (const QModelIndex& index : indexes)
0259     {
0260         const QModelIndex nameIndex     = index.model()->index(index.row(), 0);
0261         const QModelIndex birthdayIndex = index.model()->index(index.row(), 1);
0262         const QString name = nameIndex.data(Qt::DisplayRole).toString();
0263         QDate date = birthdayIndex.data(mBirthdayModel_DateRole).toDate();
0264         date.setDate(thisYear, date.month(), date.day());
0265         if (date <= today)
0266             date.setDate(thisYear + 1, date.month(), date.day());
0267         KAEvent event(KADateTime(date, KADateTime::LocalZone),
0268                       (mName ? mName->text() : QString()),
0269                       mPrefix->text() + name + mSuffix->text(),
0270                       mFontColourButton->bgColour(), mFontColourButton->fgColour(),
0271                       mFontColourButton->font(), KAEvent::SubAction::Message, mLateCancel->minutes(),
0272                       mFlags, true);
0273         float fadeVolume;
0274         int   fadeSecs;
0275         const float volume = mSoundPicker->volume(fadeVolume, fadeSecs);
0276         const int   repeatPause = mSoundPicker->repeatPause();
0277         event.setAudioFile(mSoundPicker->file().toDisplayString(), volume, fadeVolume, fadeSecs, repeatPause);
0278         const QList<int> months(1, date.month());
0279         event.setRecurAnnualByDate(1, months, 0, KARecurrence::defaultFeb29Type(), -1, QDate());
0280         event.setRepetition(mSubRepetition->repetition());
0281         event.setNextOccurrence(todayStart);
0282         if (reminder)
0283             event.setReminder(reminder, false);
0284         if (mSpecialActionsButton)
0285             event.setActions(mSpecialActionsButton->preAction(),
0286                              mSpecialActionsButton->postAction(),
0287                              mSpecialActionsButton->options());
0288         event.endChanges();
0289         list.append(event);
0290     }
0291     return list;
0292 }
0293 
0294 /******************************************************************************
0295 * Called when the OK button is selected to import the selected birthdays.
0296 */
0297 void BirthdayDlg::slotOk()
0298 {
0299     // Save prefix and suffix texts to use as future defaults
0300     KConfigGroup config(KSharedConfig::openConfig(), GENERAL_GROUP);
0301     config.writeEntry("BirthdayPrefix", mPrefix->text());
0302     config.writeEntry("BirthdaySuffix", mSuffix->text());
0303     config.sync();
0304 
0305     mFlags = KAEvent::ANY_TIME;
0306     if (mSoundPicker->sound() == Preferences::Sound_Beep) mFlags |= KAEvent::BEEP;
0307     if (mSoundPicker->repeatPause() >= 0)                 mFlags |= KAEvent::REPEAT_SOUND;
0308     if (mConfirmAck->isChecked())                         mFlags |= KAEvent::CONFIRM_ACK;
0309     if (mFontColourButton->defaultFont())                 mFlags |= KAEvent::DEFAULT_FONT;
0310     QDialog::accept();
0311 }
0312 
0313 /******************************************************************************
0314 * Called when the group of items selected changes.
0315 * Enable/disable the OK button depending on whether anything is selected.
0316 */
0317 void BirthdayDlg::slotSelectionChanged()
0318 {
0319     mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(mListView->selectionModel()->hasSelection());
0320 }
0321 
0322 /******************************************************************************
0323 * Called when the font/color button has been clicked.
0324 * Set the colors in the message text entry control.
0325 */
0326 void BirthdayDlg::setColours(const QColor& fgColour, const QColor& bgColour)
0327 {
0328     QPalette pal = mPrefix->palette();
0329     pal.setColor(mPrefix->backgroundRole(), bgColour);
0330     pal.setColor(mPrefix->foregroundRole(), fgColour);
0331     mPrefix->setPalette(pal);
0332     mSuffix->setPalette(pal);
0333 }
0334 
0335 /******************************************************************************
0336 * Called when the data has changed in the birthday list.
0337 * Resize the date column.
0338 */
0339 void BirthdayDlg::resizeViewColumns()
0340 {
0341     mListView->resizeColumnToContents(mBirthdayModel_DateColumn);
0342 }
0343 
0344 /******************************************************************************
0345 * Called when the prefix or suffix text has lost keyboard focus.
0346 * If the text has changed, re-evaluates the selection list according to the new
0347 * birthday alarm text format.
0348 */
0349 void BirthdayDlg::slotTextLostFocus()
0350 {
0351     const QString prefix = mPrefix->text();
0352     const QString suffix = mSuffix->text();
0353     if (prefix != mPrefixText  ||  suffix != mSuffixText)
0354     {
0355         // Text has changed - re-evaluate the selection list
0356         mPrefixText = prefix;
0357         mSuffixText = suffix;
0358         setSortModelSelectionList();
0359     }
0360 }
0361 
0362 /******************************************************************************
0363 * Re-evaluates the selection list according to the birthday alarm text format.
0364 */
0365 void BirthdayDlg::setSortModelSelectionList()
0366 {
0367     AkonadiPlugin* akonadiPlugin = Preferences::akonadiPlugin();
0368     if (!akonadiPlugin)
0369         return;
0370 
0371     QStringList alarmMessageList;
0372     const QList<KAEvent> activeEvents = ResourcesCalendar::events(CalEvent::ACTIVE);
0373     for (const KAEvent& event : activeEvents)
0374     {
0375         if (event.actionSubType() == KAEvent::SubAction::Message
0376         &&  event.recurType() == KARecurrence::ANNUAL_DATE
0377         &&  (mPrefixText.isEmpty()  ||  event.message().startsWith(mPrefixText)))
0378             alarmMessageList.append(event.message());
0379     }
0380     akonadiPlugin->setPrefixSuffix(mBirthdaySortModel, mPrefixText, mSuffixText, alarmMessageList);
0381 }
0382 
0383 #include "moc_birthdaydlg.cpp"
0384 
0385 // vim: et sw=4: