Krita Source Code Documentation
Loading...
Searching...
No Matches
KisWindowLayoutResource.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2018 Jouni Pentikäinen <joupent@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
8
9#include <QVector>
10#include <QList>
11#include <QFile>
12#include <QDomDocument>
13#include <QApplication>
14#include <QEventLoop>
15#include <QMessageBox>
16#include <QWindow>
17#include <QScreen>
18
19#include <KisPart.h>
20#include <KisDocument.h>
21#include <kis_dom_utils.h>
22#include <KisMainWindow.h>
23
24static const int WINDOW_LAYOUT_VERSION = 1;
25
27{
29 int screen = -1;
30 Qt::WindowStates stateFlags = Qt::WindowNoState;
31 QByteArray data;
32
33 static WindowGeometry fromWindow(const QWidget *window, QList<QScreen*> screens)
34 {
35 WindowGeometry geometry;
36 QWindow *windowHandle = window->windowHandle();
37
38 geometry.data = window->saveGeometry();
39 geometry.stateFlags = windowHandle->windowState();
40
41 int index = screens.indexOf(windowHandle->screen());
42 if (index >= 0) {
43 geometry.screen = index;
44 }
45
46 return geometry;
47 }
48
49 void forceOntoCorrectScreen(QWidget *window, QList<QScreen*> screens)
50 {
51 QWindow *windowHandle = window->windowHandle();
52
53 if (screens.indexOf(windowHandle->screen()) != screen) {
54 QScreen *qScreen = screens[screen];
55 windowHandle->setScreen(qScreen);
56 windowHandle->setPosition(qScreen->availableGeometry().topLeft());
57 }
58
59 if (stateFlags) {
60 window->setWindowState(stateFlags);
61 }
62 }
63
64 void save(QDomDocument &doc, QDomElement &elem) const
65 {
66 if (screen >= 0) {
67 elem.setAttribute("screen", screen);
68 }
69
70 if (stateFlags & Qt::WindowMaximized) {
71 elem.setAttribute("maximized", "1");
72 }
73
74 QDomElement geometry = doc.createElement("geometry");
75 geometry.appendChild(doc.createCDATASection(data.toBase64()));
76 elem.appendChild(geometry);
77 }
78
79 static WindowGeometry load(const QDomElement &element)
80 {
81 WindowGeometry geometry;
82 geometry.screen = element.attribute("screen", "-1").toInt();
83
84 if (element.attribute("maximized", "0") != "0") {
85 geometry.stateFlags |= Qt::WindowMaximized;
86 }
87
88 QDomElement dataElement = element.firstChildElement("geometry");
89 geometry.data = QByteArray::fromBase64(dataElement.text().toLatin1());
90
91 return geometry;
92 }
93 };
94
103
108
109 Private() = default;
110 Private(const Private &rhs) = default;
111
112
114 : windows(std::move(windows))
115 {}
116
118 auto *kisPart = KisPart::instance();
119
120 Q_FOREACH(const Window &window, windows) {
121 QPointer<KisMainWindow> mainWindow = kisPart->windowById(window.windowId);
122
123 if (mainWindow.isNull()) {
124 mainWindow = kisPart->createMainWindow(window.windowId);
125 currentWindows.append(mainWindow);
126 mainWindow->show();
127 }
128 }
129
130 QVector<QPointer<KisMainWindow>> windowsToMigrate;
131
132 Q_FOREACH(KisMainWindow *mainWindow, currentWindows) {
133 bool keep = false;
134 Q_FOREACH(const Window &window, windows) {
135 if (window.windowId == mainWindow->id()) {
136 keep = true;
137 break;
138 }
139 }
140
141 if (!keep) {
142 windowsToMigrate.append(mainWindow);
143
144 // Set the window hidden to prevent "show image in all windows" feature from opening new views on it
145 // while we migrate views onto the remaining windows
146 if (mainWindow->isVisible()) {
147 mainWindow->hide();
148 }
149 }
150 }
151
152 migrateViewsFromClosingWindows(windowsToMigrate);
153 }
154
156 QVector<QPointer<KisMainWindow>> windowsToClose;
157
158 Q_FOREACH(KisMainWindow *mainWindow, currentWindows) {
159 bool keep = false;
160 Q_FOREACH(const Window &window, windows) {
161 if (window.windowId == mainWindow->id()) {
162 keep = true;
163 break;
164 }
165 }
166
167 if (!keep) {
168 mainWindow->close();
169 }
170 }
171 }
172
174 {
175 auto *kisPart = KisPart::instance();
176 KisMainWindow *migrationTarget = nullptr;
177
178 Q_FOREACH(KisMainWindow *mainWindow, kisPart->mainWindows()) {
179 if (!closingWindows.contains(mainWindow)) {
180 migrationTarget = mainWindow;
181 break;
182 }
183 }
184
185 if (!migrationTarget) {
186 qWarning() << "Problem: window layout with no windows would leave user with zero main windows.";
187 migrationTarget = closingWindows.takeLast();
188 migrationTarget->show();
189 }
190
191 QVector<KisDocument*> visibleDocuments;
192 Q_FOREACH(KisView *view, kisPart->views()) {
193 KisMainWindow *window = view->mainWindow();
194 if (!closingWindows.contains(window)) {
195 visibleDocuments.append(view->document());
196 }
197 }
198
199 Q_FOREACH(KisDocument *document, kisPart->documents()) {
200 if (!visibleDocuments.contains(document)) {
201 visibleDocuments.append(document);
202 migrationTarget->newView(document);
203 }
204 }
205 }
206
207
209 QList<QScreen*> screens = QGuiApplication::screens();
210
211 std::sort(screens.begin(), screens.end(), [](const QScreen *a, const QScreen *b) {
212 QRect aRect = a->geometry();
213 QRect bRect = b->geometry();
214
215 if (aRect.y() == bRect.y()) return aRect.x() < bRect.x();
216 return (aRect.y() < bRect.y());
217 });
218
219 return screens;
220 }
221};
222
224 : KoResource(filename)
225 , d(new Private)
226{}
227
230
236
241
243 const QString &filename, const QList<QPointer<KisMainWindow>> &mainWindows, bool showImageInAllWindows,
244 bool primaryWorkspaceFollowsFocus, KisMainWindow *primaryWindow
245)
246{
248 resource->setWindows(mainWindows);
249 resource->d->showImageInAllWindows = showImageInAllWindows;
250 resource->d->primaryWorkspaceFollowsFocus = primaryWorkspaceFollowsFocus;
251 resource->d->primaryWindow = primaryWindow->id();
252 return resource;
253}
254
256{
257 auto *kisPart = KisPart::instance();
258 auto *layoutManager= KisWindowLayoutManager::instance();
259
260 layoutManager->setLastUsedLayout(this);
261
262 QList<QPointer<KisMainWindow>> currentWindows = kisPart->mainWindows();
263 bool createWindows = d->windows.isEmpty();
264
265 if (createWindows) {
266 // No windows defined (e.g. fresh new session). Leave things as they are, but make sure there's at least one visible main window
267 if (kisPart->mainwindowCount() == 0) {
268 kisPart->createMainWindow();
269 } else {
270 kisPart->mainWindows().first()->show();
271 }
272 } else {
273 d->openNecessaryWindows(currentWindows);
274 }
275
276 // Wait for the windows to finish opening / closing before applying saved geometry.
277 // If we don't, the geometry may get reset after we apply it.
278 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
279
280 Q_FOREACH(const auto &window, d->windows) {
281 QPointer<KisMainWindow> mainWindow = kisPart->windowById(window.windowId);
283
284 mainWindow->restoreGeometry(window.geometry.data);
285 mainWindow->restoreWorkspaceState(window.windowState);
286
287 mainWindow->setCanvasDetached(window.canvasDetached);
288 if (window.canvasDetached) {
289 QWidget *canvasWindow = mainWindow->canvasWindow();
290 canvasWindow->restoreGeometry(window.canvasWindowGeometry.data);
291 }
292 }
293
294 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
295
296 QList<QScreen*> screens = d->getScreensInConsistentOrder();
297 Q_FOREACH(const auto &window, d->windows) {
298 Private::WindowGeometry geometry = window.geometry;
299 QPointer<KisMainWindow> mainWindow = kisPart->windowById(window.windowId);
301
302 if (geometry.screen >= 0 && geometry.screen < screens.size()) {
303 geometry.forceOntoCorrectScreen(mainWindow, screens);
304 }
305 if (window.canvasDetached) {
306 Private::WindowGeometry canvasWindowGeometry = window.canvasWindowGeometry;
307 if (canvasWindowGeometry.screen >= 0 && canvasWindowGeometry.screen < screens.size()) {
308 canvasWindowGeometry.forceOntoCorrectScreen(mainWindow->canvasWindow(), screens);
309 }
310 }
311 }
312
313 layoutManager->setShowImageInAllWindowsEnabled(d->showImageInAllWindows);
314 layoutManager->setPrimaryWorkspaceFollowsFocus(d->primaryWorkspaceFollowsFocus, d->primaryWindow);
315 if (!createWindows) {
316 d->closeUnneededWindows(currentWindows);
317 }
318}
319
321{
322 QDomDocument doc;
323 QDomElement root = doc.createElement("WindowLayout");
324 root.setAttribute("name", name());
325 root.setAttribute("version", WINDOW_LAYOUT_VERSION);
326
327 saveXml(doc, root);
328
329 doc.appendChild(root);
330
331 QTextStream textStream(dev);
333 doc.save(textStream, 4);
334 return true;
335}
336
338{
339 Q_UNUSED(resourcesInterface);
340
341 QDomDocument doc;
342 if (!doc.setContent(dev)) {
343 return false;
344 }
345
346 QDomElement element = doc.documentElement();
347 setName(element.attribute("name"));
348
349 d->windows.clear();
350
351 loadXml(element);
352
353 setValid(true);
354 return true;
355}
356
357void KisWindowLayoutResource::saveXml(QDomDocument &doc, QDomElement &root) const
358{
359 root.setAttribute("showImageInAllWindows", (int)d->showImageInAllWindows);
360 root.setAttribute("primaryWorkspaceFollowsFocus", (int)d->primaryWorkspaceFollowsFocus);
361 root.setAttribute("primaryWindow", d->primaryWindow.toString());
362
363 Q_FOREACH(const auto &window, d->windows) {
364 QDomElement elem = doc.createElement("window");
365 elem.setAttribute("id", window.windowId.toString());
366
367 window.geometry.save(doc, elem);
368
369 if (window.canvasDetached) {
370 QDomElement canvasWindowElement = doc.createElement("canvasWindow");
371 window.canvasWindowGeometry.save(doc, canvasWindowElement);
372 elem.appendChild(canvasWindowElement);
373 }
374
375 QDomElement state = doc.createElement("windowState");
376 state.appendChild(doc.createCDATASection(window.windowState.toBase64()));
377 elem.appendChild(state);
378 root.appendChild(elem);
379 }
380}
381
382void KisWindowLayoutResource::loadXml(const QDomElement &element) const
383{
384 d->showImageInAllWindows = KisDomUtils::toInt(element.attribute("showImageInAllWindows", "0"));
385 d->primaryWorkspaceFollowsFocus = KisDomUtils::toInt(element.attribute("primaryWorkspaceFollowsFocus", "0"));
386 d->primaryWindow = QUuid::fromString(element.attribute("primaryWindow"));
387
388#ifdef Q_OS_ANDROID
389 if (element.firstChildElement("window") != element.lastChildElement("window")) {
390 QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"),
391 "Workspaces with multiple windows isn't supported on Android");
392 return;
393 }
394#endif
395
396 for (auto windowElement = element.firstChildElement("window");
397 !windowElement.isNull();
398 windowElement = windowElement.nextSiblingElement("window")) {
399
400 Private::Window window;
401
402 window.windowId = QUuid(windowElement.attribute("id", QUuid().toString()));
403 if (window.windowId.isNull()) {
404 window.windowId = QUuid::createUuid();
405 }
406
407 window.geometry = Private::WindowGeometry::load(windowElement);
408
409 QDomElement canvasWindowElement = windowElement.firstChildElement("canvasWindow");
410 if (!canvasWindowElement.isNull()) {
411 window.canvasDetached = true;
412 window.canvasWindowGeometry = Private::WindowGeometry::load(canvasWindowElement);
413 }
414
415 QDomElement state = windowElement.firstChildElement("windowState");
416 window.windowState = QByteArray::fromBase64(state.text().toLatin1());
417
418 d->windows.append(window);
419 }
420}
421
423{
424 return QString(".kwl");
425}
426
428{
429 d->windows.clear();
430
431 QList<QScreen*> screens = d->getScreensInConsistentOrder();
432
433 Q_FOREACH(auto window, mainWindows) {
434 if (!window->isVisible()) continue;
435
436 Private::Window state;
437 state.windowId = window->id();
438 state.windowState = window->saveState();
439 state.geometry = Private::WindowGeometry::fromWindow(window, screens);
440
441 state.canvasDetached = window->canvasDetached();
442 if (state.canvasDetached) {
443 state.canvasWindowGeometry = Private::WindowGeometry::fromWindow(window->canvasWindow(), screens);
444 }
445
446 d->windows.append(state);
447 }
448}
static const int WINDOW_LAYOUT_VERSION
Main window for Krita.
KisView * newView(QObject *document, QMdiSubWindow *subWindow=0)
KisAction * close
static KisPart * instance()
Definition KisPart.cpp:131
KisMainWindow * mainWindow() const
Definition KisView.cpp:1085
QPointer< KisDocument > document
Definition KisView.cpp:121
static KisWindowLayoutManager * instance()
QString defaultFileExtension() const override
KoResourceSP clone() const override
void setWindows(const QList< QPointer< KisMainWindow > > &mainWindows)
static KisWindowLayoutResourceSP fromCurrentWindows(const QString &filename, const QList< QPointer< KisMainWindow > > &mainWindows, bool showImageInAllWindows, bool primaryWorkspaceFollowsFocus, KisMainWindow *primaryWindow)
virtual void saveXml(QDomDocument &doc, QDomElement &root) const
KisWindowLayoutResource(const QString &filename)
virtual void loadXml(const QDomElement &root) const
bool saveToDevice(QIODevice *dev) const override
QScopedPointer< Private > d
bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override
#define KIS_SAFE_ASSERT_RECOVER_BREAK(cond)
Definition kis_assert.h:127
QSharedPointer< KoResource > KoResourceSP
int toInt(const QString &str, bool *ok=nullptr)
void setUtf8OnStream(QTextStream &stream)
void save(QDomDocument &doc, QDomElement &elem) const
void forceOntoCorrectScreen(QWidget *window, QList< QScreen * > screens)
static WindowGeometry fromWindow(const QWidget *window, QList< QScreen * > screens)
static WindowGeometry load(const QDomElement &element)
Private(const Private &rhs)=default
void openNecessaryWindows(QList< QPointer< KisMainWindow > > &currentWindows)
void closeUnneededWindows(QList< QPointer< KisMainWindow > > &currentWindows)
void migrateViewsFromClosingWindows(QVector< QPointer< KisMainWindow > > &closingWindows) const
void setValid(bool valid)
void setName(const QString &name)
QString filename
QString name