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
132 QVector<QPointer<KisMainWindow>> windowsToClose;
133
134 Q_FOREACH(KisMainWindow *mainWindow, currentWindows) {
135 bool keep = false;
136 Q_FOREACH(const Window &window, windows) {
137 if (window.windowId == mainWindow->id()) {
138 keep = true;
139 break;
140 }
141 }
142
143 if (!keep) {
144 windowsToClose.append(mainWindow);
145
146 // Set the window hidden to prevent "show image in all windows" feature from opening new views on it
147 // while we migrate views onto the remaining windows
148 if (mainWindow->isVisible()) {
149 mainWindow->hide();
150 }
151 }
152 }
153
154 migrateViewsFromClosingWindows(windowsToClose);
155
156 Q_FOREACH(QPointer<KisMainWindow> mainWindow, windowsToClose) {
157 mainWindow->close();
158 }
159 }
160
162 {
163 auto *kisPart = KisPart::instance();
164 KisMainWindow *migrationTarget = nullptr;
165
166 Q_FOREACH(KisMainWindow *mainWindow, kisPart->mainWindows()) {
167 if (!closingWindows.contains(mainWindow)) {
168 migrationTarget = mainWindow;
169 break;
170 }
171 }
172
173 if (!migrationTarget) {
174 qWarning() << "Problem: window layout with no windows would leave user with zero main windows.";
175 migrationTarget = closingWindows.takeLast();
176 migrationTarget->show();
177 }
178
179 QVector<KisDocument*> visibleDocuments;
180 Q_FOREACH(KisView *view, kisPart->views()) {
181 KisMainWindow *window = view->mainWindow();
182 if (!closingWindows.contains(window)) {
183 visibleDocuments.append(view->document());
184 }
185 }
186
187 Q_FOREACH(KisDocument *document, kisPart->documents()) {
188 if (!visibleDocuments.contains(document)) {
189 visibleDocuments.append(document);
190 migrationTarget->newView(document);
191 }
192 }
193 }
194
195
197 QList<QScreen*> screens = QGuiApplication::screens();
198
199 std::sort(screens.begin(), screens.end(), [](const QScreen *a, const QScreen *b) {
200 QRect aRect = a->geometry();
201 QRect bRect = b->geometry();
202
203 if (aRect.y() == bRect.y()) return aRect.x() < bRect.x();
204 return (aRect.y() < bRect.y());
205 });
206
207 return screens;
208 }
209};
210
212 : KoResource(filename)
213 , d(new Private)
214{}
215
218
224
229
231 const QString &filename, const QList<QPointer<KisMainWindow>> &mainWindows, bool showImageInAllWindows,
232 bool primaryWorkspaceFollowsFocus, KisMainWindow *primaryWindow
233)
234{
236 resource->setWindows(mainWindows);
237 resource->d->showImageInAllWindows = showImageInAllWindows;
238 resource->d->primaryWorkspaceFollowsFocus = primaryWorkspaceFollowsFocus;
239 resource->d->primaryWindow = primaryWindow->id();
240 return resource;
241}
242
244{
245 auto *kisPart = KisPart::instance();
246 auto *layoutManager= KisWindowLayoutManager::instance();
247
248 layoutManager->setLastUsedLayout(this);
249
250 QList<QPointer<KisMainWindow>> currentWindows = kisPart->mainWindows();
251
252 if (d->windows.isEmpty()) {
253 // No windows defined (e.g. fresh new session). Leave things as they are, but make sure there's at least one visible main window
254 if (kisPart->mainwindowCount() == 0) {
255 kisPart->createMainWindow();
256 } else {
257 kisPart->mainWindows().first()->show();
258 }
259 } else {
260 d->openNecessaryWindows(currentWindows);
261 d->closeUnneededWindows(currentWindows);
262 }
263
264 // Wait for the windows to finish opening / closing before applying saved geometry.
265 // If we don't, the geometry may get reset after we apply it.
266 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
267
268 Q_FOREACH(const auto &window, d->windows) {
269 QPointer<KisMainWindow> mainWindow = kisPart->windowById(window.windowId);
271
272 mainWindow->restoreGeometry(window.geometry.data);
273 mainWindow->restoreWorkspaceState(window.windowState);
274
275 mainWindow->setCanvasDetached(window.canvasDetached);
276 if (window.canvasDetached) {
277 QWidget *canvasWindow = mainWindow->canvasWindow();
278 canvasWindow->restoreGeometry(window.canvasWindowGeometry.data);
279 }
280 }
281
282 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
283
284 QList<QScreen*> screens = d->getScreensInConsistentOrder();
285 Q_FOREACH(const auto &window, d->windows) {
286 Private::WindowGeometry geometry = window.geometry;
287 QPointer<KisMainWindow> mainWindow = kisPart->windowById(window.windowId);
289
290 if (geometry.screen >= 0 && geometry.screen < screens.size()) {
291 geometry.forceOntoCorrectScreen(mainWindow, screens);
292 }
293 if (window.canvasDetached) {
294 Private::WindowGeometry canvasWindowGeometry = window.canvasWindowGeometry;
295 if (canvasWindowGeometry.screen >= 0 && canvasWindowGeometry.screen < screens.size()) {
296 canvasWindowGeometry.forceOntoCorrectScreen(mainWindow->canvasWindow(), screens);
297 }
298 }
299 }
300
301 layoutManager->setShowImageInAllWindowsEnabled(d->showImageInAllWindows);
302 layoutManager->setPrimaryWorkspaceFollowsFocus(d->primaryWorkspaceFollowsFocus, d->primaryWindow);
303}
304
306{
307 QDomDocument doc;
308 QDomElement root = doc.createElement("WindowLayout");
309 root.setAttribute("name", name());
310 root.setAttribute("version", WINDOW_LAYOUT_VERSION);
311
312 saveXml(doc, root);
313
314 doc.appendChild(root);
315
316 QTextStream textStream(dev);
318 doc.save(textStream, 4);
319 return true;
320}
321
323{
324 Q_UNUSED(resourcesInterface);
325
326 QDomDocument doc;
327 if (!doc.setContent(dev)) {
328 return false;
329 }
330
331 QDomElement element = doc.documentElement();
332 setName(element.attribute("name"));
333
334 d->windows.clear();
335
336 loadXml(element);
337
338 setValid(true);
339 return true;
340}
341
342void KisWindowLayoutResource::saveXml(QDomDocument &doc, QDomElement &root) const
343{
344 root.setAttribute("showImageInAllWindows", (int)d->showImageInAllWindows);
345 root.setAttribute("primaryWorkspaceFollowsFocus", (int)d->primaryWorkspaceFollowsFocus);
346 root.setAttribute("primaryWindow", d->primaryWindow.toString());
347
348 Q_FOREACH(const auto &window, d->windows) {
349 QDomElement elem = doc.createElement("window");
350 elem.setAttribute("id", window.windowId.toString());
351
352 window.geometry.save(doc, elem);
353
354 if (window.canvasDetached) {
355 QDomElement canvasWindowElement = doc.createElement("canvasWindow");
356 window.canvasWindowGeometry.save(doc, canvasWindowElement);
357 elem.appendChild(canvasWindowElement);
358 }
359
360 QDomElement state = doc.createElement("windowState");
361 state.appendChild(doc.createCDATASection(window.windowState.toBase64()));
362 elem.appendChild(state);
363 root.appendChild(elem);
364 }
365}
366
367void KisWindowLayoutResource::loadXml(const QDomElement &element) const
368{
369 d->showImageInAllWindows = KisDomUtils::toInt(element.attribute("showImageInAllWindows", "0"));
370 d->primaryWorkspaceFollowsFocus = KisDomUtils::toInt(element.attribute("primaryWorkspaceFollowsFocus", "0"));
371 d->primaryWindow = QUuid::fromString(element.attribute("primaryWindow"));
372
373#ifdef Q_OS_ANDROID
374 if (element.firstChildElement("window") != element.lastChildElement("window")) {
375 QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"),
376 "Workspaces with multiple windows isn't supported on Android");
377 return;
378 }
379#endif
380
381 for (auto windowElement = element.firstChildElement("window");
382 !windowElement.isNull();
383 windowElement = windowElement.nextSiblingElement("window")) {
384
385 Private::Window window;
386
387 window.windowId = QUuid(windowElement.attribute("id", QUuid().toString()));
388 if (window.windowId.isNull()) {
389 window.windowId = QUuid::createUuid();
390 }
391
392 window.geometry = Private::WindowGeometry::load(windowElement);
393
394 QDomElement canvasWindowElement = windowElement.firstChildElement("canvasWindow");
395 if (!canvasWindowElement.isNull()) {
396 window.canvasDetached = true;
397 window.canvasWindowGeometry = Private::WindowGeometry::load(canvasWindowElement);
398 }
399
400 QDomElement state = windowElement.firstChildElement("windowState");
401 window.windowState = QByteArray::fromBase64(state.text().toLatin1());
402
403 d->windows.append(window);
404 }
405}
406
408{
409 return QString(".kwl");
410}
411
413{
414 d->windows.clear();
415
416 QList<QScreen*> screens = d->getScreensInConsistentOrder();
417
418 Q_FOREACH(auto window, mainWindows) {
419 if (!window->isVisible()) continue;
420
421 Private::Window state;
422 state.windowId = window->id();
423 state.windowState = window->saveState();
424 state.geometry = Private::WindowGeometry::fromWindow(window, screens);
425
426 state.canvasDetached = window->canvasDetached();
427 if (state.canvasDetached) {
428 state.canvasWindowGeometry = Private::WindowGeometry::fromWindow(window->canvasWindow(), screens);
429 }
430
431 d->windows.append(state);
432 }
433}
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:1093
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