homescreens/halcyon: Port to new model

This commit is contained in:
Devin Lin 2022-06-18 15:42:29 -04:00
parent b41e1f2b8a
commit ef45546127
20 changed files with 854 additions and 74 deletions

View file

@ -1,14 +1,9 @@
/* // SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
* SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org> // SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org> // SPDX-License-Identifier: GPL-2.0-or-later
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
// Self
#include "applicationlistmodel.h" #include "applicationlistmodel.h"
// Qt
#include <QByteArray> #include <QByteArray>
#include <QDebug> #include <QDebug>
#include <QModelIndex> #include <QModelIndex>
@ -16,7 +11,6 @@
#include <QQuickItem> #include <QQuickItem>
#include <QQuickWindow> #include <QQuickWindow>
// KDE
#include <KApplicationTrader> #include <KApplicationTrader>
#include <KConfigGroup> #include <KConfigGroup>
#include <KIO/ApplicationLauncherJob> #include <KIO/ApplicationLauncherJob>
@ -25,11 +19,6 @@
#include <KSharedConfig> #include <KSharedConfig>
#include <KSycoca> #include <KSycoca>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
ApplicationListModel::ApplicationListModel(QObject *parent) ApplicationListModel::ApplicationListModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
{ {
@ -72,12 +61,6 @@ QHash<int, QByteArray> ApplicationListModel::roleNames() const
{ApplicationLocationRole, QByteArrayLiteral("applicationLocation")}}; {ApplicationLocationRole, QByteArrayLiteral("applicationLocation")}};
} }
ApplicationListModel *ApplicationListModel::instance()
{
static ApplicationListModel *model = new ApplicationListModel;
return model;
}
void ApplicationListModel::sycocaDbChanged() void ApplicationListModel::sycocaDbChanged()
{ {
m_applicationList.clear(); m_applicationList.clear();
@ -232,19 +215,16 @@ void ApplicationListModel::setMinimizedDelegate(int row, QQuickItem *delegate)
} }
QWindow *delegateWindow = delegate->window(); QWindow *delegateWindow = delegate->window();
if (!delegateWindow) { if (!delegateWindow) {
return; return;
} }
using namespace KWayland::Client;
KWayland::Client::PlasmaWindow *window = m_applicationList[row].window; KWayland::Client::PlasmaWindow *window = m_applicationList[row].window;
if (!window) { if (!window) {
return; return;
} }
Surface *surface = Surface::fromWindow(delegateWindow); KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(delegateWindow);
if (!surface) { if (!surface) {
return; return;
} }
@ -261,19 +241,16 @@ void ApplicationListModel::unsetMinimizedDelegate(int row, QQuickItem *delegate)
} }
QWindow *delegateWindow = delegate->window(); QWindow *delegateWindow = delegate->window();
if (!delegateWindow) { if (!delegateWindow) {
return; return;
} }
using namespace KWayland::Client;
KWayland::Client::PlasmaWindow *window = m_applicationList[row].window; KWayland::Client::PlasmaWindow *window = m_applicationList[row].window;
if (!window) { if (!window) {
return; return;
} }
Surface *surface = Surface::fromWindow(delegateWindow); KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(delegateWindow);
if (!surface) { if (!surface) {
return; return;
} }

View file

@ -1,29 +1,19 @@
/* // SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
* SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org> // SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org> // SPDX-License-Identifier: GPL-2.0-or-later
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once #pragma once
// Qt
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QList> #include <QList>
#include <QObject> #include <QObject>
#include <QQuickItem> #include <QQuickItem>
#include <QSet> #include <QSet>
class QString; #include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
namespace KWayland #include <KWayland/Client/registry.h>
{ #include <KWayland/Client/surface.h>
namespace Client
{
class PlasmaWindowManagement;
class PlasmaWindow;
}
}
/** /**
* @short The base application list, used directly by the app drawer. * @short The base application list, used directly by the app drawer.
@ -64,8 +54,6 @@ public:
ApplicationListModel(QObject *parent = nullptr); ApplicationListModel(QObject *parent = nullptr);
~ApplicationListModel() override; ~ApplicationListModel() override;
static ApplicationListModel *instance();
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;

View file

@ -3,6 +3,11 @@
set(homescreen_SRCS set(homescreen_SRCS
homescreen.cpp homescreen.cpp
application.cpp
applicationfolder.cpp
applicationlistmodel.cpp
pinnedmodel.cpp
windowlistener.cpp
) )
add_library(plasma_containment_phone_homescreen_halcyon MODULE ${homescreen_SRCS}) add_library(plasma_containment_phone_homescreen_halcyon MODULE ${homescreen_SRCS})

View file

@ -0,0 +1,153 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "application.h"
#include "windowlistener.h"
#include <QQuickWindow>
#include <KIO/ApplicationLauncherJob>
#include <KNotificationJobUiDelegate>
#include <KService>
Application::Application(QObject *parent, KService::Ptr service)
: QObject{parent}
, m_running{false}
, m_name{service->name()}
, m_icon{service->icon()}
, m_storageId{service->storageId()}
{
auto windows = WindowListener::instance()->windowsFromStorageId(m_storageId);
if (windows.empty()) {
m_window = nullptr;
} else {
m_window = windows[0];
}
connect(WindowListener::instance(), &WindowListener::windowChanged, this, [this](QString storageId) {
if (storageId == m_storageId) {
auto windows = WindowListener::instance()->windowsFromStorageId(m_storageId);
if (windows.empty()) {
setWindow(nullptr);
} else {
setWindow(windows[0]);
}
}
});
}
Application *Application::fromJson(QJsonObject &obj, QObject *parent)
{
QString storageId = obj[QStringLiteral("storageId")].toString();
if (KService::Ptr service = KService::serviceByStorageId(storageId)) {
return new Application(parent, service);
}
return nullptr;
}
QJsonObject Application::toJson()
{
QJsonObject obj;
obj[QStringLiteral("type")] = "application";
obj[QStringLiteral("storageId")] = m_storageId;
return obj;
}
bool Application::running() const
{
return m_window != nullptr;
}
QString Application::name() const
{
return m_name;
}
QString Application::icon() const
{
return m_icon;
}
QString Application::storageId() const
{
return m_storageId;
}
KWayland::Client::PlasmaWindow *Application::window() const
{
return m_window;
}
void Application::setName(QString &name)
{
m_name = name;
Q_EMIT nameChanged();
}
void Application::setIcon(QString &icon)
{
m_icon = icon;
Q_EMIT iconChanged();
}
void Application::setStorageId(QString &storageId)
{
m_storageId = storageId;
Q_EMIT storageIdChanged();
}
void Application::setWindow(KWayland::Client::PlasmaWindow *window)
{
m_window = window;
Q_EMIT windowChanged();
}
void Application::runApplication()
{
if (m_window) {
m_window->requestActivate();
return;
}
KService::Ptr service = KService::serviceByStorageId(m_storageId);
KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service);
job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled));
job->start();
}
void Application::setMinimizedDelegate(QQuickItem *delegate)
{
QWindow *delegateWindow = delegate->window();
if (!delegateWindow) {
return;
}
if (!m_window) {
return;
}
KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(delegateWindow);
if (!surface) {
return;
}
QRect rect = delegate->mapRectToScene(QRectF(0, 0, delegate->width(), delegate->height())).toRect();
m_window->setMinimizedGeometry(surface, rect);
}
void Application::unsetMinimizedDelegate(QQuickItem *delegate)
{
QWindow *delegateWindow = delegate->window();
if (!delegateWindow) {
return;
}
if (!m_window) {
return;
}
KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(delegateWindow);
if (!surface) {
return;
}
m_window->unsetMinimizedGeometry(surface);
}

View file

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QJsonObject>
#include <QObject>
#include <QQuickItem>
#include <QString>
#include <KService>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
class Application : public QObject
{
Q_OBJECT
Q_PROPERTY(bool running READ running NOTIFY windowChanged)
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged)
Q_PROPERTY(QString storageId READ storageId NOTIFY storageIdChanged)
public:
Application(QObject *parent = nullptr, KService::Ptr service = QExplicitlySharedDataPointer<KService>{nullptr});
static Application *fromJson(QJsonObject &obj, QObject *parent); // may return nullptr
QJsonObject toJson();
bool running() const;
QString name() const;
QString icon() const;
QString storageId() const;
KWayland::Client::PlasmaWindow *window() const;
void setName(QString &name);
void setIcon(QString &icon);
void setStorageId(QString &storageId);
void setWindow(KWayland::Client::PlasmaWindow *window);
Q_INVOKABLE void runApplication();
Q_INVOKABLE void setMinimizedDelegate(QQuickItem *delegate);
Q_INVOKABLE void unsetMinimizedDelegate(QQuickItem *delegate);
Q_SIGNALS:
void nameChanged();
void iconChanged();
void storageIdChanged();
void windowChanged();
private:
bool m_running;
QString m_name;
QString m_icon;
QString m_storageId;
KWayland::Client::PlasmaWindow *m_window = nullptr;
};

View file

@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "applicationfolder.h"
#include <QJsonArray>
ApplicationFolder::ApplicationFolder(QObject *parent)
{
}
ApplicationFolder *ApplicationFolder::fromJson(QJsonObject &obj, QObject *parent)
{
QString name = obj[QStringLiteral("name")].toString();
QList<Application *> apps;
for (auto storageId : obj[QStringLiteral("apps")].toArray()) {
if (KService::Ptr service = KService::serviceByStorageId(storageId.toString())) {
apps.append(new Application(parent, service));
}
}
ApplicationFolder *folder = new ApplicationFolder(parent);
folder->setName(name);
folder->setApplications(apps);
return folder;
}
QJsonObject ApplicationFolder::toJson()
{
QJsonObject obj;
obj[QStringLiteral("type")] = "folder";
obj[QStringLiteral("name")] = m_name;
QJsonArray arr;
for (auto *application : m_applications) {
arr.append(QJsonValue::fromVariant(application->storageId()));
}
obj[QStringLiteral("apps")] = arr;
return obj;
}
QString ApplicationFolder::name() const
{
return m_name;
}
void ApplicationFolder::setName(QString &name)
{
m_name = name;
Q_EMIT nameChanged();
}
QList<Application *> ApplicationFolder::applications()
{
return m_applications;
}
void ApplicationFolder::setApplications(QList<Application *> applications)
{
m_applications = applications;
Q_EMIT applicationsChanged();
}

View file

@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "application.h"
#include <QObject>
#include <QString>
#include <KService>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
class ApplicationFolder : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QList<Application *> applications READ applications NOTIFY applicationsChanged)
public:
ApplicationFolder(QObject *parent = nullptr);
static ApplicationFolder *fromJson(QJsonObject &obj, QObject *parent);
QJsonObject toJson();
QString name() const;
void setName(QString &name);
QList<Application *> applications();
void setApplications(QList<Application *> applications);
Q_SIGNALS:
void nameChanged();
void applicationsChanged();
private:
QString m_name;
QList<Application *> m_applications;
};

View file

@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "applicationlistmodel.h"
#include <QByteArray>
#include <QDebug>
#include <QModelIndex>
#include <QProcess>
#include <QQuickItem>
#include <QQuickWindow>
#include <KApplicationTrader>
#include <KConfigGroup>
#include <KIO/ApplicationLauncherJob>
#include <KNotificationJobUiDelegate>
#include <KService>
#include <KSharedConfig>
#include <KSycoca>
ApplicationListModel::ApplicationListModel(QObject *parent)
: QAbstractListModel(parent)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
connect(KSycoca::self(), qOverload<const QStringList &>(&KSycoca::databaseChanged), this, &ApplicationListModel::sycocaDbChanged);
#else
connect(KSycoca::self(), &KSycoca::databaseChanged, this, &ApplicationListModel::sycocaDbChanged);
#endif
}
ApplicationListModel::~ApplicationListModel() = default;
QHash<int, QByteArray> ApplicationListModel::roleNames() const
{
return {{ApplicationRole, QByteArrayLiteral("application")}};
}
void ApplicationListModel::sycocaDbChanged()
{
m_applicationList.clear();
loadApplications();
}
void ApplicationListModel::loadApplications()
{
auto cfg = KSharedConfig::openConfig(QStringLiteral("applications-blacklistrc"));
auto blgroup = KConfigGroup(cfg, QStringLiteral("Applications"));
const QStringList blacklist = blgroup.readEntry("blacklist", QStringList());
beginResetModel();
m_applicationList.clear();
QList<Application *> unorderedList;
auto filter = [blacklist](const KService::Ptr &service) -> bool {
if (service->noDisplay()) {
return false;
}
if (!service->showOnCurrentPlatform()) {
return false;
}
if (blacklist.contains(service->desktopEntryName())) {
return false;
}
return true;
};
const KService::List apps = KApplicationTrader::query(filter);
for (const KService::Ptr &service : apps) {
Application *application = new Application{this, service};
unorderedList.append(application);
}
std::sort(unorderedList.begin(), unorderedList.end(), [](const Application *a1, const Application *a2) {
return a1->name().compare(a2->name(), Qt::CaseInsensitive) < 0;
});
m_applicationList << unorderedList;
endResetModel();
}
QVariant ApplicationListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
return QVariant::fromValue(m_applicationList.at(index.row()));
}
int ApplicationListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_applicationList.count();
}

View file

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "application.h"
#include <QAbstractListModel>
#include <QList>
#include <QObject>
#include <QQuickItem>
#include <QSet>
/**
* @short The base application list, used directly by the app drawer.
*/
class ApplicationListModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles { ApplicationRole = Qt::UserRole + 1 };
ApplicationListModel(QObject *parent = nullptr);
~ApplicationListModel() override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void loadApplications();
public Q_SLOTS:
void sycocaDbChanged();
Q_SIGNALS:
void launchError(const QString &msg);
protected:
QList<Application *> m_applicationList;
};

