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
9#include "zoomwidget_p.h"
10
11#include <deviceskin_p.h>
12
13#include <QtDesigner/abstractformeditor.h>
14#include <QtDesigner/abstractformwindow.h>
15#include <QtDesigner/abstractformwindowmanager.h>
16#include <QtDesigner/abstractsettings.h>
17
18#include <QtWidgets/qapplication.h>
19#include <QtWidgets/qboxlayout.h>
20#include <QtWidgets/qdialog.h>
21#include <QtWidgets/qdockwidget.h>
22#include <QtWidgets/qmainwindow.h>
23#include <QtWidgets/qmenu.h>
24
25#include <QtGui/qaction.h>
26#include <QtGui/qactiongroup.h>
27#include <QtGui/qcursor.h>
28#include <QtGui/qevent.h>
29#include <QtGui/qpixmap.h>
30#include <QtGui/qscreen.h>
31#include <QtGui/qtransform.h>
32
33#include <QtCore/qdebug.h>
34#include <QtCore/qlist.h>
35#include <QtCore/qmap.h>
36#include <QtCore/qpointer.h>
37#include <QtCore/qshareddata.h>
38
40
41using namespace Qt::StringLiterals;
42
43static inline int compare(const qdesigner_internal::PreviewConfiguration &pc1,
44 const qdesigner_internal::PreviewConfiguration &pc2) noexcept
45{
46 int rc = pc1.style().compare(pc2.style());
47 if (rc)
48 return rc;
49 rc = pc1.applicationStyleSheet().compare(pc2.applicationStyleSheet());
50 if (rc)
51 return rc;
52 return pc1.deviceSkin().compare(pc2.deviceSkin());
53}
54
55namespace qdesigner_internal {
56 // ------ PreviewData (data associated with a preview window)
57 struct PreviewData {
58 PreviewData(const QPointer<QWidget> &widget, const QDesignerFormWindowInterface *formWindow, const qdesigner_internal::PreviewConfiguration &pc);
60 const QDesignerFormWindowInterface *m_formWindow;
61 qdesigner_internal::PreviewConfiguration m_configuration;
62 };
63
64 PreviewData::PreviewData(const QPointer<QWidget>& widget,
65 const QDesignerFormWindowInterface *formWindow,
66 const qdesigner_internal::PreviewConfiguration &pc) :
68 m_formWindow(formWindow),
70 {
71 }
72
73/* In designer, we have the situation that laid-out maincontainers have
74 * a geometry set (which might differ from their sizeHint()). The QGraphicsItem
75 * should return that in its size hint, else such cases won't work */
76
79public:
81protected:
82 QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint = QSizeF() ) const override;
83};
84
85DesignerZoomProxyWidget::DesignerZoomProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) :
86 ZoomProxyWidget(parent, wFlags)
87{
88}
89
90QSizeF DesignerZoomProxyWidget::sizeHint(Qt::SizeHint which, const QSizeF & constraint) const
91{
92 if (const QWidget *w = widget())
93 return QSizeF(w->size());
94 return ZoomProxyWidget::sizeHint(which, constraint);
95}
96
97// DesignerZoomWidget which returns DesignerZoomProxyWidget in its factory function
100public:
102private:
103 QGraphicsProxyWidget *createProxyWidget(QGraphicsItem *parent = nullptr,
104 Qt::WindowFlags wFlags = {}) const override;
105};
106
107DesignerZoomWidget::DesignerZoomWidget(QWidget *parent) :
108 ZoomWidget(parent)
109{
110}
111
112QGraphicsProxyWidget *DesignerZoomWidget::createProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) const
113{
114 return new DesignerZoomProxyWidget(parent, wFlags);
115}
116
117// PreviewDeviceSkin: Forwards the key events to the window and
118// provides context menu with rotation options. Derived class
119// can apply additional transformations to the skin.
120
122{
124public:
126
127 explicit PreviewDeviceSkin(const DeviceSkinParameters &parameters, QWidget *parent);
128 virtual void setPreview(QWidget *w);
129 QSize screenSize() const { return m_screenSize; }
130
131private slots:
132 void slotSkinKeyPressEvent(int code, const QString& text, bool autorep);
133 void slotSkinKeyReleaseEvent(int code, const QString& text, bool autorep);
134 void slotPopupMenu();
135
136protected:
137 virtual void populateContextMenu(QMenu *) {}
138
139private slots:
140 void slotDirection(QAction *);
141
142protected:
143 // Fit the widget in case the orientation changes (transposing screensize)
144 virtual void fitWidget(const QSize &size);
145 // Calculate the complete transformation for the skin
146 // (base class implementation provides rotation).
147 virtual QTransform skinTransform() const;
148
149private:
150 const QSize m_screenSize;
151 Direction m_direction;
152
153 QAction *m_directionUpAction;
154 QAction *m_directionLeftAction;
155 QAction *m_directionRightAction;
156 QAction *m_closeAction;
157};
158
160 DeviceSkin(parameters, parent),
163 m_directionUpAction(nullptr),
164 m_directionLeftAction(nullptr),
165 m_directionRightAction(nullptr),
166 m_closeAction(nullptr)
167{
168 connect(this, &PreviewDeviceSkin::skinKeyPressEvent,
169 this, &PreviewDeviceSkin::slotSkinKeyPressEvent);
170 connect(this, &PreviewDeviceSkin::skinKeyReleaseEvent,
171 this, &PreviewDeviceSkin::slotSkinKeyReleaseEvent);
172 connect(this, &PreviewDeviceSkin::popupMenu, this, &PreviewDeviceSkin::slotPopupMenu);
173}
174
176{
177 formWidget->setFixedSize(m_screenSize);
178 formWidget->setParent(this, Qt::SubWindow);
179 formWidget->setAutoFillBackground(true);
180 setView(formWidget);
181}
182
183void PreviewDeviceSkin::slotSkinKeyPressEvent(int code, const QString& text, bool autorep)
184{
185 if (QWidget *focusWidget = QApplication::focusWidget()) {
186 QKeyEvent e(QEvent::KeyPress, code, {}, text, autorep);
187 QApplication::sendEvent(focusWidget, &e);
188 }
189}
190
191void PreviewDeviceSkin::slotSkinKeyReleaseEvent(int code, const QString& text, bool autorep)
192{
193 if (QWidget *focusWidget = QApplication::focusWidget()) {
194 QKeyEvent e(QEvent::KeyRelease, code, {}, text, autorep);
195 QApplication::sendEvent(focusWidget, &e);
196 }
197}
198
199// Create a checkable action with integer data and
200// set it checked if it matches the currentState.
201static inline QAction
202 *createCheckableActionIntData(const QString &label,
203 int actionValue, int currentState,
204 QActionGroup *ag, QObject *parent)
205{
206 QAction *a = new QAction(label, parent);
207 a->setData(actionValue);
208 a->setCheckable(true);
209 if (actionValue == currentState)
210 a->setChecked(true);
211 ag->addAction(a);
212 return a;
213}
214
215void PreviewDeviceSkin::slotPopupMenu()
216{
217 QMenu menu(this);
218 // Create actions
219 if (!m_directionUpAction) {
220 QActionGroup *directionGroup = new QActionGroup(this);
221 connect(directionGroup, &QActionGroup::triggered, this, &PreviewDeviceSkin::slotDirection);
222 directionGroup->setExclusive(true);
223 m_directionUpAction = createCheckableActionIntData(tr("&Portrait"), DirectionUp, m_direction, directionGroup, this);
224 //: Rotate form preview counter-clockwise
225 m_directionLeftAction = createCheckableActionIntData(tr("Landscape (&CCW)"), DirectionLeft, m_direction, directionGroup, this);
226 //: Rotate form preview clockwise
227 m_directionRightAction = createCheckableActionIntData(tr("&Landscape (CW)"), DirectionRight, m_direction, directionGroup, this);
228 m_closeAction = new QAction(tr("&Close"), this);
229 connect(m_closeAction, &QAction::triggered, parentWidget(), &QWidget::close);
230 }
231 menu.addAction(m_directionUpAction);
232 menu.addAction(m_directionLeftAction);
233 menu.addAction(m_directionRightAction);
234 menu.addSeparator();
236 menu.addAction(m_closeAction);
237 menu.exec(QCursor::pos());
238}
239
240void PreviewDeviceSkin::slotDirection(QAction *a)
241{
242 const Direction newDirection = static_cast<Direction>(a->data().toInt());
243 if (m_direction == newDirection)
244 return;
245 const Qt::Orientation newOrientation = newDirection == DirectionUp ? Qt::Vertical : Qt::Horizontal;
246 const Qt::Orientation oldOrientation = m_direction == DirectionUp ? Qt::Vertical : Qt::Horizontal;
247 m_direction = newDirection;
248 QApplication::setOverrideCursor(Qt::WaitCursor);
249 if (oldOrientation != newOrientation) {
250 QSize size = screenSize();
251 if (newOrientation == Qt::Horizontal)
252 size.transpose();
253 fitWidget(size);
254 }
255 setTransform(skinTransform());
256 QApplication::restoreOverrideCursor();
257}
258
259void PreviewDeviceSkin::fitWidget(const QSize &size)
260{
261 view()->setFixedSize(size);
262}
263
265{
266 QTransform newTransform;
267 switch (m_direction) {
268 case DirectionUp:
269 break;
270 case DirectionLeft:
271 newTransform.rotate(270.0);
272 break;
273 case DirectionRight:
274 newTransform.rotate(90.0);
275 break;
276 }
277 return newTransform;
278}
279
280// ------------ PreviewConfigurationPrivate
282public:
284 explicit PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin);
285
287 // Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()).
290};
291
293 const PreviewConfigurationData &rhs) noexcept
294{
295 return lhs.m_style == rhs.m_style
296 && lhs.m_applicationStyleSheet == rhs.m_applicationStyleSheet
297 && lhs.m_deviceSkin == rhs.m_deviceSkin;
298}
299
300bool comparesEqual(const PreviewConfiguration &lhs,
301 const PreviewConfiguration &rhs) noexcept
302{
303 const auto *lhsd = lhs.m_d.constData();
304 const auto *rhsd = rhs.m_d.constData();
305 return lhsd == rhsd || comparesEqual(*lhsd, *rhsd);
306}
307
308Qt::strong_ordering compareThreeWay(const PreviewConfiguration &lhs,
309 const PreviewConfiguration &rhs) noexcept
310{
311 const int val = compare(lhs, rhs);
312 return Qt::compareThreeWay(val, 0);
313}
314
315PreviewConfigurationData::PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin) :
316 m_style(style),
319{
320}
321
322/* ZoomablePreviewDeviceSkin: A Zoomable Widget Preview skin. Embeds preview
323 * into a ZoomWidget and this in turn into the DeviceSkin view and keeps
324 * Device skin zoom + ZoomWidget zoom in sync. */
325
327{
329public:
331 void setPreview(QWidget *w) override;
332
333 int zoomPercent() const; // Device Skins have a double 'zoom' property
334
335public slots:
336 void setZoomPercent(int);
337
338signals:
340
341protected:
342 void populateContextMenu(QMenu *m) override;
343 QTransform skinTransform() const override;
344 void fitWidget(const QSize &size) override;
345
346private:
347 ZoomMenu *m_zoomMenu;
348 QAction *m_zoomSubMenuAction;
349 ZoomWidget *m_zoomWidget;
350};
351
352ZoomablePreviewDeviceSkin::ZoomablePreviewDeviceSkin(const DeviceSkinParameters &parameters, QWidget *parent) :
353 PreviewDeviceSkin(parameters, parent),
354 m_zoomMenu(new ZoomMenu(this)),
355 m_zoomSubMenuAction(nullptr),
356 m_zoomWidget(new DesignerZoomWidget)
357{
358 connect(m_zoomMenu, &ZoomMenu::zoomChanged, this, &ZoomablePreviewDeviceSkin::setZoomPercent);
359 connect(m_zoomMenu, &ZoomMenu::zoomChanged, this, &ZoomablePreviewDeviceSkin::zoomPercentChanged);
360 m_zoomWidget->setZoomContextMenuEnabled(false);
361 m_zoomWidget->setWidgetZoomContextMenuEnabled(false);
362 m_zoomWidget->resize(screenSize());
363 m_zoomWidget->setParent(this, Qt::SubWindow);
364 m_zoomWidget->setAutoFillBackground(true);
365 setView(m_zoomWidget);
366}
367
368static inline qreal zoomFactor(int percent)
369{
370 return qreal(percent) / 100.0;
371}
372
373static inline QSize scaleSize(int zoomPercent, const QSize &size)
374{
375 return zoomPercent == 100 ? size : (QSizeF(size) * zoomFactor(zoomPercent)).toSize();
376}
377
379{
380 m_zoomWidget->setWidget(formWidget);
381 m_zoomWidget->resize(scaleSize(zoomPercent(), screenSize()));
382}
383
385{
386 return m_zoomWidget->zoom();
387}
388
389void ZoomablePreviewDeviceSkin::setZoomPercent(int zp)
390{
391 if (zp == zoomPercent())
392 return;
393
394 // If not triggered by the menu itself: Update it
395 if (m_zoomMenu->zoom() != zp)
396 m_zoomMenu->setZoom(zp);
397
398 QApplication::setOverrideCursor(Qt::WaitCursor);
399 m_zoomWidget->setZoom(zp);
400 setTransform(skinTransform());
401 QApplication::restoreOverrideCursor();
402}
403
405{
406 if (!m_zoomSubMenuAction) {
407 m_zoomSubMenuAction = new QAction(tr("&Zoom"), this);
408 QMenu *zoomSubMenu = new QMenu;
409 m_zoomSubMenuAction->setMenu(zoomSubMenu);
410 m_zoomMenu->addActions(zoomSubMenu);
411 }
412 menu->addAction(m_zoomSubMenuAction);
413 menu->addSeparator();
414}
415
417{
418 // Complete transformation consisting of base class rotation and zoom.
419 QTransform rc = PreviewDeviceSkin::skinTransform();
420 const int zp = zoomPercent();
421 if (zp != 100) {
422 const qreal factor = zoomFactor(zp);
423 rc.scale(factor, factor);
424 }
425 return rc;
426}
427
428void ZoomablePreviewDeviceSkin::fitWidget(const QSize &size)
429{
430 m_zoomWidget->resize(scaleSize(zoomPercent(), size));
431}
432
433// ------------- PreviewConfiguration
434
435static constexpr auto styleKey = "Style"_L1;
436static constexpr auto appStyleSheetKey = "AppStyleSheet"_L1;
437static constexpr auto skinKey = "Skin"_L1;
438
443
448
453
459
461
469
471{
472 return m_d->m_style;
473}
474
476{
477 m_d->m_style = s;
478}
479
480// Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()).
485
490
495
500
510
530
531// ------------- PreviewManagerPrivate
549
550PreviewManagerPrivate::PreviewManagerPrivate(PreviewManager::PreviewMode mode) :
551 m_mode(mode),
552 m_core(nullptr),
553 m_updateBlocked(false)
554{
555}
556
557// ------------- PreviewManager
558
564
566{
567 delete d;
568}
569
570
572{
573#ifdef Q_OS_WIN
577#else
579 // Only Dialogs have close buttons on Mac.
580 // On Linux, we don't want an additional task bar item and we don't want a minimize button;
581 // we want the preview to be on top.
583#endif
584 return windowFlags;
585}
586
591
592// Some widgets might require fake containers
593
595{
596 // Prevent a dock widget from trying to dock to Designer's main window
597 // (which can be found in the parent hierarchy in MDI mode) by
598 // providing a fake mainwindow
599 if (QDockWidget *dock = qobject_cast<QDockWidget *>(w)) {
600 // Reparent: Clear modality, propagate title and resize outer container
601 const QSize size = w->size();
602 w->setWindowModality(Qt::NonModal);
603 dock->setFeatures(dock->features() & ~(QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetClosable));
604 dock->setAllowedAreas(Qt::LeftDockWidgetArea);
605 QMainWindow *mw = new QMainWindow;
606 const QMargins cm = mw->contentsMargins();
607 mw->addDockWidget(Qt::LeftDockWidgetArea, dock);
608 mw->resize(size + QSize(cm.left() + cm.right(), cm.top() + cm.bottom()));
609 return mw;
610 }
611 return w;
612}
613
614static PreviewConfiguration configurationFromSettings(QDesignerFormEditorInterface *core, const QString &style)
615{
616 qdesigner_internal::PreviewConfiguration pc;
617 const QDesignerSharedSettings settings(core);
618 if (settings.isCustomPreviewConfigurationEnabled())
619 pc = settings.customPreviewConfiguration();
620 if (!style.isEmpty())
621 pc.setStyle(style);
622 return pc;
623}
624
629
634
639 int initialZoom)
640{
641 if (!d->m_core)
642 d->m_core = fw->core();
643
644 const bool zoomable = initialZoom > 0;
645 // Figure out which profile to apply
647 if (deviceProfileIndex >= 0) {
649 } else {
650 if (const FormWindowBase *fwb = qobject_cast<const FormWindowBase *>(fw))
652 }
653 // Create
655 if (!formWidget)
656 return nullptr;
657
658 const QString title = tr("%1 - [Preview]").arg(formWidget->windowTitle());
661
662 // Clear any modality settings, child widget modalities must not be higher than parent's
664 // No skin
666 if (deviceSkin.isEmpty()) {
667 if (zoomable) { // Embed into ZoomWidget
672 // Keep any widgets' context menus working, do not use global menu
675 // Make preview close when Widget closes (Dialog/accept, etc)
680 return zw;
681 }
684 return formWidget;
685 }
686 // Embed into skin. find config in cache
688 if (it == d->m_deviceSkinConfigCache.end()) {
692 return nullptr;
693 }
695 }
696
698 PreviewDeviceSkin *skin = nullptr;
699 if (zoomable) {
704 skin = zds;
705 } else {
707 }
709 // Make preview close when Widget closes (Dialog/accept, etc)
714 return skinContainer;
715}
716
721{
722 enum { Spacing = 10 };
725
727 const int initialZoom = settings.zoomEnabled() ? settings.zoom() : -1;
728
730 if (!widget)
731 return nullptr;
732 // Install filter for Escape key
735
736 switch (d->m_mode) {
738 // Cannot do this on the Mac as the dialog would have no close button
740 break;
748 widget, &QWidget::close);
749 }
750 break;
751 }
752 // Semi-smart algorithm to position previews:
753 // If its the first one, position relative to form.
754 // 2nd, attempt to tile right (for comparing styles) or cascade
755 const QSize size = widget->size();
756 const bool firstPreview = d->m_previews.isEmpty();
757 if (firstPreview) {
759 } else {
764 if (newPos.x() + size.width() < availGeometry.right())
766 else
768 }
769
770 }
772 widget->show();
773 if (firstPreview)
775 return widget;
776}
777
779{
780 if (d->m_previews.isEmpty())
781 return nullptr;
782
783 // find matching window
784 for (const auto &pd : std::as_const(d->m_previews)) {
785 QWidget *w = pd.m_widget;
786 if (w && pd.m_formWindow == fw && pd.m_configuration == pc) {
787 w->raise();
788 w->activateWindow();
789 return w;
790 }
791 }
792 return nullptr;
793}
794
796{
797 if (!d->m_previews.isEmpty()) {
798 d->m_updateBlocked = true;
799 d->m_activePreview = nullptr;
800 for (const auto &pd : std::as_const(d->m_previews)) {
801 if (pd.m_widget)
802 pd.m_widget->close();
803 }
804 d->m_previews.clear();
805 d->m_updateBlocked = false;
807 }
808}
809
811{
812 if (d->m_updateBlocked)
813 return;
814 // Purge out all 0 or widgets to be deleted
815 for (auto it = d->m_previews.begin(); it != d->m_previews.end() ; ) {
816 QWidget *iw = it->m_widget; // Might be 0 when catching QEvent::Destroyed
817 if (iw == nullptr || iw == w) {
818 it = d->m_previews.erase(it);
819 } else {
820 ++it;
821 }
822 }
823 if (d->m_previews.isEmpty())
825}
826
828{
829 // Courtesy of designer
830 do {
831 if (!watched->isWidgetType())
832 break;
835 break;
836
837 switch (event->type()) {
838 case QEvent::KeyPress:
839 case QEvent::ShortcutOverride: {
840 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
841 const int key = keyEvent->key();
842 if ((key == Qt::Key_Escape
843#ifdef Q_OS_MACOS
845#endif
846 )) {
848 return true;
849 }
850 }
851 break;
854 break;
855 case QEvent::Destroy: // We don't get QEvent::Close if someone accepts a QDialog.
857 break;
858 case QEvent::Close:
861 break;
862 default:
863 break;
864 }
865 } while(false);
867}
868
870{
871 return d->m_previews.size();
872}
873
878
883
896
898{
899 if (d->m_core) { // Save the last zoom chosen by the user.
902 }
903}
904}
905
906QT_END_NAMESPACE
907
908#include "previewmanager.moc"
QWidget * view() const
void setView(QWidget *v)
friend class QWidget
Definition qpainter.h:431
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
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