Warning, file /pim/mailcommon/src/folder/foldertreewidget.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 0003 SPDX-FileCopyrightText: 2009-2024 Laurent Montel <montel@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "foldertreewidget.h" 0009 #include "entitycollectionorderproxymodel.h" 0010 #include "foldertreeview.h" 0011 #include "hierarchicalfoldermatcher_p.h" 0012 #include "kernel/mailkernel.h" 0013 0014 #include <PimCommon/PimUtil> 0015 #include <PimCommonAkonadi/ImapAclAttribute> 0016 0017 #include <Akonadi/AttributeFactory> 0018 #include <Akonadi/ChangeRecorder> 0019 #include <Akonadi/EntityMimeTypeFilterModel> 0020 #include <Akonadi/EntityTreeModel> 0021 #include <Akonadi/ItemFetchScope> 0022 #include <Akonadi/StatisticsProxyModel> 0023 0024 #include <Akonadi/ETMViewStateSaver> 0025 #include <Akonadi/EntityTreeView> 0026 0027 #include <KMime/Message> 0028 0029 #include <MessageCore/MessageCoreSettings> 0030 0031 #include <KLocalizedString> 0032 0033 #include <QApplication> 0034 #include <QFontDatabase> 0035 #include <QHeaderView> 0036 #include <QKeyEvent> 0037 #include <QLabel> 0038 #include <QLineEdit> 0039 #include <QPointer> 0040 #include <QVBoxLayout> 0041 0042 using namespace MailCommon; 0043 0044 class Q_DECL_HIDDEN MailCommon::FolderTreeWidget::FolderTreeWidgetPrivate 0045 { 0046 public: 0047 QString filter; 0048 QString oldFilterStr; 0049 Akonadi::StatisticsProxyModel *filterModel = nullptr; 0050 FolderTreeView *folderTreeView = nullptr; 0051 FolderTreeWidgetProxyModel *readableproxy = nullptr; 0052 EntityCollectionOrderProxyModel *entityOrderProxy = nullptr; 0053 QLineEdit *filterFolderLineEdit = nullptr; 0054 QPointer<Akonadi::ETMViewStateSaver> saver; 0055 QStringList expandedItems; 0056 QString currentItem; 0057 QLabel *label = nullptr; 0058 bool dontKeyFilter = false; 0059 }; 0060 0061 FolderTreeWidget::FolderTreeWidget(QWidget *parent, 0062 KXMLGUIClient *xmlGuiClient, 0063 FolderTreeWidget::TreeViewOptions options, 0064 FolderTreeWidgetProxyModel::FolderTreeWidgetProxyModelOptions optReadableProxy) 0065 : QWidget(parent) 0066 , d(new FolderTreeWidgetPrivate()) 0067 { 0068 Akonadi::AttributeFactory::registerAttribute<PimCommon::ImapAclAttribute>(); 0069 0070 d->folderTreeView = new FolderTreeView(xmlGuiClient, this, options & ShowUnreadCount); 0071 d->folderTreeView->showStatisticAnimation(options & ShowCollectionStatisticAnimation); 0072 0073 connect(d->folderTreeView, &FolderTreeView::manualSortingChanged, this, &FolderTreeWidget::slotManualSortingChanged); 0074 0075 auto lay = new QVBoxLayout(this); 0076 lay->setContentsMargins({}); 0077 0078 d->label = new QLabel(i18n("You can start typing to filter the list of folders."), this); 0079 lay->addWidget(d->label); 0080 0081 d->filterFolderLineEdit = new QLineEdit(this); 0082 0083 d->filterFolderLineEdit->setClearButtonEnabled(true); 0084 d->filterFolderLineEdit->setPlaceholderText(i18nc("@info Displayed grayed-out inside the textbox, verb to search", "Search")); 0085 lay->addWidget(d->filterFolderLineEdit); 0086 0087 if (!(options & HideStatistics)) { 0088 d->filterModel = new Akonadi::StatisticsProxyModel(this); 0089 d->filterModel->setSourceModel(KernelIf->collectionModel()); 0090 } 0091 if (options & HideHeaderViewMenu) { 0092 d->folderTreeView->header()->setContextMenuPolicy(Qt::NoContextMenu); 0093 } 0094 0095 d->readableproxy = new FolderTreeWidgetProxyModel(this, optReadableProxy); 0096 d->readableproxy->setSourceModel((options & HideStatistics) ? static_cast<QAbstractItemModel *>(KernelIf->collectionModel()) 0097 : static_cast<QAbstractItemModel *>(d->filterModel)); 0098 d->readableproxy->addContentMimeTypeInclusionFilter(KMime::Message::mimeType()); 0099 0100 connect(d->folderTreeView, &FolderTreeView::changeTooltipsPolicy, this, &FolderTreeWidget::slotChangeTooltipsPolicy); 0101 0102 d->folderTreeView->setSelectionMode(QAbstractItemView::SingleSelection); 0103 d->folderTreeView->setEditTriggers(QAbstractItemView::NoEditTriggers); 0104 d->folderTreeView->installEventFilter(this); 0105 0106 // Order proxy 0107 d->entityOrderProxy = new EntityCollectionOrderProxyModel(this); 0108 d->entityOrderProxy->setSourceModel(d->readableproxy); 0109 d->entityOrderProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); 0110 KConfigGroup grp(KernelIf->config(), QStringLiteral("CollectionTreeOrder")); 0111 d->entityOrderProxy->setOrderConfig(grp); 0112 d->folderTreeView->setModel(d->entityOrderProxy); 0113 0114 if (options & UseDistinctSelectionModel) { 0115 d->folderTreeView->setSelectionModel(new QItemSelectionModel(d->entityOrderProxy, this)); 0116 } 0117 0118 lay->addWidget(d->folderTreeView); 0119 0120 d->dontKeyFilter = (options & DontKeyFilter); 0121 0122 if ((options & UseLineEditForFiltering)) { 0123 connect(d->filterFolderLineEdit, &QLineEdit::textChanged, this, &FolderTreeWidget::slotFilterFixedString); 0124 d->label->hide(); 0125 } else { 0126 d->filterFolderLineEdit->hide(); 0127 setAttribute(Qt::WA_InputMethodEnabled); 0128 } 0129 } 0130 0131 FolderTreeWidget::~FolderTreeWidget() = default; 0132 0133 void FolderTreeWidget::slotFilterFixedString(const QString &text) 0134 { 0135 delete d->saver; 0136 if (d->oldFilterStr.isEmpty()) { 0137 // Save it. 0138 Akonadi::ETMViewStateSaver saver; 0139 saver.setView(folderTreeView()); 0140 d->expandedItems = saver.expansionKeys(); 0141 d->currentItem = saver.currentIndexKey(); 0142 } else if (text.isEmpty()) { 0143 d->saver = new Akonadi::ETMViewStateSaver; 0144 d->saver->setView(folderTreeView()); 0145 QString currentIndex = d->saver->currentIndexKey(); 0146 if (d->saver->selectionKeys().isEmpty()) { 0147 currentIndex = d->currentItem; 0148 } else if (!currentIndex.isEmpty()) { 0149 d->expandedItems << currentIndex; 0150 } 0151 d->saver->restoreExpanded(d->expandedItems); 0152 d->saver->restoreCurrentItem(currentIndex); 0153 } else { 0154 d->folderTreeView->expandAll(); 0155 } 0156 d->oldFilterStr = text; 0157 d->entityOrderProxy->setFilterWildcard(text); 0158 } 0159 0160 void FolderTreeWidget::disableContextMenuAndExtraColumn() 0161 { 0162 d->folderTreeView->disableContextMenuAndExtraColumn(); 0163 } 0164 0165 void FolderTreeWidget::selectCollectionFolder(const Akonadi::Collection &collection, bool expand) 0166 { 0167 const QModelIndex index = Akonadi::EntityTreeModel::modelIndexForCollection(d->folderTreeView->model(), collection); 0168 0169 d->folderTreeView->setCurrentIndex(index); 0170 if (expand) { 0171 d->folderTreeView->setExpanded(index, true); 0172 } 0173 d->folderTreeView->scrollTo(index); 0174 } 0175 0176 void FolderTreeWidget::setSelectionMode(QAbstractItemView::SelectionMode mode) 0177 { 0178 d->folderTreeView->setSelectionMode(mode); 0179 } 0180 0181 QAbstractItemView::SelectionMode FolderTreeWidget::selectionMode() const 0182 { 0183 return d->folderTreeView->selectionMode(); 0184 } 0185 0186 QItemSelectionModel *FolderTreeWidget::selectionModel() const 0187 { 0188 return d->folderTreeView->selectionModel(); 0189 } 0190 0191 QModelIndex FolderTreeWidget::currentIndex() const 0192 { 0193 return d->folderTreeView->currentIndex(); 0194 } 0195 0196 Akonadi::Collection FolderTreeWidget::selectedCollection() const 0197 { 0198 if (d->folderTreeView->selectionMode() == QAbstractItemView::SingleSelection) { 0199 Akonadi::Collection::List lstCollection = selectedCollections(); 0200 if (lstCollection.isEmpty()) { 0201 return {}; 0202 } else { 0203 return lstCollection.at(0); 0204 } 0205 } 0206 0207 return {}; 0208 } 0209 0210 Akonadi::Collection::List FolderTreeWidget::selectedCollections() const 0211 { 0212 Akonadi::Collection::List collections; 0213 const QItemSelectionModel *selectionModel = d->folderTreeView->selectionModel(); 0214 const QModelIndexList selectedIndexes = selectionModel->selectedIndexes(); 0215 for (const QModelIndex &index : selectedIndexes) { 0216 if (index.isValid()) { 0217 const auto collection = index.model()->data(index, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0218 if (collection.isValid()) { 0219 collections.append(collection); 0220 } 0221 } 0222 } 0223 0224 return collections; 0225 } 0226 0227 FolderTreeView *FolderTreeWidget::folderTreeView() const 0228 { 0229 return d->folderTreeView; 0230 } 0231 0232 void FolderTreeWidget::slotGeneralFontChanged() 0233 { 0234 // Custom/System font support 0235 if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) { 0236 setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); 0237 } 0238 } 0239 0240 void FolderTreeWidget::slotGeneralPaletteChanged() 0241 { 0242 d->readableproxy->updatePalette(); 0243 d->folderTreeView->updatePalette(); 0244 } 0245 0246 void FolderTreeWidget::readConfig() 0247 { 0248 setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); 0249 0250 d->folderTreeView->readConfig(); 0251 d->folderTreeView->setDropActionMenuEnabled(SettingsIf->showPopupAfterDnD()); 0252 d->readableproxy->setWarningThreshold(SettingsIf->closeToQuotaThreshold()); 0253 d->readableproxy->readConfig(); 0254 0255 KConfigGroup readerConfig(KernelIf->config(), QStringLiteral("AccountOrder")); 0256 QStringList listOrder; 0257 if (readerConfig.readEntry("EnableAccountOrder", true)) { 0258 listOrder = readerConfig.readEntry("order", QStringList()); 0259 } 0260 d->entityOrderProxy->setTopLevelOrder(listOrder); 0261 } 0262 0263 void FolderTreeWidget::restoreHeaderState(const QByteArray &data) 0264 { 0265 d->folderTreeView->restoreHeaderState(data); 0266 } 0267 0268 void FolderTreeWidget::slotChangeTooltipsPolicy(FolderTreeWidget::ToolTipDisplayPolicy policy) 0269 { 0270 changeToolTipsPolicyConfig(policy); 0271 } 0272 0273 void FolderTreeWidget::changeToolTipsPolicyConfig(ToolTipDisplayPolicy policy) 0274 { 0275 switch (policy) { 0276 case DisplayAlways: 0277 case DisplayWhenTextElided: // Need to implement in the future 0278 if (d->filterModel) { 0279 d->filterModel->setToolTipEnabled(true); 0280 } 0281 break; 0282 case DisplayNever: 0283 if (d->filterModel) { 0284 d->filterModel->setToolTipEnabled(false); 0285 } 0286 } 0287 d->folderTreeView->setTooltipsPolicy(policy); 0288 } 0289 0290 Akonadi::StatisticsProxyModel *FolderTreeWidget::statisticsProxyModel() const 0291 { 0292 return d->filterModel; 0293 } 0294 0295 FolderTreeWidgetProxyModel *FolderTreeWidget::folderTreeWidgetProxyModel() const 0296 { 0297 return d->readableproxy; 0298 } 0299 0300 EntityCollectionOrderProxyModel *FolderTreeWidget::entityOrderProxy() const 0301 { 0302 return d->entityOrderProxy; 0303 } 0304 0305 QLineEdit *FolderTreeWidget::filterFolderLineEdit() const 0306 { 0307 return d->filterFolderLineEdit; 0308 } 0309 0310 void FolderTreeWidget::applyFilter(const QString &filter) 0311 { 0312 d->label->setText(filter.isEmpty() ? i18n("You can start typing to filter the list of folders.") : i18n("Path: (%1)", filter)); 0313 0314 HierarchicalFolderMatcher matcher; 0315 matcher.setFilter(filter, d->entityOrderProxy->filterCaseSensitivity()); 0316 d->entityOrderProxy->setFolderMatcher(matcher); 0317 d->folderTreeView->expandAll(); 0318 const QAbstractItemModel *const model = d->folderTreeView->model(); 0319 const QModelIndex current = d->folderTreeView->currentIndex(); 0320 const QModelIndex start = current.isValid() ? current : model->index(0, 0); 0321 const QModelIndex firstMatch = matcher.findFirstMatch(model, start); 0322 if (firstMatch.isValid()) { 0323 d->folderTreeView->setCurrentIndex(firstMatch); 0324 d->folderTreeView->scrollTo(firstMatch); 0325 } 0326 } 0327 0328 void FolderTreeWidget::clearFilter() 0329 { 0330 d->filter.clear(); 0331 applyFilter(d->filter); 0332 const QModelIndexList lst = d->folderTreeView->selectionModel()->selectedIndexes(); 0333 if (!lst.isEmpty()) { 0334 d->folderTreeView->scrollTo(lst.first()); 0335 } 0336 } 0337 0338 void FolderTreeWidget::slotManualSortingChanged(bool active) 0339 { 0340 d->entityOrderProxy->setManualSortingActive(active); 0341 d->folderTreeView->setManualSortingActive(active); 0342 } 0343 0344 bool FolderTreeWidget::eventFilter(QObject *o, QEvent *e) 0345 { 0346 Q_UNUSED(o) 0347 if (d->dontKeyFilter) { 0348 return false; 0349 } 0350 0351 if (e->type() == QEvent::KeyPress) { 0352 const QKeyEvent *const ke = static_cast<QKeyEvent *>(e); 0353 switch (ke->key()) { 0354 case Qt::Key_Backspace: { 0355 const int filterLength(d->filter.length()); 0356 if (filterLength > 0) { 0357 d->filter.truncate(filterLength - 1); 0358 applyFilter(d->filter); 0359 } 0360 return false; 0361 } 0362 case Qt::Key_Delete: 0363 d->filter.clear(); 0364 applyFilter(d->filter); 0365 return false; 0366 default: { 0367 const QString s = ke->text(); 0368 if (!s.isEmpty() && s.at(0).isPrint()) { 0369 d->filter += s; 0370 applyFilter(d->filter); 0371 return false; 0372 } 0373 break; 0374 } 0375 } 0376 } else if (e->type() == QEvent::InputMethod) { 0377 const QInputMethodEvent *const ime = static_cast<QInputMethodEvent *>(e); 0378 d->filter += ime->commitString(); 0379 applyFilter(d->filter); 0380 return false; 0381 } 0382 return false; 0383 } 0384 0385 bool FolderTreeWidget::event(QEvent *e) 0386 { 0387 if (e->type() == QEvent::ApplicationPaletteChange) { 0388 slotGeneralPaletteChanged(); 0389 } 0390 return QWidget::event(e); 0391 } 0392 0393 #include "moc_foldertreewidget.cpp"