Mailcommon

foldertreewidget.cpp
1/*
2
3 SPDX-FileCopyrightText: 2009-2024 Laurent Montel <montel@kde.org>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6*/
7
8#include "foldertreewidget.h"
9#include "entitycollectionorderproxymodel.h"
10#include "foldertreeview.h"
11#include "hierarchicalfoldermatcher_p.h"
12#include "kernel/mailkernel.h"
13
14#include <PimCommon/PimUtil>
15#include <PimCommonAkonadi/ImapAclAttribute>
16
17#include <Akonadi/AttributeFactory>
18#include <Akonadi/ChangeRecorder>
19#include <Akonadi/EntityMimeTypeFilterModel>
20#include <Akonadi/EntityTreeModel>
21#include <Akonadi/ItemFetchScope>
22#include <Akonadi/StatisticsProxyModel>
23
24#include <Akonadi/ETMViewStateSaver>
25#include <Akonadi/EntityTreeView>
26
27#include <KMime/Message>
28
29#include <MessageCore/MessageCoreSettings>
30
31#include <KLocalizedString>
32
33#include <QApplication>
34#include <QFontDatabase>
35#include <QHeaderView>
36#include <QKeyEvent>
37#include <QLabel>
38#include <QLineEdit>
39#include <QPointer>
40#include <QVBoxLayout>
41
42using namespace MailCommon;
43
44class Q_DECL_HIDDEN MailCommon::FolderTreeWidget::FolderTreeWidgetPrivate
45{
46public:
48 QString oldFilterStr;
50 FolderTreeView *folderTreeView = nullptr;
51 FolderTreeWidgetProxyModel *readableproxy = nullptr;
52 EntityCollectionOrderProxyModel *entityOrderProxy = nullptr;
53 QLineEdit *filterFolderLineEdit = nullptr;
55 QStringList expandedItems;
56 QString currentItem;
57 QLabel *label = nullptr;
58 bool dontKeyFilter = false;
59};
60
61FolderTreeWidget::FolderTreeWidget(QWidget *parent,
62 KXMLGUIClient *xmlGuiClient,
65 : QWidget(parent)
66 , d(new FolderTreeWidgetPrivate())
67{
69
70 d->folderTreeView = new FolderTreeView(xmlGuiClient, this, options & ShowUnreadCount);
71 d->folderTreeView->showStatisticAnimation(options & ShowCollectionStatisticAnimation);
72
73 connect(d->folderTreeView, &FolderTreeView::manualSortingChanged, this, &FolderTreeWidget::slotManualSortingChanged);
74
75 auto lay = new QVBoxLayout(this);
76 lay->setContentsMargins({});
77
78 d->label = new QLabel(i18nc("@label:textbox", "You can start typing to filter the list of folders."), this);
79 lay->addWidget(d->label);
80
81 d->filterFolderLineEdit = new QLineEdit(this);
82
83 d->filterFolderLineEdit->setClearButtonEnabled(true);
84 d->filterFolderLineEdit->setPlaceholderText(i18nc("@info Displayed grayed-out inside the textbox, verb to search", "Search"));
85 lay->addWidget(d->filterFolderLineEdit);
86
87 if (!(options & HideStatistics)) {
88 d->filterModel = new Akonadi::StatisticsProxyModel(this);
89 d->filterModel->setSourceModel(KernelIf->collectionModel());
90 }
91 if (options & HideHeaderViewMenu) {
92 d->folderTreeView->header()->setContextMenuPolicy(Qt::NoContextMenu);
93 }
94
95 d->readableproxy = new FolderTreeWidgetProxyModel(this, optReadableProxy);
96 d->readableproxy->setSourceModel((options & HideStatistics) ? static_cast<QAbstractItemModel *>(KernelIf->collectionModel())
97 : static_cast<QAbstractItemModel *>(d->filterModel));
98 d->readableproxy->addContentMimeTypeInclusionFilter(KMime::Message::mimeType());
99
100 connect(d->folderTreeView, &FolderTreeView::changeTooltipsPolicy, this, &FolderTreeWidget::slotChangeTooltipsPolicy);
101
102 d->folderTreeView->setSelectionMode(QAbstractItemView::SingleSelection);
103 d->folderTreeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
104 d->folderTreeView->installEventFilter(this);
105
106 // Order proxy
107 d->entityOrderProxy = new EntityCollectionOrderProxyModel(this);
108 d->entityOrderProxy->setSourceModel(d->readableproxy);
109 d->entityOrderProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
110 KConfigGroup grp(KernelIf->config(), QStringLiteral("CollectionTreeOrder"));
111 d->entityOrderProxy->setOrderConfig(grp);
112 d->folderTreeView->setModel(d->entityOrderProxy);
113
114 if (options & UseDistinctSelectionModel) {
115 d->folderTreeView->setSelectionModel(new QItemSelectionModel(d->entityOrderProxy, this));
116 }
117
118 lay->addWidget(d->folderTreeView);
119
120 d->dontKeyFilter = (options & DontKeyFilter);
121
122 if ((options & UseLineEditForFiltering)) {
123 connect(d->filterFolderLineEdit, &QLineEdit::textChanged, this, &FolderTreeWidget::slotFilterFixedString);
124 d->label->hide();
125 } else {
126 d->filterFolderLineEdit->hide();
127 setAttribute(Qt::WA_InputMethodEnabled);
128 }
129}
130
131FolderTreeWidget::~FolderTreeWidget() = default;
132
133void FolderTreeWidget::slotFilterFixedString(const QString &text)
134{
135 delete d->saver;
136 if (d->oldFilterStr.isEmpty()) {
137 // Save it.
138 Akonadi::ETMViewStateSaver saver;
139 saver.setView(folderTreeView());
140 d->expandedItems = saver.expansionKeys();
141 d->currentItem = saver.currentIndexKey();
142 } else if (text.isEmpty()) {
143 d->saver = new Akonadi::ETMViewStateSaver;
144 d->saver->setView(folderTreeView());
145 QString currentIndex = d->saver->currentIndexKey();
146 if (d->saver->selectionKeys().isEmpty()) {
147 currentIndex = d->currentItem;
148 } else if (!currentIndex.isEmpty()) {
149 d->expandedItems << currentIndex;
150 }
151 d->saver->restoreExpanded(d->expandedItems);
152 d->saver->restoreCurrentItem(currentIndex);
153 } else {
154 d->folderTreeView->expandAll();
155 }
156 d->oldFilterStr = text;
157 d->entityOrderProxy->setFilterWildcard(text);
158}
159
160void FolderTreeWidget::disableContextMenuAndExtraColumn()
161{
162 d->folderTreeView->disableContextMenuAndExtraColumn();
163}
164
165void FolderTreeWidget::selectCollectionFolder(const Akonadi::Collection &collection, bool expand)
166{
167 const QModelIndex index = Akonadi::EntityTreeModel::modelIndexForCollection(d->folderTreeView->model(), collection);
168
169 d->folderTreeView->setCurrentIndex(index);
170 if (expand) {
171 d->folderTreeView->setExpanded(index, true);
172 }
173 d->folderTreeView->scrollTo(index);
174}
175
176void FolderTreeWidget::setSelectionMode(QAbstractItemView::SelectionMode mode)
177{
178 d->folderTreeView->setSelectionMode(mode);
179}
180
181QAbstractItemView::SelectionMode FolderTreeWidget::selectionMode() const
182{
183 return d->folderTreeView->selectionMode();
184}
185
186QItemSelectionModel *FolderTreeWidget::selectionModel() const
187{
188 return d->folderTreeView->selectionModel();
189}
190
191QModelIndex FolderTreeWidget::currentIndex() const
192{
193 return d->folderTreeView->currentIndex();
194}
195
196Akonadi::Collection FolderTreeWidget::selectedCollection() const
197{
198 if (d->folderTreeView->selectionMode() == QAbstractItemView::SingleSelection) {
199 Akonadi::Collection::List lstCollection = selectedCollections();
200 if (lstCollection.isEmpty()) {
201 return {};
202 } else {
203 return lstCollection.at(0);
204 }
205 }
206
207 return {};
208}
209
210Akonadi::Collection::List FolderTreeWidget::selectedCollections() const
211{
212 Akonadi::Collection::List collections;
213 const QItemSelectionModel *selectionModel = d->folderTreeView->selectionModel();
214 const QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
215 for (const QModelIndex &index : selectedIndexes) {
216 if (index.isValid()) {
217 const auto collection = index.model()->data(index, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
218 if (collection.isValid()) {
219 collections.append(collection);
220 }
221 }
222 }
223
224 return collections;
225}
226
227FolderTreeView *FolderTreeWidget::folderTreeView() const
228{
229 return d->folderTreeView;
230}
231
232void FolderTreeWidget::slotGeneralFontChanged()
233{
234 // Custom/System font support
235 if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) {
237 }
238}
239
240void FolderTreeWidget::slotGeneralPaletteChanged()
241{
242 d->readableproxy->updatePalette();
243 d->folderTreeView->updatePalette();
244}
245
246void FolderTreeWidget::readConfig()
247{
249
250 d->folderTreeView->readConfig();
251 d->folderTreeView->setDropActionMenuEnabled(SettingsIf->showPopupAfterDnD());
252 d->readableproxy->setWarningThreshold(SettingsIf->closeToQuotaThreshold());
253 d->readableproxy->readConfig();
254
255 KConfigGroup readerConfig(KernelIf->config(), QStringLiteral("AccountOrder"));
256 QStringList listOrder;
257 if (readerConfig.readEntry("EnableAccountOrder", true)) {
258 listOrder = readerConfig.readEntry("order", QStringList());
259 }
260 d->entityOrderProxy->setTopLevelOrder(listOrder);
261}
262
263void FolderTreeWidget::restoreHeaderState(const QByteArray &data)
264{
265 d->folderTreeView->restoreHeaderState(data);
266}
267
268void FolderTreeWidget::slotChangeTooltipsPolicy(FolderTreeWidget::ToolTipDisplayPolicy policy)
269{
270 changeToolTipsPolicyConfig(policy);
271}
272
273void FolderTreeWidget::changeToolTipsPolicyConfig(ToolTipDisplayPolicy policy)
274{
275 switch (policy) {
276 case DisplayAlways:
277 case DisplayWhenTextElided: // Need to implement in the future
278 if (d->filterModel) {
279 d->filterModel->setToolTipEnabled(true);
280 }
281 break;
282 case DisplayNever:
283 if (d->filterModel) {
284 d->filterModel->setToolTipEnabled(false);
285 }
286 }
287 d->folderTreeView->setTooltipsPolicy(policy);
288}
289
290Akonadi::StatisticsProxyModel *FolderTreeWidget::statisticsProxyModel() const
291{
292 return d->filterModel;
293}
294
295FolderTreeWidgetProxyModel *FolderTreeWidget::folderTreeWidgetProxyModel() const
296{
297 return d->readableproxy;
298}
299
300EntityCollectionOrderProxyModel *FolderTreeWidget::entityOrderProxy() const
301{
302 return d->entityOrderProxy;
303}
304
305QLineEdit *FolderTreeWidget::filterFolderLineEdit() const
306{
307 return d->filterFolderLineEdit;
308}
309
310void FolderTreeWidget::applyFilter(const QString &filter)
311{
312 d->label->setText(filter.isEmpty() ? i18n("You can start typing to filter the list of folders.") : i18n("Path: (%1)", filter));
313
314 HierarchicalFolderMatcher matcher;
315 matcher.setFilter(filter, d->entityOrderProxy->filterCaseSensitivity());
316 d->entityOrderProxy->setFolderMatcher(matcher);
317
318 const QAbstractItemModel *const model = d->folderTreeView->model();
319 const QModelIndex current = d->folderTreeView->currentIndex();
320 const QModelIndex start = current.isValid() ? current : model->index(0, 0);
321 const QModelIndex firstMatch = matcher.findFirstMatch(model, start);
322 if (firstMatch.isValid()) {
323 d->folderTreeView->setCurrentIndex(firstMatch);
324 // This will expand to the selected item if necessary.
325 d->folderTreeView->scrollTo(firstMatch);
326 }
327}
328
329void FolderTreeWidget::clearFilter()
330{
331 d->filter.clear();
332 applyFilter(d->filter);
333 const QModelIndexList lst = d->folderTreeView->selectionModel()->selectedIndexes();
334 if (!lst.isEmpty()) {
335 d->folderTreeView->scrollTo(lst.first());
336 }
337}
338
339void FolderTreeWidget::slotManualSortingChanged(bool active)
340{
341 d->entityOrderProxy->setManualSortingActive(active);
342 d->folderTreeView->setManualSortingActive(active);
343}
344
345bool FolderTreeWidget::eventFilter(QObject *o, QEvent *e)
346{
347 Q_UNUSED(o)
348 if (d->dontKeyFilter) {
349 return false;
350 }
351
352 if (e->type() == QEvent::KeyPress) {
353 const QKeyEvent *const ke = static_cast<QKeyEvent *>(e);
354 switch (ke->key()) {
355 case Qt::Key_Backspace: {
356 const int filterLength(d->filter.length());
357 if (filterLength > 0) {
358 d->filter.truncate(filterLength - 1);
359 applyFilter(d->filter);
360 }
361 return false;
362 }
363 case Qt::Key_Delete:
364 d->filter.clear();
365 applyFilter(d->filter);
366 return false;
367 default: {
368 const QString s = ke->text();
369 if (!s.isEmpty() && s.at(0).isPrint()) {
370 d->filter += s;
371 applyFilter(d->filter);
372 return false;
373 }
374 break;
375 }
376 }
377 } else if (e->type() == QEvent::InputMethod) {
378 const QInputMethodEvent *const ime = static_cast<QInputMethodEvent *>(e);
379 d->filter += ime->commitString();
380 applyFilter(d->filter);
381 return false;
382 }
383 return false;
384}
385
386bool FolderTreeWidget::event(QEvent *e)
387{
389 slotGeneralPaletteChanged();
390 }
391 return QWidget::event(e);
392}
393
394void FolderTreeWidget::setAccountActivities(Akonadi::AccountActivitiesAbstract *accountActivities)
395{
396 d->readableproxy->setAccountActivities(accountActivities);
397}
398
399#include "moc_foldertreewidget.cpp"
static void registerAttribute()
bool isValid() const
static QModelIndex modelIndexForCollection(const QAbstractItemModel *model, const Collection &collection)
static QString mimeType()
The EntityCollectionOrderProxyModel class implements ordering of mail collections.
This is an enhanced EntityTreeView specially suited for the folders in KMail's main folder widget.
The FolderTreeWidgetProxyModel class.
ToolTipDisplayPolicy
The possible tooltip display policies.
@ DisplayNever
Nevery display tooltips.
@ DisplayWhenTextElided
Display the tooltip if the item text is actually elided.
@ DisplayAlways
Always display a tooltip when hovering over an item.
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
const FMH::MODEL filterModel(const MODEL &model, const QVector< MODEL_KEY > &keys)
QString label(StandardShortcut id)
The filter dialog.
virtual QVariant data(const QModelIndex &index, int role) const const=0
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
bool isPrint(char32_t ucs4)
Type type() const const
QFont systemFont(SystemFont type)
const QString & commitString() const const
int key() const const
QString text() const const
void textChanged(const QString &text)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool isEmpty() const const
bool isValid() const const
const QAbstractItemModel * model() const const
const QChar at(qsizetype position) const const
bool isEmpty() const const
CaseInsensitive
NoContextMenu
Key_Backspace
WA_InputMethodEnabled
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
T value() const const
virtual bool event(QEvent *event) override
void setFont(const QFont &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 13 2024 11:49:10 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.