Git commit 7781d6ef794a8da9afe1c837c3a7826103568add by Ragnar Thomsen. Committed on 16/12/2016 at 13:36. Pushed by rthomsen into branch 'master'.
Implement search function An action was added to Archive menu and is also shown in the default toolbar. The search is mediated through KRecursiveFilterProxyModel instead of QSortFilterProxyModel because the latter does not recurse nested models. This adds a new dependency on the KItemModels framework. The search is always case-insensitive. The search bar is displayed above the QTreeView and contains a close button. An eventfilter was installed on Part to catch the escape keypress to close the search bar. FEATURE: 188197 FIXED-IN: 17.04.0 Differential Revision: D3573 GUI: M +1 -0 CMakeLists.txt M +1 -1 part/CMakeLists.txt M +4 -2 part/ark_part.rc M +102 -14 part/part.cpp M +12 -0 part/part.h https://commits.kde.org/ark/7781d6ef794a8da9afe1c837c3a7826103568add diff --git a/CMakeLists.txt b/CMakeLists.txt index ae1ee028..c739f99a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Archive DocTools I18n IconThemes + ItemModels KIO Service Parts diff --git a/part/CMakeLists.txt b/part/CMakeLists.txt index 879a7239..89f79210 100644 --- a/part/CMakeLists.txt +++ b/part/CMakeLists.txt @@ -22,7 +22,7 @@ ki18n_wrap_ui(arkpart_PART_SRCS add_library(arkpart MODULE ${arkpart_PART_SRCS}) -target_link_libraries(arkpart kerfuffle KF5::Parts KF5::KIOFileWidgets) +target_link_libraries(arkpart kerfuffle KF5::Parts KF5::KIOFileWidgets KF5::ItemModels) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/ark_part.desktop.cmake diff --git a/part/ark_part.rc b/part/ark_part.rc index fb45306b..b4f90ff0 100644 --- a/part/ark_part.rc +++ b/part/ark_part.rc @@ -1,5 +1,5 @@ <!DOCTYPE kpartgui> -<kpartgui name="ark_part" version="15"> +<kpartgui name="ark_part" version="16"> <MenuBar> <Menu name="archive"> <text>&Archive</text> @@ -7,8 +7,9 @@ <Action name="add" group="archive_edit"/> <Action name="edit_comment" group="archive_edit"/> <Action name="extract_all" group="archive_extract"/> - <Action name="properties" group="archive_props"/> + <Action name="find_in_archive" group="archive_props"/> <Action name="test_archive" group="archive_props"/> + <Action name="properties" group="archive_props"/> </Menu> <Menu name="ark_file"> <text>&File</text> @@ -34,6 +35,7 @@ <Action name="extract"/> <Action name="preview"/> <Action name="openfile"/> + <Action name="find_in_archive"/> <Separator/> <Action name="add"/> <Action name="delete"/> diff --git a/part/part.cpp b/part/part.cpp index fcb2805b..8246492d 100644 --- a/part/part.cpp +++ b/part/part.cpp @@ -51,6 +51,7 @@ #include <KIO/StatJob> #include <KMessageBox> #include <KPluginFactory> +#include <KRecursiveFilterProxyModel> #include <KRun> #include <KSelectAction> #include <KStandardGuiItem> @@ -75,6 +76,7 @@ #include <QFileSystemWatcher> #include <QGroupBox> #include <QPlainTextEdit> +#include <QPushButton> using namespace Kerfuffle; @@ -108,6 +110,7 @@ Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList& args) QWidget *mainWidget = new QWidget; m_vlayout = new QVBoxLayout; m_model = new ArchiveModel(pathName, this); + m_filterModel = new KRecursiveFilterProxyModel(this); m_splitter = new QSplitter(Qt::Horizontal, parentWidget); m_view = new ArchiveView; m_infoPanel = new InfoPanel(m_model); @@ -143,6 +146,28 @@ Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList& args) setWidget(mainWidget); mainWidget->setLayout(m_vlayout); + // Setup search widget. + m_searchWidget = new QWidget(parentWidget); + m_searchWidget->setVisible(false); + m_searchWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + QHBoxLayout *searchLayout = new QHBoxLayout; + searchLayout->setContentsMargins(2, 2, 2, 2); + m_vlayout->addWidget(m_searchWidget); + m_searchWidget->setLayout(searchLayout); + m_searchCloseButton = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-close")), QString(), m_searchWidget); + m_searchCloseButton->setFlat(true); + m_searchLineEdit = new QLineEdit(m_searchWidget); + m_searchLineEdit->setClearButtonEnabled(true); + m_searchLineEdit->setPlaceholderText(i18n("Type to search...")); + mainWidget->installEventFilter(this); + searchLayout->addWidget(m_searchCloseButton); + searchLayout->addWidget(m_searchLineEdit); + connect(m_searchCloseButton, &QPushButton::clicked, this, [=]() { + m_searchWidget->hide(); + m_searchLineEdit->clear(); + }); + connect(m_searchLineEdit, &QLineEdit::textChanged, this, &Part::searchEdited); + // Configure the QVBoxLayout and add widgets m_vlayout->setContentsMargins(0,0,0,0); m_vlayout->addWidget(m_messageWidget); @@ -274,7 +299,7 @@ void Part::extractSelectedFilesTo(const QString& localPath) options.setDragAndDropEnabled(true); // Create and start the ExtractJob. - ExtractJob *job = m_model->extractFiles(filesAndRootNodesForIndexes(addChildren(m_view->selectionModel()->selectedRows())), destination, options); + ExtractJob *job = m_model->extractFiles(filesAndRootNodesForIndexes(addChildren(getSelectedIndexes())), destination, options); registerJob(job); connect(job, &KJob::result, this, &Part::slotExtractionDone); @@ -291,7 +316,10 @@ void Part::setupView() { m_view->setContextMenuPolicy(Qt::CustomContextMenu); - m_view->setModel(m_model); + m_filterModel->setSourceModel(m_model); + m_view->setModel(m_filterModel); + m_filterModel->setFilterKeyColumn(0); + m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Part::updateActions); @@ -428,6 +456,13 @@ void Part::setupActions() m_testArchiveAction->setToolTip(i18nc("@info:tooltip", "Click to test the archive for integrity")); connect(m_testArchiveAction, &QAction::triggered, this, &Part::slotTestArchive); + m_searchAction = actionCollection()->addAction(QStringLiteral("find_in_archive")); + m_searchAction->setIcon(QIcon::fromTheme(QStringLiteral("search"))); + m_searchAction->setText(i18nc("@action:inmenu", "&Find Files")); + actionCollection()->setDefaultShortcut(m_searchAction, Qt::CTRL + Qt::Key_F); + m_searchAction->setToolTip(i18nc("@info:tooltip", "Click to search in archive")); + connect(m_searchAction, &QAction::triggered, this, &Part::slotShowFind); + connect(m_signalMapper, static_cast<void (QSignalMapper::*)(int)>(&QSignalMapper::mapped), this, &Part::slotOpenEntry); @@ -439,7 +474,7 @@ void Part::setupActions() void Part::updateActions() { bool isWritable = m_model->archive() && !m_model->archive()->isReadOnly(); - const Archive::Entry *entry = m_model->entryForIndex(m_view->selectionModel()->currentIndex()); + const Archive::Entry *entry = m_model->entryForIndex(m_filterModel->mapToSource(m_view->selectionModel()->currentIndex())); int selectedEntriesCount = m_view->selectionModel()->selectedRows().count(); // We disable adding files if the archive is encrypted but the password is @@ -508,6 +543,9 @@ void Part::updateActions() (selectedEntriesCount == 0 || (selectedEntriesCount == 1 && isDir)) && (m_model->filesToMove.count() > 0 || m_model->filesToCopy.count() > 0)); + m_searchAction->setEnabled(!isBusy() && + m_model->rowCount() > 0); + m_commentView->setEnabled(!isBusy()); m_commentMsgWidget->setEnabled(!isBusy()); @@ -694,7 +732,7 @@ void Part::slotQuickExtractFiles(QAction *triggeredAction) qCDebug(ARK) << "Extracting to:" << finalDestinationDirectory; - ExtractJob *job = m_model->extractFiles(filesAndRootNodesForIndexes(addChildren(m_view->selectionModel()->selectedRows())), finalDestinationDirectory, ExtractionOptions()); + ExtractJob *job = m_model->extractFiles(filesAndRootNodesForIndexes(addChildren(getSelectedIndexes())), finalDestinationDirectory, ExtractionOptions()); registerJob(job); connect(job, &KJob::result, @@ -706,7 +744,16 @@ void Part::slotQuickExtractFiles(QAction *triggeredAction) void Part::selectionChanged() { - m_infoPanel->setIndexes(m_view->selectionModel()->selectedRows()); + m_infoPanel->setIndexes(getSelectedIndexes()); +} + +QModelIndexList Part::getSelectedIndexes() +{ + QModelIndexList list; + foreach (const QModelIndex &i, m_view->selectionModel()->selectedRows()) { + list.append(m_filterModel->mapToSource(i)); + } + return list; } bool Part::openFile() @@ -922,7 +969,7 @@ void Part::slotOpenEntry(int mode) { qCDebug(ARK) << "Opening with mode" << mode; - QModelIndex index = m_view->selectionModel()->currentIndex(); + QModelIndex index = m_filterModel->mapToSource(m_view->selectionModel()->currentIndex()); Archive::Entry *entry = m_model->entryForIndex(index); // Don't open directories. @@ -1114,7 +1161,7 @@ void Part::slotShowExtractionDialog() // If the user has chosen to extract only selected entries, fetch these // from the QTreeView. if (!dialog.data()->extractAllFiles()) { - files = filesAndRootNodesForIndexes(addChildren(m_view->selectionModel()->selectedRows())); + files = filesAndRootNodesForIndexes(addChildren(getSelectedIndexes())); } qCDebug(ARK) << "Selected " << files; @@ -1336,7 +1383,7 @@ void Part::slotAddFiles() QString dialogTitle = i18nc("@title:window", "Add Files"); const Archive::Entry *destination = Q_NULLPTR; if (m_view->selectionModel()->selectedRows().count() == 1) { - destination = m_model->entryForIndex(m_view->selectionModel()->currentIndex()); + destination = m_model->entryForIndex(m_filterModel->mapToSource(m_view->selectionModel()->currentIndex())); if (destination->isDir()) { dialogTitle = i18nc("@title:window", "Add Files to %1", destination->fullPath());; } else { @@ -1382,7 +1429,7 @@ void Part::slotEditFileName() void Part::slotCutFiles() { - QModelIndexList selectedRows = addChildren(m_view->selectionModel()->selectedRows()); + QModelIndexList selectedRows = addChildren(getSelectedIndexes()); m_model->filesToMove = ArchiveModel::entryMap(filesForIndexes(selectedRows)); qCDebug(ARK) << "Entries marked to cut:" << m_model->filesToMove.values(); m_model->filesToCopy.clear(); @@ -1398,7 +1445,7 @@ void Part::slotCutFiles() void Part::slotCopyFiles() { - m_model->filesToCopy = ArchiveModel::entryMap(filesForIndexes(addChildren(m_view->selectionModel()->selectedRows()))); + m_model->filesToCopy = ArchiveModel::entryMap(filesForIndexes(addChildren(getSelectedIndexes()))); qCDebug(ARK) << "Entries marked to copy:" << m_model->filesToCopy.values(); foreach (const QModelIndex &row, m_cutIndexes) { m_view->dataChanged(row, row); @@ -1414,8 +1461,8 @@ void Part::slotRenameFile(const QString &name) displayMsgWidget(KMessageWidget::Error, i18n("Filename can't contain slashes and can't be equal to \".\" or \"..\"")); return; } - const Archive::Entry *entry = m_model->entryForIndex(m_view->selectionModel()->currentIndex()); - QVector<Archive::Entry*> entriesToMove = filesForIndexes(addChildren(m_view->selectionModel()->selectedRows())); + const Archive::Entry *entry = m_model->entryForIndex(m_filterModel->mapToSource(m_view->selectionModel()->currentIndex())); + QVector<Archive::Entry*> entriesToMove = filesForIndexes(addChildren(getSelectedIndexes())); m_destination = new Archive::Entry(); const QString &entryPath = entry->fullPath(NoTrailingSlash); @@ -1432,7 +1479,7 @@ void Part::slotRenameFile(const QString &name) void Part::slotPasteFiles() { m_destination = (m_view->selectionModel()->selectedRows().count() > 0) - ? m_model->entryForIndex(m_view->selectionModel()->currentIndex()) + ? m_model->entryForIndex(m_filterModel->mapToSource(m_view->selectionModel()->currentIndex())) : Q_NULLPTR; if (m_destination == Q_NULLPTR) { m_destination = new Archive::Entry(Q_NULLPTR, QString()); @@ -1590,7 +1637,7 @@ void Part::slotDeleteFiles() return; } - DeleteJob *job = m_model->deleteFiles(filesForIndexes(addChildren(m_view->selectionModel()->selectedRows()))); + DeleteJob *job = m_model->deleteFiles(filesForIndexes(addChildren(getSelectedIndexes()))); connect(job, &KJob::result, this, &Part::slotDeleteFilesDone); registerJob(job); @@ -1673,6 +1720,47 @@ void Part::slotShowContextMenu() popup->popup(QCursor::pos()); } +bool Part::eventFilter(QObject *target, QEvent *event) +{ + Q_UNUSED(target) + + if (event->type() == QEvent::KeyPress) { + QKeyEvent *e = static_cast<QKeyEvent *>(event); + if (e->key() == Qt::Key_Escape) { + m_searchWidget->hide(); + m_searchLineEdit->clear(); + return true; + } + } + return false; +} + +void Part::slotShowFind() +{ + if (m_searchWidget->isVisible()) { + m_searchLineEdit->selectAll(); + } else { + m_searchWidget->show(); + } + m_searchLineEdit->setFocus(); +} + +void Part::searchEdited(const QString &text) +{ + m_view->collapseAll(); + + m_filterModel->setFilterFixedString(text); + + if(text.isEmpty()) { + m_view->collapseAll(); + if (m_view->model()->rowCount() == 1) { + m_view->expandToDepth(0); + } + } else { + m_view->expandAll(); + } +} + void Part::displayMsgWidget(KMessageWidget::MessageType type, const QString& msg) { // The widget could be already visible, so hide it. diff --git a/part/part.h b/part/part.h index e7c2bdd5..81f1cc7c 100644 --- a/part/part.h +++ b/part/part.h @@ -41,9 +41,11 @@ class InfoPanel; class KAboutData; class KAbstractWidgetJobTracker; class KJob; +class KRecursiveFilterProxyModel; class KToggleAction; class QAction; +class QLineEdit; class QSplitter; class QTreeView; class QTemporaryDir; @@ -52,6 +54,7 @@ class QSignalMapper; class QFileSystemWatcher; class QGroupBox; class QPlainTextEdit; +class QPushButton; namespace Ark { @@ -76,6 +79,7 @@ public: bool isBusy() const Q_DECL_OVERRIDE; KConfigSkeleton *config() const Q_DECL_OVERRIDE; QList<Kerfuffle::SettingsPage*> settingsPages(QWidget *parent) const Q_DECL_OVERRIDE; + bool eventFilter(QObject *target, QEvent *event) Q_DECL_OVERRIDE; /** * Validate the localFilePath() associated to this part. @@ -156,7 +160,9 @@ private slots: void slotAddComment(); void slotCommentChanged(); void slotTestArchive(); + void slotShowFind(); void displayMsgWidget(KMessageWidget::MessageType type, const QString& msg); + void searchEdited(const QString &text); signals: void busy(); @@ -175,6 +181,7 @@ private: QVector<Kerfuffle::Archive::Entry*> filesAndRootNodesForIndexes(const QModelIndexList& list) const; QModelIndexList addChildren(const QModelIndexList &list) const; void registerJob(KJob *job); + QModelIndexList getSelectedIndexes(); ArchiveModel *m_model; ArchiveView *m_view; @@ -193,6 +200,7 @@ private: QAction *m_propertiesAction; QAction *m_editCommentAction; QAction *m_testArchiveAction; + QAction *m_searchAction; KToggleAction *m_showInfoPanelAction; InfoPanel *m_infoPanel; QSplitter *m_splitter; @@ -216,6 +224,10 @@ private: KMessageWidget *m_commentMsgWidget; KMessageWidget *m_messageWidget; Kerfuffle::CompressionOptions m_compressionOptions; + KRecursiveFilterProxyModel *m_filterModel; + QWidget *m_searchWidget; + QLineEdit *m_searchLineEdit; + QPushButton *m_searchCloseButton; }; } // namespace Ark
