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"