From 8587b0c04059b4b924d944dc0c32b924a85e03cf Mon Sep 17 00:00:00 2001 From: reionwong Date: Mon, 20 Sep 2021 11:04:33 +0800 Subject: [PATCH] Support keyboard search selection --- CMakeLists.txt | 1 + helper/keyboardsearchmanager.cpp | 56 ++++++++++++++++++++++++++++ helper/keyboardsearchmanager.h | 46 +++++++++++++++++++++++ helper/shortcut.cpp | 7 +++- helper/shortcut.h | 13 +------ model/foldermodel.cpp | 63 ++++++++++++++++++++++++++++++++ model/foldermodel.h | 7 ++++ qml/FolderGridItem.qml | 2 +- qml/FolderGridView.qml | 6 --- qml/FolderListView.qml | 8 ---- 10 files changed, 181 insertions(+), 28 deletions(-) create mode 100644 helper/keyboardsearchmanager.cpp create mode 100644 helper/keyboardsearchmanager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index eb74bfc..9208244 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ add_executable(cutefish-filemanager helper/fm.cpp helper/shortcut.cpp helper/filelauncher.cpp + helper/keyboardsearchmanager.cpp mimetype/mimeappmanager.cpp mimetype/xdgdesktopfile.cpp diff --git a/helper/keyboardsearchmanager.cpp b/helper/keyboardsearchmanager.cpp new file mode 100644 index 0000000..09b3b11 --- /dev/null +++ b/helper/keyboardsearchmanager.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "keyboardsearchmanager.h" + +KeyboardSearchManager *KEYBORDSRARCH_MANAGER_SELF = nullptr; + +KeyboardSearchManager *KeyboardSearchManager::self() +{ + if (!KEYBORDSRARCH_MANAGER_SELF) + KEYBORDSRARCH_MANAGER_SELF = new KeyboardSearchManager; + + return KEYBORDSRARCH_MANAGER_SELF; +} + +KeyboardSearchManager::KeyboardSearchManager(QObject *parent) + : QObject(parent) + , m_timeout(500) +{ + // m_timer.setInterval(m_timeout); + // connect(&m_timer, &QTimer::timeout, this, [=] { + // m_searchText.clear(); + // }); +} + +void KeyboardSearchManager::addKeys(const QString &keys) +{ + if (!keys.isEmpty()) { + // m_timer.stop(); + // m_searchText.append(keys); + + // const QChar firstKey = m_searchText.length() > 0 ? m_searchText.at(0) : QChar(); + // const bool sameKey = m_searchText.length() > 1 && m_searchText.count(firstKey) == m_searchText.length(); + + // emit searchTextChanged(sameKey ? firstKey : m_searchText, false); + emit searchTextChanged(keys, false); + + // m_timer.start(); + } +} diff --git a/helper/keyboardsearchmanager.h b/helper/keyboardsearchmanager.h new file mode 100644 index 0000000..c14699d --- /dev/null +++ b/helper/keyboardsearchmanager.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#ifndef KEYBOARDSEARCHMANAGER_H +#define KEYBOARDSEARCHMANAGER_H + +#include +#include + +class KeyboardSearchManager : public QObject +{ + Q_OBJECT + +public: + static KeyboardSearchManager *self(); + explicit KeyboardSearchManager(QObject *parent = nullptr); + + void addKeys(const QString &keys); + +signals: + void searchTextChanged(const QString &string, bool searchFromNextItem); + +private: + QString m_searchText; + qint64 m_timeout; + // QTimer m_timer; +}; + +#endif // KEYBOARDSEARCHMANAGER_H diff --git a/helper/shortcut.cpp b/helper/shortcut.cpp index e416179..2200c72 100644 --- a/helper/shortcut.cpp +++ b/helper/shortcut.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright 2021 Reion Wong * + * Copyright 2021 Reion Wong * * Copyright Ken * * Copyright 2016 Leslie Zhai * * * @@ -20,6 +20,7 @@ ***************************************************************************/ #include "shortcut.h" +#include "keyboardsearchmanager.h" #include @@ -67,6 +68,10 @@ bool ShortCut::eventFilter(QObject *obj, QEvent *e) emit deleteFile(); } else if (keyEvent->key() == Qt::Key_F5) { emit refresh(); + } else if (keyEvent->key() >= Qt::Key_A && keyEvent->key() <= Qt::Key_Z) { + // Handle select + KeyboardSearchManager::self()->addKeys(keyEvent->text()); + keyEvent->ignore(); } } diff --git a/helper/shortcut.h b/helper/shortcut.h index 0711ddf..490b724 100644 --- a/helper/shortcut.h +++ b/helper/shortcut.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright 2021 Reion Wong * + * Copyright 2021 Reion Wong * * Copyright Ken * * Copyright 2016 Leslie Zhai * * * @@ -24,17 +24,6 @@ #include -/** - * TODO: ShortCut is a stopgap solution and should be dropped when Qt's StandardKey - * gains support for these actions. QTBUG-54926 https://bugreports.qt.io/browse/QTBUG-54926 - * And it is *NOT* encouraged registering C++ types with the QML by using EventFilter - * but for special case QTBUG-40327 https://bugreports.qt.io/browse/QTBUG-40327 - * - * ShortCut was copied from Ken's answer. - * https://stackoverflow.com/questions/12192780/assigning-keyboard-shortcuts-to-qml-components - * it uses cc by-sa 3.0 license by default compatible with GPL. - * https://www.gnu.org/licenses/license-list.en.html#ccbysa - */ class ShortCut : public QObject { Q_OBJECT diff --git a/model/foldermodel.cpp b/model/foldermodel.cpp index 63f4a49..952b6e1 100644 --- a/model/foldermodel.cpp +++ b/model/foldermodel.cpp @@ -90,6 +90,7 @@ FolderModel::FolderModel(QObject *parent) , m_viewAdapter(nullptr) , m_mimeAppManager(MimeAppManager::self()) , m_sizeJob(nullptr) + , m_keyboardSearchManager(KeyboardSearchManager::self()) { QSettings settings("cutefishos", qApp->applicationName()); m_showHiddenFiles = settings.value("showHiddenFiles", false).toBool(); @@ -119,6 +120,9 @@ FolderModel::FolderModel(QObject *parent) connect(this, SIGNAL(rowsInserted(QModelIndex, int, int)), SIGNAL(countChanged())); connect(this, SIGNAL(rowsRemoved(QModelIndex, int, int)), SIGNAL(countChanged())); connect(this, SIGNAL(modelReset()), SIGNAL(countChanged())); + + connect(m_keyboardSearchManager, &KeyboardSearchManager::searchTextChanged, + this, &FolderModel::keyboardSearchChanged); } FolderModel::~FolderModel() @@ -232,11 +236,39 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const return QSortFilterProxyModel::data(index, role); } +int FolderModel::indexForKeyboardSearch(const QString &text, int startFromIndex) const +{ + startFromIndex = qMax(0, startFromIndex); + + for (int i = startFromIndex; i < rowCount(); ++i) { + if (fileItem(i).text().startsWith(text, Qt::CaseInsensitive)) { + return i; + } + } + + for (int i = 0; i < startFromIndex; ++i) { + if (fileItem(i).text().startsWith(text, Qt::CaseInsensitive)) { + return i; + } + } + + return -1; +} + KFileItem FolderModel::itemForIndex(const QModelIndex &index) const { return m_dirModel->itemForIndex(mapToSource(index)); } +KFileItem FolderModel::fileItem(int index) const +{ + if (index >= 0 && index < count()) { + return itemForIndex(FolderModel::index(index, 0)); + } + + return KFileItem(); +} + QList FolderModel::selectedUrls() const { const auto indexes = m_selectionModel->selectedIndexes(); @@ -1241,6 +1273,37 @@ void FolderModel::dragSelectedInternal(int x, int y) } } +void FolderModel::keyboardSearchChanged(const QString &text, bool searchFromNextItem) +{ + Q_UNUSED(searchFromNextItem); + + if (rowCount() == 0) + return; + + int index; + int currentIndex = -1; + + if (m_selectionModel->hasSelection()) { + currentIndex = m_selectionModel->selectedIndexes().first().row(); + } + +// if (searchFromNextItem) { +// index = indexForKeyboardSearch(text, (currentIndex + 1) % rowCount()); +// } else { +// index = indexForKeyboardSearch(text, 0); +// } + + index = indexForKeyboardSearch(text, (currentIndex + 1) % rowCount()); + + if (index < 0 || currentIndex == index) + return; + + if (index >= 0) { + clearSelection(); + setSelected(index); + } +} + bool FolderModel::isSupportThumbnails(const QString &mimeType) const { const QStringList supportsMimetypes = {"image/bmp", "image/png", "image/gif", "image/jpeg", "image/web", diff --git a/model/foldermodel.h b/model/foldermodel.h index f5e93de..b82fed4 100644 --- a/model/foldermodel.h +++ b/model/foldermodel.h @@ -26,6 +26,7 @@ #define FOLDERMODEL_H #include "../widgets/itemviewadapter.h" +#include "../helper/keyboardsearchmanager.h" #include "../helper/pathhistory.h" #include "../mimetype/mimeappmanager.h" @@ -109,8 +110,11 @@ public: static QHash staticRoleNames(); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int indexForKeyboardSearch(const QString &text, int startFromIndex = 0) const; KFileItem itemForIndex(const QModelIndex &index) const; + KFileItem fileItem(int index) const; + QList selectedUrls() const; QString url() const; @@ -238,6 +242,7 @@ signals: private slots: void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void dragSelectedInternal(int x, int y); + void keyboardSearchChanged(const QString &text, bool searchFromNextItem); private: void invalidateIfComplete(); @@ -294,6 +299,8 @@ private: MimeAppManager *m_mimeAppManager; CFileSizeJob *m_sizeJob; + + KeyboardSearchManager *m_keyboardSearchManager; }; #endif // FOLDERMODEL_H diff --git a/qml/FolderGridItem.qml b/qml/FolderGridItem.qml index 5337c37..fae1b5a 100644 --- a/qml/FolderGridItem.qml +++ b/qml/FolderGridItem.qml @@ -111,7 +111,7 @@ Item { anchors.fill: _icon source: _icon color: "white" - opacity: FishUI.Theme.darkMode ? 0.3 : 0.4 + opacity: 0.3 visible: control.hovered && !control.selected } } diff --git a/qml/FolderGridView.qml b/qml/FolderGridView.qml index 32b9d03..0c9c600 100644 --- a/qml/FolderGridView.qml +++ b/qml/FolderGridView.qml @@ -46,7 +46,6 @@ GridView { property bool ctrlPressed: false property bool shiftPressed: false - property int previouslySelectedItemIndex: -1 property variant cPress: null property Item editor: null property int anchorIndex: 0 @@ -138,7 +137,6 @@ GridView { function reset() { currentIndex = -1 anchorIndex = 0 - previouslySelectedItemIndex = -1 cancelRename() hoveredItem = null pressedItem = null @@ -174,7 +172,6 @@ GridView { } Keys.onEscapePressed: { if (!editor || !editor.targetItem) { - previouslySelectedItemIndex = -1 dirModel.clearSelection() event.accepted = false } @@ -602,9 +599,6 @@ GridView { } else { dirModel.clearSelection() dirModel.setSelected(currentIndex) - if (currentIndex == -1) - previouslySelectedItemIndex = -1 - previouslySelectedItemIndex = currentIndex } } diff --git a/qml/FolderListView.qml b/qml/FolderListView.qml index d6a2c8f..82f1dfb 100644 --- a/qml/FolderListView.qml +++ b/qml/FolderListView.qml @@ -40,7 +40,6 @@ ListView { property bool ctrlPressed: false property bool shiftPressed: false - property int previouslySelectedItemIndex: -1 property variant cPress: null property Item editor: null property int anchorIndex: 0 @@ -86,7 +85,6 @@ ListView { function reset() { currentIndex = -1 anchorIndex = 0 - previouslySelectedItemIndex = -1 cancelRename() hoveredItem = null pressedItem = null @@ -119,7 +117,6 @@ ListView { Keys.onEscapePressed: { if (!editor || !editor.targetItem) { - previouslySelectedItemIndex = -1 dirModel.clearSelection() event.accepted = false } @@ -215,7 +212,6 @@ ListView { if (!hoveredItem || hoveredItem.blank) { if (!control.ctrlPressed) { control.currentIndex = -1 - control.previouslySelectedItemIndex = -1 dirModel.clearSelection() } @@ -231,7 +227,6 @@ ListView { dirModel.setRangeSelected(control.anchorIndex, hoveredItem.index) } else { if (!control.ctrlPressed && !dirModel.isSelected(hoveredItem.index)) { - previouslySelectedItemIndex = -1 dirModel.clearSelection() } @@ -395,9 +390,6 @@ ListView { } else { dirModel.clearSelection() dirModel.setSelected(currentIndex) - if (currentIndex == -1) - previouslySelectedItemIndex = -1 - previouslySelectedItemIndex = currentIndex } }