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

0001 /*
0002  *  prefdlg.cpp  -  program preferences dialog
0003  *  Program:  kalarm
0004  *  SPDX-FileCopyrightText: 2001-2024 David Jarvie <djarvie@kde.org>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "prefdlg.h"
0010 #include "prefdlg_p.h"
0011 
0012 #include "alarmtimewidget.h"
0013 #include "editdlg.h"
0014 #include "editdlgtypes.h"
0015 #include "fontcolour.h"
0016 #include "kalarmapp.h"
0017 #include "kamail.h"
0018 #include "latecancel.h"
0019 #include "mainwindow.h"
0020 #include "pluginmanager.h"
0021 #include "preferences.h"
0022 #include "recurrenceedit.h"
0023 #include "resourcescalendar.h"
0024 #include "sounddlg.h"
0025 #include "soundpicker.h"
0026 #include "specialactions.h"
0027 #include "resources/resources.h"
0028 #include "lib/buttongroup.h"
0029 #include "lib/checkbox.h"
0030 #include "lib/colourbutton.h"
0031 #include "lib/config.h"
0032 #include "lib/desktop.h"
0033 #include "lib/label.h"
0034 #include "lib/locale.h"
0035 #include "lib/messagebox.h"
0036 #include "lib/radiobutton.h"
0037 #include "lib/slider.h"
0038 #include "lib/stackedwidgets.h"
0039 #include "lib/timeedit.h"
0040 #include "lib/timespinbox.h"
0041 #include "lib/timezonecombo.h"
0042 #include "kalarmcalendar/holidays.h"
0043 #include "kalarmcalendar/identities.h"
0044 #include "config-kalarm.h"
0045 #include "kalarm_debug.h"
0046 
0047 #include <KHolidays/HolidayRegion>
0048 using namespace KHolidays;
0049 
0050 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT
0051 #include <TextEditTextToSpeech/TextToSpeech>
0052 #endif
0053 
0054 #include <KLocalizedString>
0055 #include <KShell>
0056 #include <KAboutData>
0057 #include <KStandardGuiItem>
0058 #include <QIcon>
0059 #if ENABLE_X11
0060 #include <KX11Extras>
0061 #include <KWindowInfo>
0062 #endif
0063 #include <KWindowSystem>
0064 #include <KHelpClient>
0065 
0066 #include <QLabel>
0067 #include <QCheckBox>
0068 #include <QRadioButton>
0069 #include <QPushButton>
0070 #include <QTabWidget>
0071 #include <QSpinBox>
0072 #include <QGroupBox>
0073 #include <QGridLayout>
0074 #include <QHBoxLayout>
0075 #include <QVBoxLayout>
0076 #include <QStyle>
0077 #include <QResizeEvent>
0078 #include <QStandardPaths>
0079 #include <QComboBox>
0080 #include <QLocale>
0081 #include <QLineEdit>
0082 
0083 using namespace KCalendarCore;
0084 using namespace KAlarmCal;
0085 
0086 static const char PREF_DIALOG_NAME[] = "PrefDialog";
0087 
0088 
0089 /*=============================================================================
0090 = Class KAlarmPrefDlg
0091 =============================================================================*/
0092 
0093 KAlarmPrefDlg*         KAlarmPrefDlg::mInstance = nullptr;
0094 KAlarmPrefDlg::TabType KAlarmPrefDlg::mLastTab  = AnyTab;
0095 
0096 /******************************************************************************
0097 * Display the Preferences dialog as a non-modal window.
0098 */
0099 void KAlarmPrefDlg::display()
0100 {
0101     if (!mInstance)
0102     {
0103         mInstance = new KAlarmPrefDlg;
0104         QSize s;
0105         if (Config::readWindowSize(PREF_DIALOG_NAME, s))
0106             mInstance->resize(s);
0107         mInstance->restoreTab();
0108         mInstance->show();
0109     }
0110     else
0111     {
0112         mInstance->restoreTab();
0113         // Switch to the virtual desktop which the dialog is in
0114 #if ENABLE_X11
0115         KWindowInfo info = KWindowInfo(mInstance->winId(), NET::WMGeometry | NET::WMDesktop);
0116         KX11Extras::setCurrentDesktop(info.desktop());
0117 #endif
0118         mInstance->setWindowState(mInstance->windowState() & ~Qt::WindowMinimized); // un-minimize it if necessary
0119         mInstance->raise();
0120         mInstance->activateWindow();
0121     }
0122 }
0123 
0124 KAlarmPrefDlg::KAlarmPrefDlg()
0125     : KPageDialog()
0126 {
0127     setAttribute(Qt::WA_DeleteOnClose);
0128     setObjectName(QLatin1StringView("PrefDlg"));    // used by LikeBack
0129     setWindowTitle(i18nc("@title:window", "Configure"));
0130     setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Apply);
0131     button(QDialogButtonBox::Ok)->setDefault(true);
0132     button(QDialogButtonBox::RestoreDefaults)->setToolTip(KStandardGuiItem::defaults().toolTip());
0133     button(QDialogButtonBox::Help)->setToolTip(KStandardGuiItem::help().toolTip());
0134     button(QDialogButtonBox::Apply)->setToolTip(KStandardGuiItem::apply().toolTip());
0135     setFaceType(List);
0136     mTabScrollGroup = new StackedScrollGroup(this, this);
0137 
0138     mMiscPage = new MiscPrefTab(mTabScrollGroup);
0139     mMiscPageItem = new KPageWidgetItem(mMiscPage, i18nc("@title:tab General preferences", "General"));
0140     mMiscPageItem->setHeader(i18nc("@title General preferences", "General"));
0141     mMiscPageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other")));
0142     addPage(mMiscPageItem);
0143 
0144     mTimePage = new TimePrefTab(mTabScrollGroup);
0145     mTimePageItem = new KPageWidgetItem(mTimePage, i18nc("@title:tab", "Time & Date"));
0146     mTimePageItem->setHeader(i18nc("@title", "Time and Date"));
0147     mTimePageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-time")));
0148     addPage(mTimePageItem);
0149 
0150     mStorePage = new StorePrefTab(mTabScrollGroup);
0151     mStorePageItem = new KPageWidgetItem(mStorePage, i18nc("@title:tab", "Storage"));
0152     mStorePageItem->setHeader(i18nc("@title", "Alarm Storage"));
0153     mStorePageItem->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager")));
0154     addPage(mStorePageItem);
0155 
0156     mEmailPage = new EmailPrefTab(mTabScrollGroup);
0157     mEmailPageItem = new KPageWidgetItem(mEmailPage, i18nc("@title:tab Email preferences", "Email"));
0158     mEmailPageItem->setHeader(i18nc("@title", "Email Alarm Settings"));
0159     mEmailPageItem->setIcon(QIcon::fromTheme(QStringLiteral("internet-mail")));
0160     addPage(mEmailPageItem);
0161 
0162     mEditPage = new EditPrefTab(mTabScrollGroup);
0163     mEditPageItem = new KPageWidgetItem(mEditPage, i18nc("@title:tab", "Alarm Defaults"));
0164     mEditPageItem->setHeader(i18nc("@title", "Default Alarm Edit Settings"));
0165     mEditPageItem->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
0166     addPage(mEditPageItem);
0167 
0168     mViewPage = new ViewPrefTab(mTabScrollGroup);
0169     mViewPageItem = new KPageWidgetItem(mViewPage, i18nc("@title:tab", "View"));
0170     mViewPageItem->setHeader(i18nc("@title", "View Settings"));
0171     mViewPageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-theme")));
0172     addPage(mViewPageItem);
0173 
0174     connect(button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, &KAlarmPrefDlg::slotOk);
0175     connect(button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, this, &KAlarmPrefDlg::slotCancel);
0176     connect(button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &KAlarmPrefDlg::slotApply);
0177     connect(button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this, &KAlarmPrefDlg::slotDefault);
0178     connect(button(QDialogButtonBox::Help), &QAbstractButton::clicked, this, &KAlarmPrefDlg::slotHelp);
0179     connect(this, &KPageDialog::currentPageChanged, this, &KAlarmPrefDlg::slotTabChanged);
0180 
0181     restore(false);
0182     adjustSize();
0183 }
0184 
0185 KAlarmPrefDlg::~KAlarmPrefDlg()
0186 {
0187     mInstance = nullptr;
0188 }
0189 
0190 void KAlarmPrefDlg::slotHelp()
0191 {
0192     KHelpClient::invokeHelp(QStringLiteral("preferences"));
0193 }
0194 
0195 // Apply the preferences that are currently selected
0196 void KAlarmPrefDlg::slotApply()
0197 {
0198     qCDebug(KALARM_LOG) << "KAlarmPrefDlg::slotApply";
0199     mValid = true;
0200     apply();
0201 }
0202 
0203 // Apply the preferences that are currently selected
0204 void KAlarmPrefDlg::apply()
0205 {
0206     QString errmsg = mEmailPage->validate();
0207     if (!errmsg.isEmpty())
0208     {
0209         setCurrentPage(mEmailPageItem);
0210         if (KAMessageBox::warningYesNo(this, errmsg) != KMessageBox::ButtonCode::PrimaryAction)
0211         {
0212             mValid = false;
0213             return;
0214         }
0215     }
0216     errmsg = mEditPage->validate();
0217     if (!errmsg.isEmpty())
0218     {
0219         setCurrentPage(mEditPageItem);
0220         KAMessageBox::error(this, errmsg);
0221         mValid = false;
0222         return;
0223     }
0224     mValid = mEmailPage->apply(false) && mValid;
0225     mValid = mViewPage->apply(false)  && mValid;
0226     mValid = mEditPage->apply(false)  && mValid;
0227     mValid = mStorePage->apply(false) && mValid;
0228     mValid = mTimePage->apply(false)  && mValid;
0229     mValid = mMiscPage->apply(false)  && mValid;
0230     Preferences::self()->save();
0231 }
0232 
0233 // Apply the preferences that are currently selected
0234 void KAlarmPrefDlg::slotOk()
0235 {
0236     qCDebug(KALARM_LOG) << "KAlarmPrefDlg::slotOk";
0237     mValid = true;
0238     apply();
0239     if (mValid)
0240         QDialog::accept();
0241 }
0242 
0243 void KAlarmPrefDlg::accept()
0244 {
0245     // accept() is called automatically by KPageDialog when OK button is clicked.
0246     // Don't accept and close the dialog until slotOk() has finished processing.
0247 }
0248 
0249 // Discard the current preferences and close the dialog
0250 void KAlarmPrefDlg::slotCancel()
0251 {
0252     qCDebug(KALARM_LOG) << "KAlarmPrefDlg::slotCancel";
0253     restore(false);
0254     KPageDialog::reject();
0255 }
0256 
0257 // Reset all controls to the application defaults
0258 void KAlarmPrefDlg::slotDefault()
0259 {
0260     switch (KAMessageBox::questionYesNoCancel(this,
0261                          i18nc("@info", "Reset all tabs to their default values, or only reset the current tab?"),
0262                          QString(),
0263                          KGuiItem(i18nc("@action:button Reset ALL tabs", "All")),
0264                          KGuiItem(i18nc("@action:button Reset the CURRENT tab", "Current"))))
0265     {
0266         case KMessageBox::ButtonCode::PrimaryAction:
0267             restore(true);   // restore all tabs
0268             break;
0269         case KMessageBox::ButtonCode::SecondaryAction:
0270             Preferences::self()->useDefaults(true);
0271             static_cast<PrefsTabBase*>(currentPage()->widget())->restore(true, false);
0272             Preferences::self()->useDefaults(false);
0273             break;
0274         default:
0275             break;
0276     }
0277 }
0278 
0279 // Discard the current preferences and use the present ones
0280 void KAlarmPrefDlg::restore(bool defaults)
0281 {
0282     qCDebug(KALARM_LOG) << "KAlarmPrefDlg::restore:" << (defaults ? "defaults" : "");
0283     if (defaults)
0284         Preferences::self()->useDefaults(true);
0285     mEmailPage->restore(defaults, true);
0286     mViewPage->restore(defaults, true);
0287     mEditPage->restore(defaults, true);
0288     mStorePage->restore(defaults, true);
0289     mTimePage->restore(defaults, true);
0290     mMiscPage->restore(defaults, true);
0291     if (defaults)
0292         Preferences::self()->useDefaults(false);
0293 }
0294 
0295 /******************************************************************************
0296 * Return the minimum size for the dialog.
0297 * If the minimum size would be too high to fit the desktop, the tab contents
0298 * are made scrollable.
0299 */
0300 QSize KAlarmPrefDlg::minimumSizeHint() const
0301 {
0302     if (!mTabScrollGroup->sized())
0303     {
0304         QSize s = mTabScrollGroup->adjustSize();
0305         if (s.isValid())
0306         {
0307             if (mTabScrollGroup->heightReduction())
0308             {
0309                 s = QSize(s.width(), s.height() - mTabScrollGroup->heightReduction());
0310                 const_cast<KAlarmPrefDlg*>(this)->resize(s);
0311             }
0312             return s;
0313         }
0314     }
0315     return KPageDialog::minimumSizeHint();
0316 }
0317 
0318 void KAlarmPrefDlg::showEvent(QShowEvent* e)
0319 {
0320     KPageDialog::showEvent(e);
0321     if (!mShown)
0322     {
0323         mTabScrollGroup->adjustSize(true);
0324         mShown = true;
0325     }
0326 }
0327 
0328 /******************************************************************************
0329 * Called when the dialog's size has changed.
0330 * Records the new size in the config file.
0331 */
0332 void KAlarmPrefDlg::resizeEvent(QResizeEvent* re)
0333 {
0334     if (isVisible())
0335         Config::writeWindowSize(PREF_DIALOG_NAME, re->size());
0336     KPageDialog::resizeEvent(re);
0337 }
0338 
0339 /******************************************************************************
0340 * Called when a new tab is selected, to note which one it is.
0341 */
0342 void KAlarmPrefDlg::slotTabChanged(KPageWidgetItem* item)
0343 {
0344     mLastTab = (item == mMiscPageItem)  ? Misc
0345              : (item == mTimePageItem)  ? Time
0346              : (item == mStorePageItem) ? Store
0347              : (item == mEditPageItem)  ? Edit
0348              : (item == mEmailPageItem) ? Email
0349              : (item == mViewPageItem)  ? View : AnyTab;
0350 }
0351 
0352 /******************************************************************************
0353 * Selects the tab noted in mLastTab.
0354 */
0355 void KAlarmPrefDlg::restoreTab()
0356 {
0357     KPageWidgetItem* item = nullptr;
0358     switch (mLastTab)
0359     {
0360         case Misc:   item = mMiscPageItem;   break;
0361         case Time:   item = mTimePageItem;   break;
0362         case Store:  item = mStorePageItem;  break;
0363         case Edit:   item = mEditPageItem;   break;
0364         case Email:  item = mEmailPageItem;  break;
0365         case View:   item = mViewPageItem;   break;
0366         default:
0367             return;
0368     }
0369     setCurrentPage(item);
0370 }
0371 
0372 
0373 /*=============================================================================
0374 = Class PrefsTabBase
0375 =============================================================================*/
0376 int PrefsTabBase::mIndentWidth = 0;
0377 int PrefsTabBase::mGridIndentWidth = 0;
0378 
0379 PrefsTabBase::PrefsTabBase(StackedScrollGroup* scrollGroup)
0380     : StackedScrollWidget(scrollGroup)
0381 {
0382     QFrame* topWidget = new QFrame(this);
0383     setWidget(topWidget);
0384     mTopLayout = new QVBoxLayout(topWidget);
0385     mTopLayout->setContentsMargins(
0386         style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
0387         style()->pixelMetric(QStyle::PM_LayoutTopMargin),
0388         style()->pixelMetric(QStyle::PM_LayoutRightMargin),
0389         style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
0390 
0391     if (!mIndentWidth)
0392     {
0393         QCheckBox cb(this);
0394         mIndentWidth = CheckBox::textIndent(&cb);
0395         mGridIndentWidth = mIndentWidth - style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
0396     }
0397 }
0398 
0399 bool PrefsTabBase::apply(bool syncToDisc)
0400 {
0401     if (syncToDisc)
0402         Preferences::self()->save();
0403     return true;
0404 }
0405 
0406 void PrefsTabBase::addAlignedLabel(QLabel* label)
0407 {
0408     mLabels += label;
0409 }
0410 
0411 void PrefsTabBase::showEvent(QShowEvent*)
0412 {
0413     if (!mLabelsAligned)
0414     {
0415         int wid = 0;
0416         int i;
0417         const int labelCount = mLabels.count();
0418         QList<int> xpos;
0419         xpos.reserve(labelCount);
0420         for (const QLabel* label : std::as_const(mLabels))
0421         {
0422             const int x = label->mapTo(this, QPoint(0, 0)).x();
0423             xpos += x;
0424             const int w = x + label->sizeHint().width();
0425             if (w > wid)
0426                 wid = w;
0427         }
0428         for (i = 0;  i < labelCount;  ++i)
0429         {
0430             mLabels[i]->setFixedWidth(wid - xpos[i]);
0431             mLabels[i]->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
0432         }
0433         mLabelsAligned = true;
0434     }
0435 }
0436 
0437 
0438 /*=============================================================================
0439 = Class MiscPrefTab
0440 =============================================================================*/
0441 
0442 MiscPrefTab::MiscPrefTab(StackedScrollGroup* scrollGroup)
0443     : PrefsTabBase(scrollGroup)
0444 {
0445     QGroupBox* group = new QGroupBox(i18nc("@title:group", "Run Mode"));
0446     topLayout()->addWidget(group);
0447     auto vlayout = new QVBoxLayout(group);
0448 
0449     // Start at login
0450     mAutoStart = new QCheckBox(i18nc("@option:check", "Start at login"), group);
0451     connect(mAutoStart, &QAbstractButton::clicked, this, &MiscPrefTab::slotAutostartClicked);
0452     mAutoStart->setWhatsThis(xi18nc("@info:whatsthis",
0453           "<para>Automatically start <application>KAlarm</application> whenever you start KDE.</para>"
0454           "<para>This option should always be checked unless you intend to discontinue use of <application>KAlarm</application>.</para>"));
0455     vlayout->addWidget(mAutoStart, 0, Qt::AlignLeft);
0456 
0457     mQuitWarn = new QCheckBox(i18nc("@option:check", "Warn before quitting"), group);
0458     mQuitWarn->setWhatsThis(xi18nc("@info:whatsthis", "Check to display a warning prompt before quitting <application>KAlarm</application>."));
0459     vlayout->addWidget(mQuitWarn, 0, Qt::AlignLeft);
0460 
0461     // Enable alarm names?
0462     QWidget* widget = new QWidget;  // this is for consistent left alignment
0463     topLayout()->addWidget(widget);
0464     auto hbox = new QHBoxLayout(widget);
0465     mUseAlarmNames = new QCheckBox(i18nc("@option:check", "Enable alarm names"));
0466     mUseAlarmNames->setMinimumSize(mUseAlarmNames->sizeHint());
0467     mUseAlarmNames->setWhatsThis(i18nc("@info:whatsthis", "Check to have the option to give alarms a name. This is a convenience to help you to identify alarms."));
0468     hbox->addWidget(mUseAlarmNames);
0469     hbox->addStretch();    // left adjust the controls
0470 
0471     // Confirm alarm deletion?
0472     widget = new QWidget;  // this is for consistent left alignment
0473     topLayout()->addWidget(widget);
0474     hbox = new QHBoxLayout(widget);
0475     mConfirmAlarmDeletion = new QCheckBox(i18nc("@option:check", "Confirm alarm deletions"));
0476     mConfirmAlarmDeletion->setMinimumSize(mConfirmAlarmDeletion->sizeHint());
0477     mConfirmAlarmDeletion->setWhatsThis(i18nc("@info:whatsthis", "Check to be prompted for confirmation each time you delete an alarm."));
0478     hbox->addWidget(mConfirmAlarmDeletion);
0479     hbox->addStretch();    // left adjust the controls
0480 
0481     // Default alarm deferral time
0482     widget = new QWidget;   // this is to control the QWhatsThis text display area
0483     topLayout()->addWidget(widget);
0484     hbox = new QHBoxLayout(widget);
0485     QLabel* label = new QLabel(i18nc("@label:spinbox", "Default defer time interval:"));
0486     hbox->addWidget(label);
0487     mDefaultDeferTime = new TimeSpinBox(1, 5999);
0488     mDefaultDeferTime->setMinimumSize(mDefaultDeferTime->sizeHint());
0489     hbox->addWidget(mDefaultDeferTime);
0490     widget->setWhatsThis(i18nc("@info:whatsthis",
0491             "Enter the default time interval (hours & minutes) to defer alarms, used by the Defer Alarm dialog."));
0492     label->setBuddy(mDefaultDeferTime);
0493     hbox->addStretch();    // left adjust the controls
0494 
0495     // Terminal window to use for command alarms
0496     group = new QGroupBox(i18nc("@title:group", "Terminal for Command Alarms"));
0497     group->setWhatsThis(i18nc("@info:whatsthis", "Choose which application to use when a command alarm is executed in a terminal window"));
0498     topLayout()->addWidget(group);
0499     auto grid = new QGridLayout(group);
0500     int row = 0;
0501 
0502     mXtermType = new ButtonGroup(group);
0503     int index = 0;
0504     const auto xtermCommands = Preferences::cmdXTermStandardCommands();
0505     for (auto it = xtermCommands.constBegin();  it != xtermCommands.constEnd();  ++it, ++index)
0506     {
0507         QString cmd = it.value();
0508         const QStringList args = KShell::splitArgs(cmd);
0509         auto radio = new QRadioButton(args[0], group);
0510         radio->setMinimumSize(radio->sizeHint());
0511         mXtermType->addButton(radio, it.key());
0512         cmd.replace(QStringLiteral("%t"), KAboutData::applicationData().displayName());
0513         cmd.replace(QStringLiteral("%c"), QStringLiteral("<command>"));
0514         cmd.replace(QStringLiteral("%w"), QStringLiteral("<command; sleep>"));
0515         cmd.replace(QStringLiteral("%C"), QStringLiteral("[command]"));
0516         cmd.replace(QStringLiteral("%W"), QStringLiteral("[command; sleep]"));
0517         radio->setWhatsThis(
0518                 xi18nc("@info:whatsthis", "Check to execute command alarms in a terminal window by <icode>%1</icode>", cmd));
0519         grid->addWidget(radio, (row = index/3), index % 3, Qt::AlignLeft);
0520     }
0521 
0522     // QHBox used here doesn't allow the QLineEdit to expand!?
0523     auto hlayout = new QHBoxLayout();
0524     grid->addLayout(hlayout, row + 1, 0, 1, 3, Qt::AlignLeft);
0525     QRadioButton* radio = new QRadioButton(i18nc("@option:radio Other terminal window command", "Other:"), group);
0526     hlayout->addWidget(radio);
0527     connect(radio, &QAbstractButton::toggled, this, &MiscPrefTab::slotOtherTerminalToggled);
0528     mXtermType->addButton(radio, 0);
0529 
0530     mXtermCommand = new QLineEdit(group);
0531     mXtermCommand->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
0532     hlayout->addWidget(mXtermCommand);
0533     const QString wt =
0534           xi18nc("@info:whatsthis", "Enter the full command line needed to execute a command in your chosen terminal window. "
0535                "By default the alarm's command string will be appended to what you enter here. "
0536                "See the <application>KAlarm</application> Handbook for details of special codes to tailor the command line.");
0537     radio->setWhatsThis(wt);
0538     mXtermCommand->setWhatsThis(wt);
0539 
0540     if (PluginManager::instance()->akonadiPlugin())
0541     {
0542         // Plugins
0543         group = new QGroupBox(i18nc("@title:group", "Plugins"));
0544         topLayout()->addWidget(group);
0545         vlayout = new QVBoxLayout(group);
0546 
0547         // Akonadi plugin
0548         mUseAkonadi = new QCheckBox(i18nc("@option:check", "Enable Akonadi"), group);
0549         mUseAkonadi->setToolTip(i18nc("@info:tooltip",
0550               "Enable the use of Akonadi to provide features including birthday import, some email functions, and email address book lookup."));
0551         mUseAkonadi->setWhatsThis(i18nc("@info:whatsthis",
0552               "Enable the use of Akonadi to provide features including birthday import, some email functions, and email address book lookup."));
0553         vlayout->addWidget(mUseAkonadi, 0, Qt::AlignLeft);
0554     }
0555 
0556     topLayout()->addStretch();    // top adjust the widgets
0557 }
0558 
0559 void MiscPrefTab::restore(bool defaults, bool)
0560 {
0561     mAutoStart->setChecked(defaults ? true : Preferences::autoStart());
0562     mQuitWarn->setChecked(Preferences::quitWarn());
0563     mUseAlarmNames->setChecked(Preferences::useAlarmName());
0564     mConfirmAlarmDeletion->setChecked(Preferences::confirmAlarmDeletion());
0565     mDefaultDeferTime->setValue(Preferences::defaultDeferTime());
0566     const auto xtermCmd = Preferences::cmdXTermCommandIndex();
0567     int id = xtermCmd.first;
0568     if (id > 0  &&  !mXtermType->find(id))
0569     {
0570         // The command is a standard command, but there is no button for it
0571         // (because the executable doesn't exist on this system). So set it
0572         // into the 'Other' option.
0573         id = 0;
0574     }
0575     mXtermType->setButton(id);
0576     mXtermCommand->setEnabled(id == 0);
0577     mXtermCommand->setText(id == 0 ? xtermCmd.second : QString());
0578     if (mUseAkonadi)
0579         mUseAkonadi->setChecked(Preferences::useAkonadi());
0580 }
0581 
0582 bool MiscPrefTab::apply(bool syncToDisc)
0583 {
0584     // First validate anything entered in Other X-terminal command
0585     int xtermID = mXtermType->selectedId();
0586     if (!xtermID)
0587     {
0588         // 'Other' is selected.
0589         QString cmd = mXtermCommand->text();
0590         if (cmd.isEmpty())
0591         {
0592             mXtermCommand->setFocus();
0593             const QList<QAbstractButton*> buttons = mXtermType->buttons();
0594             if (buttons.count() == 2)
0595             {
0596                 // There are only radio buttons for one standard command, plus 'Other'
0597                 const int id = mXtermType->id(buttons[0]);
0598                 cmd = Preferences::cmdXTermStandardCommand(id);
0599                 const QStringList args = KShell::splitArgs(cmd);
0600                 if (KAMessageBox::warningContinueCancel(topLayout()->parentWidget(), xi18nc("@info", "<para>No command is specified to invoke a terminal window.</para><para>The default command (<command>%1</command>) will be used.</para>", args[0]))
0601                                 != KMessageBox::Continue)
0602                     return false;
0603                 xtermID = id;
0604                 mXtermType->setButton(id);
0605             }
0606             else
0607             {
0608                 if (KAMessageBox::warningContinueCancel(topLayout()->parentWidget(), xi18nc("@info", "No command is specified to invoke a terminal window"))
0609                                 != KMessageBox::Continue)
0610                     return false;
0611                 xtermID = -1;
0612             }
0613         }
0614         else
0615         {
0616             const QStringList args = KShell::splitArgs(cmd);
0617             cmd = args.isEmpty() ? QString() : args[0];
0618             if (QStandardPaths::findExecutable(cmd).isEmpty())
0619             {
0620                 mXtermCommand->setFocus();
0621                 if (KAMessageBox::warningContinueCancel(topLayout()->parentWidget(), xi18nc("@info", "Command to invoke terminal window not found: <command>%1</command>", cmd))
0622                                 != KMessageBox::Continue)
0623                     return false;
0624             }
0625         }
0626     }
0627 
0628     if (mQuitWarn->isEnabled())
0629     {
0630         const bool b = mQuitWarn->isChecked();
0631         if (b != Preferences::quitWarn())
0632             Preferences::setQuitWarn(b);
0633     }
0634     bool b = mAutoStart->isChecked();
0635     if (b != Preferences::autoStart())
0636     {
0637         Preferences::setAutoStart(b);
0638         Preferences::setAskAutoStart(true);  // cancel any start-at-login prompt suppression
0639         if (b)
0640             Preferences::setNoAutoStart(false);
0641         Preferences::setAutoStartChangedByUser(true);  // prevent prompting the user on quit, about start-at-login
0642     }
0643     b = mUseAlarmNames->isChecked();
0644     if (b != Preferences::useAlarmName())
0645         Preferences::setUseAlarmName(b);
0646     b = mConfirmAlarmDeletion->isChecked();
0647     if (b != Preferences::confirmAlarmDeletion())
0648         Preferences::setConfirmAlarmDeletion(b);
0649     int i = mDefaultDeferTime->value();
0650     if (i != Preferences::defaultDeferTime())
0651         Preferences::setDefaultDeferTime(i);
0652     const auto xtermCmd = Preferences::cmdXTermCommandIndex();
0653     if (xtermID != xtermCmd.first  ||  (!xtermID && mXtermCommand->text() != xtermCmd.second))
0654     {
0655         if (xtermID > 0)
0656             Preferences::setCmdXTermCommand(xtermID);
0657         else
0658             Preferences::setCmdXTermSpecialCommand(mXtermCommand->text());
0659     }
0660     if (mUseAkonadi)
0661     {
0662         b = mUseAkonadi->isChecked();
0663         if (b != Preferences::useAkonadi())
0664             Preferences::setUseAkonadi(b);
0665     }
0666     return PrefsTabBase::apply(syncToDisc);
0667 }
0668 
0669 void MiscPrefTab::slotAutostartClicked()
0670 {
0671     if (!mAutoStart->isChecked()
0672     &&  KAMessageBox::warningYesNo(topLayout()->parentWidget(),
0673                                    xi18nc("@info", "You should not uncheck this option unless you intend to discontinue use of <application>KAlarm</application>"),
0674                                    QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel()
0675                                   ) != KMessageBox::ButtonCode::PrimaryAction)
0676         mAutoStart->setChecked(true);
0677 }
0678 
0679 void MiscPrefTab::slotOtherTerminalToggled(bool on)
0680 {
0681     mXtermCommand->setEnabled(on);
0682 }
0683 
0684 
0685 /*=============================================================================
0686 = Class TimePrefTab
0687 =============================================================================*/
0688 
0689 TimePrefTab::TimePrefTab(StackedScrollGroup* scrollGroup)
0690     : PrefsTabBase(scrollGroup)
0691 {
0692     const int marLeft  = style()->pixelMetric(QStyle::PM_LayoutLeftMargin);
0693     const int marRight = style()->pixelMetric(QStyle::PM_LayoutRightMargin);
0694     const int marTop   = style()->pixelMetric(QStyle::PM_LayoutTopMargin);
0695 
0696     // Default time zone
0697     auto itemBox = new QHBoxLayout();
0698     itemBox->setContentsMargins(marLeft, marTop, marRight, 0);
0699     topLayout()->addLayout(itemBox);
0700 
0701     QWidget* widget = new QWidget; // this is to control the QWhatsThis text display area
0702     itemBox->addWidget(widget);
0703     auto box = new QHBoxLayout(widget);
0704     box->setContentsMargins(0, 0, 0, 0);
0705     QLabel* label = new QLabel(i18nc("@label:listbox", "Time zone:"));
0706     box->addWidget(label);
0707     addAlignedLabel(label);
0708     mTimeZone = new TimeZoneCombo(widget);
0709     mTimeZone->setMaxVisibleItems(15);
0710     box->addWidget(mTimeZone);
0711     widget->setWhatsThis(xi18nc("@info:whatsthis",
0712                                 "Select the time zone which <application>KAlarm</application> should use "
0713                                 "as its default for displaying and entering dates and times."));
0714     label->setBuddy(mTimeZone);
0715     itemBox->addStretch();
0716 
0717     // Holiday region
0718     itemBox = new QHBoxLayout();
0719     itemBox->setContentsMargins(marLeft, 0, marRight, 0);
0720     topLayout()->addLayout(itemBox);
0721 
0722     widget = new QWidget;    // this is to control the QWhatsThis text display area
0723     itemBox->addWidget(widget);
0724     box = new QHBoxLayout(widget);
0725     box->setContentsMargins(0, 0, 0, 0);
0726     label = new QLabel(i18nc("@label:listbox", "Holiday region:"));
0727     addAlignedLabel(label);
0728     box->addWidget(label);
0729     mHolidays = new QComboBox();
0730     mHolidays->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
0731     box->addWidget(mHolidays);
0732     itemBox->addStretch();
0733     label->setBuddy(mHolidays);
0734     widget->setWhatsThis(i18nc("@info:whatsthis",
0735                                "Select which holiday region to use"));
0736 
0737     const QStringList regions = HolidayRegion::regionCodes();
0738     QMap<QString, QString> regionsMap;
0739     for (const QString& regionCode : regions)
0740     {
0741         const QString name = HolidayRegion::name(regionCode);
0742         const QString languageName = QLocale::languageToString(QLocale(HolidayRegion::languageCode(regionCode)).language());
0743         const QString labelText = languageName.isEmpty() ? name : i18nc("Holiday region, region language", "%1 (%2)", name, languageName);
0744         regionsMap.insert(labelText, regionCode);
0745     }
0746 
0747     mHolidays->addItem(i18nc("No holiday region", "None"), QString());
0748     for (QMapIterator<QString, QString> it(regionsMap);  it.hasNext();  )
0749     {
0750         it.next();
0751         mHolidays->addItem(it.key(), it.value());
0752     }
0753 
0754     if (KernelWakeAlarm::isAvailable())
0755     {
0756         // Wake from suspend in advance of alarm time
0757         itemBox = new QHBoxLayout();
0758         itemBox->setContentsMargins(marLeft, 0, marRight, 0);
0759         topLayout()->addLayout(itemBox);
0760 
0761         widget = new QWidget;   // this is to control the QWhatsThis text display area
0762         itemBox->addWidget(widget);
0763         box = new QHBoxLayout(widget);
0764         box->setContentsMargins(0, 0, 0, 0);
0765         label = new QLabel(i18nc("@label:spinbox How long before alarm to wake system from suspend", "Wake from suspend before alarm:"));
0766         addAlignedLabel(label);
0767         box->addWidget(label);
0768         mPreWakeSuspend = new SpinBox;
0769         mPreWakeSuspend->setMaximum(10);   // 10 minutes maximum
0770         box->addWidget(mPreWakeSuspend);
0771         label->setBuddy(mPreWakeSuspend);
0772         widget->setWhatsThis(i18nc("@info:whatsthis",
0773               "Enter how many minutes before the alarm trigger time to wake the system, for alarms set to wake from suspend. This can be used to ensure that the system is fully restored by the time the alarm triggers."));
0774         label = new QLabel(i18nc("@label Time unit for user entered number", "minutes"));
0775         box->addWidget(label);
0776         itemBox->addStretch();
0777     }
0778 
0779     // Start-of-day time
0780     itemBox = new QHBoxLayout();
0781     itemBox->setContentsMargins(marLeft, 0, marRight, 0);
0782     topLayout()->addLayout(itemBox);
0783 
0784     widget = new QWidget;   // this is to control the QWhatsThis text display area
0785     itemBox->addWidget(widget);
0786     box = new QHBoxLayout(widget);
0787     box->setContentsMargins(0, 0, 0, 0);
0788     label = new QLabel(i18nc("@label:spinbox", "Start of day for date-only alarms:"));
0789     addAlignedLabel(label);
0790     box->addWidget(label);
0791     mStartOfDay = new TimeEdit();
0792     box->addWidget(mStartOfDay);
0793     label->setBuddy(mStartOfDay);
0794     widget->setWhatsThis(xi18nc("@info:whatsthis",
0795           "<para>The earliest time of day at which a date-only alarm will be triggered.</para>"
0796           "<para>%1</para>", TimeSpinBox::shiftWhatsThis()));
0797     itemBox->addStretch();
0798 
0799     // Working hours
0800     QGroupBox* group = new QGroupBox(i18nc("@title:group", "Working Hours"));
0801     topLayout()->addWidget(group);
0802     QBoxLayout* layout = new QVBoxLayout(group);
0803 
0804     QWidget* daybox = new QWidget(group);   // this is to control the QWhatsThis text display area
0805     layout->addWidget(daybox);
0806     auto wgrid = new QGridLayout(daybox);
0807     wgrid->setContentsMargins(0, 0, 0, 0);
0808     const QLocale locale;
0809     for (int i = 0;  i < 7;  ++i)
0810     {
0811         int day = Locale::localeDayInWeek_to_weekDay(i);
0812         mWorkDays[i] = new QCheckBox(locale.dayName(day), daybox);
0813         wgrid->addWidget(mWorkDays[i], i/3, i%3, Qt::AlignLeft);
0814     }
0815     daybox->setWhatsThis(i18nc("@info:whatsthis", "Check the days in the week which are work days"));
0816 
0817     itemBox = new QHBoxLayout();
0818     itemBox->setContentsMargins(0, 0, 0, 0);
0819     layout->addLayout(itemBox);
0820 
0821     widget = new QWidget;  // this is to control the QWhatsThis text display area
0822     itemBox->addWidget(widget);
0823     box = new QHBoxLayout(widget);
0824     box->setContentsMargins(0, 0, 0, 0);
0825     label = new QLabel(i18nc("@label:spinbox", "Daily start time:"));
0826     addAlignedLabel(label);
0827     box->addWidget(label);
0828     mWorkStart = new TimeEdit();
0829     box->addWidget(mWorkStart);
0830     label->setBuddy(mWorkStart);
0831     widget->setWhatsThis(xi18nc("@info:whatsthis",
0832             "<para>Enter the start time of the working day.</para>"
0833             "<para>%1</para>", TimeSpinBox::shiftWhatsThis()));
0834     itemBox->addStretch();
0835 
0836     itemBox = new QHBoxLayout();
0837     itemBox->setContentsMargins(0, 0, 0, 0);
0838     layout->addLayout(itemBox);
0839 
0840     widget = new QWidget;   // this is to control the QWhatsThis text display area
0841     itemBox->addWidget(widget);
0842     box = new QHBoxLayout(widget);
0843     box->setContentsMargins(0, 0, 0, 0);
0844     label = new QLabel(i18nc("@label:spinbox", "Daily end time:"));
0845     addAlignedLabel(label);
0846     box->addWidget(label);
0847     mWorkEnd = new TimeEdit();
0848     box->addWidget(mWorkEnd);
0849     label->setBuddy(mWorkEnd);
0850     widget->setWhatsThis(xi18nc("@info:whatsthis",
0851           "<para>Enter the end time of the working day.</para>"
0852           "<para>%1</para>", TimeSpinBox::shiftWhatsThis()));
0853     itemBox->addStretch();
0854 
0855     // KOrganizer event duration
0856     group = new QGroupBox(i18nc("@title:group", "KOrganizer"));
0857     topLayout()->addWidget(group);
0858     layout = new QVBoxLayout(group);
0859 
0860     widget = new QWidget;   // this is to control the QWhatsThis text display area
0861     layout->addWidget(widget);
0862     box = new QHBoxLayout(widget);
0863     box->setContentsMargins(0, 0, 0, 0);
0864     label = new QLabel(i18nc("@label:spinbox", "KOrganizer event duration:"));
0865     addAlignedLabel(label);
0866     box->addWidget(label);
0867     mKOrgEventDuration = new TimeSpinBox(0, 5999);
0868     mKOrgEventDuration->setMinimumSize(mKOrgEventDuration->sizeHint());
0869     box->addWidget(mKOrgEventDuration);
0870     widget->setWhatsThis(xi18nc("@info:whatsthis",
0871             "<para>Enter the event duration in hours and minutes, for alarms which are copied to KOrganizer.</para>"
0872             "<para>%1</para>", TimeSpinBox::shiftWhatsThis()));
0873     label->setBuddy(mKOrgEventDuration);
0874     box->addStretch();    // left adjust the controls
0875 
0876     topLayout()->addStretch();    // top adjust the widgets
0877 }
0878 
0879 void TimePrefTab::restore(bool, bool)
0880 {
0881     KADateTime::Spec timeSpec = Preferences::timeSpec();
0882     mTimeZone->setTimeZone(timeSpec.type() == KADateTime::TimeZone ? timeSpec.timeZone() : QTimeZone());
0883     const int ix = Preferences::holidays().isValid() ? mHolidays->findData(Preferences::holidays().regionCode()) : 0;
0884     mHolidays->setCurrentIndex(ix);
0885     if (mPreWakeSuspend)
0886         mPreWakeSuspend->setValue(Preferences::wakeFromSuspendAdvance());
0887     mStartOfDay->setValue(Preferences::startOfDay());
0888     mWorkStart->setValue(Preferences::workDayStart());
0889     mWorkEnd->setValue(Preferences::workDayEnd());
0890     QBitArray days = Preferences::workDays();
0891     for (int i = 0;  i < 7;  ++i)
0892     {
0893         const bool x = days.testBit(Locale::localeDayInWeek_to_weekDay(i) - 1);
0894         mWorkDays[i]->setChecked(x);
0895     }
0896     mKOrgEventDuration->setValue(Preferences::kOrgEventDuration());
0897 }
0898 
0899 bool TimePrefTab::apply(bool syncToDisc)
0900 {
0901     Preferences::setTimeSpec(mTimeZone->timeZone());
0902     const QString hol = mHolidays->itemData(mHolidays->currentIndex()).toString();
0903     if (hol != Preferences::holidays().regionCode())
0904         Preferences::setHolidayRegion(hol);
0905     if (mPreWakeSuspend)
0906     {
0907         unsigned t = static_cast<unsigned>(mPreWakeSuspend->value());
0908         if (t != Preferences::wakeFromSuspendAdvance())
0909             Preferences::setWakeFromSuspendAdvance(t);
0910     }
0911     int t = mStartOfDay->value();
0912     const QTime sodt(t/60, t%60, 0);
0913     if (sodt != Preferences::startOfDay())
0914         Preferences::setStartOfDay(sodt);
0915     t = mWorkStart->value();
0916     Preferences::setWorkDayStart(QTime(t/60, t%60, 0));
0917     t = mWorkEnd->value();
0918     Preferences::setWorkDayEnd(QTime(t/60, t%60, 0));
0919     QBitArray workDays(7);
0920     for (int i = 0;  i < 7;  ++i)
0921         if (mWorkDays[i]->isChecked())
0922             workDays.setBit(Locale::localeDayInWeek_to_weekDay(i) - 1, true);
0923     Preferences::setWorkDays(workDays);
0924     Preferences::setKOrgEventDuration(mKOrgEventDuration->value());
0925     t = mKOrgEventDuration->value();
0926     if (t != Preferences::kOrgEventDuration())
0927         Preferences::setKOrgEventDuration(t);
0928     return PrefsTabBase::apply(syncToDisc);
0929 }
0930 
0931 
0932 /*=============================================================================
0933 = Class StorePrefTab
0934 =============================================================================*/
0935 
0936 StorePrefTab::StorePrefTab(StackedScrollGroup* scrollGroup)
0937     : PrefsTabBase(scrollGroup)
0938 {
0939     // Which resource to save to
0940     QGroupBox* group = new QGroupBox(i18nc("@title:group", "New Alarms && Templates"));
0941     topLayout()->addWidget(group);
0942     auto bgroup = new QButtonGroup(group);
0943     QBoxLayout* layout = new QVBoxLayout(group);
0944 
0945     mDefaultResource = new QRadioButton(i18nc("@option:radio", "Store in default calendar"), group);
0946     bgroup->addButton(mDefaultResource);
0947     mDefaultResource->setWhatsThis(i18nc("@info:whatsthis", "Add all new alarms and alarm templates to the default calendars, without prompting."));
0948     layout->addWidget(mDefaultResource, 0, Qt::AlignLeft);
0949     mAskResource = new QRadioButton(i18nc("@option:radio", "Prompt for which calendar to store in"), group);
0950     bgroup->addButton(mAskResource);
0951     mAskResource->setWhatsThis(xi18nc("@info:whatsthis",
0952           "<para>When saving a new alarm or alarm template, prompt for which calendar to store it in, if there is more than one active calendar.</para>"
0953           "<para>Note that archived alarms are always stored in the default archived alarm calendar.</para>"));
0954     layout->addWidget(mAskResource, 0, Qt::AlignLeft);
0955 
0956     // Archived alarms
0957     group = new QGroupBox(i18nc("@title:group", "Archived Alarms"));
0958     topLayout()->addWidget(group);
0959     auto grid = new QGridLayout(group);
0960     grid->setColumnStretch(1, 1);
0961     grid->setColumnMinimumWidth(0, gridIndentWidth());
0962     mKeepArchived = new QCheckBox(i18nc("@option:check", "Keep alarms after expiry"), group);
0963     connect(mKeepArchived, &QAbstractButton::toggled, this, &StorePrefTab::slotArchivedToggled);
0964     mKeepArchived->setWhatsThis(
0965           i18nc("@info:whatsthis", "Check to archive alarms after expiry or deletion (except deleted alarms which were never triggered)."));
0966     grid->addWidget(mKeepArchived, 0, 0, 1, 2, Qt::AlignLeft);
0967 
0968     QWidget* widget = new QWidget;
0969     auto box = new QHBoxLayout(widget);
0970     box->setContentsMargins(0, 0, 0, 0);
0971     mPurgeArchived = new QCheckBox(i18nc("@option:check", "Discard archived alarms after:"));
0972     mPurgeArchived->setMinimumSize(mPurgeArchived->sizeHint());
0973     box->addWidget(mPurgeArchived);
0974     connect(mPurgeArchived, &QAbstractButton::toggled, this, &StorePrefTab::slotArchivedToggled);
0975     mPurgeAfter = new SpinBox;
0976     mPurgeAfter->setMinimum(1);
0977     mPurgeAfter->setSingleShiftStep(10);
0978     mPurgeAfter->setMinimumSize(mPurgeAfter->sizeHint());
0979     box->addWidget(mPurgeAfter);
0980     mPurgeAfterLabel = new QLabel(i18nc("@label Time unit for user-entered number", "days"));
0981     mPurgeAfterLabel->setMinimumSize(mPurgeAfterLabel->sizeHint());
0982     mPurgeAfterLabel->setBuddy(mPurgeAfter);
0983     box->addWidget(mPurgeAfterLabel);
0984     widget->setWhatsThis(i18nc("@info:whatsthis", "Uncheck to store archived alarms indefinitely. Check to enter how long archived alarms should be stored."));
0985     grid->addWidget(widget, 1, 1, Qt::AlignLeft);
0986 
0987     mClearArchived = new QPushButton(i18nc("@action:button", "Clear Archived Alarms"), group);
0988     connect(mClearArchived, &QAbstractButton::clicked, this, &StorePrefTab::slotClearArchived);
0989     mClearArchived->setWhatsThis((Resources::enabledResources(CalEvent::ARCHIVED, false).count() <= 1)
0990             ? i18nc("@info:whatsthis", "Delete all existing archived alarms.")
0991             : i18nc("@info:whatsthis", "Delete all existing archived alarms (from the default archived alarm calendar only)."));
0992     grid->addWidget(mClearArchived, 2, 1, Qt::AlignLeft);
0993 
0994     topLayout()->addStretch();    // top adjust the widgets
0995 }
0996 
0997 void StorePrefTab::restore(bool defaults, bool)
0998 {
0999     mCheckKeepChanges = defaults;
1000     if (Preferences::askResource())
1001         mAskResource->setChecked(true);
1002     else
1003         mDefaultResource->setChecked(true);
1004     const int keepDays = Preferences::archivedKeepDays();
1005     if (!defaults)
1006         mOldKeepArchived = keepDays;
1007     setArchivedControls(keepDays);
1008     mCheckKeepChanges = true;
1009 }
1010 
1011 bool StorePrefTab::apply(bool syncToDisc)
1012 {
1013     const bool b = mAskResource->isChecked();
1014     if (b != Preferences::askResource())
1015         Preferences::setAskResource(mAskResource->isChecked());
1016     const int days = !mKeepArchived->isChecked() ? 0 : mPurgeArchived->isChecked() ? mPurgeAfter->value() : -1;
1017     if (days != Preferences::archivedKeepDays())
1018         Preferences::setArchivedKeepDays(days);
1019     return PrefsTabBase::apply(syncToDisc);
1020 }
1021 
1022 void StorePrefTab::setArchivedControls(int purgeDays)
1023 {
1024     mKeepArchived->setChecked(purgeDays);
1025     mPurgeArchived->setChecked(purgeDays > 0);
1026     mPurgeAfter->setValue(purgeDays > 0 ? purgeDays : 0);
1027     slotArchivedToggled(true);
1028 }
1029 
1030 void StorePrefTab::slotArchivedToggled(bool)
1031 {
1032     const bool keep = mKeepArchived->isChecked();
1033     if (keep  &&  !mOldKeepArchived  &&  mCheckKeepChanges
1034     &&  !Resources::getStandard(CalEvent::ARCHIVED, true).isValid())
1035     {
1036         KAMessageBox::error(topLayout()->parentWidget(),
1037              xi18nc("@info", "<para>A default calendar is required in order to archive alarms, but none is currently enabled.</para>"
1038                   "<para>If you wish to keep expired alarms, please first use the calendars view to select a default "
1039                   "archived alarms calendar.</para>"));
1040         mKeepArchived->setChecked(false);
1041         return;
1042     }
1043     mOldKeepArchived = keep;
1044     mPurgeArchived->setEnabled(keep);
1045     mPurgeAfter->setEnabled(keep && mPurgeArchived->isChecked());
1046     mPurgeAfterLabel->setEnabled(keep);
1047     mClearArchived->setEnabled(keep);
1048 }
1049 
1050 void StorePrefTab::slotClearArchived()
1051 {
1052     const bool single = Resources::enabledResources(CalEvent::ARCHIVED, false).count() <= 1;
1053     if (KAMessageBox::warningContinueCancel(topLayout()->parentWidget(),
1054                                             single ? i18nc("@info", "Do you really want to delete all archived alarms?")
1055                                                    : i18nc("@info", "Do you really want to delete all alarms in the default archived alarm calendar?"))
1056             != KMessageBox::Continue)
1057         return;
1058     theApp()->purgeAll();
1059 }
1060 
1061 
1062 /*=============================================================================
1063 = Class EmailPrefTab
1064 =============================================================================*/
1065 
1066 EmailPrefTab::EmailPrefTab(StackedScrollGroup* scrollGroup)
1067     : PrefsTabBase(scrollGroup)
1068 {
1069     QWidget* widget = new QWidget;
1070     topLayout()->addWidget(widget);
1071     auto box = new QHBoxLayout(widget);
1072     box->setSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
1073     QLabel* label = new QLabel(i18nc("@label", "Email client:"));
1074     box->addWidget(label);
1075     mEmailClient = new ButtonGroup(widget);
1076     const QString kmailOption = i18nc("@option:radio", "KMail");
1077     const QString sendmailOption = i18nc("@option:radio", "Sendmail");
1078     if (PluginManager::instance()->akonadiPlugin())
1079     {
1080         mKMailButton = new RadioButton(kmailOption);
1081         mKMailButton->setMinimumSize(mKMailButton->sizeHint());
1082         box->addWidget(mKMailButton);
1083         mEmailClient->addButton(mKMailButton, Preferences::kmail);
1084     }
1085     mSendmailButton = new RadioButton(sendmailOption);
1086     mSendmailButton->setMinimumSize(mSendmailButton->sizeHint());
1087     box->addWidget(mSendmailButton);
1088     mEmailClient->addButton(mSendmailButton, Preferences::sendmail);
1089     connect(mEmailClient, &ButtonGroup::buttonSet, this, &EmailPrefTab::slotEmailClientChanged);
1090     const QString sendText = i18nc("@info", "Choose how to send email when an email alarm is triggered.");
1091     if (PluginManager::instance()->akonadiPlugin())
1092         widget->setWhatsThis(xi18nc("@info:whatsthis",
1093             "<para>%1<list><item><interface>%2</interface>: "
1094             "The email is sent automatically via <application>KMail</application>.</item>"
1095             "<item><interface>%3</interface>: "
1096             "The email is sent automatically. This option will only work if your system is "
1097             "configured to use <application>sendmail</application> or a sendmail compatible mail transport agent.</item></list></para>",
1098                                     sendText, kmailOption, sendmailOption));
1099     else
1100         widget->setWhatsThis(xi18nc("@info:whatsthis",
1101             "<para>%1<list><item><interface>%2</interface>: "
1102             "The email is sent automatically. This option will only work if your system is "
1103             "configured to use <application>sendmail</application> or a sendmail compatible mail transport agent.</item></list></para>",
1104                                     sendText, sendmailOption));
1105 
1106     if (PluginManager::instance()->akonadiPlugin())
1107     {
1108         widget = new QWidget;  // this is to allow left adjustment
1109         topLayout()->addWidget(widget);
1110         box = new QHBoxLayout(widget);
1111         mEmailCopyToKMail = new QCheckBox(xi18nc("@option:check", "Copy sent emails into <application>KMail</application>'s <resource>%1</resource> folder", KAMail::i18n_sent_mail()));
1112         mEmailCopyToKMail->setWhatsThis(xi18nc("@info:whatsthis", "After sending an email, store a copy in <application>KMail</application>'s <resource>%1</resource> folder", KAMail::i18n_sent_mail()));
1113         box->addWidget(mEmailCopyToKMail);
1114         box->addStretch();    // left adjust the controls
1115     }
1116 
1117     widget = new QWidget;   // this is to allow left adjustment
1118     topLayout()->addWidget(widget);
1119     box = new QHBoxLayout(widget);
1120     mEmailQueuedNotify = new QCheckBox(i18nc("@option:check", "Notify when remote emails are queued"));
1121     mEmailQueuedNotify->setWhatsThis(
1122           i18nc("@info:whatsthis", "Display a notification message whenever an email alarm has queued an email for sending to a remote system. "
1123                "This could be useful if, for example, you have a dial-up connection, so that you can then ensure that the email is actually transmitted."));
1124     box->addWidget(mEmailQueuedNotify);
1125     box->addStretch();    // left adjust the controls
1126 
1127     // Your Email Address group box
1128     QGroupBox* group = new QGroupBox(i18nc("@title:group", "Your Email Address"));
1129     topLayout()->addWidget(group);
1130     auto grid = new QGridLayout(group);
1131     grid->setColumnStretch(2, 1);
1132 
1133     // 'From' email address controls ...
1134     label = new Label(i18nc("@label 'From' email address", "From:"), group);
1135     grid->addWidget(label, 1, 0);
1136     mFromAddressGroup = new ButtonGroup(group);
1137     connect(mFromAddressGroup, &ButtonGroup::buttonSet, this, &EmailPrefTab::slotFromAddrChanged);
1138 
1139     // Line edit to enter a 'From' email address
1140     mFromAddrButton = new RadioButton(group);
1141     mFromAddressGroup->addButton(mFromAddrButton, Preferences::MAIL_FROM_ADDR);
1142     label->setBuddy(mFromAddrButton);
1143     grid->addWidget(mFromAddrButton, 1, 1);
1144     mEmailAddress = new QLineEdit(group);
1145     connect(mEmailAddress, &QLineEdit::textChanged, this, &EmailPrefTab::slotAddressChanged);
1146     QString whatsThis = i18nc("@info:whatsthis", "Your email address, used to identify you as the sender when sending email alarms.");
1147     mFromAddrButton->setWhatsThis(whatsThis);
1148     mEmailAddress->setWhatsThis(whatsThis);
1149     mFromAddrButton->setFocusWidget(mEmailAddress);
1150     grid->addWidget(mEmailAddress, 1, 2);
1151 
1152     // 'From' email address to be taken from KMail
1153     mFromCCentreButton = new RadioButton(xi18nc("@option:radio", "Use default <application>KMail</application> identity"), group);
1154     mFromAddressGroup->addButton(mFromCCentreButton, Preferences::MAIL_FROM_SYS_SETTINGS);
1155     mFromCCentreButton->setWhatsThis(
1156           xi18nc("@info:whatsthis", "Check to use the default email address set in <application>KMail</application>, to identify you as the sender when sending email alarms."));
1157     grid->addWidget(mFromCCentreButton, 2, 1, 1, 2, Qt::AlignLeft);
1158 
1159     // 'From' email address to be picked from KMail's identities when the email alarm is configured
1160     mFromKMailButton = new RadioButton(xi18nc("@option:radio", "Use <application>KMail</application> identities"), group);
1161     mFromAddressGroup->addButton(mFromKMailButton, Preferences::MAIL_FROM_KMAIL);
1162     mFromKMailButton->setWhatsThis(
1163           xi18nc("@info:whatsthis", "Check to use <application>KMail</application>'s email identities to identify you as the sender when sending email alarms. "
1164                "For existing email alarms, <application>KMail</application>'s default identity will be used. "
1165                "For new email alarms, you will be able to pick which of <application>KMail</application>'s identities to use."));
1166     grid->addWidget(mFromKMailButton, 3, 1, 1, 2, Qt::AlignLeft);
1167 
1168     // 'Bcc' email address controls ...
1169     grid->setRowMinimumHeight(4, style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
1170     label = new Label(i18nc("@label 'Bcc' email address", "Bcc:"), group);
1171     grid->addWidget(label, 5, 0);
1172     mBccAddressGroup = new ButtonGroup(group);
1173     connect(mBccAddressGroup, &ButtonGroup::buttonSet, this, &EmailPrefTab::slotBccAddrChanged);
1174 
1175     // Line edit to enter a 'Bcc' email address
1176     mBccAddrButton = new RadioButton(group);
1177     mBccAddressGroup->addButton(mBccAddrButton, Preferences::MAIL_FROM_ADDR);
1178     label->setBuddy(mBccAddrButton);
1179     grid->addWidget(mBccAddrButton, 5, 1);
1180     mEmailBccAddress = new QLineEdit(group);
1181     whatsThis = xi18nc("@info:whatsthis", "Your email address, used for blind copying email alarms to yourself. "
1182                      "If you want blind copies to be sent to your account on the computer which <application>KAlarm</application> runs on, you can simply enter your user login name.");
1183     mBccAddrButton->setWhatsThis(whatsThis);
1184     mEmailBccAddress->setWhatsThis(whatsThis);
1185     mBccAddrButton->setFocusWidget(mEmailBccAddress);
1186     grid->addWidget(mEmailBccAddress, 5, 2);
1187 
1188     // 'Bcc' email address to be taken from KMail
1189     mBccCCentreButton = new RadioButton(xi18nc("@option:radio", "Use default <application>KMail</application> identity"), group);
1190     mBccAddressGroup->addButton(mBccCCentreButton, Preferences::MAIL_FROM_SYS_SETTINGS);
1191     mBccCCentreButton->setWhatsThis(
1192           xi18nc("@info:whatsthis", "Check to use the default email address set in <application>KMail</application>, for blind copying email alarms to yourself."));
1193     grid->addWidget(mBccCCentreButton, 6, 1, 1, 2, Qt::AlignLeft);
1194 
1195     topLayout()->addStretch();    // top adjust the widgets
1196 }
1197 
1198 void EmailPrefTab::restore(bool defaults, bool)
1199 {
1200     mEmailClient->setButton(Preferences::emailClient());
1201     if (mEmailCopyToKMail)
1202         mEmailCopyToKMail->setChecked(Preferences::emailCopyToKMail());
1203     setEmailAddress(Preferences::emailFrom(), Preferences::emailAddress());
1204     setEmailBccAddress((Preferences::emailBccFrom() == Preferences::MAIL_FROM_SYS_SETTINGS), Preferences::emailBccAddress());
1205     mEmailQueuedNotify->setChecked(Preferences::emailQueuedNotify());
1206     if (!defaults)
1207         mAddressChanged = mBccAddressChanged = false;
1208 }
1209 
1210 bool EmailPrefTab::apply(bool syncToDisc)
1211 {
1212     const int client = mEmailClient->selectedId();
1213     if (client >= 0  &&  static_cast<Preferences::MailClient>(client) != Preferences::emailClient())
1214         Preferences::setEmailClient(static_cast<Preferences::MailClient>(client));
1215     if (mEmailCopyToKMail)
1216     {
1217         bool b = mEmailCopyToKMail->isChecked();
1218         if (b != Preferences::emailCopyToKMail())
1219             Preferences::setEmailCopyToKMail(b);
1220     }
1221     int from = mFromAddressGroup->selectedId();
1222     QString text = mEmailAddress->text().trimmed();
1223     if ((from >= 0  &&  static_cast<Preferences::MailFrom>(from) != Preferences::emailFrom())
1224     ||  text != Preferences::emailAddress())
1225         Preferences::setEmailAddress(static_cast<Preferences::MailFrom>(from), text);
1226     bool b = (mBccAddressGroup->checkedButton() == mBccCCentreButton);
1227     Preferences::MailFrom bfrom = b ? Preferences::MAIL_FROM_SYS_SETTINGS : Preferences::MAIL_FROM_ADDR;;
1228     text = mEmailBccAddress->text().trimmed();
1229     if (bfrom != Preferences::emailBccFrom()  ||  text != Preferences::emailBccAddress())
1230         Preferences::setEmailBccAddress(b, text);
1231     b = mEmailQueuedNotify->isChecked();
1232     if (b != Preferences::emailQueuedNotify())
1233         Preferences::setEmailQueuedNotify(mEmailQueuedNotify->isChecked());
1234     return PrefsTabBase::apply(syncToDisc);
1235 }
1236 
1237 void EmailPrefTab::showEvent(QShowEvent* e)
1238 {
1239     bool enable = Preferences::useAkonadiIfAvailable();
1240     if (mKMailButton)
1241     {
1242         mKMailButton->setEnabled(enable);
1243         if (!enable)
1244             mEmailClient->setButton(Preferences::sendmail);
1245     }
1246     if (mEmailCopyToKMail)
1247         mEmailCopyToKMail->setEnabled(enable);
1248 
1249     PrefsTabBase::showEvent(e);
1250 }
1251 
1252 void EmailPrefTab::setEmailAddress(Preferences::MailFrom from, const QString& address)
1253 {
1254     mFromAddressGroup->setButton(from);
1255     mEmailAddress->setText(from == Preferences::MAIL_FROM_ADDR ? address.trimmed() : QString());
1256 }
1257 
1258 void EmailPrefTab::setEmailBccAddress(bool useSystemSettings, const QString& address)
1259 {
1260     mBccAddressGroup->setButton(useSystemSettings ? Preferences::MAIL_FROM_SYS_SETTINGS : Preferences::MAIL_FROM_ADDR);
1261     mEmailBccAddress->setText(useSystemSettings ? QString() : address.trimmed());
1262 }
1263 
1264 void EmailPrefTab::slotEmailClientChanged(QAbstractButton* button)
1265 {
1266     if (mEmailCopyToKMail)
1267         mEmailCopyToKMail->setEnabled(button == mSendmailButton  &&  Preferences::useAkonadiIfAvailable());
1268 }
1269 
1270 void EmailPrefTab::slotFromAddrChanged(QAbstractButton* button)
1271 {
1272     mEmailAddress->setEnabled(button == mFromAddrButton);
1273     mAddressChanged = true;
1274 }
1275 
1276 void EmailPrefTab::slotBccAddrChanged(QAbstractButton* button)
1277 {
1278     mEmailBccAddress->setEnabled(button == mBccAddrButton);
1279     mBccAddressChanged = true;
1280 }
1281 
1282 QString EmailPrefTab::validate()
1283 {
1284     if (mAddressChanged)
1285     {
1286         mAddressChanged = false;
1287         QString errmsg = validateAddr(mFromAddressGroup, mEmailAddress, KAMail::i18n_NeedFromEmailAddress());
1288         if (!errmsg.isEmpty())
1289             return errmsg;
1290     }
1291     if (mBccAddressChanged)
1292     {
1293         mBccAddressChanged = false;
1294         return validateAddr(mBccAddressGroup, mEmailBccAddress, i18nc("@info", "No valid 'Bcc' email address is specified."));
1295     }
1296     return {};
1297 }
1298 
1299 QString EmailPrefTab::validateAddr(ButtonGroup* group, QLineEdit* addr, const QString& msg)
1300 {
1301     QString errmsg = i18nc("@info", "Are you sure you want to save your changes?");
1302     switch (group->selectedId())
1303     {
1304         case Preferences::MAIL_FROM_SYS_SETTINGS:
1305             if (!KAMail::controlCentreAddress().isEmpty())
1306                 return {};
1307             errmsg = xi18nc("@info", "<para>No default email address is currently set in <application>KMail</application>.</para><para>%1</para><para>%2</para>", msg, errmsg);
1308             break;
1309         case Preferences::MAIL_FROM_KMAIL:
1310             if (Identities::identitiesExist())
1311                 return {};
1312             errmsg = xi18nc("@info", "<para>No <application>KMail</application> identities currently exist.</para><para>%1</para><para>%2</para>", msg, errmsg);
1313             break;
1314         case Preferences::MAIL_FROM_ADDR:
1315             if (!addr->text().trimmed().isEmpty())
1316                 return {};
1317             break;
1318     }
1319     return errmsg;
1320 }
1321 
1322 
1323 /*=============================================================================
1324 = Class EditPrefTab
1325 =============================================================================*/
1326 
1327 EditPrefTab::EditPrefTab(StackedScrollGroup* scrollGroup)
1328     : PrefsTabBase(scrollGroup)
1329 {
1330     KLocalizedString defsetting = kxi18nc("@info:whatsthis", "The default setting for <interface>%1</interface> in the alarm edit dialog.");
1331 
1332     mTabs = new QTabWidget();
1333     mTabs->setDocumentMode(true);
1334     topLayout()->addWidget(mTabs);
1335     auto tabgroup = new StackedGroup(mTabs);
1336 
1337     auto topGeneral = new StackedGroupWidget(tabgroup);
1338     auto tgLayout = new QVBoxLayout(topGeneral);
1339     mTabGeneral = mTabs->addTab(topGeneral, i18nc("@title:tab", "General"));
1340 
1341     auto topTypes = new StackedGroupWidget(tabgroup);
1342     auto ttLayout = new QVBoxLayout(topTypes);
1343     ttLayout->setContentsMargins(0, style()->pixelMetric(QStyle::PM_LayoutTopMargin),
1344                                  0, style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
1345     mTabTypes = mTabs->addTab(topTypes, i18nc("@title:tab Settings specific to alarm type", "Type Specific"));
1346 
1347     auto topFontColour = new StackedGroupWidget(tabgroup);
1348     auto tfLayout = new QVBoxLayout(topFontColour);
1349     tfLayout->setContentsMargins(0, style()->pixelMetric(QStyle::PM_LayoutTopMargin),
1350                                  0, style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
1351     mTabFontColour = mTabs->addTab(topFontColour, i18nc("@title:tab", "Font && Color"));
1352 
1353     // MISCELLANEOUS
1354     // Alarm message display method
1355     QWidget* widget = new QWidget;   // this is to control the QWhatsThis text display area
1356     tgLayout->addWidget(widget);
1357     auto box = new QHBoxLayout(widget);
1358     box->setContentsMargins(0, 0, 0, 0);
1359     QLabel* label = new QLabel(EditDisplayAlarmDlg::i18n_lbl_DisplayMethod());
1360     box->addWidget(label);
1361     mDisplayMethod = new QComboBox();
1362     mDisplayMethod->addItem(EditDisplayAlarmDlg::i18n_combo_Window());
1363     mDisplayMethod->addItem(EditDisplayAlarmDlg::i18n_combo_Notify());
1364     box->addWidget(mDisplayMethod);
1365     box->addStretch();
1366     label->setBuddy(mDisplayMethod);
1367     widget->setWhatsThis(i18nc("@info:whatsthis", "The default setting for the alarm message display method in the alarm edit dialog."));
1368 
1369     // Show in KOrganizer
1370     mCopyToKOrganizer = new QCheckBox(EditAlarmDlg::i18n_chk_ShowInKOrganizer());
1371     mCopyToKOrganizer->setMinimumSize(mCopyToKOrganizer->sizeHint());
1372     mCopyToKOrganizer->setWhatsThis(defsetting.subs(EditAlarmDlg::i18n_chk_ShowInKOrganizer()).toString());
1373     tgLayout->addWidget(mCopyToKOrganizer);
1374 
1375     // Late cancellation
1376     widget = new QWidget;
1377     tgLayout->addWidget(widget);
1378     box = new QHBoxLayout(widget);
1379     box->setContentsMargins(0, 0, 0, 0);
1380     box->setSpacing(0);
1381     mLateCancel = new QCheckBox(LateCancelSelector::i18n_chk_CancelIfLate());
1382     mLateCancel->setMinimumSize(mLateCancel->sizeHint());
1383     mLateCancel->setWhatsThis(defsetting.subs(LateCancelSelector::i18n_chk_CancelIfLate()).toString());
1384     box->addWidget(mLateCancel);
1385 
1386     // Recurrence
1387     widget = new QWidget;   // this is to control the QWhatsThis text display area
1388     tgLayout->addWidget(widget);
1389     box = new QHBoxLayout(widget);
1390     box->setContentsMargins(0, 0, 0, 0);
1391     label = new QLabel(i18nc("@label:listbox", "Recurrence:"));
1392     box->addWidget(label);
1393     mRecurPeriod = new ComboBox();
1394     mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_NoRecur());
1395     mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_AtLogin());
1396     mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_HourlyMinutely());
1397     mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_Daily());
1398     mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_Weekly());
1399     mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_Monthly());
1400     mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_Yearly());
1401     box->addWidget(mRecurPeriod);
1402     box->addStretch();
1403     label->setBuddy(mRecurPeriod);
1404     widget->setWhatsThis(i18nc("@info:whatsthis", "The default setting for the recurrence rule in the alarm edit dialog."));
1405 
1406     // How to handle February 29th in yearly recurrences
1407     QWidget* febBox = new QWidget;  // this is to control the QWhatsThis text display area
1408     tgLayout->addWidget(febBox);
1409     auto vbox = new QVBoxLayout(febBox);
1410     vbox->setContentsMargins(0, 0, 0, 0);
1411     label = new QLabel(i18nc("@label", "In non-leap years, repeat yearly February 29th alarms on:"));
1412     // *** Workaround for the bug that QLabel doesn't align correctly in right-to-left mode. ***
1413     label->setAlignment((layoutDirection() == Qt::LeftToRight ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignAbsolute);
1414     label->setWordWrap(true);
1415     vbox->addWidget(label);
1416     box = new QHBoxLayout();
1417     vbox->addLayout(box);
1418     vbox->setContentsMargins(0, 0, 0, 0);
1419     box->setSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
1420     mFeb29 = new ButtonGroup(febBox);
1421     widget = new QWidget();
1422     widget->setFixedWidth(3 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
1423     box->addWidget(widget);
1424     QRadioButton* radio = new QRadioButton(i18nc("@option:radio", "February 28th"));
1425     radio->setMinimumSize(radio->sizeHint());
1426     box->addWidget(radio);
1427     mFeb29->addButton(radio, Preferences::Feb29_Feb28);
1428     radio = new QRadioButton(i18nc("@option:radio", "March 1st"));
1429     radio->setMinimumSize(radio->sizeHint());
1430     box->addWidget(radio);
1431     mFeb29->addButton(radio, Preferences::Feb29_Mar1);
1432     radio = new QRadioButton(i18nc("@option:radio", "Do not repeat"));
1433     radio->setMinimumSize(radio->sizeHint());
1434     box->addWidget(radio);
1435     mFeb29->addButton(radio, Preferences::Feb29_None);
1436     febBox->setWhatsThis(xi18nc("@info:whatsthis",
1437           "For yearly recurrences, choose what date, if any, alarms due on February 29th should occur in non-leap years."
1438           "<note>The next scheduled occurrence of existing alarms is not re-evaluated when you change this setting.</note>"));
1439 
1440     tgLayout->addStretch();    // top adjust the widgets
1441 
1442     // DISPLAY ALARMS
1443     QGroupBox* group = new QGroupBox(i18nc("@title:group", "Display Alarms"));
1444     ttLayout->addWidget(group);
1445     auto vlayout = new QVBoxLayout(group);
1446 
1447     mConfirmAck = new QCheckBox(EditDisplayAlarmDlg::i18n_chk_ConfirmAck());
1448     mConfirmAck->setMinimumSize(mConfirmAck->sizeHint());
1449     mConfirmAck->setWhatsThis(defsetting.subs(EditDisplayAlarmDlg::i18n_chk_ConfirmAck()).toString());
1450     vlayout->addWidget(mConfirmAck, 0, Qt::AlignLeft);
1451 
1452     mAutoClose = new QCheckBox(LateCancelSelector::i18n_chk_AutoCloseWinLC());
1453     mAutoClose->setMinimumSize(mAutoClose->sizeHint());
1454     mAutoClose->setWhatsThis(defsetting.subs(LateCancelSelector::i18n_chk_AutoCloseWin()).toString());
1455     vlayout->addWidget(mAutoClose, 0, Qt::AlignLeft);
1456 
1457     widget = new QWidget;
1458     vlayout->addWidget(widget);
1459     box = new QHBoxLayout(widget);
1460     box->setContentsMargins(0, 0, 0, 0);
1461     label = new QLabel(i18nc("@label:listbox", "Reminder units:"));
1462     box->addWidget(label);
1463     mReminderUnits = new QComboBox();
1464     mReminderUnits->addItem(i18nc("@item:inlistbox", "Minutes"), TimePeriod::Minutes);
1465     mReminderUnits->addItem(i18nc("@item:inlistbox", "Hours/Minutes"), TimePeriod::HoursMinutes);
1466     box->addWidget(mReminderUnits);
1467     label->setBuddy(mReminderUnits);
1468     widget->setWhatsThis(i18nc("@info:whatsthis", "The default units for the reminder in the alarm edit dialog, for alarms due soon."));
1469     box->addStretch();    // left adjust the control
1470     mSpecialActionsButton = new SpecialActionsButton(true);
1471     box->addWidget(mSpecialActionsButton);
1472 
1473     // SOUND
1474     QGroupBox* bbox = new QGroupBox(i18nc("@title:group Audio options group", "Sound"));
1475     ttLayout->addWidget(bbox);
1476     vlayout = new QVBoxLayout(bbox);
1477 
1478     auto hlayout = new QHBoxLayout;
1479     hlayout->setContentsMargins(0, 0, 0, 0);
1480     vlayout->addLayout(hlayout);
1481     mSound = new QComboBox();
1482     mSound->addItem(SoundPicker::i18n_combo_None());         // index 0
1483     mSound->addItem(SoundPicker::i18n_combo_Beep());         // index 1
1484     mSound->addItem(SoundPicker::i18n_combo_File());         // index 2
1485 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT
1486     if (TextEditTextToSpeech::TextToSpeech::self()->isReady())
1487         mSound->addItem(SoundPicker::i18n_combo_Speak());    // index 3
1488 #endif
1489     mSound->setMinimumSize(mSound->sizeHint());
1490     mSound->setWhatsThis(defsetting.subs(SoundPicker::i18n_label_Sound()).toString());
1491     hlayout->addWidget(mSound);
1492     hlayout->addStretch();
1493     mSoundRepeat = new QCheckBox(i18nc("@option:check", "Repeat sound file"));
1494     mSoundRepeat->setMinimumSize(mSoundRepeat->sizeHint());
1495     mSoundRepeat->setWhatsThis(
1496           xi18nc("@info:whatsthis sound file 'Repeat' checkbox", "The default setting for sound file <interface>%1</interface> in the alarm edit dialog.", SoundWidget::i18n_chk_Repeat()));
1497     hlayout->addWidget(mSoundRepeat);
1498 
1499     widget = new QWidget;   // this is to control the QWhatsThis text display area
1500     box = new QHBoxLayout(widget);
1501     box->setContentsMargins(0, 0, 0, 0);
1502     mSoundFileLabel = new QLabel(i18nc("@label:textbox", "Sound file:"));
1503     box->addWidget(mSoundFileLabel);
1504     mSoundFile = new QLineEdit();
1505     box->addWidget(mSoundFile);
1506     mSoundFileLabel->setBuddy(mSoundFile);
1507     mSoundFileBrowse = new QPushButton();
1508     mSoundFileBrowse->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
1509     connect(mSoundFileBrowse, &QAbstractButton::clicked, this, &EditPrefTab::slotBrowseSoundFile);
1510     mSoundFileBrowse->setToolTip(i18nc("@info:tooltip", "Choose a sound file"));
1511     box->addWidget(mSoundFileBrowse);
1512     widget->setWhatsThis(i18nc("@info:whatsthis", "Enter the default sound file to use in the alarm edit dialog."));
1513     vlayout->addWidget(widget);
1514 
1515     box = new QHBoxLayout();
1516     box->setContentsMargins(0, 0, 0, 0);
1517     mSoundVolumeCheckbox = new CheckBox(SoundWidget::i18n_chk_SetVolume(), nullptr);
1518     connect(mSoundVolumeCheckbox, &CheckBox::toggled, this, [this](bool on) { mSoundVolumeSlider->setEnabled(on); });
1519     box->addWidget(mSoundVolumeCheckbox);
1520     mSoundVolumeSlider = new Slider(0, 100, 10, Qt::Horizontal);
1521     mSoundVolumeCheckbox->setWhatsThis(i18nc("@info:whatsthis", "Select to set the default volume for sound files in the alarm edit dialog."));
1522     mSoundVolumeSlider->setTickPosition(QSlider::TicksBelow);
1523     mSoundVolumeSlider->setTickInterval(10);
1524     mSoundVolumeSlider->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
1525     mSoundVolumeSlider->setWhatsThis(i18nc("@info:whatsthis", "The default volume for sound files in the alarm edit dialog."));
1526     mSoundVolumeSlider->setEnabled(false);
1527     box->addWidget(mSoundVolumeSlider);
1528     label = new QLabel;
1529     mSoundVolumeSlider->setValueLabel(label, QStringLiteral("%1%"), true);
1530     box->addWidget(label);
1531     mSoundVolumeCheckbox->setFocusWidget(mSoundVolumeSlider);
1532     vlayout->addLayout(box);
1533 
1534     // COMMAND ALARMS
1535     group = new QGroupBox(i18nc("@title:group", "Command Alarms"));
1536     ttLayout->addWidget(group);
1537     vlayout = new QVBoxLayout(group);
1538     hlayout = new QHBoxLayout();
1539     hlayout->setContentsMargins(0, 0, 0, 0);
1540     vlayout->addLayout(hlayout);
1541 
1542     mCmdScript = new QCheckBox(EditCommandAlarmDlg::i18n_chk_EnterScript(), group);
1543     mCmdScript->setMinimumSize(mCmdScript->sizeHint());
1544     mCmdScript->setWhatsThis(defsetting.subs(EditCommandAlarmDlg::i18n_chk_EnterScript()).toString());
1545     hlayout->addWidget(mCmdScript);
1546     hlayout->addStretch();
1547 
1548     mCmdXterm = new QCheckBox(EditCommandAlarmDlg::i18n_chk_ExecInTermWindow(), group);
1549     mCmdXterm->setMinimumSize(mCmdXterm->sizeHint());
1550     mCmdXterm->setWhatsThis(defsetting.subs(EditCommandAlarmDlg::i18n_radio_ExecInTermWindow()).toString());
1551     hlayout->addWidget(mCmdXterm);
1552 
1553     // EMAIL ALARMS
1554     group = new QGroupBox(i18nc("@title:group", "Email Alarms"));
1555     ttLayout->addWidget(group);
1556     vlayout = new QVBoxLayout(group);
1557 
1558     // BCC email to sender
1559     mEmailBcc = new QCheckBox(EditEmailAlarmDlg::i18n_chk_CopyEmailToSelf(), group);
1560     mEmailBcc->setMinimumSize(mEmailBcc->sizeHint());
1561     mEmailBcc->setWhatsThis(defsetting.subs(EditEmailAlarmDlg::i18n_chk_CopyEmailToSelf()).toString());
1562     vlayout->addWidget(mEmailBcc, 0, Qt::AlignLeft);
1563 
1564     ttLayout->addStretch();
1565 
1566     // FONT / COLOUR TAB
1567     mFontChooser = new FontColourChooser(topFontColour, QStringList(), i18nc("@title:group", "Message Font && Color"), true);
1568     tfLayout->addWidget(mFontChooser);
1569     tfLayout->setContentsMargins(0, 0, 0, 0);
1570 }
1571 
1572 void EditPrefTab::restore(bool, bool allTabs)
1573 {
1574     int index;
1575     if (allTabs  ||  mTabs->currentIndex() == mTabGeneral)
1576     {
1577         mDisplayMethod->setCurrentIndex(Preferences::defaultDisplayMethod() == Preferences::Display_Window ? 0 : 1);
1578         mCopyToKOrganizer->setChecked(Preferences::defaultCopyToKOrganizer());
1579         mLateCancel->setChecked(Preferences::defaultLateCancel());
1580         switch (Preferences::defaultRecurPeriod())
1581         {
1582             case Preferences::Recur_Yearly:   index = 6; break;
1583             case Preferences::Recur_Monthly:  index = 5; break;
1584             case Preferences::Recur_Weekly:   index = 4; break;
1585             case Preferences::Recur_Daily:    index = 3; break;
1586             case Preferences::Recur_SubDaily: index = 2; break;
1587             case Preferences::Recur_Login:    index = 1; break;
1588             case Preferences::Recur_None:
1589             default:                          index = 0; break;
1590         }
1591         mRecurPeriod->setCurrentIndex(index);
1592         mFeb29->setButton(Preferences::defaultFeb29Type());
1593     }
1594     if (allTabs  ||  mTabs->currentIndex() == mTabTypes)
1595     {
1596         mConfirmAck->setChecked(Preferences::defaultConfirmAck());
1597         mAutoClose->setChecked(Preferences::defaultAutoClose());
1598         switch (Preferences::defaultReminderUnits())
1599         {
1600             case TimePeriod::Weeks:        index = 3; break;
1601             case TimePeriod::Days:         index = 2; break;
1602             default:
1603             case TimePeriod::HoursMinutes: index = 1; break;
1604             case TimePeriod::Minutes:      index = 0; break;
1605         }
1606         mReminderUnits->setCurrentIndex(index);
1607         KAEvent::ExtraActionOptions opts{};
1608         if (Preferences::defaultExecPreActionOnDeferral())
1609             opts |= KAEvent::ExecPreActOnDeferral;
1610         if (Preferences::defaultCancelOnPreActionError())
1611             opts |= KAEvent::CancelOnPreActError;
1612         if (Preferences::defaultDontShowPreActionError())
1613             opts |= KAEvent::DontShowPreActError;
1614         mSpecialActionsButton->setActions(Preferences::defaultPreAction(), Preferences::defaultPostAction(), opts);
1615         mSound->setCurrentIndex(soundIndex(Preferences::defaultSoundType()));
1616         mSoundFile->setText(Preferences::defaultSoundFile());
1617         mSoundRepeat->setChecked(Preferences::defaultSoundRepeat());
1618         const int volume = Preferences::defaultSoundVolume() * 100;
1619         mSoundVolumeCheckbox->setChecked(volume >= 0);
1620         mSoundVolumeSlider->setValue(volume >= 0 ? volume : 100);
1621         mCmdScript->setChecked(Preferences::defaultCmdScript());
1622         mCmdXterm->setChecked(Preferences::defaultCmdLogType() == Preferences::Log_Terminal);
1623         mEmailBcc->setChecked(Preferences::defaultEmailBcc());
1624     }
1625     if (allTabs  ||  mTabs->currentIndex() == mTabFontColour)
1626     {
1627         mFontChooser->setFgColour(Preferences::defaultFgColour());
1628         mFontChooser->setBgColour(Preferences::defaultBgColour());
1629         mFontChooser->setFont(Preferences::messageFont());
1630     }
1631 }
1632 
1633 bool EditPrefTab::apply(bool syncToDisc)
1634 {
1635     bool b = mAutoClose->isChecked();
1636     if (b != Preferences::defaultAutoClose())
1637         Preferences::setDefaultAutoClose(b);
1638     b = mConfirmAck->isChecked();
1639     if (b != Preferences::defaultConfirmAck())
1640         Preferences::setDefaultConfirmAck(b);
1641     TimePeriod::Units units;
1642     switch (mReminderUnits->currentIndex())
1643     {
1644         case 3:  units = TimePeriod::Weeks;        break;
1645         case 2:  units = TimePeriod::Days;         break;
1646         default:
1647         case 1:  units = TimePeriod::HoursMinutes; break;
1648         case 0:  units = TimePeriod::Minutes;      break;
1649     }
1650     if (units != Preferences::defaultReminderUnits())
1651         Preferences::setDefaultReminderUnits(units);
1652     QString text = mSpecialActionsButton->preAction();
1653     if (text != Preferences::defaultPreAction())
1654         Preferences::setDefaultPreAction(text);
1655     text = mSpecialActionsButton->postAction();
1656     if (text != Preferences::defaultPostAction())
1657         Preferences::setDefaultPostAction(text);
1658     KAEvent::ExtraActionOptions opts = mSpecialActionsButton->options();
1659     b = opts & KAEvent::ExecPreActOnDeferral;
1660     if (b != Preferences::defaultExecPreActionOnDeferral())
1661         Preferences::setDefaultExecPreActionOnDeferral(b);
1662     b = opts & KAEvent::CancelOnPreActError;
1663     if (b != Preferences::defaultCancelOnPreActionError())
1664         Preferences::setDefaultCancelOnPreActionError(b);
1665     b = opts & KAEvent::DontShowPreActError;
1666     if (b != Preferences::defaultDontShowPreActionError())
1667         Preferences::setDefaultDontShowPreActionError(b);
1668     Preferences::SoundType snd;
1669     switch (mSound->currentIndex())
1670     {
1671 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT
1672         case 3:  snd = Preferences::Sound_Speak; break;
1673 #endif
1674         case 2:  snd = Preferences::Sound_File;  break;
1675         case 1:  snd = Preferences::Sound_Beep;  break;
1676         case 0:
1677         default: snd = Preferences::Sound_None;  break;
1678     }
1679     if (snd != Preferences::defaultSoundType())
1680         Preferences::setDefaultSoundType(snd);
1681     text = mSoundFile->text();
1682     if (text != Preferences::defaultSoundFile())
1683         Preferences::setDefaultSoundFile(text);
1684     b = mSoundRepeat->isChecked();
1685     if (b != Preferences::defaultSoundRepeat())
1686         Preferences::setDefaultSoundRepeat(b);
1687     const float v = mSoundVolumeCheckbox->isChecked() ? (float)mSoundVolumeSlider->value() / 100 : -1;
1688     if (v != Preferences::defaultSoundVolume())
1689         Preferences::setDefaultSoundVolume(v);
1690     b = mCmdScript->isChecked();
1691     if (b != Preferences::defaultCmdScript())
1692         Preferences::setDefaultCmdScript(b);
1693     Preferences::CmdLogType log = mCmdXterm->isChecked() ? Preferences::Log_Terminal : Preferences::Log_Discard;
1694     if (log != Preferences::defaultCmdLogType())
1695         Preferences::setDefaultCmdLogType(log);
1696     b = mEmailBcc->isChecked();
1697     if (b != Preferences::defaultEmailBcc())
1698         Preferences::setDefaultEmailBcc(b);
1699     Preferences::DisplayMethod disp;
1700     switch(mDisplayMethod->currentIndex())
1701     {
1702         case 1:  disp = Preferences::Display_Notification; break;
1703         case 0:
1704         default: disp = Preferences::Display_Window; break;
1705     }
1706     if (disp != Preferences::defaultDisplayMethod())
1707         Preferences::setDefaultDisplayMethod(disp);
1708     b = mCopyToKOrganizer->isChecked();
1709     if (b != Preferences::defaultCopyToKOrganizer())
1710         Preferences::setDefaultCopyToKOrganizer(b);
1711     const int i = mLateCancel->isChecked() ? 1 : 0;
1712     if (i != Preferences::defaultLateCancel())
1713         Preferences::setDefaultLateCancel(i);
1714     Preferences::RecurType period;
1715     switch (mRecurPeriod->currentIndex())
1716     {
1717         case 6:  period = Preferences::Recur_Yearly;   break;
1718         case 5:  period = Preferences::Recur_Monthly;  break;
1719         case 4:  period = Preferences::Recur_Weekly;   break;
1720         case 3:  period = Preferences::Recur_Daily;    break;
1721         case 2:  period = Preferences::Recur_SubDaily; break;
1722         case 1:  period = Preferences::Recur_Login;    break;
1723         case 0:
1724         default: period = Preferences::Recur_None;     break;
1725     }
1726     if (period != Preferences::defaultRecurPeriod())
1727         Preferences::setDefaultRecurPeriod(period);
1728     const int feb29 = mFeb29->selectedId();
1729     if (feb29 >= 0  &&  static_cast<Preferences::Feb29Type>(feb29) != Preferences::defaultFeb29Type())
1730         Preferences::setDefaultFeb29Type(static_cast<Preferences::Feb29Type>(feb29));
1731     QColor colour = mFontChooser->fgColour();
1732     if (colour != Preferences::defaultFgColour())
1733         Preferences::setDefaultFgColour(colour);
1734     colour = mFontChooser->bgColour();
1735     if (colour != Preferences::defaultBgColour())
1736         Preferences::setDefaultBgColour(colour);
1737     const QFont font = mFontChooser->font();
1738     if (font != Preferences::messageFont())
1739         Preferences::setMessageFont(font);
1740     return PrefsTabBase::apply(syncToDisc);
1741 }
1742 
1743 void EditPrefTab::slotBrowseSoundFile()
1744 {
1745     QString defaultDir;
1746     QString file;
1747     if (SoundPicker::browseFile(file, defaultDir, mSoundFile->text()))
1748     {
1749         if (!file.isEmpty())
1750             mSoundFile->setText(file);
1751     }
1752 }
1753 
1754 int EditPrefTab::soundIndex(Preferences::SoundType type)
1755 {
1756     switch (type)
1757     {
1758 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT
1759         case Preferences::Sound_Speak: return 3;
1760 #endif
1761         case Preferences::Sound_File:  return 2;
1762         case Preferences::Sound_Beep:  return 1;
1763         case Preferences::Sound_None:
1764         default:                       return 0;
1765     }
1766 }
1767 
1768 QString EditPrefTab::validate()
1769 {
1770     if (mSound->currentIndex() == soundIndex(Preferences::Sound_File)  &&  mSoundFile->text().isEmpty())
1771     {
1772         mSoundFile->setFocus();
1773         return xi18nc("@info", "You must enter a sound file when <interface>%1</interface> is selected as the default sound type", SoundPicker::i18n_combo_File());;
1774     }
1775     return {};
1776 }
1777 
1778 
1779 /*=============================================================================
1780 = Class ViewPrefTab
1781 =============================================================================*/
1782 
1783 ViewPrefTab::ViewPrefTab(StackedScrollGroup* scrollGroup)
1784     : PrefsTabBase(scrollGroup)
1785 {
1786     mTabs = new QTabWidget();
1787     mTabs->setDocumentMode(true);
1788     topLayout()->addWidget(mTabs);
1789 
1790     QWidget* widget = new QWidget;
1791     auto topGeneral = new QVBoxLayout(widget);
1792     topGeneral->setContentsMargins(0, style()->pixelMetric(QStyle::PM_LayoutTopMargin),
1793                                    0, style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
1794     mTabGeneral = mTabs->addTab(widget, i18nc("@title:tab", "General"));
1795 
1796     widget =  new QWidget;
1797     auto topWindows = new QVBoxLayout(widget);
1798     topWindows->setContentsMargins(0, style()->pixelMetric(QStyle::PM_LayoutTopMargin),
1799                                    0, style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
1800     mTabWindows = mTabs->addTab(widget, i18nc("@title:tab", "Alarm Windows"));
1801 
1802     // Run-in-system-tray check box or group.
1803     static const QString showInSysTrayText = i18nc("@option:check", "Show in system tray");
1804     static const QString showInSysTrayWhatsThis = xi18nc("@info:whatsthis",
1805             "<para>Check to show <application>KAlarm</application>'s icon in the system tray."
1806             " Showing it in the system tray provides easy access and a status indication.</para>");
1807     if (Preferences::noAutoHideSystemTrayDesktops().contains(Desktop::currentIdentityName()))
1808     {
1809         // Run-in-system-tray check box.
1810         // This desktop type doesn't provide GUI controls to view hidden system tray
1811         // icons, so don't show options to hide the system tray icon.
1812         widget = new QWidget;  // this is to allow left adjustment
1813         topGeneral->addWidget(widget);
1814         auto box = new QHBoxLayout(widget);
1815         mShowInSystemTrayCheck = new QCheckBox(showInSysTrayText);
1816         mShowInSystemTrayCheck->setWhatsThis(showInSysTrayWhatsThis);
1817         box->addWidget(mShowInSystemTrayCheck);
1818         box->addStretch();    // left adjust the controls
1819     }
1820     else
1821     {
1822         // Run-in-system-tray group box
1823         mShowInSystemTrayGroup = new QGroupBox(showInSysTrayText);
1824         mShowInSystemTrayGroup->setCheckable(true);
1825         mShowInSystemTrayGroup->setWhatsThis(showInSysTrayWhatsThis);
1826         topGeneral->addWidget(mShowInSystemTrayGroup);
1827         auto grid = new QGridLayout(mShowInSystemTrayGroup);
1828         grid->setColumnStretch(1, 1);
1829         grid->setColumnMinimumWidth(0, gridIndentWidth());
1830 
1831         mAutoHideSystemTray = new ButtonGroup(mShowInSystemTrayGroup);
1832         connect(mAutoHideSystemTray, &ButtonGroup::buttonSet, this, &ViewPrefTab::slotAutoHideSysTrayChanged);
1833 
1834         QRadioButton* radio = new QRadioButton(i18nc("@option:radio Always show KAlarm icon", "Always show"), mShowInSystemTrayGroup);
1835         mAutoHideSystemTray->addButton(radio, 0);
1836         radio->setWhatsThis(
1837               xi18nc("@info:whatsthis",
1838                     "Check to show <application>KAlarm</application>'s icon in the system tray "
1839                     "regardless of whether alarms are due."));
1840         grid->addWidget(radio, 0, 0, 1, 2, Qt::AlignLeft);
1841 
1842         radio = new QRadioButton(i18nc("@option:radio", "Automatically hide if no active alarms"), mShowInSystemTrayGroup);
1843         mAutoHideSystemTray->addButton(radio, 1);
1844         radio->setWhatsThis(
1845               xi18nc("@info:whatsthis",
1846                     "Check to automatically hide <application>KAlarm</application>'s icon in "
1847                     "the system tray if there are no active alarms. When hidden, the icon can "
1848                     "always be made visible by use of the system tray option to show hidden icons."));
1849         grid->addWidget(radio, 1, 0, 1, 2, Qt::AlignLeft);
1850 
1851         QString text = xi18nc("@info:whatsthis",
1852                              "Check to automatically hide <application>KAlarm</application>'s icon in the "
1853                              "system tray if no alarms are due within the specified time period. When hidden, "
1854                              "the icon can always be made visible by use of the system tray option to show hidden icons.");
1855         radio = new QRadioButton(i18nc("@option:radio", "Automatically hide if no alarm due within time period:"), mShowInSystemTrayGroup);
1856         radio->setWhatsThis(text);
1857         mAutoHideSystemTray->addButton(radio, 2);
1858         grid->addWidget(radio, 2, 0, 1, 2, Qt::AlignLeft);
1859         mAutoHideSystemTrayPeriod = new TimePeriod(TimePeriod::ShowMinutes, mShowInSystemTrayGroup);
1860         mAutoHideSystemTrayPeriod->setWhatsThis(text);
1861         mAutoHideSystemTrayPeriod->setMaximumWidth(mAutoHideSystemTrayPeriod->sizeHint().width());
1862         grid->addWidget(mAutoHideSystemTrayPeriod, 3, 1, 1, 1, Qt::AlignLeft);
1863         mShowInSystemTrayGroup->setMaximumHeight(mShowInSystemTrayGroup->sizeHint().height());
1864     }
1865 
1866     // System tray tooltip group box
1867     QGroupBox* group = new QGroupBox(i18nc("@title:group", "System Tray Tooltip"));
1868     topGeneral->addWidget(group);
1869     auto grid = new QGridLayout(group);
1870     grid->setColumnStretch(2, 1);
1871     grid->setColumnMinimumWidth(0, gridIndentWidth());
1872     grid->setColumnMinimumWidth(1, gridIndentWidth());
1873 
1874     mTooltipShowAlarms = new QCheckBox(i18nc("@option:check", "Show next 24 hours' alarms"), group);
1875     mTooltipShowAlarms->setMinimumSize(mTooltipShowAlarms->sizeHint());
1876     connect(mTooltipShowAlarms, &QAbstractButton::toggled, this, &ViewPrefTab::slotTooltipAlarmsToggled);
1877     mTooltipShowAlarms->setWhatsThis(
1878           i18nc("@info:whatsthis", "Specify whether to include in the system tray tooltip, a summary of alarms due in the next 24 hours."));
1879     grid->addWidget(mTooltipShowAlarms, 0, 0, 1, 3, Qt::AlignLeft);
1880 
1881     widget = new QWidget;
1882     auto box = new QHBoxLayout(widget);
1883     box->setContentsMargins(0, 0, 0, 0);
1884     mTooltipMaxAlarms = new QCheckBox(i18nc("@option:check", "Maximum number of alarms to show:"));
1885     mTooltipMaxAlarms->setMinimumSize(mTooltipMaxAlarms->sizeHint());
1886     box->addWidget(mTooltipMaxAlarms);
1887     connect(mTooltipMaxAlarms, &QAbstractButton::toggled, this, &ViewPrefTab::slotTooltipMaxToggled);
1888     mTooltipMaxAlarmCount = new SpinBox(1, 99);
1889     mTooltipMaxAlarmCount->setSingleShiftStep(5);
1890     mTooltipMaxAlarmCount->setMinimumSize(mTooltipMaxAlarmCount->sizeHint());
1891     box->addWidget(mTooltipMaxAlarmCount);
1892     widget->setWhatsThis(
1893           i18nc("@info:whatsthis", "Uncheck to display all of the next 24 hours' alarms in the system tray tooltip. "
1894                "Check to enter an upper limit on the number to be displayed."));
1895     grid->addWidget(widget, 1, 1, 1, 2, Qt::AlignLeft);
1896 
1897     mTooltipShowTime = new QCheckBox(i18nc("@option:check", "Show alarm time"), group);
1898     mTooltipShowTime->setMinimumSize(mTooltipShowTime->sizeHint());
1899     connect(mTooltipShowTime, &QAbstractButton::toggled, this, &ViewPrefTab::slotTooltipTimeToggled);
1900     mTooltipShowTime->setWhatsThis(i18nc("@info:whatsthis", "Specify whether to show in the system tray tooltip, the time at which each alarm is due."));
1901     grid->addWidget(mTooltipShowTime, 2, 1, 1, 2, Qt::AlignLeft);
1902 
1903     mTooltipShowTimeTo = new QCheckBox(i18nc("@option:check", "Show time until alarm"), group);
1904     mTooltipShowTimeTo->setMinimumSize(mTooltipShowTimeTo->sizeHint());
1905     connect(mTooltipShowTimeTo, &QAbstractButton::toggled, this, &ViewPrefTab::slotTooltipTimeToToggled);
1906     mTooltipShowTimeTo->setWhatsThis(i18nc("@info:whatsthis", "Specify whether to show in the system tray tooltip, how long until each alarm is due."));
1907     grid->addWidget(mTooltipShowTimeTo, 3, 1, 1, 2, Qt::AlignLeft);
1908 
1909     widget = new QWidget; // this is to control the QWhatsThis text display area
1910     box = new QHBoxLayout(widget);
1911     box->setContentsMargins(0, 0, 0, 0);
1912     mTooltipTimeToPrefixLabel = new QLabel(i18nc("@label:textbox", "Prefix:"));
1913     box->addWidget(mTooltipTimeToPrefixLabel);
1914     mTooltipTimeToPrefix = new QLineEdit();
1915     box->addWidget(mTooltipTimeToPrefix);
1916     mTooltipTimeToPrefixLabel->setBuddy(mTooltipTimeToPrefix);
1917     widget->setWhatsThis(i18nc("@info:whatsthis", "Enter the text to be displayed in front of the time until the alarm, in the system tray tooltip."));
1918     grid->addWidget(widget, 4, 2, Qt::AlignLeft);
1919     group->setMaximumHeight(group->sizeHint().height());
1920 
1921     group = new QGroupBox(i18nc("@title:group", "Alarm List"));
1922     topGeneral->addWidget(group);
1923     auto hlayout = new QHBoxLayout(group);
1924     auto colourLayout = new QVBoxLayout();
1925     colourLayout->setContentsMargins(0, 0, 0, 0);
1926     hlayout->addLayout(colourLayout);
1927 
1928     widget = new QWidget;  // to group widgets for QWhatsThis text
1929     box = new QHBoxLayout(widget);
1930     box->setContentsMargins(0, 0, 0, 0);
1931     box->setSpacing(style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing) / 2);
1932     colourLayout->addWidget(widget);
1933     QLabel* label1 = new QLabel(i18nc("@label:listbox", "Disabled alarm color:"));
1934     box->addWidget(label1);
1935     box->setStretchFactor(new QWidget(widget), 0);
1936     mDisabledColour = new ColourButton();
1937     box->addWidget(mDisabledColour);
1938     label1->setBuddy(mDisabledColour);
1939     widget->setWhatsThis(i18nc("@info:whatsthis", "Choose the text color in the alarm list for disabled alarms."));
1940 
1941     widget = new QWidget;    // to group widgets for QWhatsThis text
1942     box = new QHBoxLayout(widget);
1943     box->setContentsMargins(0, 0, 0, 0);
1944     box->setSpacing(style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing) / 2);
1945     colourLayout->addWidget(widget);
1946     QLabel* label2 = new QLabel(i18nc("@label:listbox", "Archived alarm color:"));
1947     box->addWidget(label2);
1948     box->setStretchFactor(new QWidget(widget), 0);
1949     mArchivedColour = new ColourButton();
1950     box->addWidget(mArchivedColour);
1951     label2->setBuddy(mArchivedColour);
1952     widget->setWhatsThis(i18nc("@info:whatsthis", "Choose the text color in the alarm list for archived alarms."));
1953     hlayout->addStretch();
1954 
1955     if (topGeneral)
1956         topGeneral->addStretch();    // top adjust the widgets
1957 
1958 
1959     group = new QGroupBox(i18nc("@title:group", "Alarm Message Windows"));
1960     topWindows->addWidget(group);
1961     grid = new QGridLayout(group);
1962     grid->setColumnStretch(1, 1);
1963     if (!KWindowSystem::isPlatformWayland())  // Wayland doesn't allow positioning of windows
1964     {
1965         grid->setColumnMinimumWidth(0, gridIndentWidth());
1966 
1967         mWindowPosition = new ButtonGroup(group);
1968         connect(mWindowPosition, &ButtonGroup::buttonSet, this, &ViewPrefTab::slotWindowPosChanged);
1969 
1970         const QString whatsthis = xi18nc("@info:whatsthis",
1971               "<para>Choose how to reduce the chance of alarm messages being accidentally acknowledged:"
1972               "<list><item>Position alarm message windows as far as possible from the current mouse cursor location, or</item>"
1973               "<item>Position alarm message windows in the center of the screen, but disable buttons for a short time after the window is displayed.</item></list></para>");
1974         QRadioButton* radio = new QRadioButton(i18nc("@option:radio", "Position windows far from mouse cursor"), group);
1975         mWindowPosition->addButton(radio, 0);
1976         radio->setWhatsThis(whatsthis);
1977         grid->addWidget(radio, 0, 0, 1, 2, Qt::AlignLeft);
1978         radio = new QRadioButton(i18nc("@option:radio", "Center windows, delay activating window buttons"), group);
1979         mWindowPosition->addButton(radio, 1);
1980         radio->setWhatsThis(whatsthis);
1981         grid->addWidget(radio, 1, 0, 1, 2, Qt::AlignLeft);
1982     }
1983 
1984     widget = new QWidget;   // this is to control the QWhatsThis text display area
1985     box = new QHBoxLayout(widget);
1986     box->setContentsMargins(0, 0, 0, 0);
1987     mWindowButtonDelayLabel = new QLabel(i18nc("@label:spinbox", "Button activation delay (seconds):"));
1988     box->addWidget(mWindowButtonDelayLabel);
1989     mWindowButtonDelay = new QSpinBox();
1990     mWindowButtonDelay->setRange(1, 10);
1991     mWindowButtonDelayLabel->setBuddy(mWindowButtonDelay);
1992     box->addWidget(mWindowButtonDelay);
1993     if (KWindowSystem::isPlatformWayland())  // Wayland doesn't allow positioning of windows
1994         widget->setWhatsThis(i18nc("@info:whatsthis",
1995                             "Enter how long its buttons should remain disabled after the alarm message window is shown, "
1996                             "to reduce the chance of alarm messages being accidentally acknowledged."));
1997     else
1998         widget->setWhatsThis(i18nc("@info:whatsthis",
1999                             "Enter how long its buttons should remain disabled after the alarm message window is shown."));
2000     box->addStretch();    // left adjust the controls
2001     grid->addWidget(widget, 2, 1, Qt::AlignLeft);
2002 
2003     if (KWindowSystem::isPlatformX11())
2004     {
2005         grid->setRowMinimumHeight(3, style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
2006 
2007         mModalMessages = new QCheckBox(i18nc("@option:check", "Message windows have a title bar and take keyboard focus"), group);
2008         mModalMessages->setMinimumSize(mModalMessages->sizeHint());
2009         mModalMessages->setWhatsThis(xi18nc("@info:whatsthis",
2010               "<para>Specify the characteristics of alarm message windows:"
2011               "<list><item>If checked, the window is a normal window with a title bar, which grabs keyboard input when it is displayed.</item>"
2012               "<item>If unchecked, the window does not interfere with your typing when "
2013               "it is displayed, but it has no title bar and cannot be moved or resized.</item></list></para>"));
2014         grid->addWidget(mModalMessages, 4, 0, 1, 2, Qt::AlignLeft);
2015     }
2016 
2017     if (topWindows)
2018         topWindows->addStretch();    // top adjust the widgets
2019 }
2020 
2021 void ViewPrefTab::restore(bool, bool allTabs)
2022 {
2023     if (allTabs  ||  mTabs->currentIndex() == mTabGeneral)
2024     {
2025         if (mShowInSystemTrayGroup)
2026             mShowInSystemTrayGroup->setChecked(Preferences::showInSystemTray());
2027         else
2028             mShowInSystemTrayCheck->setChecked(Preferences::showInSystemTray());
2029         int id;
2030         const int mins = Preferences::autoHideSystemTray();
2031         switch (mins)
2032         {
2033             case -1:  id = 1;  break;    // hide if no active alarms
2034             case 0:   id = 0;  break;    // never hide
2035             default:
2036             {
2037                 id = 2;
2038                 int days = 0;
2039                 int secs = 0;
2040                 if (mins % 1440)
2041                     secs = mins * 60;
2042                 else
2043                     days = mins / 1440;
2044                 const TimePeriod::Units units = secs ? TimePeriod::HoursMinutes
2045                                               : (days % 7) ? TimePeriod::Days : TimePeriod::Weeks;
2046                 const Duration duration((secs ? secs : days), (secs ? Duration::Seconds : Duration::Days));
2047                 mAutoHideSystemTrayPeriod->setPeriod(duration, false, units);
2048                 break;
2049             }
2050         }
2051         if (mAutoHideSystemTray)
2052             mAutoHideSystemTray->setButton(id);
2053         setTooltip(Preferences::tooltipAlarmCount(),
2054                    Preferences::showTooltipAlarmTime(),
2055                    Preferences::showTooltipTimeToAlarm(),
2056                    Preferences::tooltipTimeToPrefix());
2057         mDisabledColour->setColor(Preferences::disabledColour());
2058         mArchivedColour->setColor(Preferences::archivedColour());
2059     }
2060     if (allTabs  ||  mTabs->currentIndex() == mTabWindows)
2061     {
2062         if (mWindowPosition)
2063             mWindowPosition->setButton(Preferences::messageButtonDelay() ? 1 : 0);
2064         mWindowButtonDelay->setValue(Preferences::messageButtonDelay());
2065         if (mModalMessages)
2066             mModalMessages->setChecked(Preferences::modalMessages());
2067     }
2068 }
2069 
2070 bool ViewPrefTab::apply(bool syncToDisc)
2071 {
2072     QColor colour = mDisabledColour->color();
2073     if (colour != Preferences::disabledColour())
2074         Preferences::setDisabledColour(colour);
2075     colour = mArchivedColour->color();
2076     if (colour != Preferences::archivedColour())
2077         Preferences::setArchivedColour(colour);
2078     int n = mTooltipShowAlarms->isChecked() ? -1 : 0;
2079     if (n  &&  mTooltipMaxAlarms->isChecked())
2080         n = mTooltipMaxAlarmCount->value();
2081     if (n != Preferences::tooltipAlarmCount())
2082         Preferences::setTooltipAlarmCount(n);
2083     bool b = mTooltipShowTime->isChecked();
2084     if (b != Preferences::showTooltipAlarmTime())
2085         Preferences::setShowTooltipAlarmTime(b);
2086     b = mTooltipShowTimeTo->isChecked();
2087     if (b != Preferences::showTooltipTimeToAlarm())
2088         Preferences::setShowTooltipTimeToAlarm(b);
2089     QString text = mTooltipTimeToPrefix->text();
2090     if (text != Preferences::tooltipTimeToPrefix())
2091         Preferences::setTooltipTimeToPrefix(text);
2092     b = mShowInSystemTrayGroup ? mShowInSystemTrayGroup->isChecked() : mShowInSystemTrayCheck->isChecked();
2093     if (b != Preferences::showInSystemTray())
2094         Preferences::setShowInSystemTray(b);
2095     if (b  &&  mAutoHideSystemTray)
2096     {
2097         switch (mAutoHideSystemTray->selectedId())
2098         {
2099             case 0:  n = 0;   break;    // never hide
2100             case 1:  n = -1;  break;    // hide if no active alarms
2101             case 2:                     // hide if no alarms due within period
2102                      n = mAutoHideSystemTrayPeriod->period().asSeconds() / 60;
2103                      break;
2104         }
2105         if (n != Preferences::autoHideSystemTray())
2106             Preferences::setAutoHideSystemTray(n);
2107     }
2108     n = mWindowPosition ? mWindowPosition->selectedId() : 1;
2109     if (n)
2110         n = mWindowButtonDelay->value();
2111     if (n != Preferences::messageButtonDelay())
2112         Preferences::setMessageButtonDelay(n);
2113     if (mModalMessages)
2114     {
2115         b = mModalMessages->isChecked();
2116         if (b != Preferences::modalMessages())
2117             Preferences::setModalMessages(b);
2118     }
2119     return PrefsTabBase::apply(syncToDisc);
2120 }
2121 
2122 void ViewPrefTab::setTooltip(int maxAlarms, bool time, bool timeTo, const QString& prefix)
2123 {
2124     if (!timeTo)
2125         time = true;    // ensure that at least one time option is ticked
2126 
2127     // Set the states of the controls without calling signal
2128     // handlers, since these could change the checkboxes' states.
2129     mTooltipShowAlarms->blockSignals(true);
2130     mTooltipShowTime->blockSignals(true);
2131     mTooltipShowTimeTo->blockSignals(true);
2132 
2133     mTooltipShowAlarms->setChecked(maxAlarms);
2134     mTooltipMaxAlarms->setChecked(maxAlarms > 0);
2135     mTooltipMaxAlarmCount->setValue(maxAlarms > 0 ? maxAlarms : 1);
2136     mTooltipShowTime->setChecked(time);
2137     mTooltipShowTimeTo->setChecked(timeTo);
2138     mTooltipTimeToPrefix->setText(prefix);
2139 
2140     mTooltipShowAlarms->blockSignals(false);
2141     mTooltipShowTime->blockSignals(false);
2142     mTooltipShowTimeTo->blockSignals(false);
2143 
2144     // Enable/disable controls according to their states
2145     slotTooltipTimeToToggled(timeTo);
2146     slotTooltipAlarmsToggled(maxAlarms);
2147 }
2148 
2149 void ViewPrefTab::slotTooltipAlarmsToggled(bool on)
2150 {
2151     mTooltipMaxAlarms->setEnabled(on);
2152     mTooltipMaxAlarmCount->setEnabled(on && mTooltipMaxAlarms->isChecked());
2153     mTooltipShowTime->setEnabled(on);
2154     mTooltipShowTimeTo->setEnabled(on);
2155     on = on && mTooltipShowTimeTo->isChecked();
2156     mTooltipTimeToPrefix->setEnabled(on);
2157     mTooltipTimeToPrefixLabel->setEnabled(on);
2158 }
2159 
2160 void ViewPrefTab::slotTooltipMaxToggled(bool on)
2161 {
2162     mTooltipMaxAlarmCount->setEnabled(on && mTooltipMaxAlarms->isEnabled());
2163 }
2164 
2165 void ViewPrefTab::slotTooltipTimeToggled(bool on)
2166 {
2167     if (!on  &&  !mTooltipShowTimeTo->isChecked())
2168         mTooltipShowTimeTo->setChecked(true);
2169 }
2170 
2171 void ViewPrefTab::slotTooltipTimeToToggled(bool on)
2172 {
2173     if (!on  &&  !mTooltipShowTime->isChecked())
2174         mTooltipShowTime->setChecked(true);
2175     on = on && mTooltipShowTimeTo->isEnabled();
2176     mTooltipTimeToPrefix->setEnabled(on);
2177     mTooltipTimeToPrefixLabel->setEnabled(on);
2178 }
2179 
2180 void ViewPrefTab::slotAutoHideSysTrayChanged(QAbstractButton* button)
2181 {
2182     if (mAutoHideSystemTray)
2183         mAutoHideSystemTrayPeriod->setEnabled(mAutoHideSystemTray->id(button) == 2);
2184 }
2185 
2186 void ViewPrefTab::slotWindowPosChanged(QAbstractButton* button)
2187 {
2188     const bool enable = mWindowPosition ? mWindowPosition->id(button) : true;
2189     mWindowButtonDelay->setEnabled(enable);
2190     mWindowButtonDelayLabel->setEnabled(enable);
2191 }
2192 
2193 #include "moc_prefdlg_p.cpp"
2194 #include "moc_prefdlg.cpp"
2195 
2196 // vim: et sw=4: