feat(fullscreen): 全屏模式添加应用分组model,前端适配分组model
This commit is contained in:
parent
3bfb969608
commit
65bbe28fea
|
@ -49,7 +49,7 @@ set(SingleApplication "qtsingleapplication")
|
||||||
|
|
||||||
# include文件夹
|
# include文件夹
|
||||||
include_directories(src)
|
include_directories(src)
|
||||||
include_directories(src/model)
|
#include_directories(src/model)
|
||||||
include_directories(src/appdata)
|
include_directories(src/appdata)
|
||||||
include_directories(src/libappdata)
|
include_directories(src/libappdata)
|
||||||
include_directories(src/settings)
|
include_directories(src/settings)
|
||||||
|
@ -131,6 +131,7 @@ set(SOURCE_FILES
|
||||||
src/libappdata/app-list-plugin.cpp src/libappdata/app-list-plugin.h
|
src/libappdata/app-list-plugin.cpp src/libappdata/app-list-plugin.h
|
||||||
src/libappdata/app-search-plugin.cpp src/libappdata/app-search-plugin.h
|
src/libappdata/app-search-plugin.cpp src/libappdata/app-search-plugin.h
|
||||||
src/libappdata/app-category-plugin.cpp src/libappdata/app-category-plugin.h
|
src/libappdata/app-category-plugin.cpp src/libappdata/app-category-plugin.h
|
||||||
|
src/libappdata/app-group-model.cpp src/libappdata/app-group-model.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ MouseArea {
|
||||||
id: control
|
id: control
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
||||||
|
property alias displayName: labelText.text
|
||||||
|
|
||||||
// ToolTip.text: comment
|
// ToolTip.text: comment
|
||||||
// ToolTip.visible: control.containsMouse
|
// ToolTip.visible: control.containsMouse
|
||||||
// ToolTip.delay: 500
|
// ToolTip.delay: 500
|
||||||
|
@ -18,11 +20,12 @@ MouseArea {
|
||||||
radius: Platform.Theme.minRadius
|
radius: Platform.Theme.minRadius
|
||||||
useStyleTransparency: false
|
useStyleTransparency: false
|
||||||
paletteRole: Platform.Theme.Text
|
paletteRole: Platform.Theme.Text
|
||||||
alpha: control.containsPress ? 0.16 : control.containsMouse ? 0.08 : 0.00
|
alpha: control.containsPress ? 0.15 : control.containsMouse ? 0.08 : 0.00
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
UkuiItems.StyleText {
|
UkuiItems.StyleText {
|
||||||
|
id: labelText
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.leftMargin: 12
|
Layout.leftMargin: 12
|
||||||
|
@ -30,7 +33,6 @@ MouseArea {
|
||||||
horizontalAlignment: Qt.AlignLeft
|
horizontalAlignment: Qt.AlignLeft
|
||||||
verticalAlignment: Qt.AlignVCenter
|
verticalAlignment: Qt.AlignVCenter
|
||||||
font.bold: true
|
font.bold: true
|
||||||
text: section
|
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,8 @@ AppListView {
|
||||||
AppControls2.LabelItem {
|
AppControls2.LabelItem {
|
||||||
width: ListView.view.width
|
width: ListView.view.width
|
||||||
height: appListView.itemHeight
|
height: appListView.itemHeight
|
||||||
focus: true;
|
focus: true
|
||||||
|
displayName: section
|
||||||
onClicked: labelItemClicked();
|
onClicked: labelItemClicked();
|
||||||
Keys.onPressed: {
|
Keys.onPressed: {
|
||||||
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
|
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
|
||||||
|
|
|
@ -30,10 +30,14 @@ UkuiItems.StyleBackground {
|
||||||
|
|
||||||
width: count > 0 ? (childrenRect.width + maring*2) : 0
|
width: count > 0 ? (childrenRect.width + maring*2) : 0
|
||||||
|
|
||||||
useStyleTransparency: false
|
|
||||||
paletteRole: Platform.Theme.Text
|
|
||||||
radius: height/2
|
radius: height/2
|
||||||
alpha: 0.06
|
alpha: 0.03
|
||||||
|
useStyleTransparency: false
|
||||||
|
paletteRole: Platform.Theme.WindowText
|
||||||
|
|
||||||
|
border.width: 1
|
||||||
|
borderAlpha: 0.06
|
||||||
|
borderColor: Platform.Theme.WindowText
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: mainLayout
|
id: mainLayout
|
||||||
|
@ -54,9 +58,11 @@ UkuiItems.StyleBackground {
|
||||||
ToolTip.visible: modelData.toolTip !== "" && containsMouse
|
ToolTip.visible: modelData.toolTip !== "" && containsMouse
|
||||||
|
|
||||||
background.radius: width / 2
|
background.radius: width / 2
|
||||||
background.alpha: modelData.checked ? 0.75 : containsMouse ? 0.4 : 0
|
background.paletteRole: Platform.Theme.Highlight
|
||||||
|
background.alpha: modelData.checked ? 1 : containsMouse ? 0.3 : 0
|
||||||
|
|
||||||
icon.source: modelData.icon
|
icon.source: modelData.icon
|
||||||
icon.mode: UkuiItems.Icon.AutoHighlight
|
icon.mode: modelData.checked ? UkuiItems.Icon.Highlight : UkuiItems.Icon.AutoHighlight
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
// 执行action
|
// 执行action
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024, KylinSoft Co., Ltd.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* (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, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authors: hxf <hewenfei@kylinos.cn>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
|
import org.ukui.quick.items 1.0 as UkuiItems
|
||||||
|
import org.ukui.quick.platform 1.0 as Platform
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: root
|
||||||
|
property alias icon: appIcon
|
||||||
|
property alias text: appName
|
||||||
|
property alias background: styleBackground
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
UkuiItems.StyleBackground {
|
||||||
|
id: styleBackground
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Platform.Theme.normalRadius
|
||||||
|
useStyleTransparency: false
|
||||||
|
paletteRole: Platform.Theme.WindowText
|
||||||
|
alpha: root.containsPress ? 0.15 : root.containsMouse ? 0.08 : 0
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 16
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
UkuiItems.Icon {
|
||||||
|
id: appIcon
|
||||||
|
Layout.minimumWidth: 32
|
||||||
|
Layout.minimumHeight: 32
|
||||||
|
Layout.maximumWidth: 96
|
||||||
|
Layout.maximumHeight: 96
|
||||||
|
|
||||||
|
Layout.preferredWidth: 96
|
||||||
|
Layout.preferredHeight: 96
|
||||||
|
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
UkuiItems.StyleText {
|
||||||
|
id: appName
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
elide: Text.ElideRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
paletteRole: Platform.Theme.Text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024, KylinSoft Co., Ltd.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* (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, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authors: hxf <hewenfei@kylinos.cn>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQml.Models 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
|
import org.ukui.menu.core 1.0
|
||||||
|
import AppControls2 1.0 as AppControls2
|
||||||
|
|
||||||
|
import org.ukui.quick.items 1.0 as UkuiItems
|
||||||
|
import org.ukui.quick.platform 1.0 as Platform
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: root
|
||||||
|
property int itemHeight: 40
|
||||||
|
property alias sourceModel: appGroupModel.sourceModel
|
||||||
|
|
||||||
|
spacing: 5
|
||||||
|
clip: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
||||||
|
model: AppGroupModel {
|
||||||
|
id: appGroupModel
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
width: ListView.view.width
|
||||||
|
height: childrenRect.height
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
AppControls2.LabelItem {
|
||||||
|
width: parent.width
|
||||||
|
height: root.itemHeight
|
||||||
|
displayName: model.name
|
||||||
|
}
|
||||||
|
|
||||||
|
GridView {
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
// TODO: 动态计算尺寸
|
||||||
|
cellWidth: width / 8
|
||||||
|
cellHeight: cellWidth
|
||||||
|
interactive: false
|
||||||
|
|
||||||
|
model: DelegateModel {
|
||||||
|
model: appGroupModel
|
||||||
|
rootIndex: modelIndex(index)
|
||||||
|
delegate: Item {
|
||||||
|
width: GridView.view.cellWidth
|
||||||
|
height: GridView.view.cellHeight
|
||||||
|
|
||||||
|
FullScreenAppItem {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 12
|
||||||
|
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
|
||||||
|
text.text: model.name
|
||||||
|
icon.source: model.icon
|
||||||
|
|
||||||
|
onClicked: (event) => {
|
||||||
|
if (event.button === Qt.LeftButton) {
|
||||||
|
// openApplication
|
||||||
|
appManager.launchApp(id);
|
||||||
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
|
// appListView.model.openMenu(index, MenuInfo.FullScreen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,12 @@
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import org.ukui.quick.platform 1.0 as Platform
|
import QtQml.Models 2.12
|
||||||
import org.ukui.quick.items 1.0 as UkuiItems
|
|
||||||
import org.ukui.menu.core 1.0
|
import org.ukui.menu.core 1.0
|
||||||
|
import AppControls2 1.0 as AppControls2
|
||||||
|
|
||||||
|
import org.ukui.quick.items 1.0 as UkuiItems
|
||||||
|
import org.ukui.quick.platform 1.0 as Platform
|
||||||
|
|
||||||
UkuiItems.StyleBackground {
|
UkuiItems.StyleBackground {
|
||||||
paletteRole: Platform.Theme.Dark
|
paletteRole: Platform.Theme.Dark
|
||||||
|
@ -23,6 +27,7 @@ UkuiItems.StyleBackground {
|
||||||
Item {
|
Item {
|
||||||
id: mainContainer
|
id: mainContainer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
z: 10
|
||||||
|
|
||||||
AppPageBackend {
|
AppPageBackend {
|
||||||
id: appPageBackend
|
id: appPageBackend
|
||||||
|
@ -81,10 +86,6 @@ UkuiItems.StyleBackground {
|
||||||
appPageBackend.startSearch(text);
|
appPageBackend.startSearch(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation { duration: 300; easing.type: Easing.InOutCubic }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,21 +115,13 @@ UkuiItems.StyleBackground {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用列表: [row: 1, column: 1]
|
// 应用列表: [row: 1, column: 1]
|
||||||
Item {
|
FullScreenAppList {
|
||||||
Layout.row: 1
|
Layout.row: 1
|
||||||
Layout.column: 1
|
Layout.column: 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
AppList {
|
sourceModel: appPageBackend.appModel
|
||||||
anchors.fill: parent
|
|
||||||
visible: true
|
|
||||||
model: appPageBackend.appModel
|
|
||||||
|
|
||||||
view.onContentYChanged: {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,4 +13,6 @@ PluginSelectButton 1.0 PluginSelectButton.qml
|
||||||
FullScreenHeader 1.0 FullScreenHeader.qml
|
FullScreenHeader 1.0 FullScreenHeader.qml
|
||||||
FullScreenContent 1.0 FullScreenContent.qml
|
FullScreenContent 1.0 FullScreenContent.qml
|
||||||
FullScreenFooter 1.0 FullScreenFooter.qml
|
FullScreenFooter 1.0 FullScreenFooter.qml
|
||||||
|
FullScreenAppList 1.0 FullScreenAppList.qml
|
||||||
|
FullScreenAppItem 1.0 FullScreenAppItem.qml
|
||||||
Folder 1.0 Folder.qml
|
Folder 1.0 Folder.qml
|
||||||
|
|
|
@ -36,5 +36,7 @@
|
||||||
<file>AppUI/EditText.qml</file>
|
<file>AppUI/EditText.qml</file>
|
||||||
<file>AppUI/Folder.qml</file>
|
<file>AppUI/Folder.qml</file>
|
||||||
<file>AppUI/AppPageSearch.qml</file>
|
<file>AppUI/AppPageSearch.qml</file>
|
||||||
|
<file>AppUI/FullScreenAppList.qml</file>
|
||||||
|
<file>AppUI/FullScreenAppItem.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -0,0 +1,348 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024, KylinSoft Co., Ltd.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* (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, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authors: hxf <hewenfei@kylinos.cn>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "app-group-model.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
namespace UkuiMenu {
|
||||||
|
|
||||||
|
AppGroupModel::AppGroupModel(QObject *parent) : QAbstractProxyModel(parent)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex AppGroupModel::index(int row, int column, const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (column != 0 || row < 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过parent找到所属的组,将组的信息放入index附带数据中,作为判断依据
|
||||||
|
if (parent.isValid()) {
|
||||||
|
const auto subItems = m_groups.at(parent.row());
|
||||||
|
if (row >= subItems->size()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return createIndex(row, column, subItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
return createIndex(row, column, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex AppGroupModel::parent(const QModelIndex &child) const
|
||||||
|
{
|
||||||
|
if (!child.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto subItems = static_cast<QVector<int>*>(child.internalPointer());
|
||||||
|
if (subItems) {
|
||||||
|
return createIndex(m_groups.indexOf(subItems), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppGroupModel::hasChildren(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (!sourceModel()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// root
|
||||||
|
if (!parent.isValid()) {
|
||||||
|
return !m_groups.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// child, 两层
|
||||||
|
if (parent.parent().isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AppGroupModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (!sourceModel()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// root
|
||||||
|
if (!parent.isValid()) {
|
||||||
|
return m_groups.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.parent().isValid()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = parent.row();
|
||||||
|
if (i < 0 || i >= m_groups.size()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_groups.at(i)->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
int AppGroupModel::columnCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> AppGroupModel::roleNames() const
|
||||||
|
{
|
||||||
|
return QAbstractItemModel::roleNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGroupModel::setSourceModel(QAbstractItemModel *sourceModel)
|
||||||
|
{
|
||||||
|
if (AppGroupModel::sourceModel() == sourceModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
if (AppGroupModel::sourceModel()) {
|
||||||
|
AppGroupModel::sourceModel()->disconnect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAbstractProxyModel::setSourceModel(sourceModel);
|
||||||
|
|
||||||
|
if (sourceModel) {
|
||||||
|
rebuildAppGroups();
|
||||||
|
|
||||||
|
connect(sourceModel, &QAbstractItemModel::dataChanged, this, &AppGroupModel::onDataChanged);
|
||||||
|
connect(sourceModel, &QAbstractItemModel::layoutChanged, this, &AppGroupModel::onLayoutChanged);
|
||||||
|
connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &AppGroupModel::onRowsInserted);
|
||||||
|
connect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &AppGroupModel::onRowsAboutToBeRemoved);
|
||||||
|
connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, [=] (const QModelIndex &parent, int first, int last) {
|
||||||
|
if (m_needRebuild) {
|
||||||
|
beginResetModel();
|
||||||
|
rebuildAppGroups();
|
||||||
|
endResetModel();
|
||||||
|
m_needRebuild = false;
|
||||||
|
|
||||||
|
} else if (!parent.isValid()) {
|
||||||
|
reLocationIndex(first, -(last - first + 1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(sourceModel, &QAbstractItemModel::modelReset, this, [=] {
|
||||||
|
beginResetModel();
|
||||||
|
rebuildAppGroups();
|
||||||
|
endResetModel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex AppGroupModel::mapToSource(const QModelIndex &proxyIndex) const
|
||||||
|
{
|
||||||
|
if (!sourceModel() || !proxyIndex.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto subItems = static_cast<QVector<int>*>(proxyIndex.internalPointer());
|
||||||
|
if (subItems) {
|
||||||
|
int idx = subItems->at(proxyIndex.row());
|
||||||
|
return sourceModel()->index(idx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex AppGroupModel::mapFromSource(const QModelIndex &sourceIndex) const
|
||||||
|
{
|
||||||
|
if (!sourceModel() || !sourceIndex.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int groupIndex = findGroupIndex(sourceIndex);
|
||||||
|
if (groupIndex < 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int tempIndex = m_groups.at(groupIndex)->indexOf(sourceIndex.row());
|
||||||
|
return index(tempIndex, 0, index(groupIndex, 0, QModelIndex()));
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant AppGroupModel::data(const QModelIndex &proxyIndex, int role) const
|
||||||
|
{
|
||||||
|
if (!checkIndex(proxyIndex, CheckIndexOption::IndexIsValid)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proxyIndex.parent().isValid()) {
|
||||||
|
return QAbstractProxyModel::data(proxyIndex, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == DataEntity::Name) {
|
||||||
|
return sourceModel()->index(m_groups.at(proxyIndex.row())->first(), 0).data(DataEntity::Group);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGroupModel::rebuildAppGroups()
|
||||||
|
{
|
||||||
|
qDeleteAll(m_groups);
|
||||||
|
m_groups.clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < sourceModel()->rowCount(); ++i) {
|
||||||
|
int groupIndex = findGroupIndex(sourceModel()->index(i, 0));
|
||||||
|
QVector<int> *subItems {nullptr};
|
||||||
|
if (groupIndex < 0) {
|
||||||
|
subItems = new QVector<int>();
|
||||||
|
m_groups.append(subItems);
|
||||||
|
} else {
|
||||||
|
subItems = m_groups[groupIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
subItems->append(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int AppGroupModel::findGroupIndex(const QModelIndex &sourceIndex) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_groups.size(); ++i) {
|
||||||
|
const QVector<int> *subItems = m_groups.at(i);
|
||||||
|
if (subItems->isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用group属性进行分组
|
||||||
|
QString groupA = sourceIndex.data(DataEntity::Group).toString();
|
||||||
|
QString groupB = sourceModel()->index(subItems->at(0), 0).data(DataEntity::Group).toString();
|
||||||
|
if (groupA == groupB) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGroupModel::insertApp(int groupIndex, const QModelIndex &sourceIndex)
|
||||||
|
{
|
||||||
|
if (groupIndex < 0 || groupIndex >= m_groups.size()) {
|
||||||
|
beginInsertRows(QModelIndex(), m_groups.size(), m_groups.size());
|
||||||
|
m_groups.append(new QVector<int>(1, sourceIndex.row()));
|
||||||
|
endInsertRows();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
int newItem = sourceIndex.row();
|
||||||
|
QVector<int> *subItems = m_groups[groupIndex];
|
||||||
|
for (; index < subItems->size(); ++index) {
|
||||||
|
if (newItem < subItems->at(index)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beginInsertRows(AppGroupModel::index(groupIndex, 0, QModelIndex()), index, index);
|
||||||
|
subItems->insert(index, newItem);
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
// slots
|
||||||
|
void AppGroupModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
|
||||||
|
{
|
||||||
|
for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
|
||||||
|
QModelIndex proxyIndex = mapFromSource(sourceModel()->index(i, 0));
|
||||||
|
Q_EMIT dataChanged(proxyIndex, proxyIndex, roles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGroupModel::onLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
|
||||||
|
{
|
||||||
|
Q_UNUSED(parents)
|
||||||
|
Q_UNUSED(hint)
|
||||||
|
beginResetModel();
|
||||||
|
rebuildAppGroups();
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGroupModel::onRowsInserted(const QModelIndex &parent, int first, int last)
|
||||||
|
{
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first < (sourceModel()->rowCount() - 1)) {
|
||||||
|
reLocationIndex(first, (last - first + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = first; i <= last; ++i) {
|
||||||
|
QModelIndex sourceIndex = sourceModel()->index(i, 0, parent);
|
||||||
|
insertApp(findGroupIndex(sourceIndex), sourceIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGroupModel::reLocationIndex(int base, int offset)
|
||||||
|
{
|
||||||
|
for (QVector<int> *group : m_groups) {
|
||||||
|
QMutableVectorIterator<int> it(*group);
|
||||||
|
while (it.hasNext()) {
|
||||||
|
const int &value = it.next();
|
||||||
|
if (value >= base) {
|
||||||
|
it.setValue(value + offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGroupModel::onRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
|
||||||
|
{
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int groupIndex = 0, itemIndex = 0;
|
||||||
|
for (int i = first; i <= last; ++i) {
|
||||||
|
groupIndex = findGroupIndex(sourceModel()->index(i, 0));
|
||||||
|
if (groupIndex < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto subItems = m_groups[groupIndex];
|
||||||
|
itemIndex = subItems->indexOf(i);
|
||||||
|
if (itemIndex < 0) {
|
||||||
|
// 如果出现错误,那么重新构建映射关系
|
||||||
|
m_needRebuild = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除组里的元素
|
||||||
|
beginRemoveRows(index(groupIndex, 0, QModelIndex()), itemIndex, itemIndex);
|
||||||
|
subItems->removeAt(itemIndex);
|
||||||
|
endRemoveRows();
|
||||||
|
|
||||||
|
// 删除组
|
||||||
|
if (subItems->isEmpty()) {
|
||||||
|
beginRemoveRows(QModelIndex(), groupIndex, groupIndex);
|
||||||
|
delete m_groups.takeAt(groupIndex);
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // UkuiMenu
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024, KylinSoft Co., Ltd.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* (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, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authors: hxf <hewenfei@kylinos.cn>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef UKUI_MENU_APP_GROUP_MODEL_H
|
||||||
|
#define UKUI_MENU_APP_GROUP_MODEL_H
|
||||||
|
|
||||||
|
#include <QAbstractProxyModel>
|
||||||
|
#include "data-entity.h"
|
||||||
|
|
||||||
|
namespace UkuiMenu {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class AppGroupModel
|
||||||
|
*
|
||||||
|
* 根据app的group属性进行分组,将同一组的应用挂在到某一个索引下
|
||||||
|
*/
|
||||||
|
class AppGroupModel : public QAbstractProxyModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
// Q_PROPERTY(UkuiMenu::DataEntity::PropertyName sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged)
|
||||||
|
public:
|
||||||
|
explicit AppGroupModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void setSourceModel(QAbstractItemModel *sourceModel) override;
|
||||||
|
|
||||||
|
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
|
||||||
|
QModelIndex parent(const QModelIndex &child) const override;
|
||||||
|
|
||||||
|
bool hasChildren(const QModelIndex &parent) const override;
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent) const override;
|
||||||
|
int columnCount(const QModelIndex &parent) const override;
|
||||||
|
|
||||||
|
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
|
||||||
|
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
|
||||||
|
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
QVariant data(const QModelIndex &proxyIndex, int role) const override;
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles);
|
||||||
|
void onLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint);
|
||||||
|
void onRowsInserted(const QModelIndex &parent, int first, int last);
|
||||||
|
void onRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void rebuildAppGroups();
|
||||||
|
void reLocationIndex(int base, int offset);
|
||||||
|
int findGroupIndex(const QModelIndex &sourceIndex) const;
|
||||||
|
void insertApp(int groupIndex, const QModelIndex &sourceIndex);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 存储分组信息
|
||||||
|
QVector<QVector<int>*> m_groups;
|
||||||
|
bool m_needRebuild {false};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // UkuiMenu
|
||||||
|
|
||||||
|
#endif //UKUI_MENU_APP_GROUP_MODEL_H
|
|
@ -163,7 +163,8 @@ void CombinedListModel::insertSubModel(QAbstractItemModel *subModel, int index)
|
||||||
m_subModels.insert(index, {subModel, subModel->rowCount()});
|
m_subModels.insert(index, {subModel, subModel->rowCount()});
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(subModel, &QAbstractItemModel::rowsRemoved,
|
// 在删除前通知上层model进行处理
|
||||||
|
connect(subModel, &QAbstractItemModel::rowsAboutToBeRemoved,
|
||||||
this, [subModel, this] (const QModelIndex &parent, int first, int last) {
|
this, [subModel, this] (const QModelIndex &parent, int first, int last) {
|
||||||
int offset = offsetOfSubModel(subModel);
|
int offset = offsetOfSubModel(subModel);
|
||||||
if (offset >= 0) {
|
if (offset >= 0) {
|
||||||
|
@ -241,15 +242,13 @@ void CombinedListModel::removeSubModel(QAbstractItemModel *subModel)
|
||||||
|
|
||||||
int CombinedListModel::indexOfSubModel(QAbstractItemModel *subModel)
|
int CombinedListModel::indexOfSubModel(QAbstractItemModel *subModel)
|
||||||
{
|
{
|
||||||
int index = -1;
|
|
||||||
for (int i = 0; i < m_subModels.size(); ++i) {
|
for (int i = 0; i < m_subModels.size(); ++i) {
|
||||||
if (m_subModels.at(i).first == subModel) {
|
if (m_subModels.at(i).first == subModel) {
|
||||||
index = i;
|
return i;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return index;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // UkuiMenu
|
} // UkuiMenu
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include "sidebar-button-utils.h"
|
#include "sidebar-button-utils.h"
|
||||||
#include "extension/widget-model.h"
|
#include "extension/widget-model.h"
|
||||||
#include "app-page-backend.h"
|
#include "app-page-backend.h"
|
||||||
|
#include "app-group-model.h"
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
|
@ -64,6 +65,7 @@ void UkuiMenuApplication::registerQmlTypes()
|
||||||
SidebarButtonUtils::defineModule(uri, versionMajor, versionMinor);
|
SidebarButtonUtils::defineModule(uri, versionMajor, versionMinor);
|
||||||
|
|
||||||
qmlRegisterType<WidgetModel>(uri, versionMajor, versionMinor, "WidgetModel");
|
qmlRegisterType<WidgetModel>(uri, versionMajor, versionMinor, "WidgetModel");
|
||||||
|
qmlRegisterType<AppGroupModel>(uri, versionMajor, versionMinor, "AppGroupModel");
|
||||||
qmlRegisterType<AppPageBackend>(uri, versionMajor, versionMinor, "AppPageBackend");
|
qmlRegisterType<AppPageBackend>(uri, versionMajor, versionMinor, "AppPageBackend");
|
||||||
qmlRegisterUncreatableType<AppListPluginGroup>(uri, versionMajor, versionMinor, "PluginGroup", "Use enums only.");
|
qmlRegisterUncreatableType<AppListPluginGroup>(uri, versionMajor, versionMinor, "PluginGroup", "Use enums only.");
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue