/**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the Qt Designer of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "zoomwidget_p.h" #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE typedef QList ActionList; typedef QList GraphicsItemList; enum { debugZoomWidget = 0 }; static const int menuZoomList[] = { 100, 25, 50, 75, 125, 150 , 175, 200 }; static inline QSize qCeiling(const QSizeF &s) { return QSize(qCeil(s.width()), qCeil(s.height())); } namespace qdesigner_internal { // ---------- ZoomMenu ZoomMenu::ZoomMenu(QObject *parent) : QObject(parent), m_menuActions(new QActionGroup(this)) { connect(m_menuActions, SIGNAL(triggered(QAction*)), this, SLOT(slotZoomMenu(QAction*))); const int nz = sizeof(menuZoomList)/sizeof(int); for (int i = 0; i < nz; i++) { const int zoom = menuZoomList[i]; //: Zoom factor QAction *a = m_menuActions->addAction(tr("%1 %").arg(zoom)); a->setCheckable(true); a->setData(QVariant(zoom)); if (zoom == 100) a->setChecked(true); m_menuActions->addAction(a); } } int ZoomMenu::zoomOf(const QAction *a) { return a->data().toInt(); } void ZoomMenu::addActions(QMenu *m) { const ActionList za = m_menuActions->actions(); const ActionList::const_iterator cend = za.constEnd(); for (ActionList::const_iterator it = za.constBegin(); it != cend; ++it) { m->addAction(*it); if (zoomOf(*it) == 100) m->addSeparator(); } } int ZoomMenu::zoom() const { return m_menuActions->checkedAction()->data().toInt(); } void ZoomMenu::setZoom(int percent) { const ActionList za = m_menuActions->actions(); const ActionList::const_iterator cend = za.constEnd(); for (ActionList::const_iterator it = za.constBegin(); it != cend; ++it) if (zoomOf(*it) == percent) { (*it)->setChecked(true); return; } } void ZoomMenu::slotZoomMenu(QAction *a) { emit zoomChanged(zoomOf(a)); } QList ZoomMenu::zoomValues() { QList rc; const int nz = sizeof(menuZoomList)/sizeof(int); for (int i = 0; i < nz; i++) rc.push_back(menuZoomList[i]); return rc; } // --------- ZoomView ZoomView::ZoomView(QWidget *parent) : QGraphicsView(parent), m_scene(new QGraphicsScene(this)), m_zoom(100), m_zoomFactor(1.0), m_zoomContextMenuEnabled(false), m_zoomMenu(0) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setFrameShape(QFrame::NoFrame); setScene(m_scene); if (debugZoomWidget) qDebug() << "scene" << m_scene->sceneRect(); } int ZoomView::zoom() const { return m_zoom; } void ZoomView::scrollToOrigin() { const QPoint origin(0 ,0); const QPoint current = scrollPosition(); if (current != origin) { if (debugZoomWidget) qDebug() << "ZoomView::scrollToOrigin from " << current; setScrollPosition(origin); } } void ZoomView::setZoom(int percent) { if (debugZoomWidget) qDebug() << "ZoomView::setZoom" << percent; if (m_zoom == percent) return; m_zoom = percent; const qreal hundred = 100.0; m_zoomFactor = static_cast(m_zoom) / hundred; applyZoom(); if (m_zoomMenu) // Do not force them into existence m_zoomMenu->setZoom(m_zoom); resetTransform(); scale(m_zoomFactor, m_zoomFactor); } void ZoomView::applyZoom() { } qreal ZoomView::zoomFactor() const { return m_zoomFactor; } bool ZoomView::isZoomContextMenuEnabled() const { return m_zoomContextMenuEnabled; } void ZoomView::setZoomContextMenuEnabled(bool e) { m_zoomContextMenuEnabled = e; } ZoomMenu *ZoomView::zoomMenu() { if (!m_zoomMenu) { m_zoomMenu = new ZoomMenu(this); m_zoomMenu->setZoom(m_zoom); connect(m_zoomMenu, SIGNAL(zoomChanged(int)), this, SLOT(setZoom(int))); } return m_zoomMenu; } void ZoomView::contextMenuEvent(QContextMenuEvent *event) { if (debugZoomWidget > 1) qDebug() << "ZoomView::contextMenuEvent" << event->pos() << event->globalPos() << zoom() << '%'; if (m_zoomContextMenuEnabled) { showContextMenu(event->globalPos()); } else { QGraphicsView::contextMenuEvent(event); } } void ZoomView::showContextMenu(const QPoint &globalPos) { QMenu menu; zoomMenu()->addActions(&menu); if (debugZoomWidget) { menu.addSeparator(); QAction *da = menu.addAction(QLatin1String("Dump")); connect(da, SIGNAL(triggered()), this, SLOT(dump())); } menu.exec(globalPos); } QPoint ZoomView::scrollPosition() const { return QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value()); } void ZoomView::setScrollPosition(const QPoint& pos) { horizontalScrollBar()->setValue(pos.x()); verticalScrollBar()->setValue(pos.y()); } // -------------- ZoomProxyWidget ZoomProxyWidget::ZoomProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) : QGraphicsProxyWidget(parent, wFlags) { } QVariant ZoomProxyWidget::itemChange(GraphicsItemChange change, const QVariant &value) { switch (change) { case ItemPositionChange: { const QPointF newPos = value.toPointF(); const QPointF desiredPos = QPointF(0, 0); if (newPos != desiredPos && debugZoomWidget) qDebug() << "ZoomProxyWidget::itemChange: refusing " << newPos; return desiredPos; } default: break; } return QGraphicsProxyWidget::itemChange(change, value); } /* ZoomedEventFilterRedirector: Event filter for the zoomed widget. * It redirects the events to another handler of ZoomWidget as its * base class QScrollArea also implements eventFilter() for its viewport. */ static const char *zoomedEventFilterRedirectorNameC = "__qt_ZoomedEventFilterRedirector"; class ZoomedEventFilterRedirector : public QObject { Q_DISABLE_COPY(ZoomedEventFilterRedirector) public: explicit ZoomedEventFilterRedirector(ZoomWidget *zw, QObject *parent); virtual bool eventFilter(QObject *watched, QEvent *event); private: ZoomWidget *m_zw; }; ZoomedEventFilterRedirector::ZoomedEventFilterRedirector(ZoomWidget *zw, QObject *parent) : QObject(parent), m_zw(zw) { setObjectName(QLatin1String(zoomedEventFilterRedirectorNameC)); } bool ZoomedEventFilterRedirector::eventFilter(QObject *watched, QEvent *event) { return m_zw->zoomedEventFilter(watched, event); } // --------- ZoomWidget ZoomWidget::ZoomWidget(QWidget *parent) : ZoomView(parent), m_proxy(0), m_viewResizeBlocked(false), m_widgetResizeBlocked(false), m_widgetZoomContextMenuEnabled(false) { setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } void ZoomWidget::setWidget(QWidget *w, Qt::WindowFlags wFlags) { if (debugZoomWidget) qDebug() << "ZoomWidget::setWidget" << w << bin << wFlags; if (m_proxy) { scene().removeItem(m_proxy); if (QWidget *w = m_proxy->widget()) { // remove the event filter if (QObject *evf = qFindChild(w, QLatin1String(zoomedEventFilterRedirectorNameC))) w->removeEventFilter(evf); } m_proxy->deleteLater(); } // Set window flags on the outer proxy for them to take effect m_proxy = createProxyWidget(0, Qt::Window); m_proxy->setWidget(w); m_proxy->setWindowFlags(wFlags); scene().addItem(m_proxy); w->installEventFilter(new ZoomedEventFilterRedirector(this, w)); resizeToWidgetSize(); // Do manually for new widget m_proxy->show(); } bool ZoomWidget::isWidgetZoomContextMenuEnabled() const { return m_widgetZoomContextMenuEnabled; } void ZoomWidget::setWidgetZoomContextMenuEnabled(bool e) { m_widgetZoomContextMenuEnabled = e; } QSize ZoomWidget::viewPortMargin() const { return QSize(0, 0); } QSizeF ZoomWidget::widgetDecorationSizeF() const { qreal left, top, right, bottom; m_proxy->getWindowFrameMargins (&left, &top, &right, &bottom); const QSizeF rc = QSizeF(left + right, top + bottom); return rc; } QSize ZoomWidget::widgetSize() const { if (m_proxy) return m_proxy->widget()->size(); return QSize(0, 0); } /* Convert widget size to QGraphicsView size. * Watch out for limits (0, QWIDGETSIZE_MAX); just pass them on */ QSize ZoomWidget::widgetSizeToViewSize(const QSize &s, bool *ptrToValid) const { const QSize vpMargin = viewPortMargin(); const QSizeF deco = widgetDecorationSizeF(); const int width = s.width(); QSize rc = s; bool valid = false; if (width != 0 && width != QWIDGETSIZE_MAX) { valid = true; rc.setWidth(vpMargin.width() + qCeil(deco.width() + zoomFactor() * static_cast(width))); } const int height = s.height(); if (height != 0 && height != QWIDGETSIZE_MAX) { valid = true; rc.setHeight(vpMargin.height() + qCeil(deco.height() + zoomFactor() * static_cast(height))); } if (ptrToValid) *ptrToValid = valid; return rc; } // On changing zoom: Make QGraphicsView big enough to hold the widget void ZoomWidget::resizeToWidgetSize() { if (!m_proxy) return; m_viewResizeBlocked = true; // Convert size, apply transformed min/max size if applicable const QSize wsize = widgetSize(); const QSize viewSize = widgetSizeToViewSize(wsize); bool hasMinimumSize = false; const QSize minimumSize = m_proxy->widget()->minimumSize(); const QSize viewMinimumSize = widgetSizeToViewSize(minimumSize, &hasMinimumSize); bool hasMaximumSize = false; const QSize maximumSize = m_proxy->widget()->maximumSize(); const QSize viewMaximumSize = widgetSizeToViewSize(maximumSize, &hasMaximumSize); if (debugZoomWidget) { qDebug() << "ZoomWidget::resizeToWidgetSize()\n" << "Widget: " << wsize << "(scaled)" << (wsize * zoomFactor()) << " Min/Max" << minimumSize << maximumSize << '\n' << " View: " << viewSize << hasMinimumSize << viewMinimumSize << hasMaximumSize << viewMaximumSize; } // Apply if (hasMinimumSize) setMinimumSize(viewMinimumSize); if (hasMaximumSize) setMaximumSize(viewMaximumSize); // now resize doResize(viewSize); if (debugZoomWidget) qDebug() << "ZoomWidget::resizeToWidgetSize(): resulting view size" << size(); m_viewResizeBlocked = false; } void ZoomWidget::applyZoom() { resizeToWidgetSize(); } /* virtual */ void ZoomWidget::doResize(const QSize &s) { if (debugZoomWidget > 1) qDebug() << ">ZoomWidget::doResize() " << s; resize(s); } void ZoomWidget::resizeEvent(QResizeEvent *event) { /* QGraphicsView Resized from outside: Adapt widget. For some reason, * the size passed in the event is not to be trusted. This might be due * to some QScrollArea event fiddling. Have QScrollArea resize first * and the use the size ZoomView::resizeEvent(event); */ if (m_proxy && !m_viewResizeBlocked) { if (debugZoomWidget > 1) qDebug() << '>' << Q_FUNC_INFO << size() << ")::resizeEvent from " << event->oldSize() << " to " << event->size(); const QSizeF newViewPortSize = size() - viewPortMargin(); const QSizeF widgetSizeF = newViewPortSize / zoomFactor() - widgetDecorationSizeF(); m_widgetResizeBlocked = true; m_proxy->widget()->resize(widgetSizeF.toSize()); setSceneRect(QRectF(QPointF(0, 0), widgetSizeF)); scrollToOrigin(); m_widgetResizeBlocked = false; if (debugZoomWidget > 1) qDebug() << '<' << Q_FUNC_INFO << widgetSizeF << m_proxy->widget()->size() << m_proxy->size(); } } QSize ZoomWidget::minimumSizeHint() const { if (!m_proxy) return QGraphicsView::minimumSizeHint(); const QSizeF wmsh = m_proxy->widget()->minimumSizeHint(); const QSize rc = viewPortMargin() + (wmsh * zoomFactor()).toSize(); if (debugZoomWidget > 1) qDebug() << "minimumSizeHint()" << rc; return rc; } QSize ZoomWidget::sizeHint() const { if (!m_proxy) return QGraphicsView::sizeHint(); const QSizeF wsh = m_proxy->widget()->sizeHint(); const QSize rc = viewPortMargin() + (wsh * zoomFactor()).toSize(); if (debugZoomWidget > 1) qDebug() << "sizeHint()" << rc; return rc; } bool ZoomWidget::zoomedEventFilter(QObject * /*watched*/, QEvent *event) { switch (event->type()) { case QEvent::KeyPress: if (debugZoomWidget) { // Debug helper: Press 'D' on the zoomed widget const QKeyEvent *kevent = static_cast(event); if (kevent->key() == Qt::Key_D) dump(); } break; case QEvent::Resize: if (debugZoomWidget > 1) { const QResizeEvent *re = static_cast(event); qDebug() << "ZoomWidget::zoomedEventFilter" << re->oldSize() << re->size() << " at " << m_proxy->widget()->geometry(); } if (!m_widgetResizeBlocked) resizeToWidgetSize(); break; case QEvent::ContextMenu: if (m_widgetZoomContextMenuEnabled) { // Calculate global position from scaled QContextMenuEvent *ce = static_cast(event); const QPointF origin = mapToGlobal(QPoint(0, 0)) - scrollPosition(); const QPointF pos = QPointF(origin + (QPointF(ce->pos()) * zoomFactor())); showContextMenu(pos.toPoint()); ce->accept(); return true; } break; default: break; } return false; } void ZoomWidget::setItemAcceptDrops(bool) { if (m_proxy) m_proxy->setAcceptDrops(true); } bool ZoomWidget::itemAcceptDrops() const { return m_proxy ? m_proxy->acceptDrops() : false; } // Factory function for QGraphicsProxyWidgets which can be overwritten. Default creates a ZoomProxyWidget QGraphicsProxyWidget *ZoomWidget::createProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) const { return new ZoomProxyWidget(parent, wFlags); } void ZoomWidget::dump() const { qDebug() << "ZoomWidget::dump " << geometry() << " Viewport " << viewport()->geometry() << "Scroll: " << scrollPosition() << "Matrix: " << matrix() << " SceneRect: " << sceneRect(); if (m_proxy) { qDebug() << "Proxy Pos: " << m_proxy->pos() << "Proxy " << m_proxy->size() << "\nProxy size hint" << m_proxy->effectiveSizeHint(Qt::MinimumSize) << m_proxy->effectiveSizeHint(Qt::PreferredSize) << m_proxy->effectiveSizeHint(Qt::MaximumSize) << "\nMatrix: " << m_proxy->matrix() << "\nWidget: " << m_proxy->widget()->geometry() << "scaled" << (zoomFactor() * m_proxy->widget()->size()); } } } // namespace qdesigner_internal QT_END_NAMESPACE