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 "previewmanager_p.h" |
30 | #include "qdesigner_formbuilder_p.h" |
31 | #include "shared_settings_p.h" |
32 | #include "shared_settings_p.h" |
33 | #include "zoomwidget_p.h" |
34 | #include "formwindowbase_p.h" |
35 | #include "widgetfactory_p.h" |
36 | |
37 | #include <deviceskin.h> |
38 | |
39 | #include <QtDesigner/abstractformwindow.h> |
40 | #include <QtDesigner/abstractformeditor.h> |
41 | #include <QtDesigner/abstractformwindowmanager.h> |
42 | #include <QtDesigner/abstractsettings.h> |
43 | |
44 | #include <QtWidgets/qwidget.h> |
45 | #include <QtGui/qevent.h> |
46 | #include <QtWidgets/qdesktopwidget.h> |
47 | #include <QtWidgets/qmainwindow.h> |
48 | #include <QtWidgets/qdockwidget.h> |
49 | #include <QtWidgets/qapplication.h> |
50 | #include <QtGui/qpixmap.h> |
51 | #include <QtWidgets/qboxlayout.h> |
52 | #include <QtWidgets/qdialog.h> |
53 | #include <QtWidgets/qmenu.h> |
54 | #include <QtWidgets/qaction.h> |
55 | #include <QtWidgets/qactiongroup.h> |
56 | #include <QtGui/qcursor.h> |
57 | #include <QtGui/qtransform.h> |
58 | |
59 | #include <QtCore/qmap.h> |
60 | #include <QtCore/qdebug.h> |
61 | #include <QtCore/qshareddata.h> |
62 | #include <QtCore/qvector.h> |
63 | |
64 | QT_BEGIN_NAMESPACE |
65 | |
66 | static inline int compare(const qdesigner_internal::PreviewConfiguration &pc1, const qdesigner_internal::PreviewConfiguration &pc2) |
67 | { |
68 | int rc = pc1.style().compare(s: pc2.style()); |
69 | if (rc) |
70 | return rc; |
71 | rc = pc1.applicationStyleSheet().compare(s: pc2.applicationStyleSheet()); |
72 | if (rc) |
73 | return rc; |
74 | return pc1.deviceSkin().compare(s: pc2.deviceSkin()); |
75 | } |
76 | |
77 | namespace { |
78 | // ------ PreviewData (data associated with a preview window) |
79 | struct PreviewData { |
80 | PreviewData(const QPointer<QWidget> &widget, const QDesignerFormWindowInterface *formWindow, const qdesigner_internal::PreviewConfiguration &pc); |
81 | QPointer<QWidget> m_widget; |
82 | const QDesignerFormWindowInterface *m_formWindow; |
83 | qdesigner_internal::PreviewConfiguration m_configuration; |
84 | }; |
85 | |
86 | PreviewData::PreviewData(const QPointer<QWidget>& widget, |
87 | const QDesignerFormWindowInterface *formWindow, |
88 | const qdesigner_internal::PreviewConfiguration &pc) : |
89 | m_widget(widget), |
90 | m_formWindow(formWindow), |
91 | m_configuration(pc) |
92 | { |
93 | } |
94 | } |
95 | |
96 | namespace qdesigner_internal { |
97 | |
98 | /* In designer, we have the situation that laid-out maincontainers have |
99 | * a geometry set (which might differ from their sizeHint()). The QGraphicsItem |
100 | * should return that in its size hint, else such cases won't work */ |
101 | |
102 | class DesignerZoomProxyWidget : public ZoomProxyWidget { |
103 | Q_DISABLE_COPY_MOVE(DesignerZoomProxyWidget) |
104 | public: |
105 | DesignerZoomProxyWidget(QGraphicsItem *parent = nullptr, Qt::WindowFlags wFlags = {}); |
106 | protected: |
107 | QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint = QSizeF() ) const override; |
108 | }; |
109 | |
110 | DesignerZoomProxyWidget::DesignerZoomProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) : |
111 | ZoomProxyWidget(parent, wFlags) |
112 | { |
113 | } |
114 | |
115 | QSizeF DesignerZoomProxyWidget::sizeHint(Qt::SizeHint which, const QSizeF & constraint) const |
116 | { |
117 | if (const QWidget *w = widget()) |
118 | return QSizeF(w->size()); |
119 | return ZoomProxyWidget::sizeHint(which, constraint); |
120 | } |
121 | |
122 | // DesignerZoomWidget which returns DesignerZoomProxyWidget in its factory function |
123 | class DesignerZoomWidget : public ZoomWidget { |
124 | Q_DISABLE_COPY_MOVE(DesignerZoomWidget) |
125 | public: |
126 | DesignerZoomWidget(QWidget *parent = nullptr); |
127 | private: |
128 | QGraphicsProxyWidget *createProxyWidget(QGraphicsItem *parent = nullptr, |
129 | Qt::WindowFlags wFlags = {}) const override; |
130 | }; |
131 | |
132 | DesignerZoomWidget::DesignerZoomWidget(QWidget *parent) : |
133 | ZoomWidget(parent) |
134 | { |
135 | } |
136 | |
137 | QGraphicsProxyWidget *DesignerZoomWidget::createProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) const |
138 | { |
139 | return new DesignerZoomProxyWidget(parent, wFlags); |
140 | } |
141 | |
142 | // PreviewDeviceSkin: Forwards the key events to the window and |
143 | // provides context menu with rotation options. Derived class |
144 | // can apply additional transformations to the skin. |
145 | |
146 | class PreviewDeviceSkin : public DeviceSkin |
147 | { |
148 | Q_OBJECT |
149 | public: |
150 | enum Direction { DirectionUp, DirectionLeft, DirectionRight }; |
151 | |
152 | explicit PreviewDeviceSkin(const DeviceSkinParameters ¶meters, QWidget *parent); |
153 | virtual void setPreview(QWidget *w); |
154 | QSize screenSize() const { return m_screenSize; } |
155 | |
156 | private slots: |
157 | void slotSkinKeyPressEvent(int code, const QString& text, bool autorep); |
158 | void slotSkinKeyReleaseEvent(int code, const QString& text, bool autorep); |
159 | void slotPopupMenu(); |
160 | |
161 | protected: |
162 | virtual void (QMenu *) {} |
163 | |
164 | private slots: |
165 | void slotDirection(QAction *); |
166 | |
167 | protected: |
168 | // Fit the widget in case the orientation changes (transposing screensize) |
169 | virtual void fitWidget(const QSize &size); |
170 | // Calculate the complete transformation for the skin |
171 | // (base class implementation provides rotation). |
172 | virtual QTransform skinTransform() const; |
173 | |
174 | private: |
175 | const QSize m_screenSize; |
176 | Direction m_direction; |
177 | |
178 | QAction *m_directionUpAction; |
179 | QAction *m_directionLeftAction; |
180 | QAction *m_directionRightAction; |
181 | QAction *m_closeAction; |
182 | }; |
183 | |
184 | PreviewDeviceSkin::PreviewDeviceSkin(const DeviceSkinParameters ¶meters, QWidget *parent) : |
185 | DeviceSkin(parameters, parent), |
186 | m_screenSize(parameters.screenSize()), |
187 | m_direction(DirectionUp), |
188 | m_directionUpAction(nullptr), |
189 | m_directionLeftAction(nullptr), |
190 | m_directionRightAction(nullptr), |
191 | m_closeAction(nullptr) |
192 | { |
193 | connect(sender: this, signal: &PreviewDeviceSkin::skinKeyPressEvent, |
194 | receiver: this, slot: &PreviewDeviceSkin::slotSkinKeyPressEvent); |
195 | connect(sender: this, signal: &PreviewDeviceSkin::skinKeyReleaseEvent, |
196 | receiver: this, slot: &PreviewDeviceSkin::slotSkinKeyReleaseEvent); |
197 | connect(sender: this, signal: &PreviewDeviceSkin::popupMenu, receiver: this, slot: &PreviewDeviceSkin::slotPopupMenu); |
198 | } |
199 | |
200 | void PreviewDeviceSkin::setPreview(QWidget *formWidget) |
201 | { |
202 | formWidget->setFixedSize(m_screenSize); |
203 | formWidget->setParent(parent: this, f: Qt::SubWindow); |
204 | formWidget->setAutoFillBackground(true); |
205 | setView(formWidget); |
206 | } |
207 | |
208 | void PreviewDeviceSkin::slotSkinKeyPressEvent(int code, const QString& text, bool autorep) |
209 | { |
210 | if (QWidget *focusWidget = QApplication::focusWidget()) { |
211 | QKeyEvent e(QEvent::KeyPress, code, {}, text, autorep); |
212 | QApplication::sendEvent(receiver: focusWidget, event: &e); |
213 | } |
214 | } |
215 | |
216 | void PreviewDeviceSkin::slotSkinKeyReleaseEvent(int code, const QString& text, bool autorep) |
217 | { |
218 | if (QWidget *focusWidget = QApplication::focusWidget()) { |
219 | QKeyEvent e(QEvent::KeyRelease, code, {}, text, autorep); |
220 | QApplication::sendEvent(receiver: focusWidget, event: &e); |
221 | } |
222 | } |
223 | |
224 | // Create a checkable action with integer data and |
225 | // set it checked if it matches the currentState. |
226 | static inline QAction |
227 | *createCheckableActionIntData(const QString &label, |
228 | int actionValue, int currentState, |
229 | QActionGroup *ag, QObject *parent) |
230 | { |
231 | QAction *a = new QAction(label, parent); |
232 | a->setData(actionValue); |
233 | a->setCheckable(true); |
234 | if (actionValue == currentState) |
235 | a->setChecked(true); |
236 | ag->addAction(a); |
237 | return a; |
238 | } |
239 | |
240 | void PreviewDeviceSkin::() |
241 | { |
242 | QMenu (this); |
243 | // Create actions |
244 | if (!m_directionUpAction) { |
245 | QActionGroup *directionGroup = new QActionGroup(this); |
246 | connect(sender: directionGroup, signal: &QActionGroup::triggered, receiver: this, slot: &PreviewDeviceSkin::slotDirection); |
247 | directionGroup->setExclusive(true); |
248 | m_directionUpAction = createCheckableActionIntData(label: tr(s: "&Portrait" ), actionValue: DirectionUp, currentState: m_direction, ag: directionGroup, parent: this); |
249 | //: Rotate form preview counter-clockwise |
250 | m_directionLeftAction = createCheckableActionIntData(label: tr(s: "Landscape (&CCW)" ), actionValue: DirectionLeft, currentState: m_direction, ag: directionGroup, parent: this); |
251 | //: Rotate form preview clockwise |
252 | m_directionRightAction = createCheckableActionIntData(label: tr(s: "&Landscape (CW)" ), actionValue: DirectionRight, currentState: m_direction, ag: directionGroup, parent: this); |
253 | m_closeAction = new QAction(tr(s: "&Close" ), this); |
254 | connect(sender: m_closeAction, signal: &QAction::triggered, receiver: parentWidget(), slot: &QWidget::close); |
255 | } |
256 | menu.addAction(action: m_directionUpAction); |
257 | menu.addAction(action: m_directionLeftAction); |
258 | menu.addAction(action: m_directionRightAction); |
259 | menu.addSeparator(); |
260 | populateContextMenu(&menu); |
261 | menu.addAction(action: m_closeAction); |
262 | menu.exec(pos: QCursor::pos()); |
263 | } |
264 | |
265 | void PreviewDeviceSkin::slotDirection(QAction *a) |
266 | { |
267 | const Direction newDirection = static_cast<Direction>(a->data().toInt()); |
268 | if (m_direction == newDirection) |
269 | return; |
270 | const Qt::Orientation newOrientation = newDirection == DirectionUp ? Qt::Vertical : Qt::Horizontal; |
271 | const Qt::Orientation oldOrientation = m_direction == DirectionUp ? Qt::Vertical : Qt::Horizontal; |
272 | m_direction = newDirection; |
273 | QApplication::setOverrideCursor(Qt::WaitCursor); |
274 | if (oldOrientation != newOrientation) { |
275 | QSize size = screenSize(); |
276 | if (newOrientation == Qt::Horizontal) |
277 | size.transpose(); |
278 | fitWidget(size); |
279 | } |
280 | setTransform(skinTransform()); |
281 | QApplication::restoreOverrideCursor(); |
282 | } |
283 | |
284 | void PreviewDeviceSkin::fitWidget(const QSize &size) |
285 | { |
286 | view()->setFixedSize(size); |
287 | } |
288 | |
289 | QTransform PreviewDeviceSkin::skinTransform() const |
290 | { |
291 | QTransform newTransform; |
292 | switch (m_direction) { |
293 | case DirectionUp: |
294 | break; |
295 | case DirectionLeft: |
296 | newTransform.rotate(a: 270.0); |
297 | break; |
298 | case DirectionRight: |
299 | newTransform.rotate(a: 90.0); |
300 | break; |
301 | } |
302 | return newTransform; |
303 | } |
304 | |
305 | // ------------ PreviewConfigurationPrivate |
306 | class PreviewConfigurationData : public QSharedData { |
307 | public: |
308 | PreviewConfigurationData() = default; |
309 | explicit PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin); |
310 | |
311 | QString m_style; |
312 | // Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()). |
313 | QString m_applicationStyleSheet; |
314 | QString m_deviceSkin; |
315 | }; |
316 | |
317 | PreviewConfigurationData::PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin) : |
318 | m_style(style), |
319 | m_applicationStyleSheet(applicationStyleSheet), |
320 | m_deviceSkin(deviceSkin) |
321 | { |
322 | } |
323 | |
324 | /* ZoomablePreviewDeviceSkin: A Zoomable Widget Preview skin. Embeds preview |
325 | * into a ZoomWidget and this in turn into the DeviceSkin view and keeps |
326 | * Device skin zoom + ZoomWidget zoom in sync. */ |
327 | |
328 | class ZoomablePreviewDeviceSkin : public PreviewDeviceSkin |
329 | { |
330 | Q_OBJECT |
331 | public: |
332 | explicit ZoomablePreviewDeviceSkin(const DeviceSkinParameters ¶meters, QWidget *parent); |
333 | void setPreview(QWidget *w) override; |
334 | |
335 | int zoomPercent() const; // Device Skins have a double 'zoom' property |
336 | |
337 | public slots: |
338 | void setZoomPercent(int); |
339 | |
340 | signals: |
341 | void zoomPercentChanged(int); |
342 | |
343 | protected: |
344 | void populateContextMenu(QMenu *m) override; |
345 | QTransform skinTransform() const override; |
346 | void fitWidget(const QSize &size) override; |
347 | |
348 | private: |
349 | ZoomMenu *; |
350 | QAction *; |
351 | ZoomWidget *m_zoomWidget; |
352 | }; |
353 | |
354 | ZoomablePreviewDeviceSkin::ZoomablePreviewDeviceSkin(const DeviceSkinParameters ¶meters, QWidget *parent) : |
355 | PreviewDeviceSkin(parameters, parent), |
356 | m_zoomMenu(new ZoomMenu(this)), |
357 | m_zoomSubMenuAction(nullptr), |
358 | m_zoomWidget(new DesignerZoomWidget) |
359 | { |
360 | connect(sender: m_zoomMenu, signal: &ZoomMenu::zoomChanged, receiver: this, slot: &ZoomablePreviewDeviceSkin::setZoomPercent); |
361 | connect(sender: m_zoomMenu, signal: &ZoomMenu::zoomChanged, receiver: this, slot: &ZoomablePreviewDeviceSkin::zoomPercentChanged); |
362 | m_zoomWidget->setZoomContextMenuEnabled(false); |
363 | m_zoomWidget->setWidgetZoomContextMenuEnabled(false); |
364 | m_zoomWidget->resize(screenSize()); |
365 | m_zoomWidget->setParent(parent: this, f: Qt::SubWindow); |
366 | m_zoomWidget->setAutoFillBackground(true); |
367 | setView(m_zoomWidget); |
368 | } |
369 | |
370 | static inline qreal zoomFactor(int percent) |
371 | { |
372 | return qreal(percent) / 100.0; |
373 | } |
374 | |
375 | static inline QSize scaleSize(int zoomPercent, const QSize &size) |
376 | { |
377 | return zoomPercent == 100 ? size : (QSizeF(size) * zoomFactor(percent: zoomPercent)).toSize(); |
378 | } |
379 | |
380 | void ZoomablePreviewDeviceSkin::setPreview(QWidget *formWidget) |
381 | { |
382 | m_zoomWidget->setWidget(w: formWidget); |
383 | m_zoomWidget->resize(scaleSize(zoomPercent: zoomPercent(), size: screenSize())); |
384 | } |
385 | |
386 | int ZoomablePreviewDeviceSkin::zoomPercent() const |
387 | { |
388 | return m_zoomWidget->zoom(); |
389 | } |
390 | |
391 | void ZoomablePreviewDeviceSkin::setZoomPercent(int zp) |
392 | { |
393 | if (zp == zoomPercent()) |
394 | return; |
395 | |
396 | // If not triggered by the menu itself: Update it |
397 | if (m_zoomMenu->zoom() != zp) |
398 | m_zoomMenu->setZoom(zp); |
399 | |
400 | QApplication::setOverrideCursor(Qt::WaitCursor); |
401 | m_zoomWidget->setZoom(zp); |
402 | setTransform(skinTransform()); |
403 | QApplication::restoreOverrideCursor(); |
404 | } |
405 | |
406 | void ZoomablePreviewDeviceSkin::(QMenu *) |
407 | { |
408 | if (!m_zoomSubMenuAction) { |
409 | m_zoomSubMenuAction = new QAction(tr(s: "&Zoom" ), this); |
410 | QMenu * = new QMenu; |
411 | m_zoomSubMenuAction->setMenu(zoomSubMenu); |
412 | m_zoomMenu->addActions(m: zoomSubMenu); |
413 | } |
414 | menu->addAction(action: m_zoomSubMenuAction); |
415 | menu->addSeparator(); |
416 | } |
417 | |
418 | QTransform ZoomablePreviewDeviceSkin::skinTransform() const |
419 | { |
420 | // Complete transformation consisting of base class rotation and zoom. |
421 | QTransform rc = PreviewDeviceSkin::skinTransform(); |
422 | const int zp = zoomPercent(); |
423 | if (zp != 100) { |
424 | const qreal factor = zoomFactor(percent: zp); |
425 | rc.scale(sx: factor, sy: factor); |
426 | } |
427 | return rc; |
428 | } |
429 | |
430 | void ZoomablePreviewDeviceSkin::fitWidget(const QSize &size) |
431 | { |
432 | m_zoomWidget->resize(scaleSize(zoomPercent: zoomPercent(), size)); |
433 | } |
434 | |
435 | // ------------- PreviewConfiguration |
436 | |
437 | static const char *styleKey = "Style" ; |
438 | static const char *appStyleSheetKey = "AppStyleSheet" ; |
439 | static const char *skinKey = "Skin" ; |
440 | |
441 | PreviewConfiguration::PreviewConfiguration() : |
442 | m_d(new PreviewConfigurationData) |
443 | { |
444 | } |
445 | |
446 | PreviewConfiguration::PreviewConfiguration(const QString &sty, const QString &applicationSheet, const QString &skin) : |
447 | m_d(new PreviewConfigurationData(sty, applicationSheet, skin)) |
448 | { |
449 | } |
450 | |
451 | PreviewConfiguration::PreviewConfiguration(const PreviewConfiguration &o) : |
452 | m_d(o.m_d) |
453 | { |
454 | } |
455 | |
456 | PreviewConfiguration &PreviewConfiguration::operator=(const PreviewConfiguration &o) |
457 | { |
458 | m_d.operator=(o: o.m_d); |
459 | return *this; |
460 | } |
461 | |
462 | PreviewConfiguration::~PreviewConfiguration() = default; |
463 | |
464 | void PreviewConfiguration::clear() |
465 | { |
466 | PreviewConfigurationData &d = *m_d; |
467 | d.m_style.clear(); |
468 | d.m_applicationStyleSheet.clear(); |
469 | d.m_deviceSkin.clear(); |
470 | } |
471 | |
472 | QString PreviewConfiguration::style() const |
473 | { |
474 | return m_d->m_style; |
475 | } |
476 | |
477 | void PreviewConfiguration::setStyle(const QString &s) |
478 | { |
479 | m_d->m_style = s; |
480 | } |
481 | |
482 | // Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()). |
483 | QString PreviewConfiguration::applicationStyleSheet() const |
484 | { |
485 | return m_d->m_applicationStyleSheet; |
486 | } |
487 | |
488 | void PreviewConfiguration::setApplicationStyleSheet(const QString &as) |
489 | { |
490 | m_d->m_applicationStyleSheet = as; |
491 | } |
492 | |
493 | QString PreviewConfiguration::deviceSkin() const |
494 | { |
495 | return m_d->m_deviceSkin; |
496 | } |
497 | |
498 | void PreviewConfiguration::setDeviceSkin(const QString &s) |
499 | { |
500 | m_d->m_deviceSkin = s; |
501 | } |
502 | |
503 | void PreviewConfiguration::toSettings(const QString &prefix, QDesignerSettingsInterface *settings) const |
504 | { |
505 | const PreviewConfigurationData &d = *m_d; |
506 | settings->beginGroup(prefix); |
507 | settings->setValue(key: QLatin1String(styleKey), value: d.m_style); |
508 | settings->setValue(key: QLatin1String(appStyleSheetKey), value: d.m_applicationStyleSheet); |
509 | settings->setValue(key: QLatin1String(skinKey), value: d.m_deviceSkin); |
510 | settings->endGroup(); |
511 | } |
512 | |
513 | void PreviewConfiguration::fromSettings(const QString &prefix, const QDesignerSettingsInterface *settings) |
514 | { |
515 | clear(); |
516 | QString key = prefix; |
517 | key += QLatin1Char('/'); |
518 | const int prefixSize = key.size(); |
519 | |
520 | PreviewConfigurationData &d = *m_d; |
521 | |
522 | const QVariant emptyString = QVariant(QString()); |
523 | |
524 | key += QLatin1String(styleKey); |
525 | d.m_style = settings->value(key, defaultValue: emptyString).toString(); |
526 | |
527 | key.replace(i: prefixSize, len: key.size() - prefixSize, after: QLatin1String(appStyleSheetKey)); |
528 | d.m_applicationStyleSheet = settings->value(key, defaultValue: emptyString).toString(); |
529 | |
530 | key.replace(i: prefixSize, len: key.size() - prefixSize, after: QLatin1String(skinKey)); |
531 | d.m_deviceSkin = settings->value(key, defaultValue: emptyString).toString(); |
532 | } |
533 | |
534 | |
535 | QDESIGNER_SHARED_EXPORT bool operator<(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2) |
536 | { |
537 | return compare(pc1, pc2) < 0; |
538 | } |
539 | |
540 | QDESIGNER_SHARED_EXPORT bool operator==(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2) |
541 | { |
542 | return compare(pc1, pc2) == 0; |
543 | } |
544 | |
545 | QDESIGNER_SHARED_EXPORT bool operator!=(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2) |
546 | { |
547 | return compare(pc1, pc2) != 0; |
548 | } |
549 | |
550 | // ------------- PreviewManagerPrivate |
551 | class PreviewManagerPrivate { |
552 | public: |
553 | PreviewManagerPrivate(PreviewManager::PreviewMode mode); |
554 | |
555 | const PreviewManager::PreviewMode m_mode; |
556 | |
557 | QPointer<QWidget> m_activePreview; |
558 | |
559 | using PreviewDataList = QVector<PreviewData>; |
560 | |
561 | PreviewDataList m_previews; |
562 | |
563 | typedef QMap<QString, DeviceSkinParameters> DeviceSkinConfigCache; |
564 | DeviceSkinConfigCache m_deviceSkinConfigCache; |
565 | |
566 | QDesignerFormEditorInterface *m_core; |
567 | bool m_updateBlocked; |
568 | }; |
569 | |
570 | PreviewManagerPrivate::PreviewManagerPrivate(PreviewManager::PreviewMode mode) : |
571 | m_mode(mode), |
572 | m_core(nullptr), |
573 | m_updateBlocked(false) |
574 | { |
575 | } |
576 | |
577 | // ------------- PreviewManager |
578 | |
579 | PreviewManager::PreviewManager(PreviewMode mode, QObject *parent) : |
580 | QObject(parent), |
581 | d(new PreviewManagerPrivate(mode)) |
582 | { |
583 | } |
584 | |
585 | PreviewManager:: ~PreviewManager() |
586 | { |
587 | delete d; |
588 | } |
589 | |
590 | |
591 | Qt::WindowFlags PreviewManager::previewWindowFlags(const QWidget *widget) const |
592 | { |
593 | #ifdef Q_OS_WIN |
594 | Qt::WindowFlags windowFlags = (widget->windowType() == Qt::Window) ? |
595 | Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint : |
596 | Qt::WindowFlags(Qt::Dialog); |
597 | #else |
598 | Q_UNUSED(widget); |
599 | // Only Dialogs have close buttons on Mac. |
600 | // On Linux, we don't want an additional task bar item and we don't want a minimize button; |
601 | // we want the preview to be on top. |
602 | Qt::WindowFlags windowFlags = Qt::Dialog; |
603 | #endif |
604 | return windowFlags; |
605 | } |
606 | |
607 | QWidget *PreviewManager::createDeviceSkinContainer(const QDesignerFormWindowInterface *fw) const |
608 | { |
609 | return new QDialog(fw->window()); |
610 | } |
611 | |
612 | // Some widgets might require fake containers |
613 | |
614 | static QWidget *fakeContainer(QWidget *w) |
615 | { |
616 | // Prevent a dock widget from trying to dock to Designer's main window |
617 | // (which can be found in the parent hierarchy in MDI mode) by |
618 | // providing a fake mainwindow |
619 | if (QDockWidget *dock = qobject_cast<QDockWidget *>(object: w)) { |
620 | // Reparent: Clear modality, propagate title and resize outer container |
621 | const QSize size = w->size(); |
622 | w->setWindowModality(Qt::NonModal); |
623 | dock->setFeatures(dock->features() & ~(QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetClosable)); |
624 | dock->setAllowedAreas(Qt::LeftDockWidgetArea); |
625 | QMainWindow *mw = new QMainWindow; |
626 | const QMargins cm = mw->contentsMargins(); |
627 | mw->addDockWidget(area: Qt::LeftDockWidgetArea, dockwidget: dock); |
628 | mw->resize(size + QSize(cm.left() + cm.right(), cm.top() + cm.bottom())); |
629 | return mw; |
630 | } |
631 | return w; |
632 | } |
633 | |
634 | static PreviewConfiguration configurationFromSettings(QDesignerFormEditorInterface *core, const QString &style) |
635 | { |
636 | qdesigner_internal::PreviewConfiguration pc; |
637 | const QDesignerSharedSettings settings(core); |
638 | if (settings.isCustomPreviewConfigurationEnabled()) |
639 | pc = settings.customPreviewConfiguration(); |
640 | if (!style.isEmpty()) |
641 | pc.setStyle(style); |
642 | return pc; |
643 | } |
644 | |
645 | QWidget *PreviewManager::showPreview(const QDesignerFormWindowInterface *fw, const QString &style, int deviceProfileIndex, QString *errorMessage) |
646 | { |
647 | return showPreview(fw, pc: configurationFromSettings(core: fw->core(), style), deviceProfileIndex, errorMessage); |
648 | } |
649 | |
650 | QWidget *PreviewManager::showPreview(const QDesignerFormWindowInterface *fw, const QString &style, QString *errorMessage) |
651 | { |
652 | return showPreview(fw, style, deviceProfileIndex: -1, errorMessage); |
653 | } |
654 | |
655 | QWidget *PreviewManager::createPreview(const QDesignerFormWindowInterface *fw, |
656 | const PreviewConfiguration &pc, |
657 | int deviceProfileIndex, |
658 | QString *errorMessage, |
659 | int initialZoom) |
660 | { |
661 | if (!d->m_core) |
662 | d->m_core = fw->core(); |
663 | |
664 | const bool zoomable = initialZoom > 0; |
665 | // Figure out which profile to apply |
666 | DeviceProfile deviceProfile; |
667 | if (deviceProfileIndex >= 0) { |
668 | deviceProfile = QDesignerSharedSettings(fw->core()).deviceProfileAt(idx: deviceProfileIndex); |
669 | } else { |
670 | if (const FormWindowBase *fwb = qobject_cast<const FormWindowBase *>(object: fw)) |
671 | deviceProfile = fwb->deviceProfile(); |
672 | } |
673 | // Create |
674 | QWidget *formWidget = QDesignerFormBuilder::createPreview(fw, styleName: pc.style(), appStyleSheet: pc.applicationStyleSheet(), deviceProfile, errorMessage); |
675 | if (!formWidget) |
676 | return nullptr; |
677 | |
678 | const QString title = tr(s: "%1 - [Preview]" ).arg(a: formWidget->windowTitle()); |
679 | formWidget = fakeContainer(w: formWidget); |
680 | formWidget->setWindowTitle(title); |
681 | |
682 | // Clear any modality settings, child widget modalities must not be higher than parent's |
683 | formWidget->setWindowModality(Qt::NonModal); |
684 | // No skin |
685 | const QString deviceSkin = pc.deviceSkin(); |
686 | if (deviceSkin.isEmpty()) { |
687 | if (zoomable) { // Embed into ZoomWidget |
688 | ZoomWidget *zw = new DesignerZoomWidget; |
689 | connect(sender: zw->zoomMenu(), signal: &ZoomMenu::zoomChanged, receiver: this, slot: &PreviewManager::slotZoomChanged); |
690 | zw->setWindowTitle(title); |
691 | zw->setWidget(w: formWidget); |
692 | // Keep any widgets' context menus working, do not use global menu |
693 | zw->setWidgetZoomContextMenuEnabled(true); |
694 | zw->setParent(parent: fw->window(), f: previewWindowFlags(widget: formWidget)); |
695 | // Make preview close when Widget closes (Dialog/accept, etc) |
696 | formWidget->setAttribute(Qt::WA_DeleteOnClose, on: true); |
697 | connect(sender: formWidget, signal: &QObject::destroyed, receiver: zw, slot: &QWidget::close); |
698 | zw->setZoom(initialZoom); |
699 | zw->setProperty(name: WidgetFactory::disableStyleCustomPaintingPropertyC, value: QVariant(true)); |
700 | return zw; |
701 | } |
702 | formWidget->setParent(parent: fw->window(), f: previewWindowFlags(widget: formWidget)); |
703 | formWidget->setProperty(name: WidgetFactory::disableStyleCustomPaintingPropertyC, value: QVariant(true)); |
704 | return formWidget; |
705 | } |
706 | // Embed into skin. find config in cache |
707 | PreviewManagerPrivate::DeviceSkinConfigCache::iterator it = d->m_deviceSkinConfigCache.find(akey: deviceSkin); |
708 | if (it == d->m_deviceSkinConfigCache.end()) { |
709 | DeviceSkinParameters parameters; |
710 | if (!parameters.read(skinDirectory: deviceSkin, rm: DeviceSkinParameters::ReadAll, errorMessage)) { |
711 | formWidget->deleteLater(); |
712 | return nullptr; |
713 | } |
714 | it = d->m_deviceSkinConfigCache.insert(akey: deviceSkin, avalue: parameters); |
715 | } |
716 | |
717 | QWidget *skinContainer = createDeviceSkinContainer(fw); |
718 | PreviewDeviceSkin *skin = nullptr; |
719 | if (zoomable) { |
720 | ZoomablePreviewDeviceSkin *zds = new ZoomablePreviewDeviceSkin(it.value(), skinContainer); |
721 | zds->setZoomPercent(initialZoom); |
722 | connect(sender: zds, signal: &ZoomablePreviewDeviceSkin::zoomPercentChanged, |
723 | receiver: this, slot: &PreviewManager::slotZoomChanged); |
724 | skin = zds; |
725 | } else { |
726 | skin = new PreviewDeviceSkin(it.value(), skinContainer); |
727 | } |
728 | skin->setPreview(formWidget); |
729 | // Make preview close when Widget closes (Dialog/accept, etc) |
730 | formWidget->setAttribute(Qt::WA_DeleteOnClose, on: true); |
731 | connect(sender: formWidget, signal: &QObject::destroyed, receiver: skinContainer, slot: &QWidget::close); |
732 | skinContainer->setWindowTitle(title); |
733 | skinContainer->setProperty(name: WidgetFactory::disableStyleCustomPaintingPropertyC, value: QVariant(true)); |
734 | return skinContainer; |
735 | } |
736 | |
737 | QWidget *PreviewManager::showPreview(const QDesignerFormWindowInterface *fw, |
738 | const PreviewConfiguration &pc, |
739 | int deviceProfileIndex, |
740 | QString *errorMessage) |
741 | { |
742 | enum { Spacing = 10 }; |
743 | if (QWidget *existingPreviewWidget = raise(fw, pc)) |
744 | return existingPreviewWidget; |
745 | |
746 | const QDesignerSharedSettings settings(fw->core()); |
747 | const int initialZoom = settings.zoomEnabled() ? settings.zoom() : -1; |
748 | |
749 | QWidget *widget = createPreview(fw, pc, deviceProfileIndex, errorMessage, initialZoom); |
750 | if (!widget) |
751 | return nullptr; |
752 | // Install filter for Escape key |
753 | widget->setAttribute(Qt::WA_DeleteOnClose, on: true); |
754 | widget->installEventFilter(filterObj: this); |
755 | |
756 | switch (d->m_mode) { |
757 | case ApplicationModalPreview: |
758 | // Cannot do this on the Mac as the dialog would have no close button |
759 | widget->setWindowModality(Qt::ApplicationModal); |
760 | break; |
761 | case SingleFormNonModalPreview: |
762 | case MultipleFormNonModalPreview: |
763 | widget->setWindowModality(Qt::NonModal); |
764 | connect(sender: fw, signal: &QDesignerFormWindowInterface::changed, receiver: widget, slot: &QWidget::close); |
765 | connect(sender: fw, signal: &QObject::destroyed, receiver: widget, slot: &QWidget::close); |
766 | if (d->m_mode == SingleFormNonModalPreview) { |
767 | connect(sender: fw->core()->formWindowManager(), signal: &QDesignerFormWindowManagerInterface::activeFormWindowChanged, |
768 | receiver: widget, slot: &QWidget::close); |
769 | } |
770 | break; |
771 | } |
772 | // Semi-smart algorithm to position previews: |
773 | // If its the first one, position relative to form. |
774 | // 2nd, attempt to tile right (for comparing styles) or cascade |
775 | const QSize size = widget->size(); |
776 | const bool firstPreview = d->m_previews.isEmpty(); |
777 | if (firstPreview) { |
778 | widget->move(fw->mapToGlobal(QPoint(Spacing, Spacing))); |
779 | } else { |
780 | if (QWidget *lastPreview = d->m_previews.constLast().m_widget) { |
781 | QDesktopWidget *desktop = qApp->desktop(); |
782 | const QRect lastPreviewGeometry = lastPreview->frameGeometry(); |
783 | const QRect availGeometry = desktop->availableGeometry(widget: lastPreview); |
784 | const QPoint newPos = lastPreviewGeometry.topRight() + QPoint(Spacing, 0); |
785 | if (newPos.x() + size.width() < availGeometry.right()) |
786 | widget->move(newPos); |
787 | else |
788 | widget->move(lastPreviewGeometry.topLeft() + QPoint(Spacing, Spacing)); |
789 | } |
790 | |
791 | } |
792 | d->m_previews.push_back(t: PreviewData(widget, fw, pc)); |
793 | widget->show(); |
794 | if (firstPreview) |
795 | emit firstPreviewOpened(); |
796 | return widget; |
797 | } |
798 | |
799 | QWidget *PreviewManager::raise(const QDesignerFormWindowInterface *fw, const PreviewConfiguration &pc) |
800 | { |
801 | using PreviewDataList = PreviewManagerPrivate::PreviewDataList; |
802 | if (d->m_previews.isEmpty()) |
803 | return nullptr; |
804 | |
805 | // find matching window |
806 | const PreviewDataList::const_iterator cend = d->m_previews.constEnd(); |
807 | for (PreviewDataList::const_iterator it = d->m_previews.constBegin(); it != cend ;++it) { |
808 | QWidget * w = it->m_widget; |
809 | if (w && it->m_formWindow == fw && it->m_configuration == pc) { |
810 | w->raise(); |
811 | w->activateWindow(); |
812 | return w; |
813 | } |
814 | } |
815 | return nullptr; |
816 | } |
817 | |
818 | void PreviewManager::closeAllPreviews() |
819 | { |
820 | if (!d->m_previews.isEmpty()) { |
821 | d->m_updateBlocked = true; |
822 | d->m_activePreview = nullptr; |
823 | for (auto it = d->m_previews.constBegin(), cend = d->m_previews.constEnd(); it != cend ;++it) { |
824 | if (it->m_widget) |
825 | it->m_widget->close(); |
826 | } |
827 | d->m_previews.clear(); |
828 | d->m_updateBlocked = false; |
829 | emit lastPreviewClosed(); |
830 | } |
831 | } |
832 | |
833 | void PreviewManager::updatePreviewClosed(QWidget *w) |
834 | { |
835 | using PreviewDataList = PreviewManagerPrivate::PreviewDataList; |
836 | if (d->m_updateBlocked) |
837 | return; |
838 | // Purge out all 0 or widgets to be deleted |
839 | for (PreviewDataList::iterator it = d->m_previews.begin(); it != d->m_previews.end() ; ) { |
840 | QWidget *iw = it->m_widget; // Might be 0 when catching QEvent::Destroyed |
841 | if (iw == nullptr || iw == w) { |
842 | it = d->m_previews.erase(pos: it); |
843 | } else { |
844 | ++it; |
845 | } |
846 | } |
847 | if (d->m_previews.isEmpty()) |
848 | emit lastPreviewClosed(); |
849 | } |
850 | |
851 | bool PreviewManager::eventFilter(QObject *watched, QEvent *event) |
852 | { |
853 | // Courtesy of designer |
854 | do { |
855 | if (!watched->isWidgetType()) |
856 | break; |
857 | QWidget *previewWindow = qobject_cast<QWidget *>(o: watched); |
858 | if (!previewWindow || !previewWindow->isWindow()) |
859 | break; |
860 | |
861 | switch (event->type()) { |
862 | case QEvent::KeyPress: |
863 | case QEvent::ShortcutOverride: { |
864 | const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event); |
865 | const int key = keyEvent->key(); |
866 | if ((key == Qt::Key_Escape |
867 | #ifdef Q_OS_MACOS |
868 | || (keyEvent->modifiers() == Qt::ControlModifier && key == Qt::Key_Period) |
869 | #endif |
870 | )) { |
871 | previewWindow->close(); |
872 | return true; |
873 | } |
874 | } |
875 | break; |
876 | case QEvent::WindowActivate: |
877 | d->m_activePreview = previewWindow; |
878 | break; |
879 | case QEvent::Destroy: // We don't get QEvent::Close if someone accepts a QDialog. |
880 | updatePreviewClosed(w: previewWindow); |
881 | break; |
882 | case QEvent::Close: |
883 | updatePreviewClosed(w: previewWindow); |
884 | previewWindow->removeEventFilter (obj: this); |
885 | break; |
886 | default: |
887 | break; |
888 | } |
889 | } while(false); |
890 | return QObject::eventFilter(watched, event); |
891 | } |
892 | |
893 | int PreviewManager::previewCount() const |
894 | { |
895 | return d->m_previews.size(); |
896 | } |
897 | |
898 | QPixmap PreviewManager::createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &style, int deviceProfileIndex, QString *errorMessage) |
899 | { |
900 | return createPreviewPixmap(fw, pc: configurationFromSettings(core: fw->core(), style), deviceProfileIndex, errorMessage); |
901 | } |
902 | |
903 | QPixmap PreviewManager::createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &style, QString *errorMessage) |
904 | { |
905 | return createPreviewPixmap(fw, style, deviceProfileIndex: -1, errorMessage); |
906 | } |
907 | |
908 | QPixmap PreviewManager::createPreviewPixmap(const QDesignerFormWindowInterface *fw, |
909 | const PreviewConfiguration &pc, |
910 | int deviceProfileIndex, |
911 | QString *errorMessage) |
912 | { |
913 | QWidget *widget = createPreview(fw, pc, deviceProfileIndex, errorMessage); |
914 | if (!widget) |
915 | return QPixmap(); |
916 | const QPixmap rc = widget->grab(rectangle: QRect(0, 0, -1, -1)); |
917 | widget->deleteLater(); |
918 | return rc; |
919 | } |
920 | |
921 | void PreviewManager::slotZoomChanged(int z) |
922 | { |
923 | if (d->m_core) { // Save the last zoom chosen by the user. |
924 | QDesignerSharedSettings settings(d->m_core); |
925 | settings.setZoom(z); |
926 | } |
927 | } |
928 | } |
929 | |
930 | QT_END_NAMESPACE |
931 | |
932 | #include "previewmanager.moc" |
933 | |