Messagelib

pane.cpp
1/*
2 SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "pane.h"
8
9#include <KActionCollection>
10#include <KActionMenu>
11#include <KLocalizedString>
12#include <KToggleAction>
13#include <KXMLGUIClient>
14#include <QAction>
15#include <QIcon>
16#include <QMenu>
17
18#include <QAbstractItemModel>
19#include <QAbstractProxyModel>
20#include <QApplication>
21#include <QHeaderView>
22#include <QItemSelectionModel>
23#include <QMouseEvent>
24#include <QRegularExpression>
25#include <QTabBar>
26#include <QToolButton>
27
28#include "core/model.h"
29#include "core/widgets/quicksearchline.h"
30#include "messagelistsettings.h"
31#include "messagelistutil_p.h"
32#include "storagemodel.h"
33#include "widget.h"
34#include <Akonadi/ETMViewStateSaver>
35#include <Akonadi/MessageStatus>
36
37namespace MessageList
38{
39class Pane::PanePrivate
40{
41public:
42 PanePrivate(Pane *owner)
43 : q(owner)
44 {
45 }
46
47 void setCurrentFolder(const QModelIndex &etmIndex);
48 void onNewTabClicked();
49 void onCloseTabClicked();
50 void activateTab();
51 void closeTab(QWidget *);
52 void onCurrentTabChanged();
53 void onTabContextMenuRequest(const QPoint &pos);
54 void activateNextTab();
55 void activatePreviousTab();
56 void moveTabLeft();
57 void moveTabRight();
58 void moveTabBackward();
59 void moveTabForward();
60 void changeQuicksearchVisibility(bool);
61 void addActivateTabAction(int i);
62 void slotTabCloseRequested(int index);
63 [[nodiscard]] QItemSelection mapSelectionFromSource(const QItemSelection &selection) const;
64 void updateTabControls();
65
66 Pane *const q;
67
68 KXMLGUIClient *mXmlGuiClient = nullptr;
69 KActionMenu *mActionMenu = nullptr;
70
71 QAbstractItemModel *mModel = nullptr;
72 QItemSelectionModel *mSelectionModel = nullptr;
73 Core::PreSelectionMode mPreSelectionMode = Core::PreSelectLastSelected;
74
75 QHash<Widget *, QItemSelectionModel *> mWidgetSelectionHash;
77
78 QToolButton *mNewTabButton = nullptr;
79 QToolButton *mCloseTabButton = nullptr;
80 QAction *mCloseTabAction = nullptr;
81 QAction *mActivateNextTabAction = nullptr;
82 QAction *mActivatePreviousTabAction = nullptr;
83 QAction *mMoveTabLeftAction = nullptr;
84 QAction *mMoveTabRightAction = nullptr;
85 QString mQuickSearchPlaceHolderMessage;
86 bool mPreferEmptyTab = false;
87 int mMaxTabCreated = 0;
88};
89} // namespace MessageList
90
91using namespace Akonadi;
92using namespace MessageList;
93
94Pane::Pane(bool restoreSession, QAbstractItemModel *model, QItemSelectionModel *selectionModel, QWidget *parent)
95 : QTabWidget(parent)
96 , d(new PanePrivate(this))
97{
98 setDocumentMode(true);
99 d->mModel = model;
100 d->mSelectionModel = selectionModel;
101
102 // Build the proxy stack
103 const auto *proxyModel = qobject_cast<const QAbstractProxyModel *>(d->mSelectionModel->model());
104
105 while (proxyModel) {
106 if (proxyModel == d->mModel) {
107 break;
108 }
109
110 d->mProxyStack << proxyModel;
111 const auto nextProxyModel = qobject_cast<const QAbstractProxyModel *>(proxyModel->sourceModel());
112
113 if (!nextProxyModel) {
114 // It's the final model in the chain, so it is necessarily the sourceModel.
115 Q_ASSERT(proxyModel->sourceModel() == d->mModel);
116 break;
117 }
118 proxyModel = nextProxyModel;
119 } // Proxy stack done
120
121 d->mNewTabButton = new QToolButton(this);
122 d->mNewTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
123 d->mNewTabButton->adjustSize();
124 d->mNewTabButton->setToolTip(i18nc("@info:tooltip", "Open a new tab"));
125#ifndef QT_NO_ACCESSIBILITY
126 d->mNewTabButton->setAccessibleName(i18n("New tab"));
127#endif
128 setCornerWidget(d->mNewTabButton, Qt::TopLeftCorner);
129 connect(d->mNewTabButton, &QToolButton::clicked, this, [this]() {
130 d->onNewTabClicked();
131 });
132
133 d->mCloseTabButton = new QToolButton(this);
134 d->mCloseTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
135 d->mCloseTabButton->adjustSize();
136 d->mCloseTabButton->setToolTip(i18nc("@info:tooltip", "Close the current tab"));
137#ifndef QT_NO_ACCESSIBILITY
138 d->mCloseTabButton->setAccessibleName(i18n("Close tab"));
139#endif
140 setCornerWidget(d->mCloseTabButton, Qt::TopRightCorner);
141 connect(d->mCloseTabButton, &QToolButton::clicked, this, [this]() {
142 d->onCloseTabClicked();
143 });
144
145 setTabsClosable(true);
146 connect(this, &Pane::tabCloseRequested, this, [this](int index) {
147 d->slotTabCloseRequested(index);
148 });
149
150 readConfig(restoreSession);
151 setMovable(true);
152
153 connect(this, &Pane::currentChanged, this, [this]() {
154 d->onCurrentTabChanged();
155 });
156
158 connect(this, &Pane::customContextMenuRequested, this, [this](const QPoint &point) {
159 d->onTabContextMenuRequest(point);
160 });
161
162 connect(MessageListSettings::self(), &MessageListSettings::configChanged, this, [this]() {
163 d->updateTabControls();
164 });
165
166 // connect(this, &QTabWidget::tabBarDoubleClicked, this, &Pane::createNewTab);
167
168 tabBar()->installEventFilter(this);
169}
170
171Pane::~Pane()
172{
173 saveCurrentSelection();
174 writeConfig(true);
175}
176
177void Pane::PanePrivate::addActivateTabAction(int i)
178{
179 const QString actionname = QString::asprintf("activate_tab_%02d", i);
180 auto action = new QAction(i18n("Activate Tab %1", i), q);
181 mXmlGuiClient->actionCollection()->addAction(actionname, action);
182 mXmlGuiClient->actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+%1").arg(i)));
183 connect(action, &QAction::triggered, q, [this]() {
184 activateTab();
185 });
186}
187
188void Pane::PanePrivate::slotTabCloseRequested(int index)
189{
190 QWidget *w = q->widget(index);
191 if (w) {
192 closeTab(w);
193 }
194}
195
197{
198 d->mXmlGuiClient = xmlGuiClient;
199
200 auto const showHideQuicksearch = new KToggleAction(i18n("Show Quick Search Bar"), this);
201 d->mXmlGuiClient->actionCollection()->setDefaultShortcut(showHideQuicksearch, Qt::CTRL | Qt::Key_H);
202 showHideQuicksearch->setChecked(MessageListSettings::showQuickSearch());
203
204 d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("show_quick_search"), showHideQuicksearch);
205 connect(showHideQuicksearch, &KToggleAction::triggered, this, [this](bool state) {
206 d->changeQuicksearchVisibility(state);
207 });
208
209 for (int i = 0; i < count(); ++i) {
211 if (w) {
212 w->setXmlGuiClient(d->mXmlGuiClient);
213 }
214 }
215
216 // Setup "View->Message List" actions.
217 if (xmlGuiClient) {
218 if (d->mActionMenu) {
219 d->mXmlGuiClient->actionCollection()->removeAction(d->mActionMenu);
220 }
221 d->mActionMenu = new KActionMenu(QIcon(), i18n("Message List"), this);
222 d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("view_message_list"), d->mActionMenu);
223 MessageList::Util::fillViewMenu(d->mActionMenu->menu(), this);
224
225 d->mActionMenu->addSeparator();
226
227 auto action = new QAction(i18n("Create New Tab"), this);
228 d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("create_new_tab"), action);
229 d->mXmlGuiClient->actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_O));
230 connect(action, &QAction::triggered, this, [this]() {
231 d->onNewTabClicked();
232 });
233 d->mActionMenu->addAction(action);
234 d->mActionMenu->addSeparator();
235
236 d->mMaxTabCreated = count();
237 for (int i = 1; i < 10 && i <= count(); ++i) {
238 d->addActivateTabAction(i);
239 }
240
243
249 nextIcon = QStringLiteral("go-previous-view");
250 prevIcon = QStringLiteral("go-next-view");
251 } else {
254 nextIcon = QStringLiteral("go-next-view");
255 prevIcon = QStringLiteral("go-previous-view");
256 }
257
258 d->mActivateNextTabAction = new QAction(i18n("Activate Next Tab"), this);
259 d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("activate_next_tab"), d->mActivateNextTabAction);
260 d->mActivateNextTabAction->setEnabled(false);
261 d->mActivateNextTabAction->setIcon(QIcon::fromTheme(nextIcon));
262 d->mXmlGuiClient->actionCollection()->setDefaultShortcuts(d->mActivateNextTabAction, nextShortcut);
263 connect(d->mActivateNextTabAction, &QAction::triggered, this, [this]() {
264 d->activateNextTab();
265 });
266 d->mActionMenu->addAction(d->mActivateNextTabAction);
267
268 d->mActivatePreviousTabAction = new QAction(i18n("Activate Previous Tab"), this);
269 d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("activate_previous_tab"), d->mActivatePreviousTabAction);
270 d->mXmlGuiClient->actionCollection()->setDefaultShortcuts(d->mActivatePreviousTabAction, prevShortcut);
271 d->mActivatePreviousTabAction->setIcon(QIcon::fromTheme(prevIcon));
272 d->mActivatePreviousTabAction->setEnabled(false);
273 connect(d->mActivatePreviousTabAction, &QAction::triggered, this, [this]() {
274 d->activatePreviousTab();
275 });
276 d->mActionMenu->addAction(d->mActivatePreviousTabAction);
277
278 d->mActionMenu->addSeparator();
279
280 d->mMoveTabLeftAction = new QAction(i18n("Move Tab Left"), this);
281 d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("move_tab_left"), d->mMoveTabLeftAction);
282 d->mMoveTabLeftAction->setEnabled(false);
283 connect(d->mMoveTabLeftAction, &QAction::triggered, this, [this]() {
284 d->moveTabLeft();
285 });
286 d->mActionMenu->addAction(d->mMoveTabLeftAction);
287
288 d->mMoveTabRightAction = new QAction(i18n("Move Tab Right"), this);
289 d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("move_tab_right"), d->mMoveTabRightAction);
290 d->mMoveTabRightAction->setEnabled(false);
291 connect(d->mMoveTabRightAction, &QAction::triggered, this, [this]() {
292 d->moveTabRight();
293 });
294 d->mActionMenu->addAction(d->mMoveTabRightAction);
295
296 d->mActionMenu->addSeparator();
297
298 d->mCloseTabAction = new QAction(i18n("Close Tab"), this);
299 d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("close_current_tab"), d->mCloseTabAction);
300 d->mXmlGuiClient->actionCollection()->setDefaultShortcuts(d->mCloseTabAction,
303 connect(d->mCloseTabAction, &QAction::triggered, this, [this]() {
304 d->onCloseTabClicked();
305 });
306 d->mActionMenu->addAction(d->mCloseTabAction);
307 d->mCloseTabAction->setEnabled(false);
308 }
309}
310
313 bool centerItem,
314 bool loop)
315{
316 auto w = static_cast<Widget *>(currentWidget());
317
318 if (w) {
319 if (w->view()->model()->isLoading()) {
320 return true;
321 }
322
323 return w->selectNextMessageItem(messageTypeFilter, existingSelectionBehaviour, centerItem, loop);
324 } else {
325 return false;
326 }
327}
328
331 bool centerItem,
332 bool loop)
333{
334 auto w = static_cast<Widget *>(currentWidget());
335
336 if (w) {
337 if (w->view()->model()->isLoading()) {
338 return true;
339 }
340
341 return w->selectPreviousMessageItem(messageTypeFilter, existingSelectionBehaviour, centerItem, loop);
342 } else {
343 return false;
344 }
345}
346
348{
349 auto w = static_cast<Widget *>(currentWidget());
350
351 if (w) {
352 if (w->view()->model()->isLoading()) {
353 return true;
354 }
355
356 return w->focusNextMessageItem(messageTypeFilter, centerItem, loop);
357 } else {
358 return false;
359 }
360}
361
363{
364 auto w = static_cast<Widget *>(currentWidget());
365
366 if (w) {
367 if (w->view()->model()->isLoading()) {
368 return true;
369 }
370
371 return w->focusPreviousMessageItem(messageTypeFilter, centerItem, loop);
372 } else {
373 return false;
374 }
375}
376
378{
379 auto w = static_cast<Widget *>(currentWidget());
380
381 if (w) {
382 if (w->view()->model()->isLoading()) {
383 return;
384 }
385
386 w->selectFocusedMessageItem(centerItem);
387 }
388}
389
391{
392 auto w = static_cast<Widget *>(currentWidget());
393
394 if (w) {
395 if (w->view()->model()->isLoading()) {
396 return true;
397 }
398
399 return w->selectFirstMessageItem(messageTypeFilter, centerItem);
400 } else {
401 return false;
402 }
403}
404
406{
407 auto w = static_cast<Widget *>(currentWidget());
408
409 if (w) {
410 if (w->view()->model()->isLoading()) {
411 return true;
412 }
413
414 return w->selectLastMessageItem(messageTypeFilter, centerItem);
415 } else {
416 return false;
417 }
418}
419
421{
422 auto w = static_cast<Widget *>(currentWidget());
423
424 if (w) {
425 if (w->view()->model()->isLoading()) {
426 return;
427 }
428
429 w->selectAll();
430 }
431}
432
434{
435 auto w = static_cast<Widget *>(currentWidget());
436
437 if (w) {
438 if (w->view()->model()->isLoading()) {
439 return;
440 }
441
442 w->setCurrentThreadExpanded(expand);
443 }
444}
445
447{
448 auto w = static_cast<Widget *>(currentWidget());
449
450 if (w) {
451 if (w->view()->model()->isLoading()) {
452 return;
453 }
454
455 w->setAllThreadsExpanded(expand);
456 }
457}
458
460{
461 auto w = static_cast<Widget *>(currentWidget());
462
463 if (w) {
464 if (w->view()->model()->isLoading()) {
465 return;
466 }
467
468 w->setAllGroupsExpanded(expand);
469 }
470}
471
472void Pane::focusQuickSearch(const QString &selectedText)
473{
474 auto w = static_cast<Widget *>(currentWidget());
475
476 if (w) {
477 w->focusQuickSearch(selectedText);
478 }
479}
480
481void Pane::setQuickSearchClickMessage(const QString &msg)
482{
483 d->mQuickSearchPlaceHolderMessage = msg;
484 for (int i = 0; i < count(); ++i) {
486 if (w) {
487 w->setQuickSearchClickMessage(d->mQuickSearchPlaceHolderMessage);
488 }
489 }
490}
491
492void Pane::PanePrivate::setCurrentFolder(const QModelIndex &etmIndex)
493{
494 if (mPreferEmptyTab) {
495 q->createNewTab();
496 }
497
498 auto w = static_cast<Widget *>(q->currentWidget());
499 QItemSelectionModel *s = mWidgetSelectionHash[w];
500
501 w->saveCurrentSelection();
502
503 // Deselect old before we select new - so that the messagelist can clear first.
504 s->clear();
505 if (s->selection().isEmpty()) {
506 w->view()->model()->setPreSelectionMode(mPreSelectionMode);
507 }
508 Q_ASSERT(s->model() == etmIndex.model());
510
512 QIcon icon;
514 for (const QModelIndex &index : s->selectedRows()) {
515 label += index.data(Qt::DisplayRole).toString() + QLatin1StringView(", ");
516 }
517 label.chop(2);
518
519 if (label.isEmpty()) {
520 label = i18nc("@title:tab Empty messagelist", "Empty");
521 icon = QIcon();
522 } else if (s->selectedRows().size() == 1) {
523 icon = s->selectedRows().first().data(Qt::DecorationRole).value<QIcon>();
524 QModelIndex idx = s->selectedRows().first().parent();
525 toolTip = label;
526 while (idx != QModelIndex()) {
527 toolTip = idx.data().toString() + QLatin1Char('/') + toolTip;
528 idx = idx.parent();
529 }
530 } else {
531 icon = QIcon::fromTheme(QStringLiteral("folder"));
532 }
533
534 const int index = q->indexOf(w);
535 q->setTabText(index, label);
536 q->setTabIcon(index, icon);
537 q->setTabToolTip(index, toolTip);
538 if (mPreferEmptyTab) {
539 mSelectionModel->select(mapSelectionFromSource(s->selection()), QItemSelectionModel::ClearAndSelect);
540 }
541 mPreferEmptyTab = false;
542}
543
544void Pane::PanePrivate::activateTab()
545{
546 q->tabBar()->setCurrentIndex(QStringView(q->sender()->objectName()).right(2).toInt() - 1);
547}
548
549void Pane::PanePrivate::moveTabRight()
550{
551 const int numberOfTab = q->tabBar()->count();
552 if (numberOfTab == 1) {
553 return;
554 }
556 moveTabForward();
557 } else {
558 moveTabBackward();
559 }
560}
561
562void Pane::PanePrivate::moveTabLeft()
563{
564 const int numberOfTab = q->tabBar()->count();
565 if (numberOfTab == 1) {
566 return;
567 }
569 moveTabBackward();
570 } else {
571 moveTabForward();
572 }
573}
574
575void Pane::PanePrivate::moveTabForward()
576{
577 const int currentIndex = q->tabBar()->currentIndex();
578 if (currentIndex == q->tabBar()->count() - 1) {
579 return;
580 }
581 q->tabBar()->moveTab(currentIndex, currentIndex + 1);
582}
583
584void Pane::PanePrivate::moveTabBackward()
585{
586 const int currentIndex = q->tabBar()->currentIndex();
587 if (currentIndex == 0) {
588 return;
589 }
590 q->tabBar()->moveTab(currentIndex, currentIndex - 1);
591}
592
593void Pane::PanePrivate::activateNextTab()
594{
595 const int numberOfTab = q->tabBar()->count();
596 if (numberOfTab == 1) {
597 return;
598 }
599
600 int indexTab = (q->tabBar()->currentIndex() + 1);
601
602 if (indexTab == numberOfTab) {
603 indexTab = 0;
604 }
605
606 q->tabBar()->setCurrentIndex(indexTab);
607}
608
609void Pane::PanePrivate::activatePreviousTab()
610{
611 const int numberOfTab = q->tabBar()->count();
612 if (numberOfTab == 1) {
613 return;
614 }
615
616 int indexTab = (q->tabBar()->currentIndex() - 1);
617
618 if (indexTab == -1) {
619 indexTab = numberOfTab - 1;
620 }
621
622 q->tabBar()->setCurrentIndex(indexTab);
623}
624
625void Pane::PanePrivate::onNewTabClicked()
626{
627 q->createNewTab();
628}
629
630void Pane::PanePrivate::onCloseTabClicked()
631{
632 closeTab(q->currentWidget());
633}
634
635void Pane::PanePrivate::closeTab(QWidget *w)
636{
637 if (!w || (q->count() < 2)) {
638 return;
639 }
640
642 if (wWidget) {
643 const bool isLocked = wWidget->isLocked();
644 if (isLocked) {
645 return;
646 }
647 wWidget->saveCurrentSelection();
648 }
649
650 delete w;
651 updateTabControls();
652}
653
654void Pane::PanePrivate::changeQuicksearchVisibility(bool show)
655{
656 for (int i = 0; i < q->count(); ++i) {
657 auto w = qobject_cast<Widget *>(q->widget(i));
658 if (w) {
659 w->changeQuicksearchVisibility(show);
660 }
661 }
662}
663
664bool Pane::eventFilter(QObject *object, QEvent *event)
665{
666 if (event->type() == QEvent::MouseButtonPress) {
667 auto const mouseEvent = static_cast<QMouseEvent *>(event);
668 if (mouseEvent->button() == Qt::MiddleButton) {
669 return true;
670 }
671 }
672 return QTabWidget::eventFilter(object, event);
673}
674
675void Pane::PanePrivate::onCurrentTabChanged()
676{
677 Q_EMIT q->currentTabChanged();
678
679 auto w = static_cast<Widget *>(q->currentWidget());
680 mCloseTabButton->setEnabled(!w->isLocked());
681
682 QItemSelectionModel *s = mWidgetSelectionHash[w];
683
684 mSelectionModel->select(mapSelectionFromSource(s->selection()), QItemSelectionModel::ClearAndSelect);
685}
686
687void Pane::PanePrivate::onTabContextMenuRequest(const QPoint &pos)
688{
689 QTabBar *bar = q->tabBar();
690 if (q->count() <= 1) {
691 return;
692 }
693
694 const int indexBar = bar->tabAt(bar->mapFrom(q, pos));
695 if (indexBar == -1) {
696 return;
697 }
698
699 auto w = qobject_cast<Widget *>(q->widget(indexBar));
700 if (!w) {
701 return;
702 }
703
704 QMenu menu(q);
705
706 QAction *closeTabAction = nullptr;
707 if (!w->isLocked()) {
708 closeTabAction = menu.addAction(i18nc("@action:inmenu", "Close Tab"));
709 closeTabAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
710 }
711
712 QAction *allOtherAction = menu.addAction(i18nc("@action:inmenu", "Close All Other Tabs"));
713 allOtherAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other")));
714
715 menu.addSeparator();
716
717 QAction *lockTabAction = menu.addAction(w->isLocked() ? i18nc("@action:inmenu", "Unlock Tab") : i18nc("@action:inmenu", "Lock Tab"));
718 lockTabAction->setIcon(w->isLocked() ? QIcon::fromTheme(QStringLiteral("lock")) : QIcon::fromTheme(QStringLiteral("unlock")));
719
720 QAction *action = menu.exec(q->mapToGlobal(pos));
721
722 if (action == allOtherAction) { // Close all other tabs
723 QList<Widget *> widgets;
724 const int index = q->indexOf(w);
725
726 for (int i = 0; i < q->count(); ++i) {
727 if (i == index) {
728 continue; // Skip the current one
729 }
730
731 auto other = qobject_cast<Widget *>(q->widget(i));
732 const bool isLocked = other->isLocked();
733 if (!isLocked && other) {
734 widgets << other;
735 }
736 }
737
738 for (Widget *other : std::as_const(widgets)) {
739 other->saveCurrentSelection();
740 delete other;
741 }
742
743 updateTabControls();
744 } else if (closeTabAction && (action == closeTabAction)) {
745 closeTab(q->widget(indexBar));
746 } else if (action == lockTabAction) {
747 auto tab = qobject_cast<Widget *>(q->widget(indexBar));
748 const bool isLocked = !tab->isLocked();
749 tab->setLockTab(isLocked);
750 q->setTabIcon(indexBar, isLocked ? QIcon::fromTheme(QStringLiteral("lock")) : QIcon::fromTheme(QStringLiteral("unlock")));
751 q->tabBar()->tabButton(indexBar, QTabBar::RightSide)->setEnabled(!isLocked);
752 if (q->tabBar()->currentIndex() == indexBar) {
753 mCloseTabButton->setEnabled(!isLocked);
754 }
755 }
756}
757
758MessageList::StorageModel *Pane::createStorageModel(QAbstractItemModel *model, QItemSelectionModel *selectionModel, QObject *parent)
759{
760 return new MessageList::StorageModel(model, selectionModel, parent);
761}
762
763Akonadi::Collection Pane::currentFolder() const
764{
765 auto w = static_cast<Widget *>(currentWidget());
766 if (w) {
767 return w->currentCollection();
768 }
769 return {};
770}
771
773 const QModelIndex &etmIndex,
774 bool,
776 const QString &overrideLabel)
777{
778 auto w = static_cast<Widget *>(currentWidget());
779 if (!w->isLocked()) {
780 d->setCurrentFolder(etmIndex);
781 d->mPreSelectionMode = preSelectionMode;
782 if (w) {
783 w->setCurrentFolder(collection);
784 QItemSelectionModel *s = d->mWidgetSelectionHash[w];
785 MessageList::StorageModel *m = createStorageModel(d->mModel, s, w);
786 w->setStorageModel(m, preSelectionMode);
787 if (!overrideLabel.isEmpty()) {
788 const int index = indexOf(w);
790 }
791 }
792 }
793}
794
795void Pane::updateTabIconText(const Akonadi::Collection &collection, const QString &label, const QIcon &icon)
796{
797 for (int i = 0; i < count(); ++i) {
799 if (w && (w->currentCollection() == collection)) {
800 const int index = indexOf(w);
801 setTabText(index, label);
802 setTabIcon(index, icon);
803 }
804 }
805}
806
808{
809 auto w = new Widget(this);
810 w->setXmlGuiClient(d->mXmlGuiClient);
811
812 addTab(w, i18nc("@title:tab Empty messagelist", "Empty"));
813 if (!d->mQuickSearchPlaceHolderMessage.isEmpty()) {
814 w->setQuickSearchClickMessage(d->mQuickSearchPlaceHolderMessage);
815 }
816 if (d->mXmlGuiClient && count() < 10) {
817 if (d->mMaxTabCreated < count()) {
818 d->mMaxTabCreated = count();
819 d->addActivateTabAction(d->mMaxTabCreated);
820 }
821 }
822
823 auto s = new QItemSelectionModel(d->mModel, w);
824 MessageList::StorageModel *m = createStorageModel(d->mModel, s, w);
825 w->setStorageModel(m);
826
827 d->mWidgetSelectionHash[w] = s;
828
833
835
836 connect(w, &Core::Widget::forceLostFocus, this, &Pane::forceLostFocus);
837 connect(w, &Core::Widget::unlockTabRequested, this, [this, w]() {
838 for (int i = 0; i < count(); ++i) {
839 if (w == qobject_cast<Widget *>(widget(i))) {
840 setTabIcon(i, QIcon::fromTheme(QStringLiteral("unlock")));
841 }
842 }
843 });
844
845 d->updateTabControls();
847 return s;
848}
849
850QItemSelection Pane::PanePrivate::mapSelectionFromSource(const QItemSelection &selection) const
851{
852 QItemSelection result = selection;
853
855
856 for (Iterator it = mProxyStack.end() - 1; it != mProxyStack.begin(); --it) {
857 result = (*it)->mapSelectionFromSource(result);
858 }
859 result = mProxyStack.first()->mapSelectionFromSource(result);
860
861 return result;
862}
863
864void Pane::PanePrivate::updateTabControls()
865{
866 const bool enableAction = (q->count() > 1);
867 if (enableAction) {
868 q->setCornerWidget(mCloseTabButton, Qt::TopRightCorner);
869 mCloseTabButton->setVisible(true);
870 } else {
871 q->setCornerWidget(nullptr, Qt::TopRightCorner);
872 }
873 if (mCloseTabAction) {
874 mCloseTabAction->setEnabled(enableAction);
875 }
876 if (mActivatePreviousTabAction) {
877 mActivatePreviousTabAction->setEnabled(enableAction);
878 }
879 if (mActivateNextTabAction) {
880 mActivateNextTabAction->setEnabled(enableAction);
881 }
882 if (mMoveTabRightAction) {
883 mMoveTabRightAction->setEnabled(enableAction);
884 }
885 if (mMoveTabLeftAction) {
886 mMoveTabLeftAction->setEnabled(enableAction);
887 }
888
889 q->tabBar()->setVisible(enableAction);
890 if (enableAction) {
891 q->setCornerWidget(mNewTabButton, Qt::TopLeftCorner);
892 mNewTabButton->setVisible(true);
893 } else {
894 q->setCornerWidget(nullptr, Qt::TopLeftCorner);
895 }
896
897 q->setTabsClosable(true);
898 const int numberOfTab(q->count());
899 if (numberOfTab == 1) {
900 q->tabBar()->tabButton(0, QTabBar::RightSide)->setEnabled(false);
901 } else if (numberOfTab > 1) {
902 q->tabBar()->tabButton(0, QTabBar::RightSide)->setEnabled(true);
903 }
904}
905
907{
908 auto w = static_cast<Widget *>(currentWidget());
909
910 if (!w) {
911 return {};
912 }
913
914 return w->currentItem();
915}
916
918{
919 auto w = static_cast<Widget *>(currentWidget());
920
921 if (!w) {
922 return {};
923 }
924
925 return w->currentMessage();
926}
927
929{
930 auto w = static_cast<Widget *>(currentWidget());
931 if (!w) {
932 return {};
933 }
934 return w->selectionAsMessageList(includeCollapsedChildren);
935}
936
938{
939 auto w = static_cast<Widget *>(currentWidget());
940 if (!w) {
941 return {};
942 }
943 return w->selectionAsMessageItemList(includeCollapsedChildren);
944}
945
946QList<Akonadi::Item::Id> Pane::selectionAsListMessageId(bool includeCollapsedChildren) const
947{
948 auto w = static_cast<Widget *>(currentWidget());
949 if (!w) {
950 return {};
951 }
952 return w->selectionAsListMessageId(includeCollapsedChildren);
953}
954
956{
957 auto w = static_cast<Widget *>(currentWidget());
958 if (!w) {
959 return {};
960 }
961 return w->selectionAsMessageItemListId(includeCollapsedChildren);
962}
963
965{
966 auto w = static_cast<Widget *>(currentWidget());
967 if (!w) {
968 return {};
969 }
970 return w->currentThreadAsMessageList();
971}
972
973Akonadi::Item::List Pane::itemListFromPersistentSet(MessageList::Core::MessageItemSetReference ref)
974{
975 auto w = static_cast<Widget *>(currentWidget());
976 if (w) {
977 return w->itemListFromPersistentSet(ref);
978 }
979 return {};
980}
981
982void Pane::deletePersistentSet(MessageList::Core::MessageItemSetReference ref)
983{
984 auto w = static_cast<Widget *>(currentWidget());
985 if (w) {
986 w->deletePersistentSet(ref);
987 }
988}
989
990void Pane::markMessageItemsAsAboutToBeRemoved(MessageList::Core::MessageItemSetReference ref, bool bMark)
991{
992 auto w = static_cast<Widget *>(currentWidget());
993 if (w) {
994 w->markMessageItemsAsAboutToBeRemoved(ref, bMark);
995 }
996}
997
999{
1000 auto w = static_cast<Widget *>(currentWidget());
1001 if (!w) {
1002 return {};
1003 }
1004 return w->currentFilterStatus();
1005}
1006
1007Core::QuickSearchLine::SearchOptions Pane::currentOptions() const
1008{
1009 auto w = static_cast<Widget *>(currentWidget());
1010 if (!w) {
1011 return Core::QuickSearchLine::SearchEveryWhere;
1012 }
1013 return w->currentOptions();
1014}
1015
1017{
1018 auto w = static_cast<Widget *>(currentWidget());
1019 if (w) {
1020 return w->currentFilterSearchString();
1021 }
1022 return {};
1023}
1024
1026{
1027 auto w = static_cast<Widget *>(currentWidget());
1028 if (w) {
1029 return w->isThreaded();
1030 }
1031 return false;
1032}
1033
1035{
1036 auto w = static_cast<Widget *>(currentWidget());
1037 if (w) {
1038 return w->selectionEmpty();
1039 }
1040 return false;
1041}
1042
1046 bool includeCollapsedChildren) const
1047{
1048 auto w = static_cast<Widget *>(currentWidget());
1049 if (!w) {
1050 return false;
1051 }
1052
1053 return w->getSelectionStats(selectedItems, selectedVisibleItems, allSelectedBelongToSameThread, includeCollapsedChildren);
1054}
1055
1056MessageList::Core::MessageItemSetReference Pane::selectionAsPersistentSet(bool includeCollapsedChildren) const
1057{
1058 auto w = static_cast<Widget *>(currentWidget());
1059 if (w) {
1060 return w->selectionAsPersistentSet(includeCollapsedChildren);
1061 }
1062 return -1;
1063}
1064
1065MessageList::Core::MessageItemSetReference Pane::currentThreadAsPersistentSet() const
1066{
1067 auto w = static_cast<Widget *>(currentWidget());
1068 if (w) {
1069 return w->currentThreadAsPersistentSet();
1070 }
1071 return -1;
1072}
1073
1075{
1076 auto w = static_cast<Widget *>(currentWidget());
1077 if (w) {
1078 QWidget *view = w->view();
1079 if (view) {
1080 view->setFocus();
1081 }
1082 }
1083}
1084
1086{
1087 d->updateTabControls();
1088}
1089
1091{
1092 auto w = static_cast<Widget *>(currentWidget());
1093 if (w) {
1094 return w->view()->selectionModel();
1095 }
1096 return nullptr;
1097}
1098
1099void Pane::resetModelStorage()
1100{
1101 auto w = static_cast<Widget *>(currentWidget());
1102 if (w) {
1103 auto m = static_cast<MessageList::StorageModel *>(w->storageModel());
1104 if (m) {
1105 m->resetModelStorage();
1106 }
1107 }
1108}
1109
1110void Pane::setPreferEmptyTab(bool emptyTab)
1111{
1112 d->mPreferEmptyTab = emptyTab;
1113}
1114
1115void Pane::saveCurrentSelection()
1116{
1117 for (int i = 0; i < count(); ++i) {
1118 auto w = qobject_cast<Widget *>(widget(i));
1119 if (w) {
1120 w->saveCurrentSelection();
1121 }
1122 }
1123}
1124
1125void Pane::updateTagComboBox()
1126{
1127 for (int i = 0; i < count(); ++i) {
1128 auto w = qobject_cast<Widget *>(widget(i));
1129 if (w) {
1130 w->populateStatusFilterCombo();
1131 }
1132 }
1133}
1134
1135void Pane::writeConfig(bool restoreSession)
1136{
1137 KConfigGroup conf(MessageList::MessageListSettings::self()->config(), QStringLiteral("MessageListPane"));
1138
1139 // Delete list before
1140 const QStringList list = MessageList::MessageListSettings::self()->config()->groupList().filter(QRegularExpression(QStringLiteral("MessageListTab\\d+")));
1141 for (const QString &group : list) {
1142 MessageList::MessageListSettings::self()->config()->deleteGroup(group);
1143 }
1144
1145 if (restoreSession) {
1146 conf.writeEntry(QStringLiteral("currentIndex"), currentIndex());
1147
1148 int elementTab = 0;
1149 for (int i = 0; i < count(); ++i) {
1150 auto w = qobject_cast<Widget *>(widget(i));
1151 if (w && w->currentCollection().isValid()) {
1152 KConfigGroup grp(MessageList::MessageListSettings::self()->config(), QStringLiteral("MessageListTab%1").arg(elementTab));
1153 grp.writeEntry(QStringLiteral("collectionId"), w->currentCollection().id());
1154 grp.writeEntry(QStringLiteral("HeaderState"), w->view()->header()->saveState());
1155 elementTab++;
1156 }
1157 }
1158 conf.writeEntry(QStringLiteral("tabNumber"), elementTab);
1159 }
1160 conf.sync();
1161}
1162
1163void Pane::readConfig(bool restoreSession)
1164{
1165 if (MessageList::MessageListSettings::self()->config()->hasGroup(QStringLiteral("MessageListPane"))) {
1166 KConfigGroup conf(MessageList::MessageListSettings::self()->config(), QStringLiteral("MessageListPane"));
1167 const int numberOfTab = conf.readEntry(QStringLiteral("tabNumber"), 0);
1168 if (numberOfTab == 0) {
1169 createNewTab();
1170 } else {
1171 for (int i = 0; i < numberOfTab; ++i) {
1172 createNewTab();
1173 restoreHeaderSettings(i, restoreSession);
1174 }
1175 setCurrentIndex(conf.readEntry(QStringLiteral("currentIndex"), 0));
1176 }
1177 } else {
1178 createNewTab();
1179 restoreHeaderSettings(0, false);
1180 }
1181}
1182
1183void Pane::restoreHeaderSettings(int index, bool restoreSession)
1184{
1185 KConfigGroup grp(MessageList::MessageListSettings::self()->config(), QStringLiteral("MessageListTab%1").arg(index));
1186 if (grp.exists()) {
1187 auto w = qobject_cast<Widget *>(widget(index));
1188 if (w) {
1189 w->view()->header()->restoreState(grp.readEntry(QStringLiteral("HeaderState"), QByteArray()));
1190 }
1191 if (restoreSession) {
1192 const Akonadi::Collection::Id id = grp.readEntry(QStringLiteral("collectionId"), -1);
1193 if (id != -1) {
1194 auto saver = new Akonadi::ETMViewStateSaver;
1195 saver->setSelectionModel(d->mSelectionModel);
1196 saver->selectCollections(Akonadi::Collection::List() << Akonadi::Collection(id));
1197 saver->restoreCurrentItem(QString::fromLatin1("c%1").arg(id));
1198 saver->restoreState(grp);
1199 }
1200 }
1201 }
1202}
1203
1204bool Pane::searchEditHasFocus() const
1205{
1206 auto w = static_cast<Widget *>(currentWidget());
1207 if (w) {
1208 return w->searchEditHasFocus();
1209 }
1210 return false;
1211}
1212
1213void Pane::sortOrderMenuAboutToShow()
1214{
1215 auto menu = qobject_cast<QMenu *>(sender());
1216 if (!menu) {
1217 return;
1218 }
1219 const Widget *const w = static_cast<Widget *>(currentWidget());
1220 w->view()->sortOrderMenuAboutToShow(menu);
1221}
1222
1223void Pane::aggregationMenuAboutToShow()
1224{
1225 auto menu = qobject_cast<QMenu *>(sender());
1226 if (!menu) {
1227 return;
1228 }
1229 const Widget *const w = static_cast<Widget *>(currentWidget());
1230 w->view()->aggregationMenuAboutToShow(menu);
1231}
1232
1233void Pane::themeMenuAboutToShow()
1234{
1235 auto menu = qobject_cast<QMenu *>(sender());
1236 if (!menu) {
1237 return;
1238 }
1239 const Widget *const w = static_cast<Widget *>(currentWidget());
1240 w->view()->themeMenuAboutToShow(menu);
1241}
1242
1243void Pane::populateStatusFilterCombo()
1244{
1245 for (int i = 0; i < count(); ++i) {
1246 auto w = qobject_cast<Widget *>(widget(i));
1247 if (w) {
1249 }
1250 }
1251}
1252
1253#include "moc_pane.cpp"
QAction * addAction(const QString &name, const QObject *receiver=nullptr, const char *member=nullptr)
static void setDefaultShortcut(QAction *action, const QKeySequence &shortcut)
virtual KActionCollection * actionCollection() const
View * view() const
Returns the View attached to this Widget.
void populateStatusFilterCombo()
This is called to setup the status filter's QComboBox.
void statusMessage(const QString &message)
Notify the outside when updating the status bar with a message could be useful.
This is the main MessageList panel for Akonadi applications.
Definition pane.h:46
Pane(bool restoreSession, QAbstractItemModel *model, QItemSelectionModel *selectionModel, QWidget *parent=nullptr)
Create a Pane wrapping the specified model and selection.
Definition pane.cpp:94
void messageActivated(const Akonadi::Item &item)
Emitted when a message is doubleclicked or activated by other input means.
QString currentFilterSearchString() const
Returns the search term in the current quicksearch field.
Definition pane.cpp:1016
bool getSelectionStats(Akonadi::Item::List &selectedItems, Akonadi::Item::List &selectedVisibleItems, bool *allSelectedBelongToSameThread, bool includeCollapsedChildren=true) const
Fills the lists of the selected message serial numbers and of the selected+visible ones.
Definition pane.cpp:1043
bool selectLastMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem)
Selects the last message item in the view that matches the specified Core::MessageTypeFilter.
Definition pane.cpp:405
QItemSelectionModel * currentItemSelectionModel()
Returns the QItemSelectionModel for the currently displayed tab.
Definition pane.cpp:1090
bool focusNextMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop)
Focuses the next message item in the view without actually selecting it.
Definition pane.cpp:347
bool selectPreviousMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop)
Selects the previous message item in the view.
Definition pane.cpp:329
Akonadi::Item::List itemListFromPersistentSet(MessageList::Core::MessageItemSetReference ref)
Return Akonadi::Item from messageItemReference.
Definition pane.cpp:973
MessageList::Core::MessageItemSetReference selectionAsPersistentSet(bool includeCollapsedChildren=true) const
Return a persistent set from current selection.
Definition pane.cpp:1056
QList< qlonglong > selectionAsMessageItemListId(bool includeCollapsedChildren=true) const
Returns the currently selected Items id(bound to current StorageModel).
Definition pane.cpp:955
void focusQuickSearch(const QString &selectedText=QString())
Sets the focus on the quick search line of the currently active tab.
Definition pane.cpp:472
void messageSelected(const Akonadi::Item &item)
Emitted when a message is selected (that is, single clicked and thus made current in the view) Note t...
void setCurrentThreadExpanded(bool expand)
If expand is true then it expands the current thread, otherwise collapses it.
Definition pane.cpp:433
QItemSelectionModel * createNewTab()
Add a new tab to the Pane and select it.
Definition pane.cpp:807
bool selectionEmpty() const
Fast function that determines if the selection is empty.
Definition pane.cpp:1034
void deletePersistentSet(MessageList::Core::MessageItemSetReference ref)
Deletes the persistent set pointed by the specified reference.
Definition pane.cpp:982
QList< KMime::Message::Ptr > selectionAsMessageList(bool includeCollapsedChildren=true) const
Returns the currently selected KMime::Message::Ptr (bound to current StorageModel).
Definition pane.cpp:928
void reloadGlobalConfiguration()
Reloads global configuration and eventually reloads all the views.
Definition pane.cpp:1085
void markMessageItemsAsAboutToBeRemoved(MessageList::Core::MessageItemSetReference ref, bool bMark)
If bMark is true this function marks the messages as "about to be removed" so they appear dimmer and ...
Definition pane.cpp:990
void selectAll()
Selects all the items in the current folder.
Definition pane.cpp:420
void setCurrentFolder(const Akonadi::Collection &fld, const QModelIndex &etmIndex, bool preferEmptyTab=false, MessageList::Core::PreSelectionMode preSelectionMode=MessageList::Core::PreSelectLastSelected, const QString &overrideLabel=QString())
Sets the current folder to be displayed by this Pane.
Definition pane.cpp:772
MessageList::Core::MessageItemSetReference currentThreadAsPersistentSet() const
Return a persistent set from current thread.
Definition pane.cpp:1065
void setAllThreadsExpanded(bool expand)
If expand is true then it expands all the threads, otherwise collapses them.
Definition pane.cpp:446
void selectionChanged()
Emitted when the selection in the view changes.
bool isThreaded() const
Returns true if the current Aggregation is threaded, false otherwise (or if there is no current Aggre...
Definition pane.cpp:1025
Akonadi::Item currentItem() const
Returns the current message for the list as Akonadi::Item.
Definition pane.cpp:906
bool selectNextMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop)
Selects the next message item in the view.
Definition pane.cpp:311
void statusMessage(const QString &message)
Notify the outside when updating the status bar with a message could be useful.
void setXmlGuiClient(KXMLGUIClient *xmlGuiClient)
Sets the XML GUI client which the pane is used in.
Definition pane.cpp:196
bool focusPreviousMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop)
Focuses the previous message item in the view without actually selecting it.
Definition pane.cpp:362
void focusView()
Sets the focus on the view of the currently active tab.
Definition pane.cpp:1074
void selectFocusedMessageItem(bool centerItem)
Selects the currently focused message item.
Definition pane.cpp:377
KMime::Message::Ptr currentMessage() const
Returns the current message for the list as KMime::Message::Ptr.
Definition pane.cpp:917
Akonadi::Item::List currentThreadAsMessageList() const
Returns the Akonadi::Item bound to the current StorageModel that are part of the current thread.
Definition pane.cpp:964
void setAllGroupsExpanded(bool expand)
If expand is true then it expands all the groups (only the toplevel group item: inner threads are NOT...
Definition pane.cpp:459
bool selectFirstMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem)
Selects the first message item in the view that matches the specified Core::MessageTypeFilter.
Definition pane.cpp:390
void messageStatusChangeRequest(const Akonadi::Item &item, const Akonadi::MessageStatus &set, const Akonadi::MessageStatus &clear)
Emitted when a message wants its status to be changed.
QList< Akonadi::MessageStatus > currentFilterStatus() const
Returns the Akonadi::MessageStatus in the current quicksearch field.
Definition pane.cpp:998
Akonadi::Item::List selectionAsMessageItemList(bool includeCollapsedChildren=true) const
Returns the currently selected Items (bound to current StorageModel).
Definition pane.cpp:937
The Akonadi specific implementation of the Core::StorageModel.
The Akonadi specific implementation of the Core::Widget.
Definition widget.h:33
void messageSelected(const Akonadi::Item &item)
Emitted when a message is selected (that is, single clicked and thus made current in the view) Note t...
void messageStatusChangeRequest(const Akonadi::Item &item, const Akonadi::MessageStatus &set, const Akonadi::MessageStatus &clear)
Emitted when a message wants its status to be changed.
void selectionChanged()
Emitted when the selection in the view changes.
void messageActivated(const Akonadi::Item &item)
Emitted when a message is doubleclicked or activated by other input means.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QString label(StandardShortcut id)
const QList< QKeySequence > & tabPrev()
const QList< QKeySequence > & tabNext()
ExistingSelectionBehaviour
This enum is used in the view message selection functions (for instance View::selectNextMessage())
PreSelectionMode
Pre-selection is the action of automatically selecting a message just after the folder has finished l...
MessageTypeFilter
This enum is used in the view message selection functions (for instance View::nextMessageItem()).
void clicked(bool checked)
void triggered(bool checked)
MouseButtonPress
bool isRightToLeft()
QIcon fromTheme(const QString &name)
T & first()
Q_EMITQ_EMIT
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
T qobject_cast(QObject *object)
QObject * sender() const const
QString asprintf(const char *cformat,...)
void chop(qsizetype n)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QStringList filter(QStringView str, Qt::CaseSensitivity cs) const const
QStringView right(qsizetype length) const const
int toInt(bool *ok, int base) const const
CustomContextMenu
TopLeftCorner
DisplayRole
MiddleButton
int addTab(QWidget *page, const QIcon &icon, const QString &label)
void currentChanged(int index)
QWidget * currentWidget() const const
void setDocumentMode(bool set)
virtual bool event(QEvent *ev) override
int indexOf(const QWidget *w) const const
void setMovable(bool movable)
void setCornerWidget(QWidget *widget, Qt::Corner corner)
void setCurrentWidget(QWidget *widget)
void setTabIcon(int index, const QIcon &icon)
void setTabText(int index, const QString &label)
QTabBar * tabBar() const const
void tabCloseRequested(int index)
void setTabsClosable(bool closeable)
QWidget * widget(int index) const const
void setContextMenuPolicy(Qt::ContextMenuPolicy policy)
void customContextMenuRequested(const QPoint &pos)
void setEnabled(bool)
void setFocus()
void show()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.