KWin
Loading...
Searching...
No Matches
tilemanager.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2022 Marco Martin <mart@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10#include "tilemanager.h"
11#include "core/output.h"
12#include "quicktile.h"
13#include "virtualdesktops.h"
14#include "workspace.h"
15
16#include <KConfigGroup>
17#include <KLocalizedString>
18#include <KSharedConfig>
19
20#include <QJsonArray>
21#include <QJsonDocument>
22#include <QJsonObject>
23#include <QTimer>
24
25namespace KWin
26{
27
28QDebug operator<<(QDebug debug, const TileManager *tileManager)
29{
30 if (tileManager) {
31 QList<Tile *> tiles({tileManager->rootTile()});
32 QList<Tile *> tilePath;
33 QString indent(QStringLiteral("|-"));
34 debug << tileManager->metaObject()->className() << '(' << static_cast<const void *>(tileManager) << ')' << '\n';
35 while (!tiles.isEmpty()) {
36 auto *tile = tiles.last();
37 tiles.pop_back();
38 debug << indent << qobject_cast<CustomTile *>(tile) << '\n';
39 if (tile->childCount() > 0) {
40 tiles.append(tile->childTiles());
41 tilePath.append(tile->childTiles().first());
42 indent.prepend(QStringLiteral("| "));
43 }
44 if (!tilePath.isEmpty() && tile == tilePath.last()) {
45 tilePath.pop_back();
46 indent.remove(0, 2);
47 }
48 }
49
50 } else {
51 debug << "Tile(0x0)";
52 }
53 return debug;
54}
55
57 : QObject(parent)
58 , m_output(parent)
59 , m_tileModel(new TileModel(this))
60{
61 m_saveTimer = std::make_unique<QTimer>(this);
62 m_saveTimer->setSingleShot(true);
63 m_saveTimer->setInterval(2000);
64 connect(m_saveTimer.get(), &QTimer::timeout, this, &TileManager::saveSettings);
65
66 m_rootTile = std::make_unique<RootTile>(this);
67 m_rootTile->setRelativeGeometry(QRectF(0, 0, 1, 1));
68 connect(m_rootTile.get(), &CustomTile::paddingChanged, m_saveTimer.get(), static_cast<void (QTimer::*)()>(&QTimer::start));
69 connect(m_rootTile.get(), &CustomTile::layoutModified, m_saveTimer.get(), static_cast<void (QTimer::*)()>(&QTimer::start));
70
71 m_quickRootTile = std::make_unique<QuickRootTile>(this);
72
73 readSettings();
74}
75
79
81{
82 return m_output;
83}
84
86{
87 const auto tiles = m_rootTile->descendants();
88 qreal minimumDistance = std::numeric_limits<qreal>::max();
89 Tile *ret = nullptr;
90
91 for (auto *t : tiles) {
92 if (!t->isLayout()) {
93 const auto r = t->absoluteGeometry();
94 // It's possible for tiles to overlap, so take the one which center is nearer to mouse pos
95 qreal distance = (r.center() - pos).manhattanLength();
96 if (!exclusiveContains(r, pos)) {
97 // This gives a strong preference for tiles that contain the point
98 // still base on distance though as floating tiles can overlap
99 distance += m_output->geometryF().width();
100 }
101 if (distance < minimumDistance) {
102 minimumDistance = distance;
103 ret = t;
104 }
105 }
106 }
107 return ret;
108}
109
111{
112 return bestTileForPosition({x, y});
113}
114
116{
117 return m_rootTile.get();
118}
119
120Tile *TileManager::quickTile(QuickTileMode mode) const
121{
122 return m_quickRootTile->tileForMode(mode);
123}
124
126{
127 return m_tileModel.get();
128}
129
131{
132 if (dir == QStringLiteral("horizontal")) {
134 } else if (dir == QStringLiteral("vertical")) {
136 } else {
138 }
139}
140
141CustomTile *TileManager::parseTilingJSon(const QJsonValue &val, const QRectF &availableArea, CustomTile *parentTile)
142{
143 if (availableArea.isEmpty()) {
144 return nullptr;
145 }
146
147 if (val.isObject()) {
148 const auto &obj = val.toObject();
149 CustomTile *createdTile = nullptr;
150
151 if (parentTile->layoutDirection() == Tile::LayoutDirection::Horizontal) {
152 QRectF rect = availableArea;
153 const auto width = obj.value(QStringLiteral("width"));
154 if (width.isDouble()) {
155 rect.setWidth(std::min(width.toDouble(), availableArea.width()));
156 }
157 if (!rect.isEmpty()) {
158 createdTile = parentTile->createChildAt(rect, parentTile->layoutDirection(), parentTile->childCount());
159 }
160
161 } else if (parentTile->layoutDirection() == Tile::LayoutDirection::Vertical) {
162 QRectF rect = availableArea;
163 const auto height = obj.value(QStringLiteral("height"));
164 if (height.isDouble()) {
165 rect.setHeight(std::min(height.toDouble(), availableArea.height()));
166 }
167 if (!rect.isEmpty()) {
168 createdTile = parentTile->createChildAt(rect, parentTile->layoutDirection(), parentTile->childCount());
169 }
170
171 } else if (parentTile->layoutDirection() == Tile::LayoutDirection::Floating) {
172 QRectF rect(0, 0, 1, 1);
173 rect = QRectF(obj.value(QStringLiteral("x")).toDouble(),
174 obj.value(QStringLiteral("y")).toDouble(),
175 obj.value(QStringLiteral("width")).toDouble(),
176 obj.value(QStringLiteral("height")).toDouble());
177
178 if (!rect.isEmpty()) {
179 createdTile = parentTile->createChildAt(rect, parentTile->layoutDirection(), parentTile->childCount());
180 }
181 }
182
183 if (createdTile && obj.contains(QStringLiteral("tiles"))) {
184 // It's a layout
185 const auto arr = obj.value(QStringLiteral("tiles"));
186 const auto direction = obj.value(QStringLiteral("layoutDirection"));
187 // Ignore arrays with only a single item in it
188 if (arr.isArray() && arr.toArray().count() > 0) {
189 const Tile::LayoutDirection dir = strToLayoutDirection(direction.toString());
190 createdTile->setLayoutDirection(dir);
191 parseTilingJSon(arr, createdTile->relativeGeometry(), createdTile);
192 }
193 }
194 return createdTile;
195 } else if (val.isArray()) {
196 const auto arr = val.toArray();
197 auto avail = availableArea;
198 for (auto it = arr.cbegin(); it != arr.cend(); it++) {
199 if ((*it).isObject()) {
200 auto *tile = parseTilingJSon(*it, avail, parentTile);
201 if (tile && parentTile->layoutDirection() == Tile::LayoutDirection::Horizontal) {
202 avail.setLeft(tile->relativeGeometry().right());
203 } else if (tile && parentTile->layoutDirection() == Tile::LayoutDirection::Vertical) {
204 avail.setTop(tile->relativeGeometry().bottom());
205 }
206 }
207 }
208 // make sure the children fill exactly the parent, eventually enlarging the last
209 if (parentTile->layoutDirection() != Tile::LayoutDirection::Floating
210 && parentTile->childCount() > 0) {
211 auto *last = parentTile->childTile(parentTile->childCount() - 1);
212 auto geom = last->relativeGeometry();
213 geom.setRight(parentTile->relativeGeometry().right());
214 last->setRelativeGeometry(geom);
215 }
216 return nullptr;
217 }
218 return nullptr;
219}
220
221void TileManager::readSettings()
222{
223 KConfigGroup cg = kwinApp()->config()->group(QStringLiteral("Tiling"));
224 qreal padding = cg.readEntry("padding", 4);
225 cg = KConfigGroup(&cg, m_output->uuid().toString(QUuid::WithoutBraces));
226
227 auto createDefaultSetup = [this]() {
228 Q_ASSERT(m_rootTile->childCount() == 0);
229 // If empty create an horizontal 3 columns layout
230 m_rootTile->setLayoutDirection(Tile::LayoutDirection::Horizontal);
231 m_rootTile->split(Tile::LayoutDirection::Horizontal);
232 static_cast<CustomTile *>(m_rootTile->childTile(0))->split(Tile::LayoutDirection::Horizontal);
233 Q_ASSERT(m_rootTile->childCount() == 3);
234 // Resize middle column, the other two will be auto resized accordingly
235 m_rootTile->childTile(1)->setRelativeGeometry({0.25, 0.0, 0.5, 1.0});
236 };
237
238 QJsonParseError error;
239 const auto tiles = cg.readEntry("tiles", QByteArray());
240 if (tiles.isEmpty()) {
241 qCDebug(KWIN_CORE) << "Empty tiles configuration for monitor" << m_output->uuid().toString(QUuid::WithoutBraces) << ":"
242 << "Creating default setup";
243 createDefaultSetup();
244 return;
245 }
246 QJsonDocument doc = QJsonDocument::fromJson(tiles, &error);
247
248 if (error.error != QJsonParseError::NoError) {
249 qCWarning(KWIN_CORE) << "Parse error in tiles configuration for monitor" << m_output->uuid().toString(QUuid::WithoutBraces) << ":" << error.errorString() << "Creating default setup";
250 createDefaultSetup();
251 return;
252 }
253
254 if (doc.object().contains(QStringLiteral("tiles"))) {
255 const auto arr = doc.object().value(QStringLiteral("tiles"));
256 if (arr.isArray() && arr.toArray().count() > 0) {
257 m_rootTile->setLayoutDirection(strToLayoutDirection(doc.object().value(QStringLiteral("layoutDirection")).toString()));
258 parseTilingJSon(arr, QRectF(0, 0, 1, 1), m_rootTile.get());
259 }
260 }
261
262 m_rootTile->setPadding(padding);
263}
264
265QJsonObject TileManager::tileToJSon(CustomTile *tile)
266{
267 QJsonObject obj;
268
269 auto *parentTile = static_cast<CustomTile *>(tile->parentTile());
270
271 // Exclude the root and the two children
272 if (parentTile) {
273 switch (parentTile->layoutDirection()) {
275 obj[QStringLiteral("width")] = tile->relativeGeometry().width();
276 break;
278 obj[QStringLiteral("height")] = tile->relativeGeometry().height();
279 break;
281 default:
282 obj[QStringLiteral("x")] = tile->relativeGeometry().x();
283 obj[QStringLiteral("y")] = tile->relativeGeometry().y();
284 obj[QStringLiteral("width")] = tile->relativeGeometry().width();
285 obj[QStringLiteral("height")] = tile->relativeGeometry().height();
286 }
287 }
288
289 if (tile->isLayout()) {
290 switch (tile->layoutDirection()) {
292 obj[QStringLiteral("layoutDirection")] = QStringLiteral("horizontal");
293 break;
295 obj[QStringLiteral("layoutDirection")] = QStringLiteral("vertical");
296 break;
298 default:
299 obj[QStringLiteral("layoutDirection")] = QStringLiteral("floating");
300 }
301
302 QJsonArray tiles;
303 const int nChildren = tile->childCount();
304 for (int i = 0; i < nChildren; ++i) {
305 tiles.append(tileToJSon(static_cast<CustomTile *>(tile->childTile(i))));
306 }
307 obj[QStringLiteral("tiles")] = tiles;
308 }
309
310 return obj;
311}
312
313void TileManager::saveSettings()
314{
315 auto obj = tileToJSon(m_rootTile.get());
316 QJsonDocument doc(obj);
317 KConfigGroup cg = kwinApp()->config()->group(QStringLiteral("Tiling"));
318 cg.writeEntry("padding", m_rootTile->padding());
319 cg = KConfigGroup(&cg, m_output->uuid().toString(QUuid::WithoutBraces));
320 cg.writeEntry("tiles", doc.toJson(QJsonDocument::Compact));
321 cg.sync(); // FIXME: less frequent?
322}
323
324} // namespace KWin
325
326#include "moc_tilemanager.cpp"
QRectF geometryF() const
Definition output.cpp:465
QUuid uuid() const
Definition output.cpp:364
QRectF absoluteGeometry
Definition tile.h:29
void paddingChanged(qreal padding)
LayoutDirection
Definition tile.h:40
TileManager(Output *parent=nullptr)
Output * output() const
friend class CustomTile
Definition tilemanager.h:72
KWin::Tile * rootTile
Definition tilemanager.h:40
KWin::Tile * quickTile(QuickTileMode mode) const
~TileManager() override
TileModel * model
Definition tilemanager.h:41
KWin::Tile * bestTileForPosition(const QPointF &pos)
Tile::LayoutDirection strToLayoutDirection(const QString &dir)
QDebug & operator<<(QDebug &s, const KWin::DrmConnector *obj)