1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Designer of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
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 General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include "deviceprofile_p.h" |
30 | |
31 | #include <QtDesigner/abstractformeditor.h> |
32 | #include <widgetfactory_p.h> |
33 | #include <qdesigner_utils_p.h> |
34 | |
35 | #include <QtWidgets/qapplication.h> |
36 | #include <QtGui/qfont.h> |
37 | #include <QtWidgets/qdesktopwidget.h> |
38 | #include <QtWidgets/qstyle.h> |
39 | #include <QtWidgets/qstylefactory.h> |
40 | #include <QtWidgets/qapplication.h> |
41 | |
42 | #include <QtCore/qshareddata.h> |
43 | #include <QtCore/qtextstream.h> |
44 | |
45 | #include <QtCore/qxmlstream.h> |
46 | |
47 | |
48 | static const char *dpiXPropertyC = "_q_customDpiX" ; |
49 | static const char *dpiYPropertyC = "_q_customDpiY" ; |
50 | |
51 | // XML serialization |
52 | static const char *xmlVersionC="1.0" ; |
53 | static const char *rootElementC="deviceprofile" ; |
54 | static const char *nameElementC = "name" ; |
55 | static const char *fontFamilyElementC = "fontfamily" ; |
56 | static const char *fontPointSizeElementC = "fontpointsize" ; |
57 | static const char *dPIXElementC = "dpix" ; |
58 | static const char *dPIYElementC = "dpiy" ; |
59 | static const char *styleElementC = "style" ; |
60 | |
61 | /* DeviceProfile: |
62 | * For preview purposes (preview, widget box, new form dialog), the |
63 | * QDesignerFormBuilder applies the settings to the form main container |
64 | * (Point being here that the DPI must be set directly for size calculations |
65 | * to be correct). |
66 | * For editing purposes, FormWindow applies the settings to the form container |
67 | * as not to interfere with the font settings of the form main container. |
68 | * In addition, the widgetfactory maintains the system settings style |
69 | * and applies it when creating widgets. */ |
70 | |
71 | QT_BEGIN_NAMESPACE |
72 | |
73 | namespace qdesigner_internal { |
74 | |
75 | // ---------------- DeviceProfileData |
76 | class DeviceProfileData : public QSharedData { |
77 | public: |
78 | DeviceProfileData() = default; |
79 | void fromSystem(); |
80 | void clear(); |
81 | |
82 | QString m_fontFamily; |
83 | QString m_style; |
84 | QString m_name; |
85 | int m_fontPointSize = -1; |
86 | int m_dpiX = -1; |
87 | int m_dpiY = -1; |
88 | }; |
89 | |
90 | void DeviceProfileData::clear() |
91 | { |
92 | m_fontPointSize = -1; |
93 | m_dpiX = 0; |
94 | m_dpiY = 0; |
95 | m_name.clear(); |
96 | m_style.clear(); |
97 | } |
98 | |
99 | void DeviceProfileData::fromSystem() |
100 | { |
101 | const QFont appFont = QApplication::font(); |
102 | m_fontFamily = appFont.family(); |
103 | m_fontPointSize = appFont.pointSize(); |
104 | DeviceProfile::systemResolution(dpiX: &m_dpiX, dpiY: &m_dpiY); |
105 | m_style.clear(); |
106 | } |
107 | |
108 | // ---------------- DeviceProfile |
109 | DeviceProfile::DeviceProfile() : |
110 | m_d(new DeviceProfileData) |
111 | { |
112 | } |
113 | |
114 | DeviceProfile::DeviceProfile(const DeviceProfile &o) : |
115 | m_d(o.m_d) |
116 | |
117 | { |
118 | } |
119 | |
120 | DeviceProfile& DeviceProfile::operator=(const DeviceProfile &o) |
121 | { |
122 | m_d.operator=(o: o.m_d); |
123 | return *this; |
124 | } |
125 | |
126 | DeviceProfile::~DeviceProfile() = default; |
127 | |
128 | void DeviceProfile::clear() |
129 | { |
130 | m_d->clear(); |
131 | } |
132 | |
133 | bool DeviceProfile::isEmpty() const |
134 | { |
135 | return m_d->m_name.isEmpty(); |
136 | } |
137 | |
138 | QString DeviceProfile::fontFamily() const |
139 | { |
140 | return m_d->m_fontFamily; |
141 | } |
142 | |
143 | void DeviceProfile::setFontFamily(const QString &f) |
144 | { |
145 | m_d->m_fontFamily = f; |
146 | } |
147 | |
148 | int DeviceProfile::fontPointSize() const |
149 | { |
150 | return m_d->m_fontPointSize; |
151 | } |
152 | |
153 | void DeviceProfile::setFontPointSize(int p) |
154 | { |
155 | m_d->m_fontPointSize = p; |
156 | } |
157 | |
158 | QString DeviceProfile::style() const |
159 | { |
160 | return m_d->m_style; |
161 | } |
162 | |
163 | void DeviceProfile::setStyle(const QString &s) |
164 | { |
165 | m_d->m_style = s; |
166 | } |
167 | |
168 | int DeviceProfile::dpiX() const |
169 | { |
170 | return m_d->m_dpiX; |
171 | } |
172 | |
173 | void DeviceProfile::setDpiX(int d) |
174 | { |
175 | m_d->m_dpiX = d; |
176 | } |
177 | |
178 | int DeviceProfile::dpiY() const |
179 | { |
180 | return m_d->m_dpiY; |
181 | } |
182 | |
183 | void DeviceProfile::setDpiY(int d) |
184 | { |
185 | m_d->m_dpiY = d; |
186 | } |
187 | |
188 | void DeviceProfile::fromSystem() |
189 | { |
190 | m_d->fromSystem(); |
191 | } |
192 | |
193 | QString DeviceProfile::name() const |
194 | { |
195 | return m_d->m_name; |
196 | } |
197 | |
198 | void DeviceProfile::setName(const QString &n) |
199 | { |
200 | m_d->m_name = n; |
201 | } |
202 | |
203 | void DeviceProfile::systemResolution(int *dpiX, int *dpiY) |
204 | { |
205 | const QDesktopWidget *dw = qApp->desktop(); |
206 | *dpiX = dw->logicalDpiX(); |
207 | *dpiY = dw->logicalDpiY(); |
208 | } |
209 | |
210 | class FriendlyWidget : public QWidget { |
211 | friend class DeviceProfile; |
212 | }; |
213 | |
214 | void DeviceProfile::widgetResolution(const QWidget *w, int *dpiX, int *dpiY) |
215 | { |
216 | const FriendlyWidget *fw = static_cast<const FriendlyWidget*>(w); |
217 | *dpiX = fw->metric(QPaintDevice::PdmDpiX); |
218 | *dpiY = fw->metric(QPaintDevice::PdmDpiY); |
219 | } |
220 | |
221 | QString DeviceProfile::toString() const |
222 | { |
223 | const DeviceProfileData &d = *m_d; |
224 | QString rc; |
225 | QTextStream(&rc) << "DeviceProfile:name=" << d.m_name << " Font=" << d.m_fontFamily << ' ' |
226 | << d.m_fontPointSize << " Style=" << d.m_style << " DPI=" << d.m_dpiX << ',' << d.m_dpiY; |
227 | return rc; |
228 | } |
229 | |
230 | // Apply font to widget |
231 | static void applyFont(const QString &family, int size, DeviceProfile::ApplyMode am, QWidget *widget) |
232 | { |
233 | QFont currentFont = widget->font(); |
234 | if (currentFont.pointSize() == size && currentFont.family() == family) |
235 | return; |
236 | switch (am) { |
237 | case DeviceProfile::ApplyFormParent: |
238 | // Invisible form parent: Apply all |
239 | widget->setFont(QFont(family, size)); |
240 | break; |
241 | case DeviceProfile::ApplyPreview: { |
242 | // Preview: Apply only subproperties that have not been changed by designer properties |
243 | bool apply = false; |
244 | const uint resolve = currentFont.resolve(); |
245 | if (!(resolve & QFont::FamilyResolved)) { |
246 | currentFont.setFamily(family); |
247 | apply = true; |
248 | } |
249 | if (!(resolve & QFont::SizeResolved)) { |
250 | currentFont.setPointSize(size); |
251 | apply = true; |
252 | } |
253 | if (apply) |
254 | widget->setFont(currentFont); |
255 | } |
256 | break; |
257 | } |
258 | } |
259 | |
260 | void DeviceProfile::applyDPI(int dpiX, int dpiY, QWidget *widget) |
261 | { |
262 | int sysDPIX, sysDPIY; // Set dynamic variables in case values are different from system DPI |
263 | systemResolution(dpiX: &sysDPIX, dpiY: &sysDPIY); |
264 | if (dpiX != sysDPIX && dpiY != sysDPIY) { |
265 | widget->setProperty(name: dpiXPropertyC, value: QVariant(dpiX)); |
266 | widget->setProperty(name: dpiYPropertyC, value: QVariant(dpiY)); |
267 | } |
268 | } |
269 | |
270 | void DeviceProfile::apply(const QDesignerFormEditorInterface *core, QWidget *widget, ApplyMode am) const |
271 | { |
272 | if (isEmpty()) |
273 | return; |
274 | |
275 | const DeviceProfileData &d = *m_d; |
276 | |
277 | if (!d.m_fontFamily.isEmpty()) |
278 | applyFont(family: d.m_fontFamily, size: d.m_fontPointSize, am, widget); |
279 | |
280 | applyDPI(dpiX: d.m_dpiX, dpiY: d.m_dpiY, widget); |
281 | |
282 | if (!d.m_style.isEmpty()) { |
283 | if (WidgetFactory *wf = qobject_cast<qdesigner_internal::WidgetFactory *>(object: core->widgetFactory())) |
284 | wf->applyStyleTopLevel(styleName: d.m_style, w: widget); |
285 | } |
286 | } |
287 | |
288 | bool DeviceProfile::equals(const DeviceProfile& rhs) const |
289 | { |
290 | const DeviceProfileData &d = *m_d; |
291 | const DeviceProfileData &rhs_d = *rhs.m_d; |
292 | return d.m_fontPointSize == rhs_d.m_fontPointSize && |
293 | d.m_dpiX == rhs_d.m_dpiX && d.m_dpiY == rhs_d.m_dpiY && d.m_fontFamily == rhs_d.m_fontFamily && |
294 | d.m_style == rhs_d.m_style && d.m_name == rhs_d.m_name; |
295 | } |
296 | |
297 | static inline void writeElement(QXmlStreamWriter &writer, const QString &element, const QString &cdata) |
298 | { |
299 | writer.writeStartElement(qualifiedName: element); |
300 | writer.writeCharacters(text: cdata); |
301 | writer.writeEndElement(); |
302 | } |
303 | |
304 | QString DeviceProfile::toXml() const |
305 | { |
306 | const DeviceProfileData &d = *m_d; |
307 | QString rc; |
308 | QXmlStreamWriter writer(&rc); |
309 | writer.writeStartDocument(version: QLatin1String(xmlVersionC)); |
310 | writer.writeStartElement(qualifiedName: QLatin1String(rootElementC)); |
311 | writeElement(writer, element: QLatin1String(nameElementC), cdata: d.m_name); |
312 | |
313 | if (!d.m_fontFamily.isEmpty()) |
314 | writeElement(writer, element: QLatin1String(fontFamilyElementC), cdata: d.m_fontFamily); |
315 | if (d.m_fontPointSize >= 0) |
316 | writeElement(writer, element: QLatin1String(fontPointSizeElementC), cdata: QString::number(d.m_fontPointSize)); |
317 | if (d.m_dpiX > 0) |
318 | writeElement(writer, element: QLatin1String(dPIXElementC), cdata: QString::number(d.m_dpiX)); |
319 | if (d.m_dpiY > 0) |
320 | writeElement(writer, element: QLatin1String(dPIYElementC), cdata: QString::number(d.m_dpiY)); |
321 | if (!d.m_style.isEmpty()) |
322 | writeElement(writer, element: QLatin1String(styleElementC), cdata: d.m_style); |
323 | |
324 | writer.writeEndElement(); |
325 | writer.writeEndDocument(); |
326 | return rc; |
327 | } |
328 | |
329 | /* Switch stages when encountering a start element (state table) */ |
330 | enum ParseStage { ParseBeginning, ParseWithinRoot, |
331 | ParseName, ParseFontFamily, ParseFontPointSize, ParseDPIX, ParseDPIY, ParseStyle, |
332 | ParseError }; |
333 | |
334 | static ParseStage nextStage(ParseStage currentStage, const QStringRef &startElement) |
335 | { |
336 | switch (currentStage) { |
337 | case ParseBeginning: |
338 | if (startElement == QLatin1String(rootElementC)) |
339 | return ParseWithinRoot; |
340 | break; |
341 | case ParseWithinRoot: |
342 | case ParseName: |
343 | case ParseFontFamily: |
344 | case ParseFontPointSize: |
345 | case ParseDPIX: |
346 | case ParseDPIY: |
347 | case ParseStyle: |
348 | if (startElement == QLatin1String(nameElementC)) |
349 | return ParseName; |
350 | if (startElement == QLatin1String(fontFamilyElementC)) |
351 | return ParseFontFamily; |
352 | if (startElement == QLatin1String(fontPointSizeElementC)) |
353 | return ParseFontPointSize; |
354 | if (startElement == QLatin1String(dPIXElementC)) |
355 | return ParseDPIX; |
356 | if (startElement == QLatin1String(dPIYElementC)) |
357 | return ParseDPIY; |
358 | if (startElement == QLatin1String(styleElementC)) |
359 | return ParseStyle; |
360 | break; |
361 | case ParseError: |
362 | break; |
363 | } |
364 | return ParseError; |
365 | } |
366 | |
367 | static bool readIntegerElement(QXmlStreamReader &reader, int *v) |
368 | { |
369 | const QString e = reader.readElementText(); |
370 | bool ok; |
371 | *v = e.toInt(ok: &ok); |
372 | //: Reading a number for an embedded device profile |
373 | if (!ok) |
374 | reader.raiseError(message: QApplication::translate(context: "DeviceProfile" , key: "'%1' is not a number." ).arg(a: e)); |
375 | return ok; |
376 | } |
377 | |
378 | bool DeviceProfile::fromXml(const QString &xml, QString *errorMessage) |
379 | { |
380 | DeviceProfileData &d = *m_d; |
381 | d.fromSystem(); |
382 | |
383 | QXmlStreamReader reader(xml); |
384 | |
385 | ParseStage ps = ParseBeginning; |
386 | QXmlStreamReader::TokenType tt = QXmlStreamReader::NoToken; |
387 | int iv = 0; |
388 | do { |
389 | tt = reader.readNext(); |
390 | if (tt == QXmlStreamReader::StartElement) { |
391 | ps = nextStage(currentStage: ps, startElement: reader.name()); |
392 | switch (ps) { |
393 | case ParseBeginning: |
394 | case ParseWithinRoot: |
395 | break; |
396 | case ParseError: |
397 | reader.raiseError(message: QApplication::translate(context: "DeviceProfile" , key: "An invalid tag <%1> was encountered." ).arg(a: reader.name().toString())); |
398 | tt = QXmlStreamReader::Invalid; |
399 | break; |
400 | case ParseName: |
401 | d.m_name = reader.readElementText(); |
402 | break; |
403 | case ParseFontFamily: |
404 | d.m_fontFamily = reader.readElementText(); |
405 | break; |
406 | case ParseFontPointSize: |
407 | if (readIntegerElement(reader, v: &iv)) { |
408 | d.m_fontPointSize = iv; |
409 | } else { |
410 | tt = QXmlStreamReader::Invalid; |
411 | } |
412 | break; |
413 | case ParseDPIX: |
414 | if (readIntegerElement(reader, v: &iv)) { |
415 | d.m_dpiX = iv; |
416 | } else { |
417 | tt = QXmlStreamReader::Invalid; |
418 | } |
419 | break; |
420 | case ParseDPIY: |
421 | if (readIntegerElement(reader, v: &iv)) { |
422 | d.m_dpiY = iv; |
423 | } else { |
424 | tt = QXmlStreamReader::Invalid; |
425 | } |
426 | break; |
427 | case ParseStyle: |
428 | d.m_style = reader.readElementText(); |
429 | break; |
430 | } |
431 | } |
432 | } while (tt != QXmlStreamReader::Invalid && tt != QXmlStreamReader::EndDocument); |
433 | |
434 | if (reader.hasError()) { |
435 | *errorMessage = reader.errorString(); |
436 | return false; |
437 | } |
438 | |
439 | return true; |
440 | } |
441 | } |
442 | |
443 | QT_END_NAMESPACE |
444 | |
445 | |