View file

@ -1,2 +1,45 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org> // SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "homescreen.h"
#include "application.h"
#include "applicationfolder.h"
#include "pinnedmodel.h"
#include "windowlistener.h"
#include <KWindowSystem>
#include <QDebug>
#include <QQuickItem>
#include <QtQml>
HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
: Plasma::Containment{parent, data, args}
{
setHasConfigurationInterface(true);
WindowListener::instance(); // ensure it is created
ApplicationListModel *applicationListModel = new ApplicationListModel{this};
qmlRegisterSingletonType<ApplicationListModel>("org.kde.phone.homescreen.halcyon",
1,
0,
"ApplicationListModel",
[applicationListModel](QQmlEngine *, QJSEngine *) -> QObject * {
return applicationListModel;
});
PinnedModel *pinnedModel = new PinnedModel{this, this};
qmlRegisterSingletonType<PinnedModel>("org.kde.phone.homescreen.halcyon", 1, 0, "PinnedModel", [pinnedModel](QQmlEngine *, QJSEngine *) -> QObject * {
return pinnedModel;
});
qmlRegisterType<Application>("org.kde.phone.homescreen.halcyon", 1, 0, "Application");
qmlRegisterType<ApplicationFolder>("org.kde.phone.homescreen.halcyon", 1, 0, "ApplicationFolder");
}
HomeScreen::~HomeScreen() = default;
K_PLUGIN_CLASS_WITH_JSON(HomeScreen, "metadata.json")
#include "homescreen.moc"

