shift-shell/containments/homescreens/folio/favouritesmodel.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

447 lines
13 KiB
C++
Raw Normal View History

// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "favouritesmodel.h"
#include "homescreenstate.h"
#include <QByteArray>
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QModelIndex>
#include <QProcess>
#include <QQuickWindow>
#include <KApplicationTrader>
#include <KConfigGroup>
#include <KIO/ApplicationLauncherJob>
#include <KNotificationJobUiDelegate>
#include <KService>
#include <KSharedConfig>
#include <KSycoca>
FavouritesModel::FavouritesModel(HomeScreen *parent)
: QAbstractListModel{parent}
, m_homeScreen{parent}
{
connect(m_homeScreen->homeScreenState(), &HomeScreenState::pageWidthChanged, this, [this]() {
evaluateDelegatePositions(true);
});
connect(m_homeScreen->homeScreenState(), &HomeScreenState::pageHeightChanged, this, [this]() {
evaluateDelegatePositions(true);
});
connect(m_homeScreen->homeScreenState(), &HomeScreenState::pageCellWidthChanged, this, [this]() {
evaluateDelegatePositions(true);
});
connect(m_homeScreen->homeScreenState(), &HomeScreenState::pageCellHeightChanged, this, [this]() {
evaluateDelegatePositions(true);
});
connect(m_homeScreen->homeScreenState(), &HomeScreenState::favouritesBarLocationChanged, this, [this]() {
evaluateDelegatePositions(true);
});
connect(m_homeScreen->homeScreenState(), &HomeScreenState::pageOrientationChanged, this, [this]() {
evaluateDelegatePositions(true);
});
}
int FavouritesModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_delegates.count();
}
QVariant FavouritesModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_delegates.size()) {
return QVariant();
}
switch (role) {
case DelegateRole:
return QVariant::fromValue(m_delegates.at(index.row()).delegate);
case XPositionRole:
return QVariant::fromValue(m_delegates.at(index.row()).xPosition);
}
return QVariant();
}
QHash<int, QByteArray> FavouritesModel::roleNames() const
{
return {{DelegateRole, "delegate"}, {XPositionRole, "xPosition"}};
}
void FavouritesModel::removeEntry(int row)
{
if (row < 0 || row >= m_delegates.size()) {
return;
}
beginRemoveRows(QModelIndex(), row, row);
// HACK: do not deleteLater(), because the delegate might still be used somewhere else
// m_delegates[row].delegate->deleteLater();
m_delegates.removeAt(row);
endRemoveRows();
evaluateDelegatePositions();
save();
}
void FavouritesModel::moveEntry(int fromRow, int toRow)
{
if (fromRow < 0 || toRow < 0 || fromRow >= m_delegates.size() || toRow >= m_delegates.size() || fromRow == toRow) {
return;
}
if (toRow > fromRow) {
++toRow;
}
beginMoveRows(QModelIndex(), fromRow, fromRow, QModelIndex(), toRow);
if (toRow > fromRow) {
auto delegate = m_delegates.at(fromRow);
m_delegates.insert(toRow, delegate);
m_delegates.takeAt(fromRow);
} else {
auto delegate = m_delegates.takeAt(fromRow);
m_delegates.insert(toRow, delegate);
}
endMoveRows();
evaluateDelegatePositions();
save();
}
2023-11-05 05:14:39 +00:00
bool FavouritesModel::canAddEntry(int row, FolioDelegate *delegate)
{
if (!delegate) {
return false;
}
if (row < 0 || row > m_delegates.size()) {
return false;
}
2023-11-05 05:14:39 +00:00
return true;
}
bool FavouritesModel::addEntry(int row, FolioDelegate *delegate)
{
if (!canAddEntry(row, delegate)) {
return false;
}
if (row == m_delegates.size()) {
beginInsertRows(QModelIndex(), row, row);
m_delegates.append({delegate, 0});
evaluateDelegatePositions(false);
endInsertRows();
} else if (m_delegates[row].delegate->type() == FolioDelegate::None) {
replaceGhostEntry(delegate);
} else {
beginInsertRows(QModelIndex(), row, row);
m_delegates.insert(row, {delegate, 0});
evaluateDelegatePositions(false);
endInsertRows();
}
2023-11-05 05:14:39 +00:00
// ensure saves are connected when requested by the delegate
connectSaveRequests(delegate);
evaluateDelegatePositions();
save();
return true;
}
FolioDelegate *FavouritesModel::getEntryAt(int row)
{
if (row < 0 || row >= m_delegates.size()) {
return nullptr;
}
return m_delegates[row].delegate;
}
bool FavouritesModel::isFull() const
{
auto homeScreenState = m_homeScreen->homeScreenState();
bool isLocationBottom = homeScreenState->favouritesBarLocation() == HomeScreenState::Bottom;
if (isLocationBottom) {
return m_delegates.size() >= homeScreenState->pageColumns();
} else {
return m_delegates.size() >= homeScreenState->pageRows();
}
}
int FavouritesModel::getGhostEntryPosition()
{
for (int i = 0; i < m_delegates.size(); i++) {
if (m_delegates[i].delegate->type() == FolioDelegate::None) {
return i;
}
}
return -1;
}
void FavouritesModel::setGhostEntry(int row)
{
bool found = false;
// check if a ghost entry already exists, then swap them
for (int i = 0; i < m_delegates.size(); i++) {
if (m_delegates[i].delegate->type() == FolioDelegate::None) {
found = true;
if (row != i) {
moveEntry(i, row);
}
}
}
// if it doesn't, add a new empty delegate
if (!found) {
FolioDelegate *ghost = new FolioDelegate{m_homeScreen};
addEntry(row, ghost);
}
}
void FavouritesModel::replaceGhostEntry(FolioDelegate *delegate)
{
for (int i = 0; i < m_delegates.size(); i++) {
if (m_delegates[i].delegate->type() == FolioDelegate::None) {
m_delegates[i].delegate->deleteLater();
m_delegates[i].delegate = delegate;
Q_EMIT dataChanged(createIndex(i, 0), createIndex(i, 0), {DelegateRole});
break;
}
}
}
void FavouritesModel::deleteGhostEntry()
{
for (int i = 0; i < m_delegates.size(); i++) {
if (m_delegates[i].delegate->type() == FolioDelegate::None) {
auto ghostEntry = m_delegates[i].delegate;
removeEntry(i);
// ensure ghost entry is deleted
ghostEntry->deleteLater();
}
}
}
QJsonArray FavouritesModel::exportToJson()
{
QJsonArray arr;
for (int i = 0; i < m_delegates.size(); i++) {
FolioDelegate *delegate = m_delegates[i].delegate;
// if this delegate is empty, ignore it
if (!delegate || delegate->type() == FolioDelegate::None) {
continue;
}
arr.append(delegate->toJson());
}
return arr;
}
void FavouritesModel::save()
{
if (!m_homeScreen) {
return;
}
QJsonArray arr = exportToJson();
QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact);
m_homeScreen->config().writeEntry("Favourites", QString::fromStdString(data.toStdString()));
Q_EMIT m_homeScreen->configNeedsSaving();
}
void FavouritesModel::load()
{
if (!m_homeScreen) {
return;
}
QJsonDocument doc = QJsonDocument::fromJson(m_homeScreen->config().readEntry("Favourites", "{}").toUtf8());
loadFromJson(doc.array());
}
void FavouritesModel::loadFromJson(QJsonArray arr)
{
beginResetModel();
m_delegates.clear();
for (QJsonValueRef r : arr) {
QJsonObject obj = r.toObject();
FolioDelegate *delegate = FolioDelegate::fromJson(obj, m_homeScreen);
if (delegate) {
2023-11-05 05:14:39 +00:00
connectSaveRequests(delegate);
m_delegates.append({delegate, 0});
}
}
evaluateDelegatePositions(false);
endResetModel();
}
2023-11-05 05:14:39 +00:00
void FavouritesModel::connectSaveRequests(FolioDelegate *delegate)
{
if (delegate->type() == FolioDelegate::Folder && delegate->folder()) {
connect(delegate->folder(), &FolioApplicationFolder::saveRequested, this, &FavouritesModel::save);
}
}
bool FavouritesModel::dropPositionIsEdge(qreal x, qreal y) const
{
auto homeScreenState = m_homeScreen->homeScreenState();
qreal startPosition = getDelegateRowStartPos();
bool isLocationBottom = homeScreenState->favouritesBarLocation() == HomeScreenState::Bottom;
qreal cellLength = isLocationBottom ? homeScreenState->pageCellWidth() : homeScreenState->pageCellHeight();
qreal pos = isLocationBottom ? x : y;
if (pos < startPosition) {
return true;
}
qreal currentPos = startPosition;
for (int i = 0; i < m_delegates.size(); i++) {
2024-06-16 20:50:06 +00:00
// if it is within the center 70% of a delegate, it is not at an edge
if (pos >= (currentPos + cellLength * 0.15) && pos <= (currentPos + cellLength * 0.85)) {
return false;
}
currentPos += cellLength;
}
return true;
}
int FavouritesModel::dropInsertPosition(qreal x, qreal y) const
{
auto homeScreenState = m_homeScreen->homeScreenState();
qreal startPosition = getDelegateRowStartPos();
bool isLocationBottom = homeScreenState->favouritesBarLocation() == HomeScreenState::Bottom;
qreal cellLength = isLocationBottom ? homeScreenState->pageCellWidth() : homeScreenState->pageCellHeight();
qreal pos = isLocationBottom ? x : y;
if (pos < startPosition) {
return adjustIndex(0);
}
qreal currentPos = startPosition;
for (int i = 0; i < m_delegates.size(); i++) {
if (pos < currentPos + cellLength * 0.85) {
return adjustIndex(i);
} else if (pos < currentPos + cellLength) {
return adjustIndex(i + 1);
}
currentPos += cellLength;
}
return adjustIndex(m_delegates.size());
}
QPointF FavouritesModel::getDelegateScreenPosition(int position) const
{
position = adjustIndex(position);
auto homeScreenState = m_homeScreen->homeScreenState();
qreal screenHeight = homeScreenState->viewHeight();
qreal screenWidth = homeScreenState->viewWidth();
qreal pageHeight = homeScreenState->pageHeight();
qreal pageWidth = homeScreenState->pageWidth();
qreal screenTopPadding = homeScreenState->viewTopPadding();
qreal screenBottomPadding = homeScreenState->viewBottomPadding();
qreal screenLeftPadding = homeScreenState->viewLeftPadding();
qreal screenRightPadding = homeScreenState->viewRightPadding();
qreal cellHeight = homeScreenState->pageCellHeight();
qreal cellWidth = homeScreenState->pageCellWidth();
qreal startPosition = getDelegateRowStartPos();
switch (homeScreenState->favouritesBarLocation()) {
case HomeScreenState::Bottom: {
qreal favouritesHeight = screenHeight - pageHeight - screenBottomPadding - screenTopPadding;
qreal x = screenLeftPadding + startPosition + cellWidth * position;
qreal y = screenTopPadding + pageHeight + (favouritesHeight / 2) - (cellHeight / 2);
return {x, y};
}
case HomeScreenState::Left: {
qreal favouritesWidth = screenWidth - screenLeftPadding - pageWidth - screenRightPadding;
qreal x = screenLeftPadding + (favouritesWidth / 2) - (cellWidth / 2);
qreal y = startPosition + cellHeight * position;
return {x, y};
}
case HomeScreenState::Right: {
qreal favouritesWidth = screenWidth - screenLeftPadding - pageWidth - screenRightPadding;
qreal x = screenLeftPadding + pageWidth + (favouritesWidth / 2) - (cellWidth / 2);
qreal y = startPosition + cellHeight * position;
return {x, y};
}
}
return {0, 0};
}
void FavouritesModel::evaluateDelegatePositions(bool emitSignal)
{
auto homeScreenState = m_homeScreen->homeScreenState();
bool isLocationBottom = homeScreenState->favouritesBarLocation() == HomeScreenState::Bottom;
qreal cellLength = isLocationBottom ? homeScreenState->pageCellWidth() : homeScreenState->pageCellHeight();
qreal startPosition = getDelegateRowStartPos();
qreal currentPos = startPosition;
for (int i = 0; i < m_delegates.size(); ++i) {
m_delegates[adjustIndex(i)].xPosition = qRound(currentPos);
currentPos += cellLength;
}
if (emitSignal) {
Q_EMIT dataChanged(createIndex(0, 0), createIndex(m_delegates.size() - 1, 0), {XPositionRole});
}
}
qreal FavouritesModel::getDelegateRowStartPos() const
{
auto homeScreenState = m_homeScreen->homeScreenState();
const int length = m_delegates.size();
const bool isLocationBottom = homeScreenState->favouritesBarLocation() == HomeScreenState::Bottom;
const qreal cellLength = isLocationBottom ? homeScreenState->pageCellWidth() : homeScreenState->pageCellHeight();
const qreal pageLength = isLocationBottom ? homeScreenState->pageWidth() : homeScreenState->pageHeight();
const qreal topMargin = homeScreenState->viewTopPadding();
const qreal leftMargin = homeScreenState->viewLeftPadding();
const qreal panelOffset = isLocationBottom ? leftMargin : topMargin;
return (pageLength / 2) - (((qreal)length) / 2) * cellLength + panelOffset;
}
int FavouritesModel::adjustIndex(int index) const
{
auto homeScreenState = m_homeScreen->homeScreenState();
if (homeScreenState->favouritesBarLocation() == HomeScreenState::Bottom || homeScreenState->favouritesBarLocation() == HomeScreenState::Left) {
return index;
} else {
// if it's on the right side of the screen, we flip the order of the delegates
return qMax(0, m_delegates.size() - index - 1);
}
}