[ark] /: Implement search function

Ragnar Thomsen null at kde.org
Fri Dec 16 13:39:56 UTC 2016


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


More information about the kde-doc-english mailing list