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
64QT_BEGIN_NAMESPACE
65
66static 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
77namespace {
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
96namespace 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
102class DesignerZoomProxyWidget : public ZoomProxyWidget {
103 Q_DISABLE_COPY_MOVE(DesignerZoomProxyWidget)
104public:
105 DesignerZoomProxyWidget(QGraphicsItem *parent = nullptr, Qt::WindowFlags wFlags = {});
106protected:
107 QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint = QSizeF() ) const override;
108};
109
110DesignerZoomProxyWidget::DesignerZoomProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) :
111 ZoomProxyWidget(parent, wFlags)
112{
113}
114
115QSizeF 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
123class DesignerZoomWidget : public ZoomWidget {
124 Q_DISABLE_COPY_MOVE(DesignerZoomWidget)
125public:
126 DesignerZoomWidget(QWidget *parent = nullptr);
127private:
128 QGraphicsProxyWidget *createProxyWidget(QGraphicsItem *parent = nullptr,
129 Qt::WindowFlags wFlags = {}) const override;
130};
131
132DesignerZoomWidget::DesignerZoomWidget(QWidget *parent) :
133 ZoomWidget(parent)
134{
135}
136
137QGraphicsProxyWidget *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
146class PreviewDeviceSkin : public DeviceSkin
147{
148 Q_OBJECT
149public:
150 enum Direction { DirectionUp, DirectionLeft, DirectionRight };
151
152 explicit PreviewDeviceSkin(const DeviceSkinParameters &parameters, QWidget *parent);
153 virtual void setPreview(QWidget *w);
154 QSize screenSize() const { return m_screenSize; }
155
156private 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
161protected:
162 virtual void populateContextMenu(QMenu *) {}
163
164private slots:
165 void slotDirection(QAction *);
166
167protected:
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
174private:
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
184PreviewDeviceSkin::PreviewDeviceSkin(const DeviceSkinParameters &parameters, 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
200void 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
208void 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
216void 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.
226static 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
240void PreviewDeviceSkin::slotPopupMenu()
241{
242 QMenu menu(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
265void 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
284void PreviewDeviceSkin::fitWidget(const QSize &size)
285{
286 view()->setFixedSize(size);
287}
288
289QTransform 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
306class PreviewConfigurationData : public QSharedData {
307public:
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
317PreviewConfigurationData::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
328class ZoomablePreviewDeviceSkin : public PreviewDeviceSkin
329{
330 Q_OBJECT
331public:
332 explicit ZoomablePreviewDeviceSkin(const DeviceSkinParameters &parameters, QWidget *parent);
333 void setPreview(QWidget *w) override;
334
335 int zoomPercent() const; // Device Skins have a double 'zoom' property
336
337public slots:
338 void setZoomPercent(int);
339
340signals:
341 void zoomPercentChanged(int);
342
343protected:
344 void populateContextMenu(QMenu *m) override;
345 QTransform skinTransform() const override;
346 void fitWidget(const QSize &size) override;
347
348private:
349 ZoomMenu *m_zoomMenu;
350 QAction *m_zoomSubMenuAction;
351 ZoomWidget *m_zoomWidget;
352};
353
354ZoomablePreviewDeviceSkin::ZoomablePreviewDeviceSkin(const DeviceSkinParameters &parameters, 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
370static inline qreal zoomFactor(int percent)
371{
372 return qreal(percent) / 100.0;
373}
374
375static inline QSize scaleSize(int zoomPercent, const QSize &size)
376{
377 return zoomPercent == 100 ? size : (QSizeF(size) * zoomFactor(percent: zoomPercent)).toSize();
378}
379
380void ZoomablePreviewDeviceSkin::setPreview(QWidget *formWidget)
381{
382 m_zoomWidget->setWidget(w: formWidget);
383 m_zoomWidget->resize(scaleSize(zoomPercent: zoomPercent(), size: screenSize()));
384}
385
386int ZoomablePreviewDeviceSkin::zoomPercent() const
387{
388 return m_zoomWidget->zoom();
389}
390
391void 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
406void ZoomablePreviewDeviceSkin::populateContextMenu(QMenu *menu)
407{
408 if (!m_zoomSubMenuAction) {
409 m_zoomSubMenuAction = new QAction(tr(s: "&Zoom"), this);
410 QMenu *zoomSubMenu = 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
418QTransform 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
430void ZoomablePreviewDeviceSkin::fitWidget(const QSize &size)
431{
432 m_zoomWidget->resize(scaleSize(zoomPercent: zoomPercent(), size));
433}
434
435// ------------- PreviewConfiguration
436
437static const char *styleKey = "Style";
438static const char *appStyleSheetKey = "AppStyleSheet";
439static const char *skinKey = "Skin";
440
441PreviewConfiguration::PreviewConfiguration() :
442 m_d(new PreviewConfigurationData)
443{
444}
445
446PreviewConfiguration::PreviewConfiguration(const QString &sty, const QString &applicationSheet, const QString &skin) :
447 m_d(new PreviewConfigurationData(sty, applicationSheet, skin))
448{
449}
450
451PreviewConfiguration::PreviewConfiguration(const PreviewConfiguration &o) :
452 m_d(o.m_d)
453{
454}
455
456PreviewConfiguration &PreviewConfiguration::operator=(const PreviewConfiguration &o)
457{
458 m_d.operator=(o: o.m_d);
459 return *this;
460}
461
462PreviewConfiguration::~PreviewConfiguration() = default;
463
464void 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
472QString PreviewConfiguration::style() const
473{
474 return m_d->m_style;
475}
476
477void 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()).
483QString PreviewConfiguration::applicationStyleSheet() const
484{
485 return m_d->m_applicationStyleSheet;
486}
487
488void PreviewConfiguration::setApplicationStyleSheet(const QString &as)
489{
490 m_d->m_applicationStyleSheet = as;
491}
492
493QString PreviewConfiguration::deviceSkin() const
494{
495 return m_d->m_deviceSkin;
496}
497
498void PreviewConfiguration::setDeviceSkin(const QString &s)
499{
500 m_d->m_deviceSkin = s;
501}
502
503void 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
513void 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
535QDESIGNER_SHARED_EXPORT bool operator<(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2)
536{
537 return compare(pc1, pc2) < 0;
538}
539
540QDESIGNER_SHARED_EXPORT bool operator==(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2)
541{
542 return compare(pc1, pc2) == 0;
543}
544
545QDESIGNER_SHARED_EXPORT bool operator!=(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2)
546{
547 return compare(pc1, pc2) != 0;
548}
549
550// ------------- PreviewManagerPrivate
551class PreviewManagerPrivate {
552public:
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
570PreviewManagerPrivate::PreviewManagerPrivate(PreviewManager::PreviewMode mode) :
571 m_mode(mode),
572 m_core(nullptr),
573 m_updateBlocked(false)
574{
575}
576
577// ------------- PreviewManager
578
579PreviewManager::PreviewManager(PreviewMode mode, QObject *parent) :
580 QObject(parent),
581 d(new PreviewManagerPrivate(mode))
582{
583}
584
585PreviewManager:: ~PreviewManager()
586{
587 delete d;
588}
589
590
591Qt::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
607QWidget *PreviewManager::createDeviceSkinContainer(const QDesignerFormWindowInterface *fw) const
608{
609 return new QDialog(fw->window());
610}
611
612// Some widgets might require fake containers
613
614static 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
634static 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
645QWidget *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
650QWidget *PreviewManager::showPreview(const QDesignerFormWindowInterface *fw, const QString &style, QString *errorMessage)
651{
652 return showPreview(fw, style, deviceProfileIndex: -1, errorMessage);
653}
654
655QWidget *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
737QWidget *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
799QWidget *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
818void 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
833void 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
851bool 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
893int PreviewManager::previewCount() const
894{
895 return d->m_previews.size();
896}
897
898QPixmap 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
903QPixmap PreviewManager::createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &style, QString *errorMessage)
904{
905 return createPreviewPixmap(fw, style, deviceProfileIndex: -1, errorMessage);
906}
907
908QPixmap 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
921void 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
930QT_END_NAMESPACE
931
932#include "previewmanager.moc"
933

source code of qttools/src/designer/src/lib/shared/previewmanager.cpp