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 d->folderTreeView->expandAll();
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 d->folderTreeView->scrollTo(firstMatch);
325 }
326}
327
328void FolderTreeWidget::clearFilter()
329{
330 d->filter.clear();
331 applyFilter(d->filter);
332 const QModelIndexList lst = d->folderTreeView->selectionModel()->selectedIndexes();
333 if (!lst.isEmpty()) {
334 d->folderTreeView->scrollTo(lst.first());
335 }
336}
337
338void FolderTreeWidget::slotManualSortingChanged(bool active)
339{
340 d->entityOrderProxy->setManualSortingActive(active);
341 d->folderTreeView->setManualSortingActive(active);
342}
343
344bool FolderTreeWidget::eventFilter(QObject *o, QEvent *e)
345{
346 Q_UNUSED(o)
347 if (d->dontKeyFilter) {
348 return false;
349 }
350
351 if (e->type() == QEvent::KeyPress) {
352 const QKeyEvent *const ke = static_cast<QKeyEvent *>(e);
353 switch (ke->key()) {
354 case Qt::Key_Backspace: {
355 const int filterLength(d->filter.length());
356 if (filterLength > 0) {
357 d->filter.truncate(filterLength - 1);
358 applyFilter(d->filter);
359 }
360 return false;
361 }
362 case Qt::Key_Delete:
363 d->filter.clear();
364 applyFilter(d->filter);
365 return false;
366 default: {
367 const QString s = ke->text();
368 if (!s.isEmpty() && s.at(0).isPrint()) {
369 d->filter += s;
370 applyFilter(d->filter);
371 return false;
372 }
373 break;
374 }
375 }
376 } else if (e->type() == QEvent::InputMethod) {
377 const QInputMethodEvent *const ime = static_cast<QInputMethodEvent *>(e);
378 d->filter += ime->commitString();
379 applyFilter(d->filter);
380 return false;
381 }
382 return false;
383}
384
385bool FolderTreeWidget::event(QEvent *e)
386{
388 slotGeneralPaletteChanged();
389 }
390 return QWidget::event(e);
391}
392
393void FolderTreeWidget::setAccountActivities(Akonadi::AccountActivitiesAbstract *accountActivities)
394{
395 d->readableproxy->setAccountActivities(accountActivities);
396}
397
398#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 Mon Nov 18 2024 12:09:01 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.