Mailcommon

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

KDE's Doxygen guidelines are available online.