File indexing completed on 2024-12-22 05:00:53

0001 /*
0002     SPDX-FileCopyrightText: 2003 Andreas Gungl <a.gungl@gmx.de>
0003     SPDX-FileCopyrightText: 2012-2024 Laurent Montel <montel@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-only
0006 */
0007 
0008 #include "filterlogdialog.h"
0009 #include "mailfilterpurposemenuwidget.h"
0010 #include <MailCommon/FilterLog>
0011 #include <PimCommon/PurposeMenuMessageWidget>
0012 #include <TextCustomEditor/PlainTextEditorWidget>
0013 
0014 #include <KLocalizedString>
0015 #include <KMessageBox>
0016 #include <KStandardAction>
0017 #include <QFileDialog>
0018 #include <QIcon>
0019 #include <QVBoxLayout>
0020 
0021 #include <QAction>
0022 #include <QCheckBox>
0023 #include <QGroupBox>
0024 #include <QLabel>
0025 #include <QMenu>
0026 #include <QPointer>
0027 #include <QSpinBox>
0028 #include <QStringList>
0029 
0030 #include <KConfigGroup>
0031 #include <KGuiItem>
0032 #include <KSharedConfig>
0033 #include <QDialogButtonBox>
0034 #include <QHBoxLayout>
0035 #include <QPushButton>
0036 #include <cerrno>
0037 
0038 using namespace MailCommon;
0039 
0040 FilterLogDialog::FilterLogDialog(QWidget *parent)
0041     : QDialog(parent)
0042     , mUser1Button(new QPushButton(this))
0043     , mUser2Button(new QPushButton(this))
0044 {
0045     setWindowTitle(i18nc("@title:window", "Filter Log Viewer"));
0046     auto mainLayout = new QVBoxLayout(this);
0047     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
0048     buttonBox->addButton(mUser1Button, QDialogButtonBox::ActionRole);
0049     buttonBox->addButton(mUser2Button, QDialogButtonBox::ActionRole);
0050     connect(buttonBox, &QDialogButtonBox::rejected, this, &FilterLogDialog::reject);
0051     setWindowIcon(QIcon::fromTheme(QStringLiteral("view-filter")));
0052     setModal(false);
0053 
0054     buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
0055     KGuiItem::assign(mUser1Button, KStandardGuiItem::clear());
0056     KGuiItem::assign(mUser2Button, KStandardGuiItem::saveAs());
0057     auto page = new QFrame(this);
0058 
0059     auto pageVBoxLayout = new QVBoxLayout;
0060     page->setLayout(pageVBoxLayout);
0061     pageVBoxLayout->setContentsMargins({});
0062     mainLayout->addWidget(page);
0063 
0064     auto purposeMenuMessageWidget = new PimCommon::PurposeMenuMessageWidget(this);
0065     pageVBoxLayout->addWidget(purposeMenuMessageWidget);
0066 
0067     mTextEdit = new TextCustomEditor::PlainTextEditorWidget(new FilterLogTextEdit(this), page);
0068     pageVBoxLayout->addWidget(mTextEdit);
0069 
0070     mTextEdit->setReadOnly(true);
0071     mTextEdit->editor()->setWordWrapMode(QTextOption::NoWrap);
0072     const QStringList logEntries = FilterLog::instance()->logEntries();
0073     const QString log = logEntries.join(QStringLiteral("<br>"));
0074     mTextEdit->editor()->appendHtml(log);
0075 
0076     auto purposeMenu = new MailfilterPurposeMenuWidget(this, this);
0077     connect(purposeMenu, &MailfilterPurposeMenuWidget::shareError, purposeMenuMessageWidget, &PimCommon::PurposeMenuMessageWidget::slotShareError);
0078     connect(purposeMenu, &MailfilterPurposeMenuWidget::shareSuccess, purposeMenuMessageWidget, &PimCommon::PurposeMenuMessageWidget::slotShareSuccess);
0079     auto mShareButton = new QPushButton(i18n("Share..."), this);
0080     mShareButton->setMenu(purposeMenu->menu());
0081     mShareButton->setIcon(QIcon::fromTheme(QStringLiteral("document-share")));
0082     purposeMenu->setEditorWidget(mTextEdit->editor());
0083     buttonBox->addButton(mShareButton, QDialogButtonBox::ActionRole);
0084 
0085     mLogActiveBox = new QCheckBox(i18n("&Log filter activities"), page);
0086     pageVBoxLayout->addWidget(mLogActiveBox);
0087     mLogActiveBox->setChecked(FilterLog::instance()->isLogging());
0088     connect(mLogActiveBox, &QCheckBox::clicked, this, &FilterLogDialog::slotSwitchLogState);
0089     mLogActiveBox->setWhatsThis(
0090         i18n("You can turn logging of filter activities on and off here. "
0091              "Of course, log data is collected and shown only when logging "
0092              "is turned on. "));
0093 
0094     mLogDetailsBox = new QGroupBox(i18n("Logging Details"), page);
0095     pageVBoxLayout->addWidget(mLogDetailsBox);
0096     auto layout = new QVBoxLayout;
0097     mLogDetailsBox->setLayout(layout);
0098     mLogDetailsBox->setEnabled(mLogActiveBox->isChecked());
0099     connect(mLogActiveBox, &QCheckBox::toggled, mLogDetailsBox, &QGroupBox::setEnabled);
0100 
0101     mLogPatternDescBox = new QCheckBox(i18n("Log pattern description"), mLogDetailsBox);
0102     layout->addWidget(mLogPatternDescBox);
0103     mLogPatternDescBox->setChecked(FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternDescription));
0104     connect(mLogPatternDescBox, &QCheckBox::clicked, this, &FilterLogDialog::slotChangeLogDetail);
0105 
0106     mLogRuleEvaluationBox = new QCheckBox(i18n("Log filter &rule evaluation"), mLogDetailsBox);
0107     layout->addWidget(mLogRuleEvaluationBox);
0108     mLogRuleEvaluationBox->setChecked(FilterLog::instance()->isContentTypeEnabled(FilterLog::RuleResult));
0109     connect(mLogRuleEvaluationBox, &QCheckBox::clicked, this, &FilterLogDialog::slotChangeLogDetail);
0110     mLogRuleEvaluationBox->setWhatsThis(
0111         i18n("You can control the feedback in the log concerning the "
0112              "evaluation of the filter rules of applied filters: "
0113              "having this option checked will give detailed feedback "
0114              "for each single filter rule; alternatively, only "
0115              "feedback about the result of the evaluation of all rules "
0116              "of a single filter will be given."));
0117 
0118     mLogPatternResultBox = new QCheckBox(i18n("Log filter pattern evaluation"), mLogDetailsBox);
0119     layout->addWidget(mLogPatternResultBox);
0120     mLogPatternResultBox->setChecked(FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternResult));
0121     connect(mLogPatternResultBox, &QCheckBox::clicked, this, &FilterLogDialog::slotChangeLogDetail);
0122 
0123     mLogFilterActionBox = new QCheckBox(i18n("Log filter actions"), mLogDetailsBox);
0124     layout->addWidget(mLogFilterActionBox);
0125     mLogFilterActionBox->setChecked(FilterLog::instance()->isContentTypeEnabled(FilterLog::AppliedAction));
0126     connect(mLogFilterActionBox, &QCheckBox::clicked, this, &FilterLogDialog::slotChangeLogDetail);
0127 
0128     auto hboxWidget = new QWidget(page);
0129     auto hboxHBoxLayout = new QHBoxLayout;
0130     hboxWidget->setLayout(hboxHBoxLayout);
0131     hboxHBoxLayout->setContentsMargins({});
0132     pageVBoxLayout->addWidget(hboxWidget);
0133     auto logSizeLab = new QLabel(i18n("Log size limit:"), hboxWidget);
0134     hboxHBoxLayout->addWidget(logSizeLab);
0135     mLogMemLimitSpin = new QSpinBox(hboxWidget);
0136     hboxHBoxLayout->addWidget(mLogMemLimitSpin, 1);
0137     mLogMemLimitSpin->setMinimum(1);
0138     mLogMemLimitSpin->setMaximum(1024 * 256); // 256 MB
0139     // value in the QSpinBox is in KB while it's in Byte in the FilterLog
0140     mLogMemLimitSpin->setValue(FilterLog::instance()->maxLogSize() / 1024);
0141     mLogMemLimitSpin->setSuffix(i18n(" KB"));
0142     mLogMemLimitSpin->setSpecialValueText(i18nc("@label:spinbox Set the size of the logfile to unlimited.", "unlimited"));
0143     connect(mLogMemLimitSpin, &QSpinBox::valueChanged, this, &FilterLogDialog::slotChangeLogMemLimit);
0144     mLogMemLimitSpin->setWhatsThis(
0145         i18n("Collecting log data uses memory to temporarily store the "
0146              "log data; here you can limit the maximum amount of memory "
0147              "to be used: if the size of the collected log data exceeds "
0148              "this limit then the oldest data will be discarded until "
0149              "the limit is no longer exceeded. "));
0150 
0151     connect(mLogActiveBox, &QCheckBox::toggled, mLogMemLimitSpin, &QSpinBox::setEnabled);
0152     mLogMemLimitSpin->setEnabled(mLogActiveBox->isChecked());
0153 
0154     connect(FilterLog::instance(), &FilterLog::logEntryAdded, this, &FilterLogDialog::slotLogEntryAdded);
0155     connect(FilterLog::instance(), &FilterLog::logShrinked, this, &FilterLogDialog::slotLogShrinked);
0156     connect(FilterLog::instance(), &FilterLog::logStateChanged, this, &FilterLogDialog::slotLogStateChanged);
0157 
0158     mainLayout->addWidget(buttonBox);
0159 
0160     connect(mUser1Button, &QPushButton::clicked, this, &FilterLogDialog::slotUser1);
0161     connect(mUser2Button, &QPushButton::clicked, this, &FilterLogDialog::slotUser2);
0162     connect(mTextEdit->editor(), &TextCustomEditor::PlainTextEditor::textChanged, this, &FilterLogDialog::slotTextChanged);
0163 
0164     slotTextChanged();
0165     readConfig();
0166     mIsInitialized = true;
0167 }
0168 
0169 void FilterLogDialog::slotTextChanged()
0170 {
0171     const bool hasText = !mTextEdit->isEmpty();
0172     mUser2Button->setEnabled(hasText);
0173     mUser1Button->setEnabled(hasText);
0174 }
0175 
0176 void FilterLogDialog::readConfig()
0177 {
0178     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0179     KConfigGroup group(config, QStringLiteral("FilterLog"));
0180     const bool isEnabled = group.readEntry("Enabled", false);
0181     const bool isLogPatternDescription = group.readEntry("LogPatternDescription", false);
0182     const bool isLogRuleResult = group.readEntry("LogRuleResult", false);
0183     const bool isLogPatternResult = group.readEntry("LogPatternResult", false);
0184     const bool isLogAppliedAction = group.readEntry("LogAppliedAction", false);
0185     const int maxLogSize = group.readEntry("maxLogSize", -1);
0186 
0187     if (isEnabled != FilterLog::instance()->isLogging()) {
0188         FilterLog::instance()->setLogging(isEnabled);
0189     }
0190     if (isLogPatternDescription != FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternDescription)) {
0191         FilterLog::instance()->setContentTypeEnabled(FilterLog::PatternDescription, isLogPatternDescription);
0192     }
0193     if (isLogRuleResult != FilterLog::instance()->isContentTypeEnabled(FilterLog::RuleResult)) {
0194         FilterLog::instance()->setContentTypeEnabled(FilterLog::RuleResult, isLogRuleResult);
0195     }
0196     if (isLogPatternResult != FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternResult)) {
0197         FilterLog::instance()->setContentTypeEnabled(FilterLog::PatternResult, isLogPatternResult);
0198     }
0199     if (isLogAppliedAction != FilterLog::instance()->isContentTypeEnabled(FilterLog::AppliedAction)) {
0200         FilterLog::instance()->setContentTypeEnabled(FilterLog::AppliedAction, isLogAppliedAction);
0201     }
0202     if (FilterLog::instance()->maxLogSize() != maxLogSize) {
0203         FilterLog::instance()->setMaxLogSize(maxLogSize);
0204     }
0205 
0206     KConfigGroup geometryGroup(KSharedConfig::openConfig(), QStringLiteral("Geometry"));
0207     const QSize size = geometryGroup.readEntry("filterLogSize", QSize(600, 400));
0208     if (size.isValid()) {
0209         resize(size);
0210     } else {
0211         adjustSize();
0212     }
0213 }
0214 
0215 FilterLogDialog::~FilterLogDialog()
0216 {
0217     disconnect(mTextEdit->editor(), &TextCustomEditor::PlainTextEditor::textChanged, this, &FilterLogDialog::slotTextChanged);
0218     KConfigGroup myGroup(KSharedConfig::openConfig(), QStringLiteral("Geometry"));
0219     myGroup.writeEntry("filterLogSize", size());
0220     myGroup.sync();
0221 }
0222 
0223 void FilterLogDialog::writeConfig()
0224 {
0225     if (!mIsInitialized) {
0226         return;
0227     }
0228 
0229     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0230     KConfigGroup group(config, QStringLiteral("FilterLog"));
0231     group.writeEntry("Enabled", FilterLog::instance()->isLogging());
0232     group.writeEntry("LogPatternDescription", FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternDescription));
0233     group.writeEntry("LogRuleResult", FilterLog::instance()->isContentTypeEnabled(FilterLog::RuleResult));
0234     group.writeEntry("LogPatternResult", FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternResult));
0235     group.writeEntry("LogAppliedAction", FilterLog::instance()->isContentTypeEnabled(FilterLog::AppliedAction));
0236     group.writeEntry("maxLogSize", static_cast<int>(FilterLog::instance()->maxLogSize()));
0237     group.sync();
0238 }
0239 
0240 void FilterLogDialog::slotLogEntryAdded(const QString &logEntry)
0241 {
0242     mTextEdit->editor()->appendHtml(logEntry);
0243 }
0244 
0245 void FilterLogDialog::slotLogShrinked()
0246 {
0247     // limit the size of the shown log lines as soon as
0248     // the log has reached it's memory limit
0249     if (mTextEdit->editor()->document()->maximumBlockCount() <= 0) {
0250         mTextEdit->editor()->document()->setMaximumBlockCount(mTextEdit->editor()->document()->blockCount());
0251     }
0252 }
0253 
0254 void FilterLogDialog::slotLogStateChanged()
0255 {
0256     mLogActiveBox->setChecked(FilterLog::instance()->isLogging());
0257     mLogPatternDescBox->setChecked(FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternDescription));
0258     mLogRuleEvaluationBox->setChecked(FilterLog::instance()->isContentTypeEnabled(FilterLog::RuleResult));
0259     mLogPatternResultBox->setChecked(FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternResult));
0260     mLogFilterActionBox->setChecked(FilterLog::instance()->isContentTypeEnabled(FilterLog::AppliedAction));
0261 
0262     // value in the QSpinBox is in KB while it's in Byte in the FilterLog
0263     int newLogSize = FilterLog::instance()->maxLogSize() / 1024;
0264     if (mLogMemLimitSpin->value() != newLogSize) {
0265         if (newLogSize <= 0) {
0266             mLogMemLimitSpin->setValue(1);
0267         } else {
0268             mLogMemLimitSpin->setValue(newLogSize);
0269         }
0270     }
0271     writeConfig();
0272 }
0273 
0274 void FilterLogDialog::slotChangeLogDetail()
0275 {
0276     if (mLogPatternDescBox->isChecked() != FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternDescription)) {
0277         FilterLog::instance()->setContentTypeEnabled(FilterLog::PatternDescription, mLogPatternDescBox->isChecked());
0278     }
0279 
0280     if (mLogRuleEvaluationBox->isChecked() != FilterLog::instance()->isContentTypeEnabled(FilterLog::RuleResult)) {
0281         FilterLog::instance()->setContentTypeEnabled(FilterLog::RuleResult, mLogRuleEvaluationBox->isChecked());
0282     }
0283 
0284     if (mLogPatternResultBox->isChecked() != FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternResult)) {
0285         FilterLog::instance()->setContentTypeEnabled(FilterLog::PatternResult, mLogPatternResultBox->isChecked());
0286     }
0287 
0288     if (mLogFilterActionBox->isChecked() != FilterLog::instance()->isContentTypeEnabled(FilterLog::AppliedAction)) {
0289         FilterLog::instance()->setContentTypeEnabled(FilterLog::AppliedAction, mLogFilterActionBox->isChecked());
0290     }
0291 }
0292 
0293 void FilterLogDialog::slotSwitchLogState()
0294 {
0295     FilterLog::instance()->setLogging(mLogActiveBox->isChecked());
0296 }
0297 
0298 void FilterLogDialog::slotChangeLogMemLimit(int value)
0299 {
0300     mTextEdit->editor()->document()->setMaximumBlockCount(0); // Reset value
0301     if (value == 1) { // unilimited
0302         FilterLog::instance()->setMaxLogSize(-1);
0303     } else {
0304         FilterLog::instance()->setMaxLogSize(value * 1024);
0305     }
0306 }
0307 
0308 void FilterLogDialog::slotUser1()
0309 {
0310     FilterLog::instance()->clear();
0311     mTextEdit->editor()->clear();
0312 }
0313 
0314 void FilterLogDialog::slotUser2()
0315 {
0316     QPointer<QFileDialog> fdlg(new QFileDialog(this));
0317 
0318     fdlg->setAcceptMode(QFileDialog::AcceptSave);
0319     fdlg->setFileMode(QFileDialog::AnyFile);
0320     fdlg->selectFile(QStringLiteral("kmail-filter.html"));
0321     if (fdlg->exec() == QDialog::Accepted) {
0322         const QStringList fileName = fdlg->selectedFiles();
0323 
0324         if (!fileName.isEmpty() && !FilterLog::instance()->saveToFile(fileName.at(0))) {
0325             KMessageBox::error(this,
0326                                i18n("Could not write the file %1:\n"
0327                                     "\"%2\" is the detailed error description.",
0328                                     fileName.at(0),
0329                                     QString::fromLocal8Bit(strerror(errno))),
0330                                i18nc("@title:window", "KMail Error"));
0331         }
0332     }
0333     delete fdlg;
0334 }
0335 
0336 FilterLogTextEdit::FilterLogTextEdit(QWidget *parent)
0337     : TextCustomEditor::PlainTextEditor(parent)
0338 {
0339 }
0340 
0341 void FilterLogTextEdit::addExtraMenuEntry(QMenu *menu, QPoint pos)
0342 {
0343     Q_UNUSED(pos)
0344     if (!document()->isEmpty()) {
0345         auto sep = new QAction(menu);
0346         sep->setSeparator(true);
0347         menu->addAction(sep);
0348         QAction *clearAllAction = KStandardAction::clear(this, &FilterLogTextEdit::clear, menu);
0349         menu->addAction(clearAllAction);
0350     }
0351 }
0352 
0353 #include "moc_filterlogdialog.cpp"