mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-28 22:53:09 +00:00
Currently delegates do not get deleted when removed from models. Due to the complexity of drag state and other objects, we can't simply delete the delegate when removed from the model because they might still be involved in animations or other state at the time of removal. Use smart pointers to have the delegate objects deleted instead. QML unfortunately doesn't support QSharedPointer, so we need to expose raw pointers to it.
484 lines
15 KiB
C++
484 lines
15 KiB
C++
// SPDX-FileCopyrightText: 2022-2023 Devin Lin <devin@kde.org>
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "folioapplicationfolder.h"
|
|
#include "homescreenstate.h"
|
|
|
|
#include <QJsonArray>
|
|
#include <algorithm>
|
|
|
|
FolioApplicationFolder::FolioApplicationFolder(HomeScreen *parent, QString name)
|
|
: QObject{parent}
|
|
, m_homeScreen{parent}
|
|
, m_name{name}
|
|
, m_applicationFolderModel{new ApplicationFolderModel{this}}
|
|
{
|
|
}
|
|
|
|
FolioApplicationFolder::Ptr FolioApplicationFolder::fromJson(QJsonObject &obj, HomeScreen *parent)
|
|
{
|
|
QString name = obj[QStringLiteral("name")].toString();
|
|
QList<FolioApplication::Ptr> apps;
|
|
for (auto storageId : obj[QStringLiteral("apps")].toArray()) {
|
|
if (KService::Ptr service = KService::serviceByStorageId(storageId.toString())) {
|
|
apps.append(std::make_shared<FolioApplication>(parent, service));
|
|
}
|
|
}
|
|
|
|
FolioApplicationFolder::Ptr folder = std::make_shared<FolioApplicationFolder>(parent, name);
|
|
folder->setApplications(apps);
|
|
return folder;
|
|
}
|
|
|
|
QJsonObject FolioApplicationFolder::toJson() const
|
|
{
|
|
QJsonObject obj;
|
|
obj[QStringLiteral("type")] = "folder";
|
|
obj[QStringLiteral("name")] = m_name;
|
|
|
|
QJsonArray arr;
|
|
for (auto delegate : m_delegates) {
|
|
if (delegate.delegate->type() != FolioDelegate::Application) {
|
|
continue;
|
|
}
|
|
arr.append(QJsonValue::fromVariant(delegate.delegate->application()->storageId()));
|
|
}
|
|
|
|
obj[QStringLiteral("apps")] = arr;
|
|
|
|
return obj;
|
|
}
|
|
|
|
QString FolioApplicationFolder::name() const
|
|
{
|
|
return m_name;
|
|
}
|
|
|
|
void FolioApplicationFolder::setName(QString &name)
|
|
{
|
|
m_name = name;
|
|
Q_EMIT nameChanged();
|
|
Q_EMIT saveRequested();
|
|
}
|
|
|
|
QList<FolioApplication *> FolioApplicationFolder::appPreviews()
|
|
{
|
|
QList<FolioApplication *> previews;
|
|
// we give a maximum of 4 icons
|
|
for (int i = 0; i < std::min<int>(m_delegates.size(), 4); ++i) {
|
|
if (!m_delegates[i].delegate->application()) {
|
|
continue;
|
|
}
|
|
previews.push_back(m_delegates[i].delegate->application().get());
|
|
}
|
|
return previews;
|
|
}
|
|
|
|
ApplicationFolderModel *FolioApplicationFolder::applications()
|
|
{
|
|
return m_applicationFolderModel;
|
|
}
|
|
|
|
void FolioApplicationFolder::setApplications(QList<FolioApplication::Ptr> applications)
|
|
{
|
|
if (m_applicationFolderModel) {
|
|
m_applicationFolderModel->deleteLater();
|
|
}
|
|
|
|
m_delegates.clear();
|
|
for (FolioApplication::Ptr app : applications) {
|
|
m_delegates.append({FolioDelegate::Ptr{new FolioDelegate{app, m_homeScreen}}, 0, 0, 0});
|
|
}
|
|
m_applicationFolderModel = new ApplicationFolderModel{this};
|
|
m_applicationFolderModel->evaluateDelegateIndexes();
|
|
|
|
Q_EMIT applicationsChanged();
|
|
Q_EMIT applicationsReset();
|
|
Q_EMIT saveRequested();
|
|
}
|
|
|
|
void FolioApplicationFolder::moveEntry(int fromRow, int toRow)
|
|
{
|
|
m_applicationFolderModel->moveEntry(fromRow, toRow);
|
|
}
|
|
|
|
bool FolioApplicationFolder::addDelegate(FolioDelegate::Ptr delegate, int row)
|
|
{
|
|
return m_applicationFolderModel->addDelegate(delegate, row);
|
|
}
|
|
|
|
void FolioApplicationFolder::removeDelegate(int row)
|
|
{
|
|
m_applicationFolderModel->removeDelegate(row);
|
|
}
|
|
|
|
int FolioApplicationFolder::dropInsertPosition(int page, qreal x, qreal y)
|
|
{
|
|
return m_applicationFolderModel->dropInsertPosition(page, x, y);
|
|
}
|
|
|
|
bool FolioApplicationFolder::isDropPositionOutside(qreal x, qreal y)
|
|
{
|
|
return m_applicationFolderModel->isDropPositionOutside(x, y);
|
|
}
|
|
|
|
ApplicationFolderModel::ApplicationFolderModel(FolioApplicationFolder *parent)
|
|
: QAbstractListModel{parent}
|
|
, m_folder{parent}
|
|
{
|
|
HomeScreenState *homeScreenState = m_folder->m_homeScreen->homeScreenState();
|
|
connect(homeScreenState, &HomeScreenState::folderPageWidthChanged, this, [this]() {
|
|
evaluateDelegateIndexes();
|
|
});
|
|
connect(homeScreenState, &HomeScreenState::folderPageHeightChanged, this, [this]() {
|
|
evaluateDelegateIndexes();
|
|
});
|
|
connect(homeScreenState, &HomeScreenState::folderPageContentWidthChanged, this, [this]() {
|
|
evaluateDelegateIndexes();
|
|
});
|
|
connect(homeScreenState, &HomeScreenState::folderPageContentHeightChanged, this, [this]() {
|
|
evaluateDelegateIndexes();
|
|
});
|
|
connect(homeScreenState, &HomeScreenState::viewWidthChanged, this, [this]() {
|
|
evaluateDelegateIndexes();
|
|
});
|
|
connect(homeScreenState, &HomeScreenState::viewHeightChanged, this, [this]() {
|
|
evaluateDelegateIndexes();
|
|
});
|
|
connect(homeScreenState, &HomeScreenState::pageCellWidthChanged, this, [this]() {
|
|
evaluateDelegateIndexes();
|
|
});
|
|
connect(homeScreenState, &HomeScreenState::pageCellHeightChanged, this, [this]() {
|
|
evaluateDelegateIndexes();
|
|
});
|
|
}
|
|
|
|
int ApplicationFolderModel::rowCount(const QModelIndex & /*parent*/) const
|
|
{
|
|
return m_folder->m_delegates.size();
|
|
}
|
|
|
|
QVariant ApplicationFolderModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return QVariant();
|
|
}
|
|
|
|
switch (role) {
|
|
case DelegateRole:
|
|
return QVariant::fromValue(m_folder->m_delegates.at(index.row()).delegate.get());
|
|
case columnIndexRole:
|
|
return QVariant::fromValue(m_folder->m_delegates.at(index.row()).columnIndex);
|
|
case rowIndexRole:
|
|
return QVariant::fromValue(m_folder->m_delegates.at(index.row()).rowIndex);
|
|
case pageIndexRole:
|
|
return QVariant::fromValue(m_folder->m_delegates.at(index.row()).pageIndex);
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
QHash<int, QByteArray> ApplicationFolderModel::roleNames() const
|
|
{
|
|
return {{DelegateRole, "delegate"}, {columnIndexRole, "columnIndex"}, {rowIndexRole, "rowIndex"}, {pageIndexRole, "pageIndex"}};
|
|
}
|
|
|
|
FolioDelegate::Ptr ApplicationFolderModel::getDelegate(int index)
|
|
{
|
|
if (index < 0 || index >= m_folder->m_delegates.size()) {
|
|
return nullptr;
|
|
}
|
|
return m_folder->m_delegates[index].delegate;
|
|
}
|
|
|
|
void ApplicationFolderModel::moveEntry(int fromRow, int toRow)
|
|
{
|
|
if (fromRow < 0 || toRow < 0 || fromRow >= m_folder->m_delegates.size() || toRow >= m_folder->m_delegates.size() || fromRow == toRow) {
|
|
return;
|
|
}
|
|
if (toRow > fromRow) {
|
|
++toRow;
|
|
}
|
|
|
|
beginMoveRows(QModelIndex(), fromRow, fromRow, QModelIndex(), toRow);
|
|
if (toRow > fromRow) {
|
|
auto delegate = m_folder->m_delegates.at(fromRow);
|
|
m_folder->m_delegates.insert(toRow, delegate);
|
|
m_folder->m_delegates.takeAt(fromRow);
|
|
} else {
|
|
auto delegate = m_folder->m_delegates.takeAt(fromRow);
|
|
m_folder->m_delegates.insert(toRow, delegate);
|
|
}
|
|
endMoveRows();
|
|
|
|
evaluateDelegateIndexes();
|
|
|
|
Q_EMIT m_folder->applicationsChanged();
|
|
Q_EMIT m_folder->saveRequested();
|
|
}
|
|
|
|
bool ApplicationFolderModel::canAddDelegate(FolioDelegate::Ptr delegate, int index)
|
|
{
|
|
if (index < 0 || index > m_folder->m_delegates.size()) {
|
|
return false;
|
|
}
|
|
|
|
if (!delegate) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ApplicationFolderModel::addDelegate(FolioDelegate::Ptr delegate, int index)
|
|
{
|
|
if (!canAddDelegate(delegate, index)) {
|
|
return false;
|
|
}
|
|
|
|
if (index == m_folder->m_delegates.size()) {
|
|
beginInsertRows(QModelIndex(), index, index);
|
|
m_folder->m_delegates.append({delegate, 0, 0, 0});
|
|
evaluateDelegateIndexes(false);
|
|
endInsertRows();
|
|
} else if (m_folder->m_delegates[index].delegate->type() == FolioDelegate::None) {
|
|
replaceGhostEntry(delegate);
|
|
} else {
|
|
beginInsertRows(QModelIndex(), index, index);
|
|
m_folder->m_delegates.insert(index, {delegate, 0, 0, 0});
|
|
evaluateDelegateIndexes(false);
|
|
endInsertRows();
|
|
}
|
|
|
|
evaluateDelegateIndexes();
|
|
|
|
Q_EMIT m_folder->applicationsChanged();
|
|
Q_EMIT m_folder->saveRequested();
|
|
|
|
return true;
|
|
}
|
|
|
|
void ApplicationFolderModel::removeDelegate(int index)
|
|
{
|
|
if (index < 0 || index >= m_folder->m_delegates.size()) {
|
|
return;
|
|
}
|
|
|
|
beginRemoveRows(QModelIndex(), index, index);
|
|
// HACK: do not deleteLater(), because the delegate might still be used somewhere else
|
|
// m_folder->m_delegates[index].app->deleteLater();
|
|
m_folder->m_delegates.removeAt(index);
|
|
endRemoveRows();
|
|
|
|
evaluateDelegateIndexes();
|
|
|
|
Q_EMIT m_folder->applicationsChanged();
|
|
Q_EMIT m_folder->saveRequested();
|
|
}
|
|
|
|
QPointF ApplicationFolderModel::getDelegatePosition(int index)
|
|
{
|
|
if (index < 0 || index >= m_folder->m_delegates.size()) {
|
|
return {0, 0};
|
|
}
|
|
auto delegate = m_folder->m_delegates[index];
|
|
qreal pageContentSize = m_folder->m_homeScreen->homeScreenState()->folderPageContentWidth();
|
|
qreal topMargin = verticalPageMargin();
|
|
qreal leftMargin = horizontalPageMargin();
|
|
|
|
int cellSize = pageContentSize / numGridLengthOnPage();
|
|
|
|
qreal cellWitdhRecenter = (cellSize - m_folder->m_homeScreen->homeScreenState()->pageCellWidth()) / 2;
|
|
qreal cellHeightRecenter = (cellSize - m_folder->m_homeScreen->homeScreenState()->pageCellHeight()) / 2;
|
|
|
|
qreal xPosition = cellWitdhRecenter + leftMargin + delegate.columnIndex * cellSize;
|
|
qreal yPosition = cellHeightRecenter + topMargin + delegate.rowIndex * cellSize;
|
|
|
|
return {xPosition, yPosition};
|
|
}
|
|
|
|
int ApplicationFolderModel::getGhostEntryPosition()
|
|
{
|
|
for (int i = 0; i < m_folder->m_delegates.size(); i++) {
|
|
if (m_folder->m_delegates[i].delegate->type() == FolioDelegate::None) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void ApplicationFolderModel::setGhostEntry(int index)
|
|
{
|
|
FolioDelegate::Ptr ghost = nullptr;
|
|
|
|
// check if a ghost entry already exists
|
|
for (int i = 0; i < m_folder->m_delegates.size(); i++) {
|
|
auto delegate = m_folder->m_delegates[i].delegate;
|
|
if (delegate->type() == FolioDelegate::None) {
|
|
ghost = delegate;
|
|
|
|
// remove it
|
|
removeDelegate(i);
|
|
|
|
// correct index if necessary due to deletion
|
|
if (index > i) {
|
|
index--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ghost) {
|
|
ghost = std::make_shared<FolioDelegate>(m_folder->m_homeScreen);
|
|
}
|
|
|
|
// add empty delegate at new position
|
|
addDelegate(ghost, index);
|
|
}
|
|
|
|
void ApplicationFolderModel::replaceGhostEntry(FolioDelegate::Ptr delegate)
|
|
{
|
|
for (int i = 0; i < m_folder->m_delegates.size(); i++) {
|
|
if (m_folder->m_delegates[i].delegate->type() == FolioDelegate::None) {
|
|
m_folder->m_delegates[i].delegate = delegate;
|
|
|
|
Q_EMIT dataChanged(createIndex(i, 0), createIndex(i, 0), {DelegateRole});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ApplicationFolderModel::deleteGhostEntry()
|
|
{
|
|
for (int i = 0; i < m_folder->m_delegates.size(); i++) {
|
|
if (m_folder->m_delegates[i].delegate->type() == FolioDelegate::None) {
|
|
removeDelegate(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
int ApplicationFolderModel::dropInsertPosition(int page, qreal x, qreal y)
|
|
{
|
|
qreal pageContentWidth = m_folder->m_homeScreen->homeScreenState()->folderPageContentWidth();
|
|
qreal cellSize = (pageContentWidth) / numGridLengthOnPage();
|
|
|
|
int row = (y - topMarginFromScreenEdge()) / cellSize;
|
|
row = std::max(0, std::min(numGridLengthOnPage(), row));
|
|
|
|
// the index that the position is over
|
|
int leftColumn = std::max(0.0, x - leftMarginFromScreenEdge()) / cellSize;
|
|
leftColumn = std::min(numGridLengthOnPage() - 1, leftColumn);
|
|
|
|
qreal leftColumnPosition = leftColumn * cellSize + leftMarginFromScreenEdge();
|
|
|
|
int column = leftColumn + 1;
|
|
|
|
// if it's the left half of this position or it's the last column on this row, return itself
|
|
if ((x < leftColumnPosition + cellSize * 0.5) || (leftColumn == numGridLengthOnPage() - 1)) {
|
|
column = leftColumn;
|
|
}
|
|
|
|
// calculate the position based on the page, row and column it is at
|
|
int pos = (page * numGridLengthOnPage() * numGridLengthOnPage()) + (row * numGridLengthOnPage()) + column;
|
|
// make sure it's in bounds
|
|
return std::min((int)m_folder->m_delegates.size(), std::max(0, pos));
|
|
}
|
|
|
|
bool ApplicationFolderModel::isDropPositionOutside(qreal x, qreal y)
|
|
{
|
|
return (x < leftMarginFromScreenEdge()) || (x > (m_folder->m_homeScreen->homeScreenState()->viewWidth() - leftMarginFromScreenEdge()))
|
|
|| (y < topMarginFromScreenEdge()) || (y > m_folder->m_homeScreen->homeScreenState()->viewHeight() - topMarginFromScreenEdge());
|
|
}
|
|
|
|
void ApplicationFolderModel::evaluateDelegateIndexes(bool emitSignal)
|
|
{
|
|
int rows = numGridLengthOnPage();
|
|
int columns = numGridLengthOnPage();
|
|
int numOfDelegates = m_folder->m_delegates.size();
|
|
|
|
int index = 0;
|
|
int page = 0;
|
|
|
|
while (index < m_folder->m_delegates.size()) {
|
|
int prevIndex = index;
|
|
|
|
// determine the row and column index page-by-page
|
|
for (int row = 0; row < rows && index < numOfDelegates; row++) {
|
|
for (int column = 0; column < columns && index < numOfDelegates; column++) {
|
|
m_folder->m_delegates[index].columnIndex = column;
|
|
m_folder->m_delegates[index].rowIndex = row;
|
|
m_folder->m_delegates[index].pageIndex = page;
|
|
index++;
|
|
}
|
|
}
|
|
|
|
// prevent infinite loop
|
|
if (prevIndex == index) {
|
|
break;
|
|
}
|
|
page++;
|
|
}
|
|
|
|
if (emitSignal) {
|
|
Q_EMIT dataChanged(createIndex(0, 0), createIndex(m_folder->m_delegates.size() - 1, 0), {columnIndexRole});
|
|
Q_EMIT dataChanged(createIndex(0, 0), createIndex(m_folder->m_delegates.size() - 1, 0), {rowIndexRole});
|
|
Q_EMIT dataChanged(createIndex(0, 0), createIndex(m_folder->m_delegates.size() - 1, 0), {pageIndexRole});
|
|
}
|
|
|
|
Q_EMIT numberOfPagesChanged();
|
|
}
|
|
|
|
QPointF ApplicationFolderModel::getDelegateStartPosition(int page)
|
|
{
|
|
qreal pageWidth = m_folder->m_homeScreen->homeScreenState()->folderPageWidth();
|
|
|
|
qreal x = pageWidth * page + leftMarginFromScreenEdge();
|
|
qreal y = topMarginFromScreenEdge();
|
|
return QPointF{x, y};
|
|
}
|
|
|
|
int ApplicationFolderModel::numTotalPages()
|
|
{
|
|
int numOfDelegatesOnPage = numGridLengthOnPage() * numGridLengthOnPage();
|
|
return std::ceil(((qreal)m_folder->m_delegates.size()) / numOfDelegatesOnPage);
|
|
}
|
|
|
|
int ApplicationFolderModel::numGridLengthOnPage()
|
|
{
|
|
return m_folder->m_homeScreen->homeScreenState()->folderGridLength();
|
|
}
|
|
|
|
qreal ApplicationFolderModel::leftMarginFromScreenEdge()
|
|
{
|
|
HomeScreenState *homeScreenState = m_folder->m_homeScreen->homeScreenState();
|
|
qreal viewWidth = homeScreenState->viewWidth();
|
|
qreal folderPageWidth = homeScreenState->folderPageWidth();
|
|
|
|
return (viewWidth - folderPageWidth) / 2 + horizontalPageMargin();
|
|
}
|
|
|
|
qreal ApplicationFolderModel::topMarginFromScreenEdge()
|
|
{
|
|
HomeScreenState *homeScreenState = m_folder->m_homeScreen->homeScreenState();
|
|
qreal viewHeight = homeScreenState->viewHeight();
|
|
qreal folderPageHeight = homeScreenState->folderPageHeight();
|
|
|
|
return (viewHeight - folderPageHeight) / 2 + verticalPageMargin();
|
|
}
|
|
|
|
qreal ApplicationFolderModel::horizontalPageMargin()
|
|
{
|
|
HomeScreenState *homeScreenState = m_folder->m_homeScreen->homeScreenState();
|
|
qreal pageWidth = homeScreenState->folderPageWidth();
|
|
qreal pageContentWidth = homeScreenState->folderPageContentWidth();
|
|
|
|
return (pageWidth - pageContentWidth) / 2;
|
|
}
|
|
|
|
qreal ApplicationFolderModel::verticalPageMargin()
|
|
{
|
|
HomeScreenState *homeScreenState = m_folder->m_homeScreen->homeScreenState();
|
|
qreal pageHeight = homeScreenState->folderPageHeight();
|
|
qreal pageContentHeight = homeScreenState->folderPageContentHeight();
|
|
|
|
return (pageHeight - pageContentHeight) / 2;
|
|
}
|