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 "zoomwidget_p.h"
30
31#include <QtWidgets/qgraphicsscene.h>
32#include <QtWidgets/qgraphicsproxywidget.h>
33#include <QtWidgets/qmenu.h>
34#include <QtWidgets/qaction.h>
35#include <QtWidgets/qactiongroup.h>
36#include <QtGui/qevent.h>
37#include <QtWidgets/qscrollbar.h>
38
39#include <QtCore/qtextstream.h>
40#include <QtCore/qmath.h>
41#include <QtCore/qdebug.h>
42#include <QtCore/qlist.h>
43
44QT_BEGIN_NAMESPACE
45
46enum { debugZoomWidget = 0 };
47
48static const int menuZoomList[] = { 100, 25, 50, 75, 125, 150 , 175, 200 };
49
50namespace qdesigner_internal {
51
52// ---------- ZoomMenu
53
54ZoomMenu::ZoomMenu(QObject *parent) :
55 QObject(parent),
56 m_menuActions(new QActionGroup(this))
57{
58 connect(sender: m_menuActions, signal: &QActionGroup::triggered, receiver: this, slot: &ZoomMenu::slotZoomMenu);
59 for (int zoom : menuZoomList) {
60 //: Zoom factor
61 QAction *a = m_menuActions->addAction(text: tr(s: "%1 %").arg(a: zoom));
62 a->setCheckable(true);
63 a->setData(QVariant(zoom));
64 if (zoom == 100)
65 a->setChecked(true);
66 m_menuActions->addAction(a);
67 }
68}
69
70int ZoomMenu::zoomOf(const QAction *a)
71{
72 return a->data().toInt();
73}
74
75void ZoomMenu::addActions(QMenu *m)
76{
77 const auto za = m_menuActions->actions();
78 for (QAction *a : za) {
79 m->addAction(action: a);
80 if (zoomOf(a) == 100)
81 m->addSeparator();
82 }
83}
84
85int ZoomMenu::zoom() const
86{
87 return m_menuActions->checkedAction()->data().toInt();
88}
89
90void ZoomMenu::setZoom(int percent)
91{
92 const auto za = m_menuActions->actions();
93 for (QAction *a : za) {
94 if (zoomOf(a) == percent) {
95 a->setChecked(true);
96 return;
97 }
98 }
99}
100
101void ZoomMenu::slotZoomMenu(QAction *a)
102{
103 emit zoomChanged(zoomOf(a));
104}
105
106QVector<int> ZoomMenu::zoomValues()
107{
108 QVector<int> rc;
109 const int nz = sizeof(menuZoomList)/sizeof(int);
110 rc.reserve(asize: nz);
111 for (int i = 0; i < nz; i++)
112 rc.push_back(t: menuZoomList[i]);
113 return rc;
114}
115
116// --------- ZoomView
117ZoomView::ZoomView(QWidget *parent) :
118 QGraphicsView(parent),
119 m_scene(new QGraphicsScene(this))
120{
121 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
122 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
123 setFrameShape(QFrame::NoFrame);
124 setScene(m_scene);
125 if (debugZoomWidget)
126 qDebug() << "scene" << m_scene->sceneRect();
127
128}
129
130int ZoomView::zoom() const
131{
132 return m_zoom;
133}
134
135void ZoomView::scrollToOrigin()
136{
137 const QPoint origin(0 ,0);
138 const QPoint current = scrollPosition();
139 if (current != origin) {
140 if (debugZoomWidget)
141 qDebug() << "ZoomView::scrollToOrigin from " << current;
142 setScrollPosition(origin);
143 }
144}
145
146void ZoomView::setZoom(int percent)
147{
148 if (debugZoomWidget)
149 qDebug() << "ZoomView::setZoom" << percent;
150
151 if (m_zoom == percent)
152 return;
153
154 m_zoom = percent;
155 const qreal hundred = 100.0;
156 m_zoomFactor = static_cast<qreal>(m_zoom) / hundred;
157
158 applyZoom();
159 if (m_zoomMenu) // Do not force them into existence
160 m_zoomMenu->setZoom(m_zoom);
161
162 resetTransform();
163 scale(sx: m_zoomFactor, sy: m_zoomFactor);
164}
165
166void ZoomView::applyZoom()
167{
168}
169
170qreal ZoomView::zoomFactor() const
171{
172 return m_zoomFactor;
173}
174
175bool ZoomView::isZoomContextMenuEnabled() const
176{
177 return m_zoomContextMenuEnabled;
178}
179
180void ZoomView::setZoomContextMenuEnabled(bool e)
181{
182 m_zoomContextMenuEnabled = e;
183}
184
185ZoomMenu *ZoomView::zoomMenu()
186{
187 if (!m_zoomMenu) {
188 m_zoomMenu = new ZoomMenu(this);
189 m_zoomMenu->setZoom(m_zoom);
190 connect(sender: m_zoomMenu, signal: &ZoomMenu::zoomChanged, receiver: this, slot: &ZoomView::setZoom);
191 }
192 return m_zoomMenu;
193}
194
195void ZoomView::contextMenuEvent(QContextMenuEvent *event)
196{
197 if (debugZoomWidget > 1)
198 qDebug() << "ZoomView::contextMenuEvent" << event->pos() << event->globalPos() << zoom() << '%';
199
200 if (m_zoomContextMenuEnabled) {
201 showContextMenu(globalPos: event->globalPos());
202 } else {
203 QGraphicsView::contextMenuEvent(event);
204 }
205}
206
207void ZoomView::showContextMenu(const QPoint &globalPos)
208{
209 QMenu menu;
210 zoomMenu()->addActions(m: &menu);
211 menu.exec(pos: globalPos);
212}
213
214QPoint ZoomView::scrollPosition() const
215{
216 return QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value());
217}
218
219void ZoomView::setScrollPosition(const QPoint& pos)
220{
221 horizontalScrollBar()->setValue(pos.x());
222 verticalScrollBar()->setValue(pos.y());
223}
224
225// -------------- ZoomProxyWidget
226ZoomProxyWidget::ZoomProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) :
227 QGraphicsProxyWidget(parent, wFlags)
228{
229}
230
231QVariant ZoomProxyWidget::itemChange(GraphicsItemChange change, const QVariant &value)
232{
233 switch (change) {
234 case ItemPositionChange: {
235 const QPointF newPos = value.toPointF();
236 const QPointF desiredPos = QPointF(0, 0);
237 if (newPos != desiredPos && debugZoomWidget)
238 qDebug() << "ZoomProxyWidget::itemChange: refusing " << newPos;
239 return desiredPos;
240 }
241 default:
242 break;
243 }
244 return QGraphicsProxyWidget::itemChange(change, value);
245}
246
247/* ZoomedEventFilterRedirector: Event filter for the zoomed widget.
248 * It redirects the events to another handler of ZoomWidget as its
249 * base class QScrollArea also implements eventFilter() for its viewport. */
250
251static const char *zoomedEventFilterRedirectorNameC = "__qt_ZoomedEventFilterRedirector";
252
253class ZoomedEventFilterRedirector : public QObject {
254 Q_DISABLE_COPY_MOVE(ZoomedEventFilterRedirector)
255
256public:
257 explicit ZoomedEventFilterRedirector(ZoomWidget *zw, QObject *parent);
258 bool eventFilter(QObject *watched, QEvent *event) override;
259
260private:
261 ZoomWidget *m_zw;
262};
263
264ZoomedEventFilterRedirector::ZoomedEventFilterRedirector(ZoomWidget *zw, QObject *parent) :
265 QObject(parent),
266 m_zw(zw)
267{
268 setObjectName(QLatin1String(zoomedEventFilterRedirectorNameC));
269}
270
271bool ZoomedEventFilterRedirector::eventFilter(QObject *watched, QEvent *event)
272{
273 return m_zw->zoomedEventFilter(watched, event);
274}
275
276
277// --------- ZoomWidget
278
279ZoomWidget::ZoomWidget(QWidget *parent) :
280 ZoomView(parent)
281{
282 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
283 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
284}
285
286void ZoomWidget::setWidget(QWidget *w, Qt::WindowFlags wFlags)
287{
288 if (debugZoomWidget)
289 qDebug() << "ZoomWidget::setWidget" << w << Qt::bin << wFlags;
290
291 if (m_proxy) {
292 scene().removeItem(item: m_proxy);
293 if (QWidget *w = m_proxy->widget()) {
294 // remove the event filter
295 if (QObject *evf = w->findChild<QObject*>(aName: QLatin1String(zoomedEventFilterRedirectorNameC)))
296 w->removeEventFilter(obj: evf);
297 }
298 m_proxy->deleteLater();
299 }
300 // Set window flags on the outer proxy for them to take effect
301 m_proxy = createProxyWidget(parent: nullptr, wFlags: Qt::Window);
302 m_proxy->setWidget(w);
303
304 m_proxy->setWindowFlags(wFlags);
305 scene().addItem(item: m_proxy);
306 w->installEventFilter(filterObj: new ZoomedEventFilterRedirector(this, w));
307 resizeToWidgetSize(); // Do manually for new widget
308 m_proxy->show();
309}
310
311bool ZoomWidget::isWidgetZoomContextMenuEnabled() const
312{
313 return m_widgetZoomContextMenuEnabled;
314}
315void ZoomWidget::setWidgetZoomContextMenuEnabled(bool e)
316{
317 m_widgetZoomContextMenuEnabled = e;
318}
319
320QSize ZoomWidget::viewPortMargin() const
321{
322 return QSize(0, 0);
323}
324
325QSizeF ZoomWidget::widgetDecorationSizeF() const
326{
327 qreal left, top, right, bottom;
328 m_proxy->getWindowFrameMargins (left: &left, top: &top, right: &right, bottom: &bottom);
329 const QSizeF rc = QSizeF(left + right, top + bottom);
330 return rc;
331}
332
333QSize ZoomWidget::widgetSize() const
334{
335 if (m_proxy)
336 return m_proxy->widget()->size();
337 return QSize(0, 0);
338}
339
340/* Convert widget size to QGraphicsView size.
341 * Watch out for limits (0, QWIDGETSIZE_MAX); just pass them on */
342
343QSize ZoomWidget::widgetSizeToViewSize(const QSize &s, bool *ptrToValid) const
344{
345 const QSize vpMargin = viewPortMargin();
346 const QSizeF deco = widgetDecorationSizeF();
347 const int width = s.width();
348
349 QSize rc = s;
350 bool valid = false;
351 if (width != 0 && width != QWIDGETSIZE_MAX) {
352 valid = true;
353 rc.setWidth(vpMargin.width() + qCeil(v: deco.width() + zoomFactor() * static_cast<qreal>(width)));
354 }
355
356 const int height = s.height();
357 if (height != 0 && height != QWIDGETSIZE_MAX) {
358 valid = true;
359 rc.setHeight(vpMargin.height() + qCeil(v: deco.height() + zoomFactor() * static_cast<qreal>(height)));
360 }
361
362 if (ptrToValid)
363 *ptrToValid = valid;
364
365 return rc;
366}
367
368// On changing zoom: Make QGraphicsView big enough to hold the widget
369void ZoomWidget::resizeToWidgetSize()
370{
371 if (!m_proxy)
372 return;
373
374 m_viewResizeBlocked = true;
375 // Convert size, apply transformed min/max size if applicable
376 const QSize wsize = widgetSize();
377 const QSize viewSize = widgetSizeToViewSize(s: wsize);
378
379 bool hasMinimumSize = false;
380 const QSize minimumSize = m_proxy->widget()->minimumSize();
381 const QSize viewMinimumSize = widgetSizeToViewSize(s: minimumSize, ptrToValid: &hasMinimumSize);
382
383 bool hasMaximumSize = false;
384 const QSize maximumSize = m_proxy->widget()->maximumSize();
385 const QSize viewMaximumSize = widgetSizeToViewSize(s: maximumSize, ptrToValid: &hasMaximumSize);
386
387 if (debugZoomWidget) {
388 qDebug()
389 << "ZoomWidget::resizeToWidgetSize()\n"
390 << "Widget: " << wsize << "(scaled)" << (wsize * zoomFactor()) << " Min/Max" << minimumSize << maximumSize << '\n'
391 << " View: " << viewSize << hasMinimumSize << viewMinimumSize << hasMaximumSize << viewMaximumSize;
392 }
393 // Apply
394 if (hasMinimumSize)
395 setMinimumSize(viewMinimumSize);
396 if (hasMaximumSize)
397 setMaximumSize(viewMaximumSize);
398 // now resize
399 doResize(s: viewSize);
400 if (debugZoomWidget)
401 qDebug() << "ZoomWidget::resizeToWidgetSize(): resulting view size" << size();
402 m_viewResizeBlocked = false;
403}
404
405void ZoomWidget::applyZoom()
406{
407 resizeToWidgetSize();
408}
409
410/* virtual */ void ZoomWidget::doResize(const QSize &s)
411{
412 if (debugZoomWidget > 1)
413 qDebug() << ">ZoomWidget::doResize() " << s;
414 resize(s);
415}
416
417void ZoomWidget::resizeEvent(QResizeEvent *event)
418{
419 /* QGraphicsView Resized from outside: Adapt widget. For some reason,
420 * the size passed in the event is not to be trusted. This might be due
421 * to some QScrollArea event fiddling. Have QScrollArea resize first
422 * and the use the size ZoomView::resizeEvent(event); */
423 if (m_proxy && !m_viewResizeBlocked) {
424 if (debugZoomWidget > 1)
425 qDebug() << '>' << Q_FUNC_INFO << size() << ")::resizeEvent from " << event->oldSize() << " to " << event->size();
426 const QSizeF newViewPortSize = size() - viewPortMargin();
427 const QSizeF widgetSizeF = newViewPortSize / zoomFactor() - widgetDecorationSizeF();
428 m_widgetResizeBlocked = true;
429 m_proxy->widget()->resize(widgetSizeF.toSize());
430 setSceneRect(QRectF(QPointF(0, 0), widgetSizeF));
431 scrollToOrigin();
432 m_widgetResizeBlocked = false;
433 if (debugZoomWidget > 1)
434 qDebug() << '<' << Q_FUNC_INFO << widgetSizeF << m_proxy->widget()->size() << m_proxy->size();
435 }
436}
437
438QSize ZoomWidget::minimumSizeHint() const
439{
440 if (!m_proxy)
441 return QGraphicsView::minimumSizeHint();
442
443 const QSizeF wmsh = m_proxy->widget()->minimumSizeHint();
444 const QSize rc = viewPortMargin() + (wmsh * zoomFactor()).toSize();
445 if (debugZoomWidget > 1)
446 qDebug() << "minimumSizeHint()" << rc;
447 return rc;
448}
449
450QSize ZoomWidget::sizeHint() const
451{
452 if (!m_proxy)
453 return QGraphicsView::sizeHint();
454
455 const QSizeF wsh = m_proxy->widget()->sizeHint();
456 const QSize rc = viewPortMargin() + (wsh * zoomFactor()).toSize();
457 if (debugZoomWidget > 1)
458 qDebug() << "sizeHint()" << rc;
459 return rc;
460}
461
462bool ZoomWidget::zoomedEventFilter(QObject * /*watched*/, QEvent *event)
463{
464 switch (event->type()) {
465 case QEvent::KeyPress:
466 if (debugZoomWidget) { // Debug helper: Press 'D' on the zoomed widget
467 const QKeyEvent *kevent = static_cast<QKeyEvent*>(event);
468 if (kevent->key() == Qt::Key_D)
469 dump();
470 }
471 break;
472 case QEvent::Resize:
473 if (debugZoomWidget > 1) {
474 const QResizeEvent *re = static_cast<const QResizeEvent *>(event);
475 qDebug() << "ZoomWidget::zoomedEventFilter" << re->oldSize() << re->size() << " at " << m_proxy->widget()->geometry();
476 }
477 if (!m_widgetResizeBlocked)
478 resizeToWidgetSize();
479
480 break;
481 case QEvent::ContextMenu:
482 if (m_widgetZoomContextMenuEnabled) {
483 // Calculate global position from scaled
484 QContextMenuEvent *ce = static_cast<QContextMenuEvent*>(event);
485 const QPointF origin = mapToGlobal(QPoint(0, 0)) - scrollPosition();
486 const QPointF pos = QPointF(origin + (QPointF(ce->pos()) * zoomFactor()));
487 showContextMenu(globalPos: pos.toPoint());
488 ce->accept();
489 return true;
490 }
491 break;
492 default:
493 break;
494 }
495 return false;
496}
497
498void ZoomWidget::setItemAcceptDrops(bool)
499{
500 if (m_proxy)
501 m_proxy->setAcceptDrops(true);
502}
503
504bool ZoomWidget::itemAcceptDrops() const
505{
506 return m_proxy ? m_proxy->acceptDrops() : false;
507}
508
509 // Factory function for QGraphicsProxyWidgets which can be overwritten. Default creates a ZoomProxyWidget
510QGraphicsProxyWidget *ZoomWidget::createProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) const
511{
512 return new ZoomProxyWidget(parent, wFlags);
513}
514
515void ZoomWidget::dump() const
516{
517
518 qDebug() << "ZoomWidget::dump " << geometry() << " Viewport " << viewport()->geometry()
519 << "Scroll: " << scrollPosition() << "Transform: " << transform() << " SceneRect: " << sceneRect();
520 if (m_proxy) {
521 qDebug() << "Proxy Pos: " << m_proxy->pos() << "Proxy " << m_proxy->size()
522 << "\nProxy size hint"
523 << m_proxy->effectiveSizeHint(which: Qt::MinimumSize)
524 << m_proxy->effectiveSizeHint(which: Qt::PreferredSize)
525 << m_proxy->effectiveSizeHint(which: Qt::MaximumSize)
526 << "\nTransform: " << m_proxy->transform()
527 << "\nWidget: " << m_proxy->widget()->geometry()
528 << "scaled" << (zoomFactor() * m_proxy->widget()->size());
529 }
530}
531
532} // namespace qdesigner_internal
533
534QT_END_NAMESPACE
535

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