You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
485 lines
16 KiB
485 lines
16 KiB
/* |
|
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 "panelview.h" |
|
#include "screenpool.h" |
|
#include "shellcorona.h" |
|
#include <chrono> |
|
|
|
using namespace std::chrono_literals; |
|
|
|
ScreenPoolModel::ScreenPoolModel(ShellCorona *corona, QObject *parent) |
|
: QAbstractListModel(parent) |
|
, m_corona(corona) |
|
{ |
|
m_reloadTimer = new QTimer(this); |
|
m_reloadTimer->setSingleShot(true); |
|
m_reloadTimer->setInterval(200ms); |
|
|
|
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(200ms); |
|
|
|
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(); |
|
}); |
|
} |
|
}
|
|
|