mirror of
https://github.com/Qortal/Brooklyn.git
synced 2025-02-19 05:35:53 +00:00
484 lines
16 KiB
C++
484 lines
16 KiB
C++
|
/*
|
||
|
SPDX-FileCopyrightText: 2021 Cyril Rossi <cyril.rossi@enioka.com>
|
||
|
|
||
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||
|
*/
|
||
|
|
||
|
#include "shellcontainmentconfig.h"
|
||
|
|
||
|
#include <KActionCollection>
|
||
|
#include <KActivities/Consumer>
|
||
|
#include <KActivities/Info>
|
||
|
#include <KLocalizedContext>
|
||
|
#include <KLocalizedString>
|
||
|
#include <KPackage/Package>
|
||
|
|
||
|
#include <QQmlContext>
|
||
|
#include <QQuickItem>
|
||
|
#include <QScreen>
|
||
|
|
||
|
#include "shellcorona.h"
|
||
|
#include "screenpool.h"
|
||
|
#include "panelview.h"
|
||
|
|
||
|
|
||
|
ScreenPoolModel::ScreenPoolModel(ShellCorona *corona, QObject *parent)
|
||
|
: QAbstractListModel(parent)
|
||
|
, m_corona(corona)
|
||
|
{
|
||
|
m_reloadTimer = new QTimer(this);
|
||
|
m_reloadTimer->setSingleShot(true);
|
||
|
m_reloadTimer->setInterval(200);
|
||
|
|
||
|
connect(m_reloadTimer, &QTimer::timeout, this, &ScreenPoolModel::load);
|
||
|
|
||
|
connect(m_corona, &Plasma::Corona::screenAdded, m_reloadTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
|
||
|
connect(m_corona, &Plasma::Corona::screenRemoved, m_reloadTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
|
||
|
}
|
||
|
|
||
|
ScreenPoolModel::~ScreenPoolModel() = default;
|
||
|
|
||
|
QVariant ScreenPoolModel::data(const QModelIndex &index, int role) const
|
||
|
{
|
||
|
if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= int(m_screens.size())) {
|
||
|
return QVariant();
|
||
|
}
|
||
|
const Data &d = m_screens.at(index.row());
|
||
|
switch (role) {
|
||
|
case ScreenIdRole:
|
||
|
return d.id;
|
||
|
case ScreenNameRole:
|
||
|
return d.name;
|
||
|
case ContainmentsRole: {
|
||
|
auto *cont = m_containments.at(index.row());
|
||
|
return QVariant::fromValue<QObject *>(cont);
|
||
|
}
|
||
|
case PrimaryRole:
|
||
|
return d.primary;
|
||
|
case EnabledRole:
|
||
|
return d.enabled;
|
||
|
}
|
||
|
return QVariant();
|
||
|
}
|
||
|
|
||
|
int ScreenPoolModel::rowCount(const QModelIndex &parent) const
|
||
|
{
|
||
|
if (parent.isValid()) {
|
||
|
return 0;
|
||
|
}
|
||
|
return m_screens.size();
|
||
|
}
|
||
|
|
||
|
QHash<int, QByteArray> ScreenPoolModel::roleNames() const
|
||
|
{
|
||
|
QHash<int, QByteArray> roles({{ScreenIdRole, QByteArrayLiteral("screenId")},
|
||
|
{ScreenNameRole, QByteArrayLiteral("screenName")},
|
||
|
{ContainmentsRole, QByteArrayLiteral("containments")},
|
||
|
{EnabledRole, QByteArrayLiteral("isEnabled")},
|
||
|
{PrimaryRole, QByteArrayLiteral("isPrimary")}});
|
||
|
return roles;
|
||
|
}
|
||
|
|
||
|
void ScreenPoolModel::load()
|
||
|
{
|
||
|
beginResetModel();
|
||
|
m_screens.clear();
|
||
|
qDeleteAll(m_containments);
|
||
|
m_containments.clear();
|
||
|
|
||
|
QSet<int> unknownScreenIds;
|
||
|
for (auto *cont : m_corona->containments()) {
|
||
|
unknownScreenIds.insert(cont->lastScreen());
|
||
|
}
|
||
|
for (auto &knownId : m_corona->screenPool()->knownIds()) {
|
||
|
Data d;
|
||
|
unknownScreenIds.remove(knownId);
|
||
|
d.id = knownId;
|
||
|
d.name = m_corona->screenPool()->connector(knownId);
|
||
|
d.primary = knownId == 0;
|
||
|
d.enabled = false;
|
||
|
// TODO: add a m_corona->screenPool()->screenForId() method instead of this loop, but needs the whole primary screen tracking be moved from shellcorona
|
||
|
// to screenpool
|
||
|
for (QScreen *screen : qGuiApp->screens()) {
|
||
|
if (screen->name() == d.name) {
|
||
|
d.enabled = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
auto *conts = new ShellContainmentModel(m_corona, knownId, this);
|
||
|
conts->load();
|
||
|
|
||
|
// Exclude screens which don't have any containemnt assigned
|
||
|
if (conts->rowCount() > 0) {
|
||
|
m_containments.push_back(conts);
|
||
|
m_screens.push_back(d);
|
||
|
} else {
|
||
|
delete conts;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QList sortedIds = unknownScreenIds.values();
|
||
|
std::sort(sortedIds.begin(), sortedIds.end());
|
||
|
int i = 1;
|
||
|
for (int id : sortedIds) {
|
||
|
Data d;
|
||
|
d.id = id;
|
||
|
d.name = i18n("Unknown %1", i);
|
||
|
d.primary = id == 0;
|
||
|
d.enabled = false;
|
||
|
|
||
|
auto *conts = new ShellContainmentModel(m_corona, id, this);
|
||
|
conts->load();
|
||
|
m_containments.push_back(conts);
|
||
|
m_screens.push_back(d);
|
||
|
i++;
|
||
|
}
|
||
|
endResetModel();
|
||
|
}
|
||
|
|
||
|
// ---
|
||
|
|
||
|
ShellContainmentModel::ShellContainmentModel(ShellCorona *corona, int screenId, ScreenPoolModel *parent)
|
||
|
: QAbstractListModel(parent)
|
||
|
, m_screenId(screenId)
|
||
|
, m_corona(corona)
|
||
|
, m_screenPoolModel(parent)
|
||
|
, m_activityConsumer(new KActivities::Consumer(this))
|
||
|
{
|
||
|
m_reloadTimer = new QTimer(this);
|
||
|
m_reloadTimer->setSingleShot(true);
|
||
|
m_reloadTimer->setInterval(200);
|
||
|
|
||
|
connect(m_reloadTimer, &QTimer::timeout, this, &ShellContainmentModel::load);
|
||
|
|
||
|
connect(m_corona, &ShellCorona::startupCompleted, this, &ShellContainmentModel::load);
|
||
|
|
||
|
connect(m_corona, &Plasma::Corona::containmentAdded, m_reloadTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
|
||
|
connect(m_corona, &Plasma::Corona::screenOwnerChanged, m_reloadTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
|
||
|
|
||
|
connect(m_corona, &ShellCorona::containmentPreviewReady, this, [this](Plasma::Containment *containment, const QString &path) {
|
||
|
int i = 0;
|
||
|
for (auto &d : m_containments) {
|
||
|
if (d.containment == containment) {
|
||
|
d.image = path;
|
||
|
emit dataChanged(index(i, 0), index(i, 0));
|
||
|
break;
|
||
|
}
|
||
|
++i;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
ShellContainmentModel::~ShellContainmentModel() = default;
|
||
|
|
||
|
QVariant ShellContainmentModel::data(const QModelIndex &index, int role) const
|
||
|
{
|
||
|
if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= int(m_containments.size())) {
|
||
|
return QVariant();
|
||
|
}
|
||
|
const Data &d = m_containments.at(index.row());
|
||
|
switch (role) {
|
||
|
case Qt::DisplayRole:
|
||
|
return d.name;
|
||
|
case ContainmentIdRole:
|
||
|
return d.id;
|
||
|
case NameRole:
|
||
|
return d.name;
|
||
|
case ScreenRole:
|
||
|
return d.screen;
|
||
|
case EdgeRole:
|
||
|
return ShellContainmentModel::plasmaLocationToString(d.edge);
|
||
|
case EdgePositionRole:
|
||
|
return qMax(0, m_edgeCount.value(d.screen).value(d.edge).indexOf(d.id));
|
||
|
case PanelCountAtRightRole:
|
||
|
return qMax(0, m_edgeCount.value(d.screen).value(Plasma::Types::RightEdge).count());
|
||
|
case PanelCountAtTopRole:
|
||
|
return qMax(0, m_edgeCount.value(d.screen).value(Plasma::Types::TopEdge).count());
|
||
|
case PanelCountAtLeftRole:
|
||
|
return qMax(0, m_edgeCount.value(d.screen).value(Plasma::Types::LeftEdge).count());
|
||
|
case PanelCountAtBottomRole:
|
||
|
return qMax(0, m_edgeCount.value(d.screen).value(Plasma::Types::BottomEdge).count());
|
||
|
case ActivityRole:
|
||
|
{
|
||
|
const auto *activityInfo = m_activitiesInfos.value(d.activity);
|
||
|
if (activityInfo) {
|
||
|
return activityInfo->name();
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case IsActiveRole:
|
||
|
return d.isActive;
|
||
|
case ImageSourceRole:
|
||
|
return d.image;
|
||
|
case DestroyedRole:
|
||
|
return d.containment->destroyed();
|
||
|
}
|
||
|
return QVariant();
|
||
|
}
|
||
|
|
||
|
int ShellContainmentModel::rowCount(const QModelIndex &parent) const
|
||
|
{
|
||
|
if (parent.isValid()) {
|
||
|
return 0;
|
||
|
}
|
||
|
return m_containments.size();
|
||
|
}
|
||
|
|
||
|
QHash<int, QByteArray> ShellContainmentModel::roleNames() const
|
||
|
{
|
||
|
QHash<int, QByteArray> roles({{ContainmentIdRole, QByteArrayLiteral("containmentId")},
|
||
|
{NameRole, QByteArrayLiteral("name")},
|
||
|
{ScreenRole, QByteArrayLiteral("screen")},
|
||
|
{EdgeRole, QByteArrayLiteral("edge")},
|
||
|
{EdgePositionRole, QByteArrayLiteral("edgePosition")},
|
||
|
{PanelCountAtRightRole, QByteArrayLiteral("panelCountAtRight")},
|
||
|
{PanelCountAtTopRole, QByteArrayLiteral("panelCountAtTop")},
|
||
|
{PanelCountAtLeftRole, QByteArrayLiteral("panelCountAtLeft")},
|
||
|
{PanelCountAtBottomRole, QByteArrayLiteral("panelCountAtBottom")},
|
||
|
{ActivityRole, QByteArrayLiteral("activity")},
|
||
|
{IsActiveRole, QByteArrayLiteral("active")},
|
||
|
{ImageSourceRole, QByteArrayLiteral("imageSource")},
|
||
|
{DestroyedRole, QByteArrayLiteral("isDestroyed")}});
|
||
|
return roles;
|
||
|
}
|
||
|
|
||
|
ScreenPoolModel *ShellContainmentModel::screenPoolModel() const
|
||
|
{
|
||
|
return m_screenPoolModel;
|
||
|
}
|
||
|
|
||
|
void ShellContainmentModel::remove(int contId)
|
||
|
{
|
||
|
if (contId < 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
auto *cont = containmentById(contId);
|
||
|
if (cont) {
|
||
|
disconnect(cont, nullptr, this, nullptr);
|
||
|
// Don't call destroy directly, so we can have the undo action notification
|
||
|
auto *destroyAction = cont->actions()->action("remove");
|
||
|
if (destroyAction) {
|
||
|
destroyAction->trigger();
|
||
|
}
|
||
|
}
|
||
|
load();
|
||
|
}
|
||
|
|
||
|
void ShellContainmentModel::moveContainementToScreen(unsigned int contId, int newScreen)
|
||
|
{
|
||
|
if (contId == 0 || newScreen < 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
auto containmentIt = std::find_if(m_containments.begin(), m_containments.end(), [contId](Data &d) {
|
||
|
return d.id == contId;
|
||
|
});
|
||
|
if (containmentIt == m_containments.end()) {
|
||
|
return;
|
||
|
}
|
||
|
if (containmentIt->screen == newScreen) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
auto *cont = containmentById(contId);
|
||
|
if (cont == nullptr) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// If it's a panel, only move that one
|
||
|
if (cont->containmentType() == Plasma::Types::PanelContainment || cont->containmentType() == Plasma::Types::CustomPanelContainment) {
|
||
|
m_corona->setScreenForContainment(cont, newScreen);
|
||
|
} else {
|
||
|
// If it's a desktop, for now move all desktops for all activities
|
||
|
const int oldScreen = cont->screen() >= 0 ? cont->screen() : cont->lastScreen();
|
||
|
m_corona->swapDesktopScreens(oldScreen, newScreen);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool ShellContainmentModel::findContainment(unsigned int containmentId) const
|
||
|
{
|
||
|
return m_containments.cend() != std::find_if(m_containments.cbegin(), m_containments.cend(), [containmentId](const Data &d) {
|
||
|
return d.id == containmentId;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void ShellContainmentModel::load()
|
||
|
{
|
||
|
beginResetModel();
|
||
|
|
||
|
for (auto &d : m_containments) {
|
||
|
disconnect(d.containment, nullptr, this, nullptr);
|
||
|
}
|
||
|
m_containments.clear();
|
||
|
m_edgeCount.clear();
|
||
|
|
||
|
for (const auto *cont : m_corona->containments()) {
|
||
|
// Skip the systray
|
||
|
if (qobject_cast<Plasma::Applet *>(cont->parent())) {
|
||
|
continue;
|
||
|
}
|
||
|
// Only allow current activity for now (panels always go in)
|
||
|
if (cont->containmentType() != Plasma::Types::PanelContainment && cont->containmentType() != Plasma::Types::CustomPanelContainment
|
||
|
&& cont->activity() != m_activityConsumer->currentActivity()) {
|
||
|
continue;
|
||
|
}
|
||
|
if (!m_edgeCount.contains(cont->lastScreen())) {
|
||
|
m_edgeCount[cont->lastScreen()] = QHash<Plasma::Types::Location, QList<int>>();
|
||
|
m_edgeCount[cont->lastScreen()][cont->location()] = QList<int>();
|
||
|
}
|
||
|
m_edgeCount[cont->lastScreen()][cont->location()].append(cont->id());
|
||
|
m_corona->grabContainmentPreview(const_cast<Plasma::Containment *>(cont));
|
||
|
Data d;
|
||
|
d.id = cont->id();
|
||
|
d.name = cont->title() + " (" + ShellContainmentModel::containmentTypeToString(cont->containmentType()) + ")";
|
||
|
d.screen = cont->lastScreen();
|
||
|
d.edge = cont->location();
|
||
|
d.activity = cont->activity();
|
||
|
d.isActive = cont->screen() != -1;
|
||
|
d.containment = cont;
|
||
|
d.image = containmentPreview(const_cast<Plasma::Containment *>(cont));
|
||
|
|
||
|
if (cont->lastScreen() == m_screenId || (cont->lastScreen() == -1 && cont->screen() == m_screenId)) {
|
||
|
m_containments.push_back(d);
|
||
|
connect(cont, &QObject::destroyed, this, &ShellContainmentModel::load);
|
||
|
connect(cont, &Plasma::Containment::destroyedChanged, this, &ShellContainmentModel::load);
|
||
|
connect(cont, &Plasma::Containment::locationChanged, this, &ShellContainmentModel::load);
|
||
|
}
|
||
|
}
|
||
|
endResetModel();
|
||
|
}
|
||
|
|
||
|
void ShellContainmentModel::loadActivitiesInfos()
|
||
|
{
|
||
|
beginResetModel();
|
||
|
for (const auto &cont : m_containments) {
|
||
|
const auto activitId = cont.activity;
|
||
|
if (activitId.isEmpty()) {
|
||
|
continue;
|
||
|
}
|
||
|
auto *activityInfo = new KActivities::Info(cont.activity, this);
|
||
|
if (activityInfo) {
|
||
|
if (!m_activitiesInfos.value(cont.activity)) {
|
||
|
m_activitiesInfos[cont.activity] = activityInfo;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
endResetModel();
|
||
|
}
|
||
|
|
||
|
QString ShellContainmentModel::plasmaLocationToString(Plasma::Types::Location location)
|
||
|
{
|
||
|
switch (location) {
|
||
|
case Plasma::Types::Floating:
|
||
|
return QStringLiteral("floating");
|
||
|
case Plasma::Types::Desktop:
|
||
|
return QStringLiteral("desktop");
|
||
|
case Plasma::Types::FullScreen:
|
||
|
return QStringLiteral("Full Screen");
|
||
|
case Plasma::Types::TopEdge:
|
||
|
return QStringLiteral("top");
|
||
|
case Plasma::Types::BottomEdge:
|
||
|
return QStringLiteral("bottom");
|
||
|
case Plasma::Types::LeftEdge:
|
||
|
return QStringLiteral("left");
|
||
|
case Plasma::Types::RightEdge:
|
||
|
return QStringLiteral("right");
|
||
|
default:
|
||
|
return QString("unknown");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QString ShellContainmentModel::containmentTypeToString(Plasma::Types::ContainmentType containmentType)
|
||
|
{
|
||
|
switch (containmentType) {
|
||
|
case Plasma::Types::DesktopContainment: /**< A desktop containment */
|
||
|
return QStringLiteral("Desktop");
|
||
|
case Plasma::Types::PanelContainment: /**< A desktop panel */
|
||
|
return QStringLiteral("Panel");
|
||
|
case Plasma::Types::CustomContainment: /**< A containment that is neither a desktop nor a panel
|
||
|
but something application specific */
|
||
|
return QStringLiteral("Custom");
|
||
|
case Plasma::Types::CustomPanelContainment: /**< A customized desktop panel */
|
||
|
return QStringLiteral("Custom Desktop");
|
||
|
case Plasma::Types::CustomEmbeddedContainment: /**< A customized containment embedded in another applet */
|
||
|
return QStringLiteral("Embedded");
|
||
|
default:
|
||
|
return QStringLiteral("Unknown");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Plasma::Containment *ShellContainmentModel::containmentById(unsigned int id)
|
||
|
{
|
||
|
for (auto *cont : m_corona->containments()) {
|
||
|
if (cont->id() == id) {
|
||
|
return cont;
|
||
|
}
|
||
|
}
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
QString ShellContainmentModel::containmentPreview(Plasma::Containment *containment)
|
||
|
{
|
||
|
QString savedThumbnail = m_corona->containmentPreviewPath(containment);
|
||
|
|
||
|
if (!savedThumbnail.isEmpty()) {
|
||
|
return savedThumbnail;
|
||
|
}
|
||
|
|
||
|
m_corona->grabContainmentPreview(containment);
|
||
|
|
||
|
// If not found, try to understand the configured wallpaper for the containment, assuming is using the Image plugin
|
||
|
KSharedConfig::Ptr conf = KSharedConfig::openConfig(QLatin1String("plasma-") + m_corona->shell() + QLatin1String("-appletsrc"), KConfig::SimpleConfig);
|
||
|
KConfigGroup containmentsGroup(conf, "Containments");
|
||
|
KConfigGroup config = containmentsGroup.group(QString::number(containment->id()));
|
||
|
auto wallpaperPlugin = config.readEntry("wallpaperplugin");
|
||
|
auto wallpaperConfig = config.group("Wallpaper").group(wallpaperPlugin).group("General");
|
||
|
|
||
|
if (wallpaperConfig.hasKey("Image")) {
|
||
|
// Trying for the wallpaper
|
||
|
auto wallpaper = wallpaperConfig.readEntry("Image", QString());
|
||
|
if (!wallpaper.isEmpty()) {
|
||
|
return wallpaper;
|
||
|
}
|
||
|
}
|
||
|
if (wallpaperConfig.hasKey("Color")) {
|
||
|
auto backgroundColor = wallpaperConfig.readEntry("Color", QColor(0, 0, 0));
|
||
|
return backgroundColor.name();
|
||
|
}
|
||
|
|
||
|
return QString();
|
||
|
}
|
||
|
|
||
|
// ---
|
||
|
|
||
|
ShellContainmentConfig::ShellContainmentConfig(ShellCorona *corona, QWindow *parent)
|
||
|
: QQmlApplicationEngine(parent)
|
||
|
, m_corona(corona)
|
||
|
, m_model(nullptr)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
ShellContainmentConfig::~ShellContainmentConfig() = default;
|
||
|
|
||
|
void ShellContainmentConfig::init()
|
||
|
{
|
||
|
m_model = new ScreenPoolModel(m_corona, this);
|
||
|
m_model->load();
|
||
|
|
||
|
auto *localizedContext = new KLocalizedContext(this);
|
||
|
localizedContext->setTranslationDomain(QStringLiteral("plasma_shell_") + m_corona->shell());
|
||
|
|
||
|
rootContext()->setContextObject(localizedContext);
|
||
|
rootContext()->setContextProperty(QStringLiteral("ShellContainmentModel"), m_model);
|
||
|
load(m_corona->kPackage().fileUrl("containmentmanagementui"));
|
||
|
|
||
|
if (!rootObjects().isEmpty()) {
|
||
|
auto *obj = qobject_cast<QWindow *>(rootObjects().first());
|
||
|
connect(obj, &QWindow::visibleChanged, this, [this, obj]() {
|
||
|
deleteLater();
|
||
|
});
|
||
|
}
|
||
|
}
|