Files
RedBear-OS/local/recipes/kde/kwin/source/src/tiles/tilemanager.cpp
T

333 lines
12 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "tilemanager.h"
#include "core/output.h"
#include "quicktile.h"
#include "virtualdesktops.h"
#include "workspace.h"
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTimer>
namespace KWin
{
QDebug operator<<(QDebug debug, const TileManager *tileManager)
{
if (tileManager) {
QList<Tile *> tiles({tileManager->rootTile()});
QList<Tile *> tilePath;
QString indent(QStringLiteral("|-"));
debug << tileManager->metaObject()->className() << '(' << static_cast<const void *>(tileManager) << ')' << '\n';
while (!tiles.isEmpty()) {
auto *tile = tiles.last();
tiles.pop_back();
debug << indent << qobject_cast<CustomTile *>(tile) << '\n';
if (tile->childCount() > 0) {
tiles.append(tile->childTiles());
tilePath.append(tile->childTiles().first());
indent.prepend(QStringLiteral("| "));
}
if (!tilePath.isEmpty() && tile == tilePath.last()) {
tilePath.pop_back();
indent.remove(0, 2);
}
}
} else {
debug << "Tile(0x0)";
}
return debug;
}
TileManager::TileManager(Output *parent)
: QObject(parent)
, m_output(parent)
, m_tileModel(new TileModel(this))
{
m_saveTimer = std::make_unique<QTimer>(this);
m_saveTimer->setSingleShot(true);
m_saveTimer->setInterval(2000);
connect(m_saveTimer.get(), &QTimer::timeout, this, &TileManager::saveSettings);
m_rootTile = std::make_unique<RootTile>(this);
m_rootTile->setRelativeGeometry(QRectF(0, 0, 1, 1));
connect(m_rootTile.get(), &CustomTile::paddingChanged, m_saveTimer.get(), static_cast<void (QTimer::*)()>(&QTimer::start));
connect(m_rootTile.get(), &CustomTile::layoutModified, m_saveTimer.get(), static_cast<void (QTimer::*)()>(&QTimer::start));
m_quickRootTile = std::make_unique<QuickRootTile>(this);
readSettings();
}
TileManager::~TileManager()
{
m_tearingDown = true;
}
bool TileManager::tearingDown() const
{
return m_tearingDown;
}
Output *TileManager::output() const
{
return m_output;
}
Tile *TileManager::bestTileForPosition(const QPointF &pos)
{
const auto tiles = m_rootTile->descendants();
qreal minimumDistance = std::numeric_limits<qreal>::max();
Tile *ret = nullptr;
for (auto *t : tiles) {
if (!t->isLayout()) {
const auto r = t->absoluteGeometry();
// It's possible for tiles to overlap, so take the one which center is nearer to mouse pos
qreal distance = (r.center() - pos).manhattanLength();
if (!exclusiveContains(r, pos)) {
// This gives a strong preference for tiles that contain the point
// still base on distance though as floating tiles can overlap
distance += m_output->geometryF().width();
}
if (distance < minimumDistance) {
minimumDistance = distance;
ret = t;
}
}
}
return ret;
}
Tile *TileManager::bestTileForPosition(qreal x, qreal y)
{
return bestTileForPosition({x, y});
}
CustomTile *TileManager::rootTile() const
{
return m_rootTile.get();
}
Tile *TileManager::quickTile(QuickTileMode mode) const
{
return m_quickRootTile->tileForMode(mode);
}
TileModel *TileManager::model() const
{
return m_tileModel.get();
}
Tile::LayoutDirection strToLayoutDirection(const QString &dir)
{
if (dir == QLatin1StringView("horizontal")) {
return Tile::LayoutDirection::Horizontal;
} else if (dir == QLatin1StringView("vertical")) {
return Tile::LayoutDirection::Vertical;
} else {
return Tile::LayoutDirection::Floating;
}
}
CustomTile *TileManager::parseTilingJSon(const QJsonValue &val, const QRectF &availableArea, CustomTile *parentTile)
{
if (availableArea.isEmpty()) {
return nullptr;
}
if (val.isObject()) {
const auto &obj = val.toObject();
CustomTile *createdTile = nullptr;
if (parentTile->layoutDirection() == Tile::LayoutDirection::Horizontal) {
QRectF rect = availableArea;
const auto width = obj.value(QStringLiteral("width"));
if (width.isDouble()) {
rect.setWidth(std::min(width.toDouble(), availableArea.width()));
}
if (!rect.isEmpty()) {
createdTile = parentTile->createChildAt(rect, parentTile->layoutDirection(), parentTile->childCount());
}
} else if (parentTile->layoutDirection() == Tile::LayoutDirection::Vertical) {
QRectF rect = availableArea;
const auto height = obj.value(QStringLiteral("height"));
if (height.isDouble()) {
rect.setHeight(std::min(height.toDouble(), availableArea.height()));
}
if (!rect.isEmpty()) {
createdTile = parentTile->createChildAt(rect, parentTile->layoutDirection(), parentTile->childCount());
}
} else if (parentTile->layoutDirection() == Tile::LayoutDirection::Floating) {
QRectF rect(0, 0, 1, 1);
rect = QRectF(obj.value(QStringLiteral("x")).toDouble(),
obj.value(QStringLiteral("y")).toDouble(),
obj.value(QStringLiteral("width")).toDouble(),
obj.value(QStringLiteral("height")).toDouble());
if (!rect.isEmpty()) {
createdTile = parentTile->createChildAt(rect, parentTile->layoutDirection(), parentTile->childCount());
}
}
if (createdTile && obj.contains(QStringLiteral("tiles"))) {
// It's a layout
const auto arr = obj.value(QStringLiteral("tiles"));
const auto direction = obj.value(QStringLiteral("layoutDirection"));
// Ignore arrays with only a single item in it
if (arr.isArray() && arr.toArray().count() > 0) {
const Tile::LayoutDirection dir = strToLayoutDirection(direction.toString());
createdTile->setLayoutDirection(dir);
parseTilingJSon(arr, createdTile->relativeGeometry(), createdTile);
}
}
return createdTile;
} else if (val.isArray()) {
const auto arr = val.toArray();
auto avail = availableArea;
for (auto it = arr.cbegin(); it != arr.cend(); it++) {
if ((*it).isObject()) {
auto *tile = parseTilingJSon(*it, avail, parentTile);
if (tile && parentTile->layoutDirection() == Tile::LayoutDirection::Horizontal) {
avail.setLeft(tile->relativeGeometry().right());
} else if (tile && parentTile->layoutDirection() == Tile::LayoutDirection::Vertical) {
avail.setTop(tile->relativeGeometry().bottom());
}
}
}
// make sure the children fill exactly the parent, eventually enlarging the last
if (parentTile->layoutDirection() != Tile::LayoutDirection::Floating
&& parentTile->childCount() > 0) {
auto *last = parentTile->childTile(parentTile->childCount() - 1);
auto geom = last->relativeGeometry();
geom.setRight(parentTile->relativeGeometry().right());
last->setRelativeGeometry(geom);
}
return nullptr;
}
return nullptr;
}
void TileManager::readSettings()
{
KConfigGroup cg = kwinApp()->config()->group(QStringLiteral("Tiling"));
qreal padding = cg.readEntry("padding", 4);
cg = KConfigGroup(&cg, m_output->uuid().toString(QUuid::WithoutBraces));
auto createDefaultSetup = [this]() {
Q_ASSERT(m_rootTile->childCount() == 0);
// If empty create an horizontal 3 columns layout
m_rootTile->setLayoutDirection(Tile::LayoutDirection::Horizontal);
m_rootTile->split(Tile::LayoutDirection::Horizontal);
static_cast<CustomTile *>(m_rootTile->childTile(0))->split(Tile::LayoutDirection::Horizontal);
Q_ASSERT(m_rootTile->childCount() == 3);
// Resize middle column, the other two will be auto resized accordingly
m_rootTile->childTile(1)->setRelativeGeometry({0.25, 0.0, 0.5, 1.0});
};
QJsonParseError error;
const auto tiles = cg.readEntry("tiles", QByteArray());
if (tiles.isEmpty()) {
qCDebug(KWIN_CORE) << "Empty tiles configuration for monitor" << m_output->uuid().toString(QUuid::WithoutBraces) << ":"
<< "Creating default setup";
createDefaultSetup();
return;
}
QJsonDocument doc = QJsonDocument::fromJson(tiles, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(KWIN_CORE) << "Parse error in tiles configuration for monitor" << m_output->uuid().toString(QUuid::WithoutBraces) << ":" << error.errorString() << "Creating default setup";
createDefaultSetup();
return;
}
if (doc.object().contains(QStringLiteral("tiles"))) {
const auto arr = doc.object().value(QStringLiteral("tiles"));
if (arr.isArray() && arr.toArray().count() > 0) {
m_rootTile->setLayoutDirection(strToLayoutDirection(doc.object().value(QStringLiteral("layoutDirection")).toString()));
parseTilingJSon(arr, QRectF(0, 0, 1, 1), m_rootTile.get());
}
}
m_rootTile->setPadding(padding);
}
QJsonObject TileManager::tileToJSon(CustomTile *tile)
{
QJsonObject obj;
auto *parentTile = static_cast<CustomTile *>(tile->parentTile());
// Exclude the root and the two children
if (parentTile) {
switch (parentTile->layoutDirection()) {
case Tile::LayoutDirection::Horizontal:
obj[QStringLiteral("width")] = tile->relativeGeometry().width();
break;
case Tile::LayoutDirection::Vertical:
obj[QStringLiteral("height")] = tile->relativeGeometry().height();
break;
case Tile::LayoutDirection::Floating:
default:
obj[QStringLiteral("x")] = tile->relativeGeometry().x();
obj[QStringLiteral("y")] = tile->relativeGeometry().y();
obj[QStringLiteral("width")] = tile->relativeGeometry().width();
obj[QStringLiteral("height")] = tile->relativeGeometry().height();
}
}
if (tile->isLayout()) {
switch (tile->layoutDirection()) {
case Tile::LayoutDirection::Horizontal:
obj[QStringLiteral("layoutDirection")] = QStringLiteral("horizontal");
break;
case Tile::LayoutDirection::Vertical:
obj[QStringLiteral("layoutDirection")] = QStringLiteral("vertical");
break;
case Tile::LayoutDirection::Floating:
default:
obj[QStringLiteral("layoutDirection")] = QStringLiteral("floating");
}
QJsonArray tiles;
const int nChildren = tile->childCount();
for (int i = 0; i < nChildren; ++i) {
tiles.append(tileToJSon(static_cast<CustomTile *>(tile->childTile(i))));
}
obj[QStringLiteral("tiles")] = tiles;
}
return obj;
}
void TileManager::saveSettings()
{
auto obj = tileToJSon(m_rootTile.get());
QJsonDocument doc(obj);
KConfigGroup cg = kwinApp()->config()->group(QStringLiteral("Tiling"));
cg.writeEntry("padding", m_rootTile->padding());
cg = KConfigGroup(&cg, m_output->uuid().toString(QUuid::WithoutBraces));
cg.writeEntry("tiles", doc.toJson(QJsonDocument::Compact));
cg.sync(); // FIXME: less frequent?
}
} // namespace KWin
#include "moc_tilemanager.cpp"