diff --git a/cuttlefish/autotests/CMakeLists.txt b/cuttlefish/autotests/CMakeLists.txt index b809e46..976cfd6 100644 --- a/cuttlefish/autotests/CMakeLists.txt +++ b/cuttlefish/autotests/CMakeLists.txt @@ -1,19 +1,19 @@ include(ECMAddTests) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Test) include_directories(../src) -ecm_add_test(iconmodeltest.cpp ../src/iconmodel.cpp +ecm_add_test(iconmodeltest.cpp ../src/iconmodel.cpp ../src/sortfiltermodel.cpp TEST_NAME iconmodeltest LINK_LIBRARIES Qt5::Gui Qt5::Test KF5::ConfigCore KF5::IconThemes KF5::Package KF5::Service KF5::Plasma KF5::ConfigWidgets ) diff --git a/cuttlefish/autotests/iconmodeltest.cpp b/cuttlefish/autotests/iconmodeltest.cpp index dd82062..7040eda 100644 --- a/cuttlefish/autotests/iconmodeltest.cpp +++ b/cuttlefish/autotests/iconmodeltest.cpp @@ -1,88 +1,93 @@ /* * Copyright 2014 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation; * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include "iconmodel.h" +#include "sortfiltermodel.h" using namespace CuttleFish; class IconModelTest : public QObject { Q_OBJECT private Q_SLOTS: void init() { m_iconModel = new IconModel(this); + m_proxyModel = new SortFilterModel(this); + m_proxyModel->setSourceModel(m_iconModel); } void cleanup() { delete m_iconModel; + delete m_proxyModel; } void initTestCase() { }; void testCategoryFilter() { const int _all = m_iconModel->rowCount(QModelIndex()); - m_iconModel->setFilter("edit"); - const int _edit = m_iconModel->rowCount(QModelIndex()); + m_proxyModel->setFilter("edit"); + const int _edit = m_proxyModel->rowCount(QModelIndex()); - m_iconModel->setCategory("actions"); - const int _editactions = m_iconModel->rowCount(QModelIndex()); + m_proxyModel->setCategory("actions"); + const int _editactions = m_proxyModel->rowCount(QModelIndex()); - m_iconModel->setCategory("all"); - const int _alledit = m_iconModel->rowCount(QModelIndex()); + m_proxyModel->setCategory("all"); + const int _alledit = m_proxyModel->rowCount(QModelIndex()); QVERIFY(_all > _edit); QVERIFY(_all > _editactions); QVERIFY(_edit >= _editactions); QVERIFY(_alledit >= _editactions); } private: // disable from here for testing just the above private: QJsonArray m_data; QJsonArray m_empty; IconModel* m_iconModel; + SortFilterModel* m_proxyModel; }; QTEST_MAIN(IconModelTest) #include "iconmodeltest.moc" diff --git a/cuttlefish/package/contents/ui/IconGrid.qml b/cuttlefish/package/contents/ui/IconGrid.qml index 3d4a356..0312be5 100644 --- a/cuttlefish/package/contents/ui/IconGrid.qml +++ b/cuttlefish/package/contents/ui/IconGrid.qml @@ -1,51 +1,55 @@ /*************************************************************************** * * * Copyright 2014-2015 Sebastian Kügler * * * * 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 2 of the License, or * * (at your option) 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * * * ***************************************************************************/ import QtQuick 2.5 import QtQuick.Controls 2.5 as QQC2 import QtQuick.Layouts 1.0 import org.kde.kirigami 2.8 as Kirigami GridView { id: iconGrid focus: true cellWidth: iconSize + Math.round(Kirigami.Units.gridUnit * 1.5) cellHeight: cellWidth + Math.round(Kirigami.Units.gridUnit * 2) cacheBuffer: 20 highlightMoveDuration: 0 boundsBehavior: Flickable.StopAtBounds - model: iconModel + model: proxyModel + currentIndex: proxyModel.currentIndex highlight: Item {} delegate: IconGridDelegate {} QQC2.BusyIndicator { running: iconModel.loading visible: running anchors.centerIn: parent width: Kirigami.Units.gridUnit * 8 height: width } + Component.onCompleted: { + currentItem.setAsPreview() + } } diff --git a/cuttlefish/package/contents/ui/IconGridDelegate.qml b/cuttlefish/package/contents/ui/IconGridDelegate.qml index 25016c8..3eef48e 100644 --- a/cuttlefish/package/contents/ui/IconGridDelegate.qml +++ b/cuttlefish/package/contents/ui/IconGridDelegate.qml @@ -1,100 +1,100 @@ /*************************************************************************** * * * Copyright 2014-2017 Sebastian Kügler * * * * 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 2 of the License, or * * (at your option) 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * * * ***************************************************************************/ import QtQuick 2.5 import QtQuick.Controls 2.5 as QQC2 import QtQuick.Layouts 1.0 import org.kde.kirigami 2.8 as Kirigami // for smallestFont import org.kde.plasma.core 2.0 as PlasmaCore MouseArea { id: delegateRoot width: iconSize + Kirigami.Units.gridUnit height: cellWidth + Math.round(Kirigami.Units.gridUnit * 2) acceptedButtons: Qt.LeftButton | Qt.RightButton function setAsPreview() { preview.fullPath = fullPath preview.iconName = iconName preview.fileName = fileName preview.category = category preview.type = type preview.iconTheme = iconTheme preview.sizes = sizes preview.scalable = scalable; } Rectangle { color: Kirigami.Theme.highlightColor opacity: iconGrid.currentIndex == index ? 0.5 : 0.0 visible: opacity != 0.0 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } } anchors { fill: parent } } Kirigami.Icon { id: delegateIcon width: iconSize height: width source: iconName anchors { top: parent.top horizontalCenter: parent.horizontalCenter } } QQC2.Label { font.pointSize: iconSize > 96 ? Kirigami.Theme.defaultFont.pointSize : theme.smallestFont.pointSize text: iconName wrapMode: Text.Wrap maximumLineCount: 3 horizontalAlignment: Text.AlignHCenter opacity: iconGrid.currentIndex == index ? 1.0 : 0.7 anchors { left: parent.left right: parent.right top: delegateIcon.bottom topMargin: 0 margins: Math.round(-Kirigami.Units.gridUnit / 4) } } Connections { target: iconGrid onCurrentIndexChanged: { if (delegateRoot.GridView.isCurrentItem) { delegateRoot.setAsPreview(); } } } onClicked: (mouse) => { - iconGrid.currentIndex = index; + proxyModel.currentIndex = index iconGrid.forceActiveFocus(); - if (mouse.button == Qt.RightButton) { + if (mouse.button == Qt.RightButton) { cuttlefish.itemRightClicked() } } } diff --git a/cuttlefish/package/contents/ui/Tools.qml b/cuttlefish/package/contents/ui/Tools.qml index a608175..6e02d19 100644 --- a/cuttlefish/package/contents/ui/Tools.qml +++ b/cuttlefish/package/contents/ui/Tools.qml @@ -1,159 +1,158 @@ /*************************************************************************** * * * Copyright 2014-2017 Sebastian Kügler * * Copyright 2019 Carson Black * * * * 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 2 of the License, or * * (at your option) 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * * * ***************************************************************************/ import QtQuick 2.5 import QtQuick.Controls 2.5 as QQC2 import QtQuick.Layouts 1.0 import org.kde.kirigami 2.8 as Kirigami import org.kde.kquickcontrolsaddons 2.0 as KQCAddons Rectangle { id: root width: parent.width color: Kirigami.Theme.backgroundColor signal colorschemeChanged(int index) signal sliderValueChanged(int val) property alias slider: sizeslider property alias currentIndex: colorcombo.currentIndex property alias value: sizeslider.value RowLayout { anchors.fill: parent anchors.leftMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing anchors.verticalCenter: parent.verticalCenter spacing: Kirigami.Units.largeSpacing Kirigami.SearchField { id: filterInput Layout.fillWidth: true onTextChanged: typingtimer.restart() focus: true Timer { id: typingtimer running: false repeat: false interval: 100 onTriggered: { - iconModel.filter = filterInput.text + proxyModel.filter = filterInput.text } } Component.onCompleted: { filterInput.forceActiveFocus() } } QQC2.ComboBox { id: catsCombo Layout.preferredWidth: units.gridUnit * 6 model: iconModel.categories onActivated: { if (currentText == "all") { - iconModel.category = ""; + proxyModel.category = ""; } else if (currentText != "") { - iconModel.category = currentText + proxyModel.category = currentText } - iconModel.sort() } popup.modal: false } QQC2.Slider { visible: cuttlefish.widescreen id: sizeslider Layout.preferredWidth: preview.width - units.gridUnit * 2 to: 6.0 stepSize: 1.0 snapMode: QQC2.Slider.SnapAlways value: 4.0 onValueChanged: { sizetimer.restart() pixelSizeInput.text = indexToSize(sizeslider.value) root.sliderValueChanged(sizeslider.value) } Timer { id: sizetimer running: false repeat: false interval: 200 onTriggered: iconSize = indexToSize(sizeslider.value) } Component.onCompleted: { pixelSizeInput.text = indexToSize(sizeslider.value); } } QQC2.Label { visible: cuttlefish.widescreen id: pixelSizeInput Layout.preferredWidth: units.gridUnit * 3 } QQC2.Label { visible: cuttlefish.widescreen text: i18n("Color scheme:") } QQC2.ComboBox { id: colorcombo visible: cuttlefish.widescreen model: colorSchemes.colorSchemes delegate: QQC2.ItemDelegate { Kirigami.Theme.colorSet: Kirigami.Theme.View width: parent.width highlighted: colorcombo.highlightedIndex == index contentItem: RowLayout { Kirigami.Icon { source: model.decoration Layout.preferredHeight: Kirigami.Units.iconSizes.small Layout.preferredWidth: Kirigami.Units.iconSizes.small } QQC2.Label { text: model.display color: highlighted ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor Layout.fillWidth: true } } } textRole: "display" onActivated: (index) => { root.colorschemeChanged(index) colorSchemes.activateColorScheme(currentText) } popup.modal: false } } Kirigami.Separator { height: 1 anchors { left: parent.left right: parent.right bottom: parent.bottom } } } diff --git a/cuttlefish/src/CMakeLists.txt b/cuttlefish/src/CMakeLists.txt index b49dc87..6f72d7d 100644 --- a/cuttlefish/src/CMakeLists.txt +++ b/cuttlefish/src/CMakeLists.txt @@ -1,27 +1,28 @@ set(cuttlefish_SRCS main.cpp iconmodel.cpp colorschemes.cpp + sortfiltermodel.cpp ) add_executable(cuttlefish ${cuttlefish_SRCS}) target_compile_definitions(cuttlefish PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}") target_link_libraries(cuttlefish Qt5::Quick Qt5::Gui Qt5::Widgets # for QDirModel KF5::Plasma KF5::KIOWidgets KF5::Declarative KF5::ConfigWidgets KF5::I18n KF5::IconThemes KF5::Package Qt5::DBus ) install(TARGETS cuttlefish ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) add_subdirectory(editorplugin) diff --git a/cuttlefish/src/iconmodel.cpp b/cuttlefish/src/iconmodel.cpp index a281411..919da1c 100644 --- a/cuttlefish/src/iconmodel.cpp +++ b/cuttlefish/src/iconmodel.cpp @@ -1,330 +1,271 @@ /*************************************************************************** * * * Copyright 2014-2015 Sebastian Kügler * * * * 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 2 of the License, or * * (at your option) 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * * * ***************************************************************************/ #include "iconmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace CuttleFish; static QTextStream cout(stdout); IconModel::IconModel(QObject *parent) : QAbstractListModel(parent) , m_loading(false) { m_roleNames.insert(FileName, "fileName"); m_roleNames.insert(IconName, "iconName"); m_roleNames.insert(Icon, "icon"); m_roleNames.insert(FullPath, "fullPath"); m_roleNames.insert(Category, "category"); m_roleNames.insert(Scalable, "scalable"); m_roleNames.insert(Sizes, "sizes"); m_roleNames.insert(Theme, "iconTheme"); m_roleNames.insert(Type, "type"); - connect(this, &IconModel::categoryChanged, this, &IconModel::load); - m_categories = QStringList() << "all" \ << "actions" << "animations" << "apps" << "categories" << "devices" << "emblems" << "emotes" << "filesystems" << "international" << "mimetypes" << "places" << "status"; load(); } QHash IconModel::roleNames() const { return m_roleNames; } int IconModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) if (m_data.size() <= 0) { return 0; } else { return m_data.size(); } } QVariant IconModel::data(const QModelIndex &index, int role) const { if (index.isValid()) { QString icon = m_icons.at(index.row()); switch (role) { case IconName: return icon; } return m_data[icon][key(role)]; } return QVariant(); } QString IconModel::key(int role) const { return QString::fromLocal8Bit(m_roleNames[role]); } void IconModel::add(const QFileInfo &info, const QString &cat) { QStringList cats; Q_FOREACH (auto c, m_categories) { cats << c.toLower(); } if (!cats.contains(cat)) { m_categories << cat; emit categoriesChanged(); } const QString fname = info.fileName(); bool scalable = false; QString icon = fname; if (fname.endsWith(QLatin1String(".png"))) { icon.remove(".png"); } else if (fname.endsWith(QLatin1String(".svgz"))) { icon.remove(".svgz"); scalable = true; } else if (fname.endsWith(QLatin1String(".svg"))) { icon.remove(".svg"); scalable = true; } QVariantMap &data = m_data[icon]; if (!m_icons.contains(icon)) { data["fullPath"] = info.absoluteFilePath(); data["iconName"] = icon; data["fileName"] = info.fileName(); data["category"] = cat; data["type"] = QStringLiteral("icon"); data["scalable"] = scalable; data["iconTheme"] = QStringLiteral("breeze"); m_icons << icon; } if (scalable && !data["scalable"].toBool()) { data["scalable"] = true; } QStringList _s = info.path().split('/'); if (_s.count() > 2) { QString size = _s[_s.count()-2]; // last but one is size, last is category if (size.indexOf("x") > 1) { size = size.split("x")[0]; QStringList sizes = data["sizes"].toStringList(); if (!sizes.contains(size)) { //qDebug() << "Size added" << sizes << size << data["iconName"]; sizes << size; data["sizes"] = sizes; } } } } -QString IconModel::category() const -{ - return m_category; -} - QStringList IconModel::categories() const { return m_categories; } -void IconModel::setCategory(const QString& cat) -{ - if (cat != m_category) { - m_category = cat; - emit categoryChanged(); - } -} - -QString IconModel::filter() const -{ - return m_filter; -} - -void IconModel::setFilter(const QString &filter) -{ - //qDebug() << "Filter: " << filter; - if (m_filter != filter) { - m_filter = filter; - load(); - emit filterChanged(); - } -} - void IconModel::load() { //qDebug() << "\n -- Loading (category / filter) : " << m_category << m_filter; m_loading = true; emit loadingChanged(); QElapsedTimer tt; tt.start(); const QDirIterator::IteratorFlags flags = QDirIterator::Subdirectories; const QStringList nameFilters = QStringList(); beginResetModel(); m_data.clear(); m_icons.clear(); //sm_categories.clear(); QString iconTheme; if (KIconLoader::global()) { iconTheme = KIconLoader::global()->theme()->internalName(); } else { return; } QStringList searchPaths; QStringList iconThemePaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons/") + iconTheme, QStandardPaths::LocateDirectory); if (iconThemePaths.count() > 0) { searchPaths << iconThemePaths; } QStringList hicolorThemePaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons/hicolor"), QStandardPaths::LocateDirectory); if (hicolorThemePaths.count() > 0) { searchPaths << hicolorThemePaths; } foreach (const QString &iconPath, searchPaths) { QDirIterator cats(iconPath, nameFilters, QDir::Dirs, QDirIterator::NoIteratorFlags); while (cats.hasNext()) { cats.next(); const QString fpath = cats.filePath(); const QString category = cats.fileName(); if (category != "." && category != "..") { QDirIterator it(fpath, nameFilters, QDir::Files, flags); while (it.hasNext()) { it.next(); const QFileInfo &info = it.fileInfo(); - if (matchIcons(info)) { - add(info, categoryFromPath(info.absoluteFilePath())); - } + add(info, categoryFromPath(info.absoluteFilePath())); } } } } - sort(); - endResetModel(); m_loading = false; emit loadingChanged(); } -void IconModel::sort() -{ - std::sort(m_icons.begin(), m_icons.end()); -} - -bool IconModel::matchIcons(const QFileInfo& info) -{ - bool ok = false; - - // Category is empty or all? Skip further matching. - bool catmatch = m_category.isEmpty() || m_category == QStringLiteral("all"); - // category match? - if (!catmatch && m_category == categoryFromPath(info.absoluteFilePath())) { - catmatch = true; - } - - // name filter - if (m_filter.isEmpty() || info.fileName().indexOf(m_filter) != -1) { - if (catmatch) { - ok = true; - } - } - return ok; -} - QString IconModel::categoryFromPath(const QString& path) { QStringList cats; Q_FOREACH (auto c, m_categories) { cats << c.toLower(); } //cats << "actions" << "apps" << "places" << "status"; const QStringList _p1 = path.split("/icons/"); if (_p1.count() > 1) { foreach (const QString &cat, cats) { if (_p1[1].indexOf(cat) != -1) { return cat; } } } return QString(); } bool IconModel::loading() { return m_loading; } void IconModel::output(const QString& text) { cout << text.toLocal8Bit(); cout.flush(); } QVariantList IconModel::inOtherThemes(const QString& name, int iconSize) { QVariantList list; const QStringList themes = KIconTheme::list(); for (const auto& themeName : themes) { const KIconTheme theme(themeName); const QString iconPath = theme.iconPathByName(name, iconSize, KIconLoader::MatchBest); list.append(QVariantMap({{"themeName", themeName}, {"iconPath", iconPath}})); } return list; } diff --git a/cuttlefish/src/iconmodel.h b/cuttlefish/src/iconmodel.h index fc72d6e..9ddbbe4 100644 --- a/cuttlefish/src/iconmodel.h +++ b/cuttlefish/src/iconmodel.h @@ -1,109 +1,95 @@ /*************************************************************************** * * * Copyright 2014-2015 Sebastian Kügler * * * * 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 2 of the License, or * * (at your option) 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * * * ***************************************************************************/ #ifndef CUTTLEFISHICONMODEL_H #define CUTTLEFISHICONMODEL_H #include #include #include #include #include #include namespace CuttleFish { class IconModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged) - Q_PROPERTY(QString category READ category WRITE setCategory NOTIFY categoryChanged) Q_PROPERTY(QStringList categories READ categories NOTIFY categoriesChanged) Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged); public: enum Roles { FileName = Qt::UserRole + 1, IconName, Icon, FullPath, Category, Scalable, Sizes, Type, Theme }; explicit IconModel(QObject *parent = nullptr); Q_INVOKABLE QVariantList inOtherThemes(const QString& iconName, int size); QHash roleNames() const override; int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QString key(int role) const; - bool matchIcons(const QFileInfo &info); void add(const QFileInfo &info, const QString &cat); void remove(const QString &iconFile); - void setCategory(const QString &cat); - QString category() const; - - void setFilter(const QString &filter); - QString filter() const; - QStringList categories() const; bool loading(); void load(); Q_INVOKABLE void output(const QString &text); - Q_INVOKABLE void sort(); Q_SIGNALS: - void filterChanged(); - void categoryChanged(); void categoriesChanged(); void loadingChanged(); private: QHash m_roleNames; QStringList m_icons; - QString m_category; QStringList m_categories; - QString m_filter; QHash m_data; QHash m_categoryTranslations; bool m_loading; QString categoryFromPath(const QString &path); }; } // namespace #endif // CUTTLEFISHICONMODEL_H diff --git a/cuttlefish/src/main.cpp b/cuttlefish/src/main.cpp index 82061b0..6a16b88 100644 --- a/cuttlefish/src/main.cpp +++ b/cuttlefish/src/main.cpp @@ -1,143 +1,149 @@ /*************************************************************************** * * * Copyright 2014 Sebastian Kügler * * * * 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 2 of the License, or * * (at your option) 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ // Qt #include #include #include #include #include #include #include // Frameworks #include #include #include #include #include // Own #include "colorschemes.h" #include "iconmodel.h" +#include "sortfiltermodel.h" void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { if ((msg.contains("qt.svg") && msg.contains("Could not resolve property: #linearGradient")) || msg.contains("Could not resolve property: #pattern")) { return; } QByteArray localMsg = msg.toLocal8Bit(); const char *file = context.file ? context.file : ""; const char *function = context.function ? context.function : ""; switch (type) { case QtDebugMsg: fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); break; case QtInfoMsg: fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); break; case QtWarningMsg: fprintf(stderr, "\u001b[33mWarning\u001b[0m: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); break; case QtCriticalMsg: fprintf(stderr, "\u001b[33;1mCritical\u001b[0m: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); break; case QtFatalMsg: fprintf(stderr, "\u001b[31;1mFatal\u001b[0m: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function); break; } } int main(int argc, char **argv) { qInstallMessageHandler(messageOutput); QApplication app(argc, argv); KLocalizedString::setApplicationDomain("cuttlefish"); app.setApplicationVersion(PROJECT_VERSION); app.setDesktopFileName(QStringLiteral("org.kde.cuttlefish")); app.setOrganizationName("KDE"); app.setOrganizationDomain("org.kde"); app.setApplicationName("Cuttlefish"); const static auto _category = QStringLiteral("category"); QCommandLineOption category = QCommandLineOption(QStringList() << QStringLiteral("c") << _category, i18n("Start with category"), i18n("category")); const static auto _f = QStringLiteral("fullscreen"); QCommandLineOption fullscreen = QCommandLineOption(QStringList() << QStringLiteral("f") << _f, i18n("Start full-screen")); const static auto _p = QStringLiteral("picker"); QCommandLineOption picker = QCommandLineOption(QStringList() << QStringLiteral("p") << _p, i18n("Run in icon-picker mode")); QCommandLineParser parser; parser.addVersionOption(); parser.setApplicationDescription("Cuttlefish Icon Browser"); parser.addHelpOption(); parser.addOption(category); parser.addOption(fullscreen); parser.addOption(picker); parser.process(app); QString _cc = parser.value(category); QQmlApplicationEngine engine; KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(&engine); kdeclarative.setTranslationDomain(QStringLiteral("cuttlefish")); kdeclarative.setupContext(); kdeclarative.setupEngine(&engine); auto package = KPackage::PackageLoader::self()->loadPackage("Plasma/Generic"); package.setPath("org.kde.plasma.cuttlefish"); if (!package.isValid() || !package.metadata().isValid()) { qWarning() << "Could not load package org.kde.plasma.cuttlefish:" << package.path(); return -1; } auto iconModel = new CuttleFish::IconModel(engine.rootContext()); + auto proxyModel = new CuttleFish::SortFilterModel(engine.rootContext()); + proxyModel->setSourceModel(iconModel); + proxyModel->sort(0); auto colorSchemes = new CuttleFish::ColorSchemes(engine.rootContext()); + engine.rootContext()->setContextProperty("iconModel", iconModel); + engine.rootContext()->setContextProperty("proxyModel", proxyModel); engine.rootContext()->setContextProperty("pickerMode", parser.isSet("picker")); engine.rootContext()->setContextProperty("colorSchemes", colorSchemes); engine.load(QUrl::fromLocalFile(package.filePath("mainscript"))); if (engine.rootObjects().isEmpty()) { return -1; } #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) qmlRegisterType(); qmlRegisterType(); #else qmlRegisterAnonymousType("org.kde.plasma.sdk", 1); qmlRegisterAnonymousType("org.kde.plasma.sdk", 1); #endif return app.exec(); } diff --git a/cuttlefish/src/sortfiltermodel.cpp b/cuttlefish/src/sortfiltermodel.cpp new file mode 100644 index 0000000..29c44cd --- /dev/null +++ b/cuttlefish/src/sortfiltermodel.cpp @@ -0,0 +1,106 @@ +/*************************************************************************** + * * + * Copyright 2020 David Redondo * + * * + * 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 2 of the License, or * + * (at your option) 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, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + * * + ***************************************************************************/ + +#include "iconmodel.h" +#include "sortfiltermodel.h" + +using namespace CuttleFish; + +SortFilterModel::SortFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setSortRole(IconModel::IconName); + setSortCaseSensitivity(Qt::CaseInsensitive); +} + + +bool SortFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const +{ + + const QModelIndex sourceIndex = sourceModel()->index(source_row, 0, source_parent); + if (!(m_category.isEmpty() || m_category == QLatin1String("all")) + && (m_category != sourceIndex.data(IconModel::Category))) { + return false; + } + if (!m_filter.isEmpty()) { + return sourceIndex.data(IconModel::IconName).toString().contains(m_filter, Qt::CaseInsensitive); + } + return true; +} + +int SortFilterModel::currentIndex() +{ + const QModelIndex index = mapFromSource(m_currentSourceIndex); + if (index.isValid()) { + return index.row(); + } + m_currentSourceIndex = mapFromSource(this->index(0, 0)); + return 0; +} + +void SortFilterModel::setCurrentIndex(int index) +{ + if (mapFromSource(m_currentSourceIndex).row() != index) { + m_currentSourceIndex = mapToSource(this->index(index, 0)); + emit currentIndexChanged(); + } + +} + +QString SortFilterModel::category() const +{ + return m_category; +} + +void SortFilterModel::setCategory(const QString &category) +{ + if (category == m_category) { + return; + } + int oldIndex = currentIndex(); + m_category = category; + invalidateFilter(); + emit categoryChanged(); + if (currentIndex() != oldIndex) { + emit currentIndexChanged(); + } +} + +QString SortFilterModel::filter() const +{ + return m_filter; +} + +void SortFilterModel::setFilter(const QString& filter) +{ + if (filter == m_filter) { + return; + } + int oldIndex = currentIndex(); + m_filter = filter; + invalidateFilter(); + emit filterChanged(); + if (currentIndex() != oldIndex) { + emit currentIndexChanged(); + } +} + + diff --git a/cuttlefish/package/contents/ui/IconGrid.qml b/cuttlefish/src/sortfiltermodel.h similarity index 59% copy from cuttlefish/package/contents/ui/IconGrid.qml copy to cuttlefish/src/sortfiltermodel.h index 3d4a356..2bbc029 100644 --- a/cuttlefish/package/contents/ui/IconGrid.qml +++ b/cuttlefish/src/sortfiltermodel.h @@ -1,51 +1,57 @@ /*************************************************************************** * * - * Copyright 2014-2015 Sebastian Kügler * + * Copyright 2020 David Redondo * * * * 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 2 of the License, or * * (at your option) 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * * * ***************************************************************************/ -import QtQuick 2.5 -import QtQuick.Controls 2.5 as QQC2 -import QtQuick.Layouts 1.0 -import org.kde.kirigami 2.8 as Kirigami +#include -GridView { - id: iconGrid - focus: true +namespace CuttleFish { - cellWidth: iconSize + Math.round(Kirigami.Units.gridUnit * 1.5) - cellHeight: cellWidth + Math.round(Kirigami.Units.gridUnit * 2) +class SortFilterModel : public QSortFilterProxyModel { + Q_OBJECT + Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged) + Q_PROPERTY(QString category READ category WRITE setCategory NOTIFY categoryChanged) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) +public: + SortFilterModel(QObject *parent); - cacheBuffer: 20 - highlightMoveDuration: 0 - boundsBehavior: Flickable.StopAtBounds - model: iconModel + void setCategory(const QString &category); + QString category() const; - highlight: Item {} + void setFilter(const QString &filter); + QString filter() const; - delegate: IconGridDelegate {} + void setCurrentIndex(int index); + int currentIndex(); + +Q_SIGNALS: + void filterChanged(); + void categoryChanged(); + void currentIndexChanged(); + +private: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + + QString m_category; + QString m_filter; + QModelIndex m_currentSourceIndex; +}; - QQC2.BusyIndicator { - running: iconModel.loading - visible: running - anchors.centerIn: parent - width: Kirigami.Units.gridUnit * 8 - height: width - } }