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"