blob: f17e6a6caf078fdd895d1f3958c413c61677017e [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 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$
** GNU Lesser General Public License Usage
** 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.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $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