Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
previewmanager.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant reason:default
4
10#include "zoomwidget_p.h"
11
12#include <deviceskin_p.h>
13
14#include <QtDesigner/abstractformeditor.h>
15#include <QtDesigner/abstractformwindow.h>
16#include <QtDesigner/abstractformwindowmanager.h>
17#include <QtDesigner/abstractsettings.h>
18
19#include <QtWidgets/qapplication.h>
20#include <QtWidgets/qboxlayout.h>
21#include <QtWidgets/qdialog.h>
22#include <QtWidgets/qdockwidget.h>
23#include <QtWidgets/qmainwindow.h>
24#include <QtWidgets/qmenu.h>
25
26#include <QtGui/qaction.h>
27#include <QtGui/qactiongroup.h>
28#include <QtGui/qcursor.h>
29#include <QtGui/qevent.h>
30#include <QtGui/qpixmap.h>
31#include <QtGui/qscreen.h>
32#include <QtGui/qtransform.h>
33
34#include <QtCore/qdebug.h>
35#include <QtCore/qlist.h>
36#include <QtCore/qmap.h>
37#include <QtCore/qpointer.h>
38#include <QtCore/qshareddata.h>
39
41
42using namespace Qt::StringLiterals;
43
44static inline int compare(const qdesigner_internal::PreviewConfiguration &pc1,
45 const qdesigner_internal::PreviewConfiguration &pc2) noexcept
46{
47 int rc = pc1.style().compare(pc2.style());
48 if (rc)
49 return rc;
50 rc = pc1.applicationStyleSheet().compare(pc2.applicationStyleSheet());
51 if (rc)
52 return rc;
53 return pc1.deviceSkin().compare(pc2.deviceSkin());
54}
55
56namespace qdesigner_internal {
57 // ------ PreviewData (data associated with a preview window)
58 struct PreviewData {
59 PreviewData(const QPointer<QWidget> &widget, const QDesignerFormWindowInterface *formWindow, const qdesigner_internal::PreviewConfiguration &pc);
61 const QDesignerFormWindowInterface *m_formWindow;
62 qdesigner_internal::PreviewConfiguration m_configuration;
63 };
64
65 PreviewData::PreviewData(const QPointer<QWidget>& widget,
66 const QDesignerFormWindowInterface *formWindow,
67 const qdesigner_internal::PreviewConfiguration &pc) :
69 m_formWindow(formWindow),
71 {
72 }
73
74/* In designer, we have the situation that laid-out maincontainers have
75 * a geometry set (which might differ from their sizeHint()). The QGraphicsItem
76 * should return that in its size hint, else such cases won't work */
77
80public:
82protected:
83 QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint = QSizeF() ) const override;
84};
85
86DesignerZoomProxyWidget::DesignerZoomProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) :
87 ZoomProxyWidget(parent, wFlags)
88{
89}
90
91QSizeF DesignerZoomProxyWidget::sizeHint(Qt::SizeHint which, const QSizeF & constraint) const
92{
93 if (const QWidget *w = widget())
94 return QSizeF(w->size());
95 return ZoomProxyWidget::sizeHint(which, constraint);
96}
97
98// DesignerZoomWidget which returns DesignerZoomProxyWidget in its factory function
101public:
103private:
104 QGraphicsProxyWidget *createProxyWidget(QGraphicsItem *parent = nullptr,
105 Qt::WindowFlags wFlags = {}) const override;
106};
107
108DesignerZoomWidget::DesignerZoomWidget(QWidget *parent) :
109 ZoomWidget(parent)
110{
111}
112
113QGraphicsProxyWidget *DesignerZoomWidget::createProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) const
114{
115 return new DesignerZoomProxyWidget(parent, wFlags);
116}
117
118// PreviewDeviceSkin: Forwards the key events to the window and
119// provides context menu with rotation options. Derived class
120// can apply additional transformations to the skin.
121
123{
125public:
127
128 explicit PreviewDeviceSkin(const DeviceSkinParameters &parameters, QWidget *parent);
129 virtual void setPreview(QWidget *w);
130 QSize screenSize() const { return m_screenSize; }
131
132private slots:
133 void slotSkinKeyPressEvent(int code, const QString& text, bool autorep);
134 void slotSkinKeyReleaseEvent(int code, const QString& text, bool autorep);
135 void slotPopupMenu();
136
137protected:
138 virtual void populateContextMenu(QMenu *) {}
139
140private slots:
141 void slotDirection(QAction *);
142
143protected:
144 // Fit the widget in case the orientation changes (transposing screensize)
145 virtual void fitWidget(const QSize &size);
146 // Calculate the complete transformation for the skin
147 // (base class implementation provides rotation).
148 virtual QTransform skinTransform() const;
149
150private:
151 const QSize m_screenSize;
152 Direction m_direction;
153
154 QAction *m_directionUpAction;
155 QAction *m_directionLeftAction;
156 QAction *m_directionRightAction;
157 QAction *m_closeAction;
158};
159
161 DeviceSkin(parameters, parent),
164 m_directionUpAction(nullptr),
165 m_directionLeftAction(nullptr),
166 m_directionRightAction(nullptr),
167 m_closeAction(nullptr)
168{
169 connect(this, &PreviewDeviceSkin::skinKeyPressEvent,
170 this, &PreviewDeviceSkin::slotSkinKeyPressEvent);
171 connect(this, &PreviewDeviceSkin::skinKeyReleaseEvent,
172 this, &PreviewDeviceSkin::slotSkinKeyReleaseEvent);
173 connect(this, &PreviewDeviceSkin::popupMenu, this, &PreviewDeviceSkin::slotPopupMenu);
174}
175
177{
178 formWidget->setFixedSize(m_screenSize);
179 formWidget->setParent(this, Qt::SubWindow);
180 formWidget->setAutoFillBackground(true);
181 setView(formWidget);
182}
183
184void PreviewDeviceSkin::slotSkinKeyPressEvent(int code, const QString& text, bool autorep)
185{
186 if (QWidget *focusWidget = QApplication::focusWidget()) {
187 QKeyEvent e(QEvent::KeyPress, code, {}, text, autorep);
188 QApplication::sendEvent(focusWidget, &e);
189 }
190}
191
192void PreviewDeviceSkin::slotSkinKeyReleaseEvent(int code, const QString& text, bool autorep)
193{
194 if (QWidget *focusWidget = QApplication::focusWidget()) {
195 QKeyEvent e(QEvent::KeyRelease, code, {}, text, autorep);
196 QApplication::sendEvent(focusWidget, &e);
197 }
198}
199
200// Create a checkable action with integer data and
201// set it checked if it matches the currentState.
202static inline QAction
203 *createCheckableActionIntData(const QString &label,
204 int actionValue, int currentState,
205 QActionGroup *ag, QObject *parent)
206{
207 QAction *a = new QAction(label, parent);
208 a->setData(actionValue);
209 a->setCheckable(true);
210 if (actionValue == currentState)
211 a->setChecked(true);
212 ag->addAction(a);
213 return a;
214}
215
216void PreviewDeviceSkin::slotPopupMenu()
217{
218 QMenu menu(this);
219 // Create actions
220 if (!m_directionUpAction) {
221 QActionGroup *directionGroup = new QActionGroup(this);
222 connect(directionGroup, &QActionGroup::triggered, this, &PreviewDeviceSkin::slotDirection);
223 directionGroup->setExclusive(true);
224 m_directionUpAction = createCheckableActionIntData(tr("&Portrait"), DirectionUp, m_direction, directionGroup, this);
225 //: Rotate form preview counter-clockwise
226 m_directionLeftAction = createCheckableActionIntData(tr("Landscape (&CCW)"), DirectionLeft, m_direction, directionGroup, this);
227 //: Rotate form preview clockwise
228 m_directionRightAction = createCheckableActionIntData(tr("&Landscape (CW)"), DirectionRight, m_direction, directionGroup, this);
229 m_closeAction = new QAction(tr("&Close"), this);
230 connect(m_closeAction, &QAction::triggered, parentWidget(), &QWidget::close);
231 }
232 menu.addAction(m_directionUpAction);
233 menu.addAction(m_directionLeftAction);
234 menu.addAction(m_directionRightAction);
235 menu.addSeparator();
237 menu.addAction(m_closeAction);
238 menu.exec(QCursor::pos());
239}
240
241void PreviewDeviceSkin::slotDirection(QAction *a)
242{
243 const Direction newDirection = static_cast<Direction>(a->data().toInt());
245 return;
253 size.transpose();
255 }
258}
259
260void PreviewDeviceSkin::fitWidget(const QSize &size)
261{
262 view()->setFixedSize(size);
263}
264
266{
267 QTransform newTransform;
268 switch (m_direction) {
269 case DirectionUp:
270 break;
271 case DirectionLeft:
272 newTransform.rotate(270.0);
273 break;
274 case DirectionRight:
275 newTransform.rotate(90.0);
276 break;
277 }
278 return newTransform;
279}
280
281// ------------ PreviewConfigurationPrivate
283public:
285 explicit PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin);
286
288 // Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()).
291};
292
294 const PreviewConfigurationData &rhs) noexcept
295{
296 return lhs.m_style == rhs.m_style
297 && lhs.m_applicationStyleSheet == rhs.m_applicationStyleSheet
298 && lhs.m_deviceSkin == rhs.m_deviceSkin;
299}
300
301bool comparesEqual(const PreviewConfiguration &lhs,
302 const PreviewConfiguration &rhs) noexcept
303{
304 const auto *lhsd = lhs.m_d.constData();
305 const auto *rhsd = rhs.m_d.constData();
306 return lhsd == rhsd || comparesEqual(*lhsd, *rhsd);
307}
308
309Qt::strong_ordering compareThreeWay(const PreviewConfiguration &lhs,
310 const PreviewConfiguration &rhs) noexcept
311{
312 const int val = compare(lhs, rhs);
313 return Qt::compareThreeWay(val, 0);
314}
315
316PreviewConfigurationData::PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin) :
317 m_style(style),
320{
321}
322
323/* ZoomablePreviewDeviceSkin: A Zoomable Widget Preview skin. Embeds preview
324 * into a ZoomWidget and this in turn into the DeviceSkin view and keeps
325 * Device skin zoom + ZoomWidget zoom in sync. */
326
328{
330public:
332 void setPreview(QWidget *w) override;
333
334 int zoomPercent() const; // Device Skins have a double 'zoom' property
335
336public slots:
337 void setZoomPercent(int);
338
339signals:
341
342protected:
343 void populateContextMenu(QMenu *m) override;
344 QTransform skinTransform() const override;
345 void fitWidget(const QSize &size) override;
346
347private:
348 ZoomMenu *m_zoomMenu;
349 QAction *m_zoomSubMenuAction;
350 ZoomWidget *m_zoomWidget;
351};
352
353ZoomablePreviewDeviceSkin::ZoomablePreviewDeviceSkin(const DeviceSkinParameters &parameters, QWidget *parent) :
354 PreviewDeviceSkin(parameters, parent),
355 m_zoomMenu(new ZoomMenu(this)),
356 m_zoomSubMenuAction(nullptr),
357 m_zoomWidget(new DesignerZoomWidget)
358{
359 connect(m_zoomMenu, &ZoomMenu::zoomChanged, this, &ZoomablePreviewDeviceSkin::setZoomPercent);
360 connect(m_zoomMenu, &ZoomMenu::zoomChanged, this, &ZoomablePreviewDeviceSkin::zoomPercentChanged);
361 m_zoomWidget->setZoomContextMenuEnabled(false);
362 m_zoomWidget->setWidgetZoomContextMenuEnabled(false);
363 m_zoomWidget->resize(screenSize());
364 m_zoomWidget->setParent(this, Qt::SubWindow);
365 m_zoomWidget->setAutoFillBackground(true);
366 setView(m_zoomWidget);
367}
368
369static inline qreal zoomFactor(int percent)
370{
371 return qreal(percent) / 100.0;
372}
373
374static inline QSize scaleSize(int zoomPercent, const QSize &size)
375{
376 return zoomPercent == 100 ? size : (QSizeF(size) * zoomFactor(zoomPercent)).toSize();
377}
378
380{
381 m_zoomWidget->setWidget(formWidget);
382 m_zoomWidget->resize(scaleSize(zoomPercent(), screenSize()));
383}
384
386{
387 return m_zoomWidget->zoom();
388}
389
390void ZoomablePreviewDeviceSkin::setZoomPercent(int zp)
391{
392 if (zp == zoomPercent())
393 return;
394
395 // If not triggered by the menu itself: Update it
396 if (m_zoomMenu->zoom() != zp)
397 m_zoomMenu->setZoom(zp);
398
399 QApplication::setOverrideCursor(Qt::WaitCursor);
400 m_zoomWidget->setZoom(zp);
401 setTransform(skinTransform());
402 QApplication::restoreOverrideCursor();
403}
404
406{
407 if (!m_zoomSubMenuAction) {
408 m_zoomSubMenuAction = new QAction(tr("&Zoom"), this);
409 QMenu *zoomSubMenu = new QMenu;
410 m_zoomSubMenuAction->setMenu(zoomSubMenu);
411 m_zoomMenu->addActions(zoomSubMenu);
412 }
413 menu->addAction(m_zoomSubMenuAction);
414 menu->addSeparator();
415}
416
418{
419 // Complete transformation consisting of base class rotation and zoom.
420 QTransform rc = PreviewDeviceSkin::skinTransform();
421 const int zp = zoomPercent();
422 if (zp != 100) {
423 const qreal factor = zoomFactor(zp);
424 rc.scale(factor, factor);
425 }
426 return rc;
427}
428
429void ZoomablePreviewDeviceSkin::fitWidget(const QSize &size)
430{
431 m_zoomWidget->resize(scaleSize(zoomPercent(), size));
432}
433
434// ------------- PreviewConfiguration
435
436static constexpr auto styleKey = "Style"_L1;
437static constexpr auto appStyleSheetKey = "AppStyleSheet"_L1;
438static constexpr auto skinKey = "Skin"_L1;
439
444
449
454
460
462
470
472{
473 return m_d->m_style;
474}
475
477{
478 m_d->m_style = s;
479}
480
481// Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()).
486
491
496
501
511
531
532// ------------- PreviewManagerPrivate
550
551PreviewManagerPrivate::PreviewManagerPrivate(PreviewManager::PreviewMode mode) :
552 m_mode(mode),
553 m_core(nullptr),
554 m_updateBlocked(false)
555{
556}
557
558// ------------- PreviewManager
559
565
567{
568 delete d;
569}
570
571
573{
574#ifdef Q_OS_WIN
578#else
580 // Only Dialogs have close buttons on Mac.
581 // On Linux, we don't want an additional task bar item and we don't want a minimize button;
582 // we want the preview to be on top.
584#endif
585 return windowFlags;
586}
587
592
593// Some widgets might require fake containers
594
596{
597 // Prevent a dock widget from trying to dock to Designer's main window
598 // (which can be found in the parent hierarchy in MDI mode) by
599 // providing a fake mainwindow
600 if (QDockWidget *dock = qobject_cast<QDockWidget *>(w)) {
601 // Reparent: Clear modality, propagate title and resize outer container
602 const QSize size = w->size();
603 w->setWindowModality(Qt::NonModal);
604 dock->setFeatures(dock->features() & ~(QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetClosable));
605 dock->setAllowedAreas(Qt::LeftDockWidgetArea);
606 QMainWindow *mw = new QMainWindow;
607 const QMargins cm = mw->contentsMargins();
608 mw->addDockWidget(Qt::LeftDockWidgetArea, dock);
609 mw->resize(size + QSize(cm.left() + cm.right(), cm.top() + cm.bottom()));
610 return mw;
611 }
612 return w;
613}
614
615static PreviewConfiguration configurationFromSettings(QDesignerFormEditorInterface *core, const QString &style)
616{
617 qdesigner_internal::PreviewConfiguration pc;
618 const QDesignerSharedSettings settings(core);
619 if (settings.isCustomPreviewConfigurationEnabled())
620 pc = settings.customPreviewConfiguration();
621 if (!style.isEmpty())
622 pc.setStyle(style);
623 return pc;
624}
625
630
635
640 int initialZoom)
641{
642 if (!d->m_core)
643 d->m_core = fw->core();
644
645 const bool zoomable = initialZoom > 0;
646 // Figure out which profile to apply
648 if (deviceProfileIndex >= 0) {
650 } else {
651 if (const FormWindowBase *fwb = qobject_cast<const FormWindowBase *>(fw))
653 }
654 // Create
656 if (!formWidget)
657 return nullptr;
658
659 const QString title = tr("%1 - [Preview]").arg(formWidget->windowTitle());
662
663 // Clear any modality settings, child widget modalities must not be higher than parent's
665 // No skin
667 if (deviceSkin.isEmpty()) {
668 if (zoomable) { // Embed into ZoomWidget
673 // Keep any widgets' context menus working, do not use global menu
676 // Make preview close when Widget closes (Dialog/accept, etc)
681 return zw;
682 }
685 return formWidget;
686 }
687 // Embed into skin. find config in cache
689 if (it == d->m_deviceSkinConfigCache.end()) {
693 return nullptr;
694 }
696 }
697
699 PreviewDeviceSkin *skin = nullptr;
700 if (zoomable) {
705 skin = zds;
706 } else {
708 }
710 // Make preview close when Widget closes (Dialog/accept, etc)
715 return skinContainer;
716}
717
722{
723 enum { Spacing = 10 };
726
728 const int initialZoom = settings.zoomEnabled() ? settings.zoom() : -1;
729
731 if (!widget)
732 return nullptr;
733 // Install filter for Escape key
736
737 switch (d->m_mode) {
739 // Cannot do this on the Mac as the dialog would have no close button
741 break;
749 widget, &QWidget::close);
750 }
751 break;
752 }
753 // Semi-smart algorithm to position previews:
754 // If its the first one, position relative to form.
755 // 2nd, attempt to tile right (for comparing styles) or cascade
756 const QSize size = widget->size();
757 const bool firstPreview = d->m_previews.isEmpty();
758 if (firstPreview) {
760 } else {
765 if (newPos.x() + size.width() < availGeometry.right())
767 else
769 }
770
771 }
773 widget->show();
774 if (firstPreview)
776 return widget;
777}
778
780{
781 if (d->m_previews.isEmpty())
782 return nullptr;
783
784 // find matching window
785 for (const auto &pd : std::as_const(d->m_previews)) {
786 QWidget *w = pd.m_widget;
787 if (w && pd.m_formWindow == fw && pd.m_configuration == pc) {
788 w->raise();
789 w->activateWindow();
790 return w;
791 }
792 }
793 return nullptr;
794}
795
797{
798 if (!d->m_previews.isEmpty()) {
799 d->m_updateBlocked = true;
800 d->m_activePreview = nullptr;
801 for (const auto &pd : std::as_const(d->m_previews)) {
802 if (pd.m_widget)
803 pd.m_widget->close();
804 }
805 d->m_previews.clear();
806 d->m_updateBlocked = false;
808 }
809}
810
812{
813 if (d->m_updateBlocked)
814 return;
815 // Purge out all 0 or widgets to be deleted
816 for (auto it = d->m_previews.begin(); it != d->m_previews.end() ; ) {
817 QWidget *iw = it->m_widget; // Might be 0 when catching QEvent::Destroyed
818 if (iw == nullptr || iw == w) {
819 it = d->m_previews.erase(it);
820 } else {
821 ++it;
822 }
823 }
824 if (d->m_previews.isEmpty())
826}
827
829{
830 // Courtesy of designer
831 do {
832 if (!watched->isWidgetType())
833 break;
836 break;
837
838 switch (event->type()) {
839 case QEvent::KeyPress:
840 case QEvent::ShortcutOverride: {
841 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
842 const int key = keyEvent->key();
843 if ((key == Qt::Key_Escape
844#ifdef Q_OS_MACOS
846#endif
847 )) {
849 return true;
850 }
851 }
852 break;
855 break;
856 case QEvent::Destroy: // We don't get QEvent::Close if someone accepts a QDialog.
858 break;
859 case QEvent::Close:
862 break;
863 default:
864 break;
865 }
866 } while(false);
868}
869
871{
872 return d->m_previews.size();
873}
874
879
884
897
899{
900 if (d->m_core) { // Save the last zoom chosen by the user.
903 }
904}
905}
906
907QT_END_NAMESPACE
908
909#include "previewmanager.moc"
QWidget * view() const
void setView(QWidget *v)
friend class QWidget
Definition qpainter.h:432
QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint=QSizeF()) const override
This pure virtual function returns the size hint for which of the QGraphicsLayoutItem,...
QGraphicsProxyWidget * createProxyWidget(QGraphicsItem *parent=nullptr, Qt::WindowFlags wFlags={}) const override
PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin)
virtual void fitWidget(const QSize &size)
virtual QTransform skinTransform() const
PreviewDeviceSkin(const DeviceSkinParameters &parameters, QWidget *parent)
PreviewManagerPrivate(PreviewManager::PreviewMode mode)
QDesignerFormEditorInterface * m_core
QMap< QString, DeviceSkinParameters > m_deviceSkinConfigCache
void fitWidget(const QSize &size) override
Combined button and popup list for selecting options.
Auxiliary methods to store/retrieve settings.
static PreviewConfiguration configurationFromSettings(QDesignerFormEditorInterface *core, const QString &style)
static constexpr auto skinKey
static constexpr auto appStyleSheetKey
static qreal zoomFactor(int percent)
static QAction * createCheckableActionIntData(const QString &label, int actionValue, int currentState, QActionGroup *ag, QObject *parent)
Qt::strong_ordering compareThreeWay(const PreviewConfiguration &lhs, const PreviewConfiguration &rhs) noexcept
static constexpr auto styleKey
static QWidget * fakeContainer(QWidget *w)
static QSize scaleSize(int zoomPercent, const QSize &size)
static bool comparesEqual(const PreviewConfigurationData &lhs, const PreviewConfigurationData &rhs) noexcept
bool comparesEqual(const PreviewConfiguration &lhs, const PreviewConfiguration &rhs) noexcept
static int compare(const qdesigner_internal::PreviewConfiguration &pc1, const qdesigner_internal::PreviewConfiguration &pc2) noexcept
PreviewData(const QPointer< QWidget > &widget, const QDesignerFormWindowInterface *formWindow, const qdesigner_internal::PreviewConfiguration &pc)
const QDesignerFormWindowInterface * m_formWindow
qdesigner_internal::PreviewConfiguration m_configuration