Mailcommon

foldertreewidget.cpp
1 /*
2 
3  SPDX-FileCopyrightText: 2009-2023 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 
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 
42 using namespace MailCommon;
43 
44 class Q_DECL_HIDDEN MailCommon::FolderTreeWidget::FolderTreeWidgetPrivate
45 {
46 public:
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 
61 FolderTreeWidget::FolderTreeWidget(QWidget *parent,
62  KXMLGUIClient *xmlGuiClient,
65  : QWidget(parent)
66  , d(new FolderTreeWidgetPrivate())
67 {
68  Akonadi::AttributeFactory::registerAttribute<PimCommon::ImapAclAttribute>();
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(i18n("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(), "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();
128  }
129 }
130 
131 FolderTreeWidget::~FolderTreeWidget() = default;
132 
133 void 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 
160 void FolderTreeWidget::disableContextMenuAndExtraColumn()
161 {
162  d->folderTreeView->disableContextMenuAndExtraColumn();
163 }
164 
165 void 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 
176 void FolderTreeWidget::setSelectionMode(QAbstractItemView::SelectionMode mode)
177 {
178  d->folderTreeView->setSelectionMode(mode);
179 }
180 
181 QAbstractItemView::SelectionMode FolderTreeWidget::selectionMode() const
182 {
183  return d->folderTreeView->selectionMode();
184 }
185 
186 QItemSelectionModel *FolderTreeWidget::selectionModel() const
187 {
188  return d->folderTreeView->selectionModel();
189 }
190 
191 QModelIndex FolderTreeWidget::currentIndex() const
192 {
193  return d->folderTreeView->currentIndex();
194 }
195 
196 Akonadi::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 
210 Akonadi::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 
227 FolderTreeView *FolderTreeWidget::folderTreeView() const
228 {
229  return d->folderTreeView;
230 }
231 
232 void FolderTreeWidget::slotGeneralFontChanged()
233 {
234  // Custom/System font support
235  if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) {
237  }
238 }
239 
240 void FolderTreeWidget::slotGeneralPaletteChanged()
241 {
242  d->readableproxy->updatePalette();
243  d->folderTreeView->updatePalette();
244 }
245 
246 void 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(), "AccountOrder");
256  QStringList listOrder;
257  if (readerConfig.readEntry("EnableAccountOrder", true)) {
258  listOrder = readerConfig.readEntry("order", QStringList());
259  }
260  d->entityOrderProxy->setTopLevelOrder(listOrder);
261 }
262 
263 void FolderTreeWidget::restoreHeaderState(const QByteArray &data)
264 {
265  d->folderTreeView->restoreHeaderState(data);
266 }
267 
268 void FolderTreeWidget::slotChangeTooltipsPolicy(FolderTreeWidget::ToolTipDisplayPolicy policy)
269 {
270  changeToolTipsPolicyConfig(policy);
271 }
272 
273 void 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 
290 Akonadi::StatisticsProxyModel *FolderTreeWidget::statisticsProxyModel() const
291 {
292  return d->filterModel;
293 }
294 
295 FolderTreeWidgetProxyModel *FolderTreeWidget::folderTreeWidgetProxyModel() const
296 {
297  return d->readableproxy;
298 }
299 
300 EntityCollectionOrderProxyModel *FolderTreeWidget::entityOrderProxy() const
301 {
302  return d->entityOrderProxy;
303 }
304 
305 QLineEdit *FolderTreeWidget::filterFolderLineEdit() const
306 {
307  return d->filterFolderLineEdit;
308 }
309 
310 void 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 
328 void 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 
338 void FolderTreeWidget::slotManualSortingChanged(bool active)
339 {
340  d->entityOrderProxy->setManualSortingActive(active);
341  d->folderTreeView->setManualSortingActive(active);
342 }
343 
344 bool 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 
385 bool FolderTreeWidget::event(QEvent *e)
386 {
388  slotGeneralPaletteChanged();
389  }
390  return QWidget::event(e);
391 }
392 
393 #include "moc_foldertreewidget.cpp"
void append(const T &value)
@ 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
virtual bool event(QEvent *event) override
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
Q_SCRIPTABLE Q_NOREPLY void start()
bool isPrint() const const
static QString mimeType()
QString i18n(const char *text, const TYPE &arg...)
The EntityCollectionOrderProxyModel class implements ordering of mail collections.
void textChanged(const QString &text)
bool isEmpty() const const
const T & at(int i) const const
The FolderTreeWidgetProxyModel class.
QFuture< void > filter(Sequence &sequence, KeepFunctor filterFunction)
bool isEmpty() const const
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 FMH::MODEL filterModel(const MODEL &model, const QVector< MODEL_KEY > &keys)
const QAbstractItemModel * model() const const
WA_InputMethodEnabled
The filter dialog.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Sep 27 2023 03:59:52 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.