tools/designer/src/lib/shared/zoomwidget.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/designer/src/lib/shared/zoomwidget.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,570 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** 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.
+**
+** 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 <QtGui/QGraphicsScene>
+#include <QtGui/QGraphicsProxyWidget>
+#include <QtGui/QMenu>
+#include <QtGui/QAction>
+#include <QtGui/QActionGroup>
+#include <QtGui/QContextMenuEvent>
+#include <QtGui/QScrollBar>
+
+#include <QtCore/QTextStream>
+#include <QtCore/qmath.h>
+#include <QtCore/QDebug>
+#include <QtCore/QList>
+
+QT_BEGIN_NAMESPACE
+
+typedef QList<QAction*> ActionList;
+typedef QList<QGraphicsItem *> 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<int> ZoomMenu::zoomValues()
+{
+    QList<int> 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<qreal>(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<QObject*>(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<qreal>(width)));
+    }
+
+    const int height = s.height();
+    if (height != 0 && height != QWIDGETSIZE_MAX) {
+        valid = true;
+        rc.setHeight(vpMargin.height() + qCeil(deco.height() + zoomFactor() * static_cast<qreal>(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<QKeyEvent*>(event);
+            if (kevent->key() == Qt::Key_D)
+                dump();
+        }
+        break;
+    case QEvent::Resize:
+        if (debugZoomWidget > 1) {
+            const QResizeEvent *re = static_cast<const QResizeEvent *>(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<QContextMenuEvent*>(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