File indexing completed on 2024-04-21 04:57:56

0001 /*
0002     SPDX-FileCopyrightText: 2000-2011 Dawit Alemayehu <adawit@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "dirfilterplugin.h"
0008 
0009 #include <QLabel>
0010 #include <QSpacerItem>
0011 #include <QToolButton>
0012 #include <QPushButton>
0013 #include <QHBoxLayout>
0014 #include <QBoxLayout>
0015 #include <QShowEvent>
0016 #include <QGlobalStatic>
0017 
0018 
0019 #include <KLocalizedString>
0020 #include <kfileitem.h>
0021 #include <klineedit.h>
0022 #include <kactionmenu.h>
0023 #include <kconfiggroup.h>
0024 #include <kpluginfactory.h>
0025 #include <kactioncollection.h>
0026 #include <KConfigGroup>
0027 #include <KConfig>
0028 #include "kf5compat.h" //For NavigationExtension
0029 
0030 
0031 Q_GLOBAL_STATIC(SessionManager, globalSessionManager)
0032 
0033 static void generateKey(const QUrl &url, QString *key)
0034 {
0035     if (url.isValid()) {
0036         *key = url.scheme();
0037         *key += QLatin1Char(':');
0038 
0039         if (!url.host().isEmpty()) {
0040             *key += url.host();
0041             *key += QLatin1Char(':');
0042         }
0043 
0044         if (!url.path().isEmpty()) {
0045             *key += url.path();
0046         }
0047     }
0048 }
0049 
0050 static void saveNameFilter(const QUrl &url, const QString &filter)
0051 {
0052     SessionManager::Filters f = globalSessionManager->restore(url);
0053     f.nameFilter = filter;
0054     globalSessionManager->save(url, f);
0055 }
0056 
0057 static void saveTypeFilters(const QUrl &url, const QStringList &filters)
0058 {
0059     SessionManager::Filters f = globalSessionManager->restore(url);
0060     f.typeFilters = filters;
0061     globalSessionManager->save(url, f);
0062 }
0063 
0064 SessionManager::SessionManager()
0065 {
0066     m_bSettingsLoaded = false;
0067     loadSettings();
0068 }
0069 
0070 SessionManager::~SessionManager()
0071 {
0072     saveSettings();
0073 }
0074 
0075 SessionManager::Filters SessionManager::restore(const QUrl &url)
0076 {
0077     QString key;
0078     generateKey(url, &key);
0079     return m_filters.value(key);
0080 }
0081 
0082 void SessionManager::save(const QUrl &url, const Filters &filters)
0083 {
0084     QString key;
0085     generateKey(url, &key);
0086     m_filters[key] = filters;
0087 }
0088 
0089 void SessionManager::saveSettings()
0090 {
0091     KConfig cfg(QStringLiteral("dirfilterrc"), KConfig::NoGlobals);
0092     KConfigGroup group = cfg.group("General");
0093 
0094     group.writeEntry("ShowCount", showCount);
0095     group.writeEntry("UseMultipleFilters", useMultipleFilters);
0096     cfg.sync();
0097 }
0098 
0099 void SessionManager::loadSettings()
0100 {
0101     if (m_bSettingsLoaded) {
0102         return;
0103     }
0104 
0105     KConfig cfg(QStringLiteral("dirfilterrc"), KConfig::NoGlobals);
0106     KConfigGroup group = cfg.group("General");
0107 
0108     showCount = group.readEntry("ShowCount", false);
0109     useMultipleFilters = group.readEntry("UseMultipleFilters", true);
0110     m_bSettingsLoaded = true;
0111 }
0112 
0113 FilterBar::FilterBar(QWidget *parent)
0114     : QWidget(parent)
0115 {
0116     // Create close button
0117     QToolButton *closeButton = new QToolButton(this);
0118     closeButton->setAutoRaise(true);
0119     closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
0120     closeButton->setToolTip(i18nc("@info:tooltip", "Hide Filter Bar"));
0121     connect(closeButton, SIGNAL(clicked()), this, SIGNAL(closeRequest()));
0122 
0123     // Create label
0124     QLabel *filterLabel = new QLabel(i18nc("@label:textbox", "F&ilter:"), this);
0125 
0126     // Create filter editor
0127     m_filterInput = new KLineEdit(this);
0128     m_filterInput->setLayoutDirection(Qt::LeftToRight);
0129     m_filterInput->setClearButtonEnabled(true);
0130     connect(m_filterInput, SIGNAL(textChanged(QString)),
0131             this, SIGNAL(filterChanged(QString)));
0132     setFocusProxy(m_filterInput);
0133 
0134     m_typeFilterButton = new QPushButton(this);
0135     m_typeFilterButton->setIcon(QIcon::fromTheme(QStringLiteral("view-filter")));
0136     m_typeFilterButton->setText(i18nc("@label:button", "Filter by t&ype"));
0137     m_typeFilterButton->setToolTip(i18nc("@info:tooltip", "Filter items by file type."));
0138 
0139     // Apply layout
0140     QHBoxLayout *layout = new QHBoxLayout(this);
0141     layout->setContentsMargins(0, 0, 0, 0);
0142     layout->addWidget(closeButton);
0143     layout->addWidget(filterLabel);
0144     layout->addWidget(m_filterInput);
0145     layout->addWidget(m_typeFilterButton);
0146     layout->addItem(new QSpacerItem(20, 20, QSizePolicy::MinimumExpanding, QSizePolicy::Minimum));
0147 
0148     filterLabel->setBuddy(m_filterInput);
0149 }
0150 
0151 FilterBar::~FilterBar()
0152 {
0153 }
0154 
0155 void FilterBar::selectAll()
0156 {
0157     m_filterInput->selectAll();
0158 }
0159 
0160 bool FilterBar::typeFilterMenuEnabled() const
0161 {
0162     return m_typeFilterButton->isEnabled();
0163 }
0164 
0165 void FilterBar::setEnableTypeFilterMenu(bool state)
0166 {
0167     m_typeFilterButton->setEnabled(state);
0168 }
0169 
0170 void FilterBar::setTypeFilterMenu(QMenu *menu)
0171 {
0172     m_typeFilterButton->setMenu(menu);
0173 }
0174 
0175 QMenu *FilterBar::typeFilterMenu()
0176 {
0177     return m_typeFilterButton->menu();
0178 }
0179 
0180 void FilterBar::setNameFilter(const QString &text)
0181 {
0182     m_filterInput->setText(text);
0183 }
0184 
0185 void FilterBar::clear()
0186 {
0187     m_filterInput->clear();
0188 }
0189 
0190 void FilterBar::showEvent(QShowEvent *event)
0191 {
0192     if (!event->spontaneous()) {
0193         m_filterInput->setFocus();
0194     }
0195 }
0196 
0197 void FilterBar::keyReleaseEvent(QKeyEvent *event)
0198 {
0199     QWidget::keyReleaseEvent(event);
0200     if (event->key() == Qt::Key_Escape) {
0201         if (m_filterInput->text().isEmpty()) {
0202             emit closeRequest();
0203         } else {
0204             m_filterInput->clear();
0205         }
0206     }
0207 }
0208 
0209 DirFilterPlugin::DirFilterPlugin(QObject *parent, const QVariantList &)
0210     : KonqParts::Plugin(parent)
0211     , m_filterBar(nullptr)
0212     , m_focusWidget(nullptr)
0213 {
0214     m_part = qobject_cast<KParts::ReadOnlyPart *>(parent);
0215     if (m_part) {
0216         //Can't use modern connect syntax because aboutToOpenURL is specific to Dolphin part
0217         connect(m_part, SIGNAL(aboutToOpenURL()), this, SLOT(slotOpenURL()));
0218 #if QT_VERSION_MAJOR < 6
0219         connect(m_part, QOverload<>::of(&KParts::ReadOnlyPart::completed), this, &DirFilterPlugin::slotOpenURLCompleted);
0220 #else
0221         connect(m_part, &KParts::ReadOnlyPart::completed, this, &DirFilterPlugin::slotOpenURLCompleted);
0222 #endif
0223     }
0224 
0225     KParts::ListingNotificationExtension *notifyExt = KParts::ListingNotificationExtension::childObject(m_part);
0226     if (notifyExt && notifyExt->supportedNotificationEventTypes() != KParts::ListingNotificationExtension::None) {
0227         m_listingExt = KParts::ListingFilterExtension::childObject(m_part);
0228         connect(notifyExt, SIGNAL(listingEvent(KParts::ListingNotificationExtension::NotificationEventType,KFileItemList)),
0229                 this, SLOT(slotListingEvent(KParts::ListingNotificationExtension::NotificationEventType,KFileItemList)));
0230 
0231         QAction *action = actionCollection()->addAction(QStringLiteral("filterdir"), this, SLOT(slotShowFilterBar()));
0232         action->setText(i18nc("@action:inmenu Tools", "Show Filter Bar"));
0233         action->setIcon(QIcon::fromTheme(QStringLiteral("view-filter")));
0234         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_I));
0235     }
0236 }
0237 
0238 DirFilterPlugin::~DirFilterPlugin()
0239 {
0240 }
0241 
0242 void DirFilterPlugin::slotOpenURL()
0243 {
0244     if (m_part && !m_part->arguments().reload()) {
0245         m_pMimeInfo.clear();
0246         if (m_filterBar && m_filterBar->isVisible()) {
0247             m_filterBar->clear();
0248             m_filterBar->setEnableTypeFilterMenu(false);  // Will be enabled once loading has completed
0249         }
0250     }
0251 }
0252 
0253 void DirFilterPlugin::slotOpenURLCompleted()
0254 {
0255     if (m_listingExt && m_part && m_filterBar && m_filterBar->isVisible()) {
0256         setFilterBar();
0257     }
0258 }
0259 
0260 void DirFilterPlugin::slotShowPopup()
0261 {
0262     QMenu *filterMenu = (m_filterBar ? m_filterBar->typeFilterMenu() : nullptr);
0263     if (!filterMenu) {
0264         return;
0265     }
0266 
0267     filterMenu->clear();
0268 
0269     QString label;
0270     QStringList inodes;
0271     quint64 enableReset = 0;
0272     QMapIterator<QString, MimeInfo> it(m_pMimeInfo);
0273 
0274     while (it.hasNext()) {
0275         it.next();
0276 
0277         if (it.key().startsWith(QLatin1String("inode"))) {
0278             inodes << it.key();
0279             continue;
0280         }
0281 
0282         if (!globalSessionManager->showCount) {
0283             label = it.value().mimeComment;
0284         } else {
0285             label = it.value().mimeComment;
0286             label += QLatin1String("  (");
0287             label += QString::number(it.value().filenames.size());
0288             label += ')';
0289         }
0290 
0291         QAction *action = filterMenu->addAction(QIcon::fromTheme(it.value().iconName), label);
0292         action->setCheckable(true);
0293         if (it.value().useAsFilter) {
0294             action->setChecked(true);
0295             enableReset++;
0296         }
0297         action->setData(it.key());
0298         m_pMimeInfo[it.key()].action = action;
0299     }
0300 
0301     // Add all the items that have mime-type of "inode/*" here...
0302     if (!inodes.isEmpty()) {
0303         filterMenu->addSeparator();
0304 
0305         for (const QString &inode: inodes) {
0306             if (!globalSessionManager->showCount) {
0307                 label = m_pMimeInfo[inode].mimeComment;
0308             } else {
0309                 label = m_pMimeInfo[inode].mimeComment;
0310                 label += QLatin1String("  (");
0311                 label += QString::number(m_pMimeInfo[inode].filenames.size());
0312                 label += ')';
0313             }
0314 
0315             QAction *action = filterMenu->addAction(QIcon::fromTheme(m_pMimeInfo[inode].iconName), label);
0316             action->setCheckable(true);
0317             if (m_pMimeInfo[inode].useAsFilter) {
0318                 action->setChecked(true);
0319                 enableReset ++;
0320             }
0321             action->setData(inode);
0322             m_pMimeInfo[inode].action = action;
0323         }
0324     }
0325     filterMenu->addSeparator();
0326     QAction *action = filterMenu->addAction(i18n("Use Multiple Filters"),
0327                                             this, SLOT(slotMultipleFilters()));
0328     action->setEnabled(enableReset <= 1);
0329     action->setCheckable(true);
0330     action->setChecked(globalSessionManager->useMultipleFilters);
0331 
0332     action = filterMenu->addAction(i18n("Show Count"), this,
0333                                    SLOT(slotShowCount()));
0334     action->setCheckable(true);
0335     action->setChecked(globalSessionManager->showCount);
0336 
0337     action = filterMenu->addAction(i18n("Reset"), this,
0338                                    SLOT(slotReset()));
0339     action->setEnabled(enableReset);
0340 }
0341 
0342 void DirFilterPlugin::slotItemSelected(QAction *action)
0343 {
0344     if (!m_listingExt || !action || !m_part) {
0345         return;
0346     }
0347 
0348     MimeInfoMap::iterator it = m_pMimeInfo.find(action->data().toString());
0349     if (it != m_pMimeInfo.end()) {
0350         MimeInfo &mimeInfo = it.value();
0351         QStringList filters;
0352 
0353         if (mimeInfo.useAsFilter) {
0354             mimeInfo.useAsFilter = false;
0355             filters = m_listingExt->filter(KParts::ListingFilterExtension::MimeType).toStringList();
0356             if (filters.removeAll(it.key())) {
0357                 m_listingExt->setFilter(KParts::ListingFilterExtension::MimeType, filters);
0358             }
0359         } else {
0360             m_pMimeInfo[it.key()].useAsFilter = true;
0361 
0362             if (globalSessionManager->useMultipleFilters) {
0363                 filters = m_listingExt->filter(KParts::ListingFilterExtension::MimeType).toStringList();
0364                 filters << it.key();
0365             } else {
0366                 filters << it.key();
0367 
0368                 MimeInfoMap::iterator item = m_pMimeInfo.begin();
0369                 MimeInfoMap::iterator itemEnd = m_pMimeInfo.end();
0370                 while (item != itemEnd) {
0371                     if (item != it) {
0372                         item.value().useAsFilter = false;
0373                     }
0374                     item++;
0375                 }
0376             }
0377             m_listingExt->setFilter(KParts::ListingFilterExtension::MimeType, filters);
0378         }
0379 
0380         saveTypeFilters(m_part->url(), filters);
0381     }
0382 }
0383 
0384 void DirFilterPlugin::slotNameFilterChanged(const QString &filter)
0385 {
0386     if (!m_listingExt || !m_part) {
0387         return;
0388     }
0389 
0390     m_listingExt->setFilter(KParts::ListingFilterExtension::SubString, filter);
0391     saveNameFilter(m_part->url(), filter);
0392 }
0393 
0394 void DirFilterPlugin::slotCloseRequest()
0395 {
0396     if (m_filterBar) {
0397         m_filterBar->clear();
0398         m_filterBar->hide();
0399         if (m_focusWidget) {
0400             m_focusWidget->setFocus();
0401             m_focusWidget = nullptr;
0402         }
0403     }
0404 }
0405 
0406 void DirFilterPlugin::slotListingEvent(KParts::ListingNotificationExtension::NotificationEventType type, const KFileItemList &items)
0407 {
0408     if (!m_listingExt) {
0409         return;
0410     }
0411 
0412     switch (type) {
0413     case KParts::ListingNotificationExtension::ItemsAdded: {
0414         const QStringList filters = m_listingExt->filter(KParts::ListingFilterExtension::MimeType).toStringList();
0415         for (const KFileItem &item: items) {
0416             const QString mimeType(item.mimetype());
0417             if (m_pMimeInfo.contains(mimeType)) {
0418                 m_pMimeInfo[mimeType].filenames.insert(item.name());
0419             } else {
0420                 MimeInfo &mimeInfo = m_pMimeInfo[mimeType];
0421                 mimeInfo.useAsFilter = filters.contains(mimeType);
0422                 mimeInfo.mimeComment = item.mimeComment();
0423                 mimeInfo.iconName = item.iconName();
0424                 mimeInfo.filenames.insert(item.name());
0425             }
0426         }
0427         break;
0428     }
0429     case KParts::ListingNotificationExtension::ItemsDeleted:
0430         for (const KFileItem &item: items) {
0431             const QString mimeType(item.mimetype());
0432             MimeInfoMap::iterator it = m_pMimeInfo.find(mimeType);
0433             if (it != m_pMimeInfo.end()) {
0434                 MimeInfo &info = it.value();
0435 
0436                 if (info.filenames.size() > 1) {
0437                     info.filenames.remove(item.name());
0438                 } else {
0439                     if (info.useAsFilter) {
0440                         QStringList filters = m_listingExt->filter(KParts::ListingFilterExtension::MimeType).toStringList();
0441                         filters.removeAll(mimeType);
0442                         m_listingExt->setFilter(KParts::ListingFilterExtension::MimeType, filters);
0443                         saveTypeFilters(m_part->url(), filters);
0444                     }
0445                     m_pMimeInfo.erase(it);
0446                 }
0447             }
0448         }
0449         break;
0450     default:
0451         return;
0452     }
0453 
0454     // Enable/disable mime based filtering depending on whether the number
0455     // document types in the current directory.
0456     if (m_filterBar) {
0457         m_filterBar->setEnableTypeFilterMenu(m_pMimeInfo.count() > 1);
0458     }
0459 }
0460 
0461 void DirFilterPlugin::slotReset()
0462 {
0463     if (!m_part || !m_listingExt) {
0464         return;
0465     }
0466 
0467     MimeInfoMap::iterator itEnd = m_pMimeInfo.end();
0468     for (MimeInfoMap::iterator it = m_pMimeInfo.begin(); it != itEnd; ++it) {
0469         it.value().useAsFilter = false;
0470     }
0471 
0472     const QStringList filters;
0473     m_listingExt->setFilter(KParts::ListingFilterExtension::MimeType, filters);
0474     saveTypeFilters(m_part->url(), filters);
0475 }
0476 
0477 void DirFilterPlugin::slotShowCount()
0478 {
0479     globalSessionManager->showCount = !globalSessionManager->showCount;
0480 }
0481 
0482 void DirFilterPlugin::slotMultipleFilters()
0483 {
0484     globalSessionManager->useMultipleFilters = !globalSessionManager->useMultipleFilters;
0485 }
0486 
0487 void DirFilterPlugin::slotShowFilterBar()
0488 {
0489     QWidget *partWidget = (m_part ? m_part->widget() : nullptr);
0490 
0491     if (!m_filterBar && partWidget) {
0492         m_filterBar = new FilterBar(partWidget);
0493         m_filterBar->setTypeFilterMenu(new QMenu(m_filterBar));
0494         connect(m_filterBar->typeFilterMenu(), SIGNAL(aboutToShow()),
0495                 this, SLOT(slotShowPopup()));
0496         connect(m_filterBar->typeFilterMenu(), SIGNAL(triggered(QAction*)),
0497                 this, SLOT(slotItemSelected(QAction*)));
0498         connect(m_filterBar, SIGNAL(filterChanged(QString)),
0499                 this, SLOT(slotNameFilterChanged(QString)));
0500         connect(m_filterBar, SIGNAL(closeRequest()),
0501                 this, SLOT(slotCloseRequest()));
0502         QBoxLayout *layout = qobject_cast<QBoxLayout *>(partWidget->layout());
0503         if (layout) {
0504             layout->addWidget(m_filterBar);
0505         }
0506     }
0507 
0508     // Get the widget that currently has the focus so we can properly
0509     // restore it when the filter bar is closed.
0510     QWidget *window = (partWidget ? partWidget->window() : nullptr);
0511     m_focusWidget = (window ? window->focusWidget() : nullptr);
0512 
0513     if (m_filterBar) {
0514         setFilterBar();
0515         m_filterBar->show();
0516     }
0517 }
0518 
0519 void DirFilterPlugin::setFilterBar()
0520 {
0521     SessionManager::Filters savedFilters = globalSessionManager->restore(m_part->url());
0522 
0523     if (m_listingExt) {
0524         m_listingExt->setFilter(KParts::ListingFilterExtension::MimeType, savedFilters.typeFilters);
0525     }
0526 
0527     if (m_filterBar) {
0528         m_filterBar->setNameFilter(savedFilters.nameFilter);
0529         m_filterBar->setEnableTypeFilterMenu(m_pMimeInfo.count() > 1);
0530     }
0531 
0532     for (const QString &mimeType: savedFilters.typeFilters) {
0533         if (m_pMimeInfo.contains(mimeType)) {
0534             m_pMimeInfo[mimeType].useAsFilter = true;
0535         }
0536     }
0537 }
0538 
0539 K_PLUGIN_CLASS_WITH_JSON(DirFilterPlugin, "dirfilterplugin.json")
0540 
0541 #include "dirfilterplugin.moc"