Libksieve

sieveeditorwidget.cpp
1/*
2 SPDX-FileCopyrightText: 2014-2024 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5
6*/
7
8#include "sieveeditorwidget.h"
9#include "sievepurposemenuwidget.h"
10
11#include "autocreatescripts/sieveeditorgraphicalmodewidget.h"
12#include "editor/sieveinfodialog.h"
13#include "scriptsparsing/parsingutil.h"
14#include "sieve-editor.h"
15#include "sieveeditormenubar.h"
16#include "sieveeditortextmodewidget.h"
17
18#include "libksieveui_debug.h"
19#include <KActionMenu>
20#include <KLocalizedString>
21#include <KStandardAction>
22#include <PimCommon/PurposeMenuWidget>
23#include <QAction>
24#include <QLabel>
25#include <QLineEdit>
26#include <QPushButton>
27#include <QStackedWidget>
28#include <QStandardPaths>
29#include <QToolBar>
30#include <QVBoxLayout>
31#include <kzip.h>
32
33using namespace KSieveUi;
34
35SieveEditorWidget::SieveEditorWidget(bool useMenuBar, QWidget *parent)
36 : QWidget(parent)
37{
38 auto lay = new QVBoxLayout(this);
39 lay->setContentsMargins({});
40 mDebug = !qEnvironmentVariableIsEmpty("KDEPIM_DEBUGGING");
41
42 auto toolbar = new QToolBar(this);
43 toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
44
45 mCheckSyntax = new QAction(i18n("Check Syntax"), this);
46 connect(mCheckSyntax, &QAction::triggered, this, &SieveEditorWidget::slotCheckSyntax);
47 toolbar->addAction(mCheckSyntax);
48 mSaveAs = KStandardAction::saveAs(this, &SieveEditorWidget::slotSaveAs, this);
49 toolbar->addAction(mSaveAs);
50 toolbar->addAction(i18n("Import..."), this, &SieveEditorWidget::slotImport);
51
52 mCreateRulesGraphically = new QAction(i18n("Create Rules Graphically..."), this);
53 connect(mCreateRulesGraphically, &QAction::triggered, this, &SieveEditorWidget::slotCreateRulesGraphically);
54 toolbar->addAction(mCreateRulesGraphically);
55 mSwitchMode = new QAction(this);
56 toolbar->addAction(mSwitchMode);
57 connect(mSwitchMode, &QAction::triggered, this, &SieveEditorWidget::slotSwitchMode);
58
59 if (mDebug) {
60 // Not necessary to translate it.
61 mGenerateXml = new QAction(QStringLiteral("Generate xml"), this);
62 connect(mGenerateXml, &QAction::triggered, this, &SieveEditorWidget::slotGenerateXml);
63 toolbar->addAction(mGenerateXml);
64 }
65 auto purposeMenu = new SievePurposeMenuWidget(this, this);
66 auto shareAction = new KActionMenu(i18n("Share..."), this);
67 shareAction->setPopupMode(QToolButton::InstantPopup);
68 shareAction->setMenu(purposeMenu->menu());
69 shareAction->setIcon(QIcon::fromTheme(QStringLiteral("document-share")));
70 purposeMenu->setEditorWidget(this);
71 toolbar->addAction(shareAction);
72 mServerInfo = new QAction(i18n("Server Info"), this);
73 connect(mServerInfo, &QAction::triggered, this, &SieveEditorWidget::slotServerInfo);
74 toolbar->addAction(mServerInfo);
75
76 SieveEditorMenuBar *menuBar = nullptr;
77 if (useMenuBar) {
78 menuBar = new SieveEditorMenuBar;
79 connect(this, &SieveEditorWidget::changeModeEditor, menuBar, &SieveEditorMenuBar::setEditorMode);
80 connect(menuBar, &SieveEditorMenuBar::copy, this, &SieveEditorWidget::copy);
81 connect(menuBar, &SieveEditorMenuBar::find, this, &SieveEditorWidget::find);
82 connect(menuBar, &SieveEditorMenuBar::replace, this, &SieveEditorWidget::replace);
83 connect(menuBar, &SieveEditorMenuBar::undo, this, &SieveEditorWidget::undo);
84 connect(menuBar, &SieveEditorMenuBar::redo, this, &SieveEditorWidget::redo);
85 connect(menuBar, &SieveEditorMenuBar::paste, this, &SieveEditorWidget::paste);
86 connect(menuBar, &SieveEditorMenuBar::cut, this, &SieveEditorWidget::cut);
87 connect(menuBar, &SieveEditorMenuBar::selectAll, this, &SieveEditorWidget::selectAll);
88 connect(menuBar, &SieveEditorMenuBar::gotoLine, this, &SieveEditorWidget::goToLine);
89 connect(menuBar, &SieveEditorMenuBar::comment, this, &SieveEditorWidget::comment);
90 connect(menuBar, &SieveEditorMenuBar::uncomment, this, &SieveEditorWidget::uncomment);
91 connect(menuBar, &SieveEditorMenuBar::zoomIn, this, &SieveEditorWidget::zoomIn);
92 connect(menuBar, &SieveEditorMenuBar::zoomOut, this, &SieveEditorWidget::zoomOut);
93 connect(menuBar, &SieveEditorMenuBar::zoomReset, this, &SieveEditorWidget::zoomReset);
94 connect(menuBar, &SieveEditorMenuBar::debugSieveScript, this, &SieveEditorWidget::debugSieveScript);
95 connect(menuBar, &SieveEditorMenuBar::wordWrap, this, &SieveEditorWidget::setWordWrap);
96 connect(menuBar, &SieveEditorMenuBar::print, this, &SieveEditorWidget::print);
97 connect(menuBar, &SieveEditorMenuBar::printPreview, this, &SieveEditorWidget::printPreview);
98
99 connect(this, &SieveEditorWidget::copyAvailable, menuBar, &SieveEditorMenuBar::slotCopyAvailable);
100 connect(this, &SieveEditorWidget::redoAvailable, menuBar, &SieveEditorMenuBar::slotRedoAvailable);
101 connect(this, &SieveEditorWidget::undoAvailable, menuBar, &SieveEditorMenuBar::slotUndoAvailable);
102 menuBar->fileMenu()->addSeparator();
103 menuBar->fileMenu()->addAction(mSaveAs);
104 menuBar->fileMenu()->addSeparator();
105 menuBar->toolsMenu()->addSeparator();
106 menuBar->fileMenu()->addAction(shareAction);
107 menuBar->toolsMenu()->addSeparator();
108 menuBar->toolsMenu()->addAction(mCreateRulesGraphically);
109 menuBar->toolsMenu()->addAction(mCheckSyntax);
110 lay->addWidget(menuBar);
111 }
112
113 lay->addWidget(toolbar);
114
115 auto nameLayout = new QHBoxLayout;
116 auto label = new QLabel(i18n("Script name:"), this);
117 nameLayout->addWidget(label);
118 mScriptName = new QLineEdit(this);
119 mScriptName->setReadOnly(true);
120 nameLayout->addWidget(mScriptName);
121 lay->addLayout(nameLayout);
122
123 lay->setContentsMargins({});
124 mStackedWidget = new QStackedWidget;
125
126 mTextModeWidget = new SieveEditorTextModeWidget;
127 connect(purposeMenu, &SievePurposeMenuWidget::shareError, mTextModeWidget, &SieveEditorTextModeWidget::slotShareError);
128 connect(purposeMenu, &SievePurposeMenuWidget::shareSuccess, mTextModeWidget, &SieveEditorTextModeWidget::slotShareSuccess);
129 connect(mTextModeWidget, &SieveEditorTextModeWidget::valueChanged, this, &SieveEditorWidget::slotModified);
130 if (menuBar) {
131 menuBar->setTextModeWidget(mTextModeWidget);
132 }
133 mStackedWidget->addWidget(mTextModeWidget);
134 mGraphicalModeWidget = new SieveEditorGraphicalModeWidget;
135 connect(mGraphicalModeWidget, &SieveEditorGraphicalModeWidget::valueChanged, this, &SieveEditorWidget::slotModified);
136 mStackedWidget->addWidget(mGraphicalModeWidget);
137
138 lay->addWidget(mStackedWidget);
139 connect(mTextModeWidget, &SieveEditorTextModeWidget::enableButtonOk, this, &SieveEditorWidget::slotEnableButtonOk);
140 connect(mGraphicalModeWidget, &SieveEditorGraphicalModeWidget::enableButtonOk, this, &SieveEditorWidget::slotEnableButtonOk);
141 connect(mGraphicalModeWidget, &SieveEditorGraphicalModeWidget::switchTextMode, this, &SieveEditorWidget::slotSwitchTextMode);
142 connect(mTextModeWidget, &SieveEditorTextModeWidget::switchToGraphicalMode, this, &SieveEditorWidget::slotSwitchToGraphicalMode);
143 connect(mTextModeWidget, &SieveEditorTextModeWidget::undoAvailable, this, &SieveEditorWidget::undoAvailable);
144 connect(mTextModeWidget, &SieveEditorTextModeWidget::redoAvailable, this, &SieveEditorWidget::redoAvailable);
145 connect(mTextModeWidget, &SieveEditorTextModeWidget::copyAvailable, this, &SieveEditorWidget::copyAvailable);
146 connect(mTextModeWidget, &SieveEditorTextModeWidget::sieveEditorTabCurrentChanged, this, &SieveEditorWidget::sieveEditorTabCurrentChanged);
147 if (KSieveUi::EditorSettings::useGraphicEditorByDefault()) {
148 changeMode(GraphicMode);
149 } else {
150 changeSwitchButtonText();
151 }
152}
153
154SieveEditorWidget::~SieveEditorWidget() = default;
155
156void SieveEditorWidget::setReadOnly(bool b)
157{
158 mTextModeWidget->setReadOnly(b);
159 mGraphicalModeWidget->setDisabled(b);
160}
161
162void SieveEditorWidget::slotModified()
163{
164 mModified = true;
165 Q_EMIT valueChanged(mModified);
166}
167
168bool SieveEditorWidget::isModified() const
169{
170 return mModified;
171}
172
173void SieveEditorWidget::undo()
174{
175 if (mMode == TextMode) {
176 mTextModeWidget->undo();
177 }
178}
179
180void SieveEditorWidget::redo()
181{
182 if (mMode == TextMode) {
183 mTextModeWidget->redo();
184 }
185}
186
187void SieveEditorWidget::goToLine()
188{
189 if (mMode == TextMode) {
190 mTextModeWidget->slotShowGoToLine();
191 }
192}
193
194void SieveEditorWidget::cut()
195{
196 if (mMode == TextMode) {
197 mTextModeWidget->cut();
198 }
199}
200
201void SieveEditorWidget::paste()
202{
203 if (mMode == TextMode) {
204 mTextModeWidget->paste();
205 }
206}
207
208void SieveEditorWidget::copy()
209{
210 if (mMode == TextMode) {
211 mTextModeWidget->copy();
212 }
213}
214
215void SieveEditorWidget::zoomIn()
216{
217 if (mMode == TextMode) {
218 mTextModeWidget->zoomIn();
219 }
220}
221
222bool SieveEditorWidget::isWordWrap() const
223{
224 if (mMode == TextMode) {
225 return mTextModeWidget->isWordWrap();
226 }
227 return false;
228}
229
230void SieveEditorWidget::updateOriginalScript()
231{
232 mOriginalScript = script();
233}
234
235void SieveEditorWidget::print()
236{
237 switch (mMode) {
238 case TextMode: {
239 bool wasModified = isModified();
240 mTextModeWidget->print();
241 setModified(wasModified);
242 break;
243 }
244 case GraphicMode:
245 break;
246 case Unknown:
247 qCDebug(LIBKSIEVEUI_LOG) << " Unknown mode";
248 break;
249 }
250}
251
252void SieveEditorWidget::printPreview()
253{
254 switch (mMode) {
255 case TextMode: {
256 bool wasModified = isModified();
257 mTextModeWidget->printPreview();
258 setModified(wasModified);
259 break;
260 }
261 case GraphicMode:
262 break;
263 case Unknown:
264 qCDebug(LIBKSIEVEUI_LOG) << " Unknown mode";
265 break;
266 }
267}
268
269void SieveEditorWidget::setWordWrap(bool state)
270{
271 if (mMode == TextMode) {
272 mTextModeWidget->setWordWrap(state);
273 }
274}
275
276void SieveEditorWidget::zoomReset()
277{
278 if (mMode == TextMode) {
279 mTextModeWidget->zoomReset();
280 }
281}
282
283void SieveEditorWidget::zoomOut()
284{
285 if (mMode == TextMode) {
286 mTextModeWidget->zoomOut();
287 }
288}
289
290void SieveEditorWidget::selectAll()
291{
292 if (mMode == TextMode) {
293 mTextModeWidget->selectAll();
294 }
295}
296
297void SieveEditorWidget::find()
298{
299 if (mMode == TextMode) {
300 mTextModeWidget->find();
301 }
302}
303
304void SieveEditorWidget::replace()
305{
306 if (mMode == TextMode) {
307 mTextModeWidget->replace();
308 }
309}
310
311bool SieveEditorWidget::isUndoAvailable() const
312{
313 if (mMode == TextMode) {
314 return mTextModeWidget->isUndoAvailable();
315 }
316 return false;
317}
318
319bool SieveEditorWidget::isRedoAvailable() const
320{
321 if (mMode == TextMode) {
322 return mTextModeWidget->isRedoAvailable();
323 }
324 return false;
325}
326
327bool SieveEditorWidget::hasSelection() const
328{
329 if (mMode == TextMode) {
330 return mTextModeWidget->hasSelection();
331 }
332 return false;
333}
334
335void SieveEditorWidget::comment()
336{
337 if (mMode == TextMode) {
338 mTextModeWidget->comment();
339 }
340}
341
342void SieveEditorWidget::uncomment()
343{
344 if (mMode == TextMode) {
345 mTextModeWidget->uncomment();
346 }
347}
348
349SieveEditorWidget::EditorMode SieveEditorWidget::mode() const
350{
351 return mMode;
352}
353
354void SieveEditorWidget::setModified(bool b)
355{
356 if (mModified != b) {
357 mModified = b;
358 Q_EMIT valueChanged(mModified);
359 }
360}
361
362void SieveEditorWidget::changeMode(EditorMode mode)
363{
364 if (mode != mMode) {
365 mMode = mode;
366 mStackedWidget->setCurrentIndex(static_cast<int>(mode));
367 const bool isTextMode = (mMode == TextMode);
368 mCreateRulesGraphically->setEnabled(isTextMode);
369 if (mGenerateXml) {
370 mGenerateXml->setEnabled(isTextMode);
371 }
372 if (isTextMode) {
373 mCheckSyntax->setEnabled(!mTextModeWidget->currentscript().isEmpty());
374 } else {
375 mCheckSyntax->setEnabled(false);
376 }
377 Q_EMIT modeEditorChanged(mode);
378 Q_EMIT changeModeEditor(isTextMode);
379 changeSwitchButtonText();
380 }
381}
382
383void SieveEditorWidget::changeSwitchButtonText()
384{
385 mSwitchMode->setText((mMode == TextMode) ? i18n("Simple Mode") : i18n("Advanced Mode"));
386}
387
388void SieveEditorWidget::slotEnableButtonOk(bool b)
389{
390 Q_EMIT enableButtonOk(b);
391 mSaveAs->setEnabled(b);
392 if (mMode == TextMode) {
393 mCheckSyntax->setEnabled(b);
394 } else {
395 mCheckSyntax->setEnabled(false);
396 }
397}
398
399QString SieveEditorWidget::script() const
400{
401 QString currentScript;
402 switch (mMode) {
403 case TextMode:
404 currentScript = mTextModeWidget->script();
405 break;
406 case GraphicMode:
407 currentScript = mGraphicalModeWidget->currentscript();
408 break;
409 case Unknown:
410 qCDebug(LIBKSIEVEUI_LOG) << " Unknown Mode!";
411 break;
412 }
413 return currentScript;
414}
415
416QString SieveEditorWidget::originalScript() const
417{
418 return mOriginalScript;
419}
420
421void SieveEditorWidget::setScript(const QString &script, bool clearUndoRedo)
422{
423 mTextModeWidget->setScript(script, clearUndoRedo);
424 // Necessary to take text from editor otherwise script has \r\n
425 mOriginalScript = mTextModeWidget->script();
426}
427
428void SieveEditorWidget::addFailedMessage(const QString &err)
429{
430 addMessageEntry(err, QColor(Qt::darkRed));
431}
432
433void SieveEditorWidget::addOkMessage(const QString &msg)
434{
435 addMessageEntry(msg, QColor(Qt::darkGreen));
436}
437
438void SieveEditorWidget::addNormalMessage(const QString &msg)
439{
440 addMessageEntry(msg, palette().color(QPalette::WindowText));
441}
442
443void SieveEditorWidget::addMessageEntry(const QString &errorMsg, const QColor &color)
444{
445 QString msg = errorMsg;
446 msg.replace(QLatin1Char('\n'), QStringLiteral("<br>"));
447 const QString logText = QStringLiteral("<font color=%1>%2</font>").arg(color.name(), msg);
448
449 setDebugScript(logText);
450}
451
452void SieveEditorWidget::setDebugScript(const QString &debug)
453{
454 mTextModeWidget->setDebugScript(debug);
455}
456
457void SieveEditorWidget::setScriptName(const QString &name)
458{
459 mScriptName->setText(name);
460}
461
462void SieveEditorWidget::resultDone()
463{
464 mCheckSyntax->setEnabled(true);
465}
466
467void SieveEditorWidget::setSieveCapabilities(const QStringList &capabilities)
468{
469 mTextModeWidget->setSieveCapabilities(capabilities);
470 mGraphicalModeWidget->setSieveCapabilities(capabilities);
471}
472
473void SieveEditorWidget::setSieveImapAccountSettings(const KSieveCore::SieveImapAccountSettings &sieveImapAccountSettings)
474{
475 mGraphicalModeWidget->setSieveImapAccountSettings(sieveImapAccountSettings);
476 mTextModeWidget->setSieveImapAccountSettings(sieveImapAccountSettings);
477}
478
479void SieveEditorWidget::setListOfIncludeFile(const QStringList &listOfIncludeFile)
480{
481 mTextModeWidget->setListOfIncludeFile(listOfIncludeFile);
482 mGraphicalModeWidget->setListOfIncludeFile(listOfIncludeFile);
483}
484
485void SieveEditorWidget::slotCreateRulesGraphically()
486{
487 switch (mMode) {
488 case TextMode:
489 mTextModeWidget->createRulesGraphically();
490 break;
491 case GraphicMode:
492 case Unknown:
493 break;
494 }
495}
496
497void SieveEditorWidget::slotCheckSyntax()
498{
499 switch (mMode) {
500 case TextMode:
501 mCheckSyntax->setEnabled(false);
502 Q_EMIT checkSyntax();
503 break;
504 case GraphicMode:
505 case Unknown:
506 break;
507 }
508}
509
510void SieveEditorWidget::slotGenerateXml()
511{
512 switch (mMode) {
513 case TextMode:
514 mTextModeWidget->generateXml();
515 break;
516 case GraphicMode:
517 case Unknown:
518 break;
519 }
520}
521
522void SieveEditorWidget::checkSpelling()
523{
524 switch (mMode) {
525 case TextMode:
526 mTextModeWidget->checkSpelling();
527 break;
528 case GraphicMode:
529 case Unknown:
530 break;
531 }
532}
533
534void SieveEditorWidget::slotSaveAs()
535{
536 switch (mMode) {
537 case TextMode:
538 mTextModeWidget->saveAs(mScriptName->text());
539 break;
540 case GraphicMode:
541 mGraphicalModeWidget->saveAs(mScriptName->text());
542 break;
543 case Unknown:
544 qCDebug(LIBKSIEVEUI_LOG) << " Unknown mode";
545 break;
546 }
547}
548
549void SieveEditorWidget::slotImport()
550{
551 switch (mMode) {
552 case TextMode:
553 mTextModeWidget->slotImport();
554 break;
555 case GraphicMode:
556 mGraphicalModeWidget->slotImport();
557 break;
558 case Unknown:
559 qCDebug(LIBKSIEVEUI_LOG) << " Unknown mode";
560 break;
561 }
562}
563
564void SieveEditorWidget::slotSwitchToGraphicalMode()
565{
566 mTextModeWidget->hideEditorWarning();
567 changeMode(GraphicMode);
568}
569
570void SieveEditorWidget::slotSwitchMode()
571{
572 switch (mMode) {
573 case TextMode: {
574 bool result = false;
575 const QString doc = KSieveCore::ParsingUtil::parseScript(mTextModeWidget->currentscript(), result);
576 if (result) {
578 mGraphicalModeWidget->loadScript(doc, error);
579 if (!error.isEmpty()) {
580 mTextModeWidget->setParsingEditorWarningError(mTextModeWidget->currentscript(), error);
581 mTextModeWidget->showParsingEditorWarning();
582 } else {
583 mTextModeWidget->hideEditorWarning();
584 changeMode(GraphicMode);
585 }
586 } else {
587 mTextModeWidget->showEditorWarning();
588 qCDebug(LIBKSIEVEUI_LOG) << "Impossible to parse file";
589 }
590 break;
591 }
592 case GraphicMode: {
593 const QString script = mGraphicalModeWidget->currentscript();
594 changeMode(TextMode);
595 mTextModeWidget->setScript(script);
596 break;
597 }
598 case Unknown:
599 qCDebug(LIBKSIEVEUI_LOG) << " Unknown mode";
600 break;
601 }
602}
603
604void SieveEditorWidget::slotSwitchTextMode(const QString &script)
605{
606 changeMode(TextMode);
607 mTextModeWidget->setScript(script);
608}
609
610void SieveEditorWidget::reverseCase()
611{
612 if (mMode == TextMode) {
613 mTextModeWidget->reverseCase();
614 }
615}
616
617void SieveEditorWidget::lowerCase()
618{
619 if (mMode == TextMode) {
620 mTextModeWidget->lowerCase();
621 }
622}
623
624void SieveEditorWidget::debugSieveScript()
625{
626 if (mMode == TextMode) {
627 mTextModeWidget->debugSieveScript();
628 }
629}
630
631void SieveEditorWidget::upperCase()
632{
633 if (mMode == TextMode) {
634 mTextModeWidget->upperCase();
635 }
636}
637
638void SieveEditorWidget::sentenceCase()
639{
640 if (mMode == TextMode) {
641 mTextModeWidget->sentenceCase();
642 }
643}
644
645void SieveEditorWidget::openBookmarkUrl(const QUrl &url)
646{
647 if (mMode == TextMode) {
648 mTextModeWidget->openBookmarkUrl(url);
649 }
650}
651
652QString SieveEditorWidget::currentHelpTitle() const
653{
654 if (mMode == TextMode) {
655 return mTextModeWidget->currentHelpTitle();
656 }
657 return {};
658}
659
660QUrl SieveEditorWidget::currentHelpUrl() const
661{
662 if (mMode == TextMode) {
663 return mTextModeWidget->currentHelpUrl();
664 }
665 return {};
666}
667
668bool SieveEditorWidget::isTextEditor() const
669{
670 if (mMode == TextMode) {
671 return mTextModeWidget->isTextEditor();
672 }
673 return false;
674}
675
676bool SieveEditorWidget::printSupportEnabled() const
677{
678 if (mMode == TextMode) {
679 return mTextModeWidget->printSupportEnabled();
680 }
681 return false;
682}
683
684void SieveEditorWidget::slotServerInfo()
685{
686 SieveInfoDialog dlg(this);
687 dlg.setServerInfo(mTextModeWidget->sieveCapabilities());
688 dlg.exec();
689}
690
691#include "moc_sieveeditorwidget.cpp"
The SieveImapAccountSettings class.
The SieveEditorTextModeWidget class.
Q_SCRIPTABLE QStringList logText()
QString i18n(const char *text, const TYPE &arg...)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QAction * saveAs(const QObject *recvr, const char *slot, QObject *parent)
QString label(StandardShortcut id)
void setEnabled(bool)
void setText(const QString &text)
void triggered(bool checked)
QString name(NameFormat format) const const
QIcon fromTheme(const QString &name)
void setText(const QString &)
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * addSeparator()
Q_EMITQ_EMIT
void setCurrentIndex(int index)
bool isEmpty() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
ToolButtonTextBesideIcon
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setDisabled(bool disable)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:19 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.