1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qqmlpreviewposition.h"
5
6#include <QtCore/qiodevice.h>
7#include <QtGui/qwindow.h>
8#include <QtGui/qscreen.h>
9#include <QtGui/qguiapplication.h>
10#include <QtCore/QIODevice>
11#include <private/qhighdpiscaling_p.h>
12
13QT_BEGIN_NAMESPACE
14
15static QVector<QQmlPreviewPosition::ScreenData> initScreensData()
16{
17 QVector<QQmlPreviewPosition::ScreenData> screensData;
18
19 for (QScreen *screen : QGuiApplication::screens()) {
20 QQmlPreviewPosition::ScreenData sd{.name: screen->name(), .rect: screen->geometry()};
21 screensData.append(t: sd);
22 }
23 return screensData;
24}
25
26static QScreen *findScreen(const QString &nameOfScreen)
27{
28 for (QScreen *screen : QGuiApplication::screens()) {
29 if (screen->name() == nameOfScreen)
30 return screen;
31 }
32 return nullptr;
33}
34
35static QDataStream &operator<<(QDataStream &out, const QQmlPreviewPosition::ScreenData &screenData)
36{
37 out << screenData.name;
38 out << screenData.rect;
39 return out;
40}
41
42static QDataStream &operator>>(QDataStream &in, QQmlPreviewPosition::ScreenData &screenData)
43{
44 in >> screenData.name;
45 in >> screenData.rect;
46 return in;
47}
48
49bool QQmlPreviewPosition::ScreenData::operator==(const QQmlPreviewPosition::ScreenData &other) const
50{
51 return other.rect == rect && other.name == name;
52}
53
54QQmlPreviewPosition::QQmlPreviewPosition()
55 : m_settings("QtProject", "QtQmlPreview")
56{
57 m_savePositionTimer.setSingleShot(true);
58 m_savePositionTimer.setInterval(500);
59 QObject::connect(sender: &m_savePositionTimer, signal: &QTimer::timeout, slot: [this]() {
60 saveWindowPosition();
61 });
62}
63
64QQmlPreviewPosition::~QQmlPreviewPosition()
65{
66}
67
68void QQmlPreviewPosition::takePosition(QWindow *window, InitializeState state)
69{
70 Q_ASSERT(window);
71 // only save the position if we already tried to get the last saved position
72 if (m_initializeState == PositionInitialized) {
73 m_hasPosition = true;
74 auto screen = window->screen();
75 auto nativePosition = QHighDpiScaling::mapPositionToNative(pos: window->framePosition(),
76 platformScreen: screen->handle());
77 m_lastWindowPosition = { .screenName: screen->name(), .nativePosition: nativePosition, .size: window->size() };
78
79 m_savePositionTimer.start();
80 }
81 if (state == InitializePosition)
82 m_initializeState = InitializePosition;
83}
84
85void QQmlPreviewPosition::saveWindowPosition()
86{
87 if (m_hasPosition) {
88 const QByteArray positionAsByteArray = fromPositionToByteArray(position: m_lastWindowPosition);
89 if (!m_settingsKey.isNull())
90 m_settings.setValue(key: m_settingsKey, value: positionAsByteArray);
91
92 m_settings.setValue(key: QLatin1String("global_lastpostion"), value: positionAsByteArray);
93 }
94}
95
96void QQmlPreviewPosition::loadWindowPositionSettings(const QUrl &url)
97{
98 m_settingsKey = url.toString(options: QUrl::PreferLocalFile) + QLatin1String("_lastpostion");
99
100 if (m_settings.contains(key: m_settingsKey)) {
101 m_hasPosition = true;
102 readLastPositionFromByteArray(array: m_settings.value(key: m_settingsKey).toByteArray());
103 }
104}
105
106void QQmlPreviewPosition::initLastSavedWindowPosition(QWindow *window)
107{
108 Q_ASSERT(window);
109 m_initializeState = PositionInitialized;
110 if (m_currentInitScreensData.isEmpty())
111 m_currentInitScreensData = initScreensData();
112 // if it is the first time we just use the fall back from a last shown qml file
113 if (!m_hasPosition) {
114 if (!m_settings.contains(key: QLatin1String("global_lastpostion")))
115 return;
116 readLastPositionFromByteArray(array: m_settings.value(key: QLatin1String("global_lastpostion"))
117 .toByteArray());
118 }
119 setPosition(position: m_lastWindowPosition, window);
120}
121
122QByteArray QQmlPreviewPosition::fromPositionToByteArray(
123 const QQmlPreviewPosition::Position &position)
124{
125 QByteArray array;
126 QDataStream stream(&array, QIODevice::WriteOnly);
127 stream.setVersion(QDataStream::Qt_5_12);
128
129 const quint16 majorVersion = 1;
130 const quint16 minorVersion = 0;
131
132 stream << majorVersion << minorVersion << m_currentInitScreensData << position.screenName
133 << position.nativePosition << position.size;
134 return array;
135}
136
137void QQmlPreviewPosition::readLastPositionFromByteArray(const QByteArray &array)
138{
139 QDataStream stream(array);
140 stream.setVersion(QDataStream::Qt_5_12);
141
142 // no version check for 1.0
143 //const quint16 currentMajorVersion = 1;
144 quint16 majorVersion = 0;
145 quint16 minorVersion = 0;
146
147 stream >> majorVersion >> minorVersion;
148
149 QVector<ScreenData> initScreensData;
150 stream >> initScreensData;
151
152 if (m_currentInitScreensData != initScreensData)
153 return;
154
155 QString nameOfScreen;
156 stream >> nameOfScreen;
157
158 QScreen *screen = findScreen(nameOfScreen);
159 if (!screen)
160 return;
161
162 QPoint nativePosition;
163 stream >> nativePosition;
164 if (nativePosition.isNull())
165 return;
166
167 QSize size;
168 stream >> size;
169
170 m_lastWindowPosition = { .screenName: nameOfScreen, .nativePosition: nativePosition, .size: size };
171}
172
173void QQmlPreviewPosition::setPosition(const QQmlPreviewPosition::Position &position,
174 QWindow *window)
175{
176 if (position.nativePosition.isNull())
177 return;
178 if (QScreen *screen = findScreen(nameOfScreen: position.screenName)) {
179 window->setScreen(screen);
180 const auto point = QHighDpiScaling::mapPositionFromNative(pos: position.nativePosition,
181 platformScreen: screen->handle());
182
183 const QRect geometry(point, position.size);
184 if (screen->virtualGeometry().contains(r: geometry))
185 window->setFramePosition(point);
186 else
187 qWarning(msg: "preview position is out of screen");
188 }
189}
190
191QT_END_NAMESPACE
192

source code of qtdeclarative/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.cpp