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

0001 /*
0002  *  commandoptions.cpp  -  extract command line options
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 "commandoptions.h"
0010 
0011 #include "functions.h"
0012 #include "kamail.h"
0013 #include "preferences.h"
0014 #include "kalarmcalendar/identities.h"
0015 #include "kalarm_debug.h"
0016 
0017 #include <KLocalizedString>
0018 
0019 #include <QCommandLineParser>
0020 
0021 #include <iostream>
0022 
0023 namespace
0024 {
0025 enum Option
0026 {
0027     ACK_CONFIRM,
0028     ATTACH,
0029     AUTO_CLOSE,
0030     BCC,
0031     BEEP,
0032     COLOUR,
0033     COLOURFG,
0034     OptCANCEL_EVENT,
0035     DISABLE,
0036     DISABLE_ALL,
0037     EXEC,
0038     EXEC_DISPLAY,
0039     OptEDIT,
0040     EDIT_NEW_DISPLAY,
0041     EDIT_NEW_COMMAND,
0042     EDIT_NEW_EMAIL,
0043     EDIT_NEW_AUDIO,
0044     OptEDIT_NEW_PRESET,
0045     OptFILE,
0046     FROM_ID,
0047     INTERVAL,
0048     KORGANIZER,
0049     LATE_CANCEL,
0050     OptLIST,
0051     LOGIN,
0052     MAIL,
0053     NAME,
0054     NOTIFY,
0055     PLAY,
0056     PLAY_REPEAT,
0057     RECURRENCE,
0058     REMINDER,
0059     REMINDER_ONCE,
0060     REPEAT,
0061 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT
0062     SPEAK,
0063 #endif
0064     SUBJECT,
0065 #ifndef NDEBUG
0066     TEST_SET_TIME,
0067 #endif
0068     TIME,
0069     OptTRAY,
0070     OptTRIGGER_EVENT,
0071     UNTIL,
0072     VOLUME,
0073     Num_Options,       // number of Option values
0074     Opt_Message        // special value representing "message"
0075 };
0076 
0077 bool convInterval(const QString& timeParam, KARecurrence::Type&, int& timeInterval, bool allowMonthYear = false);
0078 }
0079 
0080 class CommandOptions::Private
0081 {
0082 public:
0083     explicit Private(CommandOptions* parent)
0084         : p(parent)
0085     {}
0086     bool    checkCommand(Option, Command, EditAlarmDlg::Type = EditAlarmDlg::NO_TYPE);
0087     void    setError(const QString& error);
0088     void    setErrorRequires(Option opt, Option opt2, Option opt3 = Num_Options);
0089     void    setErrorParameter(Option);
0090     void    setErrorIncompatible(Option opt1, Option opt2);
0091     void    checkEditType(EditAlarmDlg::Type type, Option opt)
0092                           { checkEditType(type, EditAlarmDlg::NO_TYPE, opt); }
0093     void    checkEditType(EditAlarmDlg::Type, EditAlarmDlg::Type, Option);
0094     QString optionName(Option, bool shortName = false) const;
0095 
0096     CommandOptions* const p;
0097     Option mCommandOpt;             // option for the selected command
0098 };
0099 
0100 /*===========================================================================*/
0101 
0102 CommandOptions* CommandOptions::mFirstInstance = nullptr;
0103 
0104 CommandOptions::CommandOptions()
0105     : d(new Private(this))
0106     , mOptions(Num_Options, nullptr)
0107     , mBgColour(Preferences::defaultBgColour())
0108     , mFgColour(Preferences::defaultFgColour())
0109     , mFlags(KAEvent::DEFAULT_FONT)
0110 {
0111     if (!mFirstInstance)
0112         mFirstInstance = this;
0113 }
0114 
0115 /******************************************************************************
0116 * Set the command line options for the parser, and remove any arguments
0117 * following --exec or --exec-display (since the parser can't handle this).
0118 * Reply = command line arguments for parser.
0119 */
0120 QStringList CommandOptions::setOptions(QCommandLineParser* parser, const QStringList& args)
0121 {
0122     mParser = parser;
0123 
0124     mOptions[ACK_CONFIRM]
0125               = new QCommandLineOption(QStringList{QStringLiteral("a"), QStringLiteral("ack-confirm")},
0126                                        i18n("Prompt for confirmation when alarm is acknowledged"));
0127     mOptions[ATTACH]
0128               = new QCommandLineOption(QStringList{QStringLiteral("A"), QStringLiteral("attach")},
0129                                        i18n("Attach file to email (repeat as needed)"),
0130                                        QStringLiteral("url"));
0131     mOptions[AUTO_CLOSE]
0132               = new QCommandLineOption(QStringLiteral("auto-close"),
0133                                        i18n("Auto-close alarm window after --late-cancel period"));
0134     mOptions[BCC]
0135               = new QCommandLineOption(QStringLiteral("bcc"),
0136                                        i18n("Blind copy email to self"));
0137     mOptions[BEEP]
0138               = new QCommandLineOption(QStringList{QStringLiteral("b"), QStringLiteral("beep")},
0139                                        i18n("Beep when message is displayed"));
0140     mOptions[COLOUR]
0141               = new QCommandLineOption(QStringList{QStringLiteral("c"), QStringLiteral("colour"), QStringLiteral("color")},
0142                                        i18n("Message background color (name or hex 0xRRGGBB)"),
0143                                        QStringLiteral("color"));
0144     mOptions[COLOURFG]
0145               = new QCommandLineOption(QStringList{QStringLiteral("C"), QStringLiteral("colourfg"), QStringLiteral("colorfg")},
0146                                        i18n("Message foreground color (name or hex 0xRRGGBB)"),
0147                                        QStringLiteral("color"));
0148     mOptions[OptCANCEL_EVENT]
0149               = new QCommandLineOption(QStringLiteral("cancelEvent"),
0150                                        i18n("Cancel alarm with the specified event ID"),
0151                                        QStringLiteral("eventID"));
0152     mOptions[DISABLE]
0153               = new QCommandLineOption(QStringList{QStringLiteral("d"), QStringLiteral("disable")},
0154                                        i18n("Disable the alarm"));
0155     mOptions[DISABLE_ALL]
0156               = new QCommandLineOption(QStringLiteral("disable-all"),
0157                                        i18n("Disable monitoring of all alarms"));
0158     mOptions[EXEC]
0159               = new QCommandLineOption(QStringList{QStringLiteral("e"), QStringLiteral("exec")},
0160                                        i18n("Execute a shell command line"),
0161                                        QStringLiteral("commandLine"));
0162     mOptions[EXEC_DISPLAY]
0163               = new QCommandLineOption(QStringList{QStringLiteral("E"), QStringLiteral("exec-display")},
0164                                        i18n("Command line to generate alarm message text"),
0165                                        QStringLiteral("commandLine"));
0166     mOptions[OptEDIT]
0167               = new QCommandLineOption(QStringLiteral("edit"),
0168                                        i18n("Display the alarm edit dialog to edit the specified alarm"),
0169                                        QStringLiteral("eventID"));
0170     mOptions[EDIT_NEW_DISPLAY]
0171               = new QCommandLineOption(QStringLiteral("edit-new-display"),
0172                                        i18n("Display the alarm edit dialog to edit a new display alarm"));
0173     mOptions[EDIT_NEW_COMMAND]
0174               = new QCommandLineOption(QStringLiteral("edit-new-command"),
0175                                        i18n("Display the alarm edit dialog to edit a new command alarm"));
0176     mOptions[EDIT_NEW_EMAIL]
0177               = new QCommandLineOption(QStringLiteral("edit-new-email"),
0178                                        i18n("Display the alarm edit dialog to edit a new email alarm"));
0179     mOptions[EDIT_NEW_AUDIO]
0180               = new QCommandLineOption(QStringLiteral("edit-new-audio"),
0181                                        i18n("Display the alarm edit dialog to edit a new audio alarm"));
0182     mOptions[OptEDIT_NEW_PRESET]
0183               = new QCommandLineOption(QStringLiteral("edit-new-preset"),
0184                                        i18n("Display the alarm edit dialog, preset with a template"),
0185                                        QStringLiteral("templateName"));
0186     mOptions[OptFILE]
0187               = new QCommandLineOption(QStringList{QStringLiteral("f"), QStringLiteral("file")},
0188                                        i18n("File to display"),
0189                                        QStringLiteral("url"));
0190     mOptions[FROM_ID]
0191               = new QCommandLineOption(QStringList{QStringLiteral("F"), QStringLiteral("from-id")},
0192                                        i18n("KMail identity to use as sender of email"),
0193                                        QStringLiteral("ID"));
0194     mOptions[INTERVAL]
0195               = new QCommandLineOption(QStringList{QStringLiteral("i"), QStringLiteral("interval")},
0196                                        i18n("Interval between alarm repetitions"),
0197                                        QStringLiteral("period"));
0198     mOptions[KORGANIZER]
0199               = new QCommandLineOption(QStringList{QStringLiteral("k"), QStringLiteral("korganizer")},
0200                                        i18n("Show alarm as an event in KOrganizer"));
0201     mOptions[LATE_CANCEL]
0202               = new QCommandLineOption(QStringList{QStringLiteral("l"), QStringLiteral("late-cancel")},
0203                                        i18n("Cancel alarm if more than 'period' late when triggered"),
0204                                        QStringLiteral("period"),
0205                                        QStringLiteral("1"));
0206     mOptions[OptLIST]
0207               = new QCommandLineOption(QStringLiteral("list"),
0208                                        i18n("Output list of scheduled alarms to stdout"));
0209     mOptions[LOGIN]
0210               = new QCommandLineOption(QStringList{QStringLiteral("L"), QStringLiteral("login")},
0211                                        i18n("Repeat alarm at every login"));
0212     mOptions[MAIL]
0213               = new QCommandLineOption(QStringList{QStringLiteral("m"), QStringLiteral("mail")},
0214                                        i18n("Send an email to the given address (repeat as needed)"),
0215                                        QStringLiteral("address"));
0216     mOptions[NAME]
0217               = new QCommandLineOption(QStringList{QStringLiteral("n"), QStringLiteral("name")},
0218                                        i18n("Name of alarm"),
0219                                        QStringLiteral("name"));
0220     mOptions[NOTIFY]
0221               = new QCommandLineOption(QStringList{QStringLiteral("N"), QStringLiteral("notify")},
0222                                        i18n("Display alarm message as a notification"));
0223     mOptions[PLAY]
0224               = new QCommandLineOption(QStringList{QStringLiteral("p"), QStringLiteral("play")},
0225                                        i18n("Audio file to play once"),
0226                                        QStringLiteral("url"));
0227     mOptions[PLAY_REPEAT]
0228               = new QCommandLineOption(QStringList{QStringLiteral("P"), QStringLiteral("play-repeat")},
0229                                        i18n("Audio file to play repeatedly"),
0230                                        QStringLiteral("url"));
0231     mOptions[RECURRENCE]
0232               = new QCommandLineOption(QStringLiteral("recurrence"),
0233                                        i18n("Specify alarm recurrence using iCalendar syntax"),
0234                                        QStringLiteral("spec"));
0235     mOptions[REMINDER]
0236               = new QCommandLineOption(QStringList{QStringLiteral("R"), QStringLiteral("reminder")},
0237                                        i18n("Display reminder before or after alarm"),
0238                                        QStringLiteral("period"));
0239     mOptions[REMINDER_ONCE]
0240               = new QCommandLineOption(QStringLiteral("reminder-once"),
0241                                        i18n("Display reminder once, before or after first alarm recurrence"),
0242                                        QStringLiteral("period"));
0243     mOptions[REPEAT]
0244               = new QCommandLineOption(QStringList{QStringLiteral("r"), QStringLiteral("repeat")},
0245                                        i18n("Number of times to repeat alarm (including initial occasion)"),
0246                                        QStringLiteral("count"));
0247 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT
0248     mOptions[SPEAK]
0249               = new QCommandLineOption(QStringList{QStringLiteral("s"), QStringLiteral("speak")},
0250                                        i18n("Speak the message when it is displayed"));
0251 #endif
0252     mOptions[SUBJECT]
0253               = new QCommandLineOption(QStringList{QStringLiteral("S"), QStringLiteral("subject")},
0254                                        i18n("Email subject line"),
0255                                        QStringLiteral("text"));
0256 #ifndef NDEBUG
0257     mOptions[TEST_SET_TIME]
0258               = new QCommandLineOption(QStringLiteral("test-set-time"),
0259                                        i18n("Simulate system time [[[yyyy-]mm-]dd-]hh:mm [TZ] (debug mode)"),
0260                                        QStringLiteral("time"));
0261 #endif
0262     mOptions[TIME]
0263               = new QCommandLineOption(QStringList{QStringLiteral("t"), QStringLiteral("time")},
0264                                        i18n("Trigger alarm at time [[[yyyy-]mm-]dd-]hh:mm [TZ], or date yyyy-mm-dd [TZ]"),
0265                                        QStringLiteral("time"));
0266     mOptions[OptTRAY]
0267               = new QCommandLineOption(QStringLiteral("tray"),
0268                                        i18n("Display system tray icon"));
0269     mOptions[OptTRIGGER_EVENT]
0270               = new QCommandLineOption(QStringLiteral("triggerEvent"),
0271                                        i18n("Trigger alarm with the specified event ID"),
0272                                        QStringLiteral("eventID"));
0273     mOptions[UNTIL]
0274               = new QCommandLineOption(QStringList{QStringLiteral("u"), QStringLiteral("until")},
0275                                        i18n("Repeat until time [[[yyyy-]mm-]dd-]hh:mm [TZ], or date yyyy-mm-dd [TZ]"),
0276                                        QStringLiteral("time"));
0277     mOptions[VOLUME]
0278               = new QCommandLineOption(QStringList{QStringLiteral("V"), QStringLiteral("volume")},
0279                                        i18n("Volume to play audio file"),
0280                                        QStringLiteral("percent"));
0281 
0282     for (int i = 0; i < mOptions.size(); ++i)
0283     {
0284         if (!mOptions.at(i))
0285             qCCritical(KALARM_LOG) << "CommandOptions::setOptions: Command option" << i << "not initialised";
0286         else
0287             mParser->addOption(*(mOptions.at(i)));
0288     }
0289     mParser->addPositionalArgument(QStringLiteral("message"),
0290                                    i18n("Message text to display"),
0291                                    QStringLiteral("[message]"));
0292 
0293     // Check for any options which eat up all following arguments.
0294     mNonExecArguments.clear();
0295     for (int i = 0;  i < args.size();  ++i)
0296     {
0297         const QString arg = args[i];
0298         if (arg == QLatin1String("--nofork"))
0299             continue;     // Ignore debugging option
0300         mNonExecArguments << arg;
0301         if (arg == d->optionName(EXEC)  ||  arg == d->optionName(EXEC, true)
0302         ||  arg == d->optionName(EXEC_DISPLAY)  ||  arg == d->optionName(EXEC_DISPLAY, true))
0303         {
0304             // All following arguments (including ones beginning with '-')
0305             // belong to this option. QCommandLineParser can't handle this, so
0306             // remove them from the command line.
0307             ++i;   // leave the first argument, which is the command to be executed
0308             while (++i < args.size())
0309                 mExecArguments << args[i];
0310         }
0311     }
0312     return mNonExecArguments;
0313 }
0314 
0315 void CommandOptions::parse()
0316 {
0317     // First check for parse errors (unknown option, missing argument).
0318     // Simply calling mParser->process() would exit the program if an error is found,
0319     // which isn't the correct action if another KAlarm instance is running.
0320     if (!mParser->parse(mNonExecArguments))
0321     {
0322         qCWarning(KALARM_LOG) << "CommandOptions::parse:" << mParser->errorText();
0323         mError.clear();
0324         d->setError(mParser->errorText());
0325     }
0326     else
0327         mParser->process(mNonExecArguments);
0328 }
0329 
0330 void CommandOptions::process()
0331 {
0332     if (mCommand == CMD_ERROR)
0333         return;
0334 
0335 #ifndef NDEBUG
0336     if (mParser->isSet(*mOptions.at(TEST_SET_TIME)))
0337     {
0338         const QString time = mParser->value(*mOptions.at(TEST_SET_TIME));
0339         if (!KAlarm::convertTimeString(time.toLatin1(), mSimulationTime, KADateTime::realCurrentLocalDateTime(), true))
0340             d->setErrorParameter(TEST_SET_TIME);
0341     }
0342 #endif
0343     if (d->checkCommand(OptTRAY, TRAY))
0344     {
0345     }
0346     if (d->checkCommand(OptLIST, LIST))
0347     {
0348         if (!mParser->positionalArguments().empty())
0349             d->setErrorParameter(OptLIST);
0350     }
0351     if (d->checkCommand(OptTRIGGER_EVENT, TRIGGER_EVENT)
0352     ||  d->checkCommand(OptCANCEL_EVENT, CANCEL_EVENT)
0353     ||  d->checkCommand(OptEDIT, EDIT))
0354     {
0355         // Fetch the resource and event IDs. The supplied ID is the event ID,
0356         // optionally prefixed by the resource ID followed by a colon delimiter.
0357         mResourceId = EventId::extractIDs(mParser->value(*mOptions.at(d->mCommandOpt)), mEventId);
0358     }
0359     if (d->checkCommand(OptEDIT_NEW_PRESET, EDIT_NEW_PRESET))
0360     {
0361         mName = mParser->value(*mOptions.at(d->mCommandOpt));
0362     }
0363     if (d->checkCommand(OptFILE, NEW))
0364     {
0365         mEditType      = EditAlarmDlg::DISPLAY;
0366         mEditAction    = KAEvent::SubAction::File;
0367         mEditActionSet = true;
0368         mText          = mParser->value(*mOptions.at(d->mCommandOpt));
0369     }
0370     if (d->checkCommand(EXEC_DISPLAY, NEW))
0371     {
0372         mEditType      = EditAlarmDlg::DISPLAY;
0373         mEditAction    = KAEvent::SubAction::Command;
0374         mEditActionSet = true;
0375         mFlags        |= KAEvent::DISPLAY_COMMAND;
0376         mText          = mParser->value(*mOptions.at(d->mCommandOpt)) + QLatin1String(" ") + mExecArguments.join(QLatin1Char(' '));
0377     }
0378     if (d->checkCommand(EXEC, NEW))
0379     {
0380         mEditType      = EditAlarmDlg::COMMAND;
0381         mEditAction    = KAEvent::SubAction::Command;
0382         mEditActionSet = true;
0383         mText          = mParser->value(*mOptions.at(d->mCommandOpt)) + QLatin1String(" ") + mExecArguments.join(QLatin1Char(' '));
0384     }
0385     if (d->checkCommand(MAIL, NEW))
0386     {
0387         mEditType      = EditAlarmDlg::EMAIL;
0388         mEditAction    = KAEvent::SubAction::Email;
0389         mEditActionSet = true;
0390     }
0391     if (d->checkCommand(EDIT_NEW_DISPLAY, EDIT_NEW, EditAlarmDlg::DISPLAY))
0392     {
0393         mEditType = EditAlarmDlg::DISPLAY;
0394         if (!mEditActionSet  ||  (mEditAction != KAEvent::SubAction::Command && mEditAction != KAEvent::SubAction::File))
0395         {
0396             mEditAction    = KAEvent::SubAction::Message;
0397             mEditActionSet = true;
0398         }
0399         const QStringList args = mParser->positionalArguments();
0400         if (!args.empty())
0401             mText = args[0];
0402     }
0403     if (d->checkCommand(EDIT_NEW_COMMAND, EDIT_NEW))
0404     {
0405         mEditType      = EditAlarmDlg::COMMAND;
0406         mEditAction    = KAEvent::SubAction::Command;
0407         mEditActionSet = true;
0408     }
0409     if (d->checkCommand(EDIT_NEW_EMAIL, EDIT_NEW, EditAlarmDlg::EMAIL))
0410     {
0411         mEditType      = EditAlarmDlg::EMAIL;
0412         mEditAction    = KAEvent::SubAction::Email;
0413         mEditActionSet = true;
0414     }
0415     if (d->checkCommand(EDIT_NEW_AUDIO, EDIT_NEW, EditAlarmDlg::AUDIO))
0416     {
0417         mEditType      = EditAlarmDlg::AUDIO;
0418         mEditAction    = KAEvent::SubAction::Audio;
0419         mEditActionSet = true;
0420     }
0421     if (mError.isEmpty()  &&  mCommand == NONE)
0422     {
0423         if (mParser->positionalArguments().empty())
0424         {
0425             if (d->checkCommand(PLAY, NEW) || d->checkCommand(PLAY_REPEAT, NEW))
0426             {
0427                 mEditType      = EditAlarmDlg::AUDIO;
0428                 mEditAction    = KAEvent::SubAction::Audio;
0429                 mEditActionSet = true;
0430             }
0431         }
0432         else
0433         {
0434             qCDebug(KALARM_LOG) << "CommandOptions::process: Message";
0435             mCommand       = NEW;
0436             d->mCommandOpt = Opt_Message;
0437             mEditType      = EditAlarmDlg::DISPLAY;
0438             mEditAction    = KAEvent::SubAction::Message;
0439             mEditActionSet = true;
0440             mText          = arg(0);
0441         }
0442     }
0443     if (mEditActionSet  &&  mEditAction == KAEvent::SubAction::Email)
0444     {
0445         if (mParser->isSet(*mOptions.at(SUBJECT)))
0446             mSubject = mParser->value(*mOptions.at(SUBJECT));
0447         if (mParser->isSet(*mOptions.at(FROM_ID)))
0448             mFromID = Identities::identityUoid(mParser->value(*mOptions.at(FROM_ID)));
0449         const QStringList mailParams = mParser->values(*mOptions.at(MAIL));
0450         for (const QString& addr : mailParams)
0451         {
0452             QString a(addr);
0453             if (!KAMail::checkAddress(a))
0454                 d->setError(xi18nc("@info:shell", "<icode>%1</icode>: invalid email address", d->optionName(MAIL)));
0455             KCalendarCore::Person person(QString(), addr);
0456             mAddressees += person;
0457         }
0458         const QStringList attParams = mParser->values(*mOptions.at(ATTACH));
0459         for (const QString& att : attParams)
0460             mAttachments += att;
0461         const QStringList args = mParser->positionalArguments();
0462         if (!args.empty())
0463             mText = args[0];
0464     }
0465     if (mParser->isSet(*mOptions.at(DISABLE_ALL)))
0466     {
0467         if (mCommand == TRIGGER_EVENT  ||  mCommand == LIST)
0468             d->setErrorIncompatible(DISABLE_ALL, d->mCommandOpt);
0469         mDisableAll = true;
0470     }
0471 
0472     // Check that other options are only specified for the
0473     // correct main command options.
0474     d->checkEditType(EditAlarmDlg::DISPLAY, COLOUR);
0475     d->checkEditType(EditAlarmDlg::DISPLAY, COLOURFG);
0476     d->checkEditType(EditAlarmDlg::DISPLAY, EditAlarmDlg::AUDIO, PLAY);
0477     d->checkEditType(EditAlarmDlg::DISPLAY, EditAlarmDlg::AUDIO, PLAY_REPEAT);
0478     d->checkEditType(EditAlarmDlg::DISPLAY, EditAlarmDlg::AUDIO, VOLUME);
0479 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT
0480     d->checkEditType(EditAlarmDlg::DISPLAY, SPEAK);
0481 #endif
0482     d->checkEditType(EditAlarmDlg::DISPLAY, BEEP);
0483     d->checkEditType(EditAlarmDlg::DISPLAY, REMINDER);
0484     d->checkEditType(EditAlarmDlg::DISPLAY, REMINDER_ONCE);
0485     d->checkEditType(EditAlarmDlg::DISPLAY, ACK_CONFIRM);
0486     d->checkEditType(EditAlarmDlg::DISPLAY, AUTO_CLOSE);
0487     d->checkEditType(EditAlarmDlg::DISPLAY, NOTIFY);
0488     d->checkEditType(EditAlarmDlg::EMAIL, SUBJECT);
0489     d->checkEditType(EditAlarmDlg::EMAIL, FROM_ID);
0490     d->checkEditType(EditAlarmDlg::EMAIL, ATTACH);
0491     d->checkEditType(EditAlarmDlg::EMAIL, BCC);
0492 
0493     switch (mCommand)
0494     {
0495         case EDIT_NEW:
0496             if (mParser->isSet(*mOptions.at(DISABLE)))
0497                 d->setErrorIncompatible(DISABLE, d->mCommandOpt);
0498             [[fallthrough]];   // fall through to NEW
0499         case NEW:
0500         {
0501             // Display a message or file, execute a command, or send an email
0502             if (mParser->isSet(*mOptions.at(NAME)))
0503             {
0504                 // Alarm name is specified
0505                 mName = mParser->value(*mOptions.at(NAME));
0506             }
0507             if (mParser->isSet(*mOptions.at(COLOUR)))
0508             {
0509                 // Background colour is specified
0510                 QString colourText = mParser->value(*mOptions.at(COLOUR));
0511                 if (colourText.at(0) == QLatin1Char('0')
0512                 &&  colourText.at(1).toLower() == QLatin1Char('x'))
0513                     colourText.replace(0, 2, QStringLiteral("#"));
0514                 mBgColour.setNamedColor(colourText);
0515                 if (!mBgColour.isValid())
0516                     d->setErrorParameter(COLOUR);
0517             }
0518             if (mParser->isSet(*mOptions.at(COLOURFG)))
0519             {
0520                 // Foreground colour is specified
0521                 QString colourText = mParser->value(*mOptions.at(COLOURFG));
0522                 if (colourText.at(0) == QLatin1Char('0')
0523                 &&  colourText.at(1).toLower() == QLatin1Char('x'))
0524                     colourText.replace(0, 2, QStringLiteral("#"));
0525                 mFgColour.setNamedColor(colourText);
0526                 if (!mFgColour.isValid())
0527                     d->setErrorParameter(COLOURFG);
0528             }
0529 
0530             if (mParser->isSet(*mOptions.at(TIME)))
0531             {
0532                 const QByteArray dateTime = mParser->value(*mOptions.at(TIME)).toLocal8Bit();
0533                 if (!KAlarm::convertTimeString(dateTime, mAlarmTime))
0534                     d->setErrorParameter(TIME);
0535             }
0536             else
0537                 mAlarmTime = KADateTime::currentLocalDateTime();
0538 
0539             const bool haveRecurrence = mParser->isSet(*mOptions.at(RECURRENCE));
0540             if (haveRecurrence)
0541             {
0542                 if (mParser->isSet(*mOptions.at(LOGIN)))
0543                     d->setErrorIncompatible(LOGIN, RECURRENCE);
0544                 else if (mParser->isSet(*mOptions.at(UNTIL)))
0545                     d->setErrorIncompatible(UNTIL, RECURRENCE);
0546                 const QString rule = mParser->value(*mOptions.at(RECURRENCE));
0547                 mRecurrence = new KARecurrence;
0548                 mRecurrence->set(rule);
0549             }
0550             if (mParser->isSet(*mOptions.at(INTERVAL)))
0551             {
0552                 // Repeat count is specified
0553                 int count = 0;
0554                 KADateTime endTime;
0555                 if (mParser->isSet(*mOptions.at(LOGIN)))
0556                     d->setErrorIncompatible(LOGIN, INTERVAL);
0557                 bool ok;
0558                 if (mParser->isSet(*mOptions.at(REPEAT)))
0559                 {
0560                     count = mParser->value(*mOptions.at(REPEAT)).toInt(&ok);
0561                     if (!ok || !count || count < -1 || (count < 0 && haveRecurrence))
0562                         d->setErrorParameter(REPEAT);
0563                 }
0564                 else if (haveRecurrence)
0565                     d->setErrorRequires(INTERVAL, REPEAT);
0566                 else if (mParser->isSet(*mOptions.at(UNTIL)))
0567                 {
0568                     count = 0;
0569                     const QByteArray dateTime = mParser->value(*mOptions.at(UNTIL)).toLocal8Bit();
0570                     if (mParser->isSet(*mOptions.at(TIME)))
0571                         ok = KAlarm::convertTimeString(dateTime, endTime, mAlarmTime);
0572                     else
0573                         ok = KAlarm::convertTimeString(dateTime, endTime);
0574                     if (!ok)
0575                         d->setErrorParameter(UNTIL);
0576                     else if (mAlarmTime.isDateOnly()  &&  !endTime.isDateOnly())
0577                         d->setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> parameter for date-only alarm", d->optionName(UNTIL)));
0578                     if (!mAlarmTime.isDateOnly()  &&  endTime.isDateOnly())
0579                         endTime.setTime(QTime(23,59,59));
0580                     if (endTime < mAlarmTime)
0581                         d->setError(xi18nc("@info:shell", "<icode>%1</icode> earlier than <icode>%2</icode>", d->optionName(UNTIL), d->optionName(TIME)));
0582                 }
0583                 else
0584                     count = -1;
0585 
0586                 // Get the recurrence interval
0587                 int intervalOfType;
0588                 KARecurrence::Type recurType;
0589                 if (!convInterval(mParser->value(*mOptions.at(INTERVAL)), recurType, intervalOfType, !haveRecurrence))
0590                     d->setErrorParameter(INTERVAL);
0591                 else if (mAlarmTime.isDateOnly()  &&  recurType == KARecurrence::MINUTELY)
0592                     d->setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> parameter for date-only alarm", d->optionName(INTERVAL)));
0593 
0594                 if (haveRecurrence)
0595                 {
0596                     if (mRecurrence)
0597                     {
0598                         // There is a also a recurrence specified, so set up a sub-repetition.
0599                         // In this case, 'intervalOfType' is in minutes.
0600                         mRepeatInterval = KCalendarCore::Duration(intervalOfType * 60);
0601                         const KCalendarCore::Duration longestInterval = mRecurrence->longestInterval();
0602                         if (mRepeatInterval * count > longestInterval)
0603                             d->setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> and <icode>%2</icode> parameters: repetition is longer than <icode>%3</icode> interval",
0604                                            d->optionName(INTERVAL), d->optionName(REPEAT), d->optionName(RECURRENCE)));
0605                         mRepeatCount = count;
0606                     }
0607                 }
0608                 else
0609                 {
0610                     // There is no other recurrence specified, so convert the repetition
0611                     // parameters into a KCal::Recurrence
0612                     mRecurrence = new KARecurrence;
0613                     mRecurrence->set(recurType, intervalOfType, count, mAlarmTime, endTime);
0614                 }
0615             }
0616             else
0617             {
0618                 if (mParser->isSet(*mOptions.at(REPEAT)))
0619                     d->setErrorRequires(REPEAT, INTERVAL);
0620                 else if (mParser->isSet(*mOptions.at(UNTIL)))
0621                     d->setErrorRequires(UNTIL, INTERVAL);
0622             }
0623 
0624             const bool audioRepeat = mParser->isSet(*mOptions.at(PLAY_REPEAT));
0625             if (audioRepeat  ||  mParser->isSet(*mOptions.at(PLAY)))
0626             {
0627                 // Play a sound with the alarm
0628                 const Option opt = audioRepeat ? PLAY_REPEAT : PLAY;
0629                 if (audioRepeat  &&  mParser->isSet(*mOptions.at(PLAY)))
0630                     d->setErrorIncompatible(PLAY, PLAY_REPEAT);
0631                 if (mParser->isSet(*mOptions.at(BEEP)))
0632                     d->setErrorIncompatible(BEEP, opt);
0633 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT
0634                 else if (mParser->isSet(*mOptions.at(SPEAK)))
0635                     d->setErrorIncompatible(SPEAK, opt);
0636 #endif
0637                 mAudioFile = mParser->value(*mOptions.at(audioRepeat ? PLAY_REPEAT : PLAY));
0638                 if (mParser->isSet(*mOptions.at(VOLUME)))
0639                 {
0640                     bool ok;
0641                     const int volumepc = mParser->value(*mOptions.at(VOLUME)).toInt(&ok);
0642                     if (!ok  ||  volumepc < 0  ||  volumepc > 100)
0643                         d->setErrorParameter(VOLUME);
0644                     mAudioVolume = static_cast<float>(volumepc) / 100;
0645                 }
0646             }
0647             else if (mParser->isSet(*mOptions.at(VOLUME)))
0648                 d->setErrorRequires(VOLUME, PLAY, PLAY_REPEAT);
0649 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT
0650             if (mParser->isSet(*mOptions.at(SPEAK)))
0651             {
0652                 if (mParser->isSet(*mOptions.at(BEEP)))
0653                     d->setErrorIncompatible(BEEP, SPEAK);
0654             }
0655 #endif
0656             const bool onceOnly = mParser->isSet(*mOptions.at(REMINDER_ONCE));
0657             if (mParser->isSet(*mOptions.at(REMINDER))  ||  onceOnly)
0658             {
0659                 // Issue a reminder alarm in advance of or after the main alarm
0660                 if (onceOnly  &&  mParser->isSet(*mOptions.at(REMINDER)))
0661                     d->setErrorIncompatible(REMINDER, REMINDER_ONCE);
0662                 const Option opt = onceOnly ? REMINDER_ONCE : REMINDER;
0663                 KARecurrence::Type recurType;
0664                 QString optval = mParser->value(*mOptions.at(onceOnly ? REMINDER_ONCE : REMINDER));
0665                 const bool after = (optval.at(0) == QLatin1Char('+'));
0666                 if (after)
0667                     optval.remove(0, 1);   // it's a reminder after the main alarm
0668                 if (!convInterval(optval, recurType, mReminderMinutes))
0669                     d->setErrorParameter(opt);
0670                 else if (recurType == KARecurrence::MINUTELY  &&  mAlarmTime.isDateOnly())
0671                     d->setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> parameter for date-only alarm", d->optionName(opt)));
0672                 if (after)
0673                     mReminderMinutes = -mReminderMinutes;
0674                 if (onceOnly)
0675                     mFlags |= KAEvent::REMINDER_ONCE;
0676             }
0677 
0678             if (mParser->isSet(*mOptions.at(LATE_CANCEL)))
0679             {
0680                 KARecurrence::Type recurType;
0681                 const bool ok = convInterval(mParser->value(*mOptions.at(LATE_CANCEL)), recurType, mLateCancel);
0682                 if (!ok)
0683                     d->setErrorParameter(LATE_CANCEL);
0684             }
0685             else if (mParser->isSet(*mOptions.at(AUTO_CLOSE)))
0686                 d->setErrorRequires(AUTO_CLOSE, LATE_CANCEL);
0687 
0688             if (mParser->isSet(*mOptions.at(NOTIFY)))
0689             {
0690                 if (mParser->isSet(*mOptions.at(COLOUR)))
0691                     d->setErrorIncompatible(NOTIFY, COLOUR);
0692                 if (mParser->isSet(*mOptions.at(COLOURFG)))
0693                     d->setErrorIncompatible(NOTIFY, COLOURFG);
0694                 if (mParser->isSet(*mOptions.at(ACK_CONFIRM)))
0695                     d->setErrorIncompatible(NOTIFY, ACK_CONFIRM);
0696                 if (mParser->isSet(*mOptions.at(PLAY)))
0697                     d->setErrorIncompatible(NOTIFY, PLAY);
0698                 if (mParser->isSet(*mOptions.at(AUTO_CLOSE)))
0699                     d->setErrorIncompatible(NOTIFY, AUTO_CLOSE);
0700             }
0701 
0702             if (mParser->isSet(*mOptions.at(ACK_CONFIRM)))
0703                 mFlags |= KAEvent::CONFIRM_ACK;
0704             if (mParser->isSet(*mOptions.at(AUTO_CLOSE)))
0705                 mFlags |= KAEvent::AUTO_CLOSE;
0706             if (mParser->isSet(*mOptions.at(BEEP)))
0707                 mFlags |= KAEvent::BEEP;
0708 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT
0709             if (mParser->isSet(*mOptions.at(SPEAK)))
0710                 mFlags |= KAEvent::SPEAK;
0711 #endif
0712             if (mParser->isSet(*mOptions.at(NOTIFY)))
0713                 mFlags |= KAEvent::NOTIFY;
0714             if (mParser->isSet(*mOptions.at(KORGANIZER)))
0715                 mFlags |= KAEvent::COPY_KORGANIZER;
0716             if (mParser->isSet(*mOptions.at(DISABLE)))
0717                 mFlags |= KAEvent::DISABLED;
0718             if (audioRepeat)
0719                 mFlags |= KAEvent::REPEAT_SOUND;
0720             if (mParser->isSet(*mOptions.at(LOGIN)))
0721                 mFlags |= KAEvent::REPEAT_AT_LOGIN;
0722             if (mParser->isSet(*mOptions.at(BCC)))
0723                 mFlags |= KAEvent::EMAIL_BCC;
0724             if (mAlarmTime.isDateOnly())
0725                 mFlags |= KAEvent::ANY_TIME;
0726             break;
0727         }
0728         case NONE:
0729         {
0730             // No arguments - run interactively & display the main window
0731             if (!mError.isEmpty())
0732                 break;
0733             qCDebug(KALARM_LOG) << "CommandOptions::process: Interactive";
0734             QStringList errors;
0735             if (mParser->isSet(*mOptions.at(ACK_CONFIRM)))
0736                 errors << d->optionName(ACK_CONFIRM);
0737             if (mParser->isSet(*mOptions.at(ATTACH)))
0738                 errors << d->optionName(ATTACH);
0739             if (mParser->isSet(*mOptions.at(AUTO_CLOSE)))
0740                 errors << d->optionName(AUTO_CLOSE);
0741             if (mParser->isSet(*mOptions.at(BCC)))
0742                 errors << d->optionName(BCC);
0743             if (mParser->isSet(*mOptions.at(BEEP)))
0744                 errors << d->optionName(BEEP);
0745             if (mParser->isSet(*mOptions.at(COLOUR)))
0746                 errors << d->optionName(COLOUR);
0747             if (mParser->isSet(*mOptions.at(COLOURFG)))
0748                 errors << d->optionName(COLOURFG);
0749             if (mParser->isSet(*mOptions.at(DISABLE)))
0750                 errors << d->optionName(DISABLE);
0751             if (mParser->isSet(*mOptions.at(FROM_ID)))
0752                 errors << d->optionName(FROM_ID);
0753             if (mParser->isSet(*mOptions.at(KORGANIZER)))
0754                 errors << d->optionName(KORGANIZER);
0755             if (mParser->isSet(*mOptions.at(LATE_CANCEL)))
0756                 errors << d->optionName(LATE_CANCEL);
0757             if (mParser->isSet(*mOptions.at(LOGIN)))
0758                 errors << d->optionName(LOGIN);
0759             if (mParser->isSet(*mOptions.at(NAME)))
0760                 errors << d->optionName(NAME);
0761             if (mParser->isSet(*mOptions.at(NOTIFY)))
0762                 errors << d->optionName(NOTIFY);
0763             if (mParser->isSet(*mOptions.at(PLAY)))
0764                 errors << d->optionName(PLAY);
0765             if (mParser->isSet(*mOptions.at(PLAY_REPEAT)))
0766                 errors << d->optionName(PLAY_REPEAT);
0767             if (mParser->isSet(*mOptions.at(REMINDER)))
0768                 errors << d->optionName(REMINDER);
0769             if (mParser->isSet(*mOptions.at(REMINDER_ONCE)))
0770                 errors << d->optionName(REMINDER_ONCE);
0771 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT
0772             if (mParser->isSet(*mOptions.at(SPEAK)))
0773                 errors << d->optionName(SPEAK);
0774 #endif
0775             if (mParser->isSet(*mOptions.at(NOTIFY)))
0776                 errors << d->optionName(NOTIFY);
0777             if (mParser->isSet(*mOptions.at(SUBJECT)))
0778                 errors << d->optionName(SUBJECT);
0779             if (mParser->isSet(*mOptions.at(TIME)))
0780                 errors << d->optionName(TIME);
0781             if (mParser->isSet(*mOptions.at(VOLUME)))
0782                 errors << d->optionName(VOLUME);
0783             if (!errors.isEmpty())
0784                 mError = errors.join(QLatin1Char(' ')) + i18nc("@info:shell", ": option(s) only valid with an appropriate action option or message");
0785             break;
0786         }
0787         default:
0788             break;
0789     }
0790 
0791     if (!mError.isEmpty())
0792         d->setError(mError);
0793 }
0794 
0795 QString CommandOptions::commandName() const
0796 {
0797     return d->optionName(d->mCommandOpt);
0798 }
0799 
0800 void CommandOptions::printError(const QString& errmsg)
0801 {
0802     // Note: we can't use mArgs->usage() since that also quits any other
0803     // running 'instances' of the program.
0804     std::cerr << errmsg.toLocal8Bit().data()
0805               << i18nc("@info:shell", "\nUse --help to get a list of available command line options.\n").toLocal8Bit().data();
0806 }
0807 
0808 // Fetch one of the arguments (i.e. not belonging to any option).
0809 QString CommandOptions::arg(int n)
0810 {
0811     const QStringList args = mParser->positionalArguments();
0812     return (n < args.size()) ? args[n] : QString();
0813 }
0814 
0815 /*===========================================================================*/
0816 
0817 void CommandOptions::Private::setError(const QString& errmsg)
0818 {
0819     qCWarning(KALARM_LOG) << "CommandOptions::setError:" << errmsg;
0820     p->mCommand = CMD_ERROR;
0821     if (p->mError.isEmpty())
0822         p->mError = errmsg + i18nc("@info:shell", "\nUse --help to get a list of available command line options.\n");
0823 }
0824 
0825 /******************************************************************************
0826 * Check if the given command option is specified, and if so set mCommand etc.
0827 * If another command option has also been detected, issue an error.
0828 * If 'allowedEditType' is set, supersede any previous specification of that
0829 * edit type with the given command option - this allows, e.g., --mail to be
0830 * used along with --edit-new-email so the user can specify addressees.
0831 */
0832 bool CommandOptions::Private::checkCommand(Option command, Command code, EditAlarmDlg::Type allowedEditType)
0833 {
0834     if (!p->mError.isEmpty()
0835     ||  !p->mParser->isSet(*p->mOptions.at(command)))
0836         return false;
0837     if (p->mCommand != NONE
0838     &&  (allowedEditType == EditAlarmDlg::NO_TYPE  ||  (p->mCommand != NEW || p->mEditType != allowedEditType)))
0839         setErrorIncompatible(mCommandOpt, command);
0840     qCDebug(KALARM_LOG).nospace() << "CommandOptions::checkCommand: " << optionName(command);
0841     p->mCommand = code;
0842     mCommandOpt = command;
0843     return true;
0844 }
0845 
0846 // Set the error message to "--opt requires --opt2" or "--opt requires --opt2 or --opt3".
0847 void CommandOptions::Private::setErrorRequires(Option opt, Option opt2, Option opt3)
0848 {
0849     if (opt3 == Num_Options)
0850         setError(xi18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode>", optionName(opt), optionName(opt2)));
0851     else
0852         setError(xi18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode> or <icode>%3</icode>", optionName(opt), optionName(opt2), optionName(opt3)));
0853 }
0854 
0855 void CommandOptions::Private::setErrorParameter(Option opt)
0856 {
0857     setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> parameter", optionName(opt)));
0858 }
0859 
0860 void CommandOptions::Private::setErrorIncompatible(Option opt1, Option opt2)
0861 {
0862     setError(xi18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", optionName(opt1), optionName(opt2)));
0863 }
0864 
0865 void CommandOptions::Private::checkEditType(EditAlarmDlg::Type type1, EditAlarmDlg::Type type2, Option opt)
0866 {
0867     if (p->mParser->isSet(*p->mOptions.at(opt))  &&  p->mCommand != NONE
0868     &&  ((p->mCommand != NEW && p->mCommand != EDIT_NEW)  ||  (p->mEditType != type1 && (type2 == EditAlarmDlg::NO_TYPE || p->mEditType != type2))))
0869         setErrorIncompatible(opt, mCommandOpt);
0870 }
0871 
0872 QString CommandOptions::Private::optionName(Option opt, bool shortName) const
0873 {
0874     if (opt == Opt_Message)
0875         return QStringLiteral("message");
0876     const QStringList names = p->mOptions.at(opt)->names();
0877     if (names.empty())
0878         return {};
0879     for (const QString& name : names)
0880     {
0881         if (shortName  &&  name.size() == 1)
0882             return QStringLiteral("-") + name;
0883         else if (!shortName  &&  name.size() > 1)
0884             return QStringLiteral("--") + name;
0885     }
0886     return QStringLiteral("-") + names[0];
0887 }
0888 
0889 namespace
0890 {
0891 
0892 /******************************************************************************
0893 * Convert a non-zero positive time interval command line parameter.
0894 * 'timeInterval' receives the count for the recurType. If 'allowMonthYear' is
0895 * false, weeks and days are converted to minutes.
0896 * Reply = true if successful.
0897 */
0898 bool convInterval(const QString& timeParam, KARecurrence::Type& recurType, int& timeInterval, bool allowMonthYear)
0899 {
0900     QByteArray timeString = timeParam.toLocal8Bit();
0901     // Get the recurrence interval
0902     bool ok = true;
0903     uint interval = 0;
0904     uint length = timeString.length();
0905     switch (timeString.at(length - 1))
0906     {
0907         case 'Y':
0908             if (!allowMonthYear)
0909                 ok = false;
0910             recurType = KARecurrence::ANNUAL_DATE;
0911             timeString = timeString.left(length - 1);
0912             break;
0913         case 'W':
0914             recurType = KARecurrence::WEEKLY;
0915             timeString = timeString.left(length - 1);
0916             break;
0917         case 'D':
0918             recurType = KARecurrence::DAILY;
0919             timeString = timeString.left(length - 1);
0920             break;
0921         case 'M':
0922         {
0923             int i = timeString.indexOf('H');
0924             if (i < 0)
0925             {
0926                 if (!allowMonthYear)
0927                     ok = false;
0928                 recurType = KARecurrence::MONTHLY_DAY;
0929                 timeString = timeString.left(length - 1);
0930             }
0931             else
0932             {
0933                 recurType = KARecurrence::MINUTELY;
0934                 interval = timeString.left(i).toUInt(&ok) * 60;
0935                 timeString = timeString.mid(i + 1, length - i - 2);
0936             }
0937             break;
0938         }
0939         default:       // should be a digit
0940             recurType = KARecurrence::MINUTELY;
0941             break;
0942     }
0943     if (ok)
0944         interval += timeString.toUInt(&ok);
0945     if (!allowMonthYear)
0946     {
0947         // Convert time interval to minutes
0948         switch (recurType)
0949         {
0950             case KARecurrence::WEEKLY:
0951                 interval *= 7;
0952                 [[fallthrough]];   // fall through to DAILY
0953             case KARecurrence::DAILY:
0954                 interval *= 24*60;
0955                 recurType = KARecurrence::MINUTELY;
0956                 break;
0957             default:
0958                 break;
0959         }
0960     }
0961     timeInterval = static_cast<int>(interval);
0962     return ok && interval;
0963 }
0964 
0965 }
0966 
0967 // vim: et sw=4: