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"