File indexing completed on 2024-12-29 04:54:44

0001 /*
0002    SPDX-FileCopyrightText: 2013-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "sieveactionwidgetlister.h"
0008 #include "autocreatescriptutil_p.h"
0009 #include "commonwidgets/sievehelpbutton.h"
0010 #include "sieveactions/sieveaction.h"
0011 #include "sieveactions/sieveactionlist.h"
0012 #include "sieveeditorgraphicalmodewidget.h"
0013 #include "sievescriptdescriptiondialog.h"
0014 
0015 #include <KLocalizedString>
0016 #include <QIcon>
0017 #include <QPushButton>
0018 #include <QUrl>
0019 
0020 #include "libksieveui_debug.h"
0021 #include <QComboBox>
0022 #include <QGridLayout>
0023 #include <QLabel>
0024 #include <QPointer>
0025 #include <QToolButton>
0026 #include <QWhatsThis>
0027 
0028 #include "autocreatescripts/sieveactions/sieveactionsetvariable.h"
0029 
0030 using namespace KSieveUi;
0031 
0032 static int MINIMUMACTION = 1;
0033 static int MAXIMUMACTION = 8;
0034 
0035 SieveActionWidget::SieveActionWidget(SieveEditorGraphicalModeWidget *graphicalModeWidget, QWidget *parent)
0036     : QWidget(parent)
0037     , mSieveGraphicalModeWidget(graphicalModeWidget)
0038 {
0039     initWidget();
0040 }
0041 
0042 SieveActionWidget::~SieveActionWidget()
0043 {
0044     qDeleteAll(mActionList);
0045     mActionList.clear();
0046 }
0047 
0048 bool SieveActionWidget::isConfigurated() const
0049 {
0050     return mComboBox->currentIndex() != (mComboBox->count() - 1);
0051 }
0052 
0053 void SieveActionWidget::setFilterAction(QWidget *widget)
0054 {
0055     if (mLayout->itemAtPosition(1, 3)) {
0056         delete mLayout->itemAtPosition(1, 3)->widget();
0057     }
0058 
0059     if (widget) {
0060         mLayout->addWidget(widget, 1, 3);
0061     } else {
0062         mLayout->addWidget(new QLabel(i18n("Please select an action."), this), 1, 3);
0063     }
0064 }
0065 
0066 void SieveActionWidget::generatedScript(QString &script, QStringList &required, bool onlyActions, bool inForEveryPartLoop)
0067 {
0068     const int index = mComboBox->currentIndex();
0069     if (index != mComboBox->count() - 1) {
0070         KSieveUi::SieveAction *widgetAction = mActionList.at(mComboBox->currentIndex());
0071         QWidget *currentWidget = mLayout->itemAtPosition(1, 3)->widget();
0072         const QStringList lstRequires = widgetAction->needRequires(currentWidget);
0073         for (const QString &r : lstRequires) {
0074             if (!required.contains(r)) {
0075                 required.append(r);
0076             }
0077         }
0078         QString comment = widgetAction->comment();
0079         QString indent;
0080         if (!onlyActions) {
0081             indent += AutoCreateScriptUtil::indentation();
0082         }
0083         if (inForEveryPartLoop) {
0084             indent += AutoCreateScriptUtil::indentation();
0085         }
0086         if (!comment.trimmed().isEmpty()) {
0087             const QList<QStringView> commentList = QStringView(comment).split(QLatin1Char('\n'));
0088             for (const QStringView str : commentList) {
0089                 if (str.isEmpty()) {
0090                     script += QLatin1Char('\n');
0091                 } else {
0092                     script += indent + QLatin1Char('#') + str + QLatin1Char('\n');
0093                 }
0094             }
0095         }
0096         script += indent + widgetAction->code(currentWidget) + QLatin1Char('\n');
0097     }
0098 }
0099 
0100 void SieveActionWidget::initWidget()
0101 {
0102     mLayout = new QGridLayout(this);
0103     mLayout->setContentsMargins({});
0104 
0105     mComboBox = new QComboBox;
0106     mComboBox->setEditable(false);
0107     mComboBox->setMinimumWidth(50);
0108     const QList<KSieveUi::SieveAction *> list = KSieveUi::SieveActionList::actionList(mSieveGraphicalModeWidget);
0109     QStringList listCapabilities = mSieveGraphicalModeWidget->sieveCapabilities();
0110     // imapflags was old name of imap4flags but still used.
0111     if (listCapabilities.contains(QLatin1StringView("imap4flags"))) {
0112         listCapabilities.append(QStringLiteral("imapflags"));
0113     }
0114     for (const auto &action : list) {
0115         if (action->needCheckIfServerHasCapability()) {
0116             if (listCapabilities.contains(action->serverNeedsCapability())) {
0117                 // append to the list of actions:
0118                 mActionList.append(action);
0119                 connect(action, &SieveAction::valueChanged, this, &SieveActionWidget::valueChanged);
0120                 // add (i18n-ized) name to combo box
0121                 mComboBox->addItem(action->label(), action->name());
0122             } else {
0123                 delete (action);
0124             }
0125         } else {
0126             // append to the list of actions:
0127             mActionList.append(action);
0128             connect(action, &SieveAction::valueChanged, this, &SieveActionWidget::valueChanged);
0129             // add (i18n-ized) name to combo box
0130             mComboBox->addItem(action->label(), action->name());
0131         }
0132     }
0133 
0134     mHelpButton = new SieveHelpButton(this);
0135     connect(mHelpButton, &SieveHelpButton::clicked, this, &SieveActionWidget::slotHelp);
0136     mLayout->addWidget(mHelpButton, 1, 0);
0137 
0138     mCommentButton = new QToolButton(this);
0139     mCommentButton->setToolTip(i18n("Add comment"));
0140     mLayout->addWidget(mCommentButton, 1, 1);
0141     mCommentButton->setIcon(QIcon::fromTheme(QStringLiteral("view-pim-notes")));
0142     connect(mCommentButton, &QToolButton::clicked, this, &SieveActionWidget::slotAddComment);
0143 
0144     mComboBox->addItem(QLatin1StringView(""));
0145 
0146     mComboBox->setMaxCount(mComboBox->count());
0147     mComboBox->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
0148     setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
0149     mComboBox->adjustSize();
0150     mLayout->addWidget(mComboBox, 1, 2);
0151 
0152     updateGeometry();
0153 
0154     connect(mComboBox, &QComboBox::activated, this, &SieveActionWidget::slotActionChanged);
0155 
0156     mAdd = new QPushButton(this);
0157     mAdd->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0158     mAdd->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
0159 
0160     mRemove = new QPushButton(this);
0161     mRemove->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0162     mRemove->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
0163     mLayout->addWidget(mAdd, 1, 4);
0164     mLayout->addWidget(mRemove, 1, 5);
0165 
0166     // redirect focus to the filter action combo box
0167     setFocusProxy(mComboBox);
0168 
0169     connect(mAdd, &QPushButton::clicked, this, &SieveActionWidget::slotAddWidget);
0170     connect(mRemove, &QPushButton::clicked, this, &SieveActionWidget::slotRemoveWidget);
0171 
0172     clear();
0173 }
0174 
0175 void SieveActionWidget::slotHelp()
0176 {
0177     const int index = mComboBox->currentIndex();
0178     if (index < mActionList.count()) {
0179         KSieveUi::SieveAction *action = mActionList.at(index);
0180         const QString help = action->help();
0181         const QUrl href = action->href();
0182         const QString fullWhatsThis = AutoCreateScriptUtil::createFullWhatsThis(help, href.toString());
0183         QWhatsThis::showText(QCursor::pos(), fullWhatsThis, mHelpButton);
0184     }
0185 }
0186 
0187 void SieveActionWidget::clear()
0188 {
0189     mComboBox->setCurrentIndex(mComboBox->count() - 1);
0190     setFilterAction(nullptr);
0191     mCommentButton->setEnabled(false);
0192     mHelpButton->setEnabled(false);
0193 }
0194 
0195 void SieveActionWidget::slotAddComment()
0196 {
0197     const int index = mComboBox->currentIndex();
0198     if (index < mActionList.count()) {
0199         KSieveUi::SieveAction *action = mActionList.at(index);
0200         const QString comment = action->comment();
0201         QPointer<SieveScriptDescriptionDialog> dlg = new SieveScriptDescriptionDialog;
0202         dlg->setDescription(comment);
0203         if (dlg->exec()) {
0204             action->setComment(dlg->description());
0205             Q_EMIT valueChanged();
0206         }
0207         delete dlg;
0208     }
0209 }
0210 
0211 void SieveActionWidget::slotActionChanged(int index)
0212 {
0213     if (index < mActionList.count()) {
0214         KSieveUi::SieveAction *action = mActionList.at(index);
0215         mHelpButton->setEnabled(!action->help().isEmpty());
0216         mCommentButton->setEnabled(true);
0217         setFilterAction(action->createParamWidget(this));
0218         // All actions after stop will not execute => don't allow to add more actions.
0219         const bool enableAddAction = (action->name() != QLatin1StringView("stop"));
0220         mAdd->setEnabled(enableAddAction);
0221     } else {
0222         mAdd->setEnabled(true);
0223         mCommentButton->setEnabled(false);
0224         setFilterAction(nullptr);
0225         mHelpButton->setEnabled(false);
0226     }
0227     Q_EMIT valueChanged();
0228 }
0229 
0230 void SieveActionWidget::slotAddWidget()
0231 {
0232     Q_EMIT valueChanged();
0233     Q_EMIT addWidget(this);
0234 }
0235 
0236 void SieveActionWidget::slotRemoveWidget()
0237 {
0238     Q_EMIT valueChanged();
0239     Q_EMIT removeWidget(this);
0240 }
0241 
0242 void SieveActionWidget::updateAddRemoveButton(bool addButtonEnabled, bool removeButtonEnabled)
0243 {
0244     mAdd->setEnabled(addButtonEnabled);
0245     mRemove->setEnabled(removeButtonEnabled);
0246 }
0247 
0248 void SieveActionWidget::setLocaleVariable(const SieveGlobalVariableActionWidget::VariableElement &var)
0249 {
0250     const int index = mComboBox->findData(QStringLiteral("set"));
0251     if (index != -1) {
0252         mComboBox->setCurrentIndex(index);
0253         slotActionChanged(index);
0254         auto localVar = qobject_cast<KSieveUi::SieveActionSetVariable *>(mActionList.at(index));
0255         if (localVar) {
0256             localVar->setLocalVariable(this, var);
0257         }
0258     } else {
0259         // error += i18n("Script contains unsupported feature \"%1\"", actionName) + QLatin1Char('\n');
0260         // qCDebug(LIBKSIEVEUI_LOG) << "Action " << actionName << " not supported";
0261     }
0262 }
0263 
0264 void SieveActionWidget::setAction(const QString &actionName, QXmlStreamReader &element, const QString &comment, QString &error)
0265 {
0266     const int index = mComboBox->findData(actionName);
0267     if (index != -1) {
0268         mComboBox->setCurrentIndex(index);
0269         slotActionChanged(index);
0270         KSieveUi::SieveAction *action = mActionList.at(index);
0271         action->setParamWidgetValue(element, this, error);
0272         action->setComment(comment);
0273     } else {
0274         error += i18n("Script contains unsupported feature \"%1\"", actionName) + QLatin1Char('\n');
0275         qCDebug(LIBKSIEVEUI_LOG) << "Action " << actionName << " not supported";
0276         element.skipCurrentElement();
0277     }
0278 }
0279 
0280 SieveActionWidgetLister::SieveActionWidgetLister(SieveEditorGraphicalModeWidget *graphicalModeWidget, QWidget *parent)
0281     : KPIM::KWidgetLister(false, MINIMUMACTION, MAXIMUMACTION, parent)
0282     , mSieveGraphicalModeWidget(graphicalModeWidget)
0283 {
0284     slotClear();
0285     updateAddRemoveButton();
0286 }
0287 
0288 SieveActionWidgetLister::~SieveActionWidgetLister() = default;
0289 
0290 void SieveActionWidgetLister::slotAddWidget(QWidget *w)
0291 {
0292     addWidgetAfterThisWidget(w);
0293     updateAddRemoveButton();
0294 }
0295 
0296 void SieveActionWidgetLister::slotRemoveWidget(QWidget *w)
0297 {
0298     removeWidget(w);
0299     updateAddRemoveButton();
0300 }
0301 
0302 void SieveActionWidgetLister::updateAddRemoveButton()
0303 {
0304     QList<QWidget *> widgetList = widgets();
0305     const int numberOfWidget(widgetList.count());
0306     bool addButtonEnabled = false;
0307     bool removeButtonEnabled = false;
0308     if (numberOfWidget <= widgetsMinimum()) {
0309         addButtonEnabled = true;
0310         removeButtonEnabled = false;
0311     } else if (numberOfWidget >= widgetsMaximum()) {
0312         addButtonEnabled = false;
0313         removeButtonEnabled = true;
0314     } else {
0315         addButtonEnabled = true;
0316         removeButtonEnabled = true;
0317     }
0318     QList<QWidget *>::ConstIterator wIt = widgetList.constBegin();
0319     QList<QWidget *>::ConstIterator wEnd = widgetList.constEnd();
0320     for (; wIt != wEnd; ++wIt) {
0321         auto w = qobject_cast<SieveActionWidget *>(*wIt);
0322         w->updateAddRemoveButton(addButtonEnabled, removeButtonEnabled);
0323     }
0324 }
0325 
0326 void SieveActionWidgetLister::generatedScript(QString &script, QStringList &requireModules, bool onlyActions, bool inForEveryPartLoop)
0327 {
0328     const QList<QWidget *> widgetList = widgets();
0329     QList<QWidget *>::ConstIterator wIt = widgetList.constBegin();
0330     QList<QWidget *>::ConstIterator wEnd = widgetList.constEnd();
0331     for (; wIt != wEnd; ++wIt) {
0332         auto w = qobject_cast<SieveActionWidget *>(*wIt);
0333         w->generatedScript(script, requireModules, onlyActions, inForEveryPartLoop);
0334     }
0335 }
0336 
0337 void SieveActionWidgetLister::reconnectWidget(SieveActionWidget *w)
0338 {
0339     connect(w, &SieveActionWidget::addWidget, this, &SieveActionWidgetLister::slotAddWidget, Qt::UniqueConnection);
0340     connect(w, &SieveActionWidget::removeWidget, this, &SieveActionWidgetLister::slotRemoveWidget, Qt::UniqueConnection);
0341     connect(w, &SieveActionWidget::valueChanged, this, &SieveActionWidgetLister::valueChanged, Qt::UniqueConnection);
0342 }
0343 
0344 void SieveActionWidgetLister::clearWidget(QWidget *aWidget)
0345 {
0346     if (aWidget) {
0347         auto widget = static_cast<SieveActionWidget *>(aWidget);
0348         widget->clear();
0349         updateAddRemoveButton();
0350     }
0351     Q_EMIT valueChanged();
0352 }
0353 
0354 QWidget *SieveActionWidgetLister::createWidget(QWidget *parent)
0355 {
0356     auto w = new SieveActionWidget(mSieveGraphicalModeWidget, parent);
0357     reconnectWidget(w);
0358     return w;
0359 }
0360 
0361 int SieveActionWidgetLister::actionNumber() const
0362 {
0363     return widgets().count();
0364 }
0365 
0366 void SieveActionWidgetLister::loadLocalVariable(const SieveGlobalVariableActionWidget::VariableElement &var)
0367 {
0368     auto w = qobject_cast<SieveActionWidget *>(widgets().constLast());
0369     if (w->isConfigurated()) {
0370         addWidgetAfterThisWidget(widgets().constLast());
0371         w = qobject_cast<SieveActionWidget *>(widgets().constLast());
0372     }
0373     w->setLocaleVariable(var);
0374 }
0375 
0376 void SieveActionWidgetLister::loadScript(QXmlStreamReader &element, bool onlyActions, QString &error)
0377 {
0378     QString comment;
0379     if (onlyActions) {
0380         const QStringView tagName = element.name();
0381         if (tagName == QLatin1StringView("action")) {
0382             if (element.attributes().hasAttribute(QLatin1StringView("name"))) {
0383                 const QString actionName = element.attributes().value(QLatin1StringView("name")).toString();
0384                 auto w = qobject_cast<SieveActionWidget *>(widgets().constLast());
0385                 if (w->isConfigurated()) {
0386                     addWidgetAfterThisWidget(widgets().constLast());
0387                     w = qobject_cast<SieveActionWidget *>(widgets().constLast());
0388                 }
0389                 w->setAction(actionName, element, comment, error);
0390                 // comment.clear();
0391             } else if (tagName == QLatin1StringView("crlf")) {
0392                 element.skipCurrentElement();
0393                 // nothing
0394             } else {
0395                 qCDebug(LIBKSIEVEUI_LOG) << " SieveActionWidgetLister::loadScript don't have name attribute " << tagName;
0396             }
0397         } else {
0398             qCDebug(LIBKSIEVEUI_LOG) << " SieveActionWidgetLister::loadScript Unknown tag name " << tagName;
0399         }
0400     } else {
0401         bool firstAction = true;
0402         bool previousActionWasAComment = false;
0403         while (element.readNextStartElement()) {
0404             const QStringView tagName = element.name();
0405             if (tagName == QLatin1StringView("action") || tagName == QLatin1StringView("control") /*for break action*/) {
0406                 if (element.attributes().hasAttribute(QLatin1StringView("name"))) {
0407                     const QString actionName = element.attributes().value(QLatin1StringView("name")).toString();
0408                     if (tagName == QLatin1StringView("control") && actionName == QLatin1StringView("if")) {
0409                         qCDebug(LIBKSIEVEUI_LOG) << "We found an loop if in a loop if. Not supported";
0410                         error += i18n("We detected a loop if in a loop if. It's not supported") + QLatin1Char('\n');
0411                     }
0412                     if (firstAction) {
0413                         firstAction = false;
0414                     } else {
0415                         addWidgetAfterThisWidget(widgets().constLast());
0416                     }
0417                     auto w = qobject_cast<SieveActionWidget *>(widgets().constLast());
0418                     w->setAction(actionName, element, comment, error);
0419                     comment.clear();
0420                 } else {
0421                     qCDebug(LIBKSIEVEUI_LOG) << " SieveActionWidgetLister::loadScript don't have name attribute " << tagName;
0422                 }
0423                 previousActionWasAComment = false;
0424             } else if (tagName == QLatin1StringView("comment")) {
0425                 if (!comment.isEmpty()) {
0426                     comment += QLatin1Char('\n');
0427                 }
0428                 previousActionWasAComment = true;
0429                 comment += element.readElementText();
0430             } else if (tagName == QLatin1StringView("crlf")) {
0431                 // Add new line if previous action was a comment
0432                 if (previousActionWasAComment) {
0433                     comment += QLatin1Char('\n');
0434                 }
0435                 element.skipCurrentElement();
0436             } else {
0437                 qCDebug(LIBKSIEVEUI_LOG) << " SieveActionWidgetLister::loadScript unknown tagName " << tagName;
0438             }
0439         }
0440     }
0441 }
0442 
0443 #include "moc_sieveactionwidgetlister.cpp"