View file

@ -1,2 +1,17 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org> // SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <Plasma/Containment>
#include <applicationlistmodel.h>
class HomeScreen : public Plasma::Containment
{
Q_OBJECT
public:
HomeScreen(QObject *parent, const KPluginMetaData &data, const QVariantList &args);
~HomeScreen() override;
};

View file

@ -13,12 +13,19 @@ import org.kde.kquickcontrolsaddons 2.0
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.phone.homescreen.halcyon 1.0 as Halcyon
import org.kde.kirigami 2.19 as Kirigami import org.kde.kirigami 2.19 as Kirigami
MouseArea { MouseArea {
id: delegate id: delegate
property alias iconItem: icon property alias iconItem: icon
property Halcyon.Application application: model.application
readonly property string applicationName: application ? application.name : ""
readonly property string applicationStorageId: application ? application.storageId : ""
readonly property string applicationIcon: application ? application.icon : ""
signal launch(int x, int y, var source, string title, string storageId) signal launch(int x, int y, var source, string title, string storageId)
signal dragStarted(string imageSource, int x, int y, string mimeData) signal dragStarted(string imageSource, int x, int y, string mimeData)
@ -33,8 +40,8 @@ MouseArea {
Math.min(delegate.iconItem.width, delegate.iconItem.height)); Math.min(delegate.iconItem.width, delegate.iconItem.height));
} }
MobileShell.ApplicationListModel.setMinimizedDelegate(index, delegate); application.setMinimizedDelegate(delegate);
MobileShell.ApplicationListModel.runApplication(storageId); application.runApplication();
} }
onPressAndHold: { onPressAndHold: {
@ -44,10 +51,10 @@ MouseArea {
onClicked: { onClicked: {
// launch app // launch app
if (model.applicationRunning) { if (application.running) {
delegate.launch(0, 0, "", model.applicationName, model.applicationStorageId); delegate.launch(0, 0, "", applicationName, applicationStorageId);
} else { } else {
delegate.launch(delegate.x + (PlasmaCore.Units.smallSpacing * 2), delegate.y + (PlasmaCore.Units.smallSpacing * 2), icon.source, model.applicationName, model.applicationStorageId); delegate.launch(delegate.x + (PlasmaCore.Units.smallSpacing * 2), delegate.y + (PlasmaCore.Units.smallSpacing * 2), icon.source, applicationName, applicationStorageId);
} }
} }
hoverEnabled: true hoverEnabled: true
@ -63,7 +70,7 @@ MouseArea {
icon.name: "emblem-favorite" icon.name: "emblem-favorite"
text: i18n("Remove from favourites") text: i18n("Remove from favourites")
onClicked: { onClicked: {
MobileShell.FavoritesModel.removeFavorite(model.index); Halcyon.PinnedModel.removeApp(model.index);
} }
} }
onClosed: dialogLoader.active = false onClosed: dialogLoader.active = false
@ -98,14 +105,14 @@ MouseArea {
Layout.preferredHeight: Layout.minimumHeight Layout.preferredHeight: Layout.minimumHeight
usesPlasmaTheme: false usesPlasmaTheme: false
source: model.applicationIcon source: applicationIcon
Rectangle { Rectangle {
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
bottom: parent.bottom bottom: parent.bottom
} }
visible: model.applicationRunning visible: application.running
radius: width radius: width
width: PlasmaCore.Units.smallSpacing width: PlasmaCore.Units.smallSpacing
height: width height: width
@ -132,7 +139,7 @@ MouseArea {
maximumLineCount: 1 maximumLineCount: 1
elide: Text.ElideRight elide: Text.ElideRight
text: model.applicationName text: applicationName
font.pointSize: PlasmaCore.Theme.defaultFont.pointSize font.pointSize: PlasmaCore.Theme.defaultFont.pointSize
font.weight: Font.Bold font.weight: Font.Bold

View file

@ -16,6 +16,7 @@ import org.kde.kquickcontrolsaddons 2.0
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.phone.homescreen.halcyon 1.0 as Halcyon
import org.kde.kirigami 2.19 as Kirigami import org.kde.kirigami 2.19 as Kirigami
@ -24,6 +25,8 @@ MouseArea {
width: GridView.view.cellWidth width: GridView.view.cellWidth
height: GridView.view.cellHeight height: GridView.view.cellHeight
property Halcyon.Application application: model.application
property int reservedSpaceForLabel property int reservedSpaceForLabel
property alias iconItem: icon property alias iconItem: icon
@ -38,10 +41,10 @@ MouseArea {
function launchApp() { function launchApp() {
// launch app // launch app
if (model.applicationRunning) { if (application.running) {
delegate.launch(0, 0, "", model.applicationName, model.applicationStorageId); delegate.launch(0, 0, "", application.name, application.storageId);
} else { } else {
delegate.launch(delegate.x + (PlasmaCore.Units.smallSpacing * 2), delegate.y + (PlasmaCore.Units.smallSpacing * 2), icon.source, model.applicationName, model.applicationStorageId); delegate.launch(delegate.x + (PlasmaCore.Units.smallSpacing * 2), delegate.y + (PlasmaCore.Units.smallSpacing * 2), icon.source, application.name, application.storageId);
} }
} }
@ -56,7 +59,7 @@ MouseArea {
icon.name: "emblem-favorite" icon.name: "emblem-favorite"
text: i18n("Add to favourites") text: i18n("Add to favourites")
onClicked: { onClicked: {
MobileShell.FavoritesModel.addFavorite(model.applicationStorageId, 0, MobileShell.ApplicationListModel.Favorites); Halcyon.PinnedModel.addApp(application.storageId, 0);
} }
} }
onClosed: dialogLoader.active = false onClosed: dialogLoader.active = false
@ -131,14 +134,14 @@ MouseArea {
Layout.preferredHeight: Layout.minimumHeight Layout.preferredHeight: Layout.minimumHeight
usesPlasmaTheme: false usesPlasmaTheme: false
source: model.applicationIcon source: application.icon
Rectangle { Rectangle {
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
bottom: parent.bottom bottom: parent.bottom
} }
visible: model.applicationRunning visible: application.running
radius: width radius: width
width: PlasmaCore.Units.smallSpacing width: PlasmaCore.Units.smallSpacing
height: width height: width
@ -161,7 +164,7 @@ MouseArea {
verticalAlignment: Text.AlignTop verticalAlignment: Text.AlignTop
elide: Text.ElideRight elide: Text.ElideRight
text: model.applicationName text: application.name
font.pointSize: theme.defaultFont.pointSize * 0.85 font.pointSize: theme.defaultFont.pointSize * 0.85
font.weight: Font.Bold font.weight: Font.Bold

View file

@ -15,6 +15,7 @@ import org.kde.kirigami 2.10 as Kirigami
import org.kde.plasma.private.nanoshell 2.0 as NanoShell import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.phone.homescreen.halcyon 1.0 as Halcyon
GridView { GridView {
id: gridView id: gridView
@ -32,7 +33,7 @@ GridView {
cacheBuffer: Math.max(0, rows * cellHeight) cacheBuffer: Math.max(0, rows * cellHeight)
model: MobileShell.ApplicationListModel model: Halcyon.ApplicationListModel
header: Controls.Control { header: Controls.Control {
implicitWidth: gridView.width implicitWidth: gridView.width
@ -59,6 +60,8 @@ GridView {
delegate: GridAppDelegate { delegate: GridAppDelegate {
id: delegate id: delegate
property Halcyon.Application application: model.application
width: gridView.cellWidth width: gridView.cellWidth
height: gridView.cellHeight height: gridView.cellHeight
reservedSpaceForLabel: gridView.reservedSpaceForLabel reservedSpaceForLabel: gridView.reservedSpaceForLabel
@ -73,8 +76,8 @@ GridView {
Math.min(delegate.iconItem.width, delegate.iconItem.height)); Math.min(delegate.iconItem.width, delegate.iconItem.height));
} }
MobileShell.ApplicationListModel.setMinimizedDelegate(index, delegate); application.setMinimizedDelegate(delegate);
MobileShell.ApplicationListModel.runApplication(storageId); application.runApplication();
gridView.launched(); gridView.launched();
} }
} }

View file

@ -13,6 +13,7 @@ import org.kde.draganddrop 2.0 as DragDrop
import org.kde.kirigami 2.19 as Kirigami import org.kde.kirigami 2.19 as Kirigami
import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.phone.homescreen.halcyon 1.0 as Halcyon
Item { Item {
id: root id: root
@ -60,7 +61,7 @@ Item {
} }
} }
model: MobileShell.FavoritesModel model: Halcyon.PinnedModel
header: MobileShell.BaseItem { header: MobileShell.BaseItem {
topPadding: Math.round(swipeView.height * 0.2) topPadding: Math.round(swipeView.height * 0.2)
bottomPadding: PlasmaCore.Units.largeSpacing bottomPadding: PlasmaCore.Units.largeSpacing

View file

@ -10,6 +10,7 @@ import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.phone.homescreen.halcyon 1.0 as Halcyon
MobileShell.HomeScreen { MobileShell.HomeScreen {
id: root id: root
@ -23,10 +24,7 @@ MobileShell.HomeScreen {
} }
Component.onCompleted: { Component.onCompleted: {
MobileShell.ApplicationListModel.loadApplications(); Halcyon.ApplicationListModel.loadApplications();
MobileShell.FavoritesModel.applet = plasmoid;
MobileShell.FavoritesModel.loadApplications();
forceActiveFocus(); forceActiveFocus();
} }
@ -61,7 +59,7 @@ MobileShell.HomeScreen {
Connections { Connections {
target: MobileShell.HomeScreenControls.taskSwitcher target: MobileShell.HomeScreenControls.taskSwitcher
function onVisibleChanged() { function onVisibleChanged() {
searchWidget.close(); search.close();
} }
} }
} }

View file

@ -0,0 +1,130 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "pinnedmodel.h"
#include <QJsonArray>
#include <QJsonDocument>
PinnedModel::PinnedModel(QObject *parent, Plasma::Applet *applet)
: QAbstractListModel{parent}
, m_applet{applet}
{
}
PinnedModel::~PinnedModel() = default;
int PinnedModel::rowCount(const QModelIndex &parent) const
{
return m_applications.count();
}
QVariant PinnedModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
switch (role) {
case IsFolderRole:
return m_folders.at(index.row()) != nullptr;
case ApplicationRole:
return QVariant::fromValue(m_applications.at(index.row()));
case FolderRole:
return QVariant::fromValue(m_folders.at(index.row()));
}
return QVariant();
}
QHash<int, QByteArray> PinnedModel::roleNames() const
{
return {{IsFolderRole, "isFolder"}, {ApplicationRole, "application"}, {FolderRole, "folder"}};
}
void PinnedModel::addApp(const QString &storageId, int row)
{
if (row < 0 && row > m_applications.size()) {
return;
}
if (KService::Ptr service = KService::serviceByStorageId(storageId)) {
Application *app = new Application(this, service);
beginInsertRows(QModelIndex(), row, row);
m_applications.insert(row, app);
m_folders.insert(0, nullptr); // maintain indicies
endInsertRows();
save();
}
}
void PinnedModel::removeApp(int row)
{
if (row < 0 && row >= m_applications.size()) {
return;
}
beginRemoveRows(QModelIndex(), row, row);
m_applications[row]->deleteLater();
m_applications.removeAt(row);
m_folders.removeAt(row); // maintain indicies
endRemoveRows();
save();
}
void PinnedModel::load()
{
if (!m_applet) {
return;
}
QJsonDocument doc = QJsonDocument::fromJson(m_applet->config().readEntry("Pinned", "{}").toUtf8());
beginResetModel();
for (QJsonValueRef r : doc.array()) {
QJsonObject obj = r.toObject();
if (obj[QStringLiteral("type")].toString() == "application") {
// read application
Application *app = Application::fromJson(obj, this);
if (app) {
m_applications.append(app);
m_folders.append(nullptr);
}
} else if (obj[QStringLiteral("type")].toString() == "folder") {
// read folder
ApplicationFolder *folder = ApplicationFolder::fromJson(obj, this);
if (folder) {
m_applications.append(nullptr);
m_folders.append(folder);
}
}
}
endResetModel();
}
void PinnedModel::save()
{
if (!m_applet) {
return;
}
QJsonArray arr;
for (int i = 0; i < m_applications.size() && i < m_folders.size(); i++) {
if (m_applications[i]) {
arr.push_back(m_applications[i]->toJson());
} else if (m_folders[i]) {
arr.push_back(m_folders[i]->toJson());
}
}
QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact);
m_applet->config().writeEntry("Pinned", QString::fromStdString(data.toStdString()));
Q_EMIT m_applet->configNeedsSaving();
}

View file

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "application.h"
#include "applicationfolder.h"
#include <QAbstractListModel>
#include <QList>
#include <QObject>
#include <QQuickItem>
#include <QSet>
#include <Plasma/Applet>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
class PinnedModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles { IsFolderRole = Qt::UserRole + 1, ApplicationRole, FolderRole };
PinnedModel(QObject *parent = nullptr, Plasma::Applet *applet = nullptr);
~PinnedModel() override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void addApp(const QString &storageId, int row);
Q_INVOKABLE void removeApp(int row);
Q_INVOKABLE void load();
void save();
private:
QList<Application *> m_applications;
QList<ApplicationFolder *> m_folders;
Plasma::Applet *m_applet;
};

View file

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "windowlistener.h"
WindowListener::WindowListener(QObject *parent)
: QObject{parent}
{
// initialize wayland window checking
KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(this);
if (!connection) {
return;
}
auto *registry = new KWayland::Client::Registry(this);
registry->create(connection);
connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_windowManagement = registry->createPlasmaWindowManagement(name, version, this);
connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, this, &WindowListener::windowCreated);
});
registry->setup();
connection->roundtrip();
}
WindowListener *WindowListener::instance()
{
static WindowListener *listener = new WindowListener();
return listener;
}
QList<KWayland::Client::PlasmaWindow *> WindowListener::windowsFromStorageId(QString &storageId) const
{
if (!m_windows.contains(storageId)) {
return {};
}
return m_windows[storageId];
}
void WindowListener::windowCreated(KWayland::Client::PlasmaWindow *window)
{
QString storageId = window->appId() + QStringLiteral(".desktop");
// ignore empty windows
if (storageId == ".desktop" || storageId == "org.kde.plasmashell.desktop") {
return;
}
if (!m_windows.contains(storageId)) {
m_windows[storageId] = {};
}
m_windows[storageId].push_back(window);
// listen for window close
connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [this, storageId]() {
m_windows.remove(storageId);
Q_EMIT windowChanged(storageId);
});
Q_EMIT windowChanged(storageId);
}

View file

@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QList>
#include <QObject>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
class WindowListener : public QObject
{
Q_OBJECT
public:
WindowListener(QObject *parent = nullptr);
static WindowListener *instance();
QList<KWayland::Client::PlasmaWindow *> windowsFromStorageId(QString &storageId) const;
public Q_SLOTS:
void windowCreated(KWayland::Client::PlasmaWindow *window);
Q_SIGNALS:
void windowChanged(QString storageId);
private:
KWayland::Client::PlasmaWindowManagement *m_windowManagement = nullptr;
QHash<QString, QList<KWayland::Client::PlasmaWindow *>> m_windows; // <storageId, window>
};