diff --git a/containments/desktop/package/contents/ui/ConfigFilter.qml b/containments/desktop/package/contents/ui/ConfigFilter.qml index 497fc7426..ce753cc3a 100644 --- a/containments/desktop/package/contents/ui/ConfigFilter.qml +++ b/containments/desktop/package/contents/ui/ConfigFilter.qml @@ -1,166 +1,238 @@ /*************************************************************************** * Copyright (C) 2014 by Eike Hein * * * * 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.0 import QtQuick.Controls 1.0 import QtQuick.Layouts 1.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.private.desktopcontainment.folder 0.1 as Folder Item { id: configIcons width: childrenRect.width height: childrenRect.height property alias cfg_filterMode: filterMode.currentIndex property alias cfg_filterPattern: filterPattern.text property alias cfg_filterMimeTypes: mimeTypesModel.checkedTypes PlasmaCore.SortFilterModel { id: filderedMimeTypesModel sourceModel: Folder.MimeTypesModel { id: mimeTypesModel } // SortFilterModel doesn't have a case-sensitivity option // but filterRegExp always causes case-insensitive sorting. filterRegExp: mimeFilter.text - filterRole: "display" + filterRole: "name" + + sortRole: mimeTypesView.getColumn(mimeTypesView.sortIndicatorColumn).role + sortOrder: mimeTypesView.sortIndicatorOrder } ColumnLayout { width: parent.width height: parent.height ComboBox { id: filterMode Layout.fillWidth: true model: [i18n("Show All Files"), i18n("Show Files Matching"), i18n("Hide Files Matching")] } Label { Layout.fillWidth: true text: i18n("File name pattern:") } TextField { id: filterPattern Layout.fillWidth: true enabled: (filterMode.currentIndex > 0) } Label { Layout.fillWidth: true text: i18n("File types:") } TextField { id: mimeFilter Layout.fillWidth: true enabled: (filterMode.currentIndex > 0) placeholderText: i18n("Search file type...") } RowLayout { Layout.fillWidth: true Layout.fillHeight: true - ScrollView { + CheckBox { // Purely for metrics. + id: metricsCheckBox + visible: false + } + + TableView { + id: mimeTypesView + + // Signal the delegates listen to when user presses space to toggle current row. + signal toggleCurrent + Layout.fillWidth: true Layout.fillHeight: true - frameVisible: true - enabled: (filterMode.currentIndex > 0) - ListView { - model: filderedMimeTypesModel - - delegate: RowLayout { - CheckBox { - Layout.maximumWidth: 18 // FIXME HACK: Use actual radio button width. + model: filderedMimeTypesModel - checked: model.checked - onCheckedChanged: model.checked = checked - } + sortIndicatorVisible: true + sortIndicatorColumn: 2 // Default to sort by "File type". - PlasmaCore.IconItem { - anchors.verticalCenter: parent.verticalCenter + onSortIndicatorColumnChanged: { // Disallow sorting by icon. + if (sortIndicatorColumn === 1) { + sortIndicatorColumn = 2; + } + } - width: units.iconSizes.small - height: units.iconSizes.small + Keys.onSpacePressed: toggleCurrent() - Layout.maximumWidth: width - Layout.maximumHeight: height + function adjustColumns() { + // Resize description column to take whatever space is left. + var width = viewport.width; + for (var i = 0; i < columnCount - 1; ++i) { + width -= getColumn(i).width; + } + descriptionColumn.width = width; + } - source: model.decoration + onWidthChanged: adjustColumns() + // Component.onCompleted is too early to do this... + onRowCountChanged: adjustColumns() + + TableViewColumn { + role: "checked" + width: metricsCheckBox.width + resizable: false + movable: false + + delegate: CheckBox { + id: checkBox + + checked: styleData.value + activeFocusOnTab: false // only let the TableView as a whole get focus + onClicked: { + model.checked = checked + // Clicking it breaks the binding to the model value which becomes + // an issue during sorting as TableView re-uses delegates. + checked = Qt.binding(function() { + return styleData.value; + }); } - Label { - Layout.fillWidth: true - - text: model.display + Connections { + target: mimeTypesView + onToggleCurrent: { + if (styleData.row === mimeTypesView.currentRow) { + model.checked = !checkBox.checked + } + } } } } + + TableViewColumn { + role: "decoration" + width: units.iconSizes.small + resizable: false + movable: false + + delegate: PlasmaCore.IconItem { + width: units.iconSizes.small + height: units.iconSizes.small + animated: false // TableView re-uses delegates, avoid animation when sorting/filtering. + source: styleData.value + } + } + + TableViewColumn { + id: nameColumn + role: "name" + title: i18n("File type") + width: units.gridUnit * 10 // Assume somewhat reasonable default for mime type name. + onWidthChanged: mimeTypesView.adjustColumns() + movable: false + } + TableViewColumn { + id: descriptionColumn + role: "comment" + title: i18n("Description") + movable: false + resizable: false + } } ColumnLayout { Layout.alignment: Qt.AlignTop + // Need to explicitly base the size off the button's implicitWidth + // to avoid the column from growing way too wide due to fillWidth... + Layout.maximumWidth: Math.max(selectAllButton.implicitWidth, deselectAllButton.implicitWidth) Button { + id: selectAllButton Layout.fillWidth: true enabled: (filterMode.currentIndex > 0) text: i18n("Select All") onClicked: { mimeTypesModel.checkAll(); } } Button { + id: deselectAllButton Layout.fillWidth: true enabled: (filterMode.currentIndex > 0) text: i18n("Deselect All") onClicked: { mimeTypesModel.checkedTypes = ""; } } } } } } diff --git a/containments/desktop/plugins/folder/mimetypesmodel.cpp b/containments/desktop/plugins/folder/mimetypesmodel.cpp index 3254396e7..6701e1f0a 100644 --- a/containments/desktop/plugins/folder/mimetypesmodel.cpp +++ b/containments/desktop/plugins/folder/mimetypesmodel.cpp @@ -1,149 +1,152 @@ /*************************************************************************** * Copyright (C) 2014 by Eike Hein * * * * 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 "mimetypesmodel.h" #include static bool lessThan(const QMimeType &a, const QMimeType &b) { return QString::localeAwareCompare(a.name(), b.name()) < 0; } MimeTypesModel::MimeTypesModel(QObject *parent) : QAbstractListModel(parent) { QMimeDatabase db; m_mimeTypesList = db.allMimeTypes(); qStableSort(m_mimeTypesList.begin(), m_mimeTypesList.end(), lessThan); m_checkedRows = QVector(m_mimeTypesList.size(), false); } MimeTypesModel::~MimeTypesModel() { } QHash MimeTypesModel::roleNames() const { return { - { Qt::DisplayRole, "display" }, + { Qt::DisplayRole, "comment" }, + { Qt::UserRole, "name" }, { Qt::DecorationRole, "decoration" }, { Qt::CheckStateRole, "checked" } }; } QVariant MimeTypesModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= m_mimeTypesList.size()) { return QVariant(); } switch (role) { case Qt::DisplayRole: + return m_mimeTypesList.at(index.row()).comment(); + case Qt::UserRole: return m_mimeTypesList.at(index.row()).name(); case Qt::DecorationRole: { QString icon = m_mimeTypesList.at(index.row()).iconName(); if (icon.isEmpty()) { icon = m_mimeTypesList.at(index.row()).genericIconName(); } return icon; } case Qt::CheckStateRole: return m_checkedRows.at(index.row()) ? Qt::Checked : Qt::Unchecked; } return QVariant(); } bool MimeTypesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.row() < 0 || index.row() >= m_mimeTypesList.size()) { return false; } if (role == Qt::CheckStateRole) { const bool newChecked = value.toBool(); if (m_checkedRows.at(index.row()) != newChecked) { m_checkedRows[index.row()] = newChecked; emit dataChanged(index, index, {role}); emit checkedTypesChanged(); return true; } } return false; } void MimeTypesModel::checkAll() { m_checkedRows = QVector(m_mimeTypesList.size(), true); emit dataChanged(index(0, 0), index(m_mimeTypesList.size() - 1, 0), {Qt::CheckStateRole}); emit checkedTypesChanged(); } int MimeTypesModel::indexOfType(const QString &name) const { for (int i = 0; i < m_mimeTypesList.size(); i++) { if (m_mimeTypesList.at(i).name() == name) { return i; } } return -1; } QStringList MimeTypesModel::checkedTypes() const { QStringList list; for (int i =0; i < m_checkedRows.size(); ++i) { if (m_checkedRows.at(i)) { list.append(m_mimeTypesList.at(i).name()); } } if (!list.isEmpty()) { return list; } return QStringList(QLatin1String("")); } void MimeTypesModel::setCheckedTypes(const QStringList &list) { m_checkedRows = QVector(m_mimeTypesList.size(), false); foreach (const QString &name, list) { const int row = indexOfType(name); if (row != -1) { m_checkedRows[row] = true; } } emit dataChanged(index(0, 0), index(m_mimeTypesList.size() - 1, 0), {Qt::CheckStateRole}); emit checkedTypesChanged(); }