KIO

kurlrequester.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999, 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
4 SPDX-FileCopyrightText: 2013 Teo Mrnjavac <teo@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-only
7*/
8
9#include "kurlrequester.h"
10#include "../utils_p.h"
11#include "kio_widgets_debug.h"
12
13#include <KComboBox>
14#include <KDragWidgetDecorator>
15#include <KLineEdit>
16#include <KLocalizedString>
17#include <kprotocolmanager.h>
18#include <kurlcompletion.h>
19
20#include <QAction>
21#include <QApplication>
22#include <QDrag>
23#include <QEvent>
24#include <QHBoxLayout>
25#include <QKeySequence>
26#include <QMenu>
27#include <QMimeData>
28
29class KUrlDragPushButton : public QPushButton
30{
32public:
33 explicit KUrlDragPushButton(QWidget *parent)
35 {
36 new DragDecorator(this);
37 }
38 ~KUrlDragPushButton() override
39 {
40 }
41
42 void setURL(const QUrl &url)
43 {
44 m_urls.clear();
45 m_urls.append(url);
46 }
47
48private:
49 class DragDecorator : public KDragWidgetDecoratorBase
50 {
51 public:
52 explicit DragDecorator(KUrlDragPushButton *button)
54 , m_button(button)
55 {
56 }
57
58 protected:
59 QDrag *dragObject() override
60 {
61 if (m_button->m_urls.isEmpty()) {
62 return nullptr;
63 }
64
65 QDrag *drag = new QDrag(m_button);
66 QMimeData *mimeData = new QMimeData;
67 mimeData->setUrls(m_button->m_urls);
68 drag->setMimeData(mimeData);
69 return drag;
70 }
71
72 private:
73 KUrlDragPushButton *m_button;
74 };
75
76 QList<QUrl> m_urls;
77};
78
79class Q_DECL_HIDDEN KUrlRequester::KUrlRequesterPrivate
80{
81public:
82 explicit KUrlRequesterPrivate(KUrlRequester *parent)
83 : m_fileDialogModeWasDirAndFile(false)
84 , m_parent(parent)
85 , edit(nullptr)
86 , combo(nullptr)
87 , fileDialogMode(KFile::File | KFile::ExistingOnly | KFile::LocalOnly)
88 , fileDialogAcceptMode(QFileDialog::AcceptOpen)
89 {
90 }
91
92 ~KUrlRequesterPrivate()
93 {
94 delete myCompletion;
95 delete myFileDialog;
96 }
97
98 void init();
99
100 void setText(const QString &text)
101 {
102 if (combo) {
103 if (combo->isEditable()) {
104 combo->setEditText(text);
105 } else {
106 int i = combo->findText(text);
107 if (i == -1) {
108 combo->addItem(text);
109 combo->setCurrentIndex(combo->count() - 1);
110 } else {
111 combo->setCurrentIndex(i);
112 }
113 }
114 } else {
115 edit->setText(text);
116 }
117 }
118
119 void connectSignals(KUrlRequester *receiver)
120 {
121 if (combo) {
124
126 } else if (edit) {
129
130 connect(edit, qOverload<>(&QLineEdit::returnPressed), receiver, [this]() {
131 m_parent->Q_EMIT returnPressed(QString{});
132 });
133
134 if (auto kline = qobject_cast<KLineEdit *>(edit)) {
136 }
137 }
138 }
139
140 void setCompletionObject(KCompletion *comp)
141 {
142 if (combo) {
143 combo->setCompletionObject(comp);
144 } else {
145 edit->setCompletionObject(comp);
146 }
147 }
148
149 void updateCompletionStartDir(const QUrl &newStartDir)
150 {
151 myCompletion->setDir(newStartDir);
152 }
153
154 QString text() const
155 {
156 return combo ? combo->currentText() : edit->text();
157 }
158
159 /**
160 * replaces ~user or $FOO, if necessary
161 * if text() is a relative path, make it absolute using startDir()
162 */
163 QUrl url() const
164 {
165 const QString txt = text();
166 KUrlCompletion *comp;
167 if (combo) {
168 comp = qobject_cast<KUrlCompletion *>(combo->completionObject());
169 } else {
170 comp = qobject_cast<KUrlCompletion *>(edit->completionObject());
171 }
172
173 QString enteredPath;
174 if (comp) {
175 enteredPath = comp->replacedPath(txt);
176 } else {
177 enteredPath = txt;
178 }
179
180 if (Utils::isAbsoluteLocalPath(enteredPath)) {
181 return QUrl::fromLocalFile(enteredPath);
182 }
183
184 const QUrl enteredUrl = QUrl(enteredPath); // absolute or relative
185 if (enteredUrl.isRelative() && !txt.isEmpty()) {
186 QUrl finalUrl(m_startDir);
187 finalUrl.setPath(Utils::concatPaths(finalUrl.path(), enteredPath));
188 return finalUrl;
189 } else {
190 return enteredUrl;
191 }
192 }
193
194 static void applyFileMode(QFileDialog *dlg, KFile::Modes m, QFileDialog::AcceptMode acceptMode)
195 {
196 QFileDialog::FileMode fileMode;
197 bool dirsOnly = false;
198 if (m & KFile::Directory) {
199 fileMode = QFileDialog::Directory;
200 if ((m & KFile::File) == 0 && (m & KFile::Files) == 0) {
201 dirsOnly = true;
202 }
203 } else if (m & KFile::Files && m & KFile::ExistingOnly) {
205 } else if (m & KFile::File && m & KFile::ExistingOnly) {
206 fileMode = QFileDialog::ExistingFile;
207 } else {
208 fileMode = QFileDialog::AnyFile;
209 }
210
211 dlg->setFileMode(fileMode);
212 dlg->setAcceptMode(acceptMode);
213 dlg->setOption(QFileDialog::ShowDirsOnly, dirsOnly);
214 }
215
216 QUrl getDirFromFileDialog(const QUrl &openUrl) const
217 {
219 }
220
221 void createFileDialog()
222 {
223 // Creates the fileDialog if it doesn't exist yet
224 QFileDialog *dlg = m_parent->fileDialog();
225
226 if (!url().isEmpty() && !url().isRelative()) {
227 QUrl u(url());
228 // If we won't be able to list it (e.g. http), then don't try :)
230 dlg->selectUrl(u);
231 }
232 } else {
233 dlg->setDirectoryUrl(m_startDir);
234 }
235
236 dlg->setAcceptMode(fileDialogAcceptMode);
237
238 // Update the file dialog window modality
239 if (dlg->windowModality() != fileDialogModality) {
240 dlg->setWindowModality(fileDialogModality);
241 }
242
243 if (fileDialogModality == Qt::NonModal) {
244 dlg->show();
245 } else {
246 dlg->exec();
247 }
248 }
249
250 // slots
251 void slotUpdateUrl();
252 void slotOpenDialog();
253 void slotFileDialogAccepted();
254
255 QUrl m_startDir;
256 bool m_startDirCustomized;
257 bool m_fileDialogModeWasDirAndFile;
258 KUrlRequester *const m_parent; // TODO: rename to 'q'
259 KLineEdit *edit;
260 KComboBox *combo;
261 KFile::Modes fileDialogMode;
262 QFileDialog::AcceptMode fileDialogAcceptMode;
263 QStringList nameFilters;
264 QStringList mimeTypeFilters;
266 KUrlDragPushButton *myButton;
267 QFileDialog *myFileDialog;
268 KUrlCompletion *myCompletion;
269 Qt::WindowModality fileDialogModality;
270};
271
273 : QWidget(parent)
274 , d(new KUrlRequesterPrivate(this))
275{
276 // must have this as parent
277 editWidget->setParent(this);
278 d->combo = qobject_cast<KComboBox *>(editWidget);
279 d->edit = qobject_cast<KLineEdit *>(editWidget);
280 if (d->edit) {
281 d->edit->setClearButtonEnabled(true);
282 }
283
284 d->init();
285}
286
288 : QWidget(parent)
289 , d(new KUrlRequesterPrivate(this))
290{
291 d->init();
292}
293
295 : QWidget(parent)
296 , d(new KUrlRequesterPrivate(this))
297{
298 d->init();
299 setUrl(url);
300}
301
303{
304 QWidget *widget = d->combo ? static_cast<QWidget *>(d->combo) : static_cast<QWidget *>(d->edit);
305 widget->removeEventFilter(this);
306}
307
308void KUrlRequester::KUrlRequesterPrivate::init()
309{
310 myFileDialog = nullptr;
311 fileDialogModality = Qt::ApplicationModal;
312
313 if (!combo && !edit) {
314 edit = new KLineEdit(m_parent);
315 edit->setClearButtonEnabled(true);
316 }
317
318 QWidget *widget = combo ? static_cast<QWidget *>(combo) : static_cast<QWidget *>(edit);
319
320 QHBoxLayout *topLayout = new QHBoxLayout(m_parent);
321 topLayout->setContentsMargins(0, 0, 0, 0);
322 topLayout->setSpacing(-1); // use default spacing
323 topLayout->addWidget(widget);
324
325 myButton = new KUrlDragPushButton(m_parent);
326 myButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
327 int buttonSize = myButton->sizeHint().expandedTo(widget->sizeHint()).height();
328 myButton->setFixedSize(buttonSize, buttonSize);
329 myButton->setToolTip(i18n("Open file dialog"));
330
331 connect(myButton, &KUrlDragPushButton::pressed, m_parent, [this]() {
332 slotUpdateUrl();
333 });
334
335 widget->installEventFilter(m_parent);
336 m_parent->setFocusProxy(widget);
338 topLayout->addWidget(myButton);
339
340 connectSignals(m_parent);
341 connect(myButton, &KUrlDragPushButton::clicked, m_parent, [this]() {
342 slotOpenDialog();
343 });
344
346 m_startDirCustomized = false;
347
348 myCompletion = new KUrlCompletion();
349 updateCompletionStartDir(m_startDir);
350
351 setCompletionObject(myCompletion);
352
353 QAction *openAction = new QAction(m_parent);
354 openAction->setShortcut(QKeySequence::Open);
355 m_parent->connect(openAction, &QAction::triggered, m_parent, [this]() {
356 slotOpenDialog();
357 });
358}
359
361{
362 d->setText(url.toDisplayString(QUrl::PreferLocalFile));
363}
364
366{
367 d->setText(text);
368}
369
371{
372 d->m_startDir = startDir;
373 d->m_startDirCustomized = true;
374 d->updateCompletionStartDir(startDir);
375}
376
377void KUrlRequester::changeEvent(QEvent *e)
378{
379 if (e->type() == QEvent::WindowTitleChange) {
380 if (d->myFileDialog) {
381 d->myFileDialog->setWindowTitle(windowTitle());
382 }
383 }
385}
386
387QUrl KUrlRequester::url() const
388{
389 return d->url();
390}
391
393{
394 return d->m_startDir;
395}
396
397QString KUrlRequester::text() const
398{
399 return d->text();
400}
401
402void KUrlRequester::KUrlRequesterPrivate::slotOpenDialog()
403{
404 if (myFileDialog) {
405 if (myFileDialog->isVisible()) {
406 // The file dialog is already being shown, raise it and exit
407 myFileDialog->raise();
408 myFileDialog->activateWindow();
409 return;
410 }
411 }
412
413 if (!m_fileDialogModeWasDirAndFile
414 && (((fileDialogMode & KFile::Directory) && !(fileDialogMode & KFile::File)) ||
415 /* catch possible fileDialog()->setMode( KFile::Directory ) changes */
416 (myFileDialog && (myFileDialog->fileMode() == QFileDialog::Directory && myFileDialog->testOption(QFileDialog::ShowDirsOnly))))) {
417 const QUrl openUrl = (!m_parent->url().isEmpty() && !m_parent->url().isRelative()) ? m_parent->url() : m_startDir;
418
419 /* FIXME We need a new abstract interface for using KDirSelectDialog in a non-modal way */
420
421 QUrl newUrl;
422 if (fileDialogMode & KFile::LocalOnly) {
423 newUrl = QFileDialog::getExistingDirectoryUrl(m_parent, QString(), openUrl, QFileDialog::ShowDirsOnly, QStringList() << QStringLiteral("file"));
424 } else {
425 newUrl = getDirFromFileDialog(openUrl);
426 }
427
428 if (newUrl.isValid()) {
429 m_parent->setUrl(newUrl);
430 Q_EMIT m_parent->urlSelected(url());
431 }
432 } else {
433 Q_EMIT m_parent->openFileDialog(m_parent);
434
435 if (((fileDialogMode & KFile::Directory) && (fileDialogMode & KFile::File)) || m_fileDialogModeWasDirAndFile) {
436 QMenu *dirOrFileMenu = new QMenu();
437 QAction *fileAction = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("File"));
438 QAction *dirAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-new")), i18n("Directory"));
439 dirOrFileMenu->addAction(fileAction);
440 dirOrFileMenu->addAction(dirAction);
441
442 connect(fileAction, &QAction::triggered, [this]() {
443 fileDialogMode = KFile::File;
444 applyFileMode(m_parent->fileDialog(), fileDialogMode, fileDialogAcceptMode);
445 m_fileDialogModeWasDirAndFile = true;
446 createFileDialog();
447 });
448
449 connect(dirAction, &QAction::triggered, [this]() {
450 fileDialogMode = KFile::Directory;
451 applyFileMode(m_parent->fileDialog(), fileDialogMode, fileDialogAcceptMode);
452 m_fileDialogModeWasDirAndFile = true;
453 createFileDialog();
454 });
455
456 dirOrFileMenu->exec(m_parent->mapToGlobal(QPoint(m_parent->width(), m_parent->height())));
457
458 return;
459 }
460
461 createFileDialog();
462 }
463}
464
465void KUrlRequester::KUrlRequesterPrivate::slotFileDialogAccepted()
466{
467 if (!myFileDialog) {
468 return;
469 }
470
471 const QUrl newUrl = myFileDialog->selectedUrls().constFirst();
472 if (newUrl.isValid()) {
473 m_parent->setUrl(newUrl);
474 Q_EMIT m_parent->urlSelected(url());
475 // remember url as defaultStartDir and update startdir for autocompletion
476 if (newUrl.isLocalFile() && !m_startDirCustomized) {
477 m_startDir = newUrl.adjusted(QUrl::RemoveFilename);
478 updateCompletionStartDir(m_startDir);
479 }
480 }
481}
482
484{
485 Q_ASSERT((mode & KFile::Files) == 0);
486 d->fileDialogMode = mode;
487 if ((mode & KFile::Directory) && !(mode & KFile::File)) {
488 d->myCompletion->setMode(KUrlCompletion::DirCompletion);
489 }
490
491 if (d->myFileDialog) {
492 d->applyFileMode(d->myFileDialog, mode, d->fileDialogAcceptMode);
493 }
494}
495
496KFile::Modes KUrlRequester::mode() const
497{
498 return d->fileDialogMode;
499}
500
502{
503 d->fileDialogAcceptMode = mode;
504
505 if (d->myFileDialog) {
506 d->applyFileMode(d->myFileDialog, d->fileDialogMode, mode);
507 }
508}
509
510QFileDialog::AcceptMode KUrlRequester::acceptMode() const
511{
512 return d->fileDialogAcceptMode;
513}
514
516{
517 return d->nameFilters;
518}
519
521{
522 d->nameFilters = filters;
523
524 if (d->myFileDialog) {
525 d->myFileDialog->setNameFilters(d->nameFilters);
526 }
527}
528
530{
531 if (filter.isEmpty()) {
533 return;
534 }
535
536 // by default use ";;" as separator
537 // if not present, support alternatively "\n" (matching QFileDialog behaviour)
538 // if also not present split() will simply return the string passed in
539 QString separator = QStringLiteral(";;");
540 if (!filter.contains(separator)) {
541 separator = QStringLiteral("\n");
542 }
543 setNameFilters(filter.split(separator));
544}
545
547{
548 d->mimeTypeFilters = mimeTypes;
549
550 if (d->myFileDialog) {
551 d->myFileDialog->setMimeTypeFilters(d->mimeTypeFilters);
552 }
553 d->myCompletion->setMimeTypeFilters(d->mimeTypeFilters);
554}
555
557{
558 return d->mimeTypeFilters;
559}
560
562{
563 if (d->myFileDialog
564 && ((d->myFileDialog->fileMode() == QFileDialog::Directory && !(d->fileDialogMode & KFile::Directory))
565 || (d->myFileDialog->fileMode() != QFileDialog::Directory && (d->fileDialogMode & KFile::Directory)))) {
566 delete d->myFileDialog;
567 d->myFileDialog = nullptr;
568 }
569
570 if (!d->myFileDialog) {
571 d->myFileDialog = new QFileDialog(window(), windowTitle());
572 if (!d->mimeTypeFilters.isEmpty()) {
573 d->myFileDialog->setMimeTypeFilters(d->mimeTypeFilters);
574 } else {
575 d->myFileDialog->setNameFilters(d->nameFilters);
576 }
577
578 d->applyFileMode(d->myFileDialog, d->fileDialogMode, d->fileDialogAcceptMode);
579
580 d->myFileDialog->setWindowModality(d->fileDialogModality);
581 connect(d->myFileDialog, &QFileDialog::accepted, this, [this]() {
582 d->slotFileDialogAccepted();
583 });
584 }
585
586 return d->myFileDialog;
587}
588
590{
591 d->setText(QString());
592}
593
595{
596 return d->edit;
597}
598
600{
601 return d->combo;
602}
603
604void KUrlRequester::KUrlRequesterPrivate::slotUpdateUrl()
605{
606 const QUrl visibleUrl = url();
607 QUrl u = visibleUrl;
608 if (visibleUrl.isRelative()) {
610 }
611 myButton->setURL(u);
612}
613
614bool KUrlRequester::eventFilter(QObject *obj, QEvent *ev)
615{
616 if ((d->edit == obj) || (d->combo == obj)) {
617 if ((ev->type() == QEvent::FocusIn) || (ev->type() == QEvent::FocusOut))
618 // Forward focusin/focusout events to the urlrequester; needed by file form element in khtml
619 {
620 QApplication::sendEvent(this, ev);
621 }
622 }
623 return QWidget::eventFilter(obj, ev);
624}
625
627{
628 return d->myButton;
629}
630
632{
633 return d->myCompletion;
634}
635
637{
638 if (d->edit) {
639 d->edit->setPlaceholderText(msg);
640 }
641}
642
643QString KUrlRequester::placeholderText() const
644{
645 if (d->edit) {
646 return d->edit->placeholderText();
647 } else {
648 return QString();
649 }
650}
651
652Qt::WindowModality KUrlRequester::fileDialogModality() const
653{
654 return d->fileDialogModality;
655}
656
658{
659 d->fileDialogModality = modality;
660}
661
663{
665
666 KLineEdit *edit = d->edit;
667 if (!edit && d->combo) {
668 edit = qobject_cast<KLineEdit *>(d->combo->lineEdit());
669 }
670
671#ifndef NDEBUG
672 if (!edit) {
673 qCWarning(KIO_WIDGETS) << "KUrlRequester's lineedit is not a KLineEdit!??\n";
674 }
675#endif
676
677 d->editor.setRepresentationWidget(this);
678 d->editor.setLineEdit(edit);
679 return d->editor;
680}
681
682KUrlComboRequester::KUrlComboRequester(QWidget *parent)
683 : KUrlRequester(new KComboBox(false), parent)
684 , d(nullptr)
685{
686}
687
688#include "kurlrequester.moc"
689#include "moc_kurlrequester.cpp"
void returnPressed(const QString &text)
KFile is a class which provides a namespace for some enumerated values associated with the kfile libr...
Definition kfile.h:24
void returnKeyPressed(const QString &text)
static bool supportsListing(const QUrl &url)
Returns whether the protocol can list files/objects.
This class does completion of URLs including user directories (~user) and environment variables.
QString replacedPath(const QString &text) const
Replaces username and/or environment variables, depending on the current settings and returns the fil...
This class is a widget showing a lineedit and a button, which invokes a filedialog.
void setFileDialogModality(Qt::WindowModality modality)
Set the window modality for the file dialog to modality Directory selection dialogs are always modal.
void setUrl(const QUrl &url)
Sets the url in the lineedit to url.
void setStartDir(const QUrl &startDir)
Sets the start dir startDir.
QPushButton * button() const
void setNameFilter(const QString &filter)
Sets the filters for the file dialog.
QStringList nameFilters
void clear()
Clears the lineedit/combobox.
~KUrlRequester() override
Destructs the KUrlRequester.
const KEditListWidget::CustomEditor & customEditor()
virtual QFileDialog * fileDialog() const
void setMode(KFile::Modes mode)
Sets the mode of the file dialog.
KLineEdit * lineEdit() const
KUrlCompletion * completionObject() const
void setText(const QString &text)
Sets the current text in the lineedit or combobox.
void setMimeTypeFilters(const QStringList &mimeTypes)
Sets the MIME type filters for the file dialog.
QUrl startDir() const
void textChanged(const QString &)
Emitted when the text in the lineedit changes.
void returnPressed(const QString &text)
Emitted when return or enter was pressed in the lineedit.
void setAcceptMode(QFileDialog::AcceptMode m)
Sets the open / save mode of the file dialog.
KUrlRequester(QWidget *parent=nullptr)
Constructs a KUrlRequester widget.
KComboBox * comboBox() const
void textEdited(const QString &)
Emitted when the text in the lineedit was modified by the user.
void setNameFilters(const QStringList &filters)
Sets the filters for the file dialog.
void setPlaceholderText(const QString &msg)
This makes the KUrlRequester line edit display a grayed-out hinting text as long as the user didn't e...
QStringList mimeTypeFilters() const
Returns the MIME type filters for the file dialog.
QString i18n(const char *text, const TYPE &arg...)
QCA_EXPORT void init()
void clicked(bool checked)
void setIcon(const QIcon &icon)
void setShortcut(const QKeySequence &shortcut)
void triggered(bool checked)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
virtual void setSpacing(int spacing) override
void currentTextChanged(const QString &text)
void editTextChanged(const QString &text)
bool sendEvent(QObject *receiver, QEvent *event)
void accepted()
virtual int exec()
QString currentPath()
void setMimeData(QMimeData *data)
WindowTitleChange
Type type() const const
void setAcceptMode(AcceptMode mode)
void setFileMode(FileMode mode)
QUrl getExistingDirectoryUrl(QWidget *parent, const QString &caption, const QUrl &dir, Options options, const QStringList &supportedSchemes)
void selectUrl(const QUrl &url)
void setDirectoryUrl(const QUrl &directory)
void setOption(Option option, bool on)
QIcon fromTheme(const QString &name)
void setContentsMargins(const QMargins &margins)
void setClearButtonEnabled(bool enable)
void returnPressed()
void textChanged(const QString &text)
void textEdited(const QString &text)
void append(QList< T > &&value)
void clear()
bool isEmpty() const const
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * exec()
void setUrls(const QList< QUrl > &urls)
Q_EMITQ_EMIT
Q_OBJECTQ_OBJECT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool eventFilter(QObject *watched, QEvent *event)
void installEventFilter(QObject *filterObj)
QObject * parent() const const
void removeEventFilter(QObject *obj)
virtual QSize sizeHint() const const override
QSize expandedTo(const QSize &otherSize) const const
int height() const const
bool isEmpty() const const
StrongFocus
NonModal
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
PreferLocalFile
QUrl adjusted(FormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
bool isLocalFile() const const
bool isRelative() const const
bool isValid() const const
QUrl resolved(const QUrl &relative) const const
void setUrl(const QString &url, ParsingMode parsingMode)
QString toDisplayString(FormattingOptions options) const const
QString url(FormattingOptions options) const const
virtual void changeEvent(QEvent *event)
void setFocusPolicy(Qt::FocusPolicy policy)
void setFixedSize(const QSize &s)
void setFocusProxy(QWidget *w)
void setParent(QWidget *parent)
void show()
void setSizePolicy(QSizePolicy)
void setToolTip(const QString &)
QWidget * window() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.