1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QML preview debug service. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qqmlpreviewposition.h" |
41 | |
42 | #include <QtGui/qwindow.h> |
43 | #include <QtGui/qscreen.h> |
44 | #include <QtGui/qguiapplication.h> |
45 | #include <private/qhighdpiscaling_p.h> |
46 | |
47 | QT_BEGIN_NAMESPACE |
48 | |
49 | static QVector<QQmlPreviewPosition::ScreenData> initScreensData() |
50 | { |
51 | QVector<QQmlPreviewPosition::ScreenData> screensData; |
52 | |
53 | for (QScreen *screen : QGuiApplication::screens()) { |
54 | QQmlPreviewPosition::ScreenData sd{.name: screen->name(), .rect: screen->geometry()}; |
55 | screensData.append(t: sd); |
56 | } |
57 | return screensData; |
58 | } |
59 | |
60 | static QScreen *findScreen(const QString &nameOfScreen) |
61 | { |
62 | for (QScreen *screen : QGuiApplication::screens()) { |
63 | if (screen->name() == nameOfScreen) |
64 | return screen; |
65 | } |
66 | return nullptr; |
67 | } |
68 | |
69 | static QDataStream &operator<<(QDataStream &out, const QQmlPreviewPosition::ScreenData &screenData) |
70 | { |
71 | out << screenData.name; |
72 | out << screenData.rect; |
73 | return out; |
74 | } |
75 | |
76 | static QDataStream &operator>>(QDataStream &in, QQmlPreviewPosition::ScreenData &screenData) |
77 | { |
78 | in >> screenData.name; |
79 | in >> screenData.rect; |
80 | return in; |
81 | } |
82 | |
83 | bool QQmlPreviewPosition::ScreenData::operator==(const QQmlPreviewPosition::ScreenData &other) const |
84 | { |
85 | return other.rect == rect && other.name == name; |
86 | } |
87 | |
88 | QQmlPreviewPosition::QQmlPreviewPosition() |
89 | : m_settings("QtProject" , "QtQmlPreview" ) |
90 | { |
91 | m_savePositionTimer.setSingleShot(true); |
92 | m_savePositionTimer.setInterval(500); |
93 | QObject::connect(sender: &m_savePositionTimer, signal: &QTimer::timeout, slot: [this]() { |
94 | saveWindowPosition(); |
95 | }); |
96 | } |
97 | |
98 | QQmlPreviewPosition::~QQmlPreviewPosition() |
99 | { |
100 | saveWindowPosition(); |
101 | } |
102 | |
103 | void QQmlPreviewPosition::takePosition(QWindow *window, InitializeState state) |
104 | { |
105 | Q_ASSERT(window); |
106 | // only save the position if we already tried to get the last saved position |
107 | if (m_initializeState == PositionInitialized) { |
108 | m_hasPosition = true; |
109 | auto screen = window->screen(); |
110 | auto nativePosition = QHighDpiScaling::mapPositionToNative(pos: window->framePosition(), |
111 | platformScreen: screen->handle()); |
112 | m_lastWindowPosition = {.screenName: screen->name(), .nativePosition: nativePosition}; |
113 | |
114 | m_savePositionTimer.start(); |
115 | } |
116 | if (state == InitializePosition) |
117 | m_initializeState = InitializePosition; |
118 | } |
119 | |
120 | void QQmlPreviewPosition::saveWindowPosition() |
121 | { |
122 | if (m_hasPosition) { |
123 | const QByteArray positionAsByteArray = fromPositionToByteArray(position: m_lastWindowPosition); |
124 | if (!m_settingsKey.isNull()) |
125 | m_settings.setValue(key: m_settingsKey, value: positionAsByteArray); |
126 | |
127 | m_settings.setValue(key: QLatin1String("global_lastpostion" ), value: positionAsByteArray); |
128 | } |
129 | } |
130 | |
131 | void QQmlPreviewPosition::loadWindowPositionSettings(const QUrl &url) |
132 | { |
133 | m_settingsKey = url.toString(options: QUrl::PreferLocalFile) + QLatin1String("_lastpostion" ); |
134 | |
135 | if (m_settings.contains(key: m_settingsKey)) { |
136 | m_hasPosition = true; |
137 | readLastPositionFromByteArray(array: m_settings.value(key: m_settingsKey).toByteArray()); |
138 | } |
139 | } |
140 | |
141 | void QQmlPreviewPosition::initLastSavedWindowPosition(QWindow *window) |
142 | { |
143 | Q_ASSERT(window); |
144 | m_initializeState = PositionInitialized; |
145 | if (m_currentInitScreensData.isEmpty()) |
146 | m_currentInitScreensData = initScreensData(); |
147 | // if it is the first time we just use the fall back from a last shown qml file |
148 | if (!m_hasPosition) { |
149 | if (!m_settings.contains(key: QLatin1String("global_lastpostion" ))) |
150 | return; |
151 | readLastPositionFromByteArray(array: m_settings.value(key: QLatin1String("global_lastpostion" )) |
152 | .toByteArray()); |
153 | } |
154 | setPosition(position: m_lastWindowPosition, window); |
155 | } |
156 | |
157 | QByteArray QQmlPreviewPosition::fromPositionToByteArray( |
158 | const QQmlPreviewPosition::Position &position) |
159 | { |
160 | QByteArray array; |
161 | QDataStream stream(&array, QIODevice::WriteOnly); |
162 | stream.setVersion(QDataStream::Qt_5_12); |
163 | |
164 | const quint16 majorVersion = 1; |
165 | const quint16 minorVersion = 0; |
166 | |
167 | stream << majorVersion |
168 | << minorVersion |
169 | << m_currentInitScreensData |
170 | << position.screenName |
171 | << position.nativePosition; |
172 | return array; |
173 | } |
174 | |
175 | void QQmlPreviewPosition::readLastPositionFromByteArray(const QByteArray &array) |
176 | { |
177 | QDataStream stream(array); |
178 | stream.setVersion(QDataStream::Qt_5_12); |
179 | |
180 | // no version check for 1.0 |
181 | //const quint16 currentMajorVersion = 1; |
182 | quint16 majorVersion = 0; |
183 | quint16 minorVersion = 0; |
184 | |
185 | stream >> majorVersion >> minorVersion; |
186 | |
187 | QVector<ScreenData> initScreensData; |
188 | stream >> initScreensData; |
189 | |
190 | if (m_currentInitScreensData != initScreensData) |
191 | return; |
192 | |
193 | QString nameOfScreen; |
194 | stream >> nameOfScreen; |
195 | |
196 | QScreen *screen = findScreen(nameOfScreen); |
197 | if (!screen) |
198 | return; |
199 | |
200 | QPoint nativePosition; |
201 | stream >> nativePosition; |
202 | if (nativePosition.isNull()) |
203 | return; |
204 | m_lastWindowPosition = {.screenName: nameOfScreen, .nativePosition: nativePosition}; |
205 | } |
206 | |
207 | void QQmlPreviewPosition::setPosition(const QQmlPreviewPosition::Position &position, |
208 | QWindow *window) |
209 | { |
210 | if (position.nativePosition.isNull()) |
211 | return; |
212 | if (QScreen *screen = findScreen(nameOfScreen: position.screenName)) { |
213 | window->setScreen(screen); |
214 | const auto point = QHighDpiScaling::mapPositionFromNative(pos: position.nativePosition, |
215 | platformScreen: screen->handle()); |
216 | const QRect geometry(point, window->size()); |
217 | if (screen->virtualGeometry().contains(r: geometry)) |
218 | window->setFramePosition(point); |
219 | else |
220 | qWarning(msg: "preview position is out of screen" ); |
221 | } |
222 | } |
223 | |
224 | QT_END_NAMESPACE |
225 | |