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 | |
44 | QT_BEGIN_NAMESPACE |
45 | |
46 | enum { debugZoomWidget = 0 }; |
47 | |
48 | static const int [] = { 100, 25, 50, 75, 125, 150 , 175, 200 }; |
49 | |
50 | namespace qdesigner_internal { |
51 | |
52 | // ---------- ZoomMenu |
53 | |
54 | 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 | |
70 | int ZoomMenu::(const QAction *a) |
71 | { |
72 | return a->data().toInt(); |
73 | } |
74 | |
75 | void ZoomMenu::(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 | |
85 | int ZoomMenu::() const |
86 | { |
87 | return m_menuActions->checkedAction()->data().toInt(); |
88 | } |
89 | |
90 | void ZoomMenu::(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 | |
101 | void ZoomMenu::(QAction *a) |
102 | { |
103 | emit zoomChanged(zoomOf(a)); |
104 | } |
105 | |
106 | QVector<int> ZoomMenu::() |
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 |
117 | ZoomView::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 | |
130 | int ZoomView::zoom() const |
131 | { |
132 | return m_zoom; |
133 | } |
134 | |
135 | void 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 | |
146 | void 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 | |
166 | void ZoomView::applyZoom() |
167 | { |
168 | } |
169 | |
170 | qreal ZoomView::zoomFactor() const |
171 | { |
172 | return m_zoomFactor; |
173 | } |
174 | |
175 | bool ZoomView::() const |
176 | { |
177 | return m_zoomContextMenuEnabled; |
178 | } |
179 | |
180 | void ZoomView::(bool e) |
181 | { |
182 | m_zoomContextMenuEnabled = e; |
183 | } |
184 | |
185 | ZoomMenu *ZoomView::() |
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 | |
195 | void ZoomView::(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 | |
207 | void ZoomView::(const QPoint &globalPos) |
208 | { |
209 | QMenu ; |
210 | zoomMenu()->addActions(m: &menu); |
211 | menu.exec(pos: globalPos); |
212 | } |
213 | |
214 | QPoint ZoomView::scrollPosition() const |
215 | { |
216 | return QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value()); |
217 | } |
218 | |
219 | void ZoomView::setScrollPosition(const QPoint& pos) |
220 | { |
221 | horizontalScrollBar()->setValue(pos.x()); |
222 | verticalScrollBar()->setValue(pos.y()); |
223 | } |
224 | |
225 | // -------------- ZoomProxyWidget |
226 | ZoomProxyWidget::ZoomProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) : |
227 | QGraphicsProxyWidget(parent, wFlags) |
228 | { |
229 | } |
230 | |
231 | QVariant 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 | |
251 | static const char *zoomedEventFilterRedirectorNameC = "__qt_ZoomedEventFilterRedirector" ; |
252 | |
253 | class ZoomedEventFilterRedirector : public QObject { |
254 | Q_DISABLE_COPY_MOVE(ZoomedEventFilterRedirector) |
255 | |
256 | public: |
257 | explicit ZoomedEventFilterRedirector(ZoomWidget *zw, QObject *parent); |
258 | bool eventFilter(QObject *watched, QEvent *event) override; |
259 | |
260 | private: |
261 | ZoomWidget *m_zw; |
262 | }; |
263 | |
264 | ZoomedEventFilterRedirector::ZoomedEventFilterRedirector(ZoomWidget *zw, QObject *parent) : |
265 | QObject(parent), |
266 | m_zw(zw) |
267 | { |
268 | setObjectName(QLatin1String(zoomedEventFilterRedirectorNameC)); |
269 | } |
270 | |
271 | bool ZoomedEventFilterRedirector::eventFilter(QObject *watched, QEvent *event) |
272 | { |
273 | return m_zw->zoomedEventFilter(watched, event); |
274 | } |
275 | |
276 | |
277 | // --------- ZoomWidget |
278 | |
279 | ZoomWidget::ZoomWidget(QWidget *parent) : |
280 | ZoomView(parent) |
281 | { |
282 | setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
283 | setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
284 | } |
285 | |
286 | void 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 | |
311 | bool ZoomWidget::() const |
312 | { |
313 | return m_widgetZoomContextMenuEnabled; |
314 | } |
315 | void ZoomWidget::(bool e) |
316 | { |
317 | m_widgetZoomContextMenuEnabled = e; |
318 | } |
319 | |
320 | QSize ZoomWidget::viewPortMargin() const |
321 | { |
322 | return QSize(0, 0); |
323 | } |
324 | |
325 | QSizeF 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 | |
333 | QSize 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 | |
343 | QSize 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 |
369 | void 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 | |
405 | void 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 | |
417 | void 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 | |
438 | QSize 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 | |
450 | QSize 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 | |
462 | bool 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 | |
498 | void ZoomWidget::setItemAcceptDrops(bool) |
499 | { |
500 | if (m_proxy) |
501 | m_proxy->setAcceptDrops(true); |
502 | } |
503 | |
504 | bool 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 |
510 | QGraphicsProxyWidget *ZoomWidget::createProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) const |
511 | { |
512 | return new ZoomProxyWidget(parent, wFlags); |
513 | } |
514 | |
515 | void 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 | |
534 | QT_END_NAMESPACE |
535 | |