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, const qdesigner_internal::PreviewConfiguration &pc2)
44{
45 int rc = pc1.style().compare(pc2.style());
46 if (rc)
47 return rc;
48 rc = pc1.applicationStyleSheet().compare(pc2.applicationStyleSheet());
49 if (rc)
50 return rc;
51 return pc1.deviceSkin().compare(pc2.deviceSkin());
52}
53
54namespace qdesigner_internal {
55 // ------ PreviewData (data associated with a preview window)
56 struct PreviewData {
57 PreviewData(const QPointer<QWidget> &widget, const QDesignerFormWindowInterface *formWindow, const qdesigner_internal::PreviewConfiguration &pc);
59 const QDesignerFormWindowInterface *m_formWindow;
60 qdesigner_internal::PreviewConfiguration m_configuration;
61 };
62
63 PreviewData::PreviewData(const QPointer<QWidget>& widget,
64 const QDesignerFormWindowInterface *formWindow,
65 const qdesigner_internal::PreviewConfiguration &pc) :
67 m_formWindow(formWindow),
69 {
70 }
71
72/* In designer, we have the situation that laid-out maincontainers have
73 * a geometry set (which might differ from their sizeHint()). The QGraphicsItem
74 * should return that in its size hint, else such cases won't work */
75
78public:
80protected:
81 QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint = QSizeF() ) const override;
82};
83
84DesignerZoomProxyWidget::DesignerZoomProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) :
85 ZoomProxyWidget(parent, wFlags)
86{
87}
88
89QSizeF DesignerZoomProxyWidget::sizeHint(Qt::SizeHint which, const QSizeF & constraint) const
90{
91 if (const QWidget *w = widget())
92 return QSizeF(w->size());
93 return ZoomProxyWidget::sizeHint(which, constraint);
94}
95
96// DesignerZoomWidget which returns DesignerZoomProxyWidget in its factory function
99public:
101private:
102 QGraphicsProxyWidget *createProxyWidget(QGraphicsItem *parent = nullptr,
103 Qt::WindowFlags wFlags = {}) const override;
104};
105
106DesignerZoomWidget::DesignerZoomWidget(QWidget *parent) :
107 ZoomWidget(parent)
108{
109}
110
111QGraphicsProxyWidget *DesignerZoomWidget::createProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) const
112{
113 return new DesignerZoomProxyWidget(parent, wFlags);
114}
115
116// PreviewDeviceSkin: Forwards the key events to the window and
117// provides context menu with rotation options. Derived class
118// can apply additional transformations to the skin.
119
121{
123public:
125
126 explicit PreviewDeviceSkin(const DeviceSkinParameters &parameters, QWidget *parent);
127 virtual void setPreview(QWidget *w);
128 QSize screenSize() const { return m_screenSize; }
129
130private slots:
131 void slotSkinKeyPressEvent(int code, const QString& text, bool autorep);
132 void slotSkinKeyReleaseEvent(int code, const QString& text, bool autorep);
133 void slotPopupMenu();
134
135protected:
136 virtual void populateContextMenu(QMenu *) {}
137
138private slots:
139 void slotDirection(QAction *);
140
141protected:
142 // Fit the widget in case the orientation changes (transposing screensize)
143 virtual void fitWidget(const QSize &size);
144 // Calculate the complete transformation for the skin
145 // (base class implementation provides rotation).
146 virtual QTransform skinTransform() const;
147
148private:
149 const QSize m_screenSize;
150 Direction m_direction;
151
152 QAction *m_directionUpAction;
153 QAction *m_directionLeftAction;
154 QAction *m_directionRightAction;
155 QAction *m_closeAction;
156};
157
159 DeviceSkin(parameters, parent),
162 m_directionUpAction(nullptr),
163 m_directionLeftAction(nullptr),
164 m_directionRightAction(nullptr),
165 m_closeAction(nullptr)
166{
167 connect(this, &PreviewDeviceSkin::skinKeyPressEvent,
168 this, &PreviewDeviceSkin::slotSkinKeyPressEvent);
169 connect(this, &PreviewDeviceSkin::skinKeyReleaseEvent,
170 this, &PreviewDeviceSkin::slotSkinKeyReleaseEvent);
171 connect(this, &PreviewDeviceSkin::popupMenu, this, &PreviewDeviceSkin::slotPopupMenu);
172}
173
175{
176 formWidget->setFixedSize(m_screenSize);
177 formWidget->setParent(this, Qt::SubWindow);
178 formWidget->setAutoFillBackground(true);
179 setView(formWidget);
180}
181
182void PreviewDeviceSkin::slotSkinKeyPressEvent(int code, const QString& text, bool autorep)
183{
184 if (QWidget *focusWidget = QApplication::focusWidget()) {
185 QKeyEvent e(QEvent::KeyPress, code, {}, text, autorep);
186 QApplication::sendEvent(focusWidget, &e);
187 }
188}
189
190void PreviewDeviceSkin::slotSkinKeyReleaseEvent(int code, const QString& text, bool autorep)
191{
192 if (QWidget *focusWidget = QApplication::focusWidget()) {
193 QKeyEvent e(QEvent::KeyRelease, code, {}, text, autorep);
194 QApplication::sendEvent(focusWidget, &e);
195 }
196}
197
198// Create a checkable action with integer data and
199// set it checked if it matches the currentState.
200static inline QAction
201 *createCheckableActionIntData(const QString &label,
202 int actionValue, int currentState,
203 QActionGroup *ag, QObject *parent)
204{
205 QAction *a = new QAction(label, parent);
206 a->setData(actionValue);
207 a->setCheckable(true);
208 if (actionValue == currentState)
209 a->setChecked(true);
210 ag->addAction(a);
211 return a;
212}
213
214void PreviewDeviceSkin::slotPopupMenu()
215{
216 QMenu menu(this);
217 // Create actions
218 if (!m_directionUpAction) {
219 QActionGroup *directionGroup = new QActionGroup(this);
220 connect(directionGroup, &QActionGroup::triggered, this, &PreviewDeviceSkin::slotDirection);
221 directionGroup->setExclusive(true);
222 m_directionUpAction = createCheckableActionIntData(tr("&Portrait"), DirectionUp, m_direction, directionGroup, this);
223 //: Rotate form preview counter-clockwise
224 m_directionLeftAction = createCheckableActionIntData(tr("Landscape (&CCW)"), DirectionLeft, m_direction, directionGroup, this);
225 //: Rotate form preview clockwise
226 m_directionRightAction = createCheckableActionIntData(tr("&Landscape (CW)"), DirectionRight, m_direction, directionGroup, this);
227 m_closeAction = new QAction(tr("&Close"), this);
228 connect(m_closeAction, &QAction::triggered, parentWidget(), &QWidget::close);
229 }
230 menu.addAction(m_directionUpAction);
231 menu.addAction(m_directionLeftAction);
232 menu.addAction(m_directionRightAction);
233 menu.addSeparator();
235 menu.addAction(m_closeAction);
236 menu.exec(QCursor::pos());
237}
238
239void PreviewDeviceSkin::slotDirection(QAction *a)
240{
241 const Direction newDirection = static_cast<Direction>(a->data().toInt());
242 if (m_direction == newDirection)
243 return;
244 const Qt::Orientation newOrientation = newDirection == DirectionUp ? Qt::Vertical : Qt::Horizontal;
245 const Qt::Orientation oldOrientation = m_direction == DirectionUp ? Qt::Vertical : Qt::Horizontal;
246 m_direction = newDirection;
247 QApplication::setOverrideCursor(Qt::WaitCursor);
248 if (oldOrientation != newOrientation) {
249 QSize size = screenSize();
250 if (newOrientation == Qt::Horizontal)
251 size.transpose();
252 fitWidget(size);
253 }
254 setTransform(skinTransform());
255 QApplication::restoreOverrideCursor();
256}
257
258void PreviewDeviceSkin::fitWidget(const QSize &size)
259{
260 view()->setFixedSize(size);
261}
262
264{
265 QTransform newTransform;
266 switch (m_direction) {
267 case DirectionUp:
268 break;
269 case DirectionLeft:
270 newTransform.rotate(270.0);
271 break;
272 case DirectionRight:
273 newTransform.rotate(90.0);
274 break;
275 }
276 return newTransform;
277}
278
279// ------------ PreviewConfigurationPrivate
281public:
283 explicit PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin);
284
286 // Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()).
289};
290
291PreviewConfigurationData::PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin) :
292 m_style(style),
295{
296}
297
298/* ZoomablePreviewDeviceSkin: A Zoomable Widget Preview skin. Embeds preview
299 * into a ZoomWidget and this in turn into the DeviceSkin view and keeps
300 * Device skin zoom + ZoomWidget zoom in sync. */
301
303{
305public:
307 void setPreview(QWidget *w) override;
308
309 int zoomPercent() const; // Device Skins have a double 'zoom' property
310
311public slots:
312 void setZoomPercent(int);
313
314signals:
316
317protected:
318 void populateContextMenu(QMenu *m) override;
319 QTransform skinTransform() const override;
320 void fitWidget(const QSize &size) override;
321
322private:
323 ZoomMenu *m_zoomMenu;
324 QAction *m_zoomSubMenuAction;
325 ZoomWidget *m_zoomWidget;
326};
327
328ZoomablePreviewDeviceSkin::ZoomablePreviewDeviceSkin(const DeviceSkinParameters &parameters, QWidget *parent) :
329 PreviewDeviceSkin(parameters, parent),
330 m_zoomMenu(new ZoomMenu(this)),
331 m_zoomSubMenuAction(nullptr),
332 m_zoomWidget(new DesignerZoomWidget)
333{
334 connect(m_zoomMenu, &ZoomMenu::zoomChanged, this, &ZoomablePreviewDeviceSkin::setZoomPercent);
335 connect(m_zoomMenu, &ZoomMenu::zoomChanged, this, &ZoomablePreviewDeviceSkin::zoomPercentChanged);
336 m_zoomWidget->setZoomContextMenuEnabled(false);
337 m_zoomWidget->setWidgetZoomContextMenuEnabled(false);
338 m_zoomWidget->resize(screenSize());
339 m_zoomWidget->setParent(this, Qt::SubWindow);
340 m_zoomWidget->setAutoFillBackground(true);
341 setView(m_zoomWidget);
342}
343
344static inline qreal zoomFactor(int percent)
345{
346 return qreal(percent) / 100.0;
347}
348
349static inline QSize scaleSize(int zoomPercent, const QSize &size)
350{
351 return zoomPercent == 100 ? size : (QSizeF(size) * zoomFactor(zoomPercent)).toSize();
352}
353
355{
356 m_zoomWidget->setWidget(formWidget);
357 m_zoomWidget->resize(scaleSize(zoomPercent(), screenSize()));
358}
359
361{
362 return m_zoomWidget->zoom();
363}
364
365void ZoomablePreviewDeviceSkin::setZoomPercent(int zp)
366{
367 if (zp == zoomPercent())
368 return;
369
370 // If not triggered by the menu itself: Update it
371 if (m_zoomMenu->zoom() != zp)
372 m_zoomMenu->setZoom(zp);
373
374 QApplication::setOverrideCursor(Qt::WaitCursor);
375 m_zoomWidget->setZoom(zp);
376 setTransform(skinTransform());
377 QApplication::restoreOverrideCursor();
378}
379
381{
382 if (!m_zoomSubMenuAction) {
383 m_zoomSubMenuAction = new QAction(tr("&Zoom"), this);
384 QMenu *zoomSubMenu = new QMenu;
385 m_zoomSubMenuAction->setMenu(zoomSubMenu);
386 m_zoomMenu->addActions(zoomSubMenu);
387 }
388 menu->addAction(m_zoomSubMenuAction);
389 menu->addSeparator();
390}
391
393{
394 // Complete transformation consisting of base class rotation and zoom.
395 QTransform rc = PreviewDeviceSkin::skinTransform();
396 const int zp = zoomPercent();
397 if (zp != 100) {
398 const qreal factor = zoomFactor(zp);
399 rc.scale(factor, factor);
400 }
401 return rc;
402}
403
404void ZoomablePreviewDeviceSkin::fitWidget(const QSize &size)
405{
406 m_zoomWidget->resize(scaleSize(zoomPercent(), size));
407}
408
409// ------------- PreviewConfiguration
410
411static constexpr auto styleKey = "Style"_L1;
412static constexpr auto appStyleSheetKey = "AppStyleSheet"_L1;
413static constexpr auto skinKey = "Skin"_L1;
414
419
424
429
435
437
445
447{
448 return m_d->m_style;
449}
450
452{
453 m_d->m_style = s;
454}
455
456// Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()).
461
466
471
476
486
506
507
508QDESIGNER_SHARED_EXPORT bool operator<(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2)
509{
510 return compare(pc1, pc2) < 0;
511}
512
513QDESIGNER_SHARED_EXPORT bool operator==(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2)
514{
515 return compare(pc1, pc2) == 0;
516}
517
518QDESIGNER_SHARED_EXPORT bool operator!=(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2)
519{
520 return compare(pc1, pc2) != 0;
521}
522
523// ------------- PreviewManagerPrivate
541
542PreviewManagerPrivate::PreviewManagerPrivate(PreviewManager::PreviewMode mode) :
543 m_mode(mode),
544 m_core(nullptr),
545 m_updateBlocked(false)
546{
547}
548
549// ------------- PreviewManager
550
556
558{
559 delete d;
560}
561
562
564{
565#ifdef Q_OS_WIN
569#else
571 // Only Dialogs have close buttons on Mac.
572 // On Linux, we don't want an additional task bar item and we don't want a minimize button;
573 // we want the preview to be on top.
575#endif
576 return windowFlags;
577}
578
583
584// Some widgets might require fake containers
585
587{
588 // Prevent a dock widget from trying to dock to Designer's main window
589 // (which can be found in the parent hierarchy in MDI mode) by
590 // providing a fake mainwindow
591 if (QDockWidget *dock = qobject_cast<QDockWidget *>(w)) {
592 // Reparent: Clear modality, propagate title and resize outer container
593 const QSize size = w->size();
594 w->setWindowModality(Qt::NonModal);
595 dock->setFeatures(dock->features() & ~(QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetClosable));
596 dock->setAllowedAreas(Qt::LeftDockWidgetArea);
597 QMainWindow *mw = new QMainWindow;
598 const QMargins cm = mw->contentsMargins();
599 mw->addDockWidget(Qt::LeftDockWidgetArea, dock);
600 mw->resize(size + QSize(cm.left() + cm.right(), cm.top() + cm.bottom()));
601 return mw;
602 }
603 return w;
604}
605
606static PreviewConfiguration configurationFromSettings(QDesignerFormEditorInterface *core, const QString &style)
607{
608 qdesigner_internal::PreviewConfiguration pc;
609 const QDesignerSharedSettings settings(core);
610 if (settings.isCustomPreviewConfigurationEnabled())
611 pc = settings.customPreviewConfiguration();
612 if (!style.isEmpty())
613 pc.setStyle(style);
614 return pc;
615}
616
621
626
631 int initialZoom)
632{
633 if (!d->m_core)
634 d->m_core = fw->core();
635
636 const bool zoomable = initialZoom > 0;
637 // Figure out which profile to apply
639 if (deviceProfileIndex >= 0) {
641 } else {
642 if (const FormWindowBase *fwb = qobject_cast<const FormWindowBase *>(fw))
644 }
645 // Create
647 if (!formWidget)
648 return nullptr;
649
650 const QString title = tr("%1 - [Preview]").arg(formWidget->windowTitle());
653
654 // Clear any modality settings, child widget modalities must not be higher than parent's
656 // No skin
658 if (deviceSkin.isEmpty()) {
659 if (zoomable) { // Embed into ZoomWidget
664 // Keep any widgets' context menus working, do not use global menu
667 // Make preview close when Widget closes (Dialog/accept, etc)
672 return zw;
673 }
676 return formWidget;
677 }
678 // Embed into skin. find config in cache
680 if (it == d->m_deviceSkinConfigCache.end()) {
684 return nullptr;
685 }
687 }
688
690 PreviewDeviceSkin *skin = nullptr;
691 if (zoomable) {
696 skin = zds;
697 } else {
699 }
701 // Make preview close when Widget closes (Dialog/accept, etc)
706 return skinContainer;
707}
708
713{
714 enum { Spacing = 10 };
717
719 const int initialZoom = settings.zoomEnabled() ? settings.zoom() : -1;
720
722 if (!widget)
723 return nullptr;
724 // Install filter for Escape key
727
728 switch (d->m_mode) {
730 // Cannot do this on the Mac as the dialog would have no close button
732 break;
740 widget, &QWidget::close);
741 }
742 break;
743 }
744 // Semi-smart algorithm to position previews:
745 // If its the first one, position relative to form.
746 // 2nd, attempt to tile right (for comparing styles) or cascade
747 const QSize size = widget->size();
748 const bool firstPreview = d->m_previews.isEmpty();
749 if (firstPreview) {
751 } else {
756 if (newPos.x() + size.width() < availGeometry.right())
758 else
760 }
761
762 }
764 widget->show();
765 if (firstPreview)
767 return widget;
768}
769
771{
772 if (d->m_previews.isEmpty())
773 return nullptr;
774
775 // find matching window
776 for (const auto &pd : std::as_const(d->m_previews)) {
777 QWidget *w = pd.m_widget;
778 if (w && pd.m_formWindow == fw && pd.m_configuration == pc) {
779 w->raise();
780 w->activateWindow();
781 return w;
782 }
783 }
784 return nullptr;
785}
786
788{
789 if (!d->m_previews.isEmpty()) {
790 d->m_updateBlocked = true;
791 d->m_activePreview = nullptr;
792 for (const auto &pd : std::as_const(d->m_previews)) {
793 if (pd.m_widget)
794 pd.m_widget->close();
795 }
796 d->m_previews.clear();
797 d->m_updateBlocked = false;
799 }
800}
801
803{
804 if (d->m_updateBlocked)
805 return;
806 // Purge out all 0 or widgets to be deleted
807 for (auto it = d->m_previews.begin(); it != d->m_previews.end() ; ) {
808 QWidget *iw = it->m_widget; // Might be 0 when catching QEvent::Destroyed
809 if (iw == nullptr || iw == w) {
810 it = d->m_previews.erase(it);
811 } else {
812 ++it;
813 }
814 }
815 if (d->m_previews.isEmpty())
817}
818
820{
821 // Courtesy of designer
822 do {
823 if (!watched->isWidgetType())
824 break;
827 break;
828
829 switch (event->type()) {
830 case QEvent::KeyPress:
831 case QEvent::ShortcutOverride: {
832 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
833 const int key = keyEvent->key();
834 if ((key == Qt::Key_Escape
835#ifdef Q_OS_MACOS
837#endif
838 )) {
840 return true;
841 }
842 }
843 break;
846 break;
847 case QEvent::Destroy: // We don't get QEvent::Close if someone accepts a QDialog.
849 break;
850 case QEvent::Close:
853 break;
854 default:
855 break;
856 }
857 } while(false);
859}
860
862{
863 return d->m_previews.size();
864}
865
870
875
888
890{
891 if (d->m_core) { // Save the last zoom chosen by the user.
894 }
895}
896}
897
898QT_END_NAMESPACE
899
900#include "previewmanager.moc"
QWidget * view() const
void setView(QWidget *v)
friend class QWidget
Definition qpainter.h:421
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)
QDESIGNER_SHARED_EXPORT bool operator==(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2)
static constexpr auto styleKey
QDESIGNER_SHARED_EXPORT bool operator!=(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2)
static QWidget * fakeContainer(QWidget *w)
QDESIGNER_SHARED_EXPORT bool operator<(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2)
static QSize scaleSize(int zoomPercent, const QSize &size)
static int compare(const qdesigner_internal::PreviewConfiguration &pc1, const qdesigner_internal::PreviewConfiguration &pc2)
#define QDESIGNER_SHARED_EXPORT
PreviewData(const QPointer< QWidget > &widget, const QDesignerFormWindowInterface *formWindow, const qdesigner_internal::PreviewConfiguration &pc)
const QDesignerFormWindowInterface * m_formWindow
qdesigner_internal::PreviewConfiguration m_configuration