From f67b8df3ebdba2d398b9cce686b7c644adffff08 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Sat, 7 May 2011 00:02:01 +0200 Subject: library split --- src/widgets/graphicsview/graphicsview.pri | 52 + src/widgets/graphicsview/qgraph_p.h | 286 + src/widgets/graphicsview/qgraphicsanchorlayout.cpp | 533 + src/widgets/graphicsview/qgraphicsanchorlayout.h | 128 + .../graphicsview/qgraphicsanchorlayout_p.cpp | 3015 +++++ src/widgets/graphicsview/qgraphicsanchorlayout_p.h | 594 + src/widgets/graphicsview/qgraphicsgridlayout.cpp | 690 ++ src/widgets/graphicsview/qgraphicsgridlayout.h | 144 + src/widgets/graphicsview/qgraphicsitem.cpp | 11597 +++++++++++++++++++ src/widgets/graphicsview/qgraphicsitem.h | 1172 ++ src/widgets/graphicsview/qgraphicsitem_p.h | 891 ++ .../graphicsview/qgraphicsitemanimation.cpp | 599 + src/widgets/graphicsview/qgraphicsitemanimation.h | 120 + src/widgets/graphicsview/qgraphicslayout.cpp | 451 + src/widgets/graphicsview/qgraphicslayout.h | 98 + src/widgets/graphicsview/qgraphicslayout_p.cpp | 198 + src/widgets/graphicsview/qgraphicslayout_p.h | 154 + src/widgets/graphicsview/qgraphicslayoutitem.cpp | 935 ++ src/widgets/graphicsview/qgraphicslayoutitem.h | 155 + src/widgets/graphicsview/qgraphicslayoutitem_p.h | 103 + src/widgets/graphicsview/qgraphicslinearlayout.cpp | 568 + src/widgets/graphicsview/qgraphicslinearlayout.h | 119 + src/widgets/graphicsview/qgraphicsproxywidget.cpp | 1570 +++ src/widgets/graphicsview/qgraphicsproxywidget.h | 147 + src/widgets/graphicsview/qgraphicsproxywidget_p.h | 130 + src/widgets/graphicsview/qgraphicsscene.cpp | 6491 +++++++++++ src/widgets/graphicsview/qgraphicsscene.h | 329 + src/widgets/graphicsview/qgraphicsscene_bsp.cpp | 296 + src/widgets/graphicsview/qgraphicsscene_bsp_p.h | 132 + src/widgets/graphicsview/qgraphicsscene_p.h | 359 + .../graphicsview/qgraphicsscenebsptreeindex.cpp | 719 ++ .../graphicsview/qgraphicsscenebsptreeindex_p.h | 206 + src/widgets/graphicsview/qgraphicssceneevent.cpp | 1674 +++ src/widgets/graphicsview/qgraphicssceneevent.h | 326 + src/widgets/graphicsview/qgraphicssceneindex.cpp | 648 ++ src/widgets/graphicsview/qgraphicssceneindex_p.h | 182 + .../graphicsview/qgraphicsscenelinearindex.cpp | 95 + .../graphicsview/qgraphicsscenelinearindex_p.h | 109 + src/widgets/graphicsview/qgraphicstransform.cpp | 594 + src/widgets/graphicsview/qgraphicstransform.h | 159 + src/widgets/graphicsview/qgraphicstransform_p.h | 78 + src/widgets/graphicsview/qgraphicsview.cpp | 3880 +++++++ src/widgets/graphicsview/qgraphicsview.h | 316 + src/widgets/graphicsview/qgraphicsview_p.h | 233 + src/widgets/graphicsview/qgraphicswidget.cpp | 2388 ++++ src/widgets/graphicsview/qgraphicswidget.h | 257 + src/widgets/graphicsview/qgraphicswidget_p.cpp | 910 ++ src/widgets/graphicsview/qgraphicswidget_p.h | 226 + src/widgets/graphicsview/qgridlayoutengine.cpp | 1742 +++ src/widgets/graphicsview/qgridlayoutengine_p.h | 458 + src/widgets/graphicsview/qsimplex_p.cpp | 673 ++ src/widgets/graphicsview/qsimplex_p.h | 208 + 52 files changed, 48137 insertions(+) create mode 100644 src/widgets/graphicsview/graphicsview.pri create mode 100644 src/widgets/graphicsview/qgraph_p.h create mode 100644 src/widgets/graphicsview/qgraphicsanchorlayout.cpp create mode 100644 src/widgets/graphicsview/qgraphicsanchorlayout.h create mode 100644 src/widgets/graphicsview/qgraphicsanchorlayout_p.cpp create mode 100644 src/widgets/graphicsview/qgraphicsanchorlayout_p.h create mode 100644 src/widgets/graphicsview/qgraphicsgridlayout.cpp create mode 100644 src/widgets/graphicsview/qgraphicsgridlayout.h create mode 100644 src/widgets/graphicsview/qgraphicsitem.cpp create mode 100644 src/widgets/graphicsview/qgraphicsitem.h create mode 100644 src/widgets/graphicsview/qgraphicsitem_p.h create mode 100644 src/widgets/graphicsview/qgraphicsitemanimation.cpp create mode 100644 src/widgets/graphicsview/qgraphicsitemanimation.h create mode 100644 src/widgets/graphicsview/qgraphicslayout.cpp create mode 100644 src/widgets/graphicsview/qgraphicslayout.h create mode 100644 src/widgets/graphicsview/qgraphicslayout_p.cpp create mode 100644 src/widgets/graphicsview/qgraphicslayout_p.h create mode 100644 src/widgets/graphicsview/qgraphicslayoutitem.cpp create mode 100644 src/widgets/graphicsview/qgraphicslayoutitem.h create mode 100644 src/widgets/graphicsview/qgraphicslayoutitem_p.h create mode 100644 src/widgets/graphicsview/qgraphicslinearlayout.cpp create mode 100644 src/widgets/graphicsview/qgraphicslinearlayout.h create mode 100644 src/widgets/graphicsview/qgraphicsproxywidget.cpp create mode 100644 src/widgets/graphicsview/qgraphicsproxywidget.h create mode 100644 src/widgets/graphicsview/qgraphicsproxywidget_p.h create mode 100644 src/widgets/graphicsview/qgraphicsscene.cpp create mode 100644 src/widgets/graphicsview/qgraphicsscene.h create mode 100644 src/widgets/graphicsview/qgraphicsscene_bsp.cpp create mode 100644 src/widgets/graphicsview/qgraphicsscene_bsp_p.h create mode 100644 src/widgets/graphicsview/qgraphicsscene_p.h create mode 100644 src/widgets/graphicsview/qgraphicsscenebsptreeindex.cpp create mode 100644 src/widgets/graphicsview/qgraphicsscenebsptreeindex_p.h create mode 100644 src/widgets/graphicsview/qgraphicssceneevent.cpp create mode 100644 src/widgets/graphicsview/qgraphicssceneevent.h create mode 100644 src/widgets/graphicsview/qgraphicssceneindex.cpp create mode 100644 src/widgets/graphicsview/qgraphicssceneindex_p.h create mode 100644 src/widgets/graphicsview/qgraphicsscenelinearindex.cpp create mode 100644 src/widgets/graphicsview/qgraphicsscenelinearindex_p.h create mode 100644 src/widgets/graphicsview/qgraphicstransform.cpp create mode 100644 src/widgets/graphicsview/qgraphicstransform.h create mode 100644 src/widgets/graphicsview/qgraphicstransform_p.h create mode 100644 src/widgets/graphicsview/qgraphicsview.cpp create mode 100644 src/widgets/graphicsview/qgraphicsview.h create mode 100644 src/widgets/graphicsview/qgraphicsview_p.h create mode 100644 src/widgets/graphicsview/qgraphicswidget.cpp create mode 100644 src/widgets/graphicsview/qgraphicswidget.h create mode 100644 src/widgets/graphicsview/qgraphicswidget_p.cpp create mode 100644 src/widgets/graphicsview/qgraphicswidget_p.h create mode 100644 src/widgets/graphicsview/qgridlayoutengine.cpp create mode 100644 src/widgets/graphicsview/qgridlayoutengine_p.h create mode 100644 src/widgets/graphicsview/qsimplex_p.cpp create mode 100644 src/widgets/graphicsview/qsimplex_p.h (limited to 'src/widgets/graphicsview') diff --git a/src/widgets/graphicsview/graphicsview.pri b/src/widgets/graphicsview/graphicsview.pri new file mode 100644 index 0000000000..547d7ce7ae --- /dev/null +++ b/src/widgets/graphicsview/graphicsview.pri @@ -0,0 +1,52 @@ +# Qt graphicsview module +HEADERS += graphicsview/qgraphicsgridlayout.h \ + graphicsview/qgraphicsitem.h \ + graphicsview/qgraphicsitem_p.h \ + graphicsview/qgraphicsitemanimation.h \ + graphicsview/qgraphicslayout.h \ + graphicsview/qgraphicslayout_p.h \ + graphicsview/qgraphicslayoutitem.h \ + graphicsview/qgraphicslayoutitem_p.h \ + graphicsview/qgraphicslinearlayout.h \ + graphicsview/qgraphicsproxywidget.h \ + graphicsview/qgraphicsscene.h \ + graphicsview/qgraphicsscene_bsp_p.h \ + graphicsview/qgraphicsscene_p.h \ + graphicsview/qgraphicsscenebsptreeindex_p.h \ + graphicsview/qgraphicssceneevent.h \ + graphicsview/qgraphicssceneindex_p.h \ + graphicsview/qgraphicsscenelinearindex_p.h \ + graphicsview/qgraphicstransform.h \ + graphicsview/qgraphicstransform_p.h \ + graphicsview/qgraphicsview.h \ + graphicsview/qgraphicsview_p.h \ + graphicsview/qgraphicswidget.h \ + graphicsview/qgraphicswidget_p.h \ + graphicsview/qgridlayoutengine_p.h \ + graphicsview/qgraph_p.h \ + graphicsview/qsimplex_p.h \ + graphicsview/qgraphicsanchorlayout_p.h \ + graphicsview/qgraphicsanchorlayout.h + +SOURCES += graphicsview/qgraphicsgridlayout.cpp \ + graphicsview/qgraphicsitem.cpp \ + graphicsview/qgraphicsitemanimation.cpp \ + graphicsview/qgraphicslayout.cpp \ + graphicsview/qgraphicslayout_p.cpp \ + graphicsview/qgraphicslayoutitem.cpp \ + graphicsview/qgraphicslinearlayout.cpp \ + graphicsview/qgraphicsproxywidget.cpp \ + graphicsview/qgraphicsscene.cpp \ + graphicsview/qgraphicsscene_bsp.cpp \ + graphicsview/qgraphicsscenebsptreeindex.cpp \ + graphicsview/qgraphicssceneevent.cpp \ + graphicsview/qgraphicssceneindex.cpp \ + graphicsview/qgraphicsscenelinearindex.cpp \ + graphicsview/qgraphicstransform.cpp \ + graphicsview/qgraphicsview.cpp \ + graphicsview/qgraphicswidget.cpp \ + graphicsview/qgraphicswidget_p.cpp \ + graphicsview/qgridlayoutengine.cpp \ + graphicsview/qsimplex_p.cpp \ + graphicsview/qgraphicsanchorlayout_p.cpp \ + graphicsview/qgraphicsanchorlayout.cpp diff --git a/src/widgets/graphicsview/qgraph_p.h b/src/widgets/graphicsview/qgraph_p.h new file mode 100644 index 0000000000..094fbd1c2a --- /dev/null +++ b/src/widgets/graphicsview/qgraph_p.h @@ -0,0 +1,286 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPH_P_H +#define QGRAPH_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +template +class Graph +{ +public: + Graph() {} + + class const_iterator { + public: + const_iterator(const Graph *graph, bool begin) : g(graph){ + if (begin) { + row = g->m_graph.constBegin(); + //test if the graph is empty + if (row != g->m_graph.constEnd()) + { + column = (*row)->constBegin(); + } + } else { + row = g->m_graph.constEnd(); + } + } + + inline Vertex *operator*() { + return column.key(); + } + + inline Vertex *from() const { + return row.key(); + } + + inline Vertex *to() const { + return column.key(); + } + + inline bool operator==(const const_iterator &o) const { return !(*this != o); } + inline bool operator!=(const const_iterator &o) const { + if (row == g->m_graph.end()) { + return row != o.row; + } else { + return row != o.row || column != o.column; + } + } + inline const_iterator& operator=(const const_iterator &o) const { row = o.row; column = o.column; return *this;} + + // prefix + const_iterator &operator++() { + if (row != g->m_graph.constEnd()) { + ++column; + if (column == (*row)->constEnd()) { + ++row; + if (row != g->m_graph.constEnd()) { + column = (*row)->constBegin(); + } + } + } + return *this; + } + + private: + const Graph *g; + Q_TYPENAME QHash * >::const_iterator row; + Q_TYPENAME QHash::const_iterator column; + }; + + const_iterator constBegin() const { + return const_iterator(this,true); + } + + const_iterator constEnd() const { + return const_iterator(this,false); + } + + /*! + * \internal + * + * If there is an edge between \a first and \a second, it will return a structure + * containing the data associated with the edge, otherwise it will return 0. + * + */ + EdgeData *edgeData(Vertex* first, Vertex* second) { + QHash *row = m_graph.value(first); + return row ? row->value(second) : 0; + } + + void createEdge(Vertex *first, Vertex *second, EdgeData *data) + { + // Creates a bidirectional edge +#if defined(QT_DEBUG) && 0 + qDebug("Graph::createEdge(): %s", + qPrintable(QString::fromAscii("%1-%2") + .arg(first->toString()).arg(second->toString()))); +#endif + if (edgeData(first, second)) { +#ifdef QT_DEBUG + qWarning("%s-%s already has an edge", qPrintable(first->toString()), qPrintable(second->toString())); +#endif + } + createDirectedEdge(first, second, data); + createDirectedEdge(second, first, data); + } + + void removeEdge(Vertex *first, Vertex *second) + { + // Removes a bidirectional edge +#if defined(QT_DEBUG) && 0 + qDebug("Graph::removeEdge(): %s", + qPrintable(QString::fromAscii("%1-%2") + .arg(first->toString()).arg(second->toString()))); +#endif + EdgeData *data = edgeData(first, second); + removeDirectedEdge(first, second); + removeDirectedEdge(second, first); + if (data) delete data; + } + + EdgeData *takeEdge(Vertex* first, Vertex* second) + { +#if defined(QT_DEBUG) && 0 + qDebug("Graph::takeEdge(): %s", + qPrintable(QString::fromAscii("%1-%2") + .arg(first->toString()).arg(second->toString()))); +#endif + // Removes a bidirectional edge + EdgeData *data = edgeData(first, second); + if (data) { + removeDirectedEdge(first, second); + removeDirectedEdge(second, first); + } + return data; + } + + QList adjacentVertices(Vertex *vertex) const + { + QHash *row = m_graph.value(vertex); + QList l; + if (row) + l = row->keys(); + return l; + } + + QSet vertices() const { + QSet setOfVertices; + for (const_iterator it = constBegin(); it != constEnd(); ++it) { + setOfVertices.insert(*it); + } + return setOfVertices; + } + + QList > connections() const { + QList > conns; + for (const_iterator it = constBegin(); it != constEnd(); ++it) { + Vertex *from = it.from(); + Vertex *to = it.to(); + // do not return (from,to) *and* (to,from) + if (from < to) { + conns.append(qMakePair(from, to)); + } + } + return conns; + } + +#if defined(QT_DEBUG) + QString serializeToDot() { // traversal + QString strVertices; + QString edges; + + QSet setOfVertices = vertices(); + for (Q_TYPENAME QSet::const_iterator it = setOfVertices.begin(); it != setOfVertices.end(); ++it) { + Vertex *v = *it; + QList adjacents = adjacentVertices(v); + for (int i = 0; i < adjacents.count(); ++i) { + Vertex *v1 = adjacents.at(i); + EdgeData *data = edgeData(v, v1); + bool forward = data->from == v; + if (forward) { + edges += QString::fromAscii("\"%1\"->\"%2\" [label=\"[%3,%4,%5,%6,%7]\" color=\"#000000\"] \n") + .arg(v->toString()) + .arg(v1->toString()) + .arg(data->minSize) + .arg(data->minPrefSize) + .arg(data->prefSize) + .arg(data->maxPrefSize) + .arg(data->maxSize) + ; + } + } + strVertices += QString::fromAscii("\"%1\" [label=\"%2\"]\n").arg(v->toString()).arg(v->toString()); + } + return QString::fromAscii("%1\n%2\n").arg(strVertices).arg(edges); + } +#endif + +protected: + void createDirectedEdge(Vertex *from, Vertex *to, EdgeData *data) + { + QHash *adjacentToFirst = m_graph.value(from); + if (!adjacentToFirst) { + adjacentToFirst = new QHash(); + m_graph.insert(from, adjacentToFirst); + } + adjacentToFirst->insert(to, data); + } + + void removeDirectedEdge(Vertex *from, Vertex *to) + { + QHash *adjacentToFirst = m_graph.value(from); + Q_ASSERT(adjacentToFirst); + + adjacentToFirst->remove(to); + if (adjacentToFirst->isEmpty()) { + //nobody point to 'from' so we can remove it from the graph + m_graph.remove(from); + delete adjacentToFirst; + } + } + +private: + QHash *> m_graph; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/widgets/graphicsview/qgraphicsanchorlayout.cpp b/src/widgets/graphicsview/qgraphicsanchorlayout.cpp new file mode 100644 index 0000000000..9bb5424641 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsanchorlayout.cpp @@ -0,0 +1,533 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +/*! + \class QGraphicsAnchorLayout + \brief The QGraphicsAnchorLayout class provides a layout where one can anchor widgets + together in Graphics View. + \since 4.6 + \ingroup appearance + \ingroup geomanagement + \ingroup graphicsview-api + + The anchor layout allows developers to specify how widgets should be placed relative to + each other, and to the layout itself. The specification is made by adding anchors to the + layout by calling addAnchor(), addAnchors() or addCornerAnchors(). + + Existing anchors in the layout can be accessed with the anchor() function. + Items that are anchored are automatically added to the layout, and if items + are removed, all their anchors will be automatically removed. + + \div {class="float-left"} + \inlineimage simpleanchorlayout-example.png Using an anchor layout to align simple colored widgets. + \enddiv + + Anchors are always set up between edges of an item, where the "center" is also considered to + be an edge. Consider the following example: + + \snippet examples/graphicsview/simpleanchorlayout/main.cpp adding anchors + + Here, the right edge of item \c a is anchored to the left edge of item \c b and the bottom + edge of item \c a is anchored to the top edge of item \c b, with the result that + item \c b will be placed diagonally to the right and below item \c b. + + The addCornerAnchors() function provides a simpler way of anchoring the corners + of two widgets than the two individual calls to addAnchor() shown in the code + above. Here, we see how a widget can be anchored to the top-left corner of the enclosing + layout: + + \snippet examples/graphicsview/simpleanchorlayout/main.cpp adding a corner anchor + + In cases where anchors are used to match the widths or heights of widgets, it is + convenient to use the addAnchors() function. As with the other functions for specifying + anchors, it can also be used to anchor a widget to a layout. + + \clearfloat + \section1 Size Hints and Size Policies in an Anchor Layout + + QGraphicsAnchorLayout respects each item's size hints and size policies. + Note that there are some properties of QSizePolicy that are \l{Known issues}{not respected}. + + \section1 Spacing within an Anchor Layout + + The layout may distribute some space between the items. If the spacing has not been + explicitly specified, the actual amount of space will usually be 0. + + However, if the first edge is the \e opposite of the second edge (e.g., the right edge + of the first widget is anchored to the left edge of the second widget), the size of the + anchor will be queried from the style through a pixel metric: + \l{QStyle::}{PM_LayoutHorizontalSpacing} for horizontal anchors and + \l{QStyle::}{PM_LayoutVerticalSpacing} for vertical anchors. + + If the spacing is negative, the items will overlap to some extent. + + + \section1 Known issues + There are some features that QGraphicsAnchorLayout currently does not support. + This might change in the future, so avoid using these features if you want to + avoid any future regressions in behaviour: + \list + + \o Stretch factors are not respected. + + \o QSizePolicy::ExpandFlag is not respected. + + \o Height for width is not respected. + + \endlist + + \sa QGraphicsLinearLayout, QGraphicsGridLayout, QGraphicsLayout +*/ + +/*! + \class QGraphicsAnchor + \brief The QGraphicsAnchor class represents an anchor between two items in a + QGraphicsAnchorLayout. + \since 4.6 + \ingroup appearance + \ingroup geomanagement + \ingroup graphicsview-api + + The graphics anchor provides an API that enables you to query and manipulate the + properties an anchor has. When an anchor is added to the layout with + QGraphicsAnchorLayout::addAnchor(), a QGraphicsAnchor instance is returned where the properties + are initialized to their default values. The properties can then be further changed, and they + will be picked up the next time the layout is activated. + + \sa QGraphicsAnchorLayout::anchor() + +*/ +#include "qgraphicsanchorlayout_p.h" +#ifndef QT_NO_GRAPHICSVIEW +QT_BEGIN_NAMESPACE + +QGraphicsAnchor::QGraphicsAnchor(QGraphicsAnchorLayout *parentLayout) + : QObject(*(new QGraphicsAnchorPrivate)) +{ + Q_D(QGraphicsAnchor); + Q_ASSERT(parentLayout); + d->layoutPrivate = parentLayout->d_func(); +} + +/*! + Removes the QGraphicsAnchor object from the layout and destroys it. +*/ +QGraphicsAnchor::~QGraphicsAnchor() +{ +} + +/*! + \property QGraphicsAnchor::sizePolicy + \brief the size policy for the QGraphicsAnchor. + + By setting the size policy on an anchor you can configure how the anchor can resize itself + from its preferred spacing. For instance, if the anchor has the size policy + QSizePolicy::Minimum, the spacing is the minimum size of the anchor. However, its size + can grow up to the anchors maximum size. If the default size policy is QSizePolicy::Fixed, + the anchor can neither grow or shrink, which means that the only size the anchor can have + is the spacing. QSizePolicy::Fixed is the default size policy. + QGraphicsAnchor always has a minimum spacing of 0 and a very large maximum spacing. + + \sa QGraphicsAnchor::spacing +*/ + +void QGraphicsAnchor::setSizePolicy(QSizePolicy::Policy policy) +{ + Q_D(QGraphicsAnchor); + d->setSizePolicy(policy); +} + +QSizePolicy::Policy QGraphicsAnchor::sizePolicy() const +{ + Q_D(const QGraphicsAnchor); + return d->sizePolicy; +} + +/*! + \property QGraphicsAnchor::spacing + \brief the preferred space between items in the QGraphicsAnchorLayout. + + Depending on the anchor type, the default spacing is either + 0 or a value returned from the style. + + \sa QGraphicsAnchorLayout::addAnchor() +*/ +void QGraphicsAnchor::setSpacing(qreal spacing) +{ + Q_D(QGraphicsAnchor); + d->setSpacing(spacing); +} + +qreal QGraphicsAnchor::spacing() const +{ + Q_D(const QGraphicsAnchor); + return d->spacing(); +} + +void QGraphicsAnchor::unsetSpacing() +{ + Q_D(QGraphicsAnchor); + d->unsetSpacing(); +} + +/*! + Constructs a QGraphicsAnchorLayout instance. \a parent is passed to + QGraphicsLayout's constructor. + */ +QGraphicsAnchorLayout::QGraphicsAnchorLayout(QGraphicsLayoutItem *parent) + : QGraphicsLayout(*new QGraphicsAnchorLayoutPrivate(), parent) +{ + Q_D(QGraphicsAnchorLayout); + d->createLayoutEdges(); +} + +/*! + Destroys the QGraphicsAnchorLayout object. +*/ +QGraphicsAnchorLayout::~QGraphicsAnchorLayout() +{ + Q_D(QGraphicsAnchorLayout); + + for (int i = count() - 1; i >= 0; --i) { + QGraphicsLayoutItem *item = d->items.at(i); + removeAt(i); + if (item) { + if (item->ownedByLayout()) + delete item; + } + } + + d->removeCenterConstraints(this, QGraphicsAnchorLayoutPrivate::Horizontal); + d->removeCenterConstraints(this, QGraphicsAnchorLayoutPrivate::Vertical); + d->deleteLayoutEdges(); + + Q_ASSERT(d->itemCenterConstraints[0].isEmpty()); + Q_ASSERT(d->itemCenterConstraints[1].isEmpty()); + Q_ASSERT(d->items.isEmpty()); + Q_ASSERT(d->m_vertexList.isEmpty()); +} + +/*! + Creates an anchor between the edge \a firstEdge of item \a firstItem and the edge \a secondEdge + of item \a secondItem. The spacing of the anchor is picked up from the style. Anchors + between a layout edge and an item edge will have a size of 0. + If there is already an anchor between the edges, the the new anchor will replace the old one. + + \a firstItem and \a secondItem are automatically added to the layout if they are not part + of the layout. This means that count() can increase by up to 2. + + The spacing an anchor will get depends on the type of anchor. For instance, anchors from the + Right edge of one item to the Left edge of another (or vice versa) will use the default + horizontal spacing. The same behaviour applies to Bottom to Top anchors, (but they will use + the default vertical spacing). For all other anchor combinations, the spacing will be 0. + All anchoring functions will follow this rule. + + The spacing can also be set manually by using QGraphicsAnchor::setSpacing() method. + + Calling this function where \a firstItem or \a secondItem are ancestors of the layout have + undefined behaviour. + + \sa addAnchors(), addCornerAnchors() + */ +QGraphicsAnchor * +QGraphicsAnchorLayout::addAnchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge) +{ + Q_D(QGraphicsAnchorLayout); + QGraphicsAnchor *a = d->addAnchor(firstItem, firstEdge, secondItem, secondEdge); + invalidate(); + return a; +} + +/*! + Returns the anchor between the anchor points defined by \a firstItem and \a firstEdge and + \a secondItem and \a secondEdge. If there is no such anchor, the function will return 0. +*/ +QGraphicsAnchor * +QGraphicsAnchorLayout::anchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge) +{ + Q_D(QGraphicsAnchorLayout); + return d->getAnchor(firstItem, firstEdge, secondItem, secondEdge); +} + +/*! + Creates two anchors between \a firstItem and \a secondItem specified by the corners, + \a firstCorner and \a secondCorner, where one is for the horizontal edge and another + one for the vertical edge. + + This is a convenience function, since anchoring corners can be expressed as anchoring + two edges. For instance: + + \snippet examples/graphicsview/simpleanchorlayout/main.cpp adding a corner anchor in two steps + + This can also be achieved with the following line of code: + + \snippet examples/graphicsview/simpleanchorlayout/main.cpp adding a corner anchor + + If there is already an anchor between the edge pairs, it will be replaced by the anchors that + this function specifies. + + \a firstItem and \a secondItem are automatically added to the layout if they are not part of the + layout. This means that count() can increase by up to 2. + + \sa addAnchor(), addAnchors() +*/ +void QGraphicsAnchorLayout::addCornerAnchors(QGraphicsLayoutItem *firstItem, + Qt::Corner firstCorner, + QGraphicsLayoutItem *secondItem, + Qt::Corner secondCorner) +{ + Q_D(QGraphicsAnchorLayout); + + // Horizontal anchor + Qt::AnchorPoint firstEdge = (firstCorner & 1 ? Qt::AnchorRight: Qt::AnchorLeft); + Qt::AnchorPoint secondEdge = (secondCorner & 1 ? Qt::AnchorRight: Qt::AnchorLeft); + if (d->addAnchor(firstItem, firstEdge, secondItem, secondEdge)) { + // Vertical anchor + firstEdge = (firstCorner & 2 ? Qt::AnchorBottom: Qt::AnchorTop); + secondEdge = (secondCorner & 2 ? Qt::AnchorBottom: Qt::AnchorTop); + d->addAnchor(firstItem, firstEdge, secondItem, secondEdge); + + invalidate(); + } +} + +/*! + Anchors two or four edges of \a firstItem with the corresponding + edges of \a secondItem, so that \a firstItem has the same size as + \a secondItem in the dimensions specified by \a orientations. + + For example, the following example anchors the left and right edges of two items + to match their widths: + + \snippet examples/graphicsview/simpleanchorlayout/main.cpp adding anchors to match sizes in two steps + + This can also be achieved using the following line of code: + + \snippet examples/graphicsview/simpleanchorlayout/main.cpp adding anchors to match sizes + + \sa addAnchor(), addCornerAnchors() +*/ +void QGraphicsAnchorLayout::addAnchors(QGraphicsLayoutItem *firstItem, + QGraphicsLayoutItem *secondItem, + Qt::Orientations orientations) +{ + bool ok = true; + if (orientations & Qt::Horizontal) { + // Currently, if the first is ok, then the rest of the calls should be ok + ok = addAnchor(secondItem, Qt::AnchorLeft, firstItem, Qt::AnchorLeft) != 0; + if (ok) + addAnchor(firstItem, Qt::AnchorRight, secondItem, Qt::AnchorRight); + } + if (orientations & Qt::Vertical && ok) { + addAnchor(secondItem, Qt::AnchorTop, firstItem, Qt::AnchorTop); + addAnchor(firstItem, Qt::AnchorBottom, secondItem, Qt::AnchorBottom); + } +} + +/*! + Sets the default horizontal spacing for the anchor layout to \a spacing. + + \sa horizontalSpacing(), setVerticalSpacing(), setSpacing() +*/ +void QGraphicsAnchorLayout::setHorizontalSpacing(qreal spacing) +{ + Q_D(QGraphicsAnchorLayout); + + d->spacings[0] = spacing; + invalidate(); +} + +/*! + Sets the default vertical spacing for the anchor layout to \a spacing. + + \sa verticalSpacing(), setHorizontalSpacing(), setSpacing() +*/ +void QGraphicsAnchorLayout::setVerticalSpacing(qreal spacing) +{ + Q_D(QGraphicsAnchorLayout); + + d->spacings[1] = spacing; + invalidate(); +} + +/*! + Sets the default horizontal and the default vertical spacing for the anchor layout to \a spacing. + + If an item is anchored with no spacing associated with the anchor, it will use the default + spacing. + + QGraphicsAnchorLayout does not support negative spacings. Setting a negative value will unset the + previous spacing and make the layout use the spacing provided by the current widget style. + + \sa setHorizontalSpacing(), setVerticalSpacing() +*/ +void QGraphicsAnchorLayout::setSpacing(qreal spacing) +{ + Q_D(QGraphicsAnchorLayout); + + d->spacings[0] = d->spacings[1] = spacing; + invalidate(); +} + +/*! + Returns the default horizontal spacing for the anchor layout. + + \sa verticalSpacing(), setHorizontalSpacing() +*/ +qreal QGraphicsAnchorLayout::horizontalSpacing() const +{ + Q_D(const QGraphicsAnchorLayout); + return d->styleInfo().defaultSpacing(Qt::Horizontal); +} + +/*! + Returns the default vertical spacing for the anchor layout. + + \sa horizontalSpacing(), setVerticalSpacing() +*/ +qreal QGraphicsAnchorLayout::verticalSpacing() const +{ + Q_D(const QGraphicsAnchorLayout); + return d->styleInfo().defaultSpacing(Qt::Vertical); +} + +/*! + \reimp +*/ +void QGraphicsAnchorLayout::setGeometry(const QRectF &geom) +{ + Q_D(QGraphicsAnchorLayout); + + QGraphicsLayout::setGeometry(geom); + d->calculateVertexPositions(QGraphicsAnchorLayoutPrivate::Horizontal); + d->calculateVertexPositions(QGraphicsAnchorLayoutPrivate::Vertical); + d->setItemsGeometries(geom); +} + +/*! + Removes the layout item at \a index without destroying it. Ownership of + the item is transferred to the caller. + + Removing an item will also remove any of the anchors associated with it. + + \sa itemAt(), count() +*/ +void QGraphicsAnchorLayout::removeAt(int index) +{ + Q_D(QGraphicsAnchorLayout); + QGraphicsLayoutItem *item = d->items.value(index); + + if (!item) + return; + + // Removing an item affects both horizontal and vertical graphs + d->removeCenterConstraints(item, QGraphicsAnchorLayoutPrivate::Horizontal); + d->removeCenterConstraints(item, QGraphicsAnchorLayoutPrivate::Vertical); + d->removeAnchors(item); + d->items.remove(index); + + item->setParentLayoutItem(0); + invalidate(); +} + +/*! + \reimp +*/ +int QGraphicsAnchorLayout::count() const +{ + Q_D(const QGraphicsAnchorLayout); + return d->items.size(); +} + +/*! + \reimp +*/ +QGraphicsLayoutItem *QGraphicsAnchorLayout::itemAt(int index) const +{ + Q_D(const QGraphicsAnchorLayout); + return d->items.value(index); +} + +/*! + \reimp +*/ +void QGraphicsAnchorLayout::invalidate() +{ + Q_D(QGraphicsAnchorLayout); + QGraphicsLayout::invalidate(); + d->calculateGraphCacheDirty = true; + d->styleInfoDirty = true; +} + +/*! + \reimp +*/ +QSizeF QGraphicsAnchorLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_UNUSED(constraint); + Q_D(const QGraphicsAnchorLayout); + + // Some setup calculations are delayed until the information is + // actually needed, avoiding unnecessary recalculations when + // adding multiple anchors. + + // sizeHint() / effectiveSizeHint() already have a cache + // mechanism, using invalidate() to force recalculation. However + // sizeHint() is called three times after invalidation (for max, + // min and pref), but we just need do our setup once. + + const_cast(d)->calculateGraphs(); + + // ### apply constraint! + QSizeF engineSizeHint( + d->sizeHints[QGraphicsAnchorLayoutPrivate::Horizontal][which], + d->sizeHints[QGraphicsAnchorLayoutPrivate::Vertical][which]); + + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + + return engineSizeHint + QSizeF(left + right, top + bottom); +} + +QT_END_NAMESPACE +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicsanchorlayout.h b/src/widgets/graphicsview/qgraphicsanchorlayout.h new file mode 100644 index 0000000000..cdb14bc3ba --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsanchorlayout.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSANCHORLAYOUT_H +#define QGRAPHICSANCHORLAYOUT_H + +#include +#include + + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsAnchorPrivate; +class QGraphicsAnchorLayout; +class QGraphicsAnchorLayoutPrivate; + +class Q_GUI_EXPORT QGraphicsAnchor : public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing RESET unsetSpacing) + Q_PROPERTY(QSizePolicy::Policy sizePolicy READ sizePolicy WRITE setSizePolicy) +public: + void setSpacing(qreal spacing); + void unsetSpacing(); + qreal spacing() const; + void setSizePolicy(QSizePolicy::Policy policy); + QSizePolicy::Policy sizePolicy() const; + ~QGraphicsAnchor(); +private: + QGraphicsAnchor(QGraphicsAnchorLayout *parent); + + Q_DECLARE_PRIVATE(QGraphicsAnchor) + + friend class QGraphicsAnchorLayoutPrivate; + friend struct AnchorData; +}; + +class Q_GUI_EXPORT QGraphicsAnchorLayout : public QGraphicsLayout +{ +public: + QGraphicsAnchorLayout(QGraphicsLayoutItem *parent = 0); + virtual ~QGraphicsAnchorLayout(); + + QGraphicsAnchor *addAnchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge); + QGraphicsAnchor *anchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge); + + void addCornerAnchors(QGraphicsLayoutItem *firstItem, Qt::Corner firstCorner, + QGraphicsLayoutItem *secondItem, Qt::Corner secondCorner); + + void addAnchors(QGraphicsLayoutItem *firstItem, + QGraphicsLayoutItem *secondItem, + Qt::Orientations orientations = Qt::Horizontal | Qt::Vertical); + + void setHorizontalSpacing(qreal spacing); + void setVerticalSpacing(qreal spacing); + void setSpacing(qreal spacing); + qreal horizontalSpacing() const; + qreal verticalSpacing() const; + + void removeAt(int index); + void setGeometry(const QRectF &rect); + int count() const; + QGraphicsLayoutItem *itemAt(int index) const; + + void invalidate(); +protected: + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + +private: + Q_DISABLE_COPY(QGraphicsAnchorLayout) + Q_DECLARE_PRIVATE(QGraphicsAnchorLayout) + + friend class QGraphicsAnchor; +}; + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/widgets/graphicsview/qgraphicsanchorlayout_p.cpp b/src/widgets/graphicsview/qgraphicsanchorlayout_p.cpp new file mode 100644 index 0000000000..48cbec3d62 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsanchorlayout_p.cpp @@ -0,0 +1,3015 @@ +/**************************************************************************** +** +** 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 QtGui module 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 +#include +#include +#include + +#ifdef QT_DEBUG +#include +#endif + +#include "qgraphicsanchorlayout_p.h" + +#ifndef QT_NO_GRAPHICSVIEW +QT_BEGIN_NAMESPACE + +// To ensure that all variables inside the simplex solver are non-negative, +// we limit the size of anchors in the interval [-limit, limit]. Then before +// sending them to the simplex solver we add "limit" as an offset, so that +// they are actually calculated in the interval [0, 2 * limit] +// To avoid numerical errors in platforms where we use single precision, +// we use a tighter limit for the variables range. +const qreal g_offset = (sizeof(qreal) == sizeof(double)) ? QWIDGETSIZE_MAX : QWIDGETSIZE_MAX / 32; + +QGraphicsAnchorPrivate::QGraphicsAnchorPrivate(int version) + : QObjectPrivate(version), layoutPrivate(0), data(0), + sizePolicy(QSizePolicy::Fixed), preferredSize(0), + hasSize(true) +{ +} + +QGraphicsAnchorPrivate::~QGraphicsAnchorPrivate() +{ + if (data) { + // The QGraphicsAnchor was already deleted at this moment. We must clean + // the dangling pointer to avoid double deletion in the AnchorData dtor. + data->graphicsAnchor = 0; + + layoutPrivate->removeAnchor(data->from, data->to); + } +} + +void QGraphicsAnchorPrivate::setSizePolicy(QSizePolicy::Policy policy) +{ + if (sizePolicy != policy) { + sizePolicy = policy; + layoutPrivate->q_func()->invalidate(); + } +} + +void QGraphicsAnchorPrivate::setSpacing(qreal value) +{ + if (!data) { + qWarning("QGraphicsAnchor::setSpacing: The anchor does not exist."); + return; + } + + if (hasSize && (preferredSize == value)) + return; + + // The anchor has an user-defined size + hasSize = true; + preferredSize = value; + + layoutPrivate->q_func()->invalidate(); +} + +void QGraphicsAnchorPrivate::unsetSpacing() +{ + if (!data) { + qWarning("QGraphicsAnchor::setSpacing: The anchor does not exist."); + return; + } + + // Return to standard direction + hasSize = false; + + layoutPrivate->q_func()->invalidate(); +} + +qreal QGraphicsAnchorPrivate::spacing() const +{ + if (!data) { + qWarning("QGraphicsAnchor::setSpacing: The anchor does not exist."); + return 0; + } + + return preferredSize; +} + + +static void applySizePolicy(QSizePolicy::Policy policy, + qreal minSizeHint, qreal prefSizeHint, qreal maxSizeHint, + qreal *minSize, qreal *prefSize, + qreal *maxSize) +{ + // minSize, prefSize and maxSize are initialized + // with item's preferred Size: this is QSizePolicy::Fixed. + // + // Then we check each flag to find the resultant QSizePolicy, + // according to the following table: + // + // constant value + // QSizePolicy::Fixed 0 + // QSizePolicy::Minimum GrowFlag + // QSizePolicy::Maximum ShrinkFlag + // QSizePolicy::Preferred GrowFlag | ShrinkFlag + // QSizePolicy::Ignored GrowFlag | ShrinkFlag | IgnoreFlag + + if (policy & QSizePolicy::ShrinkFlag) + *minSize = minSizeHint; + else + *minSize = prefSizeHint; + + if (policy & QSizePolicy::GrowFlag) + *maxSize = maxSizeHint; + else + *maxSize = prefSizeHint; + + // Note that these two initializations are affected by the previous flags + if (policy & QSizePolicy::IgnoreFlag) + *prefSize = *minSize; + else + *prefSize = prefSizeHint; +} + +AnchorData::~AnchorData() +{ + if (graphicsAnchor) { + // Remove reference to ourself to avoid double removal in + // QGraphicsAnchorPrivate dtor. + graphicsAnchor->d_func()->data = 0; + + delete graphicsAnchor; + } +} + + +void AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo) +{ + QSizePolicy::Policy policy; + qreal minSizeHint; + qreal prefSizeHint; + qreal maxSizeHint; + + if (item) { + // It is an internal anchor, fetch size information from the item + if (isLayoutAnchor) { + minSize = 0; + prefSize = 0; + maxSize = QWIDGETSIZE_MAX; + if (isCenterAnchor) + maxSize /= 2; + + minPrefSize = prefSize; + maxPrefSize = maxSize; + return; + } else { + if (orientation == QGraphicsAnchorLayoutPrivate::Horizontal) { + policy = item->sizePolicy().horizontalPolicy(); + minSizeHint = item->effectiveSizeHint(Qt::MinimumSize).width(); + prefSizeHint = item->effectiveSizeHint(Qt::PreferredSize).width(); + maxSizeHint = item->effectiveSizeHint(Qt::MaximumSize).width(); + } else { + policy = item->sizePolicy().verticalPolicy(); + minSizeHint = item->effectiveSizeHint(Qt::MinimumSize).height(); + prefSizeHint = item->effectiveSizeHint(Qt::PreferredSize).height(); + maxSizeHint = item->effectiveSizeHint(Qt::MaximumSize).height(); + } + + if (isCenterAnchor) { + minSizeHint /= 2; + prefSizeHint /= 2; + maxSizeHint /= 2; + } + } + } else { + // It is a user-created anchor, fetch size information from the associated QGraphicsAnchor + Q_ASSERT(graphicsAnchor); + QGraphicsAnchorPrivate *anchorPrivate = graphicsAnchor->d_func(); + + // Policy, min and max sizes are straightforward + policy = anchorPrivate->sizePolicy; + minSizeHint = 0; + maxSizeHint = QWIDGETSIZE_MAX; + + // Preferred Size + if (anchorPrivate->hasSize) { + // Anchor has user-defined size + prefSizeHint = anchorPrivate->preferredSize; + } else { + // Fetch size information from style + const Qt::Orientation orient = Qt::Orientation(QGraphicsAnchorLayoutPrivate::edgeOrientation(from->m_edge) + 1); + qreal s = styleInfo->defaultSpacing(orient); + if (s < 0) { + QSizePolicy::ControlType controlTypeFrom = from->m_item->sizePolicy().controlType(); + QSizePolicy::ControlType controlTypeTo = to->m_item->sizePolicy().controlType(); + s = styleInfo->perItemSpacing(controlTypeFrom, controlTypeTo, orient); + + // ### Currently we do not support negative anchors inside the graph. + // To avoid those being created by a negative style spacing, we must + // make this test. + if (s < 0) + s = 0; + } + prefSizeHint = s; + } + } + + // Fill minSize, prefSize and maxSize based on policy and sizeHints + applySizePolicy(policy, minSizeHint, prefSizeHint, maxSizeHint, + &minSize, &prefSize, &maxSize); + + minPrefSize = prefSize; + maxPrefSize = maxSize; + + // Set the anchor effective sizes to preferred. + // + // Note: The idea here is that all items should remain at their + // preferred size unless where that's impossible. In cases where + // the item is subject to restrictions (anchored to the layout + // edges, for instance), the simplex solver will be run to + // recalculate and override the values we set here. + sizeAtMinimum = prefSize; + sizeAtPreferred = prefSize; + sizeAtMaximum = prefSize; +} + +void ParallelAnchorData::updateChildrenSizes() +{ + firstEdge->sizeAtMinimum = sizeAtMinimum; + firstEdge->sizeAtPreferred = sizeAtPreferred; + firstEdge->sizeAtMaximum = sizeAtMaximum; + + if (secondForward()) { + secondEdge->sizeAtMinimum = sizeAtMinimum; + secondEdge->sizeAtPreferred = sizeAtPreferred; + secondEdge->sizeAtMaximum = sizeAtMaximum; + } else { + secondEdge->sizeAtMinimum = -sizeAtMinimum; + secondEdge->sizeAtPreferred = -sizeAtPreferred; + secondEdge->sizeAtMaximum = -sizeAtMaximum; + } + + firstEdge->updateChildrenSizes(); + secondEdge->updateChildrenSizes(); +} + +/* + \internal + + Initialize the parallel anchor size hints using the sizeHint information from + its children. + + Note that parallel groups can lead to unfeasibility, so during calculation, we can + find out one unfeasibility. Because of that this method return boolean. This can't + happen in sequential, so there the method is void. + */ +bool ParallelAnchorData::calculateSizeHints() +{ + // Normalize second child sizes. + // A negative anchor of sizes min, minPref, pref, maxPref and max, is equivalent + // to a forward anchor of sizes -max, -maxPref, -pref, -minPref, -min + qreal secondMin; + qreal secondMinPref; + qreal secondPref; + qreal secondMaxPref; + qreal secondMax; + + if (secondForward()) { + secondMin = secondEdge->minSize; + secondMinPref = secondEdge->minPrefSize; + secondPref = secondEdge->prefSize; + secondMaxPref = secondEdge->maxPrefSize; + secondMax = secondEdge->maxSize; + } else { + secondMin = -secondEdge->maxSize; + secondMinPref = -secondEdge->maxPrefSize; + secondPref = -secondEdge->prefSize; + secondMaxPref = -secondEdge->minPrefSize; + secondMax = -secondEdge->minSize; + } + + minSize = qMax(firstEdge->minSize, secondMin); + maxSize = qMin(firstEdge->maxSize, secondMax); + + // This condition means that the maximum size of one anchor being simplified is smaller than + // the minimum size of the other anchor. The consequence is that there won't be a valid size + // for this parallel setup. + if (minSize > maxSize) { + return false; + } + + // Preferred size calculation + // The calculation of preferred size is done as follows: + // + // 1) Check whether one of the child anchors is the layout structural anchor + // If so, we can simply copy the preferred information from the other child, + // after bounding it to our minimum and maximum sizes. + // If not, then we proceed with the actual calculations. + // + // 2) The whole algorithm for preferred size calculation is based on the fact + // that, if a given anchor cannot remain at its preferred size, it'd rather + // grow than shrink. + // + // What happens though is that while this affirmative is true for simple + // anchors, it may not be true for sequential anchors that have one or more + // reversed anchors inside it. That happens because when a sequential anchor + // grows, any reversed anchors inside it may be required to shrink, something + // we try to avoid, as said above. + // + // To overcome this, besides their actual preferred size "prefSize", each anchor + // exports what we call "minPrefSize" and "maxPrefSize". These two values define + // a surrounding interval where, if required to move, the anchor would rather + // remain inside. + // + // For standard anchors, this area simply represents the region between + // prefSize and maxSize, which makes sense since our first affirmation. + // For composed anchors, these values are calculated as to reduce the global + // "damage", that is, to reduce the total deviation and the total amount of + // anchors that had to shrink. + + if (firstEdge->isLayoutAnchor) { + prefSize = qBound(minSize, secondPref, maxSize); + minPrefSize = qBound(minSize, secondMinPref, maxSize); + maxPrefSize = qBound(minSize, secondMaxPref, maxSize); + } else if (secondEdge->isLayoutAnchor) { + prefSize = qBound(minSize, firstEdge->prefSize, maxSize); + minPrefSize = qBound(minSize, firstEdge->minPrefSize, maxSize); + maxPrefSize = qBound(minSize, firstEdge->maxPrefSize, maxSize); + } else { + // Calculate the intersection between the "preferred" regions of each child + const qreal lowerBoundary = + qBound(minSize, qMax(firstEdge->minPrefSize, secondMinPref), maxSize); + const qreal upperBoundary = + qBound(minSize, qMin(firstEdge->maxPrefSize, secondMaxPref), maxSize); + const qreal prefMean = + qBound(minSize, (firstEdge->prefSize + secondPref) / 2, maxSize); + + if (lowerBoundary < upperBoundary) { + // If there is an intersection between the two regions, this intersection + // will be used as the preferred region of the parallel anchor itself. + // The preferred size will be the bounded average between the two preferred + // sizes. + prefSize = qBound(lowerBoundary, prefMean, upperBoundary); + minPrefSize = lowerBoundary; + maxPrefSize = upperBoundary; + } else { + // If there is no intersection, we have to attribute "damage" to at least + // one of the children. The minimum total damage is achieved in points + // inside the region that extends from (1) the upper boundary of the lower + // region to (2) the lower boundary of the upper region. + // Then, we expose this region as _our_ preferred region and once again, + // use the bounded average as our preferred size. + prefSize = qBound(upperBoundary, prefMean, lowerBoundary); + minPrefSize = upperBoundary; + maxPrefSize = lowerBoundary; + } + } + + // See comment in AnchorData::refreshSizeHints() about sizeAt* values + sizeAtMinimum = prefSize; + sizeAtPreferred = prefSize; + sizeAtMaximum = prefSize; + + return true; +} + +/*! + \internal + returns the factor in the interval [-1, 1]. + -1 is at Minimum + 0 is at Preferred + 1 is at Maximum +*/ +static QPair getFactor(qreal value, qreal min, + qreal minPref, qreal pref, + qreal maxPref, qreal max) +{ + QGraphicsAnchorLayoutPrivate::Interval interval; + qreal lower; + qreal upper; + + if (value < minPref) { + interval = QGraphicsAnchorLayoutPrivate::MinimumToMinPreferred; + lower = min; + upper = minPref; + } else if (value < pref) { + interval = QGraphicsAnchorLayoutPrivate::MinPreferredToPreferred; + lower = minPref; + upper = pref; + } else if (value < maxPref) { + interval = QGraphicsAnchorLayoutPrivate::PreferredToMaxPreferred; + lower = pref; + upper = maxPref; + } else { + interval = QGraphicsAnchorLayoutPrivate::MaxPreferredToMaximum; + lower = maxPref; + upper = max; + } + + qreal progress; + if (upper == lower) { + progress = 0; + } else { + progress = (value - lower) / (upper - lower); + } + + return qMakePair(interval, progress); +} + +static qreal interpolate(const QPair &factor, + qreal min, qreal minPref, qreal pref, qreal maxPref, qreal max) +{ + qreal lower; + qreal upper; + + switch (factor.first) { + case QGraphicsAnchorLayoutPrivate::MinimumToMinPreferred: + lower = min; + upper = minPref; + break; + case QGraphicsAnchorLayoutPrivate::MinPreferredToPreferred: + lower = minPref; + upper = pref; + break; + case QGraphicsAnchorLayoutPrivate::PreferredToMaxPreferred: + lower = pref; + upper = maxPref; + break; + case QGraphicsAnchorLayoutPrivate::MaxPreferredToMaximum: + lower = maxPref; + upper = max; + break; + } + + return lower + factor.second * (upper - lower); +} + +void SequentialAnchorData::updateChildrenSizes() +{ + // Band here refers if the value is in the Minimum To Preferred + // band (the lower band) or the Preferred To Maximum (the upper band). + + const QPair minFactor = + getFactor(sizeAtMinimum, minSize, minPrefSize, prefSize, maxPrefSize, maxSize); + const QPair prefFactor = + getFactor(sizeAtPreferred, minSize, minPrefSize, prefSize, maxPrefSize, maxSize); + const QPair maxFactor = + getFactor(sizeAtMaximum, minSize, minPrefSize, prefSize, maxPrefSize, maxSize); + + // XXX This is not safe if Vertex simplification takes place after the sequential + // anchor is created. In that case, "prev" will be a group-vertex, different from + // "from" or "to", that _contains_ one of them. + AnchorVertex *prev = from; + + for (int i = 0; i < m_edges.count(); ++i) { + AnchorData *e = m_edges.at(i); + + const bool edgeIsForward = (e->from == prev); + if (edgeIsForward) { + e->sizeAtMinimum = interpolate(minFactor, e->minSize, e->minPrefSize, + e->prefSize, e->maxPrefSize, e->maxSize); + e->sizeAtPreferred = interpolate(prefFactor, e->minSize, e->minPrefSize, + e->prefSize, e->maxPrefSize, e->maxSize); + e->sizeAtMaximum = interpolate(maxFactor, e->minSize, e->minPrefSize, + e->prefSize, e->maxPrefSize, e->maxSize); + prev = e->to; + } else { + Q_ASSERT(prev == e->to); + e->sizeAtMinimum = interpolate(minFactor, e->maxSize, e->maxPrefSize, + e->prefSize, e->minPrefSize, e->minSize); + e->sizeAtPreferred = interpolate(prefFactor, e->maxSize, e->maxPrefSize, + e->prefSize, e->minPrefSize, e->minSize); + e->sizeAtMaximum = interpolate(maxFactor, e->maxSize, e->maxPrefSize, + e->prefSize, e->minPrefSize, e->minSize); + prev = e->from; + } + + e->updateChildrenSizes(); + } +} + +void SequentialAnchorData::calculateSizeHints() +{ + minSize = 0; + prefSize = 0; + maxSize = 0; + minPrefSize = 0; + maxPrefSize = 0; + + AnchorVertex *prev = from; + + for (int i = 0; i < m_edges.count(); ++i) { + AnchorData *edge = m_edges.at(i); + + const bool edgeIsForward = (edge->from == prev); + if (edgeIsForward) { + minSize += edge->minSize; + prefSize += edge->prefSize; + maxSize += edge->maxSize; + minPrefSize += edge->minPrefSize; + maxPrefSize += edge->maxPrefSize; + prev = edge->to; + } else { + Q_ASSERT(prev == edge->to); + minSize -= edge->maxSize; + prefSize -= edge->prefSize; + maxSize -= edge->minSize; + minPrefSize -= edge->maxPrefSize; + maxPrefSize -= edge->minPrefSize; + prev = edge->from; + } + } + + // See comment in AnchorData::refreshSizeHints() about sizeAt* values + sizeAtMinimum = prefSize; + sizeAtPreferred = prefSize; + sizeAtMaximum = prefSize; +} + +#ifdef QT_DEBUG +void AnchorData::dump(int indent) { + if (type == Parallel) { + qDebug("%*s type: parallel:", indent, ""); + ParallelAnchorData *p = static_cast(this); + p->firstEdge->dump(indent+2); + p->secondEdge->dump(indent+2); + } else if (type == Sequential) { + SequentialAnchorData *s = static_cast(this); + int kids = s->m_edges.count(); + qDebug("%*s type: sequential(%d):", indent, "", kids); + for (int i = 0; i < kids; ++i) { + s->m_edges.at(i)->dump(indent+2); + } + } else { + qDebug("%*s type: Normal:", indent, ""); + } +} + +#endif + +QSimplexConstraint *GraphPath::constraint(const GraphPath &path) const +{ + // Calculate + QSet cPositives; + QSet cNegatives; + QSet intersection; + + cPositives = positives + path.negatives; + cNegatives = negatives + path.positives; + + intersection = cPositives & cNegatives; + + cPositives -= intersection; + cNegatives -= intersection; + + // Fill + QSimplexConstraint *c = new QSimplexConstraint; + QSet::iterator i; + for (i = cPositives.begin(); i != cPositives.end(); ++i) + c->variables.insert(*i, 1.0); + + for (i = cNegatives.begin(); i != cNegatives.end(); ++i) + c->variables.insert(*i, -1.0); + + return c; +} + +#ifdef QT_DEBUG +QString GraphPath::toString() const +{ + QString string(QLatin1String("Path: ")); + foreach(AnchorData *edge, positives) + string += QString::fromAscii(" (+++) %1").arg(edge->toString()); + + foreach(AnchorData *edge, negatives) + string += QString::fromAscii(" (---) %1").arg(edge->toString()); + + return string; +} +#endif + +QGraphicsAnchorLayoutPrivate::QGraphicsAnchorLayoutPrivate() + : calculateGraphCacheDirty(true), styleInfoDirty(true) +{ + for (int i = 0; i < NOrientations; ++i) { + for (int j = 0; j < 3; ++j) { + sizeHints[i][j] = -1; + } + interpolationProgress[i] = -1; + + spacings[i] = -1; + graphHasConflicts[i] = false; + + layoutFirstVertex[i] = 0; + layoutCentralVertex[i] = 0; + layoutLastVertex[i] = 0; + } +} + +Qt::AnchorPoint QGraphicsAnchorLayoutPrivate::oppositeEdge(Qt::AnchorPoint edge) +{ + switch (edge) { + case Qt::AnchorLeft: + edge = Qt::AnchorRight; + break; + case Qt::AnchorRight: + edge = Qt::AnchorLeft; + break; + case Qt::AnchorTop: + edge = Qt::AnchorBottom; + break; + case Qt::AnchorBottom: + edge = Qt::AnchorTop; + break; + default: + break; + } + return edge; +} + + +/*! + * \internal + * + * helper function in order to avoid overflowing anchor sizes + * the returned size will never be larger than FLT_MAX + * + */ +inline static qreal checkAdd(qreal a, qreal b) +{ + if (FLT_MAX - b < a) + return FLT_MAX; + return a + b; +} + +/*! + \internal + + Adds \a newAnchor to the graph. + + Returns the newAnchor itself if it could be added without further changes to the graph. If a + new parallel anchor had to be created, then returns the new parallel anchor. If a parallel anchor + had to be created and it results in an unfeasible setup, \a feasible is set to false, otherwise + true. + + Note that in the case a new parallel anchor is created, it might also take over some constraints + from its children anchors. +*/ +AnchorData *QGraphicsAnchorLayoutPrivate::addAnchorMaybeParallel(AnchorData *newAnchor, bool *feasible) +{ + Orientation orientation = Orientation(newAnchor->orientation); + Graph &g = graph[orientation]; + *feasible = true; + + // If already exists one anchor where newAnchor is supposed to be, we create a parallel + // anchor. + if (AnchorData *oldAnchor = g.takeEdge(newAnchor->from, newAnchor->to)) { + ParallelAnchorData *parallel = new ParallelAnchorData(oldAnchor, newAnchor); + + // The parallel anchor will "replace" its children anchors in + // every center constraint that they appear. + + // ### If the dependent (center) anchors had reference(s) to their constraints, we + // could avoid traversing all the itemCenterConstraints. + QList &constraints = itemCenterConstraints[orientation]; + + AnchorData *children[2] = { oldAnchor, newAnchor }; + QList *childrenConstraints[2] = { ¶llel->m_firstConstraints, + ¶llel->m_secondConstraints }; + + for (int i = 0; i < 2; ++i) { + AnchorData *child = children[i]; + QList *childConstraints = childrenConstraints[i]; + + // We need to fix the second child constraints if the parallel group will have the + // opposite direction of the second child anchor. For the point of view of external + // entities, this anchor was reversed. So if at some point we say that the parallel + // has a value of 20, this mean that the second child (when reversed) will be + // assigned -20. + const bool needsReverse = i == 1 && !parallel->secondForward(); + + if (!child->isCenterAnchor) + continue; + + parallel->isCenterAnchor = true; + + for (int j = 0; j < constraints.count(); ++j) { + QSimplexConstraint *c = constraints[j]; + if (c->variables.contains(child)) { + childConstraints->append(c); + qreal v = c->variables.take(child); + if (needsReverse) + v *= -1; + c->variables.insert(parallel, v); + } + } + } + + // At this point we can identify that the parallel anchor is not feasible, e.g. one + // anchor minimum size is bigger than the other anchor maximum size. + *feasible = parallel->calculateSizeHints(); + newAnchor = parallel; + } + + g.createEdge(newAnchor->from, newAnchor->to, newAnchor); + return newAnchor; +} + +/*! + \internal + + Takes the sequence of vertices described by (\a before, \a vertices, \a after) and removes + all anchors connected to the vertices in \a vertices, returning one simplified anchor between + \a before and \a after. + + Note that this function doesn't add the created anchor to the graph. This should be done by + the caller. +*/ +static AnchorData *createSequence(Graph *graph, + AnchorVertex *before, + const QVector &vertices, + AnchorVertex *after) +{ +#if defined(QT_DEBUG) && 0 + QString strVertices; + for (int i = 0; i < vertices.count(); ++i) { + strVertices += QString::fromAscii("%1 - ").arg(vertices.at(i)->toString()); + } + QString strPath = QString::fromAscii("%1 - %2%3").arg(before->toString(), strVertices, after->toString()); + qDebug("simplifying [%s] to [%s - %s]", qPrintable(strPath), qPrintable(before->toString()), qPrintable(after->toString())); +#endif + + AnchorVertex *prev = before; + QVector edges; + + // Take from the graph, the edges that will be simplificated + for (int i = 0; i < vertices.count(); ++i) { + AnchorVertex *next = vertices.at(i); + AnchorData *ad = graph->takeEdge(prev, next); + Q_ASSERT(ad); + edges.append(ad); + prev = next; + } + + // Take the last edge (not covered in the loop above) + AnchorData *ad = graph->takeEdge(vertices.last(), after); + Q_ASSERT(ad); + edges.append(ad); + + // Create sequence + SequentialAnchorData *sequence = new SequentialAnchorData(vertices, edges); + sequence->from = before; + sequence->to = after; + + sequence->calculateSizeHints(); + + return sequence; +} + +/*! + \internal + + The purpose of this function is to simplify the graph. + Simplification serves two purposes: + 1. Reduce the number of edges in the graph, (thus the number of variables to the equation + solver is reduced, and the solver performs better). + 2. Be able to do distribution of sequences of edges more intelligently (esp. with sequential + anchors) + + It is essential that it must be possible to restore simplified anchors back to their "original" + form. This is done by restoreSimplifiedAnchor(). + + There are two types of simplification that can be done: + 1. Sequential simplification + Sequential simplification means that all sequences of anchors will be merged into one single + anchor. Only anhcors that points in the same direction will be merged. + 2. Parallel simplification + If a simplified sequential anchor is about to be inserted between two vertices in the graph + and there already exist an anchor between those two vertices, a parallel anchor will be + created that serves as a placeholder for the sequential anchor and the anchor that was + already between the two vertices. + + The process of simplification can be described as: + + 1. Simplify all sequences of anchors into one anchor. + If no further simplification was done, go to (3) + - If there already exist an anchor where the sequential anchor is supposed to be inserted, + take that anchor out of the graph + - Then create a parallel anchor that holds the sequential anchor and the anchor just taken + out of the graph. + 2. Go to (1) + 3. Done + + When creating the parallel anchors, the algorithm might identify unfeasible situations. In this + case the simplification process stops and returns false. Otherwise returns true. +*/ +bool QGraphicsAnchorLayoutPrivate::simplifyGraph(Orientation orientation) +{ + if (items.isEmpty()) + return true; + +#if defined(QT_DEBUG) && 0 + qDebug("Simplifying Graph for %s", + orientation == Horizontal ? "Horizontal" : "Vertical"); + + static int count = 0; + if (orientation == Horizontal) { + count++; + dumpGraph(QString::fromAscii("%1-full").arg(count)); + } +#endif + + // Vertex simplification + if (!simplifyVertices(orientation)) { + restoreVertices(orientation); + return false; + } + + // Anchor simplification + bool dirty; + bool feasible = true; + do { + dirty = simplifyGraphIteration(orientation, &feasible); + } while (dirty && feasible); + + // Note that if we are not feasible, we fallback and make sure that the graph is fully restored + if (!feasible) { + restoreSimplifiedGraph(orientation); + restoreVertices(orientation); + return false; + } + +#if defined(QT_DEBUG) && 0 + dumpGraph(QString::fromAscii("%1-simplified-%2").arg(count).arg( + QString::fromAscii(orientation == Horizontal ? "Horizontal" : "Vertical"))); +#endif + + return true; +} + +static AnchorVertex *replaceVertex_helper(AnchorData *data, AnchorVertex *oldV, AnchorVertex *newV) +{ + AnchorVertex *other; + if (data->from == oldV) { + data->from = newV; + other = data->to; + } else { + data->to = newV; + other = data->from; + } + return other; +} + +bool QGraphicsAnchorLayoutPrivate::replaceVertex(Orientation orientation, AnchorVertex *oldV, + AnchorVertex *newV, const QList &edges) +{ + Graph &g = graph[orientation]; + bool feasible = true; + + for (int i = 0; i < edges.count(); ++i) { + AnchorData *ad = edges[i]; + AnchorVertex *otherV = replaceVertex_helper(ad, oldV, newV); + +#if defined(QT_DEBUG) + ad->name = QString::fromAscii("%1 --to--> %2").arg(ad->from->toString()).arg(ad->to->toString()); +#endif + + bool newFeasible; + AnchorData *newAnchor = addAnchorMaybeParallel(ad, &newFeasible); + feasible &= newFeasible; + + if (newAnchor != ad) { + // A parallel was created, we mark that in the list of anchors created by vertex + // simplification. This is needed because we want to restore them in a separate step + // from the restoration of anchor simplification. + anchorsFromSimplifiedVertices[orientation].append(newAnchor); + } + + g.takeEdge(oldV, otherV); + } + + return feasible; +} + +/*! + \internal +*/ +bool QGraphicsAnchorLayoutPrivate::simplifyVertices(Orientation orientation) +{ + Q_Q(QGraphicsAnchorLayout); + Graph &g = graph[orientation]; + + // We'll walk through vertices + QStack stack; + stack.push(layoutFirstVertex[orientation]); + QSet visited; + + while (!stack.isEmpty()) { + AnchorVertex *v = stack.pop(); + visited.insert(v); + + // Each adjacent of 'v' is a possible vertex to be merged. So we traverse all of + // them. Since once a merge is made, we might add new adjacents, and we don't want to + // pass two times through one adjacent. The 'index' is used to track our position. + QList adjacents = g.adjacentVertices(v); + int index = 0; + + while (index < adjacents.count()) { + AnchorVertex *next = adjacents.at(index); + index++; + + AnchorData *data = g.edgeData(v, next); + const bool bothLayoutVertices = v->m_item == q && next->m_item == q; + const bool zeroSized = !data->minSize && !data->maxSize; + + if (!bothLayoutVertices && zeroSized) { + + // Create a new vertex pair, note that we keep a list of those vertices so we can + // easily process them when restoring the graph. + AnchorVertexPair *newV = new AnchorVertexPair(v, next, data); + simplifiedVertices[orientation].append(newV); + + // Collect the anchors of both vertices, the new vertex pair will take their place + // in those anchors + const QList &vAdjacents = g.adjacentVertices(v); + const QList &nextAdjacents = g.adjacentVertices(next); + + for (int i = 0; i < vAdjacents.count(); ++i) { + AnchorVertex *adjacent = vAdjacents.at(i); + if (adjacent != next) { + AnchorData *ad = g.edgeData(v, adjacent); + newV->m_firstAnchors.append(ad); + } + } + + for (int i = 0; i < nextAdjacents.count(); ++i) { + AnchorVertex *adjacent = nextAdjacents.at(i); + if (adjacent != v) { + AnchorData *ad = g.edgeData(next, adjacent); + newV->m_secondAnchors.append(ad); + + // We'll also add new vertices to the adjacent list of the new 'v', to be + // created as a vertex pair and replace the current one. + if (!adjacents.contains(adjacent)) + adjacents.append(adjacent); + } + } + + // ### merge this loop into the ones that calculated m_firstAnchors/m_secondAnchors? + // Make newV take the place of v and next + bool feasible = replaceVertex(orientation, v, newV, newV->m_firstAnchors); + feasible &= replaceVertex(orientation, next, newV, newV->m_secondAnchors); + + // Update the layout vertex information if one of the vertices is a layout vertex. + AnchorVertex *layoutVertex = 0; + if (v->m_item == q) + layoutVertex = v; + else if (next->m_item == q) + layoutVertex = next; + + if (layoutVertex) { + // Layout vertices always have m_item == q... + newV->m_item = q; + changeLayoutVertex(orientation, layoutVertex, newV); + } + + g.takeEdge(v, next); + + // If a non-feasibility is found, we leave early and cancel the simplification + if (!feasible) + return false; + + v = newV; + visited.insert(newV); + + } else if (!visited.contains(next) && !stack.contains(next)) { + // If the adjacent is not fit for merge and it wasn't visited by the outermost + // loop, we add it to the stack. + stack.push(next); + } + } + } + + return true; +} + +/*! + \internal + + One iteration of the simplification algorithm. Returns true if another iteration is needed. + + The algorithm walks the graph in depth-first order, and only collects vertices that has two + edges connected to it. If the vertex does not have two edges or if it is a layout edge, it + will take all the previously collected vertices and try to create a simplified sequential + anchor representing all the previously collected vertices. Once the simplified anchor is + inserted, the collected list is cleared in order to find the next sequence to simplify. + + Note that there are some catches to this that are not covered by the above explanation, see + the function comments for more details. +*/ +bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(QGraphicsAnchorLayoutPrivate::Orientation orientation, + bool *feasible) +{ + Q_Q(QGraphicsAnchorLayout); + Graph &g = graph[orientation]; + + QSet visited; + QStack > stack; + stack.push(qMakePair(static_cast(0), layoutFirstVertex[orientation])); + QVector candidates; + + // Walk depth-first, in the stack we store start of the candidate sequence (beforeSequence) + // and the vertex to be visited. + while (!stack.isEmpty()) { + QPair pair = stack.pop(); + AnchorVertex *beforeSequence = pair.first; + AnchorVertex *v = pair.second; + + // The basic idea is to determine whether we found an end of sequence, + // if that's the case, we stop adding vertices to the candidate list + // and do a simplification step. + // + // A vertex can trigger an end of sequence if + // (a) it is a layout vertex, we don't simplify away the layout vertices; + // (b) it does not have exactly 2 adjacents; + // (c) its next adjacent is already visited (a cycle in the graph). + // (d) the next anchor is a center anchor. + + const QList &adjacents = g.adjacentVertices(v); + const bool isLayoutVertex = v->m_item == q; + AnchorVertex *afterSequence = v; + bool endOfSequence = false; + + // + // Identify the end cases. + // + + // Identifies cases (a) and (b) + endOfSequence = isLayoutVertex || adjacents.count() != 2; + + if (!endOfSequence) { + // This is a tricky part. We peek at the next vertex to find out whether + // + // - we already visited the next vertex (c); + // - the next anchor is a center (d). + // + // Those are needed to identify the remaining end of sequence cases. Note that unlike + // (a) and (b), we preempt the end of sequence by looking into the next vertex. + + // Peek at the next vertex + AnchorVertex *after; + if (candidates.isEmpty()) + after = (beforeSequence == adjacents.last() ? adjacents.first() : adjacents.last()); + else + after = (candidates.last() == adjacents.last() ? adjacents.first() : adjacents.last()); + + // ### At this point we assumed that candidates will not contain 'after', this may not hold + // when simplifying FLOATing anchors. + Q_ASSERT(!candidates.contains(after)); + + const AnchorData *data = g.edgeData(v, after); + Q_ASSERT(data); + const bool cycleFound = visited.contains(after); + + // Now cases (c) and (d)... + endOfSequence = cycleFound || data->isCenterAnchor; + + if (!endOfSequence) { + // If it's not an end of sequence, then the vertex didn't trigger neither of the + // previously three cases, so it can be added to the candidates list. + candidates.append(v); + } else if (cycleFound && (beforeSequence != after)) { + afterSequence = after; + candidates.append(v); + } + } + + // + // Add next non-visited vertices to the stack. + // + for (int i = 0; i < adjacents.count(); ++i) { + AnchorVertex *next = adjacents.at(i); + if (visited.contains(next)) + continue; + + // If current vertex is an end of sequence, and it'll reset the candidates list. So + // the next vertices will build candidates lists with the current vertex as 'before' + // vertex. If it's not an end of sequence, we keep the original 'before' vertex, + // since we are keeping the candidates list. + if (endOfSequence) + stack.push(qMakePair(v, next)); + else + stack.push(qMakePair(beforeSequence, next)); + } + + visited.insert(v); + + if (!endOfSequence || candidates.isEmpty()) + continue; + + // + // Create a sequence for (beforeSequence, candidates, afterSequence). + // + + // One restriction we have is to not simplify half of an anchor and let the other half + // unsimplified. So we remove center edges before and after the sequence. + const AnchorData *firstAnchor = g.edgeData(beforeSequence, candidates.first()); + if (firstAnchor->isCenterAnchor) { + beforeSequence = candidates.first(); + candidates.remove(0); + + // If there's not candidates to be simplified, leave. + if (candidates.isEmpty()) + continue; + } + + const AnchorData *lastAnchor = g.edgeData(candidates.last(), afterSequence); + if (lastAnchor->isCenterAnchor) { + afterSequence = candidates.last(); + candidates.remove(candidates.count() - 1); + + if (candidates.isEmpty()) + continue; + } + + // + // Add the sequence to the graph. + // + + AnchorData *sequence = createSequence(&g, beforeSequence, candidates, afterSequence); + + // If 'beforeSequence' and 'afterSequence' already had an anchor between them, we'll + // create a parallel anchor between the new sequence and the old anchor. + bool newFeasible; + AnchorData *newAnchor = addAnchorMaybeParallel(sequence, &newFeasible); + + if (!newFeasible) { + *feasible = false; + return false; + } + + // When a new parallel anchor is create in the graph, we finish the iteration and return + // true to indicate a new iteration is needed. This happens because a parallel anchor + // changes the number of adjacents one vertex has, possibly opening up oportunities for + // building candidate lists (when adjacents == 2). + if (newAnchor != sequence) + return true; + + // If there was no parallel simplification, we'll keep walking the graph. So we clear the + // candidates list to start again. + candidates.clear(); + } + + return false; +} + +void QGraphicsAnchorLayoutPrivate::restoreSimplifiedAnchor(AnchorData *edge) +{ +#if 0 + static const char *anchortypes[] = {"Normal", + "Sequential", + "Parallel"}; + qDebug("Restoring %s edge.", anchortypes[int(edge->type)]); +#endif + + Graph &g = graph[edge->orientation]; + + if (edge->type == AnchorData::Normal) { + g.createEdge(edge->from, edge->to, edge); + + } else if (edge->type == AnchorData::Sequential) { + SequentialAnchorData *sequence = static_cast(edge); + + for (int i = 0; i < sequence->m_edges.count(); ++i) { + AnchorData *data = sequence->m_edges.at(i); + restoreSimplifiedAnchor(data); + } + + delete sequence; + + } else if (edge->type == AnchorData::Parallel) { + + // Skip parallel anchors that were created by vertex simplification, they will be processed + // later, when restoring vertex simplification. + // ### we could improve this check bit having a bit inside 'edge' + if (anchorsFromSimplifiedVertices[edge->orientation].contains(edge)) + return; + + ParallelAnchorData* parallel = static_cast(edge); + restoreSimplifiedConstraints(parallel); + + // ### Because of the way parallel anchors are created in the anchor simplification + // algorithm, we know that one of these will be a sequence, so it'll be safe if the other + // anchor create an edge between the same vertices as the parallel. + Q_ASSERT(parallel->firstEdge->type == AnchorData::Sequential + || parallel->secondEdge->type == AnchorData::Sequential); + restoreSimplifiedAnchor(parallel->firstEdge); + restoreSimplifiedAnchor(parallel->secondEdge); + + delete parallel; + } +} + +void QGraphicsAnchorLayoutPrivate::restoreSimplifiedConstraints(ParallelAnchorData *parallel) +{ + if (!parallel->isCenterAnchor) + return; + + for (int i = 0; i < parallel->m_firstConstraints.count(); ++i) { + QSimplexConstraint *c = parallel->m_firstConstraints.at(i); + qreal v = c->variables[parallel]; + c->variables.remove(parallel); + c->variables.insert(parallel->firstEdge, v); + } + + // When restoring, we might have to revert constraints back. See comments on + // addAnchorMaybeParallel(). + const bool needsReverse = !parallel->secondForward(); + + for (int i = 0; i < parallel->m_secondConstraints.count(); ++i) { + QSimplexConstraint *c = parallel->m_secondConstraints.at(i); + qreal v = c->variables[parallel]; + if (needsReverse) + v *= -1; + c->variables.remove(parallel); + c->variables.insert(parallel->secondEdge, v); + } +} + +void QGraphicsAnchorLayoutPrivate::restoreSimplifiedGraph(Orientation orientation) +{ +#if 0 + qDebug("Restoring Simplified Graph for %s", + orientation == Horizontal ? "Horizontal" : "Vertical"); +#endif + + // Restore anchor simplification + Graph &g = graph[orientation]; + QList > connections = g.connections(); + for (int i = 0; i < connections.count(); ++i) { + AnchorVertex *v1 = connections.at(i).first; + AnchorVertex *v2 = connections.at(i).second; + AnchorData *edge = g.edgeData(v1, v2); + + // We restore only sequential anchors and parallels that were not created by + // vertex simplification. + if (edge->type == AnchorData::Sequential + || (edge->type == AnchorData::Parallel && + !anchorsFromSimplifiedVertices[orientation].contains(edge))) { + + g.takeEdge(v1, v2); + restoreSimplifiedAnchor(edge); + } + } + + restoreVertices(orientation); +} + +void QGraphicsAnchorLayoutPrivate::restoreVertices(Orientation orientation) +{ + Q_Q(QGraphicsAnchorLayout); + + Graph &g = graph[orientation]; + QList &toRestore = simplifiedVertices[orientation]; + + // Since we keep a list of parallel anchors and vertices that were created during vertex + // simplification, we can now iterate on those lists instead of traversing the graph + // recursively. + + // First, restore the constraints changed when we created parallel anchors. Note that this + // works at this point because the constraints doesn't depend on vertex information and at + // this point it's always safe to identify whether the second child is forward or backwards. + // In the next step, we'll change the anchors vertices so that would not be possible anymore. + QList ¶llelAnchors = anchorsFromSimplifiedVertices[orientation]; + + for (int i = parallelAnchors.count() - 1; i >= 0; --i) { + ParallelAnchorData *parallel = static_cast(parallelAnchors.at(i)); + restoreSimplifiedConstraints(parallel); + } + + // Then, we will restore the vertices in the inverse order of creation, this way we ensure that + // the vertex being restored was not wrapped by another simplification. + for (int i = toRestore.count() - 1; i >= 0; --i) { + AnchorVertexPair *pair = toRestore.at(i); + QList adjacents = g.adjacentVertices(pair); + + // Restore the removed edge, this will also restore both vertices 'first' and 'second' to + // the graph structure. + AnchorVertex *first = pair->m_first; + AnchorVertex *second = pair->m_second; + g.createEdge(first, second, pair->m_removedAnchor); + + // Restore the anchors for the first child vertex + for (int j = 0; j < pair->m_firstAnchors.count(); ++j) { + AnchorData *ad = pair->m_firstAnchors.at(j); + Q_ASSERT(ad->from == pair || ad->to == pair); + + replaceVertex_helper(ad, pair, first); + g.createEdge(ad->from, ad->to, ad); + } + + // Restore the anchors for the second child vertex + for (int j = 0; j < pair->m_secondAnchors.count(); ++j) { + AnchorData *ad = pair->m_secondAnchors.at(j); + Q_ASSERT(ad->from == pair || ad->to == pair); + + replaceVertex_helper(ad, pair, second); + g.createEdge(ad->from, ad->to, ad); + } + + for (int j = 0; j < adjacents.count(); ++j) { + g.takeEdge(pair, adjacents.at(j)); + } + + // The pair simplified a layout vertex, so place back the correct vertex in the variable + // that track layout vertices + if (pair->m_item == q) { + AnchorVertex *layoutVertex = first->m_item == q ? first : second; + Q_ASSERT(layoutVertex->m_item == q); + changeLayoutVertex(orientation, pair, layoutVertex); + } + + delete pair; + } + qDeleteAll(parallelAnchors); + parallelAnchors.clear(); + toRestore.clear(); +} + +QGraphicsAnchorLayoutPrivate::Orientation +QGraphicsAnchorLayoutPrivate::edgeOrientation(Qt::AnchorPoint edge) +{ + return edge > Qt::AnchorRight ? Vertical : Horizontal; +} + +/*! + \internal + + Create internal anchors to connect the layout edges (Left to Right and + Top to Bottom). + + These anchors doesn't have size restrictions, that will be enforced by + other anchors and items in the layout. +*/ +void QGraphicsAnchorLayoutPrivate::createLayoutEdges() +{ + Q_Q(QGraphicsAnchorLayout); + QGraphicsLayoutItem *layout = q; + + // Horizontal + AnchorData *data = new AnchorData; + addAnchor_helper(layout, Qt::AnchorLeft, layout, + Qt::AnchorRight, data); + data->maxSize = QWIDGETSIZE_MAX; + + // Save a reference to layout vertices + layoutFirstVertex[Horizontal] = internalVertex(layout, Qt::AnchorLeft); + layoutCentralVertex[Horizontal] = 0; + layoutLastVertex[Horizontal] = internalVertex(layout, Qt::AnchorRight); + + // Vertical + data = new AnchorData; + addAnchor_helper(layout, Qt::AnchorTop, layout, + Qt::AnchorBottom, data); + data->maxSize = QWIDGETSIZE_MAX; + + // Save a reference to layout vertices + layoutFirstVertex[Vertical] = internalVertex(layout, Qt::AnchorTop); + layoutCentralVertex[Vertical] = 0; + layoutLastVertex[Vertical] = internalVertex(layout, Qt::AnchorBottom); +} + +void QGraphicsAnchorLayoutPrivate::deleteLayoutEdges() +{ + Q_Q(QGraphicsAnchorLayout); + + Q_ASSERT(!internalVertex(q, Qt::AnchorHorizontalCenter)); + Q_ASSERT(!internalVertex(q, Qt::AnchorVerticalCenter)); + + removeAnchor_helper(internalVertex(q, Qt::AnchorLeft), + internalVertex(q, Qt::AnchorRight)); + removeAnchor_helper(internalVertex(q, Qt::AnchorTop), + internalVertex(q, Qt::AnchorBottom)); +} + +void QGraphicsAnchorLayoutPrivate::createItemEdges(QGraphicsLayoutItem *item) +{ + items.append(item); + + // Create horizontal and vertical internal anchors for the item and + // refresh its size hint / policy values. + AnchorData *data = new AnchorData; + addAnchor_helper(item, Qt::AnchorLeft, item, Qt::AnchorRight, data); + data->refreshSizeHints(); + + data = new AnchorData; + addAnchor_helper(item, Qt::AnchorTop, item, Qt::AnchorBottom, data); + data->refreshSizeHints(); +} + +/*! + \internal + + By default, each item in the layout is represented internally as + a single anchor in each direction. For instance, from Left to Right. + + However, to support anchorage of items to the center of items, we + must split this internal anchor into two half-anchors. From Left + to Center and then from Center to Right, with the restriction that + these anchors must have the same time at all times. +*/ +void QGraphicsAnchorLayoutPrivate::createCenterAnchors( + QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge) +{ + Q_Q(QGraphicsAnchorLayout); + + Orientation orientation; + switch (centerEdge) { + case Qt::AnchorHorizontalCenter: + orientation = Horizontal; + break; + case Qt::AnchorVerticalCenter: + orientation = Vertical; + break; + default: + // Don't create center edges unless needed + return; + } + + // Check if vertex already exists + if (internalVertex(item, centerEdge)) + return; + + // Orientation code + Qt::AnchorPoint firstEdge; + Qt::AnchorPoint lastEdge; + + if (orientation == Horizontal) { + firstEdge = Qt::AnchorLeft; + lastEdge = Qt::AnchorRight; + } else { + firstEdge = Qt::AnchorTop; + lastEdge = Qt::AnchorBottom; + } + + AnchorVertex *first = internalVertex(item, firstEdge); + AnchorVertex *last = internalVertex(item, lastEdge); + Q_ASSERT(first && last); + + // Create new anchors + QSimplexConstraint *c = new QSimplexConstraint; + + AnchorData *data = new AnchorData; + c->variables.insert(data, 1.0); + addAnchor_helper(item, firstEdge, item, centerEdge, data); + data->isCenterAnchor = true; + data->dependency = AnchorData::Master; + data->refreshSizeHints(); + + data = new AnchorData; + c->variables.insert(data, -1.0); + addAnchor_helper(item, centerEdge, item, lastEdge, data); + data->isCenterAnchor = true; + data->dependency = AnchorData::Slave; + data->refreshSizeHints(); + + itemCenterConstraints[orientation].append(c); + + // Remove old one + removeAnchor_helper(first, last); + + if (item == q) { + layoutCentralVertex[orientation] = internalVertex(q, centerEdge); + } +} + +void QGraphicsAnchorLayoutPrivate::removeCenterAnchors( + QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge, + bool substitute) +{ + Q_Q(QGraphicsAnchorLayout); + + Orientation orientation; + switch (centerEdge) { + case Qt::AnchorHorizontalCenter: + orientation = Horizontal; + break; + case Qt::AnchorVerticalCenter: + orientation = Vertical; + break; + default: + // Don't remove edges that not the center ones + return; + } + + // Orientation code + Qt::AnchorPoint firstEdge; + Qt::AnchorPoint lastEdge; + + if (orientation == Horizontal) { + firstEdge = Qt::AnchorLeft; + lastEdge = Qt::AnchorRight; + } else { + firstEdge = Qt::AnchorTop; + lastEdge = Qt::AnchorBottom; + } + + AnchorVertex *center = internalVertex(item, centerEdge); + if (!center) + return; + AnchorVertex *first = internalVertex(item, firstEdge); + + Q_ASSERT(first); + Q_ASSERT(center); + + Graph &g = graph[orientation]; + + + AnchorData *oldData = g.edgeData(first, center); + // Remove center constraint + for (int i = itemCenterConstraints[orientation].count() - 1; i >= 0; --i) { + if (itemCenterConstraints[orientation].at(i)->variables.contains(oldData)) { + delete itemCenterConstraints[orientation].takeAt(i); + break; + } + } + + if (substitute) { + // Create the new anchor that should substitute the left-center-right anchors. + AnchorData *data = new AnchorData; + addAnchor_helper(item, firstEdge, item, lastEdge, data); + data->refreshSizeHints(); + + // Remove old anchors + removeAnchor_helper(first, center); + removeAnchor_helper(center, internalVertex(item, lastEdge)); + + } else { + // this is only called from removeAnchors() + // first, remove all non-internal anchors + QList adjacents = g.adjacentVertices(center); + for (int i = 0; i < adjacents.count(); ++i) { + AnchorVertex *v = adjacents.at(i); + if (v->m_item != item) { + removeAnchor_helper(center, internalVertex(v->m_item, v->m_edge)); + } + } + // when all non-internal anchors is removed it will automatically merge the + // center anchor into a left-right (or top-bottom) anchor. We must also delete that. + // by this time, the center vertex is deleted and merged into a non-centered internal anchor + removeAnchor_helper(first, internalVertex(item, lastEdge)); + } + + if (item == q) { + layoutCentralVertex[orientation] = 0; + } +} + + +void QGraphicsAnchorLayoutPrivate::removeCenterConstraints(QGraphicsLayoutItem *item, + Orientation orientation) +{ + // Remove the item center constraints associated to this item + // ### This is a temporary solution. We should probably use a better + // data structure to hold items and/or their associated constraints + // so that we can remove those easily + + AnchorVertex *first = internalVertex(item, orientation == Horizontal ? + Qt::AnchorLeft : + Qt::AnchorTop); + AnchorVertex *center = internalVertex(item, orientation == Horizontal ? + Qt::AnchorHorizontalCenter : + Qt::AnchorVerticalCenter); + + // Skip if no center constraints exist + if (!center) + return; + + Q_ASSERT(first); + AnchorData *internalAnchor = graph[orientation].edgeData(first, center); + + // Look for our anchor in all item center constraints, then remove it + for (int i = 0; i < itemCenterConstraints[orientation].size(); ++i) { + if (itemCenterConstraints[orientation].at(i)->variables.contains(internalAnchor)) { + delete itemCenterConstraints[orientation].takeAt(i); + break; + } + } +} + +/*! + * \internal + * Implements the high level "addAnchor" feature. Called by the public API + * addAnchor method. + * + * The optional \a spacing argument defines the size of the anchor. If not provided, + * the anchor size is either 0 or not-set, depending on type of anchor created (see + * matrix below). + * + * All anchors that remain with size not-set will assume the standard spacing, + * set either by the layout style or through the "setSpacing" layout API. + */ +QGraphicsAnchor *QGraphicsAnchorLayoutPrivate::addAnchor(QGraphicsLayoutItem *firstItem, + Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, + Qt::AnchorPoint secondEdge, + qreal *spacing) +{ + Q_Q(QGraphicsAnchorLayout); + if ((firstItem == 0) || (secondItem == 0)) { + qWarning("QGraphicsAnchorLayout::addAnchor(): " + "Cannot anchor NULL items"); + return 0; + } + + if (firstItem == secondItem) { + qWarning("QGraphicsAnchorLayout::addAnchor(): " + "Cannot anchor the item to itself"); + return 0; + } + + if (edgeOrientation(secondEdge) != edgeOrientation(firstEdge)) { + qWarning("QGraphicsAnchorLayout::addAnchor(): " + "Cannot anchor edges of different orientations"); + return 0; + } + + const QGraphicsLayoutItem *parentWidget = q->parentLayoutItem(); + if (firstItem == parentWidget || secondItem == parentWidget) { + qWarning("QGraphicsAnchorLayout::addAnchor(): " + "You cannot add the parent of the layout to the layout."); + return 0; + } + + // In QGraphicsAnchorLayout, items are represented in its internal + // graph as four anchors that connect: + // - Left -> HCenter + // - HCenter-> Right + // - Top -> VCenter + // - VCenter -> Bottom + + // Ensure that the internal anchors have been created for both items. + if (firstItem != q && !items.contains(firstItem)) { + createItemEdges(firstItem); + addChildLayoutItem(firstItem); + } + if (secondItem != q && !items.contains(secondItem)) { + createItemEdges(secondItem); + addChildLayoutItem(secondItem); + } + + // Create center edges if needed + createCenterAnchors(firstItem, firstEdge); + createCenterAnchors(secondItem, secondEdge); + + // Use heuristics to find out what the user meant with this anchor. + correctEdgeDirection(firstItem, firstEdge, secondItem, secondEdge); + + AnchorData *data = new AnchorData; + QGraphicsAnchor *graphicsAnchor = acquireGraphicsAnchor(data); + + addAnchor_helper(firstItem, firstEdge, secondItem, secondEdge, data); + + if (spacing) { + graphicsAnchor->setSpacing(*spacing); + } else { + // If firstItem or secondItem is the layout itself, the spacing will default to 0. + // Otherwise, the following matrix is used (questionmark means that the spacing + // is queried from the style): + // from + // to Left HCenter Right + // Left 0 0 ? + // HCenter 0 0 0 + // Right ? 0 0 + if (firstItem == q + || secondItem == q + || pickEdge(firstEdge, Horizontal) == Qt::AnchorHorizontalCenter + || oppositeEdge(firstEdge) != secondEdge) { + graphicsAnchor->setSpacing(0); + } else { + graphicsAnchor->unsetSpacing(); + } + } + + return graphicsAnchor; +} + +/* + \internal + + This method adds an AnchorData to the internal graph. It is responsible for doing + the boilerplate part of such task. + + If another AnchorData exists between the mentioned vertices, it is deleted and + the new one is inserted. +*/ +void QGraphicsAnchorLayoutPrivate::addAnchor_helper(QGraphicsLayoutItem *firstItem, + Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, + Qt::AnchorPoint secondEdge, + AnchorData *data) +{ + Q_Q(QGraphicsAnchorLayout); + + const Orientation orientation = edgeOrientation(firstEdge); + + // Create or increase the reference count for the related vertices. + AnchorVertex *v1 = addInternalVertex(firstItem, firstEdge); + AnchorVertex *v2 = addInternalVertex(secondItem, secondEdge); + + // Remove previous anchor + if (graph[orientation].edgeData(v1, v2)) { + removeAnchor_helper(v1, v2); + } + + // If its an internal anchor, set the associated item + if (firstItem == secondItem) + data->item = firstItem; + + data->orientation = orientation; + + // Create a bi-directional edge in the sense it can be transversed both + // from v1 or v2. "data" however is shared between the two references + // so we still know that the anchor direction is from 1 to 2. + data->from = v1; + data->to = v2; +#ifdef QT_DEBUG + data->name = QString::fromAscii("%1 --to--> %2").arg(v1->toString()).arg(v2->toString()); +#endif + // ### bit to track internal anchors, since inside AnchorData methods + // we don't have access to the 'q' pointer. + data->isLayoutAnchor = (data->item == q); + + graph[orientation].createEdge(v1, v2, data); +} + +QGraphicsAnchor *QGraphicsAnchorLayoutPrivate::getAnchor(QGraphicsLayoutItem *firstItem, + Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, + Qt::AnchorPoint secondEdge) +{ + // Do not expose internal anchors + if (firstItem == secondItem) + return 0; + + const Orientation orientation = edgeOrientation(firstEdge); + AnchorVertex *v1 = internalVertex(firstItem, firstEdge); + AnchorVertex *v2 = internalVertex(secondItem, secondEdge); + + QGraphicsAnchor *graphicsAnchor = 0; + + AnchorData *data = graph[orientation].edgeData(v1, v2); + if (data) { + // We could use "acquireGraphicsAnchor" here, but to avoid a regression where + // an internal anchor was wrongly exposed, I want to ensure no new + // QGraphicsAnchor instances are created by this call. + // This assumption must hold because anchors are either user-created (and already + // have their public object created), or they are internal (and must not reach + // this point). + Q_ASSERT(data->graphicsAnchor); + graphicsAnchor = data->graphicsAnchor; + } + return graphicsAnchor; +} + +/*! + * \internal + * + * Implements the high level "removeAnchor" feature. Called by + * the QAnchorData destructor. + */ +void QGraphicsAnchorLayoutPrivate::removeAnchor(AnchorVertex *firstVertex, + AnchorVertex *secondVertex) +{ + Q_Q(QGraphicsAnchorLayout); + + // Save references to items while it's safe to assume the vertices exist + QGraphicsLayoutItem *firstItem = firstVertex->m_item; + QGraphicsLayoutItem *secondItem = secondVertex->m_item; + + // Delete the anchor (may trigger deletion of center vertices) + removeAnchor_helper(firstVertex, secondVertex); + + // Ensure no dangling pointer is left behind + firstVertex = secondVertex = 0; + + // Checking if the item stays in the layout or not + bool keepFirstItem = false; + bool keepSecondItem = false; + + QPair v; + int refcount = -1; + + if (firstItem != q) { + for (int i = Qt::AnchorLeft; i <= Qt::AnchorBottom; ++i) { + v = m_vertexList.value(qMakePair(firstItem, static_cast(i))); + if (v.first) { + if (i == Qt::AnchorHorizontalCenter || i == Qt::AnchorVerticalCenter) + refcount = 2; + else + refcount = 1; + + if (v.second > refcount) { + keepFirstItem = true; + break; + } + } + } + } else + keepFirstItem = true; + + if (secondItem != q) { + for (int i = Qt::AnchorLeft; i <= Qt::AnchorBottom; ++i) { + v = m_vertexList.value(qMakePair(secondItem, static_cast(i))); + if (v.first) { + if (i == Qt::AnchorHorizontalCenter || i == Qt::AnchorVerticalCenter) + refcount = 2; + else + refcount = 1; + + if (v.second > refcount) { + keepSecondItem = true; + break; + } + } + } + } else + keepSecondItem = true; + + if (!keepFirstItem) + q->removeAt(items.indexOf(firstItem)); + + if (!keepSecondItem) + q->removeAt(items.indexOf(secondItem)); + + // Removing anchors invalidates the layout + q->invalidate(); +} + +/* + \internal + + Implements the low level "removeAnchor" feature. Called by + private methods. +*/ +void QGraphicsAnchorLayoutPrivate::removeAnchor_helper(AnchorVertex *v1, AnchorVertex *v2) +{ + Q_ASSERT(v1 && v2); + + // Remove edge from graph + const Orientation o = edgeOrientation(v1->m_edge); + graph[o].removeEdge(v1, v2); + + // Decrease vertices reference count (may trigger a deletion) + removeInternalVertex(v1->m_item, v1->m_edge); + removeInternalVertex(v2->m_item, v2->m_edge); +} + +AnchorVertex *QGraphicsAnchorLayoutPrivate::addInternalVertex(QGraphicsLayoutItem *item, + Qt::AnchorPoint edge) +{ + QPair pair(item, edge); + QPair v = m_vertexList.value(pair); + + if (!v.first) { + Q_ASSERT(v.second == 0); + v.first = new AnchorVertex(item, edge); + } + v.second++; + m_vertexList.insert(pair, v); + return v.first; +} + +/** + * \internal + * + * returns the AnchorVertex that was dereferenced, also when it was removed. + * returns 0 if it did not exist. + */ +void QGraphicsAnchorLayoutPrivate::removeInternalVertex(QGraphicsLayoutItem *item, + Qt::AnchorPoint edge) +{ + QPair pair(item, edge); + QPair v = m_vertexList.value(pair); + + if (!v.first) { + qWarning("This item with this edge is not in the graph"); + return; + } + + v.second--; + if (v.second == 0) { + // Remove reference and delete vertex + m_vertexList.remove(pair); + delete v.first; + } else { + // Update reference count + m_vertexList.insert(pair, v); + + if ((v.second == 2) && + ((edge == Qt::AnchorHorizontalCenter) || + (edge == Qt::AnchorVerticalCenter))) { + removeCenterAnchors(item, edge, true); + } + } +} + +void QGraphicsAnchorLayoutPrivate::removeVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge) +{ + if (AnchorVertex *v = internalVertex(item, edge)) { + Graph &g = graph[edgeOrientation(edge)]; + const QList allVertices = graph[edgeOrientation(edge)].adjacentVertices(v); + AnchorVertex *v2; + foreach (v2, allVertices) { + g.removeEdge(v, v2); + removeInternalVertex(item, edge); + removeInternalVertex(v2->m_item, v2->m_edge); + } + } +} + +void QGraphicsAnchorLayoutPrivate::removeAnchors(QGraphicsLayoutItem *item) +{ + // remove the center anchor first!! + removeCenterAnchors(item, Qt::AnchorHorizontalCenter, false); + removeVertex(item, Qt::AnchorLeft); + removeVertex(item, Qt::AnchorRight); + + removeCenterAnchors(item, Qt::AnchorVerticalCenter, false); + removeVertex(item, Qt::AnchorTop); + removeVertex(item, Qt::AnchorBottom); +} + +/*! + \internal + + Use heuristics to determine the correct orientation of a given anchor. + + After API discussions, we decided we would like expressions like + anchor(A, Left, B, Right) to mean the same as anchor(B, Right, A, Left). + The problem with this is that anchors could become ambiguous, for + instance, what does the anchor A, B of size X mean? + + "pos(B) = pos(A) + X" or "pos(A) = pos(B) + X" ? + + To keep the API user friendly and at the same time, keep our algorithm + deterministic, we use an heuristic to determine a direction for each + added anchor and then keep it. The heuristic is based on the fact + that people usually avoid overlapping items, therefore: + + "A, RIGHT to B, LEFT" means that B is to the LEFT of A. + "B, LEFT to A, RIGHT" is corrected to the above anchor. + + Special correction is also applied when one of the items is the + layout. We handle Layout Left as if it was another items's Right + and Layout Right as another item's Left. +*/ +void QGraphicsAnchorLayoutPrivate::correctEdgeDirection(QGraphicsLayoutItem *&firstItem, + Qt::AnchorPoint &firstEdge, + QGraphicsLayoutItem *&secondItem, + Qt::AnchorPoint &secondEdge) +{ + Q_Q(QGraphicsAnchorLayout); + + if ((firstItem != q) && (secondItem != q)) { + // If connection is between widgets (not the layout itself) + // Ensure that "right-edges" sit to the left of "left-edges". + if (firstEdge < secondEdge) { + qSwap(firstItem, secondItem); + qSwap(firstEdge, secondEdge); + } + } else if (firstItem == q) { + // If connection involves the right or bottom of a layout, ensure + // the layout is the second item. + if ((firstEdge == Qt::AnchorRight) || (firstEdge == Qt::AnchorBottom)) { + qSwap(firstItem, secondItem); + qSwap(firstEdge, secondEdge); + } + } else if ((secondEdge != Qt::AnchorRight) && (secondEdge != Qt::AnchorBottom)) { + // If connection involves the left, center or top of layout, ensure + // the layout is the first item. + qSwap(firstItem, secondItem); + qSwap(firstEdge, secondEdge); + } +} + +QLayoutStyleInfo &QGraphicsAnchorLayoutPrivate::styleInfo() const +{ + if (styleInfoDirty) { + Q_Q(const QGraphicsAnchorLayout); + //### Fix this if QGV ever gets support for Metal style or different Aqua sizes. + QWidget *wid = 0; + + QGraphicsLayoutItem *parent = q->parentLayoutItem(); + while (parent && parent->isLayout()) { + parent = parent->parentLayoutItem(); + } + QGraphicsWidget *w = 0; + if (parent) { + QGraphicsItem *parentItem = parent->graphicsItem(); + if (parentItem && parentItem->isWidget()) + w = static_cast(parentItem); + } + + QStyle *style = w ? w->style() : QApplication::style(); + cachedStyleInfo = QLayoutStyleInfo(style, wid); + cachedStyleInfo.setDefaultSpacing(Qt::Horizontal, spacings[0]); + cachedStyleInfo.setDefaultSpacing(Qt::Vertical, spacings[1]); + + styleInfoDirty = false; + } + return cachedStyleInfo; +} + +/*! + \internal + + Called on activation. Uses Linear Programming to define minimum, preferred + and maximum sizes for the layout. Also calculates the sizes that each item + should assume when the layout is in one of such situations. +*/ +void QGraphicsAnchorLayoutPrivate::calculateGraphs() +{ + if (!calculateGraphCacheDirty) + return; + calculateGraphs(Horizontal); + calculateGraphs(Vertical); + calculateGraphCacheDirty = false; +} + +// ### Maybe getGraphParts could return the variables when traversing, at least +// for trunk... +QList getVariables(QList constraints) +{ + QSet variableSet; + for (int i = 0; i < constraints.count(); ++i) { + const QSimplexConstraint *c = constraints.at(i); + foreach (QSimplexVariable *var, c->variables.keys()) { + variableSet += static_cast(var); + } + } + return variableSet.toList(); +} + +/*! + \internal + + Calculate graphs is the method that puts together all the helper routines + so that the AnchorLayout can calculate the sizes of each item. + + In a nutshell it should do: + + 1) Refresh anchor nominal sizes, that is, the size that each anchor would + have if no other restrictions applied. This is done by quering the + layout style and the sizeHints of the items belonging to the layout. + + 2) Simplify the graph by grouping together parallel and sequential anchors + into "group anchors". These have equivalent minimum, preferred and maximum + sizeHints as the anchors they replace. + + 3) Check if we got to a trivial case. In some cases, the whole graph can be + simplified into a single anchor. If so, use this information. If not, + then call the Simplex solver to calculate the anchors sizes. + + 4) Once the root anchors had its sizes calculated, propagate that to the + anchors they represent. +*/ +void QGraphicsAnchorLayoutPrivate::calculateGraphs( + QGraphicsAnchorLayoutPrivate::Orientation orientation) +{ +#if defined(QT_DEBUG) || defined(Q_AUTOTEST_EXPORT) + lastCalculationUsedSimplex[orientation] = false; +#endif + + static bool simplificationEnabled = qgetenv("QT_ANCHORLAYOUT_NO_SIMPLIFICATION").isEmpty(); + + // Reset the nominal sizes of each anchor based on the current item sizes + refreshAllSizeHints(orientation); + + // Simplify the graph + if (simplificationEnabled && !simplifyGraph(orientation)) { + qWarning("QGraphicsAnchorLayout: anchor setup is not feasible."); + graphHasConflicts[orientation] = true; + return; + } + + // Traverse all graph edges and store the possible paths to each vertex + findPaths(orientation); + + // From the paths calculated above, extract the constraints that the current + // anchor setup impose, to our Linear Programming problem. + constraintsFromPaths(orientation); + + // Split the constraints and anchors into groups that should be fed to the + // simplex solver independently. Currently we find two groups: + // + // 1) The "trunk", that is, the set of anchors (items) that are connected + // to the two opposite sides of our layout, and thus need to stretch in + // order to fit in the current layout size. + // + // 2) The floating or semi-floating anchors (items) that are those which + // are connected to only one (or none) of the layout sides, thus are not + // influenced by the layout size. + QList > parts = getGraphParts(orientation); + + // Now run the simplex solver to calculate Minimum, Preferred and Maximum sizes + // of the "trunk" set of constraints and variables. + // ### does trunk always exist? empty = trunk is the layout left->center->right + QList trunkConstraints = parts.at(0); + QList trunkVariables = getVariables(trunkConstraints); + + // For minimum and maximum, use the path between the two layout sides as the + // objective function. + AnchorVertex *v = layoutLastVertex[orientation]; + GraphPath trunkPath = graphPaths[orientation].value(v); + + bool feasible = calculateTrunk(orientation, trunkPath, trunkConstraints, trunkVariables); + + // For the other parts that not the trunk, solve only for the preferred size + // that is the size they will remain at, since they are not stretched by the + // layout. + + // Skipping the first (trunk) + for (int i = 1; i < parts.count(); ++i) { + if (!feasible) + break; + + QList partConstraints = parts.at(i); + QList partVariables = getVariables(partConstraints); + Q_ASSERT(!partVariables.isEmpty()); + feasible &= calculateNonTrunk(partConstraints, partVariables); + } + + // Propagate the new sizes down the simplified graph, ie. tell the + // group anchors to set their children anchors sizes. + updateAnchorSizes(orientation); + + graphHasConflicts[orientation] = !feasible; + + // Clean up our data structures. They are not needed anymore since + // distribution uses just interpolation. + qDeleteAll(constraints[orientation]); + constraints[orientation].clear(); + graphPaths[orientation].clear(); // ### + + if (simplificationEnabled) + restoreSimplifiedGraph(orientation); +} + +/*! + \internal + + Shift all the constraints by a certain amount. This allows us to deal with negative values in + the linear program if they are bounded by a certain limit. Functions should be careful to + call it again with a negative amount, to shift the constraints back. +*/ +static void shiftConstraints(const QList &constraints, qreal amount) +{ + for (int i = 0; i < constraints.count(); ++i) { + QSimplexConstraint *c = constraints.at(i); + qreal multiplier = 0; + foreach (qreal v, c->variables.values()) { + multiplier += v; + } + c->constant += multiplier * amount; + } +} + +/*! + \internal + + Calculate the sizes for all anchors which are part of the trunk. This works + on top of a (possibly) simplified graph. +*/ +bool QGraphicsAnchorLayoutPrivate::calculateTrunk(Orientation orientation, const GraphPath &path, + const QList &constraints, + const QList &variables) +{ + bool feasible = true; + bool needsSimplex = !constraints.isEmpty(); + +#if 0 + qDebug("Simplex %s for trunk of %s", needsSimplex ? "used" : "NOT used", + orientation == Horizontal ? "Horizontal" : "Vertical"); +#endif + + if (needsSimplex) { + + QList sizeHintConstraints = constraintsFromSizeHints(variables); + QList allConstraints = constraints + sizeHintConstraints; + + shiftConstraints(allConstraints, g_offset); + + // Solve min and max size hints + qreal min, max; + feasible = solveMinMax(allConstraints, path, &min, &max); + + if (feasible) { + solvePreferred(constraints, variables); + + // Calculate and set the preferred size for the layout, + // from the edge sizes that were calculated above. + qreal pref(0.0); + foreach (const AnchorData *ad, path.positives) { + pref += ad->sizeAtPreferred; + } + foreach (const AnchorData *ad, path.negatives) { + pref -= ad->sizeAtPreferred; + } + + sizeHints[orientation][Qt::MinimumSize] = min; + sizeHints[orientation][Qt::PreferredSize] = pref; + sizeHints[orientation][Qt::MaximumSize] = max; + } + + qDeleteAll(sizeHintConstraints); + shiftConstraints(constraints, -g_offset); + + } else { + // No Simplex is necessary because the path was simplified all the way to a single + // anchor. + Q_ASSERT(path.positives.count() == 1); + Q_ASSERT(path.negatives.count() == 0); + + AnchorData *ad = path.positives.toList()[0]; + ad->sizeAtMinimum = ad->minSize; + ad->sizeAtPreferred = ad->prefSize; + ad->sizeAtMaximum = ad->maxSize; + + sizeHints[orientation][Qt::MinimumSize] = ad->sizeAtMinimum; + sizeHints[orientation][Qt::PreferredSize] = ad->sizeAtPreferred; + sizeHints[orientation][Qt::MaximumSize] = ad->sizeAtMaximum; + } + +#if defined(QT_DEBUG) || defined(Q_AUTOTEST_EXPORT) + lastCalculationUsedSimplex[orientation] = needsSimplex; +#endif + + return feasible; +} + +/*! + \internal +*/ +bool QGraphicsAnchorLayoutPrivate::calculateNonTrunk(const QList &constraints, + const QList &variables) +{ + shiftConstraints(constraints, g_offset); + bool feasible = solvePreferred(constraints, variables); + + if (feasible) { + // Propagate size at preferred to other sizes. Semi-floats always will be + // in their sizeAtPreferred. + for (int j = 0; j < variables.count(); ++j) { + AnchorData *ad = variables.at(j); + Q_ASSERT(ad); + ad->sizeAtMinimum = ad->sizeAtPreferred; + ad->sizeAtMaximum = ad->sizeAtPreferred; + } + } + + shiftConstraints(constraints, -g_offset); + return feasible; +} + +/*! + \internal + + Traverse the graph refreshing the size hints. Edges will query their associated + item or graphicsAnchor for their size hints. +*/ +void QGraphicsAnchorLayoutPrivate::refreshAllSizeHints(Orientation orientation) +{ + Graph &g = graph[orientation]; + QList > vertices = g.connections(); + + QLayoutStyleInfo styleInf = styleInfo(); + for (int i = 0; i < vertices.count(); ++i) { + AnchorData *data = g.edgeData(vertices.at(i).first, vertices.at(i).second); + data->refreshSizeHints(&styleInf); + } +} + +/*! + \internal + + This method walks the graph using a breadth-first search to find paths + between the root vertex and each vertex on the graph. The edges + directions in each path are considered and they are stored as a + positive edge (left-to-right) or negative edge (right-to-left). + + The list of paths is used later to generate a list of constraints. + */ +void QGraphicsAnchorLayoutPrivate::findPaths(Orientation orientation) +{ + QQueue > queue; + + QSet visited; + + AnchorVertex *root = layoutFirstVertex[orientation]; + + graphPaths[orientation].insert(root, GraphPath()); + + foreach (AnchorVertex *v, graph[orientation].adjacentVertices(root)) { + queue.enqueue(qMakePair(root, v)); + } + + while(!queue.isEmpty()) { + QPair pair = queue.dequeue(); + AnchorData *edge = graph[orientation].edgeData(pair.first, pair.second); + + if (visited.contains(edge)) + continue; + + visited.insert(edge); + GraphPath current = graphPaths[orientation].value(pair.first); + + if (edge->from == pair.first) + current.positives.insert(edge); + else + current.negatives.insert(edge); + + graphPaths[orientation].insert(pair.second, current); + + foreach (AnchorVertex *v, + graph[orientation].adjacentVertices(pair.second)) { + queue.enqueue(qMakePair(pair.second, v)); + } + } + + // We will walk through every reachable items (non-float) store them in a temporary set. + // We them create a set of all items and subtract the non-floating items from the set in + // order to get the floating items. The floating items is then stored in m_floatItems + identifyFloatItems(visited, orientation); +} + +/*! + \internal + + Each vertex on the graph that has more than one path to it + represents a contra int to the sizes of the items in these paths. + + This method walks the list of paths to each vertex, generate + the constraints and store them in a list so they can be used later + by the Simplex solver. +*/ +void QGraphicsAnchorLayoutPrivate::constraintsFromPaths(Orientation orientation) +{ + foreach (AnchorVertex *vertex, graphPaths[orientation].uniqueKeys()) + { + int valueCount = graphPaths[orientation].count(vertex); + if (valueCount == 1) + continue; + + QList pathsToVertex = graphPaths[orientation].values(vertex); + for (int i = 1; i < valueCount; ++i) { + constraints[orientation] += \ + pathsToVertex[0].constraint(pathsToVertex.at(i)); + } + } +} + +/*! + \internal +*/ +void QGraphicsAnchorLayoutPrivate::updateAnchorSizes(Orientation orientation) +{ + Graph &g = graph[orientation]; + const QList > &vertices = g.connections(); + + for (int i = 0; i < vertices.count(); ++i) { + AnchorData *ad = g.edgeData(vertices.at(i).first, vertices.at(i).second); + ad->updateChildrenSizes(); + } +} + +/*! + \internal + + Create LP constraints for each anchor based on its minimum and maximum + sizes, as specified in its size hints +*/ +QList QGraphicsAnchorLayoutPrivate::constraintsFromSizeHints( + const QList &anchors) +{ + if (anchors.isEmpty()) + return QList(); + + // Look for the layout edge. That can be either the first half in case the + // layout is split in two, or the whole layout anchor. + Orientation orient = Orientation(anchors.first()->orientation); + AnchorData *layoutEdge = 0; + if (layoutCentralVertex[orient]) { + layoutEdge = graph[orient].edgeData(layoutFirstVertex[orient], layoutCentralVertex[orient]); + } else { + layoutEdge = graph[orient].edgeData(layoutFirstVertex[orient], layoutLastVertex[orient]); + } + + // If maxSize is less then "infinite", that means there are other anchors + // grouped together with this one. We can't ignore its maximum value so we + // set back the variable to NULL to prevent the continue condition from being + // satisfied in the loop below. + const qreal expectedMax = layoutCentralVertex[orient] ? QWIDGETSIZE_MAX / 2 : QWIDGETSIZE_MAX; + qreal actualMax; + if (layoutEdge->from == layoutFirstVertex[orient]) { + actualMax = layoutEdge->maxSize; + } else { + actualMax = -layoutEdge->minSize; + } + if (actualMax != expectedMax) { + layoutEdge = 0; + } + + // For each variable, create constraints based on size hints + QList anchorConstraints; + bool unboundedProblem = true; + for (int i = 0; i < anchors.size(); ++i) { + AnchorData *ad = anchors.at(i); + + // Anchors that have their size directly linked to another one don't need constraints + // For exammple, the second half of an item has exactly the same size as the first half + // thus constraining the latter is enough. + if (ad->dependency == AnchorData::Slave) + continue; + + // To use negative variables inside simplex, we shift them so the minimum negative value is + // mapped to zero before solving. To make sure that it works, we need to guarantee that the + // variables are all inside a certain boundary. + qreal boundedMin = qBound(-g_offset, ad->minSize, g_offset); + qreal boundedMax = qBound(-g_offset, ad->maxSize, g_offset); + + if ((boundedMin == boundedMax) || qFuzzyCompare(boundedMin, boundedMax)) { + QSimplexConstraint *c = new QSimplexConstraint; + c->variables.insert(ad, 1.0); + c->constant = boundedMin; + c->ratio = QSimplexConstraint::Equal; + anchorConstraints += c; + unboundedProblem = false; + } else { + QSimplexConstraint *c = new QSimplexConstraint; + c->variables.insert(ad, 1.0); + c->constant = boundedMin; + c->ratio = QSimplexConstraint::MoreOrEqual; + anchorConstraints += c; + + // We avoid adding restrictions to the layout internal anchors. That's + // to prevent unnecessary fair distribution from happening due to this + // artificial restriction. + if (ad == layoutEdge) + continue; + + c = new QSimplexConstraint; + c->variables.insert(ad, 1.0); + c->constant = boundedMax; + c->ratio = QSimplexConstraint::LessOrEqual; + anchorConstraints += c; + unboundedProblem = false; + } + } + + // If no upper boundary restriction was added, add one to avoid unbounded problem + if (unboundedProblem) { + QSimplexConstraint *c = new QSimplexConstraint; + c->variables.insert(layoutEdge, 1.0); + // The maximum size that the layout can take + c->constant = g_offset; + c->ratio = QSimplexConstraint::LessOrEqual; + anchorConstraints += c; + } + + return anchorConstraints; +} + +/*! + \internal +*/ +QList< QList > +QGraphicsAnchorLayoutPrivate::getGraphParts(Orientation orientation) +{ + Q_ASSERT(layoutFirstVertex[orientation] && layoutLastVertex[orientation]); + + AnchorData *edgeL1 = 0; + AnchorData *edgeL2 = 0; + + // The layout may have a single anchor between Left and Right or two half anchors + // passing through the center + if (layoutCentralVertex[orientation]) { + edgeL1 = graph[orientation].edgeData(layoutFirstVertex[orientation], layoutCentralVertex[orientation]); + edgeL2 = graph[orientation].edgeData(layoutCentralVertex[orientation], layoutLastVertex[orientation]); + } else { + edgeL1 = graph[orientation].edgeData(layoutFirstVertex[orientation], layoutLastVertex[orientation]); + } + + QLinkedList remainingConstraints; + for (int i = 0; i < constraints[orientation].count(); ++i) { + remainingConstraints += constraints[orientation].at(i); + } + for (int i = 0; i < itemCenterConstraints[orientation].count(); ++i) { + remainingConstraints += itemCenterConstraints[orientation].at(i); + } + + QList trunkConstraints; + QSet trunkVariables; + + trunkVariables += edgeL1; + if (edgeL2) + trunkVariables += edgeL2; + + bool dirty; + do { + dirty = false; + + QLinkedList::iterator it = remainingConstraints.begin(); + while (it != remainingConstraints.end()) { + QSimplexConstraint *c = *it; + bool match = false; + + // Check if this constraint have some overlap with current + // trunk variables... + foreach (QSimplexVariable *ad, trunkVariables) { + if (c->variables.contains(ad)) { + match = true; + break; + } + } + + // If so, we add it to trunk, and erase it from the + // remaining constraints. + if (match) { + trunkConstraints += c; + trunkVariables += QSet::fromList(c->variables.keys()); + it = remainingConstraints.erase(it); + dirty = true; + } else { + // Note that we don't erase the constraint if it's not + // a match, since in a next iteration of a do-while we + // can pass on it again and it will be a match. + // + // For example: if trunk share a variable with + // remainingConstraints[1] and it shares with + // remainingConstraints[0], we need a second iteration + // of the do-while loop to match both. + ++it; + } + } + } while (dirty); + + QList< QList > result; + result += trunkConstraints; + + if (!remainingConstraints.isEmpty()) { + QList nonTrunkConstraints; + QLinkedList::iterator it = remainingConstraints.begin(); + while (it != remainingConstraints.end()) { + nonTrunkConstraints += *it; + ++it; + } + result += nonTrunkConstraints; + } + + return result; +} + +/*! + \internal + + Use all visited Anchors on findPaths() so we can identify non-float Items. +*/ +void QGraphicsAnchorLayoutPrivate::identifyFloatItems(const QSet &visited, Orientation orientation) +{ + QSet nonFloating; + + foreach (const AnchorData *ad, visited) + identifyNonFloatItems_helper(ad, &nonFloating); + + QSet allItems; + foreach (QGraphicsLayoutItem *item, items) + allItems.insert(item); + m_floatItems[orientation] = allItems - nonFloating; +} + + +/*! + \internal + + Given an anchor, if it is an internal anchor and Normal we must mark it's item as non-float. + If the anchor is Sequential or Parallel, we must iterate on its children recursively until we reach + internal anchors (items). +*/ +void QGraphicsAnchorLayoutPrivate::identifyNonFloatItems_helper(const AnchorData *ad, QSet *nonFloatingItemsIdentifiedSoFar) +{ + Q_Q(QGraphicsAnchorLayout); + + switch(ad->type) { + case AnchorData::Normal: + if (ad->item && ad->item != q) + nonFloatingItemsIdentifiedSoFar->insert(ad->item); + break; + case AnchorData::Sequential: + foreach (const AnchorData *d, static_cast(ad)->m_edges) + identifyNonFloatItems_helper(d, nonFloatingItemsIdentifiedSoFar); + break; + case AnchorData::Parallel: + identifyNonFloatItems_helper(static_cast(ad)->firstEdge, nonFloatingItemsIdentifiedSoFar); + identifyNonFloatItems_helper(static_cast(ad)->secondEdge, nonFloatingItemsIdentifiedSoFar); + break; + } +} + +/*! + \internal + + Use the current vertices distance to calculate and set the geometry of + each item. +*/ +void QGraphicsAnchorLayoutPrivate::setItemsGeometries(const QRectF &geom) +{ + Q_Q(QGraphicsAnchorLayout); + AnchorVertex *firstH, *secondH, *firstV, *secondV; + + qreal top; + qreal left; + qreal right; + + q->getContentsMargins(&left, &top, &right, 0); + const Qt::LayoutDirection visualDir = visualDirection(); + if (visualDir == Qt::RightToLeft) + qSwap(left, right); + + left += geom.left(); + top += geom.top(); + right = geom.right() - right; + + foreach (QGraphicsLayoutItem *item, items) { + QRectF newGeom; + QSizeF itemPreferredSize = item->effectiveSizeHint(Qt::PreferredSize); + if (m_floatItems[Horizontal].contains(item)) { + newGeom.setLeft(0); + newGeom.setRight(itemPreferredSize.width()); + } else { + firstH = internalVertex(item, Qt::AnchorLeft); + secondH = internalVertex(item, Qt::AnchorRight); + + if (visualDir == Qt::LeftToRight) { + newGeom.setLeft(left + firstH->distance); + newGeom.setRight(left + secondH->distance); + } else { + newGeom.setLeft(right - secondH->distance); + newGeom.setRight(right - firstH->distance); + } + } + + if (m_floatItems[Vertical].contains(item)) { + newGeom.setTop(0); + newGeom.setBottom(itemPreferredSize.height()); + } else { + firstV = internalVertex(item, Qt::AnchorTop); + secondV = internalVertex(item, Qt::AnchorBottom); + + newGeom.setTop(top + firstV->distance); + newGeom.setBottom(top + secondV->distance); + } + + item->setGeometry(newGeom); + } +} + +/*! + \internal + + Calculate the position of each vertex based on the paths to each of + them as well as the current edges sizes. +*/ +void QGraphicsAnchorLayoutPrivate::calculateVertexPositions( + QGraphicsAnchorLayoutPrivate::Orientation orientation) +{ + QQueue > queue; + QSet visited; + + // Get root vertex + AnchorVertex *root = layoutFirstVertex[orientation]; + + root->distance = 0; + visited.insert(root); + + // Add initial edges to the queue + foreach (AnchorVertex *v, graph[orientation].adjacentVertices(root)) { + queue.enqueue(qMakePair(root, v)); + } + + // Do initial calculation required by "interpolateEdge()" + setupEdgesInterpolation(orientation); + + // Traverse the graph and calculate vertex positions + while (!queue.isEmpty()) { + QPair pair = queue.dequeue(); + AnchorData *edge = graph[orientation].edgeData(pair.first, pair.second); + + if (visited.contains(pair.second)) + continue; + + visited.insert(pair.second); + interpolateEdge(pair.first, edge); + + QList adjacents = graph[orientation].adjacentVertices(pair.second); + for (int i = 0; i < adjacents.count(); ++i) { + if (!visited.contains(adjacents.at(i))) + queue.enqueue(qMakePair(pair.second, adjacents.at(i))); + } + } +} + +/*! + \internal + + Calculate interpolation parameters based on current Layout Size. + Must be called once before calling "interpolateEdgeSize()" for + the edges. +*/ +void QGraphicsAnchorLayoutPrivate::setupEdgesInterpolation( + Orientation orientation) +{ + Q_Q(QGraphicsAnchorLayout); + + qreal current; + current = (orientation == Horizontal) ? q->contentsRect().width() : q->contentsRect().height(); + + QPair result; + result = getFactor(current, + sizeHints[orientation][Qt::MinimumSize], + sizeHints[orientation][Qt::PreferredSize], + sizeHints[orientation][Qt::PreferredSize], + sizeHints[orientation][Qt::PreferredSize], + sizeHints[orientation][Qt::MaximumSize]); + + interpolationInterval[orientation] = result.first; + interpolationProgress[orientation] = result.second; +} + +/*! + \internal + + Calculate the current Edge size based on the current Layout size and the + size the edge is supposed to have when the layout is at its: + + - minimum size, + - preferred size, + - maximum size. + + These three key values are calculated in advance using linear + programming (more expensive) or the simplification algorithm, then + subsequential resizes of the parent layout require a simple + interpolation. +*/ +void QGraphicsAnchorLayoutPrivate::interpolateEdge(AnchorVertex *base, AnchorData *edge) +{ + const Orientation orientation = Orientation(edge->orientation); + const QPair factor(interpolationInterval[orientation], + interpolationProgress[orientation]); + + qreal edgeDistance = interpolate(factor, edge->sizeAtMinimum, edge->sizeAtPreferred, + edge->sizeAtPreferred, edge->sizeAtPreferred, + edge->sizeAtMaximum); + + Q_ASSERT(edge->from == base || edge->to == base); + + // Calculate the distance for the vertex opposite to the base + if (edge->from == base) { + edge->to->distance = base->distance + edgeDistance; + } else { + edge->from->distance = base->distance - edgeDistance; + } +} + +bool QGraphicsAnchorLayoutPrivate::solveMinMax(const QList &constraints, + GraphPath path, qreal *min, qreal *max) +{ + QSimplex simplex; + bool feasible = simplex.setConstraints(constraints); + if (feasible) { + // Obtain the objective constraint + QSimplexConstraint objective; + QSet::const_iterator iter; + for (iter = path.positives.constBegin(); iter != path.positives.constEnd(); ++iter) + objective.variables.insert(*iter, 1.0); + + for (iter = path.negatives.constBegin(); iter != path.negatives.constEnd(); ++iter) + objective.variables.insert(*iter, -1.0); + + const qreal objectiveOffset = (path.positives.count() - path.negatives.count()) * g_offset; + simplex.setObjective(&objective); + + // Calculate minimum values + *min = simplex.solveMin() - objectiveOffset; + + // Save sizeAtMinimum results + QList variables = getVariables(constraints); + for (int i = 0; i < variables.size(); ++i) { + AnchorData *ad = static_cast(variables.at(i)); + ad->sizeAtMinimum = ad->result - g_offset; + } + + // Calculate maximum values + *max = simplex.solveMax() - objectiveOffset; + + // Save sizeAtMaximum results + for (int i = 0; i < variables.size(); ++i) { + AnchorData *ad = static_cast(variables.at(i)); + ad->sizeAtMaximum = ad->result - g_offset; + } + } + return feasible; +} + +enum slackType { Grower = -1, Shrinker = 1 }; +static QPair createSlack(QSimplexConstraint *sizeConstraint, + qreal interval, slackType type) +{ + QSimplexVariable *slack = new QSimplexVariable; + sizeConstraint->variables.insert(slack, type); + + QSimplexConstraint *limit = new QSimplexConstraint; + limit->variables.insert(slack, 1.0); + limit->ratio = QSimplexConstraint::LessOrEqual; + limit->constant = interval; + + return qMakePair(slack, limit); +} + +bool QGraphicsAnchorLayoutPrivate::solvePreferred(const QList &constraints, + const QList &variables) +{ + QList preferredConstraints; + QList preferredVariables; + QSimplexConstraint objective; + + // Fill the objective coefficients for this variable. In the + // end the objective function will be + // + // z = n * (A_shrinker_hard + A_grower_hard + B_shrinker_hard + B_grower_hard + ...) + + // (A_shrinker_soft + A_grower_soft + B_shrinker_soft + B_grower_soft + ...) + // + // where n is the number of variables that have + // slacks. Note that here we use the number of variables + // as coefficient, this is to mark the "shrinker slack + // variable" less likely to get value than the "grower + // slack variable". + + // This will fill the values for the structural constraints + // and we now fill the values for the slack constraints (one per variable), + // which have this form (the constant A_pref was set when creating the slacks): + // + // A + A_shrinker_hard + A_shrinker_soft - A_grower_hard - A_grower_soft = A_pref + // + for (int i = 0; i < variables.size(); ++i) { + AnchorData *ad = variables.at(i); + + // The layout original structure anchors are not relevant in preferred size calculation + if (ad->isLayoutAnchor) + continue; + + // By default, all variables are equal to their preferred size. If they have room to + // grow or shrink, such flexibility will be added by the additional variables below. + QSimplexConstraint *sizeConstraint = new QSimplexConstraint; + preferredConstraints += sizeConstraint; + sizeConstraint->variables.insert(ad, 1.0); + sizeConstraint->constant = ad->prefSize + g_offset; + + // Can easily shrink + QPair slack; + const qreal softShrinkInterval = ad->prefSize - ad->minPrefSize; + if (softShrinkInterval) { + slack = createSlack(sizeConstraint, softShrinkInterval, Shrinker); + preferredVariables += slack.first; + preferredConstraints += slack.second; + + // Add to objective with ratio == 1 (soft) + objective.variables.insert(slack.first, 1.0); + } + + // Can easily grow + const qreal softGrowInterval = ad->maxPrefSize - ad->prefSize; + if (softGrowInterval) { + slack = createSlack(sizeConstraint, softGrowInterval, Grower); + preferredVariables += slack.first; + preferredConstraints += slack.second; + + // Add to objective with ratio == 1 (soft) + objective.variables.insert(slack.first, 1.0); + } + + // Can shrink if really necessary + const qreal hardShrinkInterval = ad->minPrefSize - ad->minSize; + if (hardShrinkInterval) { + slack = createSlack(sizeConstraint, hardShrinkInterval, Shrinker); + preferredVariables += slack.first; + preferredConstraints += slack.second; + + // Add to objective with ratio == N (hard) + objective.variables.insert(slack.first, variables.size()); + } + + // Can grow if really necessary + const qreal hardGrowInterval = ad->maxSize - ad->maxPrefSize; + if (hardGrowInterval) { + slack = createSlack(sizeConstraint, hardGrowInterval, Grower); + preferredVariables += slack.first; + preferredConstraints += slack.second; + + // Add to objective with ratio == N (hard) + objective.variables.insert(slack.first, variables.size()); + } + } + + QSimplex *simplex = new QSimplex; + bool feasible = simplex->setConstraints(constraints + preferredConstraints); + if (feasible) { + simplex->setObjective(&objective); + + // Calculate minimum values + simplex->solveMin(); + + // Save sizeAtPreferred results + for (int i = 0; i < variables.size(); ++i) { + AnchorData *ad = variables.at(i); + ad->sizeAtPreferred = ad->result - g_offset; + } + + // Make sure we delete the simplex solver -before- we delete the + // constraints used by it. + delete simplex; + } + // Delete constraints and variables we created. + qDeleteAll(preferredConstraints); + qDeleteAll(preferredVariables); + + return feasible; +} + +/*! + \internal + Returns true if there are no arrangement that satisfies all constraints. + Otherwise returns false. + + \sa addAnchor() +*/ +bool QGraphicsAnchorLayoutPrivate::hasConflicts() const +{ + QGraphicsAnchorLayoutPrivate *that = const_cast(this); + that->calculateGraphs(); + + bool floatConflict = !m_floatItems[0].isEmpty() || !m_floatItems[1].isEmpty(); + + return graphHasConflicts[0] || graphHasConflicts[1] || floatConflict; +} + +#ifdef QT_DEBUG +void QGraphicsAnchorLayoutPrivate::dumpGraph(const QString &name) +{ + QFile file(QString::fromAscii("anchorlayout.%1.dot").arg(name)); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + qWarning("Could not write to %s", file.fileName().toLocal8Bit().constData()); + + QString str = QString::fromAscii("digraph anchorlayout {\nnode [shape=\"rect\"]\n%1}"); + QString dotContents = graph[0].serializeToDot(); + dotContents += graph[1].serializeToDot(); + file.write(str.arg(dotContents).toLocal8Bit()); + + file.close(); +} +#endif + +QT_END_NAMESPACE +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicsanchorlayout_p.h b/src/widgets/graphicsview/qgraphicsanchorlayout_p.h new file mode 100644 index 0000000000..9a91c3c8df --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsanchorlayout_p.h @@ -0,0 +1,594 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSANCHORLAYOUT_P_H +#define QGRAPHICSANCHORLAYOUT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include "qgraphicslayout_p.h" +#include "qgraphicsanchorlayout.h" +#include "qgraph_p.h" +#include "qsimplex_p.h" +#ifndef QT_NO_GRAPHICSVIEW +QT_BEGIN_NAMESPACE + +/* + The public QGraphicsAnchorLayout interface represents an anchorage point + as a pair of a and a . + + Internally though, it has a graph of anchorage points (vertices) and + anchors (edges), represented by the AnchorVertex and AnchorData structs + respectively. +*/ + +/*! + \internal + + Represents a vertex (anchorage point) in the internal graph +*/ +struct AnchorVertex { + enum Type { + Normal = 0, + Pair + }; + + AnchorVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge) + : m_item(item), m_edge(edge), m_type(Normal) {} + + AnchorVertex() + : m_item(0), m_edge(Qt::AnchorPoint(0)), m_type(Normal) {} + +#ifdef QT_DEBUG + inline QString toString() const; +#endif + + QGraphicsLayoutItem *m_item; + Qt::AnchorPoint m_edge; + uint m_type : 1; + + // Current distance from this vertex to the layout edge (Left or Top) + // Value is calculated from the current anchors sizes. + qreal distance; +}; + +/*! + \internal + + Represents an edge (anchor) in the internal graph. +*/ +struct AnchorData : public QSimplexVariable { + enum Type { + Normal = 0, + Sequential, + Parallel + }; + + enum Dependency { + Independent = 0, + Master, + Slave + }; + + AnchorData() + : QSimplexVariable(), from(0), to(0), + minSize(0), prefSize(0), maxSize(0), + minPrefSize(0), maxPrefSize(0), + sizeAtMinimum(0), sizeAtPreferred(0), + sizeAtMaximum(0), item(0), graphicsAnchor(0), + type(Normal), isLayoutAnchor(false), + isCenterAnchor(false), orientation(0), + dependency(Independent) {} + virtual ~AnchorData(); + + virtual void updateChildrenSizes() {} + void refreshSizeHints(const QLayoutStyleInfo *styleInfo = 0); + +#ifdef QT_DEBUG + void dump(int indent = 2); + inline QString toString() const; + QString name; +#endif + + // Anchor is semantically directed + AnchorVertex *from; + AnchorVertex *to; + + // Nominal sizes + // These are the intrinsic size restrictions for a given item. They are + // used as input for the calculation of the actual sizes. + // These values are filled by the refreshSizeHints method, based on the + // anchor size policy, the size hints of the item it (possibly) represents + // and the layout spacing information. + qreal minSize; + qreal prefSize; + qreal maxSize; + + qreal minPrefSize; + qreal maxPrefSize; + + // Calculated sizes + // These attributes define which sizes should that anchor be in when the + // layout is at its minimum, preferred or maximum sizes. Values are + // calculated by the Simplex solver based on the current layout setup. + qreal sizeAtMinimum; + qreal sizeAtPreferred; + qreal sizeAtMaximum; + + // References to the classes that represent this anchor in the public world + // An anchor may represent a LayoutItem, it may also be acessible externally + // through a GraphicsAnchor "handler". + QGraphicsLayoutItem *item; + QGraphicsAnchor *graphicsAnchor; + + uint type : 2; // either Normal, Sequential or Parallel + uint isLayoutAnchor : 1; // if this anchor is an internal layout anchor + uint isCenterAnchor : 1; + uint orientation : 1; + uint dependency : 2; // either Independent, Master or Slave +}; + +#ifdef QT_DEBUG +inline QString AnchorData::toString() const +{ + return QString::fromAscii("Anchor(%1)").arg(name); +} +#endif + +struct SequentialAnchorData : public AnchorData +{ + SequentialAnchorData(const QVector &vertices, const QVector &edges) + : AnchorData(), m_children(vertices), m_edges(edges) + { + type = AnchorData::Sequential; + orientation = m_edges.at(0)->orientation; +#ifdef QT_DEBUG + name = QString::fromAscii("%1 -- %2").arg(vertices.first()->toString(), vertices.last()->toString()); +#endif + } + + virtual void updateChildrenSizes(); + void calculateSizeHints(); + + QVector m_children; // list of vertices in the sequence + QVector m_edges; // keep the list of edges too. +}; + +struct ParallelAnchorData : public AnchorData +{ + ParallelAnchorData(AnchorData *first, AnchorData *second) + : AnchorData(), firstEdge(first), secondEdge(second) + { + type = AnchorData::Parallel; + orientation = first->orientation; + + // This assert whether the child anchors share their vertices + Q_ASSERT(((first->from == second->from) && (first->to == second->to)) || + ((first->from == second->to) && (first->to == second->from))); + + // Our convention will be that the parallel group anchor will have the same + // direction as the first anchor. + from = first->from; + to = first->to; +#ifdef QT_DEBUG + name = QString::fromAscii("%1 | %2").arg(first->toString(), second->toString()); +#endif + } + + virtual void updateChildrenSizes(); + bool calculateSizeHints(); + + bool secondForward() const { + // We have the convention that the first children will define the direction of the + // pararell group. Note that we can't rely on 'this->from' or 'this->to' because they + // might be changed by vertex simplification. + return firstEdge->from == secondEdge->from; + } + + AnchorData* firstEdge; + AnchorData* secondEdge; + + QList m_firstConstraints; + QList m_secondConstraints; +}; + +struct AnchorVertexPair : public AnchorVertex { + AnchorVertexPair(AnchorVertex *v1, AnchorVertex *v2, AnchorData *data) + : AnchorVertex(), m_first(v1), m_second(v2), m_removedAnchor(data) { + m_type = AnchorVertex::Pair; + } + + AnchorVertex *m_first; + AnchorVertex *m_second; + + AnchorData *m_removedAnchor; + QList m_firstAnchors; + QList m_secondAnchors; +}; + +#ifdef QT_DEBUG +inline QString AnchorVertex::toString() const +{ + if (!this) { + return QLatin1String("NULL"); + } else if (m_type == Pair) { + const AnchorVertexPair *vp = static_cast(this); + return QString::fromAscii("(%1, %2)").arg(vp->m_first->toString()).arg(vp->m_second->toString()); + } else if (!m_item) { + return QString::fromAscii("NULL_%1").arg(quintptr(this)); + } + QString edge; + switch (m_edge) { + case Qt::AnchorLeft: + edge = QLatin1String("Left"); + break; + case Qt::AnchorHorizontalCenter: + edge = QLatin1String("HorizontalCenter"); + break; + case Qt::AnchorRight: + edge = QLatin1String("Right"); + break; + case Qt::AnchorTop: + edge = QLatin1String("Top"); + break; + case Qt::AnchorVerticalCenter: + edge = QLatin1String("VerticalCenter"); + break; + case Qt::AnchorBottom: + edge = QLatin1String("Bottom"); + break; + default: + edge = QLatin1String("None"); + break; + } + QString itemName; + if (m_item->isLayout()) { + itemName = QLatin1String("layout"); + } else { + if (QGraphicsItem *item = m_item->graphicsItem()) { + itemName = item->data(0).toString(); + } + } + edge.insert(0, QLatin1String("%1_")); + return edge.arg(itemName); +} +#endif + +/*! + \internal + + Representation of a valid path for a given vertex in the graph. + In this struct, "positives" is the set of anchors that have been + traversed in the forward direction, while "negatives" is the set + with the ones walked backwards. + + This paths are compared against each other to produce LP Constraints, + the exact order in which the anchors were traversed is not relevant. +*/ +class GraphPath +{ +public: + GraphPath() {} + + QSimplexConstraint *constraint(const GraphPath &path) const; +#ifdef QT_DEBUG + QString toString() const; +#endif + QSet positives; + QSet negatives; +}; + +class QGraphicsAnchorLayoutPrivate; +/*! + \internal +*/ +class QGraphicsAnchorPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsAnchor) + +public: + explicit QGraphicsAnchorPrivate(int version = QObjectPrivateVersion); + ~QGraphicsAnchorPrivate(); + + void setSpacing(qreal value); + void unsetSpacing(); + qreal spacing() const; + + void setSizePolicy(QSizePolicy::Policy policy); + + QGraphicsAnchorLayoutPrivate *layoutPrivate; + AnchorData *data; + + // Size information for user controlled anchor + QSizePolicy::Policy sizePolicy; + qreal preferredSize; + + uint hasSize : 1; // if false, get size from style. +}; + + + + +/*! + \internal + + QGraphicsAnchorLayout private methods and attributes. +*/ +class Q_AUTOTEST_EXPORT QGraphicsAnchorLayoutPrivate : public QGraphicsLayoutPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsAnchorLayout) + +public: + // When the layout geometry is different from its Minimum, Preferred + // or Maximum values, interpolation is used to calculate the geometries + // of the items. + // + // Interval represents which interpolation interval are we operating in. + enum Interval { + MinimumToMinPreferred = 0, + MinPreferredToPreferred, + PreferredToMaxPreferred, + MaxPreferredToMaximum + }; + + // Several structures internal to the layout are duplicated to handle + // both Horizontal and Vertical restrictions. + // + // Orientation is used to reference the right structure in each context + enum Orientation { + Horizontal = 0, + Vertical, + NOrientations + }; + + QGraphicsAnchorLayoutPrivate(); + + static QGraphicsAnchorLayoutPrivate *get(QGraphicsAnchorLayout *q) + { + return q ? q->d_func() : 0; + } + + static Qt::AnchorPoint oppositeEdge( + Qt::AnchorPoint edge); + + static Orientation edgeOrientation(Qt::AnchorPoint edge); + + static Qt::AnchorPoint pickEdge(Qt::AnchorPoint edge, Orientation orientation) + { + if (orientation == Vertical && int(edge) <= 2) + return (Qt::AnchorPoint)(edge + 3); + else if (orientation == Horizontal && int(edge) >= 3) { + return (Qt::AnchorPoint)(edge - 3); + } + return edge; + } + + // Init methods + void createLayoutEdges(); + void deleteLayoutEdges(); + void createItemEdges(QGraphicsLayoutItem *item); + void createCenterAnchors(QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge); + void removeCenterAnchors(QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge, bool substitute = true); + void removeCenterConstraints(QGraphicsLayoutItem *item, Orientation orientation); + + QGraphicsAnchor *acquireGraphicsAnchor(AnchorData *data) + { + Q_Q(QGraphicsAnchorLayout); + if (!data->graphicsAnchor) { + data->graphicsAnchor = new QGraphicsAnchor(q); + data->graphicsAnchor->d_func()->data = data; + } + return data->graphicsAnchor; + } + + // function used by the 4 API functions + QGraphicsAnchor *addAnchor(QGraphicsLayoutItem *firstItem, + Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, + Qt::AnchorPoint secondEdge, + qreal *spacing = 0); + + // Helper for Anchor Manipulation methods + void addAnchor_helper(QGraphicsLayoutItem *firstItem, + Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, + Qt::AnchorPoint secondEdge, + AnchorData *data); + + QGraphicsAnchor *getAnchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge); + + void removeAnchor(AnchorVertex *firstVertex, AnchorVertex *secondVertex); + void removeAnchor_helper(AnchorVertex *v1, AnchorVertex *v2); + + void removeAnchors(QGraphicsLayoutItem *item); + + void removeVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge); + + void correctEdgeDirection(QGraphicsLayoutItem *&firstItem, + Qt::AnchorPoint &firstEdge, + QGraphicsLayoutItem *&secondItem, + Qt::AnchorPoint &secondEdge); + + QLayoutStyleInfo &styleInfo() const; + + AnchorData *addAnchorMaybeParallel(AnchorData *newAnchor, bool *feasible); + + // Activation + void calculateGraphs(); + void calculateGraphs(Orientation orientation); + + // Simplification + bool simplifyGraph(Orientation orientation); + bool simplifyVertices(Orientation orientation); + bool simplifyGraphIteration(Orientation orientation, bool *feasible); + + bool replaceVertex(Orientation orientation, AnchorVertex *oldV, + AnchorVertex *newV, const QList &edges); + + + void restoreSimplifiedGraph(Orientation orientation); + void restoreSimplifiedAnchor(AnchorData *edge); + void restoreSimplifiedConstraints(ParallelAnchorData *parallel); + void restoreVertices(Orientation orientation); + + bool calculateTrunk(Orientation orientation, const GraphPath &trunkPath, + const QList &constraints, + const QList &variables); + bool calculateNonTrunk(const QList &constraints, + const QList &variables); + + // Support functions for calculateGraph() + void refreshAllSizeHints(Orientation orientation); + void findPaths(Orientation orientation); + void constraintsFromPaths(Orientation orientation); + void updateAnchorSizes(Orientation orientation); + QList constraintsFromSizeHints(const QList &anchors); + QList > getGraphParts(Orientation orientation); + void identifyFloatItems(const QSet &visited, Orientation orientation); + void identifyNonFloatItems_helper(const AnchorData *ad, QSet *nonFloatingItemsIdentifiedSoFar); + + inline AnchorVertex *internalVertex(const QPair &itemEdge) const + { + return m_vertexList.value(itemEdge).first; + } + + inline AnchorVertex *internalVertex(const QGraphicsLayoutItem *item, Qt::AnchorPoint edge) const + { + return internalVertex(qMakePair(const_cast(item), edge)); + } + + inline void changeLayoutVertex(Orientation orientation, AnchorVertex *oldV, AnchorVertex *newV) + { + if (layoutFirstVertex[orientation] == oldV) + layoutFirstVertex[orientation] = newV; + else if (layoutCentralVertex[orientation] == oldV) + layoutCentralVertex[orientation] = newV; + else if (layoutLastVertex[orientation] == oldV) + layoutLastVertex[orientation] = newV; + } + + + AnchorVertex *addInternalVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge); + void removeInternalVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge); + + // Geometry interpolation methods + void setItemsGeometries(const QRectF &geom); + + void calculateVertexPositions(Orientation orientation); + void setupEdgesInterpolation(Orientation orientation); + void interpolateEdge(AnchorVertex *base, AnchorData *edge); + + // Linear Programming solver methods + bool solveMinMax(const QList &constraints, + GraphPath path, qreal *min, qreal *max); + bool solvePreferred(const QList &constraints, + const QList &variables); + bool hasConflicts() const; + +#ifdef QT_DEBUG + void dumpGraph(const QString &name = QString()); +#endif + + + qreal spacings[NOrientations]; + // Size hints from simplex engine + qreal sizeHints[2][3]; + + // Items + QVector items; + + // Mapping between high level anchorage points (Item, Edge) to low level + // ones (Graph Vertices) + + QHash, QPair > m_vertexList; + + // Internal graph of anchorage points and anchors, for both orientations + Graph graph[2]; + + AnchorVertex *layoutFirstVertex[2]; + AnchorVertex *layoutCentralVertex[2]; + AnchorVertex *layoutLastVertex[2]; + + // Combined anchors in order of creation + QList simplifiedVertices[2]; + QList anchorsFromSimplifiedVertices[2]; + + // Graph paths and constraints, for both orientations + QMultiHash graphPaths[2]; + QList constraints[2]; + QList itemCenterConstraints[2]; + + // The interpolation interval and progress based on the current size + // as well as the key values (minimum, preferred and maximum) + Interval interpolationInterval[2]; + qreal interpolationProgress[2]; + + bool graphHasConflicts[2]; + QSet m_floatItems[2]; + +#if defined(QT_DEBUG) || defined(Q_AUTOTEST_EXPORT) + bool lastCalculationUsedSimplex[2]; +#endif + + uint calculateGraphCacheDirty : 1; + mutable uint styleInfoDirty : 1; + mutable QLayoutStyleInfo cachedStyleInfo; + + friend class QGraphicsAnchorPrivate; +}; + +QT_END_NAMESPACE +#endif //QT_NO_GRAPHICSVIEW + +#endif diff --git a/src/widgets/graphicsview/qgraphicsgridlayout.cpp b/src/widgets/graphicsview/qgraphicsgridlayout.cpp new file mode 100644 index 0000000000..f6eec1d405 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsgridlayout.cpp @@ -0,0 +1,690 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +/*! + \class QGraphicsGridLayout + \brief The QGraphicsGridLayout class provides a grid layout for managing + widgets in Graphics View. + \since 4.4 + + \ingroup graphicsview-api + + The most common way to use QGraphicsGridLayout is to construct an object + on the heap with no parent, add widgets and layouts by calling addItem(), + and finally assign the layout to a widget by calling + QGraphicsWidget::setLayout(). QGraphicsGridLayout automatically computes + the dimensions of the grid as you add items. + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsgridlayout.cpp 0 + + The layout takes ownership of the items. In some cases when the layout + item also inherits from QGraphicsItem (such as QGraphicsWidget) there will be a + ambiguity in ownership because the layout item belongs to two ownership hierarchies. + See the documentation of QGraphicsLayoutItem::setOwnedByLayout() how to handle + this. + You can access each item in the layout by calling count() and itemAt(). Calling + removeAt() will remove an item from the layout, without + destroying it. + + \section1 Size Hints and Size Policies in QGraphicsGridLayout + + QGraphicsGridLayout respects each item's size hints and size policies, + and when a cell in the grid has more space than the items can fill, each item + is arranged according to the layout's alignment for that item. You can set + an alignment for each item by calling setAlignment(), and check the + alignment for any item by calling alignment(). You can also set the alignment + for an entire row or column by calling setRowAlignment() and setColumnAlignment() + respectively. By default, items are aligned to the top left. + + + \sa QGraphicsLinearLayout, QGraphicsWidget +*/ + +#include "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qapplication.h" +#include "qwidget.h" +#include "qgraphicslayout_p.h" +#include "qgraphicslayoutitem.h" +#include "qgraphicsgridlayout.h" +#include "qgraphicswidget.h" +#include "qgridlayoutengine_p.h" +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsGridLayoutPrivate : public QGraphicsLayoutPrivate +{ +public: + QGraphicsGridLayoutPrivate() { } + QLayoutStyleInfo styleInfo() const; + + QGridLayoutEngine engine; +#ifdef QT_DEBUG + void dump(int indent) const; +#endif +}; + +Q_GLOBAL_STATIC(QWidget, globalStyleInfoWidget); + +QLayoutStyleInfo QGraphicsGridLayoutPrivate::styleInfo() const +{ + QGraphicsItem *item = parentItem(); + QStyle *style = (item && item->isWidget()) ? static_cast(item)->style() : QApplication::style(); + return QLayoutStyleInfo(style, globalStyleInfoWidget()); +} + +/*! + Constructs a QGraphicsGridLayout instance. \a parent is passed to + QGraphicsLayout's constructor. +*/ +QGraphicsGridLayout::QGraphicsGridLayout(QGraphicsLayoutItem *parent) + : QGraphicsLayout(*new QGraphicsGridLayoutPrivate(), parent) +{ +} + +/*! + Destroys the QGraphicsGridLayout object. +*/ +QGraphicsGridLayout::~QGraphicsGridLayout() +{ + for (int i = count() - 1; i >= 0; --i) { + QGraphicsLayoutItem *item = itemAt(i); + // The following lines can be removed, but this removes the item + // from the layout more efficiently than the implementation of + // ~QGraphicsLayoutItem. + removeAt(i); + if (item) { + item->setParentLayoutItem(0); + if (item->ownedByLayout()) + delete item; + } + } +} + +/*! + Adds \a item to the grid on \a row and \a column. You can specify a + \a rowSpan and \a columnSpan and an optional \a alignment. +*/ +void QGraphicsGridLayout::addItem(QGraphicsLayoutItem *item, int row, int column, + int rowSpan, int columnSpan, Qt::Alignment alignment) +{ + Q_D(QGraphicsGridLayout); + if (row < 0 || column < 0) { + qWarning("QGraphicsGridLayout::addItem: invalid row/column: %d", + row < 0 ? row : column); + return; + } + if (columnSpan < 1 || rowSpan < 1) { + qWarning("QGraphicsGridLayout::addItem: invalid row span/column span: %d", + rowSpan < 1 ? rowSpan : columnSpan); + return; + } + if (!item) { + qWarning("QGraphicsGridLayout::addItem: cannot add null item"); + return; + } + if (item == this) { + qWarning("QGraphicsGridLayout::addItem: cannot insert itself"); + return; + } + + d->addChildLayoutItem(item); + + new QGridLayoutItem(&d->engine, item, row, column, rowSpan, columnSpan, alignment); + invalidate(); +} + +/*! + \fn QGraphicsGridLayout::addItem(QGraphicsLayoutItem *item, int row, int column, Qt::Alignment alignment = 0) + + Adds \a item to the grid on \a row and \a column. You can specify + an optional \a alignment for \a item. +*/ + +/*! + Sets the default horizontal spacing for the grid layout to \a spacing. +*/ +void QGraphicsGridLayout::setHorizontalSpacing(qreal spacing) +{ + Q_D(QGraphicsGridLayout); + d->engine.setSpacing(spacing, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the default horizontal spacing for the grid layout. +*/ +qreal QGraphicsGridLayout::horizontalSpacing() const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.spacing(d->styleInfo(), Qt::Horizontal); +} + +/*! + Sets the default vertical spacing for the grid layout to \a spacing. +*/ +void QGraphicsGridLayout::setVerticalSpacing(qreal spacing) +{ + Q_D(QGraphicsGridLayout); + d->engine.setSpacing(spacing, Qt::Vertical); + invalidate(); +} + +/*! + Returns the default vertical spacing for the grid layout. +*/ +qreal QGraphicsGridLayout::verticalSpacing() const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.spacing(d->styleInfo(), Qt::Vertical); +} + +/*! + Sets the grid layout's default spacing, both vertical and + horizontal, to \a spacing. + + \sa rowSpacing(), columnSpacing() +*/ +void QGraphicsGridLayout::setSpacing(qreal spacing) +{ + Q_D(QGraphicsGridLayout); + d->engine.setSpacing(spacing, Qt::Horizontal | Qt::Vertical); + invalidate(); +} + +/*! + Sets the spacing for \a row to \a spacing. +*/ +void QGraphicsGridLayout::setRowSpacing(int row, qreal spacing) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSpacing(row, spacing, Qt::Vertical); + invalidate(); +} + +/*! + Returns the row spacing for \a row. +*/ +qreal QGraphicsGridLayout::rowSpacing(int row) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSpacing(row, Qt::Vertical); +} + +/*! + Sets the spacing for \a column to \a spacing. +*/ +void QGraphicsGridLayout::setColumnSpacing(int column, qreal spacing) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSpacing(column, spacing, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the column spacing for \a column. +*/ +qreal QGraphicsGridLayout::columnSpacing(int column) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSpacing(column, Qt::Horizontal); +} + +/*! + Sets the stretch factor for \a row to \a stretch. +*/ +void QGraphicsGridLayout::setRowStretchFactor(int row, int stretch) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowStretchFactor(row, stretch, Qt::Vertical); + invalidate(); +} + +/*! + Returns the stretch factor for \a row. +*/ +int QGraphicsGridLayout::rowStretchFactor(int row) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowStretchFactor(row, Qt::Vertical); +} + +/*! + Sets the stretch factor for \a column to \a stretch. +*/ +void QGraphicsGridLayout::setColumnStretchFactor(int column, int stretch) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowStretchFactor(column, stretch, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the stretch factor for \a column. +*/ +int QGraphicsGridLayout::columnStretchFactor(int column) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowStretchFactor(column, Qt::Horizontal); +} + +/*! + Sets the minimum height for row, \a row, to \a height. +*/ +void QGraphicsGridLayout::setRowMinimumHeight(int row, qreal height) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::MinimumSize, row, height, Qt::Vertical); + invalidate(); +} + +/*! + Returns the minimum height for row, \a row. +*/ +qreal QGraphicsGridLayout::rowMinimumHeight(int row) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSizeHint(Qt::MinimumSize, row, Qt::Vertical); +} + +/*! + Sets the preferred height for row, \a row, to \a height. +*/ +void QGraphicsGridLayout::setRowPreferredHeight(int row, qreal height) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::PreferredSize, row, height, Qt::Vertical); + invalidate(); +} + +/*! + Returns the preferred height for row, \a row. +*/ +qreal QGraphicsGridLayout::rowPreferredHeight(int row) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSizeHint(Qt::PreferredSize, row, Qt::Vertical); +} + +/*! + Sets the maximum height for row, \a row, to \a height. +*/ +void QGraphicsGridLayout::setRowMaximumHeight(int row, qreal height) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::MaximumSize, row, height, Qt::Vertical); + invalidate(); +} + +/*! + Returns the maximum height for row, \a row. +*/ +qreal QGraphicsGridLayout::rowMaximumHeight(int row) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSizeHint(Qt::MaximumSize, row, Qt::Vertical); +} + +/*! + Sets the fixed height for row, \a row, to \a height. +*/ +void QGraphicsGridLayout::setRowFixedHeight(int row, qreal height) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::MinimumSize, row, height, Qt::Vertical); + d->engine.setRowSizeHint(Qt::MaximumSize, row, height, Qt::Vertical); + invalidate(); +} + +/*! + Sets the minimum width for \a column to \a width. +*/ +void QGraphicsGridLayout::setColumnMinimumWidth(int column, qreal width) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::MinimumSize, column, width, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the minimum width for \a column. +*/ +qreal QGraphicsGridLayout::columnMinimumWidth(int column) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSizeHint(Qt::MinimumSize, column, Qt::Horizontal); +} + +/*! + Sets the preferred width for \a column to \a width. +*/ +void QGraphicsGridLayout::setColumnPreferredWidth(int column, qreal width) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::PreferredSize, column, width, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the preferred width for \a column. +*/ +qreal QGraphicsGridLayout::columnPreferredWidth(int column) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSizeHint(Qt::PreferredSize, column, Qt::Horizontal); +} + +/*! + Sets the maximum width of \a column to \a width. +*/ +void QGraphicsGridLayout::setColumnMaximumWidth(int column, qreal width) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::MaximumSize, column, width, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the maximum width for \a column. +*/ +qreal QGraphicsGridLayout::columnMaximumWidth(int column) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSizeHint(Qt::MaximumSize, column, Qt::Horizontal); +} + +/*! + Sets the fixed width of \a column to \a width. +*/ +void QGraphicsGridLayout::setColumnFixedWidth(int column, qreal width) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::MinimumSize, column, width, Qt::Horizontal); + d->engine.setRowSizeHint(Qt::MaximumSize, column, width, Qt::Horizontal); + invalidate(); +} + +/*! + Sets the alignment of \a row to \a alignment. +*/ +void QGraphicsGridLayout::setRowAlignment(int row, Qt::Alignment alignment) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowAlignment(row, alignment, Qt::Vertical); + invalidate(); +} + +/*! + Returns the alignment of \a row. +*/ +Qt::Alignment QGraphicsGridLayout::rowAlignment(int row) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowAlignment(row, Qt::Vertical); +} + +/*! + Sets the alignment for \a column to \a alignment. +*/ +void QGraphicsGridLayout::setColumnAlignment(int column, Qt::Alignment alignment) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowAlignment(column, alignment, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the alignment for \a column. +*/ +Qt::Alignment QGraphicsGridLayout::columnAlignment(int column) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowAlignment(column, Qt::Horizontal); +} + +/*! + Sets the alignment for \a item to \a alignment. +*/ +void QGraphicsGridLayout::setAlignment(QGraphicsLayoutItem *item, Qt::Alignment alignment) +{ + Q_D(QGraphicsGridLayout); + d->engine.setAlignment(item, alignment); + invalidate(); +} + +/*! + Returns the alignment for \a item. +*/ +Qt::Alignment QGraphicsGridLayout::alignment(QGraphicsLayoutItem *item) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.alignment(item); +} + +/*! + Returns the number of rows in the grid layout. This is always one more + than the index of the last row that is occupied by a layout item (empty + rows are counted except for those at the end). +*/ +int QGraphicsGridLayout::rowCount() const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.effectiveLastRow(Qt::Vertical) + 1; +} + +/*! + Returns the number of columns in the grid layout. This is always one more + than the index of the last column that is occupied by a layout item (empty + columns are counted except for those at the end). +*/ +int QGraphicsGridLayout::columnCount() const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.effectiveLastRow(Qt::Horizontal) + 1; +} + +/*! + Returns a pointer to the layout item at (\a row, \a column). +*/ +QGraphicsLayoutItem *QGraphicsGridLayout::itemAt(int row, int column) const +{ + Q_D(const QGraphicsGridLayout); + if (row < 0 || row >= rowCount() || column < 0 || column >= columnCount()) { + qWarning("QGraphicsGridLayout::itemAt: invalid row, column %d, %d", row, column); + return 0; + } + if (QGridLayoutItem *item = d->engine.itemAt(row, column)) + return item->layoutItem(); + return 0; +} + +/*! + Returns the number of layout items in this grid layout. +*/ +int QGraphicsGridLayout::count() const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.itemCount(); +} + +/*! + Returns the layout item at \a index, or 0 if there is no layout item at + this index. +*/ +QGraphicsLayoutItem *QGraphicsGridLayout::itemAt(int index) const +{ + Q_D(const QGraphicsGridLayout); + if (index < 0 || index >= d->engine.itemCount()) { + qWarning("QGraphicsGridLayout::itemAt: invalid index %d", index); + return 0; + } + QGraphicsLayoutItem *item = 0; + if (QGridLayoutItem *gridItem = d->engine.itemAt(index)) + item = gridItem->layoutItem(); + return item; +} + +/*! + Removes the layout item at \a index without destroying it. Ownership of + the item is transferred to the caller. + + \sa addItem() +*/ +void QGraphicsGridLayout::removeAt(int index) +{ + Q_D(QGraphicsGridLayout); + if (index < 0 || index >= d->engine.itemCount()) { + qWarning("QGraphicsGridLayout::removeAt: invalid index %d", index); + return; + } + if (QGridLayoutItem *gridItem = d->engine.itemAt(index)) { + if (QGraphicsLayoutItem *layoutItem = gridItem->layoutItem()) + layoutItem->setParentLayoutItem(0); + d->engine.removeItem(gridItem); + + // recalculate rowInfo.count if we remove an item that is on the right/bottommost row + for (int j = 0; j < NOrientations; ++j) { + // 0: Hor, 1: Ver + const Qt::Orientation orient = (j == 0 ? Qt::Horizontal : Qt::Vertical); + const int oldCount = d->engine.rowCount(orient); + if (gridItem->lastRow(orient) == oldCount - 1) { + const int newCount = d->engine.effectiveLastRow(orient) + 1; + d->engine.removeRows(newCount, oldCount - newCount, orient); + } + } + + delete gridItem; + invalidate(); + } +} + +/*! + Removes the layout item \a item without destroying it. + Ownership of the item is transferred to the caller. + + \sa addItem() +*/ +void QGraphicsGridLayout::removeItem(QGraphicsLayoutItem *item) +{ + Q_D(QGraphicsGridLayout); + int index = d->engine.indexOf(item); + removeAt(index); +} +/*! + \reimp +*/ +void QGraphicsGridLayout::invalidate() +{ + Q_D(QGraphicsGridLayout); + d->engine.invalidate(); + QGraphicsLayout::invalidate(); +} + +#ifdef QT_DEBUG +void QGraphicsGridLayoutPrivate::dump(int indent) const +{ + if (qt_graphicsLayoutDebug()) { + engine.dump(indent + 1); + } +} +#endif + +/*! + Sets the bounding geometry of the grid layout to \a rect. +*/ +void QGraphicsGridLayout::setGeometry(const QRectF &rect) +{ + Q_D(QGraphicsGridLayout); + QGraphicsLayout::setGeometry(rect); + QRectF effectiveRect = geometry(); + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + Qt::LayoutDirection visualDir = d->visualDirection(); + d->engine.setVisualDirection(visualDir); + if (visualDir == Qt::RightToLeft) + qSwap(left, right); + effectiveRect.adjust(+left, +top, -right, -bottom); + d->engine.setGeometries(d->styleInfo(), effectiveRect); +#ifdef QT_DEBUG + if (qt_graphicsLayoutDebug()) { + static int counter = 0; + qDebug("==== BEGIN DUMP OF QGraphicsGridLayout (%d)====", counter++); + d->dump(1); + qDebug("==== END DUMP OF QGraphicsGridLayout ===="); + } +#endif +} + +/*! + \reimp +*/ +QSizeF QGraphicsGridLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_D(const QGraphicsGridLayout); + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + const QSizeF extraMargins(left + right, top + bottom); + return d->engine.sizeHint(d->styleInfo(), which , constraint - extraMargins) + extraMargins; +} + + +#if 0 +// ### kill? (implement and kill?) +QRect QGraphicsGridLayout::cellRect(int row, int column, int rowSpan, int columnSpan) const +{ + Q_D(const QGraphicsGridLayout); + return QRect(); +// return d->engine.cellRect(parentLayoutable(), contentsGeometry(), row, column, rowSpan, columnSpan); +} + +QSizePolicy::ControlTypes QGraphicsGridLayout::controlTypes(LayoutSide side) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.controlTypes(side); +} +#endif + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicsgridlayout.h b/src/widgets/graphicsview/qgraphicsgridlayout.h new file mode 100644 index 0000000000..f493eb6153 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsgridlayout.h @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSGRIDLAYOUT_H +#define QGRAPHICSGRIDLAYOUT_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsGridLayoutPrivate; + +class Q_GUI_EXPORT QGraphicsGridLayout : public QGraphicsLayout +{ +public: + QGraphicsGridLayout(QGraphicsLayoutItem *parent = 0); + virtual ~QGraphicsGridLayout(); + + void addItem(QGraphicsLayoutItem *item, int row, int column, int rowSpan, int columnSpan, + Qt::Alignment alignment = 0); + inline void addItem(QGraphicsLayoutItem *item, int row, int column, Qt::Alignment alignment = 0); + + void setHorizontalSpacing(qreal spacing); + qreal horizontalSpacing() const; + void setVerticalSpacing(qreal spacing); + qreal verticalSpacing() const; + void setSpacing(qreal spacing); + + void setRowSpacing(int row, qreal spacing); + qreal rowSpacing(int row) const; + void setColumnSpacing(int column, qreal spacing); + qreal columnSpacing(int column) const; + + void setRowStretchFactor(int row, int stretch); + int rowStretchFactor(int row) const; + void setColumnStretchFactor(int column, int stretch); + int columnStretchFactor(int column) const; + + void setRowMinimumHeight(int row, qreal height); + qreal rowMinimumHeight(int row) const; + void setRowPreferredHeight(int row, qreal height); + qreal rowPreferredHeight(int row) const; + void setRowMaximumHeight(int row, qreal height); + qreal rowMaximumHeight(int row) const; + void setRowFixedHeight(int row, qreal height); + + void setColumnMinimumWidth(int column, qreal width); + qreal columnMinimumWidth(int column) const; + void setColumnPreferredWidth(int column, qreal width); + qreal columnPreferredWidth(int column) const; + void setColumnMaximumWidth(int column, qreal width); + qreal columnMaximumWidth(int column) const; + void setColumnFixedWidth(int column, qreal width); + + void setRowAlignment(int row, Qt::Alignment alignment); + Qt::Alignment rowAlignment(int row) const; + void setColumnAlignment(int column, Qt::Alignment alignment); + Qt::Alignment columnAlignment(int column) const; + + void setAlignment(QGraphicsLayoutItem *item, Qt::Alignment alignment); + Qt::Alignment alignment(QGraphicsLayoutItem *item) const; + + int rowCount() const; + int columnCount() const; + + QGraphicsLayoutItem *itemAt(int row, int column) const; + + // inherited from QGraphicsLayout + int count() const; + QGraphicsLayoutItem *itemAt(int index) const; + void removeAt(int index); + void removeItem(QGraphicsLayoutItem *item); + + void invalidate(); + + // inherited from QGraphicsLayoutItem + void setGeometry(const QRectF &rect); + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + + // #### + //QRect cellRect(int row, int column, int rowSpan = 1, int columnSpan = 1) const; + //QSizePolicy::ControlTypes controlTypes(LayoutSide side) const; + +private: + Q_DISABLE_COPY(QGraphicsGridLayout) + Q_DECLARE_PRIVATE(QGraphicsGridLayout) +}; + +inline void QGraphicsGridLayout::addItem(QGraphicsLayoutItem *aitem, int arow, int acolumn, Qt::Alignment aalignment) +{ addItem(aitem, arow, acolumn, 1, 1, aalignment); } + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif + diff --git a/src/widgets/graphicsview/qgraphicsitem.cpp b/src/widgets/graphicsview/qgraphicsitem.cpp new file mode 100644 index 0000000000..e67fe82045 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsitem.cpp @@ -0,0 +1,11597 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +/*! + \class QGraphicsItem + \brief The QGraphicsItem class is the base class for all graphical + items in a QGraphicsScene. + \since 4.2 + + \ingroup graphicsview-api + + It provides a light-weight foundation for writing your own custom items. + This includes defining the item's geometry, collision detection, its + painting implementation and item interaction through its event handlers. + QGraphicsItem is part of the \l{Graphics View Framework} + + \image graphicsview-items.png + + For convenience, Qt provides a set of standard graphics items for the most + common shapes. These are: + + \list + \o QGraphicsEllipseItem provides an ellipse item + \o QGraphicsLineItem provides a line item + \o QGraphicsPathItem provides an arbitrary path item + \o QGraphicsPixmapItem provides a pixmap item + \o QGraphicsPolygonItem provides a polygon item + \o QGraphicsRectItem provides a rectangular item + \o QGraphicsSimpleTextItem provides a simple text label item + \o QGraphicsTextItem provides an advanced text browser item + \endlist + + All of an item's geometric information is based on its local coordinate + system. The item's position, pos(), is the only function that does not + operate in local coordinates, as it returns a position in parent + coordinates. \l {The Graphics View Coordinate System} describes the coordinate + system in detail. + + You can set whether an item should be visible (i.e., drawn, and accepting + events), by calling setVisible(). Hiding an item will also hide its + children. Similarly, you can enable or disable an item by calling + setEnabled(). If you disable an item, all its children will also be + disabled. By default, items are both visible and enabled. To toggle + whether an item is selected or not, first enable selection by setting + the ItemIsSelectable flag, and then call setSelected(). Normally, + selection is toggled by the scene, as a result of user interaction. + + To write your own graphics item, you first create a subclass of + QGraphicsItem, and then start by implementing its two pure virtual public + functions: boundingRect(), which returns an estimate of the area painted + by the item, and paint(), which implements the actual painting. For + example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 0 + + The boundingRect() function has many different purposes. + QGraphicsScene bases its item index on boundingRect(), and + QGraphicsView uses it both for culling invisible items, and for + determining the area that needs to be recomposed when drawing + overlapping items. In addition, QGraphicsItem's collision + detection mechanisms use boundingRect() to provide an efficient + cut-off. The fine grained collision algorithm in + collidesWithItem() is based on calling shape(), which returns an + accurate outline of the item's shape as a QPainterPath. + + QGraphicsScene expects all items boundingRect() and shape() to + remain unchanged unless it is notified. If you want to change an + item's geometry in any way, you must first call + prepareGeometryChange() to allow QGraphicsScene to update its + bookkeeping. + + Collision detection can be done in two ways: + + \list 1 + + \o Reimplement shape() to return an accurate shape for your item, + and rely on the default implementation of collidesWithItem() to do + shape-shape intersection. This can be rather expensive if the + shapes are complex. + + \o Reimplement collidesWithItem() to provide your own custom item + and shape collision algorithm. + + \endlist + + The contains() function can be called to determine whether the item \e + contains a point or not. This function can also be reimplemented by the + item. The default behavior of contains() is based on calling shape(). + + Items can contain other items, and also be contained by other items. All + items can have a parent item and a list of children. Unless the item has + no parent, its position is in \e parent coordinates (i.e., the parent's + local coordinates). Parent items propagate both their position and their + transformation to all children. + + \img graphicsview-parentchild.png + + \target Transformations + \section1 Transformations + + QGraphicsItem supports projective transformations in addition to its base + position, pos(). There are several ways to change an item's transformation. + For simple transformations, you can call either of the convenience + functions setRotation() or setScale(), or you can pass any transformation + matrix to setTransform(). For advanced transformation control you also have + the option of setting several combined transformations by calling + setTransformations(). + + Item transformations accumulate from parent to child, so if both a parent + and child item are rotated 90 degrees, the child's total transformation + will be 180 degrees. Similarly, if the item's parent is scaled to 2x its + original size, its children will also be twice as large. An item's + transformation does not affect its own local geometry; all geometry + functions (e.g., contains(), update(), and all the mapping functions) still + operate in local coordinates. For convenience, QGraphicsItem provides the + functions sceneTransform(), which returns the item's total transformation + matrix (including its position and all parents' positions and + transformations), and scenePos(), which returns its position in scene + coordinates. To reset an item's matrix, call resetTransform(). + + Certain transformation operations produce a different outcome depending on + the order in which they are applied. For example, if you scale an + transform, and then rotate it, you may get a different result than if the + transform was rotated first. However, the order you set the transformation + properties on QGraphicsItem does not affect the resulting transformation; + QGraphicsItem always applies the properties in a fixed, defined order: + + \list + \o The item's base transform is applied (transform()) + \o The item's transformations list is applied in order (transformations()) + \o The item is rotated relative to its transform origin point (rotation(), transformOriginPoint()) + \o The item is scaled relative to its transform origin point (scale(), transformOriginPoint()) + \endlist + + \section1 Painting + + The paint() function is called by QGraphicsView to paint the item's + contents. The item has no background or default fill of its own; whatever + is behind the item will shine through all areas that are not explicitly + painted in this function. You can call update() to schedule a repaint, + optionally passing the rectangle that needs a repaint. Depending on + whether or not the item is visible in a view, the item may or may not be + repainted; there is no equivalent to QWidget::repaint() in QGraphicsItem. + + Items are painted by the view, starting with the parent items and then + drawing children, in ascending stacking order. You can set an item's + stacking order by calling setZValue(), and test it by calling + zValue(), where items with low z-values are painted before items with + high z-values. Stacking order applies to sibling items; parents are always + drawn before their children. + + \section1 Sorting + + All items are drawn in a defined, stable order, and this same order decides + which items will receive mouse input first when you click on the scene. + Normally you don't have to worry about sorting, as the items follow a + "natural order", following the logical structure of the scene. + + An item's children are stacked on top of the parent, and sibling items are + stacked by insertion order (i.e., in the same order that they were either + added to the scene, or added to the same parent). If you add item A, and + then B, then B will be on top of A. If you then add C, the items' stacking + order will be A, then B, then C. + + \image graphicsview-zorder.png + + This example shows the stacking order of all limbs of the robot from the + \l{graphicsview/dragdroprobot}{Drag and Drop Robot} example. The torso is + the root item (all other items are children or descendants of the torso), + so it is drawn first. Next, the head is drawn, as it is the first item in + the torso's list of children. Then the upper left arm is drawn. As the + lower arm is a child of the upper arm, the lower arm is then drawn, + followed by the upper arm's next sibling, which is the upper right arm, and + so on. + + For advanced users, there are ways to alter how your items are sorted: + + \list + \o You can call setZValue() on an item to explicitly stack it on top of, or + under, other sibling items. The default Z value for an item is 0. Items + with the same Z value are stacked by insertion order. + + \o You can call stackBefore() to reorder the list of children. This will + directly modify the insertion order. + + \o You can set the ItemStacksBehindParent flag to stack a child item behind + its parent. + \endlist + + The stacking order of two sibling items also counts for each item's + children and descendant items. So if one item is on top of another, then + all its children will also be on top of all the other item's children as + well. + + \section1 Events + + QGraphicsItem receives events from QGraphicsScene through the virtual + function sceneEvent(). This function distributes the most common events + to a set of convenience event handlers: + + \list + \o contextMenuEvent() handles context menu events + \o focusInEvent() and focusOutEvent() handle focus in and out events + \o hoverEnterEvent(), hoverMoveEvent(), and hoverLeaveEvent() handles + hover enter, move and leave events + \o inputMethodEvent() handles input events, for accessibility support + \o keyPressEvent() and keyReleaseEvent() handle key press and release events + \o mousePressEvent(), mouseMoveEvent(), mouseReleaseEvent(), and + mouseDoubleClickEvent() handles mouse press, move, release, click and + doubleclick events + \endlist + + You can filter events for any other item by installing event filters. This + functionality is separate from Qt's regular event filters (see + QObject::installEventFilter()), which only work on subclasses of QObject. After + installing your item as an event filter for another item by calling + installSceneEventFilter(), the filtered events will be received by the virtual + function sceneEventFilter(). You can remove item event filters by calling + removeSceneEventFilter(). + + \section1 Custom Data + + Sometimes it's useful to register custom data with an item, be it a custom + item, or a standard item. You can call setData() on any item to store data + in it using a key-value pair (the key being an integer, and the value is a + QVariant). To get custom data from an item, call data(). This + functionality is completely untouched by Qt itself; it is provided for the + user's convenience. + + \sa QGraphicsScene, QGraphicsView, {Graphics View Framework} +*/ + +/*! + \variable QGraphicsItem::Type + + The type value returned by the virtual type() function in standard + graphics item classes in Qt. All such standard graphics item + classes in Qt are associated with a unique value for Type, + e.g. the value returned by QGraphicsPathItem::type() is 2. + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 18 +*/ + +/*! + \variable QGraphicsItem::UserType + + The lowest permitted type value for custom items (subclasses + of QGraphicsItem or any of the standard items). This value is + used in conjunction with a reimplementation of QGraphicsItem::type() + and declaring a Type enum value. Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 1 + + \note UserType = 65536 +*/ + +/*! + \enum QGraphicsItem::GraphicsItemFlag + + This enum describes different flags that you can set on an item to + toggle different features in the item's behavior. + + All flags are disabled by default. + + \value ItemIsMovable The item supports interactive movement using + the mouse. By clicking on the item and then dragging, the item + will move together with the mouse cursor. If the item has + children, all children are also moved. If the item is part of a + selection, all selected items are also moved. This feature is + provided as a convenience through the base implementation of + QGraphicsItem's mouse event handlers. + + \value ItemIsSelectable The item supports selection. Enabling this + feature will enable setSelected() to toggle selection for the + item. It will also let the item be selected automatically as a + result of calling QGraphicsScene::setSelectionArea(), by clicking + on an item, or by using rubber band selection in QGraphicsView. + + \value ItemIsFocusable The item supports keyboard input focus (i.e., it is + an input item). Enabling this flag will allow the item to accept focus, + which again allows the delivery of key events to + QGraphicsItem::keyPressEvent() and QGraphicsItem::keyReleaseEvent(). + + \value ItemClipsToShape The item clips to its own shape. The item cannot + draw or receive mouse, tablet, drag and drop or hover events outside its + shape. It is disabled by default. This behavior is enforced by + QGraphicsView::drawItems() or QGraphicsScene::drawItems(). This flag was + introduced in Qt 4.3. + + \value ItemClipsChildrenToShape The item clips the painting of all its + descendants to its own shape. Items that are either direct or indirect + children of this item cannot draw outside this item's shape. By default, + this flag is disabled; children can draw anywhere. This behavior is + enforced by QGraphicsView::drawItems() or + QGraphicsScene::drawItems(). This flag was introduced in Qt 4.3. + + \value ItemIgnoresTransformations The item ignores inherited + transformations (i.e., its position is still anchored to its parent, but + the parent or view rotation, zoom or shear transformations are ignored). + This flag is useful for keeping text label items horizontal and unscaled, + so they will still be readable if the view is transformed. When set, the + item's view geometry and scene geometry will be maintained separately. You + must call deviceTransform() to map coordinates and detect collisions in + the view. By default, this flag is disabled. This flag was introduced in + Qt 4.3. \note With this flag set you can still scale the item itself, and + that scale transformation will influence the item's children. + + \value ItemIgnoresParentOpacity The item ignores its parent's opacity. The + item's effective opacity is the same as its own; it does not combine with + the parent's opacity. This flags allows your item to keep its absolute + opacity even if the parent is semitransparent. This flag was introduced in + Qt 4.5. + + \value ItemDoesntPropagateOpacityToChildren The item doesn't propagate its + opacity to its children. This flag allows you to create a semitransparent + item that does not affect the opacity of its children. This flag was + introduced in Qt 4.5. + + \value ItemStacksBehindParent The item is stacked behind its parent. By + default, child items are stacked on top of the parent item. But setting + this flag, the child will be stacked behind it. This flag is useful for + drop shadow effects and for decoration objects that follow the parent + item's geometry without drawing on top of it. This flag was introduced + in Qt 4.5. + + \value ItemUsesExtendedStyleOption The item makes use of either + \l{QStyleOptionGraphicsItem::} {exposedRect} or + \l{QStyleOptionGraphicsItem::} {matrix} in + QStyleOptionGraphicsItem. By default, the + \l{QStyleOptionGraphicsItem::} {exposedRect} is initialized to the + item's boundingRect() and the + \l{QStyleOptionGraphicsItem::}{matrix} is untransformed. You can + enable this flag for the style options to be set up with more + fine-grained values. Note that + QStyleOptionGraphicsItem::levelOfDetail is unaffected by this flag + and always initialized to 1. Use + QStyleOptionGraphicsItem::levelOfDetailFromTransform() if you need + a higher value. This flag was introduced in Qt 4.6. + + \value ItemHasNoContents The item does not paint anything (i.e., calling + paint() on the item has no effect). You should set this flag on items that + do not need to be painted to ensure that Graphics View avoids unnecessary + painting preparations. This flag was introduced in Qt 4.6. + + \value ItemSendsGeometryChanges The item enables itemChange() + notifications for ItemPositionChange, ItemPositionHasChanged, + ItemMatrixChange, ItemTransformChange, ItemTransformHasChanged, + ItemRotationChange, ItemRotationHasChanged, ItemScaleChange, ItemScaleHasChanged, + ItemTransformOriginPointChange, and ItemTransformOriginPointHasChanged. For + performance reasons, these notifications are disabled by default. You must + enable this flag to receive notifications for position and transform + changes. This flag was introduced in Qt 4.6. + + \value ItemAcceptsInputMethod The item supports input methods typically + used for Asian languages. + This flag was introduced in Qt 4.6. + + \value ItemNegativeZStacksBehindParent The item automatically + stacks behind it's parent if it's z-value is negative. This flag + enables setZValue() to toggle ItemStacksBehindParent. This flag + was introduced in Qt 4.6. + + \value ItemIsPanel The item is a panel. A panel provides activation and + contained focus handling. Only one panel can be active at a time (see + QGraphicsItem::isActive()). When no panel is active, QGraphicsScene + activates all non-panel items. Window items (i.e., + QGraphicsItem::isWindow() returns true) are panels. This flag was + introduced in Qt 4.6. + + \omitvalue ItemIsFocusScope \omit Internal only (for now). \endomit + + \value ItemSendsScenePositionChanges The item enables itemChange() + notifications for ItemScenePositionHasChanged. For performance reasons, + these notifications are disabled by default. You must enable this flag + to receive notifications for scene position changes. This flag was + introduced in Qt 4.6. + + \omitvalue ItemStopsClickFocusPropagation \omit The item stops propagating + click focus to items underneath when being clicked on. This flag + allows you create a non-focusable item that can be clicked on without + changing the focus. \endomit + + \omitvalue ItemStopsFocusHandling \omit Same as + ItemStopsClickFocusPropagation, but also suppresses focus-out. This flag + allows you to completely take over focus handling. + This flag was introduced in Qt 4.7. \endomit +*/ + +/*! + \enum QGraphicsItem::GraphicsItemChange + + This enum describes the state changes that are notified by + QGraphicsItem::itemChange(). The notifications are sent as the state + changes, and in some cases, adjustments can be made (see the documentation + for each change for details). + + Note: Be careful with calling functions on the QGraphicsItem itself inside + itemChange(), as certain function calls can lead to unwanted + recursion. For example, you cannot call setPos() in itemChange() on an + ItemPositionChange notification, as the setPos() function will again call + itemChange(ItemPositionChange). Instead, you can return the new, adjusted + position from itemChange(). + + \value ItemEnabledChange The item's enabled state changes. If the item is + presently enabled, it will become disabled, and vice verca. The value + argument is the new enabled state (i.e., true or false). Do not call + setEnabled() in itemChange() as this notification is delivered. Instead, + you can return the new state from itemChange(). + + \value ItemEnabledHasChanged The item's enabled state has changed. The + value argument is the new enabled state (i.e., true or false). Do not call + setEnabled() in itemChange() as this notification is delivered. The return + value is ignored. + + \value ItemMatrixChange The item's affine transformation matrix is + changing. This value is obsolete; you can use ItemTransformChange instead. + + \value ItemPositionChange The item's position changes. This notification + is sent if the ItemSendsGeometryChanges flag is enabled, and when the + item's local position changes, relative to its parent (i.e., as a result + of calling setPos() or moveBy()). The value argument is the new position + (i.e., a QPointF). You can call pos() to get the original position. Do + not call setPos() or moveBy() in itemChange() as this notification is + delivered; instead, you can return the new, adjusted position from + itemChange(). After this notification, QGraphicsItem immediately sends the + ItemPositionHasChanged notification if the position changed. + + \value ItemPositionHasChanged The item's position has changed. This + notification is sent if the ItemSendsGeometryChanges flag is enabled, and + after the item's local position, relative to its parent, has changed. The + value argument is the new position (the same as pos()), and QGraphicsItem + ignores the return value for this notification (i.e., a read-only + notification). + + \value ItemTransformChange The item's transformation matrix changes. This + notification is send if the ItemSendsGeometryChanges flag is enabled, and + when the item's local transformation matrix changes (i.e., as a result of + calling setTransform(). The value argument is the new matrix (i.e., a + QTransform); to get the old matrix, call transform(). Do not call + setTransform() or set any of the transformation properties in itemChange() + as this notification is delivered; instead, you can return the new matrix + from itemChange(). This notification is not sent if you change the + transformation properties. + + \value ItemTransformHasChanged The item's transformation matrix has + changed either because setTransform is called, or one of the + transformation properties is changed. This notification is sent if the + ItemSendsGeometryChanges flag is enabled, and after the item's local + transformation matrix has changed. The value argument is the new matrix + (same as transform()), and QGraphicsItem ignores the return value for this + notification (i.e., a read-only notification). + + \value ItemRotationChange The item's rotation property changes. This + notification is sent if the ItemSendsGeometryChanges flag is enabled, and + when the item's rotation property changes (i.e., as a result of calling + setRotation()). The value argument is the new rotation (i.e., a double); + to get the old rotation, call rotation(). Do not call setRotation() in + itemChange() as this notification is delivered; instead, you can return + the new rotation from itemChange(). + + \value ItemRotationHasChanged The item's rotation property has changed. + This notification is sent if the ItemSendsGeometryChanges flag is enabled, + and after the item's rotation property has changed. The value argument is + the new rotation (i.e., a double), and QGraphicsItem ignores the return + value for this notification (i.e., a read-only notification). Do not call + setRotation() in itemChange() as this notification is delivered. + + \value ItemScaleChange The item's scale property changes. This notification + is sent if the ItemSendsGeometryChanges flag is enabled, and when the item's + scale property changes (i.e., as a result of calling setScale()). The value + argument is the new scale (i.e., a double); to get the old scale, call + scale(). Do not call setScale() in itemChange() as this notification is + delivered; instead, you can return the new scale from itemChange(). + + \value ItemScaleHasChanged The item's scale property has changed. This + notification is sent if the ItemSendsGeometryChanges flag is enabled, and + after the item's scale property has changed. The value argument is the new + scale (i.e., a double), and QGraphicsItem ignores the return value for this + notification (i.e., a read-only notification). Do not call setScale() in + itemChange() as this notification is delivered. + + \value ItemTransformOriginPointChange The item's transform origin point + property changes. This notification is sent if the ItemSendsGeometryChanges + flag is enabled, and when the item's transform origin point property changes + (i.e., as a result of calling setTransformOriginPoint()). The value argument + is the new origin point (i.e., a QPointF); to get the old origin point, call + transformOriginPoint(). Do not call setTransformOriginPoint() in itemChange() + as this notification is delivered; instead, you can return the new transform + origin point from itemChange(). + + \value ItemTransformOriginPointHasChanged The item's transform origin point + property has changed. This notification is sent if the ItemSendsGeometryChanges + flag is enabled, and after the item's transform origin point property has + changed. The value argument is the new origin point (i.e., a QPointF), and + QGraphicsItem ignores the return value for this notification (i.e., a read-only + notification). Do not call setTransformOriginPoint() in itemChange() as this + notification is delivered. + + \value ItemSelectedChange The item's selected state changes. If the item is + presently selected, it will become unselected, and vice verca. The value + argument is the new selected state (i.e., true or false). Do not call + setSelected() in itemChange() as this notification is delivered; instead, you + can return the new selected state from itemChange(). + + \value ItemSelectedHasChanged The item's selected state has changed. The + value argument is the new selected state (i.e., true or false). Do not + call setSelected() in itemChange() as this notification is delivered. The + return value is ignored. + + \value ItemVisibleChange The item's visible state changes. If the item is + presently visible, it will become invisible, and vice verca. The value + argument is the new visible state (i.e., true or false). Do not call + setVisible() in itemChange() as this notification is delivered; instead, + you can return the new visible state from itemChange(). + + \value ItemVisibleHasChanged The item's visible state has changed. The + value argument is the new visible state (i.e., true or false). Do not call + setVisible() in itemChange() as this notification is delivered. The return + value is ignored. + + \value ItemParentChange The item's parent changes. The value argument is + the new parent item (i.e., a QGraphicsItem pointer). Do not call + setParentItem() in itemChange() as this notification is delivered; + instead, you can return the new parent from itemChange(). + + \value ItemParentHasChanged The item's parent has changed. The value + argument is the new parent (i.e., a pointer to a QGraphicsItem). Do not + call setParentItem() in itemChange() as this notification is + delivered. The return value is ignored. + + \value ItemChildAddedChange A child is added to this item. The value + argument is the new child item (i.e., a QGraphicsItem pointer). Do not + pass this item to any item's setParentItem() function as this notification + is delivered. The return value is unused; you cannot adjust anything in + this notification. Note that the new child might not be fully constructed + when this notification is sent; calling pure virtual functions on + the child can lead to a crash. + + \value ItemChildRemovedChange A child is removed from this item. The value + argument is the child item that is about to be removed (i.e., a + QGraphicsItem pointer). The return value is unused; you cannot adjust + anything in this notification. + + \value ItemSceneChange The item is moved to a new scene. This notification is + also sent when the item is added to its initial scene, and when it is removed. + The item's scene() is the old scene (or 0 if the item has not been added to a + scene yet). The value argument is the new scene (i.e., a QGraphicsScene + pointer), or a null pointer if the item is removed from a scene. Do not + override this change by passing this item to QGraphicsScene::addItem() as this + notification is delivered; instead, you can return the new scene from + itemChange(). Use this feature with caution; objecting to a scene change can + quickly lead to unwanted recursion. + + \value ItemSceneHasChanged The item's scene has changed. The item's scene() is + the new scene. This notification is also sent when the item is added to its + initial scene, and when it is removed.The value argument is the new scene + (i.e., a pointer to a QGraphicsScene). Do not call setScene() in itemChange() + as this notification is delivered. The return value is ignored. + + \value ItemCursorChange The item's cursor changes. The value argument is + the new cursor (i.e., a QCursor). Do not call setCursor() in itemChange() + as this notification is delivered. Instead, you can return a new cursor + from itemChange(). + + \value ItemCursorHasChanged The item's cursor has changed. The value + argument is the new cursor (i.e., a QCursor). Do not call setCursor() as + this notification is delivered. The return value is ignored. + + \value ItemToolTipChange The item's tooltip changes. The value argument is + the new tooltip (i.e., a QToolTip). Do not call setToolTip() in + itemChange() as this notification is delivered. Instead, you can return a + new tooltip from itemChange(). + + \value ItemToolTipHasChanged The item's tooltip has changed. The value + argument is the new tooltip (i.e., a QToolTip). Do not call setToolTip() + as this notification is delivered. The return value is ignored. + + \value ItemFlagsChange The item's flags change. The value argument is the + new flags (i.e., a quint32). Do not call setFlags() in itemChange() as + this notification is delivered. Instead, you can return the new flags from + itemChange(). + + \value ItemFlagsHaveChanged The item's flags have changed. The value + argument is the new flags (i.e., a quint32). Do not call setFlags() in + itemChange() as this notification is delivered. The return value is + ignored. + + \value ItemZValueChange The item's Z-value changes. The value argument is + the new Z-value (i.e., a double). Do not call setZValue() in itemChange() + as this notification is delivered. Instead, you can return a new Z-value + from itemChange(). + + \value ItemZValueHasChanged The item's Z-value has changed. The value + argument is the new Z-value (i.e., a double). Do not call setZValue() as + this notification is delivered. The return value is ignored. + + \value ItemOpacityChange The item's opacity changes. The value argument is + the new opacity (i.e., a double). Do not call setOpacity() in itemChange() + as this notification is delivered. Instead, you can return a new opacity + from itemChange(). + + \value ItemOpacityHasChanged The item's opacity has changed. The value + argument is the new opacity (i.e., a double). Do not call setOpacity() as + this notification is delivered. The return value is ignored. + + \value ItemScenePositionHasChanged The item's scene position has changed. + This notification is sent if the ItemSendsScenePositionChanges flag is + enabled, and after the item's scene position has changed (i.e., the + position or transformation of the item itself or the position or + transformation of any ancestor has changed). The value argument is the + new scene position (the same as scenePos()), and QGraphicsItem ignores + the return value for this notification (i.e., a read-only notification). +*/ + +/*! + \enum QGraphicsItem::CacheMode + \since 4.4 + + This enum describes QGraphicsItem's cache modes. Caching is used to speed + up rendering by allocating and rendering to an off-screen pixel buffer, + which can be reused when the item requires redrawing. For some paint + devices, the cache is stored directly in graphics memory, which makes + rendering very quick. + + \value NoCache The default; all item caching is + disabled. QGraphicsItem::paint() is called every time the item needs + redrawing. + + \value ItemCoordinateCache Caching is enabled for the item's logical + (local) coordinate system. QGraphicsItem creates an off-screen pixel + buffer with a configurable size / resolution that you can pass to + QGraphicsItem::setCacheMode(). Rendering quality will typically degrade, + depending on the resolution of the cache and the item transformation. The + first time the item is redrawn, it will render itself into the cache, and + the cache is then reused for every subsequent expose. The cache is also + reused as the item is transformed. To adjust the resolution of the cache, + you can call setCacheMode() again. + + \value DeviceCoordinateCache Caching is enabled at the paint device level, + in device coordinates. This mode is for items that can move, but are not + rotated, scaled or sheared. If the item is transformed directly or + indirectly, the cache will be regenerated automatically. Unlike + ItemCoordinateCacheMode, DeviceCoordinateCache always renders at maximum + quality. + + \sa QGraphicsItem::setCacheMode() +*/ + +/*! + \enum QGraphicsItem::Extension + \internal + + Note: This is provided as a hook to avoid future problems related + to adding virtual functions. See also extension(), + supportsExtension() and setExtension(). +*/ + +/*! + \enum QGraphicsItem::PanelModality + \since 4.6 + + This enum specifies the behavior of a modal panel. A modal panel + is one that blocks input to other panels. Note that items that + are children of a modal panel are not blocked. + + The values are: + + \value NonModal The panel is not modal and does not block input to + other panels. This is the default value for panels. + + \value PanelModal The panel is modal to a single item hierarchy + and blocks input to its parent pane, all grandparent panels, and + all siblings of its parent and grandparent panels. + + \value SceneModal The window is modal to the entire scene and + blocks input to all panels. + + \sa QGraphicsItem::setPanelModality(), QGraphicsItem::panelModality(), QGraphicsItem::ItemIsPanel +*/ + +#include "qgraphicsitem.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicsscene.h" +#include "qgraphicsscene_p.h" +#include "qgraphicssceneevent.h" +#include "qgraphicsview.h" +#include "qgraphicswidget.h" +#include "qgraphicsproxywidget.h" +#include "qgraphicsscenebsptreeindex_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef QT_NO_ACCESSIBILITY +# include "qaccessible.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_WS_X11 +#include +#include +#endif + +#include + +#include + +QT_BEGIN_NAMESPACE + +static inline void _q_adjustRect(QRect *rect) +{ + Q_ASSERT(rect); + if (!rect->width()) + rect->adjust(0, 0, 1, 0); + if (!rect->height()) + rect->adjust(0, 0, 0, 1); +} + +/* + ### Move this into QGraphicsItemPrivate + */ +class QGraphicsItemCustomDataStore +{ +public: + QMap > data; +}; +Q_GLOBAL_STATIC(QGraphicsItemCustomDataStore, qt_dataStore) + +/*! + \internal + + Returns a QPainterPath of \a path when stroked with the \a pen. + Ignoring dash pattern. +*/ +static QPainterPath qt_graphicsItem_shapeFromPath(const QPainterPath &path, const QPen &pen) +{ + // We unfortunately need this hack as QPainterPathStroker will set a width of 1.0 + // if we pass a value of 0.0 to QPainterPathStroker::setWidth() + const qreal penWidthZero = qreal(0.00000001); + + if (path == QPainterPath()) + return path; + QPainterPathStroker ps; + ps.setCapStyle(pen.capStyle()); + if (pen.widthF() <= 0.0) + ps.setWidth(penWidthZero); + else + ps.setWidth(pen.widthF()); + ps.setJoinStyle(pen.joinStyle()); + ps.setMiterLimit(pen.miterLimit()); + QPainterPath p = ps.createStroke(path); + p.addPath(path); + return p; +} + +/*! + \internal + + Propagates the ancestor flag \a flag with value \a enabled to all this + item's children. If \a root is false, the flag is also set on this item + (default is true). +*/ +void QGraphicsItemPrivate::updateAncestorFlag(QGraphicsItem::GraphicsItemFlag childFlag, + AncestorFlag flag, bool enabled, bool root) +{ + Q_Q(QGraphicsItem); + if (root) { + // For root items only. This is the item that has either enabled or + // disabled \a childFlag, or has been reparented. + switch (int(childFlag)) { + case -2: + flag = AncestorFiltersChildEvents; + enabled = q->filtersChildEvents(); + break; + case -1: + flag = AncestorHandlesChildEvents; + enabled = q->handlesChildEvents(); + break; + case QGraphicsItem::ItemClipsChildrenToShape: + flag = AncestorClipsChildren; + enabled = flags & QGraphicsItem::ItemClipsChildrenToShape; + break; + case QGraphicsItem::ItemIgnoresTransformations: + flag = AncestorIgnoresTransformations; + enabled = flags & QGraphicsItem::ItemIgnoresTransformations; + break; + default: + return; + } + + if (parent) { + // Inherit the enabled-state from our parents. + if ((parent->d_ptr->ancestorFlags & flag) + || (int(parent->d_ptr->flags & childFlag) == childFlag) + || (childFlag == -1 && parent->d_ptr->handlesChildEvents) + || (childFlag == -2 && parent->d_ptr->filtersDescendantEvents)) { + enabled = true; + ancestorFlags |= flag; + } else { + ancestorFlags &= ~flag; + } + } else { + // Top-level root items don't have any ancestors, so there are no + // ancestor flags either. + ancestorFlags = 0; + } + } else { + // Don't set or propagate the ancestor flag if it's already correct. + if (((ancestorFlags & flag) && enabled) || (!(ancestorFlags & flag) && !enabled)) + return; + + // Set the flag. + if (enabled) + ancestorFlags |= flag; + else + ancestorFlags &= ~flag; + + // Don't process children if the item has the main flag set on itself. + if ((childFlag != -1 && int(flags & childFlag) == childFlag) + || (int(childFlag) == -1 && handlesChildEvents) + || (int(childFlag) == -2 && filtersDescendantEvents)) + return; + } + + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->updateAncestorFlag(childFlag, flag, enabled, false); +} + +void QGraphicsItemPrivate::updateAncestorFlags() +{ + int flags = 0; + if (parent) { + // Inherit the parent's ancestor flags. + QGraphicsItemPrivate *pd = parent->d_ptr.data(); + flags = pd->ancestorFlags; + + // Add in flags from the parent. + if (pd->filtersDescendantEvents) + flags |= AncestorFiltersChildEvents; + if (pd->handlesChildEvents) + flags |= AncestorHandlesChildEvents; + if (pd->flags & QGraphicsItem::ItemClipsChildrenToShape) + flags |= AncestorClipsChildren; + if (pd->flags & QGraphicsItem::ItemIgnoresTransformations) + flags |= AncestorIgnoresTransformations; + } + + if (ancestorFlags == flags) + return; // No change; stop propagation. + ancestorFlags = flags; + + // Propagate to children recursively. + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->updateAncestorFlags(); +} + +/*! + \internal + + Propagates item group membership. +*/ +void QGraphicsItemPrivate::setIsMemberOfGroup(bool enabled) +{ + Q_Q(QGraphicsItem); + isMemberOfGroup = enabled; + if (!qgraphicsitem_cast(q)) { + foreach (QGraphicsItem *child, children) + child->d_func()->setIsMemberOfGroup(enabled); + } +} + +/*! + \internal + + Maps any item pos properties of \a event to \a item's coordinate system. +*/ +void QGraphicsItemPrivate::remapItemPos(QEvent *event, QGraphicsItem *item) +{ + Q_Q(QGraphicsItem); + switch (event->type()) { + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: { + QGraphicsSceneMouseEvent *mouseEvent = static_cast(event); + mouseEvent->setPos(item->mapFromItem(q, mouseEvent->pos())); + mouseEvent->setLastPos(item->mapFromItem(q, mouseEvent->pos())); + for (int i = 0x1; i <= 0x10; i <<= 1) { + if (mouseEvent->buttons() & i) { + Qt::MouseButton button = Qt::MouseButton(i); + mouseEvent->setButtonDownPos(button, item->mapFromItem(q, mouseEvent->buttonDownPos(button))); + } + } + break; + } + case QEvent::GraphicsSceneWheel: { + QGraphicsSceneWheelEvent *wheelEvent = static_cast(event); + wheelEvent->setPos(item->mapFromItem(q, wheelEvent->pos())); + break; + } + case QEvent::GraphicsSceneContextMenu: { + QGraphicsSceneContextMenuEvent *contextEvent = static_cast(event); + contextEvent->setPos(item->mapFromItem(q, contextEvent->pos())); + break; + } + case QEvent::GraphicsSceneHoverMove: { + QGraphicsSceneHoverEvent *hoverEvent = static_cast(event); + hoverEvent->setPos(item->mapFromItem(q, hoverEvent->pos())); + break; + } + default: + break; + } +} + +/*! + \internal + + Maps the point \a pos from scene to item coordinates. If \a view is passed and the item + is untransformable, this function will correctly map \a pos from the scene using the + view's transformation. +*/ +QPointF QGraphicsItemPrivate::genericMapFromScene(const QPointF &pos, + const QWidget *viewport) const +{ + Q_Q(const QGraphicsItem); + if (!itemIsUntransformable()) + return q->mapFromScene(pos); + QGraphicsView *view = 0; + if (viewport) + view = qobject_cast(viewport->parentWidget()); + if (!view) + return q->mapFromScene(pos); + // ### More ping pong than needed. + return q->deviceTransform(view->viewportTransform()).inverted().map(view->mapFromScene(pos)); +} + +/*! + \internal + + Combines this item's position and transform onto \a transform. + + If you need to change this function (e.g., adding more transformation + modes / options), make sure to change all places marked with COMBINE. +*/ +void QGraphicsItemPrivate::combineTransformToParent(QTransform *x, const QTransform *viewTransform) const +{ + // COMBINE + if (viewTransform && itemIsUntransformable()) { + *x = q_ptr->deviceTransform(*viewTransform); + } else { + if (transformData) + *x *= transformData->computedFullTransform(); + if (!pos.isNull()) + *x *= QTransform::fromTranslate(pos.x(), pos.y()); + } +} + +/*! + \internal + + Combines this item's position and transform onto \a transform. + + If you need to change this function (e.g., adding more transformation + modes / options), make sure to change QGraphicsItem::deviceTransform() as + well. +*/ +void QGraphicsItemPrivate::combineTransformFromParent(QTransform *x, const QTransform *viewTransform) const +{ + // COMBINE + if (viewTransform && itemIsUntransformable()) { + *x = q_ptr->deviceTransform(*viewTransform); + } else { + x->translate(pos.x(), pos.y()); + if (transformData) + *x = transformData->computedFullTransform(x); + } +} + +void QGraphicsItemPrivate::updateSceneTransformFromParent() +{ + if (parent) { + Q_ASSERT(!parent->d_ptr->dirtySceneTransform); + if (parent->d_ptr->sceneTransformTranslateOnly) { + sceneTransform = QTransform::fromTranslate(parent->d_ptr->sceneTransform.dx() + pos.x(), + parent->d_ptr->sceneTransform.dy() + pos.y()); + } else { + sceneTransform = parent->d_ptr->sceneTransform; + sceneTransform.translate(pos.x(), pos.y()); + } + if (transformData) { + sceneTransform = transformData->computedFullTransform(&sceneTransform); + sceneTransformTranslateOnly = (sceneTransform.type() <= QTransform::TxTranslate); + } else { + sceneTransformTranslateOnly = parent->d_ptr->sceneTransformTranslateOnly; + } + } else if (!transformData) { + sceneTransform = QTransform::fromTranslate(pos.x(), pos.y()); + sceneTransformTranslateOnly = 1; + } else if (transformData->onlyTransform) { + sceneTransform = transformData->transform; + if (!pos.isNull()) + sceneTransform *= QTransform::fromTranslate(pos.x(), pos.y()); + sceneTransformTranslateOnly = (sceneTransform.type() <= QTransform::TxTranslate); + } else if (pos.isNull()) { + sceneTransform = transformData->computedFullTransform(); + sceneTransformTranslateOnly = (sceneTransform.type() <= QTransform::TxTranslate); + } else { + sceneTransform = QTransform::fromTranslate(pos.x(), pos.y()); + sceneTransform = transformData->computedFullTransform(&sceneTransform); + sceneTransformTranslateOnly = (sceneTransform.type() <= QTransform::TxTranslate); + } + dirtySceneTransform = 0; +} + +/*! + \internal + + This helper function helped us add input method query support in + Qt 4.4.1 without having to reimplement the inputMethodQuery() + function in QGraphicsProxyWidget. ### Qt 5: Remove. We cannot + remove it in 4.5+ even if we do reimplement the function properly, + because apps compiled with 4.4 will not be able to call the + reimplementation. +*/ +QVariant QGraphicsItemPrivate::inputMethodQueryHelper(Qt::InputMethodQuery query) const +{ + Q_UNUSED(query); + return QVariant(); +} + +/*! + \internal + + Make sure not to trigger any pure virtual function calls (e.g., + prepareGeometryChange) if the item is in its destructor, i.e. + inDestructor is 1. +*/ +void QGraphicsItemPrivate::setParentItemHelper(QGraphicsItem *newParent, const QVariant *newParentVariant, + const QVariant *thisPointerVariant) +{ + Q_Q(QGraphicsItem); + if (newParent == parent) + return; + + if (isWidget) + static_cast(this)->fixFocusChainBeforeReparenting((newParent && + newParent->isWidget()) ? static_cast(newParent) : 0, + scene); + if (scene) { + // Deliver the change to the index + if (scene->d_func()->indexMethod != QGraphicsScene::NoIndex) + scene->d_func()->index->itemChange(q, QGraphicsItem::ItemParentChange, newParent); + + // Disable scene pos notifications for old ancestors + if (scenePosDescendants || (flags & QGraphicsItem::ItemSendsScenePositionChanges)) + scene->d_func()->setScenePosItemEnabled(q, false); + } + + if (subFocusItem && parent) { + // Make sure none of the old parents point to this guy. + subFocusItem->d_ptr->clearSubFocus(parent); + } + + // We anticipate geometry changes. If the item is deleted, it will be + // removed from the index at a later stage, and the whole scene will be + // updated. + if (!inDestructor) + q_ptr->prepareGeometryChange(); + + if (parent) { + // Remove from current parent + parent->d_ptr->removeChild(q); + if (thisPointerVariant) + parent->itemChange(QGraphicsItem::ItemChildRemovedChange, *thisPointerVariant); + } + + // Update toplevelitem list. If this item is being deleted, its parent + // will be 0 but we don't want to register/unregister it in the TLI list. + if (scene && !inDestructor) { + if (parent && !newParent) { + scene->d_func()->registerTopLevelItem(q); + } else if (!parent && newParent) { + scene->d_func()->unregisterTopLevelItem(q); + } + } + + // Ensure any last parent focus scope does not point to this item or any of + // its descendents. + QGraphicsItem *p = parent; + QGraphicsItem *parentFocusScopeItem = 0; + while (p) { + if (p->d_ptr->flags & QGraphicsItem::ItemIsFocusScope) { + // If this item's focus scope's focus scope item points + // to this item or a descendent, then clear it. + QGraphicsItem *fsi = p->d_ptr->focusScopeItem; + if (q_ptr == fsi || q_ptr->isAncestorOf(fsi)) { + parentFocusScopeItem = fsi; + p->d_ptr->focusScopeItem = 0; + fsi->d_ptr->focusScopeItemChange(false); + } + break; + } + p = p->d_ptr->parent; + } + + // Update graphics effect optimization flag + if (newParent && (graphicsEffect || mayHaveChildWithGraphicsEffect)) + newParent->d_ptr->updateChildWithGraphicsEffectFlagRecursively(); + + // Update focus scope item ptr in new scope. + QGraphicsItem *newFocusScopeItem = subFocusItem ? subFocusItem : parentFocusScopeItem; + if (newFocusScopeItem && newParent) { + if (subFocusItem) { + // Find the subFocusItem's topmost focus scope. + QGraphicsItem *ancestorScope = 0; + QGraphicsItem *p = subFocusItem->d_ptr->parent; + while (p) { + if (p->d_ptr->flags & QGraphicsItem::ItemIsFocusScope) + ancestorScope = p; + if (p->d_ptr->flags & QGraphicsItem::ItemIsPanel) + break; + p = p->d_ptr->parent; + } + if (ancestorScope) + newFocusScopeItem = ancestorScope; + } + + QGraphicsItem *p = newParent; + while (p) { + if (p->d_ptr->flags & QGraphicsItem::ItemIsFocusScope) { + p->d_ptr->focusScopeItem = newFocusScopeItem; + newFocusScopeItem->d_ptr->focusScopeItemChange(true); + // Ensure the new item is no longer the subFocusItem. The + // only way to set focus on a child of a focus scope is + // by setting focus on the scope itself. + if (subFocusItem && !p->focusItem()) + subFocusItem->d_ptr->clearSubFocus(); + break; + } + p = p->d_ptr->parent; + } + } + + // Resolve depth. + invalidateDepthRecursively(); + + if ((parent = newParent)) { + if (parent->d_func()->scene && parent->d_func()->scene != scene) { + // Move this item to its new parent's scene + parent->d_func()->scene->addItem(q); + } else if (!parent->d_func()->scene && scene) { + // Remove this item from its former scene + scene->removeItem(q); + } + + parent->d_ptr->addChild(q); + if (thisPointerVariant) + parent->itemChange(QGraphicsItem::ItemChildAddedChange, *thisPointerVariant); + if (scene) { + // Re-enable scene pos notifications for new ancestors + if (scenePosDescendants || (flags & QGraphicsItem::ItemSendsScenePositionChanges)) + scene->d_func()->setScenePosItemEnabled(q, true); + } + + // Propagate dirty flags to the new parent + markParentDirty(/*updateBoundingRect=*/true); + + // Inherit ancestor flags from the new parent. + updateAncestorFlags(); + + // Update item visible / enabled. + if (parent->d_ptr->visible != visible) { + if (!parent->d_ptr->visible || !explicitlyHidden) + setVisibleHelper(parent->d_ptr->visible, /* explicit = */ false, /* update = */ false); + } + if (parent->isEnabled() != enabled) { + if (!parent->d_ptr->enabled || !explicitlyDisabled) + setEnabledHelper(parent->d_ptr->enabled, /* explicit = */ false, /* update = */ false); + } + + // Auto-activate if visible and the parent is active. + if (visible && parent->isActive()) + q->setActive(true); + } else { + // Inherit ancestor flags from the new parent. + updateAncestorFlags(); + + if (!inDestructor) { + // Update item visible / enabled. + if (!visible && !explicitlyHidden) + setVisibleHelper(true, /* explicit = */ false); + if (!enabled && !explicitlyDisabled) + setEnabledHelper(true, /* explicit = */ false); + } + } + + dirtySceneTransform = 1; + if (!inDestructor && (transformData || (newParent && newParent->d_ptr->transformData))) + transformChanged(); + + // Restore the sub focus chain. + if (subFocusItem) { + subFocusItem->d_ptr->setSubFocus(newParent); + if (parent && parent->isActive()) + subFocusItem->setFocus(); + } + + // Deliver post-change notification + if (newParentVariant) + q->itemChange(QGraphicsItem::ItemParentHasChanged, *newParentVariant); + + if (isObject) + emit static_cast(q)->parentChanged(); +} + +/*! + \internal + + Returns the bounding rect of this item's children (excluding itself). +*/ +void QGraphicsItemPrivate::childrenBoundingRectHelper(QTransform *x, QRectF *rect, QGraphicsItem *topMostEffectItem) +{ + Q_Q(QGraphicsItem); + + QRectF childrenRect; + QRectF *result = rect; + rect = &childrenRect; + const bool setTopMostEffectItem = !topMostEffectItem; + + for (int i = 0; i < children.size(); ++i) { + QGraphicsItem *child = children.at(i); + QGraphicsItemPrivate *childd = child->d_ptr.data(); + if (setTopMostEffectItem) + topMostEffectItem = child; + bool hasPos = !childd->pos.isNull(); + if (hasPos || childd->transformData) { + // COMBINE + QTransform matrix = childd->transformToParent(); + if (x) + matrix *= *x; + *rect |= matrix.mapRect(child->d_ptr->effectiveBoundingRect(topMostEffectItem)); + if (!childd->children.isEmpty()) + childd->childrenBoundingRectHelper(&matrix, rect, topMostEffectItem); + } else { + if (x) + *rect |= x->mapRect(child->d_ptr->effectiveBoundingRect(topMostEffectItem)); + else + *rect |= child->d_ptr->effectiveBoundingRect(topMostEffectItem); + if (!childd->children.isEmpty()) + childd->childrenBoundingRectHelper(x, rect, topMostEffectItem); + } + } + + if (flags & QGraphicsItem::ItemClipsChildrenToShape){ + if (x) + *rect &= x->mapRect(q->boundingRect()); + else + *rect &= q->boundingRect(); + } + + *result |= *rect; +} + +void QGraphicsItemPrivate::initStyleOption(QStyleOptionGraphicsItem *option, const QTransform &worldTransform, + const QRegion &exposedRegion, bool allItems) const +{ + Q_ASSERT(option); + Q_Q(const QGraphicsItem); + + // Initialize standard QStyleOption values. + const QRectF brect = q->boundingRect(); + option->state = QStyle::State_None; + option->rect = brect.toRect(); + option->levelOfDetail = 1; + option->exposedRect = brect; + if (selected) + option->state |= QStyle::State_Selected; + if (enabled) + option->state |= QStyle::State_Enabled; + if (q->hasFocus()) + option->state |= QStyle::State_HasFocus; + if (scene) { + if (scene->d_func()->hoverItems.contains(q_ptr)) + option->state |= QStyle::State_MouseOver; + if (q == scene->mouseGrabberItem()) + option->state |= QStyle::State_Sunken; + } + + if (!(flags & QGraphicsItem::ItemUsesExtendedStyleOption)) + return; + + // Initialize QStyleOptionGraphicsItem specific values (matrix, exposedRect). + option->matrix = worldTransform.toAffine(); //### discards perspective + + if (!allItems) { + // Determine the item's exposed area + option->exposedRect = QRectF(); + const QTransform reverseMap = worldTransform.inverted(); + const QVector exposedRects(exposedRegion.rects()); + for (int i = 0; i < exposedRects.size(); ++i) { + option->exposedRect |= reverseMap.mapRect(QRectF(exposedRects.at(i))); + if (option->exposedRect.contains(brect)) + break; + } + option->exposedRect &= brect; + } +} + +/*! + \internal + + Empty all cached pixmaps from the pixmap cache. +*/ +void QGraphicsItemCache::purge() +{ + QPixmapCache::remove(key); + key = QPixmapCache::Key(); + QMutableMapIterator it(deviceData); + while (it.hasNext()) { + DeviceData &data = it.next().value(); + QPixmapCache::remove(data.key); + data.cacheIndent = QPoint(); + } + deviceData.clear(); + allExposed = true; + exposed.clear(); +} + +/*! + Constructs a QGraphicsItem with the given \a parent item. + It does not modify the parent object returned by QObject::parent(). + + If \a parent is 0, you can add the item to a scene by calling + QGraphicsScene::addItem(). The item will then become a top-level item. + + \sa QGraphicsScene::addItem(), setParentItem() +*/ +QGraphicsItem::QGraphicsItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : d_ptr(new QGraphicsItemPrivate) +{ + d_ptr->q_ptr = this; + setParentItem(parent); + + if (scene && parent && parent->scene() != scene) { + qWarning("QGraphicsItem::QGraphicsItem: ignoring scene (%p), which is" + " different from parent's scene (%p)", + scene, parent->scene()); + return; + } + if (scene && !parent) + scene->addItem(this); +} + +/*! + \internal +*/ +QGraphicsItem::QGraphicsItem(QGraphicsItemPrivate &dd, QGraphicsItem *parent, + QGraphicsScene *scene) + : d_ptr(&dd) +{ + d_ptr->q_ptr = this; + setParentItem(parent); + + if (scene && parent && parent->scene() != scene) { + qWarning("QGraphicsItem::QGraphicsItem: ignoring scene (%p), which is" + " different from parent's scene (%p)", + scene, parent->scene()); + return; + } + if (scene && !parent) + scene->addItem(this); +} + +/*! + Destroys the QGraphicsItem and all its children. If this item is currently + associated with a scene, the item will be removed from the scene before it + is deleted. + + \note It is more efficient to remove the item from the QGraphicsScene before + destroying the item. +*/ +QGraphicsItem::~QGraphicsItem() +{ + if (d_ptr->isObject) { + QGraphicsObject *o = static_cast(this); + QObjectPrivate *p = QObjectPrivate::get(o); + p->wasDeleted = true; + if (p->declarativeData) { + QAbstractDeclarativeData::destroyed(p->declarativeData, o); + p->declarativeData = 0; + } + } + + d_ptr->inDestructor = 1; + d_ptr->removeExtraItemCache(); + +#ifndef QT_NO_GESTURES + if (d_ptr->isObject && !d_ptr->gestureContext.isEmpty()) { + QGraphicsObject *o = static_cast(this); + if (QGestureManager *manager = QGestureManager::instance()) { + foreach (Qt::GestureType type, d_ptr->gestureContext.keys()) + manager->cleanupCachedGestures(o, type); + } + } +#endif + + clearFocus(); + + // Update focus scope item ptr. + QGraphicsItem *p = d_ptr->parent; + while (p) { + if (p->flags() & ItemIsFocusScope) { + if (p->d_ptr->focusScopeItem == this) + p->d_ptr->focusScopeItem = 0; + break; + } + p = p->d_ptr->parent; + } + + if (!d_ptr->children.isEmpty()) { + while (!d_ptr->children.isEmpty()) + delete d_ptr->children.first(); + Q_ASSERT(d_ptr->children.isEmpty()); + } + + if (d_ptr->scene) { + d_ptr->scene->d_func()->removeItemHelper(this); + } else { + d_ptr->resetFocusProxy(); + setParentItem(0); + } + +#ifndef QT_NO_GRAPHICSEFFECT + delete d_ptr->graphicsEffect; +#endif //QT_NO_GRAPHICSEFFECT + if (d_ptr->transformData) { + for(int i = 0; i < d_ptr->transformData->graphicsTransforms.size(); ++i) { + QGraphicsTransform *t = d_ptr->transformData->graphicsTransforms.at(i); + static_cast(t->d_ptr.data())->item = 0; + delete t; + } + } + delete d_ptr->transformData; + + if (QGraphicsItemCustomDataStore *dataStore = qt_dataStore()) + dataStore->data.remove(this); +} + +/*! + Returns the current scene for the item, or 0 if the item is not stored in + a scene. + + To add or move an item to a scene, call QGraphicsScene::addItem(). +*/ +QGraphicsScene *QGraphicsItem::scene() const +{ + return d_ptr->scene; +} + +/*! + Returns a pointer to this item's item group, or 0 if this item is not + member of a group. + + \sa QGraphicsItemGroup, QGraphicsScene::createItemGroup() +*/ +QGraphicsItemGroup *QGraphicsItem::group() const +{ + if (!d_ptr->isMemberOfGroup) + return 0; + QGraphicsItem *parent = const_cast(this); + while ((parent = parent->d_ptr->parent)) { + if (QGraphicsItemGroup *group = qgraphicsitem_cast(parent)) + return group; + } + // Unreachable; if d_ptr->isMemberOfGroup is != 0, then one parent of this + // item is a group item. + return 0; +} + +/*! + Adds this item to the item group \a group. If \a group is 0, this item is + removed from any current group and added as a child of the previous + group's parent. + + \sa group(), QGraphicsScene::createItemGroup() +*/ +void QGraphicsItem::setGroup(QGraphicsItemGroup *group) +{ + if (!group) { + if (QGraphicsItemGroup *group = this->group()) + group->removeFromGroup(this); + } else { + group->addToGroup(this); + } +} + +/*! + Returns a pointer to this item's parent item. If this item does not have a + parent, 0 is returned. + + \sa setParentItem(), childItems() +*/ +QGraphicsItem *QGraphicsItem::parentItem() const +{ + return d_ptr->parent; +} + +/*! + Returns this item's top-level item. The top-level item is the item's + topmost ancestor item whose parent is 0. If an item has no parent, its own + pointer is returned (i.e., a top-level item is its own top-level item). + + \sa parentItem() +*/ +QGraphicsItem *QGraphicsItem::topLevelItem() const +{ + QGraphicsItem *parent = const_cast(this); + while (QGraphicsItem *grandPa = parent->parentItem()) + parent = grandPa; + return parent; +} + +/*! + \since 4.6 + + Returns a pointer to the item's parent, cast to a QGraphicsObject. returns 0 if the parent item + is not a QGraphicsObject. + + \sa parentItem(), childItems() +*/ +QGraphicsObject *QGraphicsItem::parentObject() const +{ + QGraphicsItem *p = d_ptr->parent; + return (p && p->d_ptr->isObject) ? static_cast(p) : 0; +} + +/*! + \since 4.4 + + Returns a pointer to the item's parent widget. The item's parent widget is + the closest parent item that is a widget. + + \sa parentItem(), childItems() +*/ +QGraphicsWidget *QGraphicsItem::parentWidget() const +{ + QGraphicsItem *p = parentItem(); + while (p && !p->isWidget()) + p = p->parentItem(); + return (p && p->isWidget()) ? static_cast(p) : 0; +} + +/*! + \since 4.4 + + Returns a pointer to the item's top level widget (i.e., the item's + ancestor whose parent is 0, or whose parent is not a widget), or 0 if this + item does not have a top level widget. If the item is its own top level + widget, this function returns a pointer to the item itself. +*/ +QGraphicsWidget *QGraphicsItem::topLevelWidget() const +{ + if (const QGraphicsWidget *p = parentWidget()) + return p->topLevelWidget(); + return isWidget() ? static_cast(const_cast(this)) : 0; +} + +/*! + \since 4.4 + + Returns the item's window, or 0 if this item does not have a window. If + the item is a window, it will return itself. Otherwise it will return the + closest ancestor that is a window. + + \sa QGraphicsWidget::isWindow() +*/ +QGraphicsWidget *QGraphicsItem::window() const +{ + QGraphicsItem *p = panel(); + if (p && p->isWindow()) + return static_cast(p); + return 0; +} + +/*! + \since 4.6 + + Returns the item's panel, or 0 if this item does not have a panel. If the + item is a panel, it will return itself. Otherwise it will return the + closest ancestor that is a panel. + + \sa isPanel(), ItemIsPanel +*/ +QGraphicsItem *QGraphicsItem::panel() const +{ + if (d_ptr->flags & ItemIsPanel) + return const_cast(this); + return d_ptr->parent ? d_ptr->parent->panel() : 0; +} + +/*! + \since 4.6 + + Return the graphics item cast to a QGraphicsObject, if the class is actually a + graphics object, 0 otherwise. +*/ +QGraphicsObject *QGraphicsItem::toGraphicsObject() +{ + return d_ptr->isObject ? static_cast(this) : 0; +} + +/*! + \since 4.6 + + Return the graphics item cast to a QGraphicsObject, if the class is actually a + graphics object, 0 otherwise. +*/ +const QGraphicsObject *QGraphicsItem::toGraphicsObject() const +{ + return d_ptr->isObject ? static_cast(this) : 0; +} + +/*! + Sets this item's parent item to \a newParent. If this item already + has a parent, it is first removed from the previous parent. If \a + newParent is 0, this item will become a top-level item. + + Note that this implicitly adds this graphics item to the scene of + the parent. You should not \l{QGraphicsScene::addItem()}{add} the + item to the scene yourself. + + Calling this function on an item that is an ancestor of \a newParent + have undefined behaviour. + + \sa parentItem(), childItems() +*/ +void QGraphicsItem::setParentItem(QGraphicsItem *newParent) +{ + if (newParent == this) { + qWarning("QGraphicsItem::setParentItem: cannot assign %p as a parent of itself", this); + return; + } + if (newParent == d_ptr->parent) + return; + + const QVariant newParentVariant(itemChange(QGraphicsItem::ItemParentChange, + QVariant::fromValue(newParent))); + newParent = qvariant_cast(newParentVariant); + if (newParent == d_ptr->parent) + return; + + const QVariant thisPointerVariant(QVariant::fromValue(this)); + d_ptr->setParentItemHelper(newParent, &newParentVariant, &thisPointerVariant); +} + +/*! + \obsolete + + Use childItems() instead. + + \sa setParentItem() +*/ +QList QGraphicsItem::children() const +{ + return childItems(); +} + +/*! + \since 4.4 + + Returns a list of this item's children. + + The items are sorted by stacking order. This takes into account both the + items' insertion order and their Z-values. + + \sa setParentItem(), zValue(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsItem::childItems() const +{ + const_cast(this)->d_ptr->ensureSortedChildren(); + return d_ptr->children; +} + +/*! + \since 4.4 + Returns true if this item is a widget (i.e., QGraphicsWidget); otherwise, + returns false. +*/ +bool QGraphicsItem::isWidget() const +{ + return d_ptr->isWidget; +} + +/*! + \since 4.4 + Returns true if the item is a QGraphicsWidget window, otherwise returns + false. + + \sa QGraphicsWidget::windowFlags() +*/ +bool QGraphicsItem::isWindow() const +{ + return d_ptr->isWidget && (static_cast(this)->windowType() & Qt::Window); +} + +/*! + \since 4.6 + Returns true if the item is a panel; otherwise returns false. + + \sa QGraphicsItem::panel(), ItemIsPanel +*/ +bool QGraphicsItem::isPanel() const +{ + return d_ptr->flags & ItemIsPanel; +} + +/*! + Returns this item's flags. The flags describe what configurable features + of the item are enabled and not. For example, if the flags include + ItemIsFocusable, the item can accept input focus. + + By default, no flags are enabled. + + \sa setFlags(), setFlag() +*/ +QGraphicsItem::GraphicsItemFlags QGraphicsItem::flags() const +{ + return GraphicsItemFlags(d_ptr->flags); +} + +/*! + If \a enabled is true, the item flag \a flag is enabled; otherwise, it is + disabled. + + \sa flags(), setFlags() +*/ +void QGraphicsItem::setFlag(GraphicsItemFlag flag, bool enabled) +{ + if (enabled) + setFlags(GraphicsItemFlags(d_ptr->flags) | flag); + else + setFlags(GraphicsItemFlags(d_ptr->flags) & ~flag); +} + +/*! + \internal + + Sets the flag \a flag on \a item and all its children, to \a enabled. +*/ +static void _q_qgraphicsItemSetFlag(QGraphicsItem *item, QGraphicsItem::GraphicsItemFlag flag, + bool enabled) +{ + if (item->flags() & flag) { + // If this item already has the correct flag set, we don't have to + // propagate it. + return; + } + item->setFlag(flag, enabled); + foreach (QGraphicsItem *child, item->children()) + _q_qgraphicsItemSetFlag(child, flag, enabled); +} + +/*! + Sets the item flags to \a flags. All flags in \a flags are enabled; all + flags not in \a flags are disabled. + + If the item had focus and \a flags does not enable ItemIsFocusable, the + item loses focus as a result of calling this function. Similarly, if the + item was selected, and \a flags does not enabled ItemIsSelectable, the + item is automatically unselected. + + By default, no flags are enabled. (QGraphicsWidget enables the + ItemSendsGeometryChanges flag by default in order to track position + changes.) + + \sa flags(), setFlag() +*/ +void QGraphicsItem::setFlags(GraphicsItemFlags flags) +{ + // Notify change and check for adjustment. + if (quint32(d_ptr->flags) == quint32(flags)) + return; + flags = GraphicsItemFlags(itemChange(ItemFlagsChange, quint32(flags)).toUInt()); + if (quint32(d_ptr->flags) == quint32(flags)) + return; + if (d_ptr->scene && d_ptr->scene->d_func()->indexMethod != QGraphicsScene::NoIndex) + d_ptr->scene->d_func()->index->itemChange(this, ItemFlagsChange, &flags); + + // Flags that alter the geometry of the item (or its children). + const quint32 geomChangeFlagsMask = (ItemClipsChildrenToShape | ItemClipsToShape | ItemIgnoresTransformations | ItemIsSelectable); + bool fullUpdate = (quint32(flags) & geomChangeFlagsMask) != (d_ptr->flags & geomChangeFlagsMask); + if (fullUpdate) + d_ptr->updatePaintedViewBoundingRects(/*children=*/true); + + // Keep the old flags to compare the diff. + GraphicsItemFlags oldFlags = GraphicsItemFlags(d_ptr->flags); + + // Update flags. + d_ptr->flags = flags; + + if (!(d_ptr->flags & ItemIsFocusable) && hasFocus()) { + // Clear focus on the item if it has focus when the focusable flag + // is unset. + clearFocus(); + } + + if (!(d_ptr->flags & ItemIsSelectable) && isSelected()) { + // Unselect the item if it is selected when the selectable flag is + // unset. + setSelected(false); + } + + if ((flags & ItemClipsChildrenToShape) != (oldFlags & ItemClipsChildrenToShape)) { + // Item children clipping changes. Propagate the ancestor flag to + // all children. + d_ptr->updateAncestorFlag(ItemClipsChildrenToShape); + // The childrenBoundingRect is clipped to the boundingRect in case of ItemClipsChildrenToShape, + // which means we have to invalidate the cached childrenBoundingRect whenever this flag changes. + d_ptr->dirtyChildrenBoundingRect = 1; + d_ptr->markParentDirty(true); + } + + if ((flags & ItemIgnoresTransformations) != (oldFlags & ItemIgnoresTransformations)) { + // Item children clipping changes. Propagate the ancestor flag to + // all children. + d_ptr->updateAncestorFlag(ItemIgnoresTransformations); + } + + if ((flags & ItemNegativeZStacksBehindParent) != (oldFlags & ItemNegativeZStacksBehindParent)) { + // NB! We change the flags directly here, so we must also update d_ptr->flags. + // Note that this has do be done before the ItemStacksBehindParent check + // below; otherwise we will loose the change. + + // Update stack-behind. + if (d_ptr->z < qreal(0.0)) + flags |= ItemStacksBehindParent; + else + flags &= ~ItemStacksBehindParent; + d_ptr->flags = flags; + } + + if ((flags & ItemStacksBehindParent) != (oldFlags & ItemStacksBehindParent)) { + // NB! This check has to come after the ItemNegativeZStacksBehindParent + // check above. Be careful. + + // Ensure child item sorting is up to date when toggling this flag. + if (d_ptr->parent) + d_ptr->parent->d_ptr->needSortChildren = 1; + else if (d_ptr->scene) + d_ptr->scene->d_func()->needSortTopLevelItems = 1; + } + + if ((flags & ItemAcceptsInputMethod) != (oldFlags & ItemAcceptsInputMethod)) { + // Update input method sensitivity in any views. + if (d_ptr->scene) + d_ptr->scene->d_func()->updateInputMethodSensitivityInViews(); + } + + + if ((d_ptr->panelModality != NonModal) + && d_ptr->scene + && (flags & ItemIsPanel) != (oldFlags & ItemIsPanel)) { + // update the panel's modal state + if (flags & ItemIsPanel) + d_ptr->scene->d_func()->enterModal(this); + else + d_ptr->scene->d_func()->leaveModal(this); + } + + if (d_ptr->scene) { + if ((flags & ItemSendsScenePositionChanges) != (oldFlags & ItemSendsScenePositionChanges)) { + if (flags & ItemSendsScenePositionChanges) + d_ptr->scene->d_func()->registerScenePosItem(this); + else + d_ptr->scene->d_func()->unregisterScenePosItem(this); + } + d_ptr->scene->d_func()->markDirty(this, QRectF(), /*invalidateChildren=*/true); + } + + // Notify change. + itemChange(ItemFlagsHaveChanged, quint32(flags)); +} + +/*! + \since 4.4 + Returns the cache mode for this item. The default mode is NoCache (i.e., + cache is disabled and all painting is immediate). + + \sa setCacheMode() +*/ +QGraphicsItem::CacheMode QGraphicsItem::cacheMode() const +{ + return QGraphicsItem::CacheMode(d_ptr->cacheMode); +} + +/*! + \since 4.4 + Sets the item's cache mode to \a mode. + + The optional \a logicalCacheSize argument is used only by + ItemCoordinateCache mode, and describes the resolution of the cache + buffer; if \a logicalCacheSize is (100, 100), QGraphicsItem will fit the + item into 100x100 pixels in graphics memory, regardless of the logical + size of the item itself. By default QGraphicsItem uses the size of + boundingRect(). For all other cache modes than ItemCoordinateCache, \a + logicalCacheSize is ignored. + + Caching can speed up rendering if your item spends a significant time + redrawing itself. In some cases the cache can also slow down rendering, in + particular when the item spends less time redrawing than QGraphicsItem + spends redrawing from the cache. When enabled, the item's paint() function + will be called only once for each call to update(); for any subsequent + repaint requests, the Graphics View framework will redraw from the + cache. This approach works particularly well with QGLWidget, which stores + all the cache as OpenGL textures. + + Be aware that QPixmapCache's cache limit may need to be changed to obtain + optimal performance. + + You can read more about the different cache modes in the CacheMode + documentation. + + \sa CacheMode, QPixmapCache::setCacheLimit() +*/ +void QGraphicsItem::setCacheMode(CacheMode mode, const QSize &logicalCacheSize) +{ + CacheMode lastMode = CacheMode(d_ptr->cacheMode); + d_ptr->cacheMode = mode; + bool noVisualChange = (mode == NoCache && lastMode == NoCache) + || (mode == NoCache && lastMode == DeviceCoordinateCache) + || (mode == DeviceCoordinateCache && lastMode == NoCache) + || (mode == DeviceCoordinateCache && lastMode == DeviceCoordinateCache); + if (mode == NoCache) { + d_ptr->removeExtraItemCache(); + } else { + QGraphicsItemCache *cache = d_ptr->extraItemCache(); + + // Reset old cache + cache->purge(); + + if (mode == ItemCoordinateCache) { + if (lastMode == mode && cache->fixedSize == logicalCacheSize) + noVisualChange = true; + cache->fixedSize = logicalCacheSize; + } + } + if (!noVisualChange) + update(); +} + +/*! + \since 4.6 + + Returns the modality for this item. +*/ +QGraphicsItem::PanelModality QGraphicsItem::panelModality() const +{ + return d_ptr->panelModality; +} + +/*! + \since 4.6 + + Sets the modality for this item to \a panelModality. + + Changing the modality of a visible item takes effect immediately. +*/ +void QGraphicsItem::setPanelModality(PanelModality panelModality) +{ + if (d_ptr->panelModality == panelModality) + return; + + PanelModality previousModality = d_ptr->panelModality; + bool enterLeaveModal = (isPanel() && d_ptr->scene && isVisible()); + if (enterLeaveModal && panelModality == NonModal) + d_ptr->scene->d_func()->leaveModal(this); + d_ptr->panelModality = panelModality; + if (enterLeaveModal && d_ptr->panelModality != NonModal) + d_ptr->scene->d_func()->enterModal(this, previousModality); +} + +/*! + \since 4.6 + + Returns true if this item is blocked by a modal panel, false otherwise. If \a blockingPanel is + non-zero, \a blockingPanel will be set to the modal panel that is blocking this item. If this + item is not blocked, \a blockingPanel will not be set by this function. + + This function always returns false for items not in a scene. + + \sa panelModality() setPanelModality() PanelModality +*/ +bool QGraphicsItem::isBlockedByModalPanel(QGraphicsItem **blockingPanel) const +{ + if (!d_ptr->scene) + return false; + + + QGraphicsItem *dummy = 0; + if (!blockingPanel) + blockingPanel = &dummy; + + QGraphicsScenePrivate *scene_d = d_ptr->scene->d_func(); + if (scene_d->modalPanels.isEmpty()) + return false; + + // ### + if (!scene_d->popupWidgets.isEmpty() && scene_d->popupWidgets.first() == this) + return false; + + for (int i = 0; i < scene_d->modalPanels.count(); ++i) { + QGraphicsItem *modalPanel = scene_d->modalPanels.at(i); + if (modalPanel->panelModality() == QGraphicsItem::SceneModal) { + // Scene modal panels block all non-descendents. + if (modalPanel != this && !modalPanel->isAncestorOf(this)) { + *blockingPanel = modalPanel; + return true; + } + } else { + // Window modal panels block ancestors and siblings/cousins. + if (modalPanel != this + && !modalPanel->isAncestorOf(this) + && commonAncestorItem(modalPanel)) { + *blockingPanel = modalPanel; + return true; + } + } + } + return false; +} + +#ifndef QT_NO_TOOLTIP +/*! + Returns the item's tool tip, or an empty QString if no tool tip has been + set. + + \sa setToolTip(), QToolTip +*/ +QString QGraphicsItem::toolTip() const +{ + return d_ptr->extra(QGraphicsItemPrivate::ExtraToolTip).toString(); +} + +/*! + Sets the item's tool tip to \a toolTip. If \a toolTip is empty, the item's + tool tip is cleared. + + \sa toolTip(), QToolTip +*/ +void QGraphicsItem::setToolTip(const QString &toolTip) +{ + const QVariant toolTipVariant(itemChange(ItemToolTipChange, toolTip)); + d_ptr->setExtra(QGraphicsItemPrivate::ExtraToolTip, toolTipVariant.toString()); + itemChange(ItemToolTipHasChanged, toolTipVariant); +} +#endif // QT_NO_TOOLTIP + +#ifndef QT_NO_CURSOR +/*! + Returns the current cursor shape for the item. The mouse cursor + will assume this shape when it's over this item. See the \link + Qt::CursorShape list of predefined cursor objects\endlink for a + range of useful shapes. + + An editor item might want to use an I-beam cursor: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 2 + + If no cursor has been set, the cursor of the item beneath is used. + + \sa setCursor(), hasCursor(), unsetCursor(), QWidget::cursor, + QApplication::overrideCursor() +*/ +QCursor QGraphicsItem::cursor() const +{ + return qvariant_cast(d_ptr->extra(QGraphicsItemPrivate::ExtraCursor)); +} + +/*! + Sets the current cursor shape for the item to \a cursor. The mouse cursor + will assume this shape when it's over this item. See the \link + Qt::CursorShape list of predefined cursor objects\endlink for a range of + useful shapes. + + An editor item might want to use an I-beam cursor: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 3 + + If no cursor has been set, the cursor of the item beneath is used. + + \sa cursor(), hasCursor(), unsetCursor(), QWidget::cursor, + QApplication::overrideCursor() +*/ +void QGraphicsItem::setCursor(const QCursor &cursor) +{ + const QVariant cursorVariant(itemChange(ItemCursorChange, QVariant::fromValue(cursor))); + d_ptr->setExtra(QGraphicsItemPrivate::ExtraCursor, qvariant_cast(cursorVariant)); + d_ptr->hasCursor = 1; + if (d_ptr->scene) { + d_ptr->scene->d_func()->allItemsUseDefaultCursor = false; + foreach (QGraphicsView *view, d_ptr->scene->views()) { + view->viewport()->setMouseTracking(true); + // Note: Some of this logic is duplicated in QGraphicsView's mouse events. + if (view->underMouse()) { + foreach (QGraphicsItem *itemUnderCursor, view->items(view->mapFromGlobal(QCursor::pos()))) { + if (itemUnderCursor->hasCursor()) { + QMetaObject::invokeMethod(view, "_q_setViewportCursor", + Q_ARG(QCursor, itemUnderCursor->cursor())); + break; + } + } + break; + } + } + } + itemChange(ItemCursorHasChanged, cursorVariant); +} + +/*! + Returns true if this item has a cursor set; otherwise, false is returned. + + By default, items don't have any cursor set. cursor() will return a + standard pointing arrow cursor. + + \sa unsetCursor() +*/ +bool QGraphicsItem::hasCursor() const +{ + return d_ptr->hasCursor; +} + +/*! + Clears the cursor from this item. + + \sa hasCursor(), setCursor() +*/ +void QGraphicsItem::unsetCursor() +{ + d_ptr->unsetExtra(QGraphicsItemPrivate::ExtraCursor); + d_ptr->hasCursor = 0; + if (d_ptr->scene) { + foreach (QGraphicsView *view, d_ptr->scene->views()) { + if (view->underMouse() && view->itemAt(view->mapFromGlobal(QCursor::pos())) == this) { + QMetaObject::invokeMethod(view, "_q_unsetViewportCursor"); + break; + } + } + } +} + +#endif // QT_NO_CURSOR + +/*! + Returns true if the item is visible; otherwise, false is returned. + + Note that the item's general visibility is unrelated to whether or not it + is actually being visualized by a QGraphicsView. + + \sa setVisible() +*/ +bool QGraphicsItem::isVisible() const +{ + return d_ptr->visible; +} + +/*! + \since 4.4 + Returns true if the item is visible to \a parent; otherwise, false is + returned. \a parent can be 0, in which case this function will return + whether the item is visible to the scene or not. + + An item may not be visible to its ancestors even if isVisible() is true. If + any ancestor is hidden, the item itself will be implicitly hidden, in which + case this function will return false. + + \sa isVisible(), setVisible() +*/ +bool QGraphicsItem::isVisibleTo(const QGraphicsItem *parent) const +{ + if (!d_ptr->visible) + return false; + if (parent == this) + return true; + if (parentItem() && parentItem()->isVisibleTo(parent)) + return true; + if (!parent && !parentItem()) + return true; + return false; +} + +/*! + \internal + + Sets this item's visibility to \a newVisible. If \a explicitly is true, + this item will be "explicitly" \a newVisible; otherwise, it.. will not be. +*/ +void QGraphicsItemPrivate::setVisibleHelper(bool newVisible, bool explicitly, bool update) +{ + Q_Q(QGraphicsItem); + + // Update explicit bit. + if (explicitly) + explicitlyHidden = newVisible ? 0 : 1; + + // Check if there's nothing to do. + if (visible == quint32(newVisible)) + return; + + // Don't show child if parent is not visible + if (parent && newVisible && !parent->d_ptr->visible) + return; + + // Modify the property. + const QVariant newVisibleVariant(q_ptr->itemChange(QGraphicsItem::ItemVisibleChange, + quint32(newVisible))); + newVisible = newVisibleVariant.toBool(); + if (visible == quint32(newVisible)) + return; + visible = newVisible; + + // Schedule redrawing + if (update) { + QGraphicsItemCache *c = (QGraphicsItemCache *)qvariant_cast(extra(ExtraCacheData)); + if (c) + c->purge(); + if (scene) { +#ifndef QT_NO_GRAPHICSEFFECT + invalidateParentGraphicsEffectsRecursively(); +#endif //QT_NO_GRAPHICSEFFECT + scene->d_func()->markDirty(q_ptr, QRectF(), /*invalidateChildren=*/false, /*force=*/true); + } + } + + // Certain properties are dropped as an item becomes invisible. + bool hasFocus = q_ptr->hasFocus(); + if (!newVisible) { + if (scene) { + if (scene->d_func()->mouseGrabberItems.contains(q)) + q->ungrabMouse(); + if (scene->d_func()->keyboardGrabberItems.contains(q)) + q->ungrabKeyboard(); + if (q->isPanel() && panelModality != QGraphicsItem::NonModal) + scene->d_func()->leaveModal(q_ptr); + } + if (hasFocus && scene) { + // Hiding the closest non-panel ancestor of the focus item + QGraphicsItem *focusItem = scene->focusItem(); + bool clear = true; + if (isWidget && !focusItem->isPanel()) { + do { + if (focusItem == q_ptr) { + clear = !static_cast(q_ptr)->focusNextPrevChild(true); + break; + } + } while ((focusItem = focusItem->parentWidget()) && !focusItem->isPanel()); + } + if (clear) + clearFocusHelper(/* giveFocusToParent = */ false); + } + if (q_ptr->isSelected()) + q_ptr->setSelected(false); + } else { + geometryChanged = 1; + paintedViewBoundingRectsNeedRepaint = 1; + if (scene) { + if (isWidget) { + QGraphicsWidget *widget = static_cast(q_ptr); + if (widget->windowType() == Qt::Popup) + scene->d_func()->addPopup(widget); + } + if (q->isPanel() && panelModality != QGraphicsItem::NonModal) { + scene->d_func()->enterModal(q_ptr); + } + } + } + + // Update children with explicitly = false. + const bool updateChildren = update && !(flags & QGraphicsItem::ItemClipsChildrenToShape); + foreach (QGraphicsItem *child, children) { + if (!newVisible || !child->d_ptr->explicitlyHidden) + child->d_ptr->setVisibleHelper(newVisible, false, updateChildren); + } + + // Update activation + if (scene && q->isPanel()) { + if (newVisible) { + if (parent && parent->isActive()) + q->setActive(true); + } else { + if (q->isActive()) + scene->setActivePanel(parent); + } + } + + // Enable subfocus + if (scene) { + if (newVisible) { + // Item is shown + QGraphicsItem *p = parent; + bool done = false; + while (p) { + if (p->flags() & QGraphicsItem::ItemIsFocusScope) { + QGraphicsItem *fsi = p->d_ptr->focusScopeItem; + if (q_ptr == fsi || q_ptr->isAncestorOf(fsi)) { + done = true; + while (fsi->d_ptr->focusScopeItem && fsi->d_ptr->focusScopeItem->isVisible()) + fsi = fsi->d_ptr->focusScopeItem; + fsi->d_ptr->setFocusHelper(Qt::OtherFocusReason, /* climb = */ true, + /* focusFromHide = */ false); + } + break; + } + p = p->d_ptr->parent; + } + if (!done) { + QGraphicsItem *fi = subFocusItem; + if (fi && fi != scene->focusItem()) { + scene->setFocusItem(fi); + } else if (flags & QGraphicsItem::ItemIsFocusScope && + !scene->focusItem() && + q->isAncestorOf(scene->d_func()->lastFocusItem)) { + q_ptr->setFocus(); + } + } + } else { + // Item is hidden + if (hasFocus) { + QGraphicsItem *p = parent; + while (p) { + if (p->flags() & QGraphicsItem::ItemIsFocusScope) { + if (p->d_ptr->visible) { + p->d_ptr->setFocusHelper(Qt::OtherFocusReason, /* climb = */ true, + /* focusFromHide = */ true); + } + break; + } + p = p->d_ptr->parent; + } + } + } + } + + // Deliver post-change notification. + q_ptr->itemChange(QGraphicsItem::ItemVisibleHasChanged, newVisibleVariant); + + if (isObject) + emit static_cast(q_ptr)->visibleChanged(); +} + +/*! + If \a visible is true, the item is made visible. Otherwise, the item is + made invisible. Invisible items are not painted, nor do they receive any + events. In particular, mouse events pass right through invisible items, + and are delivered to any item that may be behind. Invisible items are also + unselectable, they cannot take input focus, and are not detected by + QGraphicsScene's item location functions. + + If an item becomes invisible while grabbing the mouse, (i.e., while it is + receiving mouse events,) it will automatically lose the mouse grab, and + the grab is not regained by making the item visible again; it must receive + a new mouse press to regain the mouse grab. + + Similarly, an invisible item cannot have focus, so if the item has focus + when it becomes invisible, it will lose focus, and the focus is not + regained by simply making the item visible again. + + If you hide a parent item, all its children will also be hidden. If you + show a parent item, all children will be shown, unless they have been + explicitly hidden (i.e., if you call setVisible(false) on a child, it will + not be reshown even if its parent is hidden, and then shown again). + + Items are visible by default; it is unnecessary to call + setVisible() on a new item. + + \sa isVisible(), show(), hide() +*/ +void QGraphicsItem::setVisible(bool visible) +{ + d_ptr->setVisibleHelper(visible, /* explicit = */ true); +} + +/*! + \fn void QGraphicsItem::hide() + + Hides the item. (Items are visible by default.) + + This convenience function is equivalent to calling \c setVisible(false). + + \sa show(), setVisible() +*/ + +/*! + \fn void QGraphicsItem::show() + + Shows the item. (Items are visible by default.) + + This convenience function is equivalent to calling \c setVisible(true). + + \sa hide(), setVisible() +*/ + +/*! + Returns true if the item is enabled; otherwise, false is returned. + + \sa setEnabled() +*/ +bool QGraphicsItem::isEnabled() const +{ + return d_ptr->enabled; +} + +/*! + \internal + + Sets this item's visibility to \a newEnabled. If \a explicitly is true, + this item will be "explicitly" \a newEnabled; otherwise, it.. will not be. +*/ +void QGraphicsItemPrivate::setEnabledHelper(bool newEnabled, bool explicitly, bool update) +{ + // Update explicit bit. + if (explicitly) + explicitlyDisabled = newEnabled ? 0 : 1; + + // Check if there's nothing to do. + if (enabled == quint32(newEnabled)) + return; + + // Certain properties are dropped when an item is disabled. + if (!newEnabled) { + if (scene && scene->mouseGrabberItem() == q_ptr) + q_ptr->ungrabMouse(); + if (q_ptr->hasFocus()) { + // Disabling the closest non-panel ancestor of the focus item + // causes focus to pop to the next item, otherwise it's cleared. + QGraphicsItem *focusItem = scene->focusItem(); + bool clear = true; + if (isWidget && !focusItem->isPanel() && q_ptr->isAncestorOf(focusItem)) { + do { + if (focusItem == q_ptr) { + clear = !static_cast(q_ptr)->focusNextPrevChild(true); + break; + } + } while ((focusItem = focusItem->parentWidget()) && !focusItem->isPanel()); + } + if (clear) + q_ptr->clearFocus(); + } + if (q_ptr->isSelected()) + q_ptr->setSelected(false); + } + + // Modify the property. + const QVariant newEnabledVariant(q_ptr->itemChange(QGraphicsItem::ItemEnabledChange, + quint32(newEnabled))); + enabled = newEnabledVariant.toBool(); + + // Schedule redraw. + if (update) + q_ptr->update(); + + foreach (QGraphicsItem *child, children) { + if (!newEnabled || !child->d_ptr->explicitlyDisabled) + child->d_ptr->setEnabledHelper(newEnabled, /* explicitly = */ false); + } + + // Deliver post-change notification. + q_ptr->itemChange(QGraphicsItem::ItemEnabledHasChanged, newEnabledVariant); + + if (isObject) + emit static_cast(q_ptr)->enabledChanged(); +} + +/*! + If \a enabled is true, the item is enabled; otherwise, it is disabled. + + Disabled items are visible, but they do not receive any events, and cannot + take focus nor be selected. Mouse events are discarded; they are not + propagated unless the item is also invisible, or if it does not accept + mouse events (see acceptedMouseButtons()). A disabled item cannot become the + mouse grabber, and as a result of this, an item loses the grab if it + becomes disabled when grabbing the mouse, just like it loses focus if it + had focus when it was disabled. + + Disabled items are traditionally drawn using grayed-out colors (see \l + QPalette::Disabled). + + If you disable a parent item, all its children will also be disabled. If + you enable a parent item, all children will be enabled, unless they have + been explicitly disabled (i.e., if you call setEnabled(false) on a child, + it will not be reenabled if its parent is disabled, and then enabled + again). + + Items are enabled by default. + + \note If you install an event filter, you can still intercept events + before they are delivered to items; this mechanism disregards the item's + enabled state. + + \sa isEnabled() +*/ +void QGraphicsItem::setEnabled(bool enabled) +{ + d_ptr->setEnabledHelper(enabled, /* explicitly = */ true); +} + +/*! + Returns true if this item is selected; otherwise, false is returned. + + Items that are in a group inherit the group's selected state. + + Items are not selected by default. + + \sa setSelected(), QGraphicsScene::setSelectionArea() +*/ +bool QGraphicsItem::isSelected() const +{ + if (QGraphicsItemGroup *group = this->group()) + return group->isSelected(); + return d_ptr->selected; +} + +/*! + If \a selected is true and this item is selectable, this item is selected; + otherwise, it is unselected. + + If the item is in a group, the whole group's selected state is toggled by + this function. If the group is selected, all items in the group are also + selected, and if the group is not selected, no item in the group is + selected. + + Only visible, enabled, selectable items can be selected. If \a selected + is true and this item is either invisible or disabled or unselectable, + this function does nothing. + + By default, items cannot be selected. To enable selection, set the + ItemIsSelectable flag. + + This function is provided for convenience, allowing individual toggling of + the selected state of an item. However, a more common way of selecting + items is to call QGraphicsScene::setSelectionArea(), which will call this + function for all visible, enabled, and selectable items within a specified + area on the scene. + + \sa isSelected(), QGraphicsScene::selectedItems() +*/ +void QGraphicsItem::setSelected(bool selected) +{ + if (QGraphicsItemGroup *group = this->group()) { + group->setSelected(selected); + return; + } + + if (!(d_ptr->flags & ItemIsSelectable) || !d_ptr->enabled || !d_ptr->visible) + selected = false; + if (d_ptr->selected == selected) + return; + const QVariant newSelectedVariant(itemChange(ItemSelectedChange, quint32(selected))); + bool newSelected = newSelectedVariant.toBool(); + if (d_ptr->selected == newSelected) + return; + d_ptr->selected = newSelected; + + update(); + if (d_ptr->scene) { + QGraphicsScenePrivate *sceneD = d_ptr->scene->d_func(); + if (selected) { + sceneD->selectedItems << this; + } else { + // QGraphicsScene::selectedItems() lazily pulls out all items that are + // no longer selected. + } + if (!sceneD->selectionChanging) + emit d_ptr->scene->selectionChanged(); + } + + // Deliver post-change notification. + itemChange(QGraphicsItem::ItemSelectedHasChanged, newSelectedVariant); +} + +/*! + \since 4.5 + + Returns this item's local opacity, which is between 0.0 (transparent) and + 1.0 (opaque). This value is combined with parent and ancestor values into + the effectiveOpacity(). The effective opacity decides how the item is + rendered. + + The opacity property decides the state of the painter passed to the + paint() function. If the item is cached, i.e., ItemCoordinateCache or + DeviceCoordinateCache, the effective property will be applied to the item's + cache as it is rendered. + + The default opacity is 1.0; fully opaque. + + \sa setOpacity(), paint(), ItemIgnoresParentOpacity, + ItemDoesntPropagateOpacityToChildren +*/ +qreal QGraphicsItem::opacity() const +{ + return d_ptr->opacity; +} + +/*! + \since 4.5 + + Returns this item's \e effective opacity, which is between 0.0 + (transparent) and 1.0 (opaque). This value is a combination of this item's + local opacity, and its parent and ancestors' opacities. The effective + opacity decides how the item is rendered. + + \sa opacity(), setOpacity(), paint(), ItemIgnoresParentOpacity, + ItemDoesntPropagateOpacityToChildren +*/ +qreal QGraphicsItem::effectiveOpacity() const +{ + return d_ptr->effectiveOpacity(); +} + +/*! + \since 4.5 + + Sets this item's local \a opacity, between 0.0 (transparent) and 1.0 + (opaque). The item's local opacity is combined with parent and ancestor + opacities into the effectiveOpacity(). + + By default, opacity propagates from parent to child, so if a parent's + opacity is 0.5 and the child is also 0.5, the child's effective opacity + will be 0.25. + + The opacity property decides the state of the painter passed to the + paint() function. If the item is cached, i.e., ItemCoordinateCache or + DeviceCoordinateCache, the effective property will be applied to the + item's cache as it is rendered. + + There are two item flags that affect how the item's opacity is combined + with the parent: ItemIgnoresParentOpacity and + ItemDoesntPropagateOpacityToChildren. + + \sa opacity(), effectiveOpacity() +*/ +void QGraphicsItem::setOpacity(qreal opacity) +{ + // Notify change. + const QVariant newOpacityVariant(itemChange(ItemOpacityChange, opacity)); + + // Normalized opacity + qreal newOpacity = qBound(qreal(0), newOpacityVariant.toReal(), qreal(1)); + + // No change? Done. + if (newOpacity == d_ptr->opacity) + return; + + bool wasFullyTransparent = d_ptr->isOpacityNull(); + d_ptr->opacity = newOpacity; + + // Notify change. + itemChange(ItemOpacityHasChanged, newOpacityVariant); + + // Update. + if (d_ptr->scene) { +#ifndef QT_NO_GRAPHICSEFFECT + d_ptr->invalidateParentGraphicsEffectsRecursively(); + if (!(d_ptr->flags & ItemDoesntPropagateOpacityToChildren)) + d_ptr->invalidateChildGraphicsEffectsRecursively(QGraphicsItemPrivate::OpacityChanged); +#endif //QT_NO_GRAPHICSEFFECT + d_ptr->scene->d_func()->markDirty(this, QRectF(), + /*invalidateChildren=*/true, + /*force=*/false, + /*ignoreOpacity=*/d_ptr->isOpacityNull()); + if (wasFullyTransparent) + d_ptr->paintedViewBoundingRectsNeedRepaint = 1; + } + + if (d_ptr->isObject) + emit static_cast(this)->opacityChanged(); +} + +/*! + Returns a pointer to this item's effect if it has one; otherwise 0. + + \since 4.6 +*/ +#ifndef QT_NO_GRAPHICSEFFECT +QGraphicsEffect *QGraphicsItem::graphicsEffect() const +{ + return d_ptr->graphicsEffect; +} + +/*! + Sets \a effect as the item's effect. If there already is an effect installed + on this item, QGraphicsItem will delete the existing effect before installing + the new \a effect. + + If \a effect is the installed on a different item, setGraphicsEffect() will remove + the effect from the item and install it on this item. + + QGraphicsItem takes ownership of \a effect. + + \note This function will apply the effect on itself and all its children. + + \since 4.6 +*/ +void QGraphicsItem::setGraphicsEffect(QGraphicsEffect *effect) +{ + if (d_ptr->graphicsEffect == effect) + return; + + if (d_ptr->graphicsEffect) { + delete d_ptr->graphicsEffect; + d_ptr->graphicsEffect = 0; + } else if (d_ptr->parent) { + d_ptr->parent->d_ptr->updateChildWithGraphicsEffectFlagRecursively(); + } + + if (effect) { + // Set new effect. + QGraphicsEffectSourcePrivate *sourced = new QGraphicsItemEffectSourcePrivate(this); + QGraphicsEffectSource *source = new QGraphicsEffectSource(*sourced); + d_ptr->graphicsEffect = effect; + effect->d_func()->setGraphicsEffectSource(source); + prepareGeometryChange(); + } +} +#endif //QT_NO_GRAPHICSEFFECT + +void QGraphicsItemPrivate::updateChildWithGraphicsEffectFlagRecursively() +{ +#ifndef QT_NO_GRAPHICSEFFECT + QGraphicsItemPrivate *itemPrivate = this; + do { + // parent chain already notified? + if (itemPrivate->mayHaveChildWithGraphicsEffect) + return; + itemPrivate->mayHaveChildWithGraphicsEffect = 1; + } while ((itemPrivate = itemPrivate->parent ? itemPrivate->parent->d_ptr.data() : 0)); +#endif +} + +/*! + \internal + \since 4.6 + Returns the effective bounding rect of the given item space rect. + If the item has no effect, the rect is returned unmodified. + If the item has an effect, the effective rect can be extend beyond the + item's bounding rect, depending on the effect. + + \sa boundingRect() +*/ +QRectF QGraphicsItemPrivate::effectiveBoundingRect(const QRectF &rect) const +{ +#ifndef QT_NO_GRAPHICSEFFECT + Q_Q(const QGraphicsItem); + QGraphicsEffect *effect = graphicsEffect; + if (scene && effect && effect->isEnabled()) { + if (scene->d_func()->views.isEmpty()) + return effect->boundingRectFor(rect); + QRectF sceneRect = q->mapRectToScene(rect); + QRectF sceneEffectRect; + foreach (QGraphicsView *view, scene->views()) { + QRectF deviceRect = view->d_func()->mapRectFromScene(sceneRect); + QRect deviceEffectRect = effect->boundingRectFor(deviceRect).toAlignedRect(); + sceneEffectRect |= view->d_func()->mapRectToScene(deviceEffectRect); + } + return q->mapRectFromScene(sceneEffectRect); + } +#endif //QT_NO_GRAPHICSEFFECT + return rect; +} + +/*! + \internal + \since 4.6 + Returns the effective bounding rect of the item. + If the item has no effect, this is the same as the item's bounding rect. + If the item has an effect, the effective rect can be larger than the item's + bouding rect, depending on the effect. + + \sa boundingRect() +*/ +QRectF QGraphicsItemPrivate::effectiveBoundingRect(QGraphicsItem *topMostEffectItem) const +{ +#ifndef QT_NO_GRAPHICSEFFECT + Q_Q(const QGraphicsItem); + QRectF brect = effectiveBoundingRect(q_ptr->boundingRect()); + if (ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren || topMostEffectItem == q) + return brect; + + const QGraphicsItem *effectParent = parent; + while (effectParent) { + QGraphicsEffect *effect = effectParent->d_ptr->graphicsEffect; + if (scene && effect && effect->isEnabled()) { + const QRectF brectInParentSpace = q->mapRectToItem(effectParent, brect); + const QRectF effectRectInParentSpace = effectParent->d_ptr->effectiveBoundingRect(brectInParentSpace); + brect = effectParent->mapRectToItem(q, effectRectInParentSpace); + } + if (effectParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren + || topMostEffectItem == effectParent) { + return brect; + } + effectParent = effectParent->d_ptr->parent; + } + + return brect; +#else //QT_NO_GRAPHICSEFFECT + return q_ptr->boundingRect(); +#endif //QT_NO_GRAPHICSEFFECT + +} + +/*! + \internal + \since 4.6 + Returns the effective bounding rect of this item in scene coordinates, + by combining sceneTransform() with boundingRect(), taking into account + the effect that the item might have. + + If the item has no effect, this is the same as sceneBoundingRect(). + + \sa effectiveBoundingRect(), sceneBoundingRect() +*/ +QRectF QGraphicsItemPrivate::sceneEffectiveBoundingRect() const +{ + // Find translate-only offset + // COMBINE + QPointF offset; + const QGraphicsItem *parentItem = q_ptr; + const QGraphicsItemPrivate *itemd; + do { + itemd = parentItem->d_ptr.data(); + if (itemd->transformData) + break; + offset += itemd->pos; + } while ((parentItem = itemd->parent)); + + QRectF br = effectiveBoundingRect(); + br.translate(offset); + return !parentItem ? br : parentItem->sceneTransform().mapRect(br); +} + +/*! + Returns true if this item can accept drag and drop events; otherwise, + returns false. By default, items do not accept drag and drop events; items + are transparent to drag and drop. + + \sa setAcceptDrops() +*/ +bool QGraphicsItem::acceptDrops() const +{ + return d_ptr->acceptDrops; +} + +/*! + If \a on is true, this item will accept drag and drop events; otherwise, + it is transparent for drag and drop events. By default, items do not + accept drag and drop events. + + \sa acceptDrops() +*/ +void QGraphicsItem::setAcceptDrops(bool on) +{ + d_ptr->acceptDrops = on; +} + +/*! + Returns the mouse buttons that this item accepts mouse events for. By + default, all mouse buttons are accepted. + + If an item accepts a mouse button, it will become the mouse + grabber item when a mouse press event is delivered for that mouse + button. However, if the item does not accept the button, + QGraphicsScene will forward the mouse events to the first item + beneath it that does. + + \sa setAcceptedMouseButtons(), mousePressEvent() +*/ +Qt::MouseButtons QGraphicsItem::acceptedMouseButtons() const +{ + return Qt::MouseButtons(d_ptr->acceptedMouseButtons); +} + +/*! + Sets the mouse \a buttons that this item accepts mouse events for. + + By default, all mouse buttons are accepted. If an item accepts a + mouse button, it will become the mouse grabber item when a mouse + press event is delivered for that button. However, if the item + does not accept the mouse button, QGraphicsScene will forward the + mouse events to the first item beneath it that does. + + To disable mouse events for an item (i.e., make it transparent for mouse + events), call setAcceptedMouseButtons(0). + + \sa acceptedMouseButtons(), mousePressEvent() +*/ +void QGraphicsItem::setAcceptedMouseButtons(Qt::MouseButtons buttons) +{ + if (Qt::MouseButtons(d_ptr->acceptedMouseButtons) != buttons) { + if (buttons == 0 && d_ptr->scene && d_ptr->scene->mouseGrabberItem() == this + && d_ptr->scene->d_func()->lastMouseGrabberItemHasImplicitMouseGrab) { + ungrabMouse(); + } + d_ptr->acceptedMouseButtons = quint32(buttons); + } +} + +/*! + \since 4.4 + + Returns true if an item accepts hover events + (QGraphicsSceneHoverEvent); otherwise, returns false. By default, + items do not accept hover events. + + \sa setAcceptedMouseButtons() +*/ +bool QGraphicsItem::acceptHoverEvents() const +{ + return d_ptr->acceptsHover; +} + +/*! + \obsolete + + Call acceptHoverEvents() instead. +*/ +bool QGraphicsItem::acceptsHoverEvents() const +{ + return d_ptr->acceptsHover; +} + +/*! + \since 4.4 + + If \a enabled is true, this item will accept hover events; + otherwise, it will ignore them. By default, items do not accept + hover events. + + Hover events are delivered when there is no current mouse grabber + item. They are sent when the mouse cursor enters an item, when it + moves around inside the item, and when the cursor leaves an + item. Hover events are commonly used to highlight an item when + it's entered, and for tracking the mouse cursor as it hovers over + the item (equivalent to QWidget::mouseTracking). + + Parent items receive hover enter events before their children, and + leave events after their children. The parent does not receive a + hover leave event if the cursor enters a child, though; the parent + stays "hovered" until the cursor leaves its area, including its + children's areas. + + If a parent item handles child events, it will receive hover move, + drag move, and drop events as the cursor passes through its + children, but it does not receive hover enter and hover leave, nor + drag enter and drag leave events on behalf of its children. + + A QGraphicsWidget with window decorations will accept hover events + regardless of the value of acceptHoverEvents(). + + \sa acceptHoverEvents(), hoverEnterEvent(), hoverMoveEvent(), + hoverLeaveEvent() +*/ +void QGraphicsItem::setAcceptHoverEvents(bool enabled) +{ + if (d_ptr->acceptsHover == quint32(enabled)) + return; + d_ptr->acceptsHover = quint32(enabled); + if (d_ptr->acceptsHover && d_ptr->scene && d_ptr->scene->d_func()->allItemsIgnoreHoverEvents) { + d_ptr->scene->d_func()->allItemsIgnoreHoverEvents = false; + d_ptr->scene->d_func()->enableMouseTrackingOnViews(); + } +} + +/*! + \obsolete + + Use setAcceptHoverEvents(\a enabled) instead. +*/ +void QGraphicsItem::setAcceptsHoverEvents(bool enabled) +{ + setAcceptHoverEvents(enabled); +} + +/*! \since 4.6 + + Returns true if an item accepts \l{QTouchEvent}{touch events}; + otherwise, returns false. By default, items do not accept touch events. + + \sa setAcceptTouchEvents() +*/ +bool QGraphicsItem::acceptTouchEvents() const +{ + return d_ptr->acceptTouchEvents; +} + +/*! + \since 4.6 + + If \a enabled is true, this item will accept \l{QTouchEvent}{touch events}; + otherwise, it will ignore them. By default, items do not accept + touch events. +*/ +void QGraphicsItem::setAcceptTouchEvents(bool enabled) +{ + if (d_ptr->acceptTouchEvents == quint32(enabled)) + return; + d_ptr->acceptTouchEvents = quint32(enabled); + if (d_ptr->acceptTouchEvents && d_ptr->scene && d_ptr->scene->d_func()->allItemsIgnoreTouchEvents) { + d_ptr->scene->d_func()->allItemsIgnoreTouchEvents = false; + d_ptr->scene->d_func()->enableTouchEventsOnViews(); + } +} + +/*! + \since 4.6 + + Returns true if this item filters child events (i.e., all events + intended for any of its children are instead sent to this item); + otherwise, false is returned. + + The default value is false; child events are not filtered. + + \sa setFiltersChildEvents() +*/ +bool QGraphicsItem::filtersChildEvents() const +{ + return d_ptr->filtersDescendantEvents; +} + +/*! + \since 4.6 + + If \a enabled is true, this item is set to filter all events for + all its children (i.e., all events intented for any of its + children are instead sent to this item); otherwise, if \a enabled + is false, this item will only handle its own events. The default + value is false. + + \sa filtersChildEvents() +*/ +void QGraphicsItem::setFiltersChildEvents(bool enabled) +{ + if (d_ptr->filtersDescendantEvents == enabled) + return; + + d_ptr->filtersDescendantEvents = enabled; + d_ptr->updateAncestorFlag(QGraphicsItem::GraphicsItemFlag(-2)); +} + +/*! + \obsolete + + Returns true if this item handles child events (i.e., all events + intended for any of its children are instead sent to this item); + otherwise, false is returned. + + This property is useful for item groups; it allows one item to + handle events on behalf of its children, as opposed to its + children handling their events individually. + + The default is to return false; children handle their own events. + The exception for this is if the item is a QGraphicsItemGroup, then + it defaults to return true. + + \sa setHandlesChildEvents() +*/ +bool QGraphicsItem::handlesChildEvents() const +{ + return d_ptr->handlesChildEvents; +} + +/*! + \obsolete + + If \a enabled is true, this item is set to handle all events for + all its children (i.e., all events intented for any of its + children are instead sent to this item); otherwise, if \a enabled + is false, this item will only handle its own events. The default + value is false. + + This property is useful for item groups; it allows one item to + handle events on behalf of its children, as opposed to its + children handling their events individually. + + If a child item accepts hover events, its parent will receive + hover move events as the cursor passes through the child, but it + does not receive hover enter and hover leave events on behalf of + its child. + + \sa handlesChildEvents() +*/ +void QGraphicsItem::setHandlesChildEvents(bool enabled) +{ + if (d_ptr->handlesChildEvents == enabled) + return; + + d_ptr->handlesChildEvents = enabled; + d_ptr->updateAncestorFlag(QGraphicsItem::GraphicsItemFlag(-1)); +} +/*! + \since 4.6 + Returns true if this item is active; otherwise returns false. + + An item can only be active if the scene is active. An item is active + if it is, or is a descendent of, an active panel. Items in non-active + panels are not active. + + Items that are not part of a panel follow scene activation when the + scene has no active panel. + + Only active items can gain input focus. + + \sa QGraphicsScene::isActive(), QGraphicsScene::activePanel(), panel(), isPanel() +*/ +bool QGraphicsItem::isActive() const +{ + if (!d_ptr->scene || !d_ptr->scene->isActive()) + return false; + return panel() == d_ptr->scene->activePanel(); +} + +/*! + \since 4.6 + + If \a active is true, and the scene is active, this item's panel will be + activated. Otherwise, the panel is deactivated. + + If the item is not part of an active scene, \a active will decide what + happens to the panel when the scene becomes active or the item is added to + the scene. If true, the item's panel will be activated when the item is + either added to the scene or the scene is activated. Otherwise, the item + will stay inactive independent of the scene's activated state. + + \sa isPanel(), QGraphicsScene::setActivePanel(), QGraphicsScene::isActive() +*/ +void QGraphicsItem::setActive(bool active) +{ + d_ptr->explicitActivate = 1; + d_ptr->wantsActive = active; + if (d_ptr->scene) { + if (active) { + // Activate this item. + d_ptr->scene->setActivePanel(this); + } else { + // Deactivate this item, and reactivate the last active item + // (if any). + QGraphicsItem *lastActive = d_ptr->scene->d_func()->lastActivePanel; + d_ptr->scene->setActivePanel(lastActive != this ? lastActive : 0); + } + } +} + +/*! + Returns true if this item is active, and it or its \l{focusProxy()}{focus + proxy} has keyboard input focus; otherwise, returns false. + + \sa focusItem(), setFocus(), QGraphicsScene::setFocusItem(), isActive() +*/ +bool QGraphicsItem::hasFocus() const +{ + if (!d_ptr->scene || !d_ptr->scene->isActive()) + return false; + + if (d_ptr->focusProxy) + return d_ptr->focusProxy->hasFocus(); + + if (d_ptr->scene->d_func()->focusItem != this) + return false; + + return panel() == d_ptr->scene->d_func()->activePanel; +} + +/*! + Gives keyboard input focus to this item. The \a focusReason argument will + be passed into any \l{QFocusEvent}{focus event} generated by this function; + it is used to give an explanation of what caused the item to get focus. + + Only enabled items that set the ItemIsFocusable flag can accept keyboard + focus. + + If this item is not visible, not active, or not associated with a scene, + it will not gain immediate input focus. However, it will be registered as + the preferred focus item for its subtree of items, should it later become + visible. + + As a result of calling this function, this item will receive a + \l{focusInEvent()}{focus in event} with \a focusReason. If another item + already has focus, that item will first receive a \l{focusOutEvent()} + {focus out event} indicating that it has lost input focus. + + \sa clearFocus(), hasFocus(), focusItem(), focusProxy() +*/ +void QGraphicsItem::setFocus(Qt::FocusReason focusReason) +{ + d_ptr->setFocusHelper(focusReason, /* climb = */ true, /* focusFromHide = */ false); +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::setFocusHelper(Qt::FocusReason focusReason, bool climb, bool focusFromHide) +{ + // Disabled / unfocusable items cannot accept focus. + if (!q_ptr->isEnabled() || !(flags & QGraphicsItem::ItemIsFocusable)) + return; + + // Find focus proxy. + QGraphicsItem *f = q_ptr; + while (f->d_ptr->focusProxy) + f = f->d_ptr->focusProxy; + + // Return if it already has focus. + if (scene && scene->focusItem() == f) + return; + + // Update focus scope item ptr. + QGraphicsItem *p = parent; + while (p) { + if (p->flags() & QGraphicsItem::ItemIsFocusScope) { + QGraphicsItem *oldFocusScopeItem = p->d_ptr->focusScopeItem; + p->d_ptr->focusScopeItem = q_ptr; + if (!p->focusItem() && !focusFromHide) { + if (oldFocusScopeItem) + oldFocusScopeItem->d_ptr->focusScopeItemChange(false); + focusScopeItemChange(true); + // If you call setFocus on a child of a focus scope that + // doesn't currently have a focus item, then stop. + return; + } + break; + } + p = p->d_ptr->parent; + } + + if (climb) { + while (f->d_ptr->focusScopeItem && f->d_ptr->focusScopeItem->isVisible()) + f = f->d_ptr->focusScopeItem; + } + + // Update the child focus chain. + QGraphicsItem *commonAncestor = 0; + if (scene && scene->focusItem()) { + commonAncestor = scene->focusItem()->commonAncestorItem(f); + scene->focusItem()->d_ptr->clearSubFocus(scene->focusItem(), commonAncestor); + } + + f->d_ptr->setSubFocus(f, commonAncestor); + + // Update the scene's focus item. + if (scene) { + QGraphicsItem *p = q_ptr->panel(); + if ((!p && scene->isActive()) || (p && p->isActive())) { + // Visible items immediately gain focus from scene. + scene->d_func()->setFocusItemHelper(f, focusReason); + } + } +} + +/*! + Takes keyboard input focus from the item. + + If it has focus, a \l{focusOutEvent()}{focus out event} is sent to this + item to tell it that it is about to lose the focus. + + Only items that set the ItemIsFocusable flag, or widgets that set an + appropriate focus policy, can accept keyboard focus. + + \sa setFocus(), hasFocus(), QGraphicsWidget::focusPolicy +*/ +void QGraphicsItem::clearFocus() +{ + d_ptr->clearFocusHelper(/* giveFocusToParent = */ true); +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::clearFocusHelper(bool giveFocusToParent) +{ + if (giveFocusToParent) { + // Pass focus to the closest parent focus scope + if (!inDestructor) { + QGraphicsItem *p = parent; + while (p) { + if (p->flags() & QGraphicsItem::ItemIsFocusScope) { + if (p->d_ptr->focusScopeItem == q_ptr) { + p->d_ptr->focusScopeItem = 0; + if (!q_ptr->hasFocus()) //if it has focus, focusScopeItemChange is called elsewhere + focusScopeItemChange(false); + } + if (q_ptr->hasFocus()) + p->d_ptr->setFocusHelper(Qt::OtherFocusReason, /* climb = */ false, + /* focusFromHide = */ false); + return; + } + p = p->d_ptr->parent; + } + } + } + + if (q_ptr->hasFocus()) { + // Invisible items with focus must explicitly clear subfocus. + clearSubFocus(q_ptr); + + // If this item has the scene's input focus, clear it. + scene->setFocusItem(0); + } +} + +/*! + \since 4.6 + + Returns this item's focus proxy, or 0 if this item has no + focus proxy. + + \sa setFocusProxy(), setFocus(), hasFocus() +*/ +QGraphicsItem *QGraphicsItem::focusProxy() const +{ + return d_ptr->focusProxy; +} + +/*! + \since 4.6 + + Sets the item's focus proxy to \a item. + + If an item has a focus proxy, the focus proxy will receive + input focus when the item gains input focus. The item itself + will still have focus (i.e., hasFocus() will return true), + but only the focus proxy will receive the keyboard input. + + A focus proxy can itself have a focus proxy, and so on. In + such case, keyboard input will be handled by the outermost + focus proxy. + + The focus proxy \a item must belong to the same scene as + this item. + + \sa focusProxy(), setFocus(), hasFocus() +*/ +void QGraphicsItem::setFocusProxy(QGraphicsItem *item) +{ + if (item == d_ptr->focusProxy) + return; + if (item == this) { + qWarning("QGraphicsItem::setFocusProxy: cannot assign self as focus proxy"); + return; + } + if (item) { + if (item->d_ptr->scene != d_ptr->scene) { + qWarning("QGraphicsItem::setFocusProxy: focus proxy must be in same scene"); + return; + } + for (QGraphicsItem *f = item->focusProxy(); f != 0; f = f->focusProxy()) { + if (f == this) { + qWarning("QGraphicsItem::setFocusProxy: %p is already in the focus proxy chain", item); + return; + } + } + } + + QGraphicsItem *lastFocusProxy = d_ptr->focusProxy; + if (lastFocusProxy) + lastFocusProxy->d_ptr->focusProxyRefs.removeOne(&d_ptr->focusProxy); + d_ptr->focusProxy = item; + if (item) + item->d_ptr->focusProxyRefs << &d_ptr->focusProxy; +} + +/*! + \since 4.6 + + If this item, a child or descendant of this item currently has input + focus, this function will return a pointer to that item. If + no descendant has input focus, 0 is returned. + + \sa hasFocus(), setFocus(), QWidget::focusWidget() +*/ +QGraphicsItem *QGraphicsItem::focusItem() const +{ + return d_ptr->subFocusItem; +} + +/*! + \internal + + Returns this item's focus scope item. +*/ +QGraphicsItem *QGraphicsItem::focusScopeItem() const +{ + return d_ptr->focusScopeItem; +} + +/*! + \since 4.4 + Grabs the mouse input. + + This item will receive all mouse events for the scene until any of the + following events occurs: + + \list + \o The item becomes invisible + \o The item is removed from the scene + \o The item is deleted + \o The item call ungrabMouse() + \o Another item calls grabMouse(); the item will regain the mouse grab + when the other item calls ungrabMouse(). + \endlist + + When an item gains the mouse grab, it receives a QEvent::GrabMouse + event. When it loses the mouse grab, it receives a QEvent::UngrabMouse + event. These events can be used to detect when your item gains or loses + the mouse grab through other means than receiving mouse button events. + + It is almost never necessary to explicitly grab the mouse in Qt, as Qt + grabs and releases it sensibly. In particular, Qt grabs the mouse when you + press a mouse button, and keeps the mouse grabbed until you release the + last mouse button. Also, Qt::Popup widgets implicitly call grabMouse() + when shown, and ungrabMouse() when hidden. + + Note that only visible items can grab mouse input. Calling grabMouse() on + an invisible item has no effect. + + Keyboard events are not affected. + + \sa QGraphicsScene::mouseGrabberItem(), ungrabMouse(), grabKeyboard() +*/ +void QGraphicsItem::grabMouse() +{ + if (!d_ptr->scene) { + qWarning("QGraphicsItem::grabMouse: cannot grab mouse without scene"); + return; + } + if (!d_ptr->visible) { + qWarning("QGraphicsItem::grabMouse: cannot grab mouse while invisible"); + return; + } + d_ptr->scene->d_func()->grabMouse(this); +} + +/*! + \since 4.4 + Releases the mouse grab. + + \sa grabMouse(), ungrabKeyboard() +*/ +void QGraphicsItem::ungrabMouse() +{ + if (!d_ptr->scene) { + qWarning("QGraphicsItem::ungrabMouse: cannot ungrab mouse without scene"); + return; + } + d_ptr->scene->d_func()->ungrabMouse(this); +} + +/*! + \since 4.4 + Grabs the keyboard input. + + The item will receive all keyboard input to the scene until one of the + following events occur: + + \list + \o The item becomes invisible + \o The item is removed from the scene + \o The item is deleted + \o The item calls ungrabKeyboard() + \o Another item calls grabKeyboard(); the item will regain the keyboard grab + when the other item calls ungrabKeyboard(). + \endlist + + When an item gains the keyboard grab, it receives a QEvent::GrabKeyboard + event. When it loses the keyboard grab, it receives a + QEvent::UngrabKeyboard event. These events can be used to detect when your + item gains or loses the keyboard grab through other means than gaining + input focus. + + It is almost never necessary to explicitly grab the keyboard in Qt, as Qt + grabs and releases it sensibly. In particular, Qt grabs the keyboard when + your item gains input focus, and releases it when your item loses input + focus, or when the item is hidden. + + Note that only visible items can grab keyboard input. Calling + grabKeyboard() on an invisible item has no effect. + + Keyboard events are not affected. + + \sa ungrabKeyboard(), grabMouse(), setFocus() +*/ +void QGraphicsItem::grabKeyboard() +{ + if (!d_ptr->scene) { + qWarning("QGraphicsItem::grabKeyboard: cannot grab keyboard without scene"); + return; + } + if (!d_ptr->visible) { + qWarning("QGraphicsItem::grabKeyboard: cannot grab keyboard while invisible"); + return; + } + d_ptr->scene->d_func()->grabKeyboard(this); +} + +/*! + \since 4.4 + Releases the keyboard grab. + + \sa grabKeyboard(), ungrabMouse() +*/ +void QGraphicsItem::ungrabKeyboard() +{ + if (!d_ptr->scene) { + qWarning("QGraphicsItem::ungrabKeyboard: cannot ungrab keyboard without scene"); + return; + } + d_ptr->scene->d_func()->ungrabKeyboard(this); +} + +/*! + Returns the position of the item in parent coordinates. If the item has no + parent, its position is given in scene coordinates. + + The position of the item describes its origin (local coordinate + (0, 0)) in parent coordinates; this function returns the same as + mapToParent(0, 0). + + For convenience, you can also call scenePos() to determine the + item's position in scene coordinates, regardless of its parent. + + \sa x(), y(), setPos(), transform(), {The Graphics View Coordinate System} +*/ +QPointF QGraphicsItem::pos() const +{ + return d_ptr->pos; +} + +/*! + \fn QGraphicsItem::x() const + + This convenience function is equivalent to calling pos().x(). + + \sa y() +*/ + +/*! + \since 4.6 + + Set's the \a x coordinate of the item's position. Equivalent to + calling setPos(x, y()). + + \sa x(), setPos() +*/ +void QGraphicsItem::setX(qreal x) +{ + if (d_ptr->inDestructor) + return; + + if (qIsNaN(x)) + return; + + setPos(QPointF(x, d_ptr->pos.y())); +} + +/*! + \fn QGraphicsItem::y() const + + This convenience function is equivalent to calling pos().y(). + + \sa x() +*/ + +/*! + \since 4.6 + + Set's the \a y coordinate of the item's position. Equivalent to + calling setPos(x(), y). + + \sa x(), setPos() +*/ +void QGraphicsItem::setY(qreal y) +{ + if (d_ptr->inDestructor) + return; + + if (qIsNaN(y)) + return; + + setPos(QPointF(d_ptr->pos.x(), y)); +} + +/*! + Returns the item's position in scene coordinates. This is + equivalent to calling \c mapToScene(0, 0). + + \sa pos(), sceneTransform(), {The Graphics View Coordinate System} +*/ +QPointF QGraphicsItem::scenePos() const +{ + return mapToScene(0, 0); +} + +/*! + \internal + + Sets the position \a pos. +*/ +void QGraphicsItemPrivate::setPosHelper(const QPointF &pos) +{ + Q_Q(QGraphicsItem); + inSetPosHelper = 1; + if (scene) + q->prepareGeometryChange(); + QPointF oldPos = this->pos; + this->pos = pos; + dirtySceneTransform = 1; + inSetPosHelper = 0; + if (isObject) { + if (pos.x() != oldPos.x()) + emit static_cast(q_ptr)->xChanged(); + if (pos.y() != oldPos.y()) + emit static_cast(q_ptr)->yChanged(); + } +} + +/*! + \internal + + Sets the transform \a transform. +*/ +void QGraphicsItemPrivate::setTransformHelper(const QTransform &transform) +{ + q_ptr->prepareGeometryChange(); + transformData->transform = transform; + dirtySceneTransform = 1; + transformChanged(); +} + +/*! + Sets the position of the item to \a pos, which is in parent + coordinates. For items with no parent, \a pos is in scene + coordinates. + + The position of the item describes its origin (local coordinate + (0, 0)) in parent coordinates. + + \sa pos(), scenePos(), {The Graphics View Coordinate System} +*/ +void QGraphicsItem::setPos(const QPointF &pos) +{ + if (d_ptr->pos == pos) + return; + + if (d_ptr->inDestructor) + return; + + // Update and repositition. + if (!(d_ptr->flags & (ItemSendsGeometryChanges | ItemSendsScenePositionChanges))) { + d_ptr->setPosHelper(pos); + if (d_ptr->isWidget) + static_cast(this)->d_func()->setGeometryFromSetPos(); + if (d_ptr->scenePosDescendants) + d_ptr->sendScenePosChange(); + return; + } + + // Notify the item that the position is changing. + const QVariant newPosVariant(itemChange(ItemPositionChange, QVariant::fromValue(pos))); + QPointF newPos = newPosVariant.toPointF(); + if (newPos == d_ptr->pos) + return; + + // Update and repositition. + d_ptr->setPosHelper(newPos); + + // Send post-notification. + itemChange(QGraphicsItem::ItemPositionHasChanged, newPosVariant); + d_ptr->sendScenePosChange(); +} + +/*! + \fn void QGraphicsItem::setPos(qreal x, qreal y) + \overload + + This convenience function is equivalent to calling setPos(QPointF(\a x, \a + y)). +*/ + +/*! + \fn void QGraphicsItem::moveBy(qreal dx, qreal dy) + + Moves the item by \a dx points horizontally, and \a dy point + vertically. This function is equivalent to calling setPos(pos() + + QPointF(\a dx, \a dy)). +*/ + +/*! + If this item is part of a scene that is viewed by a QGraphicsView, this + convenience function will attempt to scroll the view to ensure that \a + rect is visible inside the view's viewport. If \a rect is a null rect (the + default), QGraphicsItem will default to the item's bounding rect. \a xmargin + and \a ymargin are the number of pixels the view should use for margins. + + If the specified rect cannot be reached, the contents are scrolled to the + nearest valid position. + + If this item is not viewed by a QGraphicsView, this function does nothing. + + \sa QGraphicsView::ensureVisible() +*/ +void QGraphicsItem::ensureVisible(const QRectF &rect, int xmargin, int ymargin) +{ + if (d_ptr->scene) { + QRectF sceneRect; + if (!rect.isNull()) + sceneRect = sceneTransform().mapRect(rect); + else + sceneRect = sceneBoundingRect(); + foreach (QGraphicsView *view, d_ptr->scene->d_func()->views) + view->ensureVisible(sceneRect, xmargin, ymargin); + } +} + +/*! + \fn void QGraphicsItem::ensureVisible(qreal x, qreal y, qreal w, qreal h, + int xmargin = 50, int ymargin = 50) + + This convenience function is equivalent to calling + ensureVisible(QRectF(\a x, \a y, \a w, \a h), \a xmargin, \a ymargin): +*/ + +/*! + \obsolete + + Returns the item's affine transformation matrix. This is a subset or the + item's full transformation matrix, and might not represent the item's full + transformation. + + Use transform() instead. + + \sa setTransform(), sceneTransform() +*/ +QMatrix QGraphicsItem::matrix() const +{ + return transform().toAffine(); +} + +/*! + \since 4.3 + + Returns this item's transformation matrix. + + The transformation matrix is combined with the item's rotation(), scale() + and transformations() into a combined transformations for the item. + + The default transformation matrix is an identity matrix. + + \sa setTransform(), sceneTransform() +*/ +QTransform QGraphicsItem::transform() const +{ + if (!d_ptr->transformData) + return QTransform(); + return d_ptr->transformData->transform; +} + +/*! + \since 4.6 + + Returns the clockwise rotation, in degrees, around the Z axis. The default + value is 0 (i.e., the item is not rotated). + + The rotation is combined with the item's scale(), transform() and + transformations() to map the item's coordinate system to the parent item. + + \sa setRotation(), transformOriginPoint(), {Transformations} +*/ +qreal QGraphicsItem::rotation() const +{ + if (!d_ptr->transformData) + return 0; + return d_ptr->transformData->rotation; +} + +/*! + \since 4.6 + + Sets the clockwise rotation \a angle, in degrees, around the Z axis. The + default value is 0 (i.e., the item is not rotated). Assigning a negative + value will rotate the item counter-clockwise. Normally the rotation angle + is in the range (-360, 360), but it's also possible to assign values + outside of this range (e.g., a rotation of 370 degrees is the same as a + rotation of 10 degrees). + + The item is rotated around its transform origin point, which by default + is (0, 0). You can select a different transformation origin by calling + setTransformOriginPoint(). + + The rotation is combined with the item's scale(), transform() and + transformations() to map the item's coordinate system to the parent item. + + \sa rotation(), setTransformOriginPoint(), {Transformations} +*/ +void QGraphicsItem::setRotation(qreal angle) +{ + prepareGeometryChange(); + qreal newRotation = angle; + + if (d_ptr->flags & ItemSendsGeometryChanges) { + // Notify the item that the rotation is changing. + const QVariant newRotationVariant(itemChange(ItemRotationChange, angle)); + newRotation = newRotationVariant.toReal(); + } + + if (!d_ptr->transformData) + d_ptr->transformData = new QGraphicsItemPrivate::TransformData; + + if (d_ptr->transformData->rotation == newRotation) + return; + + d_ptr->transformData->rotation = newRotation; + d_ptr->transformData->onlyTransform = false; + d_ptr->dirtySceneTransform = 1; + + // Send post-notification. + if (d_ptr->flags & ItemSendsGeometryChanges) + itemChange(ItemRotationHasChanged, newRotation); + + if (d_ptr->isObject) + emit static_cast(this)->rotationChanged(); + + d_ptr->transformChanged(); +} + +/*! + \since 4.6 + + Returns the scale factor of the item. The default scale factor is 1.0 + (i.e., the item is not scaled). + + The scale is combined with the item's rotation(), transform() and + transformations() to map the item's coordinate system to the parent item. + + \sa setScale(), rotation(), {Transformations} +*/ +qreal QGraphicsItem::scale() const +{ + if (!d_ptr->transformData) + return 1.; + return d_ptr->transformData->scale; +} + +/*! + \since 4.6 + + Sets the scale \a factor of the item. The default scale factor is 1.0 + (i.e., the item is not scaled). A scale factor of 0.0 will collapse the + item to a single point. If you provide a negative scale factor, the + item will be flipped and mirrored (i.e., rotated 180 degrees). + + The item is scaled around its transform origin point, which by default + is (0, 0). You can select a different transformation origin by calling + setTransformOriginPoint(). + + The scale is combined with the item's rotation(), transform() and + transformations() to map the item's coordinate system to the parent item. + + \sa scale(), setTransformOriginPoint(), {Transformations Example} +*/ +void QGraphicsItem::setScale(qreal factor) +{ + prepareGeometryChange(); + qreal newScale = factor; + + if (d_ptr->flags & ItemSendsGeometryChanges) { + // Notify the item that the scale is changing. + const QVariant newScaleVariant(itemChange(ItemScaleChange, factor)); + newScale = newScaleVariant.toReal(); + } + + if (!d_ptr->transformData) + d_ptr->transformData = new QGraphicsItemPrivate::TransformData; + + if (d_ptr->transformData->scale == newScale) + return; + + d_ptr->transformData->scale = newScale; + d_ptr->transformData->onlyTransform = false; + d_ptr->dirtySceneTransform = 1; + + // Send post-notification. + if (d_ptr->flags & ItemSendsGeometryChanges) + itemChange(ItemScaleHasChanged, newScale); + + if (d_ptr->isObject) + emit static_cast(this)->scaleChanged(); + + d_ptr->transformChanged(); +} + + +/*! + \since 4.6 + + Returns a list of graphics transforms that currently apply to this item. + + QGraphicsTransform is for applying and controlling a chain of individual + transformation operations on an item. It's particularly useful in + animations, where each transform operation needs to be interpolated + independently, or differently. + + The transformations are combined with the item's rotation(), scale() and + transform() to map the item's coordinate system to the parent item. + + \sa scale(), rotation(), transformOriginPoint(), {Transformations} +*/ +QList QGraphicsItem::transformations() const +{ + if (!d_ptr->transformData) + return QList(); + return d_ptr->transformData->graphicsTransforms; +} + +/*! + \since 4.6 + + Sets a list of graphics \a transformations (QGraphicsTransform) that + currently apply to this item. + + If all you want is to rotate or scale an item, you should call setRotation() + or setScale() instead. If you want to set an arbitrary transformation on + an item, you can call setTransform(). + + QGraphicsTransform is for applying and controlling a chain of individual + transformation operations on an item. It's particularly useful in + animations, where each transform operation needs to be interpolated + independently, or differently. + + The transformations are combined with the item's rotation(), scale() and + transform() to map the item's coordinate system to the parent item. + + \sa scale(), setTransformOriginPoint(), {Transformations} +*/ +void QGraphicsItem::setTransformations(const QList &transformations) +{ + prepareGeometryChange(); + if (!d_ptr->transformData) + d_ptr->transformData = new QGraphicsItemPrivate::TransformData; + d_ptr->transformData->graphicsTransforms = transformations; + for (int i = 0; i < transformations.size(); ++i) + transformations.at(i)->d_func()->setItem(this); + d_ptr->transformData->onlyTransform = false; + d_ptr->dirtySceneTransform = 1; + d_ptr->transformChanged(); +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::prependGraphicsTransform(QGraphicsTransform *t) +{ + if (!transformData) + transformData = new QGraphicsItemPrivate::TransformData; + if (!transformData->graphicsTransforms.contains(t)) + transformData->graphicsTransforms.prepend(t); + + Q_Q(QGraphicsItem); + t->d_func()->setItem(q); + transformData->onlyTransform = false; + dirtySceneTransform = 1; + transformChanged(); +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::appendGraphicsTransform(QGraphicsTransform *t) +{ + if (!transformData) + transformData = new QGraphicsItemPrivate::TransformData; + if (!transformData->graphicsTransforms.contains(t)) + transformData->graphicsTransforms.append(t); + + Q_Q(QGraphicsItem); + t->d_func()->setItem(q); + transformData->onlyTransform = false; + dirtySceneTransform = 1; + transformChanged(); +} + +/*! + \since 4.6 + + Returns the origin point for the transformation in item coordinates. + + The default is QPointF(0,0). + + \sa setTransformOriginPoint(), {Transformations} +*/ +QPointF QGraphicsItem::transformOriginPoint() const +{ + if (!d_ptr->transformData) + return QPointF(0,0); + return QPointF(d_ptr->transformData->xOrigin, d_ptr->transformData->yOrigin); +} + +/*! + \since 4.6 + + Sets the \a origin point for the transformation in item coordinates. + + \sa transformOriginPoint(), {Transformations} +*/ +void QGraphicsItem::setTransformOriginPoint(const QPointF &origin) +{ + prepareGeometryChange(); + QPointF newOrigin = origin; + + if (d_ptr->flags & ItemSendsGeometryChanges) { + // Notify the item that the origin point is changing. + const QVariant newOriginVariant(itemChange(ItemTransformOriginPointChange, + QVariant::fromValue(origin))); + newOrigin = newOriginVariant.toPointF(); + } + + if (!d_ptr->transformData) + d_ptr->transformData = new QGraphicsItemPrivate::TransformData; + + if (d_ptr->transformData->xOrigin == newOrigin.x() + && d_ptr->transformData->yOrigin == newOrigin.y()) { + return; + } + + d_ptr->transformData->xOrigin = newOrigin.x(); + d_ptr->transformData->yOrigin = newOrigin.y(); + d_ptr->transformData->onlyTransform = false; + d_ptr->dirtySceneTransform = 1; + + // Send post-notification. + if (d_ptr->flags & ItemSendsGeometryChanges) + itemChange(ItemTransformOriginPointHasChanged, QVariant::fromValue(newOrigin)); +} + +/*! + \fn void QGraphicsItem::setTransformOriginPoint(qreal x, qreal y) + + \since 4.6 + \overload + + Sets the origin point for the transformation in item coordinates. + This is equivalent to calling setTransformOriginPoint(QPointF(\a x, \a y)). + + \sa setTransformOriginPoint(), {Transformations} +*/ + + +/*! + \obsolete + + Use sceneTransform() instead. + + \sa transform(), setTransform(), scenePos(), {The Graphics View Coordinate System} +*/ +QMatrix QGraphicsItem::sceneMatrix() const +{ + d_ptr->ensureSceneTransform(); + return d_ptr->sceneTransform.toAffine(); +} + + +/*! + \since 4.3 + + Returns this item's scene transformation matrix. This matrix can be used + to map coordinates and geometrical shapes from this item's local + coordinate system to the scene's coordinate system. To map coordinates + from the scene, you must first invert the returned matrix. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 4 + + Unlike transform(), which returns only an item's local transformation, this + function includes the item's (and any parents') position, and all the transfomation properties. + + \sa transform(), setTransform(), scenePos(), {The Graphics View Coordinate System}, {Transformations} +*/ +QTransform QGraphicsItem::sceneTransform() const +{ + d_ptr->ensureSceneTransform(); + return d_ptr->sceneTransform; +} + +/*! + \since 4.3 + + Returns this item's device transformation matrix, using \a + viewportTransform to map from scene to device coordinates. This matrix can + be used to map coordinates and geometrical shapes from this item's local + coordinate system to the viewport's (or any device's) coordinate + system. To map coordinates from the viewport, you must first invert the + returned matrix. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 5 + + This function is the same as combining this item's scene transform with + the view's viewport transform, but it also understands the + ItemIgnoresTransformations flag. The device transform can be used to do + accurate coordinate mapping (and collision detection) for untransformable + items. + + \sa transform(), setTransform(), scenePos(), {The Graphics View Coordinate + System}, itemTransform() +*/ +QTransform QGraphicsItem::deviceTransform(const QTransform &viewportTransform) const +{ + // Ensure we return the standard transform if we're not untransformable. + if (!d_ptr->itemIsUntransformable()) { + d_ptr->ensureSceneTransform(); + return d_ptr->sceneTransform * viewportTransform; + } + + // Find the topmost item that ignores view transformations. + const QGraphicsItem *untransformedAncestor = this; + QList parents; + while (untransformedAncestor && ((untransformedAncestor->d_ptr->ancestorFlags + & QGraphicsItemPrivate::AncestorIgnoresTransformations))) { + parents.prepend(untransformedAncestor); + untransformedAncestor = untransformedAncestor->parentItem(); + } + + if (!untransformedAncestor) { + // Assert in debug mode, continue in release. + Q_ASSERT_X(untransformedAncestor, "QGraphicsItem::deviceTransform", + "Invalid object structure!"); + return QTransform(); + } + + // First translate the base untransformable item. + untransformedAncestor->d_ptr->ensureSceneTransform(); + QPointF mappedPoint = (untransformedAncestor->d_ptr->sceneTransform * viewportTransform).map(QPointF(0, 0)); + + // COMBINE + QTransform matrix = QTransform::fromTranslate(mappedPoint.x(), mappedPoint.y()); + if (untransformedAncestor->d_ptr->transformData) + matrix = untransformedAncestor->d_ptr->transformData->computedFullTransform(&matrix); + + // Then transform and translate all children. + for (int i = 0; i < parents.size(); ++i) { + const QGraphicsItem *parent = parents.at(i); + parent->d_ptr->combineTransformFromParent(&matrix); + } + + return matrix; +} + +/*! + \since 4.5 + + Returns a QTransform that maps coordinates from this item to \a other. If + \a ok is not null, and if there is no such transform, the boolean pointed + to by \a ok will be set to false; otherwise it will be set to true. + + This transform provides an alternative to the mapToItem() or mapFromItem() + functions, by returning the appropriate transform so that you can map + shapes and coordinates yourself. It also helps you write more efficient + code when repeatedly mapping between the same two items. + + \note In rare circumstances, there is no transform that maps between two + items. + + \sa mapToItem(), mapFromItem(), deviceTransform() +*/ +QTransform QGraphicsItem::itemTransform(const QGraphicsItem *other, bool *ok) const +{ + // Catch simple cases first. + if (other == 0) { + qWarning("QGraphicsItem::itemTransform: null pointer passed"); + return QTransform(); + } + if (other == this) { + if (ok) + *ok = true; + return QTransform(); + } + + QGraphicsItem *parent = d_ptr->parent; + const QGraphicsItem *otherParent = other->d_ptr->parent; + + // This is other's child + if (parent == other) { + if (ok) + *ok = true; + QTransform x; + d_ptr->combineTransformFromParent(&x); + return x; + } + + // This is other's parent + if (otherParent == this) { + const QPointF &otherPos = other->d_ptr->pos; + if (other->d_ptr->transformData) { + QTransform otherToParent; + other->d_ptr->combineTransformFromParent(&otherToParent); + return otherToParent.inverted(ok); + } + if (ok) + *ok = true; + return QTransform::fromTranslate(-otherPos.x(), -otherPos.y()); + } + + // Siblings + if (parent == otherParent) { + // COMBINE + const QPointF &itemPos = d_ptr->pos; + const QPointF &otherPos = other->d_ptr->pos; + if (!d_ptr->transformData && !other->d_ptr->transformData) { + QPointF delta = itemPos - otherPos; + if (ok) + *ok = true; + return QTransform::fromTranslate(delta.x(), delta.y()); + } + + QTransform itemToParent; + d_ptr->combineTransformFromParent(&itemToParent); + QTransform otherToParent; + other->d_ptr->combineTransformFromParent(&otherToParent); + return itemToParent * otherToParent.inverted(ok); + } + + // Find the closest common ancestor. If the two items don't share an + // ancestor, then the only way is to combine their scene transforms. + const QGraphicsItem *commonAncestor = commonAncestorItem(other); + if (!commonAncestor) { + d_ptr->ensureSceneTransform(); + other->d_ptr->ensureSceneTransform(); + return d_ptr->sceneTransform * other->d_ptr->sceneTransform.inverted(ok); + } + + // If the two items are cousins (in sibling branches), map both to the + // common ancestor, and combine the two transforms. + bool cousins = other != commonAncestor && this != commonAncestor; + if (cousins) { + bool good = false; + QTransform thisToScene = itemTransform(commonAncestor, &good); + QTransform otherToScene(Qt::Uninitialized); + if (good) + otherToScene = other->itemTransform(commonAncestor, &good); + if (!good) { + if (ok) + *ok = false; + return QTransform(); + } + return thisToScene * otherToScene.inverted(ok); + } + + // One is an ancestor of the other; walk the chain. + bool parentOfOther = isAncestorOf(other); + const QGraphicsItem *child = parentOfOther ? other : this; + const QGraphicsItem *root = parentOfOther ? this : other; + + QTransform x; + const QGraphicsItem *p = child; + do { + p->d_ptr.data()->combineTransformToParent(&x); + } while ((p = p->d_ptr->parent) && p != root); + if (parentOfOther) + return x.inverted(ok); + if (ok) + *ok = true; + return x; +} + +/*! + \obsolete + + Sets the item's affine transformation matrix. This is a subset or the + item's full transformation matrix, and might not represent the item's full + transformation. + + Use setTransform() instead. + + \sa transform(), {The Graphics View Coordinate System} +*/ +void QGraphicsItem::setMatrix(const QMatrix &matrix, bool combine) +{ + if (!d_ptr->transformData) + d_ptr->transformData = new QGraphicsItemPrivate::TransformData; + + QTransform newTransform(combine ? QTransform(matrix) * d_ptr->transformData->transform : QTransform(matrix)); + if (d_ptr->transformData->transform == newTransform) + return; + + // Update and set the new transformation. + if (!(d_ptr->flags & ItemSendsGeometryChanges)) { + d_ptr->setTransformHelper(newTransform); + return; + } + + // Notify the item that the transformation matrix is changing. + const QVariant newMatrixVariant = QVariant::fromValue(newTransform.toAffine()); + newTransform = QTransform(qvariant_cast(itemChange(ItemMatrixChange, newMatrixVariant))); + if (d_ptr->transformData->transform == newTransform) + return; + + // Update and set the new transformation. + d_ptr->setTransformHelper(newTransform); + + // Send post-notification. + itemChange(ItemTransformHasChanged, QVariant::fromValue(newTransform)); +} + +/*! + \since 4.3 + + Sets the item's current transformation matrix to \a matrix. + + If \a combine is true, then \a matrix is combined with the current matrix; + otherwise, \a matrix \e replaces the current matrix. \a combine is false + by default. + + To simplify interation with items using a transformed view, QGraphicsItem + provides mapTo... and mapFrom... functions that can translate between + items' and the scene's coordinates. For example, you can call mapToScene() + to map an item coordiate to a scene coordinate, or mapFromScene() to map + from scene coordinates to item coordinates. + + The transformation matrix is combined with the item's rotation(), scale() + and transformations() into a combined transformation that maps the item's + coordinate system to its parent. + + \sa transform(), setRotation(), setScale(), setTransformOriginPoint(), {The Graphics View Coordinate System}, {Transformations} +*/ +void QGraphicsItem::setTransform(const QTransform &matrix, bool combine) +{ + if (!d_ptr->transformData) + d_ptr->transformData = new QGraphicsItemPrivate::TransformData; + + QTransform newTransform(combine ? matrix * d_ptr->transformData->transform : matrix); + if (d_ptr->transformData->transform == newTransform) + return; + + // Update and set the new transformation. + if (!(d_ptr->flags & (ItemSendsGeometryChanges | ItemSendsScenePositionChanges))) { + d_ptr->setTransformHelper(newTransform); + if (d_ptr->scenePosDescendants) + d_ptr->sendScenePosChange(); + return; + } + + // Notify the item that the transformation matrix is changing. + const QVariant newTransformVariant(itemChange(ItemTransformChange, + QVariant::fromValue(newTransform))); + newTransform = qvariant_cast(newTransformVariant); + if (d_ptr->transformData->transform == newTransform) + return; + + // Update and set the new transformation. + d_ptr->setTransformHelper(newTransform); + + // Send post-notification. + itemChange(ItemTransformHasChanged, newTransformVariant); + d_ptr->sendScenePosChange(); +} + +/*! + \obsolete + + Use resetTransform() instead. +*/ +void QGraphicsItem::resetMatrix() +{ + resetTransform(); +} + +/*! + \since 4.3 + + Resets this item's transformation matrix to the identity matrix or + all the transformation properties to their default values. + This is equivalent to calling \c setTransform(QTransform()). + + \sa setTransform(), transform() +*/ +void QGraphicsItem::resetTransform() +{ + setTransform(QTransform(), false); +} + +/*! + \obsolete + + Use + + \code + setRotation(rotation() + angle); + \endcode + + instead. + + Rotates the current item transformation \a angle degrees clockwise around + its origin. To translate around an arbitrary point (x, y), you need to + combine translation and rotation with setTransform(). + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 6 + + \sa setTransform(), transform(), scale(), shear(), translate() +*/ +void QGraphicsItem::rotate(qreal angle) +{ + setTransform(QTransform().rotate(angle), true); +} + +/*! + \obsolete + + Use + + \code + setTransform(QTransform::fromScale(sx, sy), true); + \endcode + + instead. + + Scales the current item transformation by (\a sx, \a sy) around its + origin. To scale from an arbitrary point (x, y), you need to combine + translation and scaling with setTransform(). + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 7 + + \sa setTransform(), transform() +*/ +void QGraphicsItem::scale(qreal sx, qreal sy) +{ + setTransform(QTransform::fromScale(sx, sy), true); +} + +/*! + \obsolete + + Use + + \code + setTransform(QTransform().shear(sh, sv), true); + \endcode + + instead. + + Shears the current item transformation by (\a sh, \a sv). + + \sa setTransform(), transform() +*/ +void QGraphicsItem::shear(qreal sh, qreal sv) +{ + setTransform(QTransform().shear(sh, sv), true); +} + +/*! + \obsolete + + Use setPos() or setTransformOriginPoint() instead. For identical + behavior, use + + \code + setTransform(QTransform::fromTranslate(dx, dy), true); + \endcode + + Translates the current item transformation by (\a dx, \a dy). + + If all you want is to move an item, you should call moveBy() or + setPos() instead; this function changes the item's translation, + which is conceptually separate from its position. + + \sa setTransform(), transform() +*/ +void QGraphicsItem::translate(qreal dx, qreal dy) +{ + setTransform(QTransform::fromTranslate(dx, dy), true); +} + +/*! + This virtual function is called twice for all items by the + QGraphicsScene::advance() slot. In the first phase, all items are called + with \a phase == 0, indicating that items on the scene are about to + advance, and then all items are called with \a phase == 1. Reimplement + this function to update your item if you need simple scene-controlled + animation. + + The default implementation does nothing. + + For individual item animation, an alternative to this function is to + either use QGraphicsItemAnimation, or to multiple-inherit from QObject and + QGraphicsItem, and animate your item using QObject::startTimer() and + QObject::timerEvent(). + + \sa QGraphicsItemAnimation, QTimeLine +*/ +void QGraphicsItem::advance(int phase) +{ + Q_UNUSED(phase); +} + +/*! + Returns the Z-value of the item. The Z-value affects the stacking order of + sibling (neighboring) items. + + The default Z-value is 0. + + \sa setZValue(), {QGraphicsItem#Sorting}{Sorting}, stackBefore(), ItemStacksBehindParent +*/ +qreal QGraphicsItem::zValue() const +{ + return d_ptr->z; +} + +/*! + Sets the Z-value of the item to \a z. The Z value decides the stacking + order of sibling (neighboring) items. A sibling item of high Z value will + always be drawn on top of another sibling item with a lower Z value. + + If you restore the Z value, the item's insertion order will decide its + stacking order. + + The Z-value does not affect the item's size in any way. + + The default Z-value is 0. + + \sa zValue(), {QGraphicsItem#Sorting}{Sorting}, stackBefore(), ItemStacksBehindParent +*/ +void QGraphicsItem::setZValue(qreal z) +{ + const QVariant newZVariant(itemChange(ItemZValueChange, z)); + qreal newZ = newZVariant.toReal(); + if (newZ == d_ptr->z) + return; + + if (d_ptr->scene && d_ptr->scene->d_func()->indexMethod != QGraphicsScene::NoIndex) { + // Z Value has changed, we have to notify the index. + d_ptr->scene->d_func()->index->itemChange(this, ItemZValueChange, &newZ); + } + + d_ptr->z = newZ; + if (d_ptr->parent) + d_ptr->parent->d_ptr->needSortChildren = 1; + else if (d_ptr->scene) + d_ptr->scene->d_func()->needSortTopLevelItems = 1; + + if (d_ptr->scene) + d_ptr->scene->d_func()->markDirty(this, QRectF(), /*invalidateChildren=*/true); + + itemChange(ItemZValueHasChanged, newZVariant); + + if (d_ptr->flags & ItemNegativeZStacksBehindParent) + setFlag(QGraphicsItem::ItemStacksBehindParent, z < qreal(0.0)); + + if (d_ptr->isObject) + emit static_cast(this)->zChanged(); +} + +/*! + \internal + + Ensures that the list of children is sorted by insertion order, and that + the siblingIndexes are packed (no gaps), and start at 0. + + ### This function is almost identical to + QGraphicsScenePrivate::ensureSequentialTopLevelSiblingIndexes(). +*/ +void QGraphicsItemPrivate::ensureSequentialSiblingIndex() +{ + if (!sequentialOrdering) { + qSort(children.begin(), children.end(), insertionOrder); + sequentialOrdering = 1; + needSortChildren = 1; + } + if (holesInSiblingIndex) { + holesInSiblingIndex = 0; + for (int i = 0; i < children.size(); ++i) + children[i]->d_ptr->siblingIndex = i; + } +} + +/*! + \internal +*/ +inline void QGraphicsItemPrivate::sendScenePosChange() +{ + Q_Q(QGraphicsItem); + if (scene) { + if (flags & QGraphicsItem::ItemSendsScenePositionChanges) + q->itemChange(QGraphicsItem::ItemScenePositionHasChanged, q->scenePos()); + if (scenePosDescendants) { + foreach (QGraphicsItem *item, scene->d_func()->scenePosItems) { + if (q->isAncestorOf(item)) + item->itemChange(QGraphicsItem::ItemScenePositionHasChanged, item->scenePos()); + } + } + } +} + +/*! + \since 4.6 + + Stacks this item before \a sibling, which must be a sibling item (i.e., the + two items must share the same parent item, or must both be toplevel items). + The \a sibling must have the same Z value as this item, otherwise calling + this function will have no effect. + + By default, all sibling items are stacked by insertion order (i.e., the + first item you add is drawn before the next item you add). If two items' Z + values are different, then the item with the highest Z value is drawn on + top. When the Z values are the same, the insertion order will decide the + stacking order. + + \sa setZValue(), ItemStacksBehindParent, {QGraphicsItem#Sorting}{Sorting} +*/ +void QGraphicsItem::stackBefore(const QGraphicsItem *sibling) +{ + if (sibling == this) + return; + if (!sibling || d_ptr->parent != sibling->parentItem()) { + qWarning("QGraphicsItem::stackUnder: cannot stack under %p, which must be a sibling", sibling); + return; + } + QList *siblings = d_ptr->parent + ? &d_ptr->parent->d_ptr->children + : (d_ptr->scene ? &d_ptr->scene->d_func()->topLevelItems : 0); + if (!siblings) { + qWarning("QGraphicsItem::stackUnder: cannot stack under %p, which must be a sibling", sibling); + return; + } + + // First, make sure that the sibling indexes have no holes. This also + // marks the children list for sorting. + if (d_ptr->parent) + d_ptr->parent->d_ptr->ensureSequentialSiblingIndex(); + else + d_ptr->scene->d_func()->ensureSequentialTopLevelSiblingIndexes(); + + // Only move items with the same Z value, and that need moving. + int siblingIndex = sibling->d_ptr->siblingIndex; + int myIndex = d_ptr->siblingIndex; + if (myIndex >= siblingIndex) { + siblings->move(myIndex, siblingIndex); + // Fixup the insertion ordering. + for (int i = 0; i < siblings->size(); ++i) { + int &index = siblings->at(i)->d_ptr->siblingIndex; + if (i != siblingIndex && index >= siblingIndex && index <= myIndex) + ++index; + } + d_ptr->siblingIndex = siblingIndex; + for (int i = 0; i < siblings->size(); ++i) { + int &index = siblings->at(i)->d_ptr->siblingIndex; + if (i != siblingIndex && index >= siblingIndex && index <= myIndex) + siblings->at(i)->d_ptr->siblingOrderChange(); + } + d_ptr->siblingOrderChange(); + } +} + +/*! + Returns the bounding rect of this item's descendants (i.e., its + children, their children, etc.) in local coordinates. The + rectangle will contain all descendants after they have been mapped + to local coordinates. If the item has no children, this function + returns an empty QRectF. + + This does not include this item's own bounding rect; it only returns + its descendants' accumulated bounding rect. If you need to include this + item's bounding rect, you can add boundingRect() to childrenBoundingRect() + using QRectF::operator|(). + + This function is linear in complexity; it determines the size of the + returned bounding rect by iterating through all descendants. + + \sa boundingRect(), sceneBoundingRect() +*/ +QRectF QGraphicsItem::childrenBoundingRect() const +{ + if (!d_ptr->dirtyChildrenBoundingRect) + return d_ptr->childrenBoundingRect; + + d_ptr->childrenBoundingRect = QRectF(); + d_ptr->childrenBoundingRectHelper(0, &d_ptr->childrenBoundingRect, 0); + d_ptr->dirtyChildrenBoundingRect = 0; + return d_ptr->childrenBoundingRect; +} + +/*! + \fn virtual QRectF QGraphicsItem::boundingRect() const = 0 + + This pure virtual function defines the outer bounds of the item as + a rectangle; all painting must be restricted to inside an item's + bounding rect. QGraphicsView uses this to determine whether the + item requires redrawing. + + Although the item's shape can be arbitrary, the bounding rect is + always rectangular, and it is unaffected by the items' + transformation. + + If you want to change the item's bounding rectangle, you must first call + prepareGeometryChange(). This notifies the scene of the imminent change, + so that its can update its item geometry index; otherwise, the scene will + be unaware of the item's new geometry, and the results are undefined + (typically, rendering artifacts are left around in the view). + + Reimplement this function to let QGraphicsView determine what + parts of the widget, if any, need to be redrawn. + + Note: For shapes that paint an outline / stroke, it is important + to include half the pen width in the bounding rect. It is not + necessary to compensate for antialiasing, though. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 8 + + \sa boundingRegion(), shape(), contains(), {The Graphics View Coordinate + System}, prepareGeometryChange() +*/ + +/*! + Returns the bounding rect of this item in scene coordinates, by combining + sceneTransform() with boundingRect(). + + \sa boundingRect(), {The Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::sceneBoundingRect() const +{ + // Find translate-only offset + // COMBINE + QPointF offset; + const QGraphicsItem *parentItem = this; + const QGraphicsItemPrivate *itemd; + do { + itemd = parentItem->d_ptr.data(); + if (itemd->transformData) + break; + offset += itemd->pos; + } while ((parentItem = itemd->parent)); + + QRectF br = boundingRect(); + br.translate(offset); + if (!parentItem) + return br; + if (parentItem->d_ptr->hasTranslateOnlySceneTransform()) { + br.translate(parentItem->d_ptr->sceneTransform.dx(), parentItem->d_ptr->sceneTransform.dy()); + return br; + } + return parentItem->d_ptr->sceneTransform.mapRect(br); +} + +/*! + Returns the shape of this item as a QPainterPath in local + coordinates. The shape is used for many things, including collision + detection, hit tests, and for the QGraphicsScene::items() functions. + + The default implementation calls boundingRect() to return a simple + rectangular shape, but subclasses can reimplement this function to return + a more accurate shape for non-rectangular items. For example, a round item + may choose to return an elliptic shape for better collision detection. For + example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 9 + + The outline of a shape can vary depending on the width and style of the + pen used when drawing. If you want to include this outline in the item's + shape, you can create a shape from the stroke using QPainterPathStroker. + + This function is called by the default implementations of contains() and + collidesWithPath(). + + \sa boundingRect(), contains(), prepareGeometryChange(), QPainterPathStroker +*/ +QPainterPath QGraphicsItem::shape() const +{ + QPainterPath path; + path.addRect(boundingRect()); + return path; +} + +/*! + Returns true if this item is clipped. An item is clipped if it has either + set the \l ItemClipsToShape flag, or if it or any of its ancestors has set + the \l ItemClipsChildrenToShape flag. + + Clipping affects the item's appearance (i.e., painting), as well as mouse + and hover event delivery. + + \sa clipPath(), shape(), setFlags() +*/ +bool QGraphicsItem::isClipped() const +{ + Q_D(const QGraphicsItem); + return (d->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) + || (d->flags & QGraphicsItem::ItemClipsToShape); +} + +/*! + \since 4.5 + + Returns this item's clip path, or an empty QPainterPath if this item is + not clipped. The clip path constrains the item's appearance and + interaction (i.e., restricts the area the item can draw, and it also + restricts the area that the item receives events). + + You can enable clipping by setting the ItemClipsToShape or + ItemClipsChildrenToShape flags. The item's clip path is calculated by + intersecting all clipping ancestors' shapes. If the item sets + ItemClipsToShape, the final clip is intersected with the item's own shape. + + \note Clipping introduces a performance penalty for all items involved; + you should generally avoid using clipping if you can (e.g., if your items + always draw inside boundingRect() or shape() boundaries, clipping is not + necessary). + + \sa isClipped(), shape(), setFlags() +*/ +QPainterPath QGraphicsItem::clipPath() const +{ + Q_D(const QGraphicsItem); + if (!isClipped()) + return QPainterPath(); + + const QRectF thisBoundingRect(boundingRect()); + if (thisBoundingRect.isEmpty()) + return QPainterPath(); + + QPainterPath clip; + // Start with the item's bounding rect. + clip.addRect(thisBoundingRect); + + if (d->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) { + const QGraphicsItem *parent = this; + const QGraphicsItem *lastParent = this; + + // Intersect any in-between clips starting at the top and moving downwards. + while ((parent = parent->d_ptr->parent)) { + if (parent->d_ptr->flags & ItemClipsChildrenToShape) { + // Map clip to the current parent and intersect with its shape/clipPath + clip = lastParent->itemTransform(parent).map(clip); + clip = clip.intersected(parent->shape()); + if (clip.isEmpty()) + return clip; + lastParent = parent; + } + + if (!(parent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)) + break; + } + + if (lastParent != this) { + // Map clip back to the item's transform. + // ### what if itemtransform fails + clip = lastParent->itemTransform(this).map(clip); + } + } + + if (d->flags & ItemClipsToShape) + clip = clip.intersected(shape()); + + return clip; +} + +/*! + Returns true if this item contains \a point, which is in local + coordinates; otherwise, false is returned. It is most often called from + QGraphicsView to determine what item is under the cursor, and for that + reason, the implementation of this function should be as light-weight as + possible. + + By default, this function calls shape(), but you can reimplement it in a + subclass to provide a (perhaps more efficient) implementation. + + \sa shape(), boundingRect(), collidesWithPath() +*/ +bool QGraphicsItem::contains(const QPointF &point) const +{ + return isClipped() ? clipPath().contains(point) : shape().contains(point); +} + +/*! + + Returns true if this item collides with \a other; otherwise + returns false. + + The \a mode is applied to \a other, and the resulting shape or + bounding rectangle is then compared to this item's shape. The + default value for \a mode is Qt::IntersectsItemShape; \a other + collides with this item if it either intersects, contains, or is + contained by this item's shape (see Qt::ItemSelectionMode for + details). + + The default implementation is based on shape intersection, and it calls + shape() on both items. Because the complexity of arbitrary shape-shape + intersection grows with an order of magnitude when the shapes are complex, + this operation can be noticably time consuming. You have the option of + reimplementing this function in a subclass of QGraphicsItem to provide a + custom algorithm. This allows you to make use of natural constraints in + the shapes of your own items, in order to improve the performance of the + collision detection. For instance, two untransformed perfectly circular + items' collision can be determined very efficiently by comparing their + positions and radii. + + Keep in mind that when reimplementing this function and calling shape() or + boundingRect() on \a other, the returned coordinates must be mapped to + this item's coordinate system before any intersection can take place. + + \sa contains(), shape() +*/ +bool QGraphicsItem::collidesWithItem(const QGraphicsItem *other, Qt::ItemSelectionMode mode) const +{ + if (other == this) + return true; + if (!other) + return false; + // The items share the same clip if their closest clipper is the same, or + // if one clips the other. + bool clips = (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren); + bool otherClips = (other->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren); + if (clips || otherClips) { + const QGraphicsItem *closestClipper = isAncestorOf(other) ? this : parentItem(); + while (closestClipper && !(closestClipper->flags() & ItemClipsChildrenToShape)) + closestClipper = closestClipper->parentItem(); + const QGraphicsItem *otherClosestClipper = other->isAncestorOf(this) ? other : other->parentItem(); + while (otherClosestClipper && !(otherClosestClipper->flags() & ItemClipsChildrenToShape)) + otherClosestClipper = otherClosestClipper->parentItem(); + if (closestClipper == otherClosestClipper) { + d_ptr->localCollisionHack = 1; + bool res = collidesWithPath(mapFromItem(other, other->shape()), mode); + d_ptr->localCollisionHack = 0; + return res; + } + } + + QPainterPath otherShape = other->isClipped() ? other->clipPath() : other->shape(); + return collidesWithPath(mapFromItem(other, otherShape), mode); +} + +/*! + Returns true if this item collides with \a path. + + The collision is determined by \a mode. The default value for \a mode is + Qt::IntersectsItemShape; \a path collides with this item if it either + intersects, contains, or is contained by this item's shape. + + Note that this function checks whether the item's shape or + bounding rectangle (depending on \a mode) is contained within \a + path, and not whether \a path is contained within the items shape + or bounding rectangle. + + \sa collidesWithItem(), contains(), shape() +*/ +bool QGraphicsItem::collidesWithPath(const QPainterPath &path, Qt::ItemSelectionMode mode) const +{ + if (path.isEmpty()) { + // No collision with empty paths. + return false; + } + + QRectF rectA(boundingRect()); + _q_adjustRect(&rectA); + QRectF rectB(path.controlPointRect()); + _q_adjustRect(&rectB); + if (!rectA.intersects(rectB)) { + // This we can determine efficiently. If the two rects neither + // intersect nor contain eachother, then the two items do not collide. + return false; + } + + // For further testing, we need this item's shape or bounding rect. + QPainterPath thisShape; + if (mode == Qt::IntersectsItemShape || mode == Qt::ContainsItemShape) + thisShape = (isClipped() && !d_ptr->localCollisionHack) ? clipPath() : shape(); + else + thisShape.addRect(rectA); + + if (thisShape == QPainterPath()) { + // Empty shape? No collision. + return false; + } + + // Use QPainterPath boolean operations to determine the collision, O(N*logN). + if (mode == Qt::IntersectsItemShape || mode == Qt::IntersectsItemBoundingRect) + return path.intersects(thisShape); + return path.contains(thisShape); +} + +/*! + Returns a list of all items that collide with this item. + + The way collisions are detected is determined by applying \a mode + to items that are compared to this item, i.e., each item's shape + or bounding rectangle is checked against this item's shape. The + default value for \a mode is Qt::IntersectsItemShape. + + \sa collidesWithItem() +*/ +QList QGraphicsItem::collidingItems(Qt::ItemSelectionMode mode) const +{ + if (d_ptr->scene) + return d_ptr->scene->collidingItems(this, mode); + return QList(); +} + +/*! + Returns true if this item's bounding rect is completely obscured by the + opaque shape of any of colliding items above it (i.e., with a higher Z + value than this item). + + Its implementation is based on calling isObscuredBy(), which you can + reimplement to provide a custom obscurity algorithm. + + \sa opaqueArea() +*/ +bool QGraphicsItem::isObscured() const +{ + return isObscured(QRectF()); +} + +/*! + \internal + + Item obscurity helper function. + + Returns true if the subrect \a rect of \a item's bounding rect is obscured + by \a other (i.e., \a other's opaque area covers \a item's \a rect + completely. \a other is assumed to already be "on top of" \a item + wrt. stacking order. +*/ +static bool qt_QGraphicsItem_isObscured(const QGraphicsItem *item, + const QGraphicsItem *other, + const QRectF &rect) +{ + return other->mapToItem(item, other->opaqueArea()).contains(rect); +} + +/*! + \overload + \since 4.3 + + Returns true if \a rect is completely obscured by the opaque shape of any + of colliding items above it (i.e., with a higher Z value than this item). + + Unlike the default isObscured() function, this function does not call + isObscuredBy(). + + \sa opaqueArea() +*/ +bool QGraphicsItem::isObscured(const QRectF &rect) const +{ + Q_D(const QGraphicsItem); + if (!d->scene) + return false; + + QRectF br = boundingRect(); + QRectF testRect = rect.isNull() ? br : rect; + + foreach (QGraphicsItem *item, d->scene->items(mapToScene(br), Qt::IntersectsItemBoundingRect)) { + if (item == this) + break; + if (qt_QGraphicsItem_isObscured(this, item, testRect)) + return true; + } + return false; +} + +/*! + \fn bool QGraphicsItem::isObscured(qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling isObscured(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Returns true if this item's bounding rect is completely obscured by the + opaque shape of \a item. + + The base implementation maps \a item's opaqueArea() to this item's + coordinate system, and then checks if this item's boundingRect() is fully + contained within the mapped shape. + + You can reimplement this function to provide a custom algorithm for + determining whether this item is obscured by \a item. + + \sa opaqueArea(), isObscured() +*/ +bool QGraphicsItem::isObscuredBy(const QGraphicsItem *item) const +{ + if (!item) + return false; + return qt_closestItemFirst(item, this) + && qt_QGraphicsItem_isObscured(this, item, boundingRect()); +} + +/*! + This virtual function returns a shape representing the area where this + item is opaque. An area is opaque if it is filled using an opaque brush or + color (i.e., not transparent). + + This function is used by isObscuredBy(), which is called by underlying + items to determine if they are obscured by this item. + + The default implementation returns an empty QPainterPath, indicating that + this item is completely transparent and does not obscure any other items. + + \sa isObscuredBy(), isObscured(), shape() +*/ +QPainterPath QGraphicsItem::opaqueArea() const +{ + return QPainterPath(); +} + +/*! + \since 4.4 + + Returns the bounding region for this item. The coordinate space of the + returned region depends on \a itemToDeviceTransform. If you pass an + identity QTransform as a parameter, this function will return a local + coordinate region. + + The bounding region describes a coarse outline of the item's visual + contents. Although it's expensive to calculate, it's also more precise + than boundingRect(), and it can help to avoid unnecessary repainting when + an item is updated. This is particularly efficient for thin items (e.g., + lines or simple polygons). You can tune the granularity for the bounding + region by calling setBoundingRegionGranularity(). The default granularity + is 0; in which the item's bounding region is the same as its bounding + rect. + + \a itemToDeviceTransform is the transformation from item coordinates to + device coordinates. If you want this function to return a QRegion in scene + coordinates, you can pass sceneTransform() as an argument. + + \sa boundingRegionGranularity() +*/ +QRegion QGraphicsItem::boundingRegion(const QTransform &itemToDeviceTransform) const +{ + // ### Ideally we would have a better way to generate this region, + // preferably something in the lines of QPainterPath::toRegion(QTransform) + // coupled with a way to generate a painter path from a set of painter + // operations (e.g., QPicture::toPainterPath() or so). The current + // approach generates a bitmap with the size of the item's bounding rect + // in device coordinates, scaled by b.r.granularity, then paints the item + // into the bitmap, converts the result to a QRegion and scales the region + // back to device space with inverse granularity. + qreal granularity = boundingRegionGranularity(); + QRect deviceRect = itemToDeviceTransform.mapRect(boundingRect()).toRect(); + _q_adjustRect(&deviceRect); + if (granularity == 0.0) + return QRegion(deviceRect); + + int pad = 1; + QSize bitmapSize(qMax(1, int(deviceRect.width() * granularity) + pad * 2), + qMax(1, int(deviceRect.height() * granularity) + pad * 2)); + QImage mask(bitmapSize, QImage::Format_ARGB32_Premultiplied); + mask.fill(0); + QPainter p(&mask); + p.setRenderHints(QPainter::Antialiasing); + + // Transform painter (### this code is from QGraphicsScene::drawItemHelper + // and doesn't work properly with perspective transformations). + QPointF viewOrigo = itemToDeviceTransform.map(QPointF(0, 0)); + QPointF offset = viewOrigo - deviceRect.topLeft(); + p.scale(granularity, granularity); + p.translate(offset); + p.translate(pad, pad); + p.setWorldTransform(itemToDeviceTransform, true); + p.translate(itemToDeviceTransform.inverted().map(QPointF(0, 0))); + + // Render + QStyleOptionGraphicsItem option; + const_cast(this)->paint(&p, &option, 0); + p.end(); + + // Transform QRegion back to device space + QTransform unscale = QTransform::fromScale(1 / granularity, 1 / granularity); + QRegion r; + QBitmap colorMask = QBitmap::fromImage(mask.createMaskFromColor(0)); + foreach (const QRect &rect, QRegion( colorMask ).rects()) { + QRect xrect = unscale.mapRect(rect).translated(deviceRect.topLeft() - QPoint(pad, pad)); + r += xrect.adjusted(-1, -1, 1, 1) & deviceRect; + } + return r; +} + +/*! + \since 4.4 + + Returns the item's bounding region granularity; a value between and + including 0 and 1. The default value is 0 (i.e., the lowest granularity, + where the bounding region corresponds to the item's bounding rectangle). + +\omit +### NOTE +\endomit + + \sa setBoundingRegionGranularity() +*/ +qreal QGraphicsItem::boundingRegionGranularity() const +{ + return d_ptr->hasBoundingRegionGranularity + ? qvariant_cast(d_ptr->extra(QGraphicsItemPrivate::ExtraBoundingRegionGranularity)) + : 0; +} + +/*! + \since 4.4 + Sets the bounding region granularity to \a granularity; a value between + and including 0 and 1. The default value is 0 (i.e., the lowest + granularity, where the bounding region corresponds to the item's bounding + rectangle). + + The granularity is used by boundingRegion() to calculate how fine the + bounding region of the item should be. The highest achievable granularity + is 1, where boundingRegion() will return the finest outline possible for + the respective device (e.g., for a QGraphicsView viewport, this gives you + a pixel-perfect bounding region). The lowest possible granularity is + 0. The value of \a granularity describes the ratio between device + resolution and the resolution of the bounding region (e.g., a value of + 0.25 will provide a region where each chunk corresponds to 4x4 device + units / pixels). + + \sa boundingRegionGranularity() +*/ +void QGraphicsItem::setBoundingRegionGranularity(qreal granularity) +{ + if (granularity < 0.0 || granularity > 1.0) { + qWarning("QGraphicsItem::setBoundingRegionGranularity: invalid granularity %g", granularity); + return; + } + if (granularity == 0.0) { + d_ptr->unsetExtra(QGraphicsItemPrivate::ExtraBoundingRegionGranularity); + d_ptr->hasBoundingRegionGranularity = 0; + return; + } + d_ptr->hasBoundingRegionGranularity = 1; + d_ptr->setExtra(QGraphicsItemPrivate::ExtraBoundingRegionGranularity, + QVariant::fromValue(granularity)); +} + +/*! + \fn virtual void QGraphicsItem::paint(QPainter *painter, const + QStyleOptionGraphicsItem *option, QWidget *widget = 0) = 0 + + This function, which is usually called by QGraphicsView, paints the + contents of an item in local coordinates. + + Reimplement this function in a QGraphicsItem subclass to provide the + item's painting implementation, using \a painter. The \a option parameter + provides style options for the item, such as its state, exposed area and + its level-of-detail hints. The \a widget argument is optional. If + provided, it points to the widget that is being painted on; otherwise, it + is 0. For cached painting, \a widget is always 0. + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 10 + + The painter's pen is 0-width by default, and its pen is initialized to the + QPalette::Text brush from the paint device's palette. The brush is + initialized to QPalette::Window. + + Make sure to constrain all painting inside the boundaries of + boundingRect() to avoid rendering artifacts (as QGraphicsView does not + clip the painter for you). In particular, when QPainter renders the + outline of a shape using an assigned QPen, half of the outline will be + drawn outside, and half inside, the shape you're rendering (e.g., with a + pen width of 2 units, you must draw outlines 1 unit inside + boundingRect()). QGraphicsItem does not support use of cosmetic pens with + a non-zero width. + + All painting is done in local coordinates. + + \sa setCacheMode(), QPen::width(), {Item Coordinates}, ItemUsesExtendedStyleOption +*/ + +/*! + \internal + Returns true if we can discard an update request; otherwise false. +*/ +bool QGraphicsItemPrivate::discardUpdateRequest(bool ignoreVisibleBit, bool ignoreDirtyBit, + bool ignoreOpacity) const +{ + // No scene, or if the scene is updating everything, means we have nothing + // to do. The only exception is if the scene tracks the growing scene rect. + return !scene + || (!visible && !ignoreVisibleBit && !this->ignoreVisible) + || (!ignoreDirtyBit && fullUpdatePending) + || (!ignoreOpacity && !this->ignoreOpacity && childrenCombineOpacity() && isFullyTransparent()); +} + +/*! + \internal +*/ +int QGraphicsItemPrivate::depth() const +{ + if (itemDepth == -1) + const_cast(this)->resolveDepth(); + + return itemDepth; +} + +/*! + \internal +*/ +#ifndef QT_NO_GRAPHICSEFFECT +void QGraphicsItemPrivate::invalidateParentGraphicsEffectsRecursively() +{ + QGraphicsItemPrivate *itemPrivate = this; + do { + if (itemPrivate->graphicsEffect) { + itemPrivate->notifyInvalidated = 1; + + if (!itemPrivate->updateDueToGraphicsEffect) + static_cast(itemPrivate->graphicsEffect->d_func()->source->d_func())->invalidateCache(); + } + } while ((itemPrivate = itemPrivate->parent ? itemPrivate->parent->d_ptr.data() : 0)); +} + +void QGraphicsItemPrivate::invalidateChildGraphicsEffectsRecursively(QGraphicsItemPrivate::InvalidateReason reason) +{ + if (!mayHaveChildWithGraphicsEffect) + return; + + for (int i = 0; i < children.size(); ++i) { + QGraphicsItemPrivate *childPrivate = children.at(i)->d_ptr.data(); + if (reason == OpacityChanged && (childPrivate->flags & QGraphicsItem::ItemIgnoresParentOpacity)) + continue; + if (childPrivate->graphicsEffect) { + childPrivate->notifyInvalidated = 1; + static_cast(childPrivate->graphicsEffect->d_func()->source->d_func())->invalidateCache(); + } + + childPrivate->invalidateChildGraphicsEffectsRecursively(reason); + } +} +#endif //QT_NO_GRAPHICSEFFECT + +/*! + \internal +*/ +void QGraphicsItemPrivate::invalidateDepthRecursively() +{ + if (itemDepth == -1) + return; + + itemDepth = -1; + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->invalidateDepthRecursively(); +} + +/*! + \internal + + Resolves the stacking depth of this object and all its ancestors. +*/ +void QGraphicsItemPrivate::resolveDepth() +{ + if (!parent) + itemDepth = 0; + else { + if (parent->d_ptr->itemDepth == -1) + parent->d_ptr->resolveDepth(); + itemDepth = parent->d_ptr->itemDepth + 1; + } +} + +/*! + \internal + + ### This function is almost identical to + QGraphicsScenePrivate::registerTopLevelItem(). +*/ +void QGraphicsItemPrivate::addChild(QGraphicsItem *child) +{ + // Remove all holes from the sibling index list. Now the max index + // number is equal to the size of the children list. + ensureSequentialSiblingIndex(); + needSortChildren = 1; // ### maybe 0 + child->d_ptr->siblingIndex = children.size(); + children.append(child); + if (isObject) + emit static_cast(q_ptr)->childrenChanged(); +} + +/*! + \internal + + ### This function is almost identical to + QGraphicsScenePrivate::unregisterTopLevelItem(). +*/ +void QGraphicsItemPrivate::removeChild(QGraphicsItem *child) +{ + // When removing elements in the middle of the children list, + // there will be a "gap" in the list of sibling indexes (0,1,3,4). + if (!holesInSiblingIndex) + holesInSiblingIndex = child->d_ptr->siblingIndex != children.size() - 1; + if (sequentialOrdering && !holesInSiblingIndex) + children.removeAt(child->d_ptr->siblingIndex); + else + children.removeOne(child); + // NB! Do not use children.removeAt(child->d_ptr->siblingIndex) because + // the child is not guaranteed to be at the index after the list is sorted. + // (see ensureSortedChildren()). + child->d_ptr->siblingIndex = -1; + if (isObject) + emit static_cast(q_ptr)->childrenChanged(); +} + +/*! + \internal +*/ +QGraphicsItemCache *QGraphicsItemPrivate::maybeExtraItemCache() const +{ + return (QGraphicsItemCache *)qvariant_cast(extra(ExtraCacheData)); +} + +/*! + \internal +*/ +QGraphicsItemCache *QGraphicsItemPrivate::extraItemCache() const +{ + QGraphicsItemCache *c = (QGraphicsItemCache *)qvariant_cast(extra(ExtraCacheData)); + if (!c) { + QGraphicsItemPrivate *that = const_cast(this); + c = new QGraphicsItemCache; + that->setExtra(ExtraCacheData, QVariant::fromValue(c)); + } + return c; +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::removeExtraItemCache() +{ + QGraphicsItemCache *c = (QGraphicsItemCache *)qvariant_cast(extra(ExtraCacheData)); + if (c) { + c->purge(); + delete c; + } + unsetExtra(ExtraCacheData); +} + +void QGraphicsItemPrivate::updatePaintedViewBoundingRects(bool updateChildren) +{ + if (!scene) + return; + + for (int i = 0; i < scene->d_func()->views.size(); ++i) { + QGraphicsViewPrivate *viewPrivate = scene->d_func()->views.at(i)->d_func(); + QRect rect = paintedViewBoundingRects.value(viewPrivate->viewport); + rect.translate(viewPrivate->dirtyScrollOffset); + viewPrivate->updateRect(rect); + } + + if (updateChildren) { + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->updatePaintedViewBoundingRects(true); + } +} + +// Traverses all the ancestors up to the top-level and updates the pointer to +// always point to the top-most item that has a dirty scene transform. +// It then backtracks to the top-most dirty item and start calculating the +// scene transform by combining the item's transform (+pos) with the parent's +// cached scene transform (which we at this point know for sure is valid). +void QGraphicsItemPrivate::ensureSceneTransformRecursive(QGraphicsItem **topMostDirtyItem) +{ + Q_ASSERT(topMostDirtyItem); + + if (dirtySceneTransform) + *topMostDirtyItem = q_ptr; + + if (parent) + parent->d_ptr->ensureSceneTransformRecursive(topMostDirtyItem); + + if (*topMostDirtyItem == q_ptr) { + if (!dirtySceneTransform) + return; // OK, neither my ancestors nor I have dirty scene transforms. + *topMostDirtyItem = 0; + } else if (*topMostDirtyItem) { + return; // Continue backtrack. + } + + // This item and all its descendants have dirty scene transforms. + // We're about to validate this item's scene transform, so we have to + // invalidate all the children; otherwise there's no way for the descendants + // to detect that the ancestor has changed. + invalidateChildrenSceneTransform(); + + // COMBINE my transform with the parent's scene transform. + updateSceneTransformFromParent(); + Q_ASSERT(!dirtySceneTransform); +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::setSubFocus(QGraphicsItem *rootItem, QGraphicsItem *stopItem) +{ + // Update focus child chain. Stop at panels, or if this item + // is hidden, stop at the first item with a visible parent. + QGraphicsItem *parent = rootItem ? rootItem : q_ptr; + if (parent->panel() != q_ptr->panel()) + return; + + do { + // Clear any existing ancestor's subFocusItem. + if (parent != q_ptr && parent->d_ptr->subFocusItem) { + if (parent->d_ptr->subFocusItem == q_ptr) + break; + parent->d_ptr->subFocusItem->d_ptr->clearSubFocus(0, stopItem); + } + parent->d_ptr->subFocusItem = q_ptr; + parent->d_ptr->subFocusItemChange(); + } while (!parent->isPanel() && (parent = parent->d_ptr->parent) && (visible || !parent->d_ptr->visible)); + + if (scene && !scene->isActive()) { + scene->d_func()->passiveFocusItem = subFocusItem; + scene->d_func()->lastFocusItem = subFocusItem; + } +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::clearSubFocus(QGraphicsItem *rootItem, QGraphicsItem *stopItem) +{ + // Reset sub focus chain. + QGraphicsItem *parent = rootItem ? rootItem : q_ptr; + do { + if (parent->d_ptr->subFocusItem != q_ptr) + break; + parent->d_ptr->subFocusItem = 0; + if (parent != stopItem && !parent->isAncestorOf(stopItem)) + parent->d_ptr->subFocusItemChange(); + } while (!parent->isPanel() && (parent = parent->d_ptr->parent)); +} + +/*! + \internal + + Sets the focusProxy pointer to 0 for all items that have this item as their + focusProxy. ### Qt 5: Use QPointer instead. +*/ +void QGraphicsItemPrivate::resetFocusProxy() +{ + for (int i = 0; i < focusProxyRefs.size(); ++i) + *focusProxyRefs.at(i) = 0; + focusProxyRefs.clear(); +} + +/*! + \internal + + Subclasses can reimplement this function to be notified when subFocusItem + changes. +*/ +void QGraphicsItemPrivate::subFocusItemChange() +{ +} + +/*! + \internal + + Subclasses can reimplement this function to be notified when an item + becomes a focusScopeItem (or is no longer a focusScopeItem). +*/ +void QGraphicsItemPrivate::focusScopeItemChange(bool isSubFocusItem) +{ + Q_UNUSED(isSubFocusItem); +} + +/*! + \internal + + Subclasses can reimplement this function to be notified when its + siblingIndex order is changed. +*/ +void QGraphicsItemPrivate::siblingOrderChange() +{ +} + +/*! + \internal + + Tells us if it is a proxy widget +*/ +bool QGraphicsItemPrivate::isProxyWidget() const +{ + return false; +} + +/*! + Schedules a redraw of the area covered by \a rect in this item. You can + call this function whenever your item needs to be redrawn, such as if it + changes appearance or size. + + This function does not cause an immediate paint; instead it schedules a + paint request that is processed by QGraphicsView after control reaches the + event loop. The item will only be redrawn if it is visible in any + associated view. + + As a side effect of the item being repainted, other items that overlap the + area \a rect may also be repainted. + + If the item is invisible (i.e., isVisible() returns false), this function + does nothing. + + \sa paint(), boundingRect() +*/ +void QGraphicsItem::update(const QRectF &rect) +{ + if (rect.isEmpty() && !rect.isNull()) + return; + + // Make sure we notify effects about invalidated source. +#ifndef QT_NO_GRAPHICSEFFECT + d_ptr->invalidateParentGraphicsEffectsRecursively(); +#endif //QT_NO_GRAPHICSEFFECT + + if (CacheMode(d_ptr->cacheMode) != NoCache) { + // Invalidate cache. + QGraphicsItemCache *cache = d_ptr->extraItemCache(); + if (!cache->allExposed) { + if (rect.isNull()) { + cache->allExposed = true; + cache->exposed.clear(); + } else { + cache->exposed.append(rect); + } + } + // Only invalidate cache; item is already dirty. + if (d_ptr->fullUpdatePending) + return; + } + + if (d_ptr->scene) + d_ptr->scene->d_func()->markDirty(this, rect); +} + +/*! + \since 4.4 + Scrolls the contents of \a rect by \a dx, \a dy. If \a rect is a null rect + (the default), the item's bounding rect is scrolled. + + Scrolling provides a fast alternative to simply redrawing when the + contents of the item (or parts of the item) are shifted vertically or + horizontally. Depending on the current transformation and the capabilities + of the paint device (i.e., the viewport), this operation may consist of + simply moving pixels from one location to another using memmove(). In most + cases this is faster than rerendering the entire area. + + After scrolling, the item will issue an update for the newly exposed + areas. If scrolling is not supported (e.g., you are rendering to an OpenGL + viewport, which does not benefit from scroll optimizations), this function + is equivalent to calling update(\a rect). + + \bold{Note:} Scrolling is only supported when QGraphicsItem::ItemCoordinateCache + is enabled; in all other cases calling this function is equivalent to calling + update(\a rect). If you for sure know that the item is opaque and not overlapped + by other items, you can map the \a rect to viewport coordinates and scroll the + viewport. + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 19 + + \sa boundingRect() +*/ +void QGraphicsItem::scroll(qreal dx, qreal dy, const QRectF &rect) +{ + Q_D(QGraphicsItem); + if (dx == 0.0 && dy == 0.0) + return; + if (!d->scene) + return; + + // Accelerated scrolling means moving pixels from one location to another + // and only redraw the newly exposed area. The following requirements must + // be fulfilled in order to do that: + // + // 1) Item is opaque. + // 2) Item is not overlapped by other items. + // + // There's (yet) no way to detect whether an item is opaque or not, which means + // we cannot do accelerated scrolling unless the cache is enabled. In case of using + // DeviceCoordinate cache we also have to take the device transform into account in + // order to determine whether we can do accelerated scrolling or not. That's left out + // for simplicity here, but it is definitely something we can consider in the future + // as a performance improvement. + if (d->cacheMode != QGraphicsItem::ItemCoordinateCache + || !qFuzzyIsNull(dx - int(dx)) || !qFuzzyIsNull(dy - int(dy))) { + update(rect); + return; + } + + QGraphicsItemCache *cache = d->extraItemCache(); + if (cache->allExposed || cache->fixedSize.isValid()) { + // Cache is either invalidated or item is scaled (see QGraphicsItem::setCacheMode). + update(rect); + return; + } + + // Find pixmap in cache. + QPixmap cachedPixmap; + if (!QPixmapCache::find(cache->key, &cachedPixmap)) { + update(rect); + return; + } + + QRect scrollRect = (rect.isNull() ? boundingRect() : rect).toAlignedRect(); + if (!scrollRect.intersects(cache->boundingRect)) + return; // Nothing to scroll. + + // Remove from cache to avoid deep copy when modifying. + QPixmapCache::remove(cache->key); + + QRegion exposed; + cachedPixmap.scroll(dx, dy, scrollRect.translated(-cache->boundingRect.topLeft()), &exposed); + + // Reinsert into cache. + cache->key = QPixmapCache::insert(cachedPixmap); + + // Translate the existing expose. + for (int i = 0; i < cache->exposed.size(); ++i) { + QRectF &e = cache->exposed[i]; + if (!rect.isNull() && !e.intersects(rect)) + continue; + e.translate(dx, dy); + } + + // Append newly exposed areas. Note that the exposed region is currently + // in pixmap coordinates, so we have to translate it to item coordinates. + exposed.translate(cache->boundingRect.topLeft()); + const QVector exposedRects = exposed.rects(); + for (int i = 0; i < exposedRects.size(); ++i) + cache->exposed += exposedRects.at(i); + + // Trigger update. This will redraw the newly exposed area and make sure + // the pixmap is re-blitted in case there are overlapping items. + d->scene->d_func()->markDirty(this, rect); +} + +/*! + \fn void QGraphicsItem::update(qreal x, qreal y, qreal width, qreal height) + \overload + + This convenience function is equivalent to calling update(QRectF(\a x, \a + y, \a width, \a height)). +*/ + +/*! + Maps the point \a point, which is in this item's coordinate system, to \a + item's coordinate system, and returns the mapped coordinate. + + If \a item is 0, this function returns the same as mapToScene(). + + \sa itemTransform(), mapToParent(), mapToScene(), transform(), mapFromItem(), {The Graphics + View Coordinate System} +*/ +QPointF QGraphicsItem::mapToItem(const QGraphicsItem *item, const QPointF &point) const +{ + if (item) + return itemTransform(item).map(point); + return mapToScene(point); +} + +/*! + \fn QPointF QGraphicsItem::mapToItem(const QGraphicsItem *item, qreal x, qreal y) const + \overload + + This convenience function is equivalent to calling mapToItem(\a item, + QPointF(\a x, \a y)). +*/ + +/*! + Maps the point \a point, which is in this item's coordinate system, to its + parent's coordinate system, and returns the mapped coordinate. If the item + has no parent, \a point will be mapped to the scene's coordinate system. + + \sa mapToItem(), mapToScene(), transform(), mapFromParent(), {The Graphics + View Coordinate System} +*/ +QPointF QGraphicsItem::mapToParent(const QPointF &point) const +{ + // COMBINE + if (!d_ptr->transformData) + return point + d_ptr->pos; + return d_ptr->transformToParent().map(point); +} + +/*! + \fn QPointF QGraphicsItem::mapToParent(qreal x, qreal y) const + \overload + + This convenience function is equivalent to calling mapToParent(QPointF(\a + x, \a y)). +*/ + +/*! + Maps the point \a point, which is in this item's coordinate system, to the + scene's coordinate system, and returns the mapped coordinate. + + \sa mapToItem(), mapToParent(), transform(), mapFromScene(), {The Graphics + View Coordinate System} +*/ +QPointF QGraphicsItem::mapToScene(const QPointF &point) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return QPointF(point.x() + d_ptr->sceneTransform.dx(), point.y() + d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.map(point); +} + +/*! + \fn QPointF QGraphicsItem::mapToScene(qreal x, qreal y) const + \overload + + This convenience function is equivalent to calling mapToScene(QPointF(\a + x, \a y)). +*/ + +/*! + Maps the rectangle \a rect, which is in this item's coordinate system, to + \a item's coordinate system, and returns the mapped rectangle as a polygon. + + If \a item is 0, this function returns the same as mapToScene(). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QPolygonF QGraphicsItem::mapToItem(const QGraphicsItem *item, const QRectF &rect) const +{ + if (item) + return itemTransform(item).map(rect); + return mapToScene(rect); +} + +/*! + \fn QPolygonF QGraphicsItem::mapToItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling mapToItem(item, QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Maps the rectangle \a rect, which is in this item's coordinate system, to + its parent's coordinate system, and returns the mapped rectangle as a + polygon. If the item has no parent, \a rect will be mapped to the scene's + coordinate system. + + \sa mapToScene(), mapToItem(), mapFromParent(), {The Graphics View + Coordinate System} +*/ +QPolygonF QGraphicsItem::mapToParent(const QRectF &rect) const +{ + // COMBINE + if (!d_ptr->transformData) + return rect.translated(d_ptr->pos); + return d_ptr->transformToParent().map(rect); +} + +/*! + \fn QPolygonF QGraphicsItem::mapToParent(qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling mapToParent(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Maps the rectangle \a rect, which is in this item's coordinate system, to + the scene's coordinate system, and returns the mapped rectangle as a polygon. + + \sa mapToParent(), mapToItem(), mapFromScene(), {The Graphics View + Coordinate System} +*/ +QPolygonF QGraphicsItem::mapToScene(const QRectF &rect) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return rect.translated(d_ptr->sceneTransform.dx(), d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.map(rect); +} + +/*! + \fn QPolygonF QGraphicsItem::mapToScene(qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling mapToScene(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + \since 4.5 + + Maps the rectangle \a rect, which is in this item's coordinate system, to + \a item's coordinate system, and returns the mapped rectangle as a new + rectangle (i.e., the bounding rectangle of the resulting polygon). + + If \a item is 0, this function returns the same as mapRectToScene(). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::mapRectToItem(const QGraphicsItem *item, const QRectF &rect) const +{ + if (item) + return itemTransform(item).mapRect(rect); + return mapRectToScene(rect); +} + +/*! + \fn QRectF QGraphicsItem::mapRectToItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const + \since 4.5 + + This convenience function is equivalent to calling mapRectToItem(item, QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + \since 4.5 + + Maps the rectangle \a rect, which is in this item's coordinate system, to + its parent's coordinate system, and returns the mapped rectangle as a new + rectangle (i.e., the bounding rectangle of the resulting polygon). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::mapRectToParent(const QRectF &rect) const +{ + // COMBINE + if (!d_ptr->transformData) + return rect.translated(d_ptr->pos); + return d_ptr->transformToParent().mapRect(rect); +} + +/*! + \fn QRectF QGraphicsItem::mapRectToParent(qreal x, qreal y, qreal w, qreal h) const + \since 4.5 + + This convenience function is equivalent to calling mapRectToParent(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + \since 4.5 + + Maps the rectangle \a rect, which is in this item's coordinate system, to + the scene coordinate system, and returns the mapped rectangle as a new + rectangle (i.e., the bounding rectangle of the resulting polygon). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::mapRectToScene(const QRectF &rect) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return rect.translated(d_ptr->sceneTransform.dx(), d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.mapRect(rect); +} + +/*! + \fn QRectF QGraphicsItem::mapRectToScene(qreal x, qreal y, qreal w, qreal h) const + \since 4.5 + + This convenience function is equivalent to calling mapRectToScene(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + \since 4.5 + + Maps the rectangle \a rect, which is in \a item's coordinate system, to + this item's coordinate system, and returns the mapped rectangle as a new + rectangle (i.e., the bounding rectangle of the resulting polygon). + + If \a item is 0, this function returns the same as mapRectFromScene(). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::mapRectFromItem(const QGraphicsItem *item, const QRectF &rect) const +{ + if (item) + return item->itemTransform(this).mapRect(rect); + return mapRectFromScene(rect); +} + +/*! + \fn QRectF QGraphicsItem::mapRectFromItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const + \since 4.5 + + This convenience function is equivalent to calling mapRectFromItem(item, QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + \since 4.5 + + Maps the rectangle \a rect, which is in this item's parent's coordinate + system, to this item's coordinate system, and returns the mapped rectangle + as a new rectangle (i.e., the bounding rectangle of the resulting + polygon). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::mapRectFromParent(const QRectF &rect) const +{ + // COMBINE + if (!d_ptr->transformData) + return rect.translated(-d_ptr->pos); + return d_ptr->transformToParent().inverted().mapRect(rect); +} + +/*! + \fn QRectF QGraphicsItem::mapRectFromParent(qreal x, qreal y, qreal w, qreal h) const + \since 4.5 + + This convenience function is equivalent to calling mapRectFromParent(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + \since 4.5 + + Maps the rectangle \a rect, which is in scene coordinates, to this item's + coordinate system, and returns the mapped rectangle as a new rectangle + (i.e., the bounding rectangle of the resulting polygon). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::mapRectFromScene(const QRectF &rect) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return rect.translated(-d_ptr->sceneTransform.dx(), -d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.inverted().mapRect(rect); +} + +/*! + \fn QRectF QGraphicsItem::mapRectFromScene(qreal x, qreal y, qreal w, qreal h) const + \since 4.5 + + This convenience function is equivalent to calling mapRectFromScene(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Maps the polygon \a polygon, which is in this item's coordinate system, to + \a item's coordinate system, and returns the mapped polygon. + + If \a item is 0, this function returns the same as mapToScene(). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QPolygonF QGraphicsItem::mapToItem(const QGraphicsItem *item, const QPolygonF &polygon) const +{ + if (item) + return itemTransform(item).map(polygon); + return mapToScene(polygon); +} + +/*! + Maps the polygon \a polygon, which is in this item's coordinate system, to + its parent's coordinate system, and returns the mapped polygon. If the + item has no parent, \a polygon will be mapped to the scene's coordinate + system. + + \sa mapToScene(), mapToItem(), mapFromParent(), {The Graphics View + Coordinate System} +*/ +QPolygonF QGraphicsItem::mapToParent(const QPolygonF &polygon) const +{ + // COMBINE + if (!d_ptr->transformData) + return polygon.translated(d_ptr->pos); + return d_ptr->transformToParent().map(polygon); +} + +/*! + Maps the polygon \a polygon, which is in this item's coordinate system, to + the scene's coordinate system, and returns the mapped polygon. + + \sa mapToParent(), mapToItem(), mapFromScene(), {The Graphics View + Coordinate System} +*/ +QPolygonF QGraphicsItem::mapToScene(const QPolygonF &polygon) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return polygon.translated(d_ptr->sceneTransform.dx(), d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.map(polygon); +} + +/*! + Maps the path \a path, which is in this item's coordinate system, to + \a item's coordinate system, and returns the mapped path. + + If \a item is 0, this function returns the same as mapToScene(). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QPainterPath QGraphicsItem::mapToItem(const QGraphicsItem *item, const QPainterPath &path) const +{ + if (item) + return itemTransform(item).map(path); + return mapToScene(path); +} + +/*! + Maps the path \a path, which is in this item's coordinate system, to + its parent's coordinate system, and returns the mapped path. If the + item has no parent, \a path will be mapped to the scene's coordinate + system. + + \sa mapToScene(), mapToItem(), mapFromParent(), {The Graphics View + Coordinate System} +*/ +QPainterPath QGraphicsItem::mapToParent(const QPainterPath &path) const +{ + // COMBINE + if (!d_ptr->transformData) + return path.translated(d_ptr->pos); + return d_ptr->transformToParent().map(path); +} + +/*! + Maps the path \a path, which is in this item's coordinate system, to + the scene's coordinate system, and returns the mapped path. + + \sa mapToParent(), mapToItem(), mapFromScene(), {The Graphics View + Coordinate System} +*/ +QPainterPath QGraphicsItem::mapToScene(const QPainterPath &path) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return path.translated(d_ptr->sceneTransform.dx(), d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.map(path); +} + +/*! + Maps the point \a point, which is in \a item's coordinate system, to this + item's coordinate system, and returns the mapped coordinate. + + If \a item is 0, this function returns the same as mapFromScene(). + + \sa itemTransform(), mapFromParent(), mapFromScene(), transform(), mapToItem(), {The Graphics + View Coordinate System} +*/ +QPointF QGraphicsItem::mapFromItem(const QGraphicsItem *item, const QPointF &point) const +{ + if (item) + return item->itemTransform(this).map(point); + return mapFromScene(point); +} + +/*! + \fn QPointF QGraphicsItem::mapFromItem(const QGraphicsItem *item, qreal x, qreal y) const + \overload + + This convenience function is equivalent to calling mapFromItem(\a item, + QPointF(\a x, \a y)). +*/ + +/*! + Maps the point \a point, which is in this item's parent's coordinate + system, to this item's coordinate system, and returns the mapped + coordinate. + + \sa mapFromItem(), mapFromScene(), transform(), mapToParent(), {The Graphics + View Coordinate System} +*/ +QPointF QGraphicsItem::mapFromParent(const QPointF &point) const +{ + // COMBINE + if (d_ptr->transformData) + return d_ptr->transformToParent().inverted().map(point); + return point - d_ptr->pos; +} + +/*! + \fn QPointF QGraphicsItem::mapFromParent(qreal x, qreal y) const + \overload + + This convenience function is equivalent to calling + mapFromParent(QPointF(\a x, \a y)). +*/ + +/*! + Maps the point \a point, which is in this item's scene's coordinate + system, to this item's coordinate system, and returns the mapped + coordinate. + + \sa mapFromItem(), mapFromParent(), transform(), mapToScene(), {The Graphics + View Coordinate System} +*/ +QPointF QGraphicsItem::mapFromScene(const QPointF &point) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return QPointF(point.x() - d_ptr->sceneTransform.dx(), point.y() - d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.inverted().map(point); +} + +/*! + \fn QPointF QGraphicsItem::mapFromScene(qreal x, qreal y) const + \overload + + This convenience function is equivalent to calling mapFromScene(QPointF(\a + x, \a y)). +*/ + +/*! + Maps the rectangle \a rect, which is in \a item's coordinate system, to + this item's coordinate system, and returns the mapped rectangle as a + polygon. + + If \a item is 0, this function returns the same as mapFromScene() + + \sa itemTransform(), mapToItem(), mapFromParent(), transform(), {The Graphics View Coordinate + System} +*/ +QPolygonF QGraphicsItem::mapFromItem(const QGraphicsItem *item, const QRectF &rect) const +{ + if (item) + return item->itemTransform(this).map(rect); + return mapFromScene(rect); +} + +/*! + \fn QPolygonF QGraphicsItem::mapFromItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling mapFromItem(item, QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Maps the rectangle \a rect, which is in this item's parent's coordinate + system, to this item's coordinate system, and returns the mapped rectangle + as a polygon. + + \sa mapToParent(), mapFromItem(), transform(), {The Graphics View Coordinate + System} +*/ +QPolygonF QGraphicsItem::mapFromParent(const QRectF &rect) const +{ + // COMBINE + if (!d_ptr->transformData) + return rect.translated(-d_ptr->pos); + return d_ptr->transformToParent().inverted().map(rect); +} + +/*! + \fn QPolygonF QGraphicsItem::mapFromParent(qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling mapFromItem(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Maps the rectangle \a rect, which is in this item's scene's coordinate + system, to this item's coordinate system, and returns the mapped rectangle + as a polygon. + + \sa mapToScene(), mapFromItem(), transform(), {The Graphics View Coordinate + System} +*/ +QPolygonF QGraphicsItem::mapFromScene(const QRectF &rect) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return rect.translated(-d_ptr->sceneTransform.dx(), -d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.inverted().map(rect); +} + +/*! + \fn QPolygonF QGraphicsItem::mapFromScene(qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling mapFromScene(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Maps the polygon \a polygon, which is in \a item's coordinate system, to + this item's coordinate system, and returns the mapped polygon. + + If \a item is 0, this function returns the same as mapFromScene(). + + \sa itemTransform(), mapToItem(), mapFromParent(), transform(), {The + Graphics View Coordinate System} +*/ +QPolygonF QGraphicsItem::mapFromItem(const QGraphicsItem *item, const QPolygonF &polygon) const +{ + if (item) + return item->itemTransform(this).map(polygon); + return mapFromScene(polygon); +} + +/*! + Maps the polygon \a polygon, which is in this item's parent's coordinate + system, to this item's coordinate system, and returns the mapped polygon. + + \sa mapToParent(), mapToItem(), transform(), {The Graphics View Coordinate + System} +*/ +QPolygonF QGraphicsItem::mapFromParent(const QPolygonF &polygon) const +{ + // COMBINE + if (!d_ptr->transformData) + return polygon.translated(-d_ptr->pos); + return d_ptr->transformToParent().inverted().map(polygon); +} + +/*! + Maps the polygon \a polygon, which is in this item's scene's coordinate + system, to this item's coordinate system, and returns the mapped polygon. + + \sa mapToScene(), mapFromParent(), transform(), {The Graphics View Coordinate + System} +*/ +QPolygonF QGraphicsItem::mapFromScene(const QPolygonF &polygon) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return polygon.translated(-d_ptr->sceneTransform.dx(), -d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.inverted().map(polygon); +} + +/*! + Maps the path \a path, which is in \a item's coordinate system, to + this item's coordinate system, and returns the mapped path. + + If \a item is 0, this function returns the same as mapFromScene(). + + \sa itemTransform(), mapFromParent(), mapFromScene(), mapToItem(), {The + Graphics View Coordinate System} +*/ +QPainterPath QGraphicsItem::mapFromItem(const QGraphicsItem *item, const QPainterPath &path) const +{ + if (item) + return item->itemTransform(this).map(path); + return mapFromScene(path); +} + +/*! + Maps the path \a path, which is in this item's parent's coordinate + system, to this item's coordinate system, and returns the mapped path. + + \sa mapFromScene(), mapFromItem(), mapToParent(), {The Graphics View + Coordinate System} +*/ +QPainterPath QGraphicsItem::mapFromParent(const QPainterPath &path) const +{ + // COMBINE + if (!d_ptr->transformData) + return path.translated(-d_ptr->pos); + return d_ptr->transformToParent().inverted().map(path); +} + +/*! + Maps the path \a path, which is in this item's scene's coordinate + system, to this item's coordinate system, and returns the mapped path. + + \sa mapFromParent(), mapFromItem(), mapToScene(), {The Graphics View + Coordinate System} +*/ +QPainterPath QGraphicsItem::mapFromScene(const QPainterPath &path) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return path.translated(-d_ptr->sceneTransform.dx(), -d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.inverted().map(path); +} + +/*! + Returns true if this item is an ancestor of \a child (i.e., if this item + is \a child's parent, or one of \a child's parent's ancestors). + + \sa parentItem() +*/ +bool QGraphicsItem::isAncestorOf(const QGraphicsItem *child) const +{ + if (!child || child == this) + return false; + if (child->d_ptr->depth() < d_ptr->depth()) + return false; + const QGraphicsItem *ancestor = child; + while ((ancestor = ancestor->d_ptr->parent)) { + if (ancestor == this) + return true; + } + return false; +} + +/*! + \since 4.4 + + Returns the closest common ancestor item of this item and \a other, or 0 + if either \a other is 0, or there is no common ancestor. + + \sa isAncestorOf() +*/ +QGraphicsItem *QGraphicsItem::commonAncestorItem(const QGraphicsItem *other) const +{ + if (!other) + return 0; + if (other == this) + return const_cast(this); + const QGraphicsItem *thisw = this; + const QGraphicsItem *otherw = other; + int thisDepth = d_ptr->depth(); + int otherDepth = other->d_ptr->depth(); + while (thisDepth > otherDepth) { + thisw = thisw->d_ptr->parent; + --thisDepth; + } + while (otherDepth > thisDepth) { + otherw = otherw->d_ptr->parent; + --otherDepth; + } + while (thisw && thisw != otherw) { + thisw = thisw->d_ptr->parent; + otherw = otherw->d_ptr->parent; + } + return const_cast(thisw); +} + +/*! + \since 4,4 + Returns true if this item is currently under the mouse cursor in one of + the views; otherwise, false is returned. + + \sa QGraphicsScene::views(), QCursor::pos() +*/ +bool QGraphicsItem::isUnderMouse() const +{ + Q_D(const QGraphicsItem); + if (!d->scene) + return false; + + QPoint cursorPos = QCursor::pos(); + foreach (QGraphicsView *view, d->scene->views()) { + if (contains(mapFromScene(view->mapToScene(view->mapFromGlobal(cursorPos))))) + return true; + } + return false; +} + +/*! + Returns this item's custom data for the key \a key as a QVariant. + + Custom item data is useful for storing arbitrary properties in any + item. Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 11 + + Qt does not use this feature for storing data; it is provided solely + for the convenience of the user. + + \sa setData() +*/ +QVariant QGraphicsItem::data(int key) const +{ + QGraphicsItemCustomDataStore *store = qt_dataStore(); + if (!store->data.contains(this)) + return QVariant(); + return store->data.value(this).value(key); +} + +/*! + Sets this item's custom data for the key \a key to \a value. + + Custom item data is useful for storing arbitrary properties for any + item. Qt does not use this feature for storing data; it is provided solely + for the convenience of the user. + + \sa data() +*/ +void QGraphicsItem::setData(int key, const QVariant &value) +{ + qt_dataStore()->data[this][key] = value; +} + +/*! + \fn T qgraphicsitem_cast(QGraphicsItem *item) + \relates QGraphicsItem + \since 4.2 + + Returns the given \a item cast to type T if \a item is of type T; + otherwise, 0 is returned. + + \note To make this function work correctly with custom items, reimplement + the \l{QGraphicsItem::}{type()} function for each custom QGraphicsItem + subclass. + + \sa QGraphicsItem::type(), QGraphicsItem::UserType +*/ + +/*! + Returns the type of an item as an int. All standard graphicsitem classes + are associated with a unique value; see QGraphicsItem::Type. This type + information is used by qgraphicsitem_cast() to distinguish between types. + + The default implementation (in QGraphicsItem) returns UserType. + + To enable use of qgraphicsitem_cast() with a custom item, reimplement this + function and declare a Type enum value equal to your custom item's type. + Custom items must return a value larger than or equal to UserType (65536). + + For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp QGraphicsItem type + + \sa UserType +*/ +int QGraphicsItem::type() const +{ + return (int)UserType; +} + +/*! + Installs an event filter for this item on \a filterItem, causing + all events for this item to first pass through \a filterItem's + sceneEventFilter() function. + + To filter another item's events, install this item as an event filter + for the other item. Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 12 + + An item can only filter events for other items in the same + scene. Also, an item cannot filter its own events; instead, you + can reimplement sceneEvent() directly. + + Items must belong to a scene for scene event filters to be installed and + used. + + \sa removeSceneEventFilter(), sceneEventFilter(), sceneEvent() +*/ +void QGraphicsItem::installSceneEventFilter(QGraphicsItem *filterItem) +{ + if (!d_ptr->scene) { + qWarning("QGraphicsItem::installSceneEventFilter: event filters can only be installed" + " on items in a scene."); + return; + } + if (d_ptr->scene != filterItem->scene()) { + qWarning("QGraphicsItem::installSceneEventFilter: event filters can only be installed" + " on items in the same scene."); + return; + } + d_ptr->scene->d_func()->installSceneEventFilter(this, filterItem); +} + +/*! + Removes an event filter on this item from \a filterItem. + + \sa installSceneEventFilter() +*/ +void QGraphicsItem::removeSceneEventFilter(QGraphicsItem *filterItem) +{ + if (!d_ptr->scene || d_ptr->scene != filterItem->scene()) + return; + d_ptr->scene->d_func()->removeSceneEventFilter(this, filterItem); +} + +/*! + Filters events for the item \a watched. \a event is the filtered + event. + + Reimplementing this function in a subclass makes it possible + for the item to be used as an event filter for other items, + intercepting all the events send to those items before they are + able to respond. + + Reimplementations must return true to prevent further processing of + a given event, ensuring that it will not be delivered to the watched + item, or return false to indicate that the event should be propagated + further by the event system. + + \sa installSceneEventFilter() +*/ +bool QGraphicsItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event) +{ + Q_UNUSED(watched); + Q_UNUSED(event); + return false; +} + +/*! + This virtual function receives events to this item. Reimplement + this function to intercept events before they are dispatched to + the specialized event handlers contextMenuEvent(), focusInEvent(), + focusOutEvent(), hoverEnterEvent(), hoverMoveEvent(), + hoverLeaveEvent(), keyPressEvent(), keyReleaseEvent(), + mousePressEvent(), mouseReleaseEvent(), mouseMoveEvent(), and + mouseDoubleClickEvent(). + + Returns true if the event was recognized and handled; otherwise, (e.g., if + the event type was not recognized,) false is returned. + + \a event is the intercepted event. +*/ +bool QGraphicsItem::sceneEvent(QEvent *event) +{ + if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorHandlesChildEvents) { + if (event->type() == QEvent::HoverEnter || event->type() == QEvent::HoverLeave + || event->type() == QEvent::DragEnter || event->type() == QEvent::DragLeave) { + // Hover enter and hover leave events for children are ignored; + // hover move events are forwarded. + return true; + } + + QGraphicsItem *handler = this; + do { + handler = handler->d_ptr->parent; + Q_ASSERT(handler); + } while (handler->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorHandlesChildEvents); + // Forward the event to the closest parent that handles child + // events, mapping existing item-local coordinates to its + // coordinate system. + d_ptr->remapItemPos(event, handler); + handler->sceneEvent(event); + return true; + } + + if (event->type() == QEvent::FocusOut) { + focusOutEvent(static_cast(event)); + return true; + } + + if (!d_ptr->visible) { + // Eaten + return true; + } + + switch (event->type()) { + case QEvent::FocusIn: + focusInEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneContextMenu: + contextMenuEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDragEnter: + dragEnterEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDragMove: + dragMoveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDragLeave: + dragLeaveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDrop: + dropEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverEnter: + hoverEnterEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverMove: + hoverMoveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverLeave: + hoverLeaveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseMove: + mouseMoveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMousePress: + mousePressEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseRelease: + mouseReleaseEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseDoubleClick: + mouseDoubleClickEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneWheel: + wheelEvent(static_cast(event)); + break; + case QEvent::KeyPress: { + QKeyEvent *k = static_cast(event); + if (k->key() == Qt::Key_Tab || k->key() == Qt::Key_Backtab) { + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + bool res = false; + if (k->key() == Qt::Key_Backtab + || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) { + if (d_ptr->isWidget) { + res = static_cast(this)->focusNextPrevChild(false); + } else if (d_ptr->scene) { + res = d_ptr->scene->focusNextPrevChild(false); + } + } else if (k->key() == Qt::Key_Tab) { + if (d_ptr->isWidget) { + res = static_cast(this)->focusNextPrevChild(true); + } else if (d_ptr->scene) { + res = d_ptr->scene->focusNextPrevChild(true); + } + } + if (!res) + event->ignore(); + return true; + } + } + keyPressEvent(static_cast(event)); + break; + } + case QEvent::KeyRelease: + keyReleaseEvent(static_cast(event)); + break; + case QEvent::InputMethod: + inputMethodEvent(static_cast(event)); + break; + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + // Propagate panel activation. + if (d_ptr->scene) { + for (int i = 0; i < d_ptr->children.size(); ++i) { + QGraphicsItem *child = d_ptr->children.at(i); + if (child->isVisible() && !child->isPanel()) { + if (!(child->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorHandlesChildEvents)) + d_ptr->scene->sendEvent(child, event); + } + } + } + break; + default: + return false; + } + + return true; +} + +/*! + This event handler can be reimplemented in a subclass to process context + menu events. The \a event parameter contains details about the event to + be handled. + + If you ignore the event, (i.e., by calling QEvent::ignore(),) \a event + will propagate to any item beneath this item. If no items accept the + event, it will be ignored by the scene, and propagate to the view. + + It's common to open a QMenu in response to receiving a context menu + event. Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 13 + + The default implementation ignores the event. + + \sa sceneEvent() +*/ +void QGraphicsItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + event->ignore(); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + drag enter events for this item. Drag enter events are generated as the + cursor enters the item's area. + + By accepting the event, (i.e., by calling QEvent::accept(),) the item will + accept drop events, in addition to receiving drag move and drag + leave. Otherwise, the event will be ignored and propagate to the item + beneath. If the event is accepted, the item will receive a drag move event + before control goes back to the event loop. + + A common implementation of dragEnterEvent accepts or ignores \a event + depending on the associated mime data in \a event. Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 14 + + Items do not receive drag and drop events by default; to enable this + feature, call \c setAcceptDrops(true). + + The default implementation does nothing. + + \sa dropEvent(), dragMoveEvent(), dragLeaveEvent() +*/ +void QGraphicsItem::dragEnterEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsItem); + // binary compatibility workaround between 4.4 and 4.5 + if (d->isProxyWidget()) + static_cast(this)->dragEnterEvent(event); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + drag leave events for this item. Drag leave events are generated as the + cursor leaves the item's area. Most often you will not need to reimplement + this function, but it can be useful for resetting state in your item + (e.g., highlighting). + + Calling QEvent::ignore() or QEvent::accept() on \a event has no effect. + + Items do not receive drag and drop events by default; to enable this + feature, call \c setAcceptDrops(true). + + The default implementation does nothing. + + \sa dragEnterEvent(), dropEvent(), dragMoveEvent() +*/ +void QGraphicsItem::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsItem); + // binary compatibility workaround between 4.4 and 4.5 + if (d->isProxyWidget()) + static_cast(this)->dragLeaveEvent(event); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + drag move events for this item. Drag move events are generated as the + cursor moves around inside the item's area. Most often you will not need + to reimplement this function; it is used to indicate that only parts of + the item can accept drops. + + Calling QEvent::ignore() or QEvent::accept() on \a event toggles whether + or not the item will accept drops at the position from the event. By + default, \a event is accepted, indicating that the item allows drops at + the specified position. + + Items do not receive drag and drop events by default; to enable this + feature, call \c setAcceptDrops(true). + + The default implementation does nothing. + + \sa dropEvent(), dragEnterEvent(), dragLeaveEvent() +*/ +void QGraphicsItem::dragMoveEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsItem); + // binary compatibility workaround between 4.4 and 4.5 + if (d->isProxyWidget()) + static_cast(this)->dragMoveEvent(event); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + drop events for this item. Items can only receive drop events if the last + drag move event was accepted. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no effect. + + Items do not receive drag and drop events by default; to enable this + feature, call \c setAcceptDrops(true). + + The default implementation does nothing. + + \sa dragEnterEvent(), dragMoveEvent(), dragLeaveEvent() +*/ +void QGraphicsItem::dropEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsItem); + // binary compatibility workaround between 4.4 and 4.5 + if (d->isProxyWidget()) + static_cast(this)->dropEvent(event); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + focus in events for this item. The default implementation calls + ensureVisible(). + + \sa focusOutEvent(), sceneEvent(), setFocus() +*/ +void QGraphicsItem::focusInEvent(QFocusEvent *event) +{ + Q_UNUSED(event); + update(); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + focus out events for this item. The default implementation does nothing. + + \sa focusInEvent(), sceneEvent(), setFocus() +*/ +void QGraphicsItem::focusOutEvent(QFocusEvent *event) +{ + Q_UNUSED(event); + update(); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + hover enter events for this item. The default implementation calls + update(); otherwise it does nothing. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no effect. + + \sa hoverMoveEvent(), hoverLeaveEvent(), sceneEvent(), setAcceptHoverEvents() +*/ +void QGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + update(); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + hover move events for this item. The default implementation does nothing. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no effect. + + \sa hoverEnterEvent(), hoverLeaveEvent(), sceneEvent(), setAcceptHoverEvents() +*/ +void QGraphicsItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + hover leave events for this item. The default implementation calls + update(); otherwise it does nothing. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no effect. + + \sa hoverEnterEvent(), hoverMoveEvent(), sceneEvent(), setAcceptHoverEvents() +*/ +void QGraphicsItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + update(); +} + +/*! + This event handler, for event \a event, can be reimplemented to + receive key press events for this item. The default implementation + ignores the event. If you reimplement this handler, the event will by + default be accepted. + + Note that key events are only received for items that set the + ItemIsFocusable flag, and that have keyboard input focus. + + \sa keyReleaseEvent(), setFocus(), QGraphicsScene::setFocusItem(), + sceneEvent() +*/ +void QGraphicsItem::keyPressEvent(QKeyEvent *event) +{ + event->ignore(); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + key release events for this item. The default implementation + ignores the event. If you reimplement this handler, the event will by + default be accepted. + + Note that key events are only received for items that set the + ItemIsFocusable flag, and that have keyboard input focus. + + \sa keyPressEvent(), setFocus(), QGraphicsScene::setFocusItem(), + sceneEvent() +*/ +void QGraphicsItem::keyReleaseEvent(QKeyEvent *event) +{ + event->ignore(); +} + +/*! + This event handler, for event \a event, can be reimplemented to + receive mouse press events for this item. Mouse press events are + only delivered to items that accept the mouse button that is + pressed. By default, an item accepts all mouse buttons, but you + can change this by calling setAcceptedMouseButtons(). + + The mouse press event decides which item should become the mouse + grabber (see QGraphicsScene::mouseGrabberItem()). If you do not + reimplement this function, the press event will propagate to any + topmost item beneath this item, and no other mouse events will be + delivered to this item. + + If you do reimplement this function, \a event will by default be + accepted (see QEvent::accept()), and this item is then the mouse + grabber. This allows the item to receive future move, release and + doubleclick events. If you call QEvent::ignore() on \a event, this + item will lose the mouse grab, and \a event will propagate to any + topmost item beneath. No further mouse events will be delivered to + this item unless a new mouse press event is received. + + The default implementation handles basic item interaction, such as + selection and moving. If you want to keep the base implementation + when reimplementing this function, call + QGraphicsItem::mousePressEvent() in your reimplementation. + + The event is \l{QEvent::ignore()}d for items that are neither + \l{QGraphicsItem::ItemIsMovable}{movable} nor + \l{QGraphicsItem::ItemIsSelectable}{selectable}. + + \sa mouseMoveEvent(), mouseReleaseEvent(), + mouseDoubleClickEvent(), sceneEvent() +*/ +void QGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->button() == Qt::LeftButton && (flags() & ItemIsSelectable)) { + bool multiSelect = (event->modifiers() & Qt::ControlModifier) != 0; + if (!multiSelect) { + if (!d_ptr->selected) { + if (QGraphicsScene *scene = d_ptr->scene) { + ++scene->d_func()->selectionChanging; + scene->clearSelection(); + --scene->d_func()->selectionChanging; + } + setSelected(true); + } + } + } else if (!(flags() & ItemIsMovable)) { + event->ignore(); + } + if (d_ptr->isWidget) { + // Qt::Popup closes when you click outside. + QGraphicsWidget *w = static_cast(this); + if ((w->windowFlags() & Qt::Popup) == Qt::Popup) { + event->accept(); + if (!w->rect().contains(event->pos())) + w->close(); + } + } +} + +/*! + obsolete +*/ +bool _qt_movableAncestorIsSelected(const QGraphicsItem *item) +{ + const QGraphicsItem *parent = item->parentItem(); + return parent && (((parent->flags() & QGraphicsItem::ItemIsMovable) && parent->isSelected()) || _qt_movableAncestorIsSelected(parent)); +} + +bool QGraphicsItemPrivate::movableAncestorIsSelected(const QGraphicsItem *item) +{ + const QGraphicsItem *parent = item->d_ptr->parent; + return parent && (((parent->flags() & QGraphicsItem::ItemIsMovable) && parent->isSelected()) || _qt_movableAncestorIsSelected(parent)); +} + +/*! + This event handler, for event \a event, can be reimplemented to + receive mouse move events for this item. If you do receive this + event, you can be certain that this item also received a mouse + press event, and that this item is the current mouse grabber. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no + effect. + + The default implementation handles basic item interaction, such as + selection and moving. If you want to keep the base implementation + when reimplementing this function, call + QGraphicsItem::mouseMoveEvent() in your reimplementation. + + Please note that mousePressEvent() decides which graphics item it + is that receives mouse events. See the mousePressEvent() + description for details. + + \sa mousePressEvent(), mouseReleaseEvent(), + mouseDoubleClickEvent(), sceneEvent() +*/ +void QGraphicsItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if ((event->buttons() & Qt::LeftButton) && (flags() & ItemIsMovable)) { + // Determine the list of items that need to be moved. + QList selectedItems; + QMap initialPositions; + if (d_ptr->scene) { + selectedItems = d_ptr->scene->selectedItems(); + initialPositions = d_ptr->scene->d_func()->movingItemsInitialPositions; + if (initialPositions.isEmpty()) { + foreach (QGraphicsItem *item, selectedItems) + initialPositions[item] = item->pos(); + initialPositions[this] = pos(); + } + d_ptr->scene->d_func()->movingItemsInitialPositions = initialPositions; + } + + // Find the active view. + QGraphicsView *view = 0; + if (event->widget()) + view = qobject_cast(event->widget()->parentWidget()); + + // Move all selected items + int i = 0; + bool movedMe = false; + while (i <= selectedItems.size()) { + QGraphicsItem *item = 0; + if (i < selectedItems.size()) + item = selectedItems.at(i); + else + item = this; + if (item == this) { + // Slightly clumsy-looking way to ensure that "this" is part + // of the list of items to move, this is to avoid allocations + // (appending this item to the list of selected items causes a + // detach). + if (movedMe) + break; + movedMe = true; + } + + if ((item->flags() & ItemIsMovable) && !QGraphicsItemPrivate::movableAncestorIsSelected(item)) { + QPointF currentParentPos; + QPointF buttonDownParentPos; + if (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorIgnoresTransformations) { + // Items whose ancestors ignore transformations need to + // map screen coordinates to local coordinates, then map + // those to the parent. + QTransform viewToItemTransform = (item->deviceTransform(view->viewportTransform())).inverted(); + currentParentPos = mapToParent(viewToItemTransform.map(QPointF(view->mapFromGlobal(event->screenPos())))); + buttonDownParentPos = mapToParent(viewToItemTransform.map(QPointF(view->mapFromGlobal(event->buttonDownScreenPos(Qt::LeftButton))))); + } else if (item->flags() & ItemIgnoresTransformations) { + // Root items that ignore transformations need to + // calculate their diff by mapping viewport coordinates + // directly to parent coordinates. + // COMBINE + QTransform itemTransform; + if (item->d_ptr->transformData) + itemTransform = item->d_ptr->transformData->computedFullTransform(); + itemTransform.translate(item->d_ptr->pos.x(), item->d_ptr->pos.y()); + QTransform viewToParentTransform = itemTransform + * (item->sceneTransform() * view->viewportTransform()).inverted(); + currentParentPos = viewToParentTransform.map(QPointF(view->mapFromGlobal(event->screenPos()))); + buttonDownParentPos = viewToParentTransform.map(QPointF(view->mapFromGlobal(event->buttonDownScreenPos(Qt::LeftButton)))); + } else { + // All other items simply map from the scene. + currentParentPos = item->mapToParent(item->mapFromScene(event->scenePos())); + buttonDownParentPos = item->mapToParent(item->mapFromScene(event->buttonDownScenePos(Qt::LeftButton))); + } + + item->setPos(initialPositions.value(item) + currentParentPos - buttonDownParentPos); + + if (item->flags() & ItemIsSelectable) + item->setSelected(true); + } + ++i; + } + + } else { + event->ignore(); + } +} + +/*! + This event handler, for event \a event, can be reimplemented to + receive mouse release events for this item. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no + effect. + + The default implementation handles basic item interaction, such as + selection and moving. If you want to keep the base implementation + when reimplementing this function, call + QGraphicsItem::mouseReleaseEvent() in your reimplementation. + + Please note that mousePressEvent() decides which graphics item it + is that receives mouse events. See the mousePressEvent() + description for details. + + \sa mousePressEvent(), mouseMoveEvent(), mouseDoubleClickEvent(), + sceneEvent() +*/ +void QGraphicsItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (flags() & ItemIsSelectable) { + bool multiSelect = (event->modifiers() & Qt::ControlModifier) != 0; + if (event->scenePos() == event->buttonDownScenePos(Qt::LeftButton)) { + // The item didn't move + if (multiSelect) { + setSelected(!isSelected()); + } else { + bool selectionChanged = false; + if (QGraphicsScene *scene = d_ptr->scene) { + ++scene->d_func()->selectionChanging; + // Clear everything but this item. Bypass + // QGraphicsScene::clearSelection()'s default behavior by + // temporarily removing this item from the selection list. + if (d_ptr->selected) { + scene->d_func()->selectedItems.remove(this); + foreach (QGraphicsItem *item, scene->d_func()->selectedItems) { + if (item->isSelected()) { + selectionChanged = true; + break; + } + } + } + scene->clearSelection(); + if (d_ptr->selected) + scene->d_func()->selectedItems.insert(this); + --scene->d_func()->selectionChanging; + if (selectionChanged) + emit d_ptr->scene->selectionChanged(); + } + setSelected(true); + } + } + } + if (d_ptr->scene && !event->buttons()) + d_ptr->scene->d_func()->movingItemsInitialPositions.clear(); +} + +/*! + This event handler, for event \a event, can be reimplemented to + receive mouse doubleclick events for this item. + + When doubleclicking an item, the item will first receive a mouse + press event, followed by a release event (i.e., a click), then a + doubleclick event, and finally a release event. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no + effect. + + The default implementation calls mousePressEvent(). If you want to + keep the base implementation when reimplementing this function, + call QGraphicsItem::mouseDoubleClickEvent() in your + reimplementation. + + Note that an item will not receive double click events if it is + neither \l {QGraphicsItem::ItemIsSelectable}{selectable} nor + \l{QGraphicsItem::ItemIsMovable}{movable} (single mouse clicks are + ignored in this case, and that stops the generation of double + clicks). + + \sa mousePressEvent(), mouseMoveEvent(), mouseReleaseEvent(), sceneEvent() +*/ +void QGraphicsItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + mousePressEvent(event); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + wheel events for this item. If you reimplement this function, \a event + will be accepted by default. + + If you ignore the event, (i.e., by calling QEvent::ignore(),) it will + propagate to any item beneath this item. If no items accept the event, it + will be ignored by the scene, and propagate to the view (e.g., the view's + vertical scroll bar). + + The default implementation ignores the event. + + \sa sceneEvent() +*/ +void QGraphicsItem::wheelEvent(QGraphicsSceneWheelEvent *event) +{ + event->ignore(); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + input method events for this item. The default implementation ignores the + event. + + \sa inputMethodQuery(), sceneEvent() +*/ +void QGraphicsItem::inputMethodEvent(QInputMethodEvent *event) +{ + event->ignore(); +} + +/*! + This method is only relevant for input items. It is used by the + input method to query a set of properties of the item to be able + to support complex input method operations, such as support for + surrounding text and reconversions. \a query specifies which + property is queried. + + \sa inputMethodEvent(), QInputMethodEvent, QInputContext +*/ +QVariant QGraphicsItem::inputMethodQuery(Qt::InputMethodQuery query) const +{ + if (isWidget()) { + // ### Qt 5: Remove. The reimplementation in + // QGraphicsProxyWidget solves this problem (but requires a + // recompile to take effect). + return d_ptr->inputMethodQueryHelper(query); + } + + Q_UNUSED(query); + return QVariant(); +} + +/*! + Returns the current input method hints of this item. + + Input method hints are only relevant for input items. + The hints are used by the input method to indicate how it should operate. + For example, if the Qt::ImhNumbersOnly flag is set, the input method may change + its visual components to reflect that only numbers can be entered. + + The effect may vary between input method implementations. + + \since 4.6 + + \sa setInputMethodHints(), inputMethodQuery(), QInputContext +*/ +Qt::InputMethodHints QGraphicsItem::inputMethodHints() const +{ + Q_D(const QGraphicsItem); + return d->imHints; +} + +/*! + Sets the current input method hints of this item to \a hints. + + \since 4.6 + + \sa inputMethodHints(), inputMethodQuery(), QInputContext +*/ +void QGraphicsItem::setInputMethodHints(Qt::InputMethodHints hints) +{ + Q_D(QGraphicsItem); + d->imHints = hints; + if (!hasFocus()) + return; + d->scene->d_func()->updateInputMethodSensitivityInViews(); +#if !defined(QT_NO_IM) && (defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN)) + QWidget *fw = QApplication::focusWidget(); + if (!fw) + return; + for (int i = 0 ; i < scene()->views().count() ; ++i) + if (scene()->views().at(i) == fw) + if (QInputContext *inputContext = fw->inputContext()) + inputContext->update(); +#endif +} + +/*! + Updates the item's micro focus. + + \since 4.7 + + \sa QInputContext +*/ +void QGraphicsItem::updateMicroFocus() +{ +#if !defined(QT_NO_IM) && (defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN)) + if (QWidget *fw = QApplication::focusWidget()) { + if (scene()) { + for (int i = 0 ; i < scene()->views().count() ; ++i) { + if (scene()->views().at(i) == fw) + if (QInputContext *inputContext = fw->inputContext()) + inputContext->update(); + } + } +#ifndef QT_NO_ACCESSIBILITY + // ##### is this correct + QAccessible::updateAccessibility(fw, 0, QAccessible::StateChanged); +#endif + } +#endif +} + +/*! + This virtual function is called by QGraphicsItem to notify custom items + that some part of the item's state changes. By reimplementing this + function, your can react to a change, and in some cases, (depending on \a + change,) adjustments can be made. + + \a change is the parameter of the item that is changing. \a value is the + new value; the type of the value depends on \a change. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 15 + + The default implementation does nothing, and returns \a value. + + Note: Certain QGraphicsItem functions cannot be called in a + reimplementation of this function; see the GraphicsItemChange + documentation for details. + + \sa GraphicsItemChange +*/ +QVariant QGraphicsItem::itemChange(GraphicsItemChange change, const QVariant &value) +{ + Q_UNUSED(change); + return value; +} + +/*! + \internal + + Note: This is provided as a hook to avoid future problems related + to adding virtual functions. +*/ +bool QGraphicsItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal + + Note: This is provided as a hook to avoid future problems related + to adding virtual functions. +*/ +void QGraphicsItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal + + Note: This is provided as a hook to avoid future problems related + to adding virtual functions. +*/ +QVariant QGraphicsItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \internal + + Adds this item to the scene's index. Called in conjunction with + removeFromIndex() to ensure the index bookkeeping is correct when + the item's position, transformation or shape changes. +*/ +void QGraphicsItem::addToIndex() +{ + if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) { + // ### add to child index only if applicable + return; + } + if (d_ptr->scene) + d_ptr->scene->d_func()->index->addItem(this); +} + +/*! + \internal + + Removes this item from the scene's index. Called in conjunction + with addToIndex() to ensure the index bookkeeping is correct when + the item's position, transformation or shape changes. +*/ +void QGraphicsItem::removeFromIndex() +{ + if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) { + // ### remove from child index only if applicable + return; + } + if (d_ptr->scene) + d_ptr->scene->d_func()->index->removeItem(this); +} + +/*! + Prepares the item for a geometry change. Call this function before + changing the bounding rect of an item to keep QGraphicsScene's index up to + date. + + prepareGeometryChange() will call update() if this is necessary. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 16 + + \sa boundingRect() +*/ +void QGraphicsItem::prepareGeometryChange() +{ + if (d_ptr->inDestructor) + return; + if (d_ptr->scene) { + d_ptr->scene->d_func()->dirtyGrowingItemsBoundingRect = true; + d_ptr->geometryChanged = 1; + d_ptr->paintedViewBoundingRectsNeedRepaint = 1; + d_ptr->notifyBoundingRectChanged = !d_ptr->inSetPosHelper; + + QGraphicsScenePrivate *scenePrivate = d_ptr->scene->d_func(); + scenePrivate->index->prepareBoundingRectChange(this); + scenePrivate->markDirty(this, QRectF(), /*invalidateChildren=*/true, /*force=*/false, + /*ignoreOpacity=*/ false, /*removingItemFromScene=*/ false, + /*updateBoundingRect=*/true); + + // For compatibility reasons, we have to update the item's old geometry + // if someone is connected to the changed signal or the scene has no views. + // Note that this has to be done *after* markDirty to ensure that + // _q_processDirtyItems is called before _q_emitUpdated. + if (scenePrivate->isSignalConnected(scenePrivate->changedSignalIndex) + || scenePrivate->views.isEmpty()) { + if (d_ptr->hasTranslateOnlySceneTransform()) { + d_ptr->scene->update(boundingRect().translated(d_ptr->sceneTransform.dx(), + d_ptr->sceneTransform.dy())); + } else { + d_ptr->scene->update(d_ptr->sceneTransform.mapRect(boundingRect())); + } + } + } + + d_ptr->markParentDirty(/*updateBoundingRect=*/true); +} + +/*! + \internal + + Highlights \a item as selected. + + NOTE: This function is a duplicate of qt_graphicsItem_highlightSelected() in + qgraphicssvgitem.cpp! +*/ +static void qt_graphicsItem_highlightSelected( + QGraphicsItem *item, QPainter *painter, const QStyleOptionGraphicsItem *option) +{ + const QRectF murect = painter->transform().mapRect(QRectF(0, 0, 1, 1)); + if (qFuzzyIsNull(qMax(murect.width(), murect.height()))) + return; + + const QRectF mbrect = painter->transform().mapRect(item->boundingRect()); + if (qMin(mbrect.width(), mbrect.height()) < qreal(1.0)) + return; + + qreal itemPenWidth; + switch (item->type()) { + case QGraphicsEllipseItem::Type: + itemPenWidth = static_cast(item)->pen().widthF(); + break; + case QGraphicsPathItem::Type: + itemPenWidth = static_cast(item)->pen().widthF(); + break; + case QGraphicsPolygonItem::Type: + itemPenWidth = static_cast(item)->pen().widthF(); + break; + case QGraphicsRectItem::Type: + itemPenWidth = static_cast(item)->pen().widthF(); + break; + case QGraphicsSimpleTextItem::Type: + itemPenWidth = static_cast(item)->pen().widthF(); + break; + case QGraphicsLineItem::Type: + itemPenWidth = static_cast(item)->pen().widthF(); + break; + default: + itemPenWidth = 1.0; + } + const qreal pad = itemPenWidth / 2; + + const qreal penWidth = 0; // cosmetic pen + + const QColor fgcolor = option->palette.windowText().color(); + const QColor bgcolor( // ensure good contrast against fgcolor + fgcolor.red() > 127 ? 0 : 255, + fgcolor.green() > 127 ? 0 : 255, + fgcolor.blue() > 127 ? 0 : 255); + + painter->setPen(QPen(bgcolor, penWidth, Qt::SolidLine)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad)); + + painter->setPen(QPen(option->palette.windowText(), 0, Qt::DashLine)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad)); +} + +/*! + \class QGraphicsObject + \brief The QGraphicsObject class provides a base class for all graphics items that + require signals, slots and properties. + \since 4.6 + \ingroup graphicsview-api + + The class extends a QGraphicsItem with QObject's signal/slot and property mechanisms. + It maps many of QGraphicsItem's basic setters and getters to properties and adds notification + signals for many of them. + + \section1 Parents and Children + + Each graphics object can be constructed with a parent item. This ensures that the + item will be destroyed when its parent item is destroyed. Although QGraphicsObject + inherits from both QObject and QGraphicsItem, you should use the functions provided + by QGraphicsItem, \e not QObject, to manage the relationships between parent and + child items. + + The relationships between items can be explored using the parentItem() and childItems() + functions. In the hierarchy of items in a scene, the parentObject() and parentWidget() + functions are the equivalent of the QWidget::parent() and QWidget::parentWidget() + functions for QWidget subclasses. + + \sa QGraphicsWidget +*/ + +/*! + Constructs a QGraphicsObject with \a parent. +*/ +QGraphicsObject::QGraphicsObject(QGraphicsItem *parent) + : QGraphicsItem(parent) +{ + QGraphicsItem::d_ptr->isObject = true; +} + +/*! + \internal +*/ +QGraphicsObject::QGraphicsObject(QGraphicsItemPrivate &dd, QGraphicsItem *parent, QGraphicsScene *scene) + : QGraphicsItem(dd, parent, scene) +{ + QGraphicsItem::d_ptr->isObject = true; +} + +#ifndef QT_NO_GESTURES +/*! + Subscribes the graphics object to the given \a gesture with specific \a flags. + + \sa ungrabGesture(), QGestureEvent +*/ +void QGraphicsObject::grabGesture(Qt::GestureType gesture, Qt::GestureFlags flags) +{ + bool contains = QGraphicsItem::d_ptr->gestureContext.contains(gesture); + QGraphicsItem::d_ptr->gestureContext.insert(gesture, flags); + if (!contains && QGraphicsItem::d_ptr->scene) + QGraphicsItem::d_ptr->scene->d_func()->grabGesture(this, gesture); +} + +/*! + Unsubscribes the graphics object from the given \a gesture. + + \sa grabGesture(), QGestureEvent +*/ +void QGraphicsObject::ungrabGesture(Qt::GestureType gesture) +{ + if (QGraphicsItem::d_ptr->gestureContext.remove(gesture) && QGraphicsItem::d_ptr->scene) + QGraphicsItem::d_ptr->scene->d_func()->ungrabGesture(this, gesture); +} +#endif // QT_NO_GESTURES + +/*! + Updates the item's micro focus. This is slot for convenience. + + \since 4.7 + + \sa QInputContext +*/ +void QGraphicsObject::updateMicroFocus() +{ + QGraphicsItem::updateMicroFocus(); +} + +void QGraphicsItemPrivate::children_append(QDeclarativeListProperty *list, QGraphicsObject *item) +{ + if (item) { + QGraphicsObject *graphicsObject = static_cast(list->object); + if (QGraphicsItemPrivate::get(graphicsObject)->sendParentChangeNotification) { + item->setParentItem(graphicsObject); + } else { + QGraphicsItemPrivate::get(item)->setParentItemHelper(graphicsObject, 0, 0); + } + } +} + +int QGraphicsItemPrivate::children_count(QDeclarativeListProperty *list) +{ + QGraphicsItemPrivate *d = QGraphicsItemPrivate::get(static_cast(list->object)); + return d->children.count(); +} + +QGraphicsObject *QGraphicsItemPrivate::children_at(QDeclarativeListProperty *list, int index) +{ + QGraphicsItemPrivate *d = QGraphicsItemPrivate::get(static_cast(list->object)); + if (index >= 0 && index < d->children.count()) + return d->children.at(index)->toGraphicsObject(); + else + return 0; +} + +void QGraphicsItemPrivate::children_clear(QDeclarativeListProperty *list) +{ + QGraphicsItemPrivate *d = QGraphicsItemPrivate::get(static_cast(list->object)); + int childCount = d->children.count(); + if (d->sendParentChangeNotification) { + for (int index = 0; index < childCount; index++) + d->children.at(0)->setParentItem(0); + } else { + for (int index = 0; index < childCount; index++) + QGraphicsItemPrivate::get(d->children.at(0))->setParentItemHelper(0, 0, 0); + } +} + +/*! + Returns a list of this item's children. + + The items are sorted by stacking order. This takes into account both the + items' insertion order and their Z-values. + +*/ +QDeclarativeListProperty QGraphicsItemPrivate::childrenList() +{ + Q_Q(QGraphicsItem); + if (isObject) { + QGraphicsObject *that = static_cast(q); + return QDeclarativeListProperty(that, &children, children_append, + children_count, children_at, children_clear); + } else { + //QGraphicsItem is not supported for this property + return QDeclarativeListProperty(); + } +} + +/*! + \internal + Returns the width of the item + Reimplemented by QGraphicsWidget +*/ +qreal QGraphicsItemPrivate::width() const +{ + return 0; +} + +/*! + \internal + Set the width of the item + Reimplemented by QGraphicsWidget +*/ +void QGraphicsItemPrivate::setWidth(qreal w) +{ + Q_UNUSED(w); +} + +/*! + \internal + Reset the width of the item + Reimplemented by QGraphicsWidget +*/ +void QGraphicsItemPrivate::resetWidth() +{ +} + +/*! + \internal + Returns the height of the item + Reimplemented by QGraphicsWidget +*/ +qreal QGraphicsItemPrivate::height() const +{ + return 0; +} + +/*! + \internal + Set the height of the item + Reimplemented by QGraphicsWidget +*/ +void QGraphicsItemPrivate::setHeight(qreal h) +{ + Q_UNUSED(h); +} + +/*! + \internal + Reset the height of the item + Reimplemented by QGraphicsWidget +*/ +void QGraphicsItemPrivate::resetHeight() +{ +} + +/*! + \property QGraphicsObject::children + \since 4.7 + \internal +*/ + +/*! + \property QGraphicsObject::width + \since 4.7 + \internal +*/ + +/*! + \property QGraphicsObject::height + \since 4.7 + \internal +*/ + +/*! + \property QGraphicsObject::parent + \brief the parent of the item + + \note The item's parent is set independently of the parent object returned + by QObject::parent(). + + \sa QGraphicsItem::setParentItem(), QGraphicsItem::parentObject() +*/ + +/*! + \property QGraphicsObject::opacity + \brief the opacity of the item + + \sa QGraphicsItem::setOpacity(), QGraphicsItem::opacity() +*/ + +/*! + \fn QGraphicsObject::opacityChanged() + + This signal gets emitted whenever the opacity of the item changes + + \sa QGraphicsItem::opacity() +*/ + +/*! + \fn QGraphicsObject::parentChanged() + + This signal gets emitted whenever the parent of the item changes +*/ + +/*! + \property QGraphicsObject::pos + \brief the position of the item + + Describes the items position. + + \sa QGraphicsItem::setPos(), QGraphicsItem::pos() +*/ + +/*! + \property QGraphicsObject::x + \brief the x position of the item + + Describes the items x position. + + \sa QGraphicsItem::setX(), setPos(), xChanged() +*/ + +/*! + \fn QGraphicsObject::xChanged() + + This signal gets emitted whenever the x position of the item changes + + \sa pos() +*/ + +/*! + \property QGraphicsObject::y + \brief the y position of the item + + Describes the items y position. + + \sa QGraphicsItem::setY(), setPos(), yChanged() +*/ + +/*! + \fn QGraphicsObject::yChanged() + + This signal gets emitted whenever the y position of the item changes. + + \sa pos() +*/ + +/*! + \property QGraphicsObject::z + \brief the z value of the item + + Describes the items z value. + + \sa QGraphicsItem::setZValue(), zValue(), zChanged() +*/ + +/*! + \fn QGraphicsObject::zChanged() + + This signal gets emitted whenever the z value of the item changes. + + \sa pos() +*/ + +/*! + \property QGraphicsObject::rotation + This property holds the rotation of the item in degrees. + + This specifies how many degrees to rotate the item around its transformOrigin. + The default rotation is 0 degrees (i.e. not rotated at all). +*/ + +/*! + \fn QGraphicsObject::rotationChanged() + + This signal gets emitted whenever the roation of the item changes. +*/ + +/*! + \property QGraphicsObject::scale + This property holds the scale of the item. + + A scale of less than 1 means the item will be displayed smaller than + normal, and a scale of greater than 1 means the item will be + displayed larger than normal. A negative scale means the item will + be mirrored. + + By default, items are displayed at a scale of 1 (i.e. at their + normal size). + + Scaling is from the item's transformOrigin. +*/ + +/*! + \fn void QGraphicsObject::scaleChanged() + + This signal is emitted when the scale of the item changes. +*/ + + +/*! + \property QGraphicsObject::enabled + \brief whether the item is enabled or not + + This property is declared in QGraphicsItem. + + By default, this property is true. + + \sa QGraphicsItem::isEnabled(), QGraphicsItem::setEnabled() + \sa QGraphicsObject::enabledChanged() +*/ + +/*! + \fn void QGraphicsObject::enabledChanged() + + This signal gets emitted whenever the item get's enabled or disabled. + + \sa isEnabled() +*/ + +/*! + \property QGraphicsObject::visible + \brief whether the item is visible or not + + This property is declared in QGraphicsItem. + + By default, this property is true. + + \sa QGraphicsItem::isVisible(), QGraphicsItem::setVisible(), visibleChanged() +*/ + +/*! + \fn QGraphicsObject::visibleChanged() + + This signal gets emitted whenever the visibility of the item changes + + \sa visible +*/ + +/*! + \fn const QObjectList &QGraphicsObject::children() const + \internal + + This function returns the same value as QObject::children(). It's + provided to differentiate between the obsolete member + QGraphicsItem::children() and QObject::children(). QGraphicsItem now + provides childItems() instead. +*/ + +/*! + \property QGraphicsObject::transformOriginPoint + \brief the transformation origin + + This property sets a specific point in the items coordiante system as the + origin for scale and rotation. + + \sa scale, rotation, QGraphicsItem::transformOriginPoint() +*/ + +/*! + \fn void QGraphicsObject::widthChanged() + \internal +*/ + +/*! + \fn void QGraphicsObject::heightChanged() + \internal +*/ + +/*! + + \fn QGraphicsObject::childrenChanged() + + This signal gets emitted whenever the children list changes + \internal +*/ + +/*! + \property QGraphicsObject::effect + \since 4.7 + \brief the effect attached to this item + + \sa QGraphicsItem::setGraphicsEffect(), QGraphicsItem::graphicsEffect() +*/ + +/*! + \class QAbstractGraphicsShapeItem + \brief The QAbstractGraphicsShapeItem class provides a common base for + all path items. + \since 4.2 + \ingroup graphicsview-api + + This class does not fully implement an item by itself; in particular, it + does not implement boundingRect() and paint(), which are inherited by + QGraphicsItem. + + You can subclass this item to provide a simple base implementation of + accessors for the item's pen and brush. + + \sa QGraphicsRectItem, QGraphicsEllipseItem, QGraphicsPathItem, + QGraphicsPolygonItem, QGraphicsTextItem, QGraphicsLineItem, + QGraphicsPixmapItem, {Graphics View Framework} +*/ + +class QAbstractGraphicsShapeItemPrivate : public QGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QAbstractGraphicsShapeItem) +public: + + QBrush brush; + QPen pen; + + // Cached bounding rectangle + mutable QRectF boundingRect; +}; + +/*! + Constructs a QAbstractGraphicsShapeItem. \a parent is passed to + QGraphicsItem's constructor. +*/ +QAbstractGraphicsShapeItem::QAbstractGraphicsShapeItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QAbstractGraphicsShapeItemPrivate, parent, scene) +{ +} + +/*! + \internal +*/ +QAbstractGraphicsShapeItem::QAbstractGraphicsShapeItem(QAbstractGraphicsShapeItemPrivate &dd, + QGraphicsItem *parent, + QGraphicsScene *scene) + : QGraphicsItem(dd, parent, scene) +{ +} + +/*! + Destroys a QAbstractGraphicsShapeItem. +*/ +QAbstractGraphicsShapeItem::~QAbstractGraphicsShapeItem() +{ +} + +/*! + Returns the item's pen. If no pen has been set, this function returns + QPen(), a default black solid line pen with 0 width. +*/ +QPen QAbstractGraphicsShapeItem::pen() const +{ + Q_D(const QAbstractGraphicsShapeItem); + return d->pen; +} + +/*! + Sets the pen for this item to \a pen. + + The pen is used to draw the item's outline. + + \sa pen() +*/ +void QAbstractGraphicsShapeItem::setPen(const QPen &pen) +{ + Q_D(QAbstractGraphicsShapeItem); + if (d->pen == pen) + return; + prepareGeometryChange(); + d->pen = pen; + d->boundingRect = QRectF(); + update(); +} + +/*! + Returns the item's brush, or an empty brush if no brush has been set. + + \sa setBrush() +*/ +QBrush QAbstractGraphicsShapeItem::brush() const +{ + Q_D(const QAbstractGraphicsShapeItem); + return d->brush; +} + +/*! + Sets the item's brush to \a brush. + + The item's brush is used to fill the item. + + If you use a brush with a QGradient, the gradient + is relative to the item's coordinate system. + + \sa brush() +*/ +void QAbstractGraphicsShapeItem::setBrush(const QBrush &brush) +{ + Q_D(QAbstractGraphicsShapeItem); + if (d->brush == brush) + return; + d->brush = brush; + update(); +} + +/*! + \reimp +*/ +bool QAbstractGraphicsShapeItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QGraphicsItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QAbstractGraphicsShapeItem::opaqueArea() const +{ + Q_D(const QAbstractGraphicsShapeItem); + if (d->brush.isOpaque()) + return isClipped() ? clipPath() : shape(); + return QGraphicsItem::opaqueArea(); +} + +/*! + \class QGraphicsPathItem + \brief The QGraphicsPathItem class provides a path item that you + can add to a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + To set the item's path, pass a QPainterPath to QGraphicsPathItem's + constructor, or call the setPath() function. The path() function + returns the current path. + + \image graphicsview-pathitem.png + + QGraphicsPathItem uses the path to provide a reasonable + implementation of boundingRect(), shape(), and contains(). The + paint() function draws the path using the item's associated pen + and brush, which you can set by calling the setPen() and + setBrush() functions. + + \sa QGraphicsRectItem, QGraphicsEllipseItem, QGraphicsPolygonItem, + QGraphicsTextItem, QGraphicsLineItem, QGraphicsPixmapItem, {Graphics + View Framework} +*/ + +class QGraphicsPathItemPrivate : public QAbstractGraphicsShapeItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsPathItem) +public: + QPainterPath path; +}; + +/*! + Constructs a QGraphicsPath item using \a path as the default path. \a + parent is passed to QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsPathItem::QGraphicsPathItem(const QPainterPath &path, + QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsPathItemPrivate, parent, scene) +{ + if (!path.isEmpty()) + setPath(path); +} + +/*! + Constructs a QGraphicsPath. \a parent is passed to + QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsPathItem::QGraphicsPathItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsPathItemPrivate, parent, scene) +{ +} + +/*! + Destroys the QGraphicsPathItem. +*/ +QGraphicsPathItem::~QGraphicsPathItem() +{ +} + +/*! + Returns the item's path as a QPainterPath. If no item has been set, an + empty QPainterPath is returned. + + \sa setPath() +*/ +QPainterPath QGraphicsPathItem::path() const +{ + Q_D(const QGraphicsPathItem); + return d->path; +} + +/*! + Sets the item's path to be the given \a path. + + \sa path() +*/ +void QGraphicsPathItem::setPath(const QPainterPath &path) +{ + Q_D(QGraphicsPathItem); + if (d->path == path) + return; + prepareGeometryChange(); + d->path = path; + d->boundingRect = QRectF(); + update(); +} + +/*! + \reimp +*/ +QRectF QGraphicsPathItem::boundingRect() const +{ + Q_D(const QGraphicsPathItem); + if (d->boundingRect.isNull()) { + qreal pw = pen().widthF(); + if (pw == 0.0) + d->boundingRect = d->path.controlPointRect(); + else { + d->boundingRect = shape().controlPointRect(); + } + } + return d->boundingRect; +} + +/*! + \reimp +*/ +QPainterPath QGraphicsPathItem::shape() const +{ + Q_D(const QGraphicsPathItem); + return qt_graphicsItem_shapeFromPath(d->path, d->pen); +} + +/*! + \reimp +*/ +bool QGraphicsPathItem::contains(const QPointF &point) const +{ + return QAbstractGraphicsShapeItem::contains(point); +} + +/*! + \reimp +*/ +void QGraphicsPathItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_D(QGraphicsPathItem); + Q_UNUSED(widget); + painter->setPen(d->pen); + painter->setBrush(d->brush); + painter->drawPath(d->path); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsPathItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QAbstractGraphicsShapeItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsPathItem::opaqueArea() const +{ + return QAbstractGraphicsShapeItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsPathItem::type() const +{ + return Type; +} + +/*! + \internal +*/ +bool QGraphicsPathItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsPathItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsPathItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsRectItem + \brief The QGraphicsRectItem class provides a rectangle item that you + can add to a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + To set the item's rectangle, pass a QRectF to QGraphicsRectItem's + constructor, or call the setRect() function. The rect() function + returns the current rectangle. + + \image graphicsview-rectitem.png + + QGraphicsRectItem uses the rectangle and the pen width to provide + a reasonable implementation of boundingRect(), shape(), and + contains(). The paint() function draws the rectangle using the + item's associated pen and brush, which you can set by calling the + setPen() and setBrush() functions. + + \note The rendering of invalid rectangles, such as those with negative + widths or heights, is undefined. If you cannot be sure that you are + using valid rectangles (for example, if you are creating + rectangles using data from an unreliable source) then you should + use QRectF::normalized() to create normalized rectangles, and use + those instead. + + \sa QGraphicsPathItem, QGraphicsEllipseItem, QGraphicsPolygonItem, + QGraphicsTextItem, QGraphicsLineItem, QGraphicsPixmapItem, {Graphics + View Framework} +*/ + +class QGraphicsRectItemPrivate : public QAbstractGraphicsShapeItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsRectItem) +public: + QRectF rect; +}; + +/*! + Constructs a QGraphicsRectItem, using \a rect as the default rectangle. + \a parent is passed to QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsRectItem::QGraphicsRectItem(const QRectF &rect, QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsRectItemPrivate, parent, scene) +{ + setRect(rect); +} + +/*! + \fn QGraphicsRectItem::QGraphicsRectItem(qreal x, qreal y, qreal width, qreal height, + QGraphicsItem *parent) + + Constructs a QGraphicsRectItem with a default rectangle defined + by (\a x, \a y) and the given \a width and \a height. + + \a parent is passed to QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsRectItem::QGraphicsRectItem(qreal x, qreal y, qreal w, qreal h, + QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsRectItemPrivate, parent, scene) +{ + setRect(QRectF(x, y, w, h)); +} + +/*! + Constructs a QGraphicsRectItem. \a parent is passed to + QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsRectItem::QGraphicsRectItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsRectItemPrivate, parent, scene) +{ +} + +/*! + Destroys the QGraphicsRectItem. +*/ +QGraphicsRectItem::~QGraphicsRectItem() +{ +} + +/*! + Returns the item's rectangle. + + \sa setRect() +*/ +QRectF QGraphicsRectItem::rect() const +{ + Q_D(const QGraphicsRectItem); + return d->rect; +} + +/*! + \fn void QGraphicsRectItem::setRect(const QRectF &rectangle) + + Sets the item's rectangle to be the given \a rectangle. + + \sa rect() +*/ +void QGraphicsRectItem::setRect(const QRectF &rect) +{ + Q_D(QGraphicsRectItem); + if (d->rect == rect) + return; + prepareGeometryChange(); + d->rect = rect; + d->boundingRect = QRectF(); + update(); +} + +/*! + \fn void QGraphicsRectItem::setRect(qreal x, qreal y, qreal width, qreal height) + \fn void QGraphicsEllipseItem::setRect(qreal x, qreal y, qreal width, qreal height) + + Sets the item's rectangle to the rectangle defined by (\a x, \a y) + and the given \a width and \a height. + + This convenience function is equivalent to calling \c + {setRect(QRectF(x, y, width, height))} + + \sa rect() +*/ + +/*! + \reimp +*/ +QRectF QGraphicsRectItem::boundingRect() const +{ + Q_D(const QGraphicsRectItem); + if (d->boundingRect.isNull()) { + qreal halfpw = pen().widthF() / 2; + d->boundingRect = d->rect; + if (halfpw > 0.0) + d->boundingRect.adjust(-halfpw, -halfpw, halfpw, halfpw); + } + return d->boundingRect; +} + +/*! + \reimp +*/ +QPainterPath QGraphicsRectItem::shape() const +{ + Q_D(const QGraphicsRectItem); + QPainterPath path; + path.addRect(d->rect); + return qt_graphicsItem_shapeFromPath(path, d->pen); +} + +/*! + \reimp +*/ +bool QGraphicsRectItem::contains(const QPointF &point) const +{ + return QAbstractGraphicsShapeItem::contains(point); +} + +/*! + \reimp +*/ +void QGraphicsRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_D(QGraphicsRectItem); + Q_UNUSED(widget); + painter->setPen(d->pen); + painter->setBrush(d->brush); + painter->drawRect(d->rect); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsRectItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QAbstractGraphicsShapeItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsRectItem::opaqueArea() const +{ + return QAbstractGraphicsShapeItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsRectItem::type() const +{ + return Type; +} + +/*! + \internal +*/ +bool QGraphicsRectItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsRectItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsRectItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsEllipseItem + \brief The QGraphicsEllipseItem class provides an ellipse item that you + can add to a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + QGraphicsEllipseItem respresents an ellipse with a fill and an outline, + and you can also use it for ellipse segments (see startAngle(), + spanAngle()). + + \table + \row + \o \inlineimage graphicsview-ellipseitem.png + \o \inlineimage graphicsview-ellipseitem-pie.png + \endtable + + To set the item's ellipse, pass a QRectF to QGraphicsEllipseItem's + constructor, or call setRect(). The rect() function returns the + current ellipse geometry. + + QGraphicsEllipseItem uses the rect and the pen width to provide a + reasonable implementation of boundingRect(), shape(), and contains(). The + paint() function draws the ellipse using the item's associated pen and + brush, which you can set by calling setPen() and setBrush(). + + \sa QGraphicsPathItem, QGraphicsRectItem, QGraphicsPolygonItem, + QGraphicsTextItem, QGraphicsLineItem, QGraphicsPixmapItem, {Graphics + View Framework} +*/ + +class QGraphicsEllipseItemPrivate : public QAbstractGraphicsShapeItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsEllipseItem) +public: + inline QGraphicsEllipseItemPrivate() + : startAngle(0), spanAngle(360 * 16) + { } + + QRectF rect; + int startAngle; + int spanAngle; +}; + +/*! + Constructs a QGraphicsEllipseItem using \a rect as the default rectangle. + \a parent is passed to QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsEllipseItem::QGraphicsEllipseItem(const QRectF &rect, QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsEllipseItemPrivate, parent, scene) +{ + setRect(rect); +} + +/*! + \fn QGraphicsEllipseItem::QGraphicsEllipseItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent) + \since 4.3 + + Constructs a QGraphicsEllipseItem using the rectangle defined by (\a x, \a + y) and the given \a width and \a height, as the default rectangle. \a + parent is passed to QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsEllipseItem::QGraphicsEllipseItem(qreal x, qreal y, qreal w, qreal h, + QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsEllipseItemPrivate, parent, scene) +{ + setRect(x,y,w,h); +} + + + +/*! + Constructs a QGraphicsEllipseItem. \a parent is passed to + QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsEllipseItem::QGraphicsEllipseItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsEllipseItemPrivate, parent, scene) +{ +} + +/*! + Destroys the QGraphicsEllipseItem. +*/ +QGraphicsEllipseItem::~QGraphicsEllipseItem() +{ +} + +/*! + Returns the item's ellipse geometry as a QRectF. + + \sa setRect(), QPainter::drawEllipse() +*/ +QRectF QGraphicsEllipseItem::rect() const +{ + Q_D(const QGraphicsEllipseItem); + return d->rect; +} + +/*! + Sets the item's ellipse geometry to \a rect. The rectangle's left edge + defines the left edge of the ellipse, and the rectangle's top edge + describes the top of the ellipse. The height and width of the rectangle + describe the height and width of the ellipse. + + \sa rect(), QPainter::drawEllipse() +*/ +void QGraphicsEllipseItem::setRect(const QRectF &rect) +{ + Q_D(QGraphicsEllipseItem); + if (d->rect == rect) + return; + prepareGeometryChange(); + d->rect = rect; + d->boundingRect = QRectF(); + update(); +} + +/*! + Returns the start angle for an ellipse segment in 16ths of a degree. This + angle is used together with spanAngle() for representing an ellipse + segment (a pie). By default, the start angle is 0. + + \sa setStartAngle(), spanAngle() +*/ +int QGraphicsEllipseItem::startAngle() const +{ + Q_D(const QGraphicsEllipseItem); + return d->startAngle; +} + +/*! + Sets the start angle for an ellipse segment to \a angle, which is in 16ths + of a degree. This angle is used together with spanAngle() for representing + an ellipse segment (a pie). By default, the start angle is 0. + + \sa startAngle(), setSpanAngle(), QPainter::drawPie() +*/ +void QGraphicsEllipseItem::setStartAngle(int angle) +{ + Q_D(QGraphicsEllipseItem); + if (angle != d->startAngle) { + prepareGeometryChange(); + d->boundingRect = QRectF(); + d->startAngle = angle; + update(); + } +} + +/*! + Returns the span angle of an ellipse segment in 16ths of a degree. This + angle is used together with startAngle() for representing an ellipse + segment (a pie). By default, this function returns 5760 (360 * 16, a full + ellipse). + + \sa setSpanAngle(), startAngle() +*/ +int QGraphicsEllipseItem::spanAngle() const +{ + Q_D(const QGraphicsEllipseItem); + return d->spanAngle; +} + +/*! + Sets the span angle for an ellipse segment to \a angle, which is in 16ths + of a degree. This angle is used together with startAngle() to represent an + ellipse segment (a pie). By default, the span angle is 5760 (360 * 16, a + full ellipse). + + \sa spanAngle(), setStartAngle(), QPainter::drawPie() +*/ +void QGraphicsEllipseItem::setSpanAngle(int angle) +{ + Q_D(QGraphicsEllipseItem); + if (angle != d->spanAngle) { + prepareGeometryChange(); + d->boundingRect = QRectF(); + d->spanAngle = angle; + update(); + } +} + +/*! + \reimp +*/ +QRectF QGraphicsEllipseItem::boundingRect() const +{ + Q_D(const QGraphicsEllipseItem); + if (d->boundingRect.isNull()) { + qreal pw = pen().widthF(); + if (pw == 0.0 && d->spanAngle == 360 * 16) + d->boundingRect = d->rect; + else + d->boundingRect = shape().controlPointRect(); + } + return d->boundingRect; +} + +/*! + \reimp +*/ +QPainterPath QGraphicsEllipseItem::shape() const +{ + Q_D(const QGraphicsEllipseItem); + QPainterPath path; + if (d->rect.isNull()) + return path; + if (d->spanAngle != 360 * 16) { + path.moveTo(d->rect.center()); + path.arcTo(d->rect, d->startAngle / 16.0, d->spanAngle / 16.0); + } else { + path.addEllipse(d->rect); + } + + return qt_graphicsItem_shapeFromPath(path, d->pen); +} + +/*! + \reimp +*/ +bool QGraphicsEllipseItem::contains(const QPointF &point) const +{ + return QAbstractGraphicsShapeItem::contains(point); +} + +/*! + \reimp +*/ +void QGraphicsEllipseItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_D(QGraphicsEllipseItem); + Q_UNUSED(widget); + painter->setPen(d->pen); + painter->setBrush(d->brush); + if ((d->spanAngle != 0) && (qAbs(d->spanAngle) % (360 * 16) == 0)) + painter->drawEllipse(d->rect); + else + painter->drawPie(d->rect, d->startAngle, d->spanAngle); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsEllipseItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QAbstractGraphicsShapeItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsEllipseItem::opaqueArea() const +{ + return QAbstractGraphicsShapeItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsEllipseItem::type() const +{ + return Type; +} + + +/*! + \internal +*/ +bool QGraphicsEllipseItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsEllipseItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsEllipseItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsPolygonItem + \brief The QGraphicsPolygonItem class provides a polygon item that you + can add to a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + To set the item's polygon, pass a QPolygonF to + QGraphicsPolygonItem's constructor, or call the setPolygon() + function. The polygon() function returns the current polygon. + + \image graphicsview-polygonitem.png + + QGraphicsPolygonItem uses the polygon and the pen width to provide + a reasonable implementation of boundingRect(), shape(), and + contains(). The paint() function draws the polygon using the + item's associated pen and brush, which you can set by calling the + setPen() and setBrush() functions. + + \sa QGraphicsPathItem, QGraphicsRectItem, QGraphicsEllipseItem, + QGraphicsTextItem, QGraphicsLineItem, QGraphicsPixmapItem, {Graphics + View Framework} +*/ + +class QGraphicsPolygonItemPrivate : public QAbstractGraphicsShapeItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsPolygonItem) +public: + inline QGraphicsPolygonItemPrivate() + : fillRule(Qt::OddEvenFill) + { } + + QPolygonF polygon; + Qt::FillRule fillRule; +}; + +/*! + Constructs a QGraphicsPolygonItem with \a polygon as the default + polygon. \a parent is passed to QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsPolygonItem::QGraphicsPolygonItem(const QPolygonF &polygon, + QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsPolygonItemPrivate, parent, scene) +{ + setPolygon(polygon); +} + +/*! + Constructs a QGraphicsPolygonItem. \a parent is passed to + QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsPolygonItem::QGraphicsPolygonItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsPolygonItemPrivate, parent, scene) +{ +} + +/*! + Destroys the QGraphicsPolygonItem. +*/ +QGraphicsPolygonItem::~QGraphicsPolygonItem() +{ +} + +/*! + Returns the item's polygon, or an empty polygon if no polygon + has been set. + + \sa setPolygon() +*/ +QPolygonF QGraphicsPolygonItem::polygon() const +{ + Q_D(const QGraphicsPolygonItem); + return d->polygon; +} + +/*! + Sets the item's polygon to be the given \a polygon. + + \sa polygon() +*/ +void QGraphicsPolygonItem::setPolygon(const QPolygonF &polygon) +{ + Q_D(QGraphicsPolygonItem); + if (d->polygon == polygon) + return; + prepareGeometryChange(); + d->polygon = polygon; + d->boundingRect = QRectF(); + update(); +} + +/*! + Returns the fill rule of the polygon. The default fill rule is + Qt::OddEvenFill. + + \sa setFillRule(), QPainterPath::fillRule(), QPainter::drawPolygon() +*/ +Qt::FillRule QGraphicsPolygonItem::fillRule() const +{ + Q_D(const QGraphicsPolygonItem); + return d->fillRule; +} + +/*! + Sets the fill rule of the polygon to \a rule. The default fill rule is + Qt::OddEvenFill. + + \sa fillRule(), QPainterPath::fillRule(), QPainter::drawPolygon() +*/ +void QGraphicsPolygonItem::setFillRule(Qt::FillRule rule) +{ + Q_D(QGraphicsPolygonItem); + if (rule != d->fillRule) { + d->fillRule = rule; + update(); + } +} + +/*! + \reimp +*/ +QRectF QGraphicsPolygonItem::boundingRect() const +{ + Q_D(const QGraphicsPolygonItem); + if (d->boundingRect.isNull()) { + qreal pw = pen().widthF(); + if (pw == 0.0) + d->boundingRect = d->polygon.boundingRect(); + else + d->boundingRect = shape().controlPointRect(); + } + return d->boundingRect; +} + +/*! + \reimp +*/ +QPainterPath QGraphicsPolygonItem::shape() const +{ + Q_D(const QGraphicsPolygonItem); + QPainterPath path; + path.addPolygon(d->polygon); + return qt_graphicsItem_shapeFromPath(path, d->pen); +} + +/*! + \reimp +*/ +bool QGraphicsPolygonItem::contains(const QPointF &point) const +{ + return QAbstractGraphicsShapeItem::contains(point); +} + +/*! + \reimp +*/ +void QGraphicsPolygonItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_D(QGraphicsPolygonItem); + Q_UNUSED(widget); + painter->setPen(d->pen); + painter->setBrush(d->brush); + painter->drawPolygon(d->polygon, d->fillRule); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsPolygonItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QAbstractGraphicsShapeItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsPolygonItem::opaqueArea() const +{ + return QAbstractGraphicsShapeItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsPolygonItem::type() const +{ + return Type; +} + +/*! + \internal +*/ +bool QGraphicsPolygonItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsPolygonItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsPolygonItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsLineItem + \brief The QGraphicsLineItem class provides a line item that you can add to a + QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + To set the item's line, pass a QLineF to QGraphicsLineItem's + constructor, or call the setLine() function. The line() function + returns the current line. By default the line is black with a + width of 0, but you can change this by calling setPen(). + + \img graphicsview-lineitem.png + + QGraphicsLineItem uses the line and the pen width to provide a reasonable + implementation of boundingRect(), shape(), and contains(). The paint() + function draws the line using the item's associated pen. + + \sa QGraphicsPathItem, QGraphicsRectItem, QGraphicsEllipseItem, + QGraphicsTextItem, QGraphicsPolygonItem, QGraphicsPixmapItem, + {Graphics View Framework} +*/ + +class QGraphicsLineItemPrivate : public QGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsLineItem) +public: + QLineF line; + QPen pen; +}; + +/*! + Constructs a QGraphicsLineItem, using \a line as the default line. \a + parent is passed to QGraphicsItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsLineItem::QGraphicsLineItem(const QLineF &line, QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QGraphicsLineItemPrivate, parent, scene) +{ + setLine(line); +} + +/*! + Constructs a QGraphicsLineItem, using the line between (\a x1, \a y1) and + (\a x2, \a y2) as the default line. \a parent is passed to + QGraphicsItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsLineItem::QGraphicsLineItem(qreal x1, qreal y1, qreal x2, qreal y2, QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QGraphicsLineItemPrivate, parent, scene) +{ + setLine(x1, y1, x2, y2); +} + + + +/*! + Constructs a QGraphicsLineItem. \a parent is passed to QGraphicsItem's + constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsLineItem::QGraphicsLineItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QGraphicsLineItemPrivate, parent, scene) +{ +} + +/*! + Destroys the QGraphicsLineItem. +*/ +QGraphicsLineItem::~QGraphicsLineItem() +{ +} + +/*! + Returns the item's pen, or a black solid 0-width pen if no pen has + been set. + + \sa setPen() +*/ +QPen QGraphicsLineItem::pen() const +{ + Q_D(const QGraphicsLineItem); + return d->pen; +} + +/*! + Sets the item's pen to \a pen. If no pen is set, the line will be painted + using a black solid 0-width pen. + + \sa pen() +*/ +void QGraphicsLineItem::setPen(const QPen &pen) +{ + Q_D(QGraphicsLineItem); + if (d->pen == pen) + return; + prepareGeometryChange(); + d->pen = pen; + update(); +} + +/*! + Returns the item's line, or a null line if no line has been set. + + \sa setLine() +*/ +QLineF QGraphicsLineItem::line() const +{ + Q_D(const QGraphicsLineItem); + return d->line; +} + +/*! + Sets the item's line to be the given \a line. + + \sa line() +*/ +void QGraphicsLineItem::setLine(const QLineF &line) +{ + Q_D(QGraphicsLineItem); + if (d->line == line) + return; + prepareGeometryChange(); + d->line = line; + update(); +} + +/*! + \fn void QGraphicsLineItem::setLine(qreal x1, qreal y1, qreal x2, qreal y2) + \overload + + Sets the item's line to be the line between (\a x1, \a y1) and (\a + x2, \a y2). + + This is the same as calling \c {setLine(QLineF(x1, y1, x2, y2))}. +*/ + +/*! + \reimp +*/ +QRectF QGraphicsLineItem::boundingRect() const +{ + Q_D(const QGraphicsLineItem); + if (d->pen.widthF() == 0.0) { + const qreal x1 = d->line.p1().x(); + const qreal x2 = d->line.p2().x(); + const qreal y1 = d->line.p1().y(); + const qreal y2 = d->line.p2().y(); + qreal lx = qMin(x1, x2); + qreal rx = qMax(x1, x2); + qreal ty = qMin(y1, y2); + qreal by = qMax(y1, y2); + return QRectF(lx, ty, rx - lx, by - ty); + } + return shape().controlPointRect(); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsLineItem::shape() const +{ + Q_D(const QGraphicsLineItem); + QPainterPath path; + if (d->line == QLineF()) + return path; + + path.moveTo(d->line.p1()); + path.lineTo(d->line.p2()); + return qt_graphicsItem_shapeFromPath(path, d->pen); +} + +/*! + \reimp +*/ +bool QGraphicsLineItem::contains(const QPointF &point) const +{ + return QGraphicsItem::contains(point); +} + +/*! + \reimp +*/ +void QGraphicsLineItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_D(QGraphicsLineItem); + Q_UNUSED(widget); + painter->setPen(d->pen); + painter->drawLine(d->line); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsLineItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QGraphicsItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsLineItem::opaqueArea() const +{ + return QGraphicsItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsLineItem::type() const +{ + return Type; +} + +/*! + \internal +*/ +bool QGraphicsLineItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsLineItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsLineItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsPixmapItem + \brief The QGraphicsPixmapItem class provides a pixmap item that you can add to + a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + To set the item's pixmap, pass a QPixmap to QGraphicsPixmapItem's + constructor, or call the setPixmap() function. The pixmap() + function returns the current pixmap. + + QGraphicsPixmapItem uses pixmap's optional alpha mask to provide a + reasonable implementation of boundingRect(), shape(), and contains(). + + \image graphicsview-pixmapitem.png + + The pixmap is drawn at the item's (0, 0) coordinate, as returned by + offset(). You can change the drawing offset by calling setOffset(). + + You can set the pixmap's transformation mode by calling + setTransformationMode(). By default, Qt::FastTransformation is used, which + provides fast, non-smooth scaling. Qt::SmoothTransformation enables + QPainter::SmoothPixmapTransform on the painter, and the quality depends on + the platform and viewport. The result is usually not as good as calling + QPixmap::scale() directly. Call transformationMode() to get the current + transformation mode for the item. + + \sa QGraphicsPathItem, QGraphicsRectItem, QGraphicsEllipseItem, + QGraphicsTextItem, QGraphicsPolygonItem, QGraphicsLineItem, + {Graphics View Framework} +*/ + +/*! + \enum QGraphicsPixmapItem::ShapeMode + + This enum describes how QGraphicsPixmapItem calculates its shape and + opaque area. + + The default value is MaskShape. + + \value MaskShape The shape is determined by calling QPixmap::mask(). + This shape includes only the opaque pixels of the pixmap. + Because the shape is more complex, however, it can be slower than the other modes, + and uses more memory. + + \value BoundingRectShape The shape is determined by tracing the outline of + the pixmap. This is the fastest shape mode, but it does not take into account + any transparent areas on the pixmap. + + \value HeuristicMaskShape The shape is determine by calling + QPixmap::createHeuristicMask(). The performance and memory consumption + is similar to MaskShape. +*/ +extern QPainterPath qt_regionToPath(const QRegion ®ion); + +class QGraphicsPixmapItemPrivate : public QGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsPixmapItem) +public: + QGraphicsPixmapItemPrivate() + : transformationMode(Qt::FastTransformation), + shapeMode(QGraphicsPixmapItem::MaskShape), + hasShape(false) + {} + + QPixmap pixmap; + Qt::TransformationMode transformationMode; + QPointF offset; + QGraphicsPixmapItem::ShapeMode shapeMode; + QPainterPath shape; + bool hasShape; + + void updateShape() + { + shape = QPainterPath(); + switch (shapeMode) { + case QGraphicsPixmapItem::MaskShape: { + QBitmap mask = pixmap.mask(); + if (!mask.isNull()) { + shape = qt_regionToPath(QRegion(mask).translated(offset.toPoint())); + break; + } + // FALL THROUGH + } + case QGraphicsPixmapItem::BoundingRectShape: + shape.addRect(QRectF(offset.x(), offset.y(), pixmap.width(), pixmap.height())); + break; + case QGraphicsPixmapItem::HeuristicMaskShape: +#ifndef QT_NO_IMAGE_HEURISTIC_MASK + shape = qt_regionToPath(QRegion(pixmap.createHeuristicMask()).translated(offset.toPoint())); +#else + shape.addRect(QRectF(offset.x(), offset.y(), pixmap.width(), pixmap.height())); +#endif + break; + } + } +}; + +/*! + Constructs a QGraphicsPixmapItem, using \a pixmap as the default pixmap. + \a parent is passed to QGraphicsItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsPixmapItem::QGraphicsPixmapItem(const QPixmap &pixmap, + QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QGraphicsPixmapItemPrivate, parent, scene) +{ + setPixmap(pixmap); +} + +/*! + Constructs a QGraphicsPixmapItem. \a parent is passed to QGraphicsItem's + constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsPixmapItem::QGraphicsPixmapItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QGraphicsPixmapItemPrivate, parent, scene) +{ +} + +/*! + Destroys the QGraphicsPixmapItem. +*/ +QGraphicsPixmapItem::~QGraphicsPixmapItem() +{ +} + +/*! + Sets the item's pixmap to \a pixmap. + + \sa pixmap() +*/ +void QGraphicsPixmapItem::setPixmap(const QPixmap &pixmap) +{ + Q_D(QGraphicsPixmapItem); + prepareGeometryChange(); + d->pixmap = pixmap; + d->hasShape = false; + update(); +} + +/*! + Returns the item's pixmap, or an invalid QPixmap if no pixmap has been + set. + + \sa setPixmap() +*/ +QPixmap QGraphicsPixmapItem::pixmap() const +{ + Q_D(const QGraphicsPixmapItem); + return d->pixmap; +} + +/*! + Returns the transformation mode of the pixmap. The default mode is + Qt::FastTransformation, which provides quick transformation with no + smoothing. + + \sa setTransformationMode() +*/ +Qt::TransformationMode QGraphicsPixmapItem::transformationMode() const +{ + Q_D(const QGraphicsPixmapItem); + return d->transformationMode; +} + +/*! + Sets the pixmap item's transformation mode to \a mode, and toggles an + update of the item. The default mode is Qt::FastTransformation, which + provides quick transformation with no smoothing. + + Qt::SmoothTransformation enables QPainter::SmoothPixmapTransform on the + painter, and the quality depends on the platform and viewport. The result + is usually not as good as calling QPixmap::scale() directly. + + \sa transformationMode() +*/ +void QGraphicsPixmapItem::setTransformationMode(Qt::TransformationMode mode) +{ + Q_D(QGraphicsPixmapItem); + if (mode != d->transformationMode) { + d->transformationMode = mode; + update(); + } +} + +/*! + Returns the pixmap item's \e offset, which defines the point of the + top-left corner of the pixmap, in local coordinates. + + \sa setOffset() +*/ +QPointF QGraphicsPixmapItem::offset() const +{ + Q_D(const QGraphicsPixmapItem); + return d->offset; +} + +/*! + Sets the pixmap item's offset to \a offset. QGraphicsPixmapItem will draw + its pixmap using \a offset for its top-left corner. + + \sa offset() +*/ +void QGraphicsPixmapItem::setOffset(const QPointF &offset) +{ + Q_D(QGraphicsPixmapItem); + if (d->offset == offset) + return; + prepareGeometryChange(); + d->offset = offset; + d->hasShape = false; + update(); +} + +/*! + \fn void QGraphicsPixmapItem::setOffset(qreal x, qreal y) + \since 4.3 + + This convenience function is equivalent to calling setOffset(QPointF(\a x, \a y)). +*/ + +/*! + \reimp +*/ +QRectF QGraphicsPixmapItem::boundingRect() const +{ + Q_D(const QGraphicsPixmapItem); + if (d->pixmap.isNull()) + return QRectF(); + if (d->flags & ItemIsSelectable) { + qreal pw = 1.0; + return QRectF(d->offset, d->pixmap.size()).adjusted(-pw/2, -pw/2, pw/2, pw/2); + } else { + return QRectF(d->offset, d->pixmap.size()); + } +} + +/*! + \reimp +*/ +QPainterPath QGraphicsPixmapItem::shape() const +{ + Q_D(const QGraphicsPixmapItem); + if (!d->hasShape) { + QGraphicsPixmapItemPrivate *thatD = const_cast(d); + thatD->updateShape(); + thatD->hasShape = true; + } + return d_func()->shape; +} + +/*! + \reimp +*/ +bool QGraphicsPixmapItem::contains(const QPointF &point) const +{ + return QGraphicsItem::contains(point); +} + +/*! + \reimp +*/ +void QGraphicsPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_D(QGraphicsPixmapItem); + Q_UNUSED(widget); + + painter->setRenderHint(QPainter::SmoothPixmapTransform, + (d->transformationMode == Qt::SmoothTransformation)); + + painter->drawPixmap(d->offset, d->pixmap); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsPixmapItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QGraphicsItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsPixmapItem::opaqueArea() const +{ + return shape(); +} + +/*! + \reimp +*/ +int QGraphicsPixmapItem::type() const +{ + return Type; +} + +/*! + Returns the item's shape mode. The shape mode describes how + QGraphicsPixmapItem calculates its shape. The default mode is MaskShape. + + \sa setShapeMode(), ShapeMode +*/ +QGraphicsPixmapItem::ShapeMode QGraphicsPixmapItem::shapeMode() const +{ + return d_func()->shapeMode; +} + +/*! + Sets the item's shape mode to \a mode. The shape mode describes how + QGraphicsPixmapItem calculates its shape. The default mode is MaskShape. + + \sa shapeMode(), ShapeMode +*/ +void QGraphicsPixmapItem::setShapeMode(ShapeMode mode) +{ + Q_D(QGraphicsPixmapItem); + if (d->shapeMode == mode) + return; + d->shapeMode = mode; + d->hasShape = false; +} + +/*! + \internal +*/ +bool QGraphicsPixmapItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsPixmapItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsPixmapItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsTextItem + \brief The QGraphicsTextItem class provides a text item that you can add to + a QGraphicsScene to display formatted text. + \since 4.2 + \ingroup graphicsview-api + + If you only need to show plain text in an item, consider using QGraphicsSimpleTextItem + instead. + + To set the item's text, pass a QString to QGraphicsTextItem's + constructor, or call setHtml()/setPlainText(). + + QGraphicsTextItem uses the text's formatted size and the associated font + to provide a reasonable implementation of boundingRect(), shape(), + and contains(). You can set the font by calling setFont(). + + It is possible to make the item editable by setting the Qt::TextEditorInteraction flag + using setTextInteractionFlags(). + + The item's preferred text width can be set using setTextWidth() and obtained + using textWidth(). + + \note In order to align HTML text in the center, the item's text width must be set. + + \img graphicsview-textitem.png + + \note QGraphicsTextItem accepts \l{QGraphicsItem::acceptHoverEvents()}{hover events} + by default. You can change this with \l{QGraphicsItem::}{setAcceptHoverEvents()}. + + \sa QGraphicsSimpleTextItem, QGraphicsPathItem, QGraphicsRectItem, + QGraphicsEllipseItem, QGraphicsPixmapItem, QGraphicsPolygonItem, + QGraphicsLineItem, {Graphics View Framework} +*/ + +class QGraphicsTextItemPrivate +{ +public: + QGraphicsTextItemPrivate() + : control(0), pageNumber(0), useDefaultImpl(false), tabChangesFocus(false), clickCausedFocus(0) + { } + + mutable QTextControl *control; + QTextControl *textControl() const; + + inline QPointF controlOffset() const + { return QPointF(0., pageNumber * control->document()->pageSize().height()); } + inline void sendControlEvent(QEvent *e) + { if (control) control->processEvent(e, controlOffset()); } + + void _q_updateBoundingRect(const QSizeF &); + void _q_update(QRectF); + void _q_ensureVisible(QRectF); + bool _q_mouseOnEdge(QGraphicsSceneMouseEvent *); + + QRectF boundingRect; + int pageNumber; + bool useDefaultImpl; + bool tabChangesFocus; + + uint clickCausedFocus : 1; + + QGraphicsTextItem *qq; +}; + + +/*! + Constructs a QGraphicsTextItem, using \a text as the default plain + text. \a parent is passed to QGraphicsItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsTextItem::QGraphicsTextItem(const QString &text, QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsObject(*new QGraphicsItemPrivate, parent, scene), dd(new QGraphicsTextItemPrivate) +{ + dd->qq = this; + if (!text.isEmpty()) + setPlainText(text); + setAcceptDrops(true); + setAcceptHoverEvents(true); + setFlags(ItemUsesExtendedStyleOption); +} + +/*! + Constructs a QGraphicsTextItem. \a parent is passed to QGraphicsItem's + constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsTextItem::QGraphicsTextItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsObject(*new QGraphicsItemPrivate, parent, scene), dd(new QGraphicsTextItemPrivate) +{ + dd->qq = this; + setAcceptDrops(true); + setAcceptHoverEvents(true); + setFlag(ItemUsesExtendedStyleOption); +} + +/*! + Destroys the QGraphicsTextItem. +*/ +QGraphicsTextItem::~QGraphicsTextItem() +{ + delete dd; +} + +/*! + Returns the item's text converted to HTML, or an empty QString if no text has been set. + + \sa setHtml() +*/ +QString QGraphicsTextItem::toHtml() const +{ +#ifndef QT_NO_TEXTHTMLPARSER + if (dd->control) + return dd->control->toHtml(); +#endif + return QString(); +} + +/*! + Sets the item's text to \a text, assuming that text is HTML formatted. If + the item has keyboard input focus, this function will also call + ensureVisible() to ensure that the text is visible in all viewports. + + \sa toHtml(), hasFocus(), QGraphicsSimpleTextItem +*/ +void QGraphicsTextItem::setHtml(const QString &text) +{ + dd->textControl()->setHtml(text); +} + +/*! + Returns the item's text converted to plain text, or an empty QString if no text has been set. + + \sa setPlainText() +*/ +QString QGraphicsTextItem::toPlainText() const +{ + if (dd->control) + return dd->control->toPlainText(); + return QString(); +} + +/*! + Sets the item's text to \a text. If the item has keyboard input focus, + this function will also call ensureVisible() to ensure that the text is + visible in all viewports. + + \sa toHtml(), hasFocus() +*/ +void QGraphicsTextItem::setPlainText(const QString &text) +{ + dd->textControl()->setPlainText(text); +} + +/*! + Returns the item's font, which is used to render the text. + + \sa setFont() +*/ +QFont QGraphicsTextItem::font() const +{ + if (!dd->control) + return QFont(); + return dd->control->document()->defaultFont(); +} + +/*! + Sets the font used to render the text item to \a font. + + \sa font() +*/ +void QGraphicsTextItem::setFont(const QFont &font) +{ + dd->textControl()->document()->setDefaultFont(font); +} + +/*! + Sets the color for unformatted text to \a col. +*/ +void QGraphicsTextItem::setDefaultTextColor(const QColor &col) +{ + QTextControl *c = dd->textControl(); + QPalette pal = c->palette(); + QColor old = pal.color(QPalette::Text); + pal.setColor(QPalette::Text, col); + c->setPalette(pal); + if (old != col) + update(); +} + +/*! + Returns the default text color that is used to for unformatted text. +*/ +QColor QGraphicsTextItem::defaultTextColor() const +{ + return dd->textControl()->palette().color(QPalette::Text); +} + +/*! + \reimp +*/ +QRectF QGraphicsTextItem::boundingRect() const +{ + return dd->boundingRect; +} + +/*! + \reimp +*/ +QPainterPath QGraphicsTextItem::shape() const +{ + if (!dd->control) + return QPainterPath(); + QPainterPath path; + path.addRect(dd->boundingRect); + return path; +} + +/*! + \reimp +*/ +bool QGraphicsTextItem::contains(const QPointF &point) const +{ + return dd->boundingRect.contains(point); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(widget); + if (dd->control) { + painter->save(); + QRectF r = option->exposedRect; + painter->translate(-dd->controlOffset()); + r.translate(dd->controlOffset()); + + QTextDocument *doc = dd->control->document(); + QTextDocumentLayout *layout = qobject_cast(doc->documentLayout()); + + // the layout might need to expand the root frame to + // the viewport if NoWrap is set + if (layout) + layout->setViewport(dd->boundingRect); + + dd->control->drawContents(painter, r); + + if (layout) + layout->setViewport(QRect()); + + painter->restore(); + } + + if (option->state & (QStyle::State_Selected | QStyle::State_HasFocus)) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsTextItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QGraphicsItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsTextItem::opaqueArea() const +{ + return QGraphicsItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsTextItem::type() const +{ + return Type; +} + +/*! + Sets the preferred width for the item's text. If the actual text + is wider than the specified width then it will be broken into + multiple lines. + + If \a width is set to -1 then the text will not be broken into + multiple lines unless it is enforced through an explicit line + break or a new paragraph. + + The default value is -1. + + Note that QGraphicsTextItem keeps a QTextDocument internally, + which is used to calculate the text width. + + \sa textWidth(), QTextDocument::setTextWidth() +*/ +void QGraphicsTextItem::setTextWidth(qreal width) +{ + dd->textControl()->setTextWidth(width); +} + +/*! + Returns the text width. + + The width is calculated with the QTextDocument that + QGraphicsTextItem keeps internally. + + \sa setTextWidth(), QTextDocument::textWidth() +*/ +qreal QGraphicsTextItem::textWidth() const +{ + if (!dd->control) + return -1; + return dd->control->textWidth(); +} + +/*! + Adjusts the text item to a reasonable size. +*/ +void QGraphicsTextItem::adjustSize() +{ + if (dd->control) + dd->control->adjustSize(); +} + +/*! + Sets the text document \a document on the item. +*/ +void QGraphicsTextItem::setDocument(QTextDocument *document) +{ + dd->textControl()->setDocument(document); + dd->_q_updateBoundingRect(dd->control->size()); +} + +/*! + Returns the item's text document. +*/ +QTextDocument *QGraphicsTextItem::document() const +{ + return dd->textControl()->document(); +} + +/*! + \reimp +*/ +bool QGraphicsTextItem::sceneEvent(QEvent *event) +{ + QEvent::Type t = event->type(); + if (!dd->tabChangesFocus && (t == QEvent::KeyPress || t == QEvent::KeyRelease)) { + int k = ((QKeyEvent *)event)->key(); + if (k == Qt::Key_Tab || k == Qt::Key_Backtab) { + dd->sendControlEvent(event); + return true; + } + } + bool result = QGraphicsItem::sceneEvent(event); + + // Ensure input context is updated. + switch (event->type()) { + case QEvent::ContextMenu: + case QEvent::FocusIn: + case QEvent::FocusOut: + case QEvent::GraphicsSceneDragEnter: + case QEvent::GraphicsSceneDragLeave: + case QEvent::GraphicsSceneDragMove: + case QEvent::GraphicsSceneDrop: + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverLeave: + case QEvent::GraphicsSceneHoverMove: + case QEvent::GraphicsSceneMouseDoubleClick: + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::KeyPress: + case QEvent::KeyRelease: + // Reset the focus widget's input context, regardless + // of how this item gained or lost focus. + if (QWidget *fw = qApp->focusWidget()) { +#ifndef QT_NO_IM + if (QInputContext *qic = fw->inputContext()) { + if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) + qic->reset(); + else + qic->update(); + } +#endif //QT_NO_IM + } + break; + case QEvent::ShortcutOverride: + dd->sendControlEvent(event); + return true; + default: + break; + } + + return result; +} + +/*! + \reimp +*/ +void QGraphicsTextItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if ((QGraphicsItem::d_ptr->flags & (ItemIsSelectable | ItemIsMovable)) + && (event->buttons() & Qt::LeftButton) && dd->_q_mouseOnEdge(event)) { + // User left-pressed on edge of selectable/movable item, use + // base impl. + dd->useDefaultImpl = true; + } else if (event->buttons() == event->button() + && dd->control->textInteractionFlags() == Qt::NoTextInteraction) { + // User pressed first button on non-interactive item. + dd->useDefaultImpl = true; + } + if (dd->useDefaultImpl) { + QGraphicsItem::mousePressEvent(event); + if (!event->isAccepted()) + dd->useDefaultImpl = false; + return; + } + + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (dd->useDefaultImpl) { + QGraphicsItem::mouseMoveEvent(event); + return; + } + + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (dd->useDefaultImpl) { + QGraphicsItem::mouseReleaseEvent(event); + if (dd->control->textInteractionFlags() == Qt::NoTextInteraction + && !event->buttons()) { + // User released last button on non-interactive item. + dd->useDefaultImpl = false; + } else if ((event->buttons() & Qt::LeftButton) == 0) { + // User released the left button on an interactive item. + dd->useDefaultImpl = false; + } + return; + } + + QWidget *widget = event->widget(); + if (widget && (dd->control->textInteractionFlags() & Qt::TextEditable) && boundingRect().contains(event->pos())) { + qt_widget_private(widget)->handleSoftwareInputPanel(event->button(), dd->clickCausedFocus); + } + dd->clickCausedFocus = 0; + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + if (dd->useDefaultImpl) { + QGraphicsItem::mouseDoubleClickEvent(event); + return; + } + + if (!hasFocus()) { + QGraphicsItem::mouseDoubleClickEvent(event); + return; + } + + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::keyPressEvent(QKeyEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::keyReleaseEvent(QKeyEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::focusInEvent(QFocusEvent *event) +{ + dd->sendControlEvent(event); + if (event->reason() == Qt::MouseFocusReason) { + dd->clickCausedFocus = 1; + } + update(); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::focusOutEvent(QFocusEvent *event) +{ + dd->sendControlEvent(event); + update(); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::dragEnterEvent(QGraphicsSceneDragDropEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::dragMoveEvent(QGraphicsSceneDragDropEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::dropEvent(QGraphicsSceneDragDropEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::inputMethodEvent(QInputMethodEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +QVariant QGraphicsTextItem::inputMethodQuery(Qt::InputMethodQuery query) const +{ + QVariant v; + if (dd->control) + v = dd->control->inputMethodQuery(query); + if (v.type() == QVariant::RectF) + v = v.toRectF().translated(-dd->controlOffset()); + else if (v.type() == QVariant::PointF) + v = v.toPointF() - dd->controlOffset(); + else if (v.type() == QVariant::Rect) + v = v.toRect().translated(-dd->controlOffset().toPoint()); + else if (v.type() == QVariant::Point) + v = v.toPoint() - dd->controlOffset().toPoint(); + return v; +} + +/*! + \internal +*/ +bool QGraphicsTextItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsTextItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsTextItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \internal +*/ +void QGraphicsTextItemPrivate::_q_update(QRectF rect) +{ + if (rect.isValid()) { + rect.translate(-controlOffset()); + } else { + rect = boundingRect; + } + if (rect.intersects(boundingRect)) + qq->update(rect); +} + +/*! + \internal +*/ +void QGraphicsTextItemPrivate::_q_updateBoundingRect(const QSizeF &size) +{ + if (!control) return; // can't happen + const QSizeF pageSize = control->document()->pageSize(); + // paged items have a constant (page) size + if (size == boundingRect.size() || pageSize.height() != -1) + return; + qq->prepareGeometryChange(); + boundingRect.setSize(size); + qq->update(); +} + +/*! + \internal +*/ +void QGraphicsTextItemPrivate::_q_ensureVisible(QRectF rect) +{ + if (qq->hasFocus()) { + rect.translate(-controlOffset()); + qq->ensureVisible(rect, /*xmargin=*/0, /*ymargin=*/0); + } +} + +QTextControl *QGraphicsTextItemPrivate::textControl() const +{ + if (!control) { + QGraphicsTextItem *that = const_cast(qq); + control = new QTextControl(that); + control->setTextInteractionFlags(Qt::NoTextInteraction); + + QObject::connect(control, SIGNAL(updateRequest(QRectF)), + qq, SLOT(_q_update(QRectF))); + QObject::connect(control, SIGNAL(documentSizeChanged(QSizeF)), + qq, SLOT(_q_updateBoundingRect(QSizeF))); + QObject::connect(control, SIGNAL(visibilityRequest(QRectF)), + qq, SLOT(_q_ensureVisible(QRectF))); + QObject::connect(control, SIGNAL(linkActivated(QString)), + qq, SIGNAL(linkActivated(QString))); + QObject::connect(control, SIGNAL(linkHovered(QString)), + qq, SIGNAL(linkHovered(QString))); + + const QSizeF pgSize = control->document()->pageSize(); + if (pgSize.height() != -1) { + qq->prepareGeometryChange(); + that->dd->boundingRect.setSize(pgSize); + qq->update(); + } else { + that->dd->_q_updateBoundingRect(control->size()); + } + } + return control; +} + +/*! + \internal +*/ +bool QGraphicsTextItemPrivate::_q_mouseOnEdge(QGraphicsSceneMouseEvent *event) +{ + QPainterPath path; + path.addRect(qq->boundingRect()); + + QPainterPath docPath; + const QTextFrameFormat format = control->document()->rootFrame()->frameFormat(); + docPath.addRect( + qq->boundingRect().adjusted( + format.leftMargin(), + format.topMargin(), + -format.rightMargin(), + -format.bottomMargin())); + + return path.subtracted(docPath).contains(event->pos()); +} + +/*! + \fn QGraphicsTextItem::linkActivated(const QString &link) + + This signal is emitted when the user clicks on a link on a text item + that enables Qt::LinksAccessibleByMouse or Qt::LinksAccessibleByKeyboard. + \a link is the link that was clicked. + + \sa setTextInteractionFlags() +*/ + +/*! + \fn QGraphicsTextItem::linkHovered(const QString &link) + + This signal is emitted when the user hovers over a link on a text item + that enables Qt::LinksAccessibleByMouse. \a link is + the link that was hovered over. + + \sa setTextInteractionFlags() +*/ + +/*! + Sets the flags \a flags to specify how the text item should react to user + input. + + The default for a QGraphicsTextItem is Qt::NoTextInteraction. This function + also affects the ItemIsFocusable QGraphicsItem flag by setting it if \a flags + is different from Qt::NoTextInteraction and clearing it otherwise. + + By default, the text is read-only. To transform the item into an editor, + set the Qt::TextEditable flag. +*/ +void QGraphicsTextItem::setTextInteractionFlags(Qt::TextInteractionFlags flags) +{ + if (flags == Qt::NoTextInteraction) + setFlags(this->flags() & ~(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemAcceptsInputMethod)); + else + setFlags(this->flags() | QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemAcceptsInputMethod); + + dd->textControl()->setTextInteractionFlags(flags); +} + +/*! + Returns the current text interaction flags. + + \sa setTextInteractionFlags() +*/ +Qt::TextInteractionFlags QGraphicsTextItem::textInteractionFlags() const +{ + if (!dd->control) + return Qt::NoTextInteraction; + return dd->control->textInteractionFlags(); +} + +/*! + \since 4.5 + + If \a b is true, the \gui Tab key will cause the widget to change focus; + otherwise, the tab key will insert a tab into the document. + + In some occasions text edits should not allow the user to input tabulators + or change indentation using the \gui Tab key, as this breaks the focus + chain. The default is false. + + \sa tabChangesFocus(), ItemIsFocusable, textInteractionFlags() +*/ +void QGraphicsTextItem::setTabChangesFocus(bool b) +{ + dd->tabChangesFocus = b; +} + +/*! + \since 4.5 + + Returns true if the \gui Tab key will cause the widget to change focus; + otherwise, false is returned. + + By default, this behavior is disabled, and this function will return false. + + \sa setTabChangesFocus() +*/ +bool QGraphicsTextItem::tabChangesFocus() const +{ + return dd->tabChangesFocus; +} + +/*! + \property QGraphicsTextItem::openExternalLinks + + Specifies whether QGraphicsTextItem should automatically open links using + QDesktopServices::openUrl() instead of emitting the + linkActivated signal. + + The default value is false. +*/ +void QGraphicsTextItem::setOpenExternalLinks(bool open) +{ + dd->textControl()->setOpenExternalLinks(open); +} + +bool QGraphicsTextItem::openExternalLinks() const +{ + if (!dd->control) + return false; + return dd->control->openExternalLinks(); +} + +/*! + \property QGraphicsTextItem::textCursor + + This property represents the visible text cursor in an editable + text item. + + By default, if the item's text has not been set, this property + contains a null text cursor; otherwise it contains a text cursor + placed at the start of the item's document. +*/ +void QGraphicsTextItem::setTextCursor(const QTextCursor &cursor) +{ + dd->textControl()->setTextCursor(cursor); +} + +QTextCursor QGraphicsTextItem::textCursor() const +{ + if (!dd->control) + return QTextCursor(); + return dd->control->textCursor(); +} + +class QGraphicsSimpleTextItemPrivate : public QAbstractGraphicsShapeItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSimpleTextItem) +public: + inline QGraphicsSimpleTextItemPrivate() { + pen.setStyle(Qt::NoPen); + brush.setStyle(Qt::SolidPattern); + } + QString text; + QFont font; + QRectF boundingRect; + + void updateBoundingRect(); +}; + +static QRectF setupTextLayout(QTextLayout *layout) +{ + layout->setCacheEnabled(true); + layout->beginLayout(); + while (layout->createLine().isValid()) + ; + layout->endLayout(); + qreal maxWidth = 0; + qreal y = 0; + for (int i = 0; i < layout->lineCount(); ++i) { + QTextLine line = layout->lineAt(i); + maxWidth = qMax(maxWidth, line.naturalTextWidth()); + line.setPosition(QPointF(0, y)); + y += line.height(); + } + return QRectF(0, 0, maxWidth, y); +} + +void QGraphicsSimpleTextItemPrivate::updateBoundingRect() +{ + Q_Q(QGraphicsSimpleTextItem); + QRectF br; + if (text.isEmpty()) { + br = QRectF(); + } else { + QString tmp = text; + tmp.replace(QLatin1Char('\n'), QChar::LineSeparator); + QStackTextEngine engine(tmp, font); + QTextLayout layout(&engine); + br = setupTextLayout(&layout); + } + if (br != boundingRect) { + q->prepareGeometryChange(); + boundingRect = br; + q->update(); + } +} + +/*! + \class QGraphicsSimpleTextItem + \brief The QGraphicsSimpleTextItem class provides a simple text path item + that you can add to a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + To set the item's text, you can either pass a QString to + QGraphicsSimpleTextItem's constructor, or call setText() to change the + text later. To set the text fill color, call setBrush(). + + The simple text item can have both a fill and an outline; setBrush() will + set the text fill (i.e., text color), and setPen() sets the pen that will + be used to draw the text outline. (The latter can be slow, especially for + complex pens, and items with long text content.) If all you want is to + draw a simple line of text, you should call setBrush() only, and leave the + pen unset; QGraphicsSimpleTextItem's pen is by default Qt::NoPen. + + QGraphicsSimpleTextItem uses the text's formatted size and the associated + font to provide a reasonable implementation of boundingRect(), shape(), + and contains(). You can set the font by calling setFont(). + + QGraphicsSimpleText does not display rich text; instead, you can use + QGraphicsTextItem, which provides full text control capabilities. + + \img graphicsview-simpletextitem.png + + \sa QGraphicsTextItem, QGraphicsPathItem, QGraphicsRectItem, + QGraphicsEllipseItem, QGraphicsPixmapItem, QGraphicsPolygonItem, + QGraphicsLineItem, {Graphics View Framework} +*/ + +/*! + Constructs a QGraphicsSimpleTextItem. + + \a parent is passed to QGraphicsItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsSimpleTextItem::QGraphicsSimpleTextItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsSimpleTextItemPrivate, parent, scene) +{ +} + +/*! + Constructs a QGraphicsSimpleTextItem, using \a text as the default plain text. + + \a parent is passed to QGraphicsItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsSimpleTextItem::QGraphicsSimpleTextItem(const QString &text, QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsSimpleTextItemPrivate, parent, scene) +{ + setText(text); +} + +/*! + Destroys the QGraphicsSimpleTextItem. +*/ +QGraphicsSimpleTextItem::~QGraphicsSimpleTextItem() +{ +} + +/*! + Sets the item's text to \a text. The text will be displayed as + plain text. Newline characters ('\n') as well as characters of + type QChar::LineSeparator will cause item to break the text into + multiple lines. +*/ +void QGraphicsSimpleTextItem::setText(const QString &text) +{ + Q_D(QGraphicsSimpleTextItem); + if (d->text == text) + return; + d->text = text; + d->updateBoundingRect(); + update(); +} + +/*! + Returns the item's text. +*/ +QString QGraphicsSimpleTextItem::text() const +{ + Q_D(const QGraphicsSimpleTextItem); + return d->text; +} + +/*! + Sets the font that is used to draw the item's text to \a font. +*/ +void QGraphicsSimpleTextItem::setFont(const QFont &font) +{ + Q_D(QGraphicsSimpleTextItem); + d->font = font; + d->updateBoundingRect(); +} + +/*! + Returns the font that is used to draw the item's text. +*/ +QFont QGraphicsSimpleTextItem::font() const +{ + Q_D(const QGraphicsSimpleTextItem); + return d->font; +} + +/*! + \reimp +*/ +QRectF QGraphicsSimpleTextItem::boundingRect() const +{ + Q_D(const QGraphicsSimpleTextItem); + return d->boundingRect; +} + +/*! + \reimp +*/ +QPainterPath QGraphicsSimpleTextItem::shape() const +{ + Q_D(const QGraphicsSimpleTextItem); + QPainterPath path; + path.addRect(d->boundingRect); + return path; +} + +/*! + \reimp +*/ +bool QGraphicsSimpleTextItem::contains(const QPointF &point) const +{ + Q_D(const QGraphicsSimpleTextItem); + return d->boundingRect.contains(point); +} + +/*! + \reimp +*/ +void QGraphicsSimpleTextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(widget); + Q_D(QGraphicsSimpleTextItem); + + painter->setFont(d->font); + + QString tmp = d->text; + tmp.replace(QLatin1Char('\n'), QChar::LineSeparator); + QStackTextEngine engine(tmp, d->font); + QTextLayout layout(&engine); + setupTextLayout(&layout); + + QPen p; + p.setBrush(d->brush); + painter->setPen(p); + if (d->pen.style() == Qt::NoPen && d->brush.style() == Qt::SolidPattern) { + painter->setBrush(Qt::NoBrush); + } else { + QTextLayout::FormatRange range; + range.start = 0; + range.length = layout.text().length(); + range.format.setTextOutline(d->pen); + QList formats; + formats.append(range); + layout.setAdditionalFormats(formats); + } + + layout.draw(painter, QPointF(0, 0)); + + if (option->state & (QStyle::State_Selected | QStyle::State_HasFocus)) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsSimpleTextItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QAbstractGraphicsShapeItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsSimpleTextItem::opaqueArea() const +{ + return QAbstractGraphicsShapeItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsSimpleTextItem::type() const +{ + return Type; +} + +/*! + \internal +*/ +bool QGraphicsSimpleTextItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsSimpleTextItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsSimpleTextItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsItemGroup + \brief The QGraphicsItemGroup class provides a container that treats + a group of items as a single item. + \since 4.2 + \ingroup graphicsview-api + + A QGraphicsItemGroup is a special type of compound item that + treats itself and all its children as one item (i.e., all events + and geometries for all children are merged together). It's common + to use item groups in presentation tools, when the user wants to + group several smaller items into one big item in order to simplify + moving and copying of items. + + If all you want is to store items inside other items, you can use + any QGraphicsItem directly by passing a suitable parent to + setParentItem(). + + The boundingRect() function of QGraphicsItemGroup returns the + bounding rectangle of all items in the item group. + QGraphicsItemGroup ignores the ItemIgnoresTransformations flag on + its children (i.e., with respect to the geometry of the group + item, the children are treated as if they were transformable). + + There are two ways to construct an item group. The easiest and + most common approach is to pass a list of items (e.g., all + selected items) to QGraphicsScene::createItemGroup(), which + returns a new QGraphicsItemGroup item. The other approach is to + manually construct a QGraphicsItemGroup item, add it to the scene + calling QGraphicsScene::addItem(), and then add items to the group + manually, one at a time by calling addToGroup(). To dismantle + ("ungroup") an item group, you can either call + QGraphicsScene::destroyItemGroup(), or you can manually remove all + items from the group by calling removeFromGroup(). + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 17 + + The operation of adding and removing items preserves the items' + scene-relative position and transformation, as opposed to calling + setParentItem(), where only the child item's parent-relative + position and transformation are kept. + + The addtoGroup() function reparents the target item to this item + group, keeping the item's position and transformation intact + relative to the scene. Visually, this means that items added via + addToGroup() will remain completely unchanged as a result of this + operation, regardless of the item or the group's current position + or transformation; although the item's position and matrix are + likely to change. + + The removeFromGroup() function has similar semantics to + setParentItem(); it reparents the item to the parent item of the + item group. As with addToGroup(), the item's scene-relative + position and transformation remain intact. + + \sa QGraphicsItem, {Graphics View Framework} +*/ + +class QGraphicsItemGroupPrivate : public QGraphicsItemPrivate +{ +public: + QRectF itemsBoundingRect; +}; + +/*! + Constructs a QGraphicsItemGroup. \a parent is passed to QGraphicsItem's + constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsItemGroup::QGraphicsItemGroup(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QGraphicsItemGroupPrivate, parent, scene) +{ + setHandlesChildEvents(true); +} + +/*! + Destroys the QGraphicsItemGroup. +*/ +QGraphicsItemGroup::~QGraphicsItemGroup() +{ +} + +/*! + Adds the given \a item to this item group. The item will be + reparented to this group, but its position and transformation + relative to the scene will stay intact. + + \sa removeFromGroup(), QGraphicsScene::createItemGroup() +*/ +void QGraphicsItemGroup::addToGroup(QGraphicsItem *item) +{ + Q_D(QGraphicsItemGroup); + if (!item) { + qWarning("QGraphicsItemGroup::addToGroup: cannot add null item"); + return; + } + if (item == this) { + qWarning("QGraphicsItemGroup::addToGroup: cannot add a group to itself"); + return; + } + + // COMBINE + bool ok; + QTransform itemTransform = item->itemTransform(this, &ok); + + if (!ok) { + qWarning("QGraphicsItemGroup::addToGroup: could not find a valid transformation from item to group coordinates"); + return; + } + + QTransform newItemTransform(itemTransform); + item->setPos(mapFromItem(item, 0, 0)); + item->setParentItem(this); + + // removing position from translation component of the new transform + if (!item->pos().isNull()) + newItemTransform *= QTransform::fromTranslate(-item->x(), -item->y()); + + // removing additional transformations properties applied with itemTransform() + QPointF origin = item->transformOriginPoint(); + QMatrix4x4 m; + QList transformList = item->transformations(); + for (int i = 0; i < transformList.size(); ++i) + transformList.at(i)->applyTo(&m); + newItemTransform *= m.toTransform().inverted(); + newItemTransform.translate(origin.x(), origin.y()); + newItemTransform.rotate(-item->rotation()); + newItemTransform.scale(1/item->scale(), 1/item->scale()); + newItemTransform.translate(-origin.x(), -origin.y()); + + // ### Expensive, we could maybe use dirtySceneTransform bit for optimization + + item->setTransform(newItemTransform); + item->d_func()->setIsMemberOfGroup(true); + prepareGeometryChange(); + d->itemsBoundingRect |= itemTransform.mapRect(item->boundingRect() | item->childrenBoundingRect()); + update(); +} + +/*! + Removes the specified \a item from this group. The item will be + reparented to this group's parent item, or to 0 if this group has + no parent. Its position and transformation relative to the scene + will stay intact. + + \sa addToGroup(), QGraphicsScene::destroyItemGroup() +*/ +void QGraphicsItemGroup::removeFromGroup(QGraphicsItem *item) +{ + Q_D(QGraphicsItemGroup); + if (!item) { + qWarning("QGraphicsItemGroup::removeFromGroup: cannot remove null item"); + return; + } + + QGraphicsItem *newParent = d_ptr->parent; + + // COMBINE + bool ok; + QTransform itemTransform; + if (newParent) + itemTransform = item->itemTransform(newParent, &ok); + else + itemTransform = item->sceneTransform(); + + QPointF oldPos = item->mapToItem(newParent, 0, 0); + item->setParentItem(newParent); + item->setPos(oldPos); + + // removing position from translation component of the new transform + if (!item->pos().isNull()) + itemTransform *= QTransform::fromTranslate(-item->x(), -item->y()); + + // removing additional transformations properties applied + // with itemTransform() or sceneTransform() + QPointF origin = item->transformOriginPoint(); + QMatrix4x4 m; + QList transformList = item->transformations(); + for (int i = 0; i < transformList.size(); ++i) + transformList.at(i)->applyTo(&m); + itemTransform *= m.toTransform().inverted(); + itemTransform.translate(origin.x(), origin.y()); + itemTransform.rotate(-item->rotation()); + itemTransform.scale(1 / item->scale(), 1 / item->scale()); + itemTransform.translate(-origin.x(), -origin.y()); + + // ### Expensive, we could maybe use dirtySceneTransform bit for optimization + + item->setTransform(itemTransform); + item->d_func()->setIsMemberOfGroup(item->group() != 0); + + // ### Quite expensive. But removeFromGroup() isn't called very often. + prepareGeometryChange(); + d->itemsBoundingRect = childrenBoundingRect(); +} + +/*! + \reimp + + Returns the bounding rect of this group item, and all its children. +*/ +QRectF QGraphicsItemGroup::boundingRect() const +{ + Q_D(const QGraphicsItemGroup); + return d->itemsBoundingRect; +} + +/*! + \reimp +*/ +void QGraphicsItemGroup::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(widget); + if (option->state & QStyle::State_Selected) { + Q_D(QGraphicsItemGroup); + painter->setBrush(Qt::NoBrush); + painter->drawRect(d->itemsBoundingRect); + } +} + +/*! + \reimp +*/ +bool QGraphicsItemGroup::isObscuredBy(const QGraphicsItem *item) const +{ + return QGraphicsItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsItemGroup::opaqueArea() const +{ + return QGraphicsItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsItemGroup::type() const +{ + return Type; +} + +#ifndef QT_NO_GRAPHICSEFFECT +QRectF QGraphicsItemEffectSourcePrivate::boundingRect(Qt::CoordinateSystem system) const +{ + const bool deviceCoordinates = (system == Qt::DeviceCoordinates); + if (!info && deviceCoordinates) { + // Device coordinates without info not yet supported. + qWarning("QGraphicsEffectSource::boundingRect: Not yet implemented, lacking device context"); + return QRectF(); + } + + QRectF rect = item->boundingRect(); + if (!item->d_ptr->children.isEmpty()) + rect |= item->childrenBoundingRect(); + + if (deviceCoordinates) { + Q_ASSERT(info->painter); + rect = info->painter->worldTransform().mapRect(rect); + } + + return rect; +} + +void QGraphicsItemEffectSourcePrivate::draw(QPainter *painter) +{ + if (!info) { + qWarning("QGraphicsEffectSource::draw: Can only begin as a result of QGraphicsEffect::draw"); + return; + } + + Q_ASSERT(item->d_ptr->scene); + QGraphicsScenePrivate *scened = item->d_ptr->scene->d_func(); + if (painter == info->painter) { + scened->draw(item, painter, info->viewTransform, info->transformPtr, info->exposedRegion, + info->widget, info->opacity, info->effectTransform, info->wasDirtySceneTransform, + info->drawItem); + } else { + QTransform effectTransform = info->painter->worldTransform().inverted(); + effectTransform *= painter->worldTransform(); + scened->draw(item, painter, info->viewTransform, info->transformPtr, info->exposedRegion, + info->widget, info->opacity, &effectTransform, info->wasDirtySceneTransform, + info->drawItem); + } +} + +// sourceRect must be in the given coordinate system +QRect QGraphicsItemEffectSourcePrivate::paddedEffectRect(Qt::CoordinateSystem system, QGraphicsEffect::PixmapPadMode mode, const QRectF &sourceRect, bool *unpadded) const +{ + QRectF effectRectF; + + if (unpadded) + *unpadded = false; + + if (mode == QGraphicsEffect::PadToEffectiveBoundingRect) { + if (info) { + QRectF deviceRect = system == Qt::DeviceCoordinates ? sourceRect : info->painter->worldTransform().mapRect(sourceRect); + effectRectF = item->graphicsEffect()->boundingRectFor(deviceRect); + if (unpadded) + *unpadded = (effectRectF.size() == sourceRect.size()); + if (info && system == Qt::LogicalCoordinates) + effectRectF = info->painter->worldTransform().inverted().mapRect(effectRectF); + } else { + // no choice but to send a logical coordinate bounding rect to boundingRectFor + effectRectF = item->graphicsEffect()->boundingRectFor(sourceRect); + } + } else if (mode == QGraphicsEffect::PadToTransparentBorder) { + // adjust by 1.5 to account for cosmetic pens + effectRectF = sourceRect.adjusted(-1.5, -1.5, 1.5, 1.5); + } else { + effectRectF = sourceRect; + if (unpadded) + *unpadded = true; + } + + return effectRectF.toAlignedRect(); +} + +QPixmap QGraphicsItemEffectSourcePrivate::pixmap(Qt::CoordinateSystem system, QPoint *offset, + QGraphicsEffect::PixmapPadMode mode) const +{ + const bool deviceCoordinates = (system == Qt::DeviceCoordinates); + if (!info && deviceCoordinates) { + // Device coordinates without info not yet supported. + qWarning("QGraphicsEffectSource::pixmap: Not yet implemented, lacking device context"); + return QPixmap(); + } + if (!item->d_ptr->scene) + return QPixmap(); + QGraphicsScenePrivate *scened = item->d_ptr->scene->d_func(); + + bool unpadded; + const QRectF sourceRect = boundingRect(system); + QRect effectRect = paddedEffectRect(system, mode, sourceRect, &unpadded); + + if (offset) + *offset = effectRect.topLeft(); + + bool untransformed = !deviceCoordinates + || info->painter->worldTransform().type() <= QTransform::TxTranslate; + if (untransformed && unpadded && isPixmap()) { + if (offset) + *offset = boundingRect(system).topLeft().toPoint(); + return static_cast(item)->pixmap(); + } + + if (effectRect.isEmpty()) + return QPixmap(); + + QPixmap pixmap(effectRect.size()); + pixmap.fill(Qt::transparent); + QPainter pixmapPainter(&pixmap); + pixmapPainter.setRenderHints(info ? info->painter->renderHints() : QPainter::TextAntialiasing); + + QTransform effectTransform = QTransform::fromTranslate(-effectRect.x(), -effectRect.y()); + if (deviceCoordinates && info->effectTransform) + effectTransform *= *info->effectTransform; + + if (!info) { + // Logical coordinates without info. + QTransform sceneTransform = item->sceneTransform(); + QTransform newEffectTransform = sceneTransform.inverted(); + newEffectTransform *= effectTransform; + scened->draw(item, &pixmapPainter, 0, &sceneTransform, 0, 0, qreal(1.0), + &newEffectTransform, false, true); + } else if (deviceCoordinates) { + // Device coordinates with info. + scened->draw(item, &pixmapPainter, info->viewTransform, info->transformPtr, 0, + info->widget, info->opacity, &effectTransform, info->wasDirtySceneTransform, + info->drawItem); + } else { + // Item coordinates with info. + QTransform newEffectTransform = info->transformPtr->inverted(); + newEffectTransform *= effectTransform; + scened->draw(item, &pixmapPainter, info->viewTransform, info->transformPtr, 0, + info->widget, info->opacity, &newEffectTransform, info->wasDirtySceneTransform, + info->drawItem); + } + + pixmapPainter.end(); + + return pixmap; +} +#endif //QT_NO_GRAPHICSEFFECT + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, QGraphicsItem *item) +{ + if (!item) { + debug << "QGraphicsItem(0)"; + return debug; + } + + if (QGraphicsObject *o = item->toGraphicsObject()) + debug << o->metaObject()->className(); + else + debug << "QGraphicsItem"; + debug << "(this =" << (void*)item + << ", parent =" << (void*)item->parentItem() + << ", pos =" << item->pos() + << ", z =" << item->zValue() << ", flags = " + << item->flags() << ")"; + return debug; +} + +QDebug operator<<(QDebug debug, QGraphicsObject *item) +{ + if (!item) { + debug << "QGraphicsObject(0)"; + return debug; + } + + debug.nospace() << item->metaObject()->className() << '(' << (void*)item; + if (!item->objectName().isEmpty()) + debug << ", name = " << item->objectName(); + debug.nospace() << ", parent = " << ((void*)item->parentItem()) + << ", pos = " << item->pos() + << ", z = " << item->zValue() << ", flags = " + << item->flags() << ')'; + return debug.space(); +} + +QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemChange change) +{ + const char *str = "UnknownChange"; + switch (change) { + case QGraphicsItem::ItemChildAddedChange: + str = "ItemChildAddedChange"; + break; + case QGraphicsItem::ItemChildRemovedChange: + str = "ItemChildRemovedChange"; + break; + case QGraphicsItem::ItemCursorChange: + str = "ItemCursorChange"; + break; + case QGraphicsItem::ItemCursorHasChanged: + str = "ItemCursorHasChanged"; + break; + case QGraphicsItem::ItemEnabledChange: + str = "ItemEnabledChange"; + break; + case QGraphicsItem::ItemEnabledHasChanged: + str = "ItemEnabledHasChanged"; + break; + case QGraphicsItem::ItemFlagsChange: + str = "ItemFlagsChange"; + break; + case QGraphicsItem::ItemFlagsHaveChanged: + str = "ItemFlagsHaveChanged"; + break; + case QGraphicsItem::ItemMatrixChange: + str = "ItemMatrixChange"; + break; + case QGraphicsItem::ItemParentChange: + str = "ItemParentChange"; + break; + case QGraphicsItem::ItemParentHasChanged: + str = "ItemParentHasChanged"; + break; + case QGraphicsItem::ItemPositionChange: + str = "ItemPositionChange"; + break; + case QGraphicsItem::ItemPositionHasChanged: + str = "ItemPositionHasChanged"; + break; + case QGraphicsItem::ItemSceneChange: + str = "ItemSceneChange"; + break; + case QGraphicsItem::ItemSceneHasChanged: + str = "ItemSceneHasChanged"; + break; + case QGraphicsItem::ItemSelectedChange: + str = "ItemSelectedChange"; + break; + case QGraphicsItem::ItemSelectedHasChanged: + str = "ItemSelectedHasChanged"; + break; + case QGraphicsItem::ItemToolTipChange: + str = "ItemToolTipChange"; + break; + case QGraphicsItem::ItemToolTipHasChanged: + str = "ItemToolTipHasChanged"; + break; + case QGraphicsItem::ItemTransformChange: + str = "ItemTransformChange"; + break; + case QGraphicsItem::ItemTransformHasChanged: + str = "ItemTransformHasChanged"; + break; + case QGraphicsItem::ItemVisibleChange: + str = "ItemVisibleChange"; + break; + case QGraphicsItem::ItemVisibleHasChanged: + str = "ItemVisibleHasChanged"; + break; + case QGraphicsItem::ItemZValueChange: + str = "ItemZValueChange"; + break; + case QGraphicsItem::ItemZValueHasChanged: + str = "ItemZValueHasChanged"; + break; + case QGraphicsItem::ItemOpacityChange: + str = "ItemOpacityChange"; + break; + case QGraphicsItem::ItemOpacityHasChanged: + str = "ItemOpacityHasChanged"; + break; + case QGraphicsItem::ItemScenePositionHasChanged: + str = "ItemScenePositionHasChanged"; + break; + case QGraphicsItem::ItemRotationChange: + str = "ItemRotationChange"; + break; + case QGraphicsItem::ItemRotationHasChanged: + str = "ItemRotationHasChanged"; + break; + case QGraphicsItem::ItemScaleChange: + str = "ItemScaleChange"; + break; + case QGraphicsItem::ItemScaleHasChanged: + str = "ItemScaleHasChanged"; + break; + case QGraphicsItem::ItemTransformOriginPointChange: + str = "ItemTransformOriginPointChange"; + break; + case QGraphicsItem::ItemTransformOriginPointHasChanged: + str = "ItemTransformOriginPointHasChanged"; + break; + } + debug << str; + return debug; +} + +QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemFlag flag) +{ + const char *str = "UnknownFlag"; + switch (flag) { + case QGraphicsItem::ItemIsMovable: + str = "ItemIsMovable"; + break; + case QGraphicsItem::ItemIsSelectable: + str = "ItemIsSelectable"; + break; + case QGraphicsItem::ItemIsFocusable: + str = "ItemIsFocusable"; + break; + case QGraphicsItem::ItemClipsToShape: + str = "ItemClipsToShape"; + break; + case QGraphicsItem::ItemClipsChildrenToShape: + str = "ItemClipsChildrenToShape"; + break; + case QGraphicsItem::ItemIgnoresTransformations: + str = "ItemIgnoresTransformations"; + break; + case QGraphicsItem::ItemIgnoresParentOpacity: + str = "ItemIgnoresParentOpacity"; + break; + case QGraphicsItem::ItemDoesntPropagateOpacityToChildren: + str = "ItemDoesntPropagateOpacityToChildren"; + break; + case QGraphicsItem::ItemStacksBehindParent: + str = "ItemStacksBehindParent"; + break; + case QGraphicsItem::ItemUsesExtendedStyleOption: + str = "ItemUsesExtendedStyleOption"; + break; + case QGraphicsItem::ItemHasNoContents: + str = "ItemHasNoContents"; + break; + case QGraphicsItem::ItemSendsGeometryChanges: + str = "ItemSendsGeometryChanges"; + break; + case QGraphicsItem::ItemAcceptsInputMethod: + str = "ItemAcceptsInputMethod"; + break; + case QGraphicsItem::ItemNegativeZStacksBehindParent: + str = "ItemNegativeZStacksBehindParent"; + break; + case QGraphicsItem::ItemIsPanel: + str = "ItemIsPanel"; + break; + case QGraphicsItem::ItemIsFocusScope: + str = "ItemIsFocusScope"; + break; + case QGraphicsItem::ItemSendsScenePositionChanges: + str = "ItemSendsScenePositionChanges"; + break; + case QGraphicsItem::ItemStopsClickFocusPropagation: + str = "ItemStopsClickFocusPropagation"; + break; + case QGraphicsItem::ItemStopsFocusHandling: + str = "ItemStopsFocusHandling"; + break; + } + debug << str; + return debug; +} + +QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemFlags flags) +{ + debug << '('; + bool f = false; + for (int i = 0; i < 17; ++i) { + if (flags & (1 << i)) { + if (f) + debug << '|'; + f = true; + debug << QGraphicsItem::GraphicsItemFlag(int(flags & (1 << i))); + } + } + debug << ')'; + return debug; +} + +#endif + +QT_END_NAMESPACE + +#include "moc_qgraphicsitem.cpp" + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicsitem.h b/src/widgets/graphicsview/qgraphicsitem.h new file mode 100644 index 0000000000..67c9cd3e2a --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsitem.h @@ -0,0 +1,1172 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSITEM_H +#define QGRAPHICSITEM_H + +#include +#include +#include +#include +#include +#include +#include + +class tst_QGraphicsItem; + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QBrush; +class QCursor; +class QFocusEvent; +class QGraphicsEffect; +class QGraphicsItemGroup; +class QGraphicsObject; +class QGraphicsSceneContextMenuEvent; +class QGraphicsSceneDragDropEvent; +class QGraphicsSceneEvent; +class QGraphicsSceneHoverEvent; +class QGraphicsSceneMouseEvent; +class QGraphicsSceneWheelEvent; +class QGraphicsScene; +class QGraphicsTransform; +class QGraphicsWidget; +class QInputMethodEvent; +class QKeyEvent; +class QMatrix; +class QMenu; +class QPainter; +class QPen; +class QPointF; +class QRectF; +class QStyleOptionGraphicsItem; + +class QGraphicsItemPrivate; +class Q_GUI_EXPORT QGraphicsItem +{ +public: + enum GraphicsItemFlag { + ItemIsMovable = 0x1, + ItemIsSelectable = 0x2, + ItemIsFocusable = 0x4, + ItemClipsToShape = 0x8, + ItemClipsChildrenToShape = 0x10, + ItemIgnoresTransformations = 0x20, + ItemIgnoresParentOpacity = 0x40, + ItemDoesntPropagateOpacityToChildren = 0x80, + ItemStacksBehindParent = 0x100, + ItemUsesExtendedStyleOption = 0x200, + ItemHasNoContents = 0x400, + ItemSendsGeometryChanges = 0x800, + ItemAcceptsInputMethod = 0x1000, + ItemNegativeZStacksBehindParent = 0x2000, + ItemIsPanel = 0x4000, + ItemIsFocusScope = 0x8000, // internal + ItemSendsScenePositionChanges = 0x10000, + ItemStopsClickFocusPropagation = 0x20000, + ItemStopsFocusHandling = 0x40000 + // NB! Don't forget to increase the d_ptr->flags bit field by 1 when adding a new flag. + }; + Q_DECLARE_FLAGS(GraphicsItemFlags, GraphicsItemFlag) + + enum GraphicsItemChange { + ItemPositionChange, + ItemMatrixChange, + ItemVisibleChange, + ItemEnabledChange, + ItemSelectedChange, + ItemParentChange, + ItemChildAddedChange, + ItemChildRemovedChange, + ItemTransformChange, + ItemPositionHasChanged, + ItemTransformHasChanged, + ItemSceneChange, + ItemVisibleHasChanged, + ItemEnabledHasChanged, + ItemSelectedHasChanged, + ItemParentHasChanged, + ItemSceneHasChanged, + ItemCursorChange, + ItemCursorHasChanged, + ItemToolTipChange, + ItemToolTipHasChanged, + ItemFlagsChange, + ItemFlagsHaveChanged, + ItemZValueChange, + ItemZValueHasChanged, + ItemOpacityChange, + ItemOpacityHasChanged, + ItemScenePositionHasChanged, + ItemRotationChange, + ItemRotationHasChanged, + ItemScaleChange, + ItemScaleHasChanged, + ItemTransformOriginPointChange, + ItemTransformOriginPointHasChanged + }; + + enum CacheMode { + NoCache, + ItemCoordinateCache, + DeviceCoordinateCache + }; + + enum PanelModality + { + NonModal, + PanelModal, + SceneModal + }; + + QGraphicsItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + virtual ~QGraphicsItem(); + + QGraphicsScene *scene() const; + + QGraphicsItem *parentItem() const; + QGraphicsItem *topLevelItem() const; + QGraphicsObject *parentObject() const; + QGraphicsWidget *parentWidget() const; + QGraphicsWidget *topLevelWidget() const; + QGraphicsWidget *window() const; + QGraphicsItem *panel() const; + void setParentItem(QGraphicsItem *parent); + QList children() const; // ### obsolete + QList childItems() const; + bool isWidget() const; + bool isWindow() const; + bool isPanel() const; + + QGraphicsObject *toGraphicsObject(); + const QGraphicsObject *toGraphicsObject() const; + + QGraphicsItemGroup *group() const; + void setGroup(QGraphicsItemGroup *group); + + GraphicsItemFlags flags() const; + void setFlag(GraphicsItemFlag flag, bool enabled = true); + void setFlags(GraphicsItemFlags flags); + + CacheMode cacheMode() const; + void setCacheMode(CacheMode mode, const QSize &cacheSize = QSize()); + + PanelModality panelModality() const; + void setPanelModality(PanelModality panelModality); + bool isBlockedByModalPanel(QGraphicsItem **blockingPanel = 0) const; + +#ifndef QT_NO_TOOLTIP + QString toolTip() const; + void setToolTip(const QString &toolTip); +#endif + +#ifndef QT_NO_CURSOR + QCursor cursor() const; + void setCursor(const QCursor &cursor); + bool hasCursor() const; + void unsetCursor(); +#endif + + bool isVisible() const; + bool isVisibleTo(const QGraphicsItem *parent) const; + void setVisible(bool visible); + inline void hide() { setVisible(false); } + inline void show() { setVisible(true); } + + bool isEnabled() const; + void setEnabled(bool enabled); + + bool isSelected() const; + void setSelected(bool selected); + + bool acceptDrops() const; + void setAcceptDrops(bool on); + + qreal opacity() const; + qreal effectiveOpacity() const; + void setOpacity(qreal opacity); + +#ifndef QT_NO_GRAPHICSEFFECT + // Effect + QGraphicsEffect *graphicsEffect() const; + void setGraphicsEffect(QGraphicsEffect *effect); +#endif //QT_NO_GRAPHICSEFFECT + + Qt::MouseButtons acceptedMouseButtons() const; + void setAcceptedMouseButtons(Qt::MouseButtons buttons); + + bool acceptsHoverEvents() const; // ### obsolete + void setAcceptsHoverEvents(bool enabled); // ### obsolete + bool acceptHoverEvents() const; + void setAcceptHoverEvents(bool enabled); + bool acceptTouchEvents() const; + void setAcceptTouchEvents(bool enabled); + + bool filtersChildEvents() const; + void setFiltersChildEvents(bool enabled); + + bool handlesChildEvents() const; + void setHandlesChildEvents(bool enabled); + + bool isActive() const; + void setActive(bool active); + + bool hasFocus() const; + void setFocus(Qt::FocusReason focusReason = Qt::OtherFocusReason); + void clearFocus(); + + QGraphicsItem *focusProxy() const; + void setFocusProxy(QGraphicsItem *item); + + QGraphicsItem *focusItem() const; + QGraphicsItem *focusScopeItem() const; + + void grabMouse(); + void ungrabMouse(); + void grabKeyboard(); + void ungrabKeyboard(); + + // Positioning in scene coordinates + QPointF pos() const; + inline qreal x() const { return pos().x(); } + void setX(qreal x); + inline qreal y() const { return pos().y(); } + void setY(qreal y); + QPointF scenePos() const; + void setPos(const QPointF &pos); + inline void setPos(qreal x, qreal y); + inline void moveBy(qreal dx, qreal dy) { setPos(pos().x() + dx, pos().y() + dy); } + + void ensureVisible(const QRectF &rect = QRectF(), int xmargin = 50, int ymargin = 50); + inline void ensureVisible(qreal x, qreal y, qreal w, qreal h, int xmargin = 50, int ymargin = 50); + + // Local transformation + QMatrix matrix() const; + QMatrix sceneMatrix() const; + void setMatrix(const QMatrix &matrix, bool combine = false); + void resetMatrix(); + QTransform transform() const; + QTransform sceneTransform() const; + QTransform deviceTransform(const QTransform &viewportTransform) const; + QTransform itemTransform(const QGraphicsItem *other, bool *ok = 0) const; + void setTransform(const QTransform &matrix, bool combine = false); + void resetTransform(); + + void rotate(qreal angle); // ### obsolete + void scale(qreal sx, qreal sy); // ### obsolete + void shear(qreal sh, qreal sv); // ### obsolete + void translate(qreal dx, qreal dy); // ### obsolete + + void setRotation(qreal angle); + qreal rotation() const; + + void setScale(qreal scale); + qreal scale() const; + + QList transformations() const; + void setTransformations(const QList &transformations); + + QPointF transformOriginPoint() const; + void setTransformOriginPoint(const QPointF &origin); + inline void setTransformOriginPoint(qreal ax, qreal ay) + { setTransformOriginPoint(QPointF(ax,ay)); } + + virtual void advance(int phase); + + // Stacking order + qreal zValue() const; + void setZValue(qreal z); + void stackBefore(const QGraphicsItem *sibling); + + // Hit test + virtual QRectF boundingRect() const = 0; + QRectF childrenBoundingRect() const; + QRectF sceneBoundingRect() const; + virtual QPainterPath shape() const; + bool isClipped() const; + QPainterPath clipPath() const; + virtual bool contains(const QPointF &point) const; + virtual bool collidesWithItem(const QGraphicsItem *other, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + virtual bool collidesWithPath(const QPainterPath &path, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + QList collidingItems(Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + bool isObscured() const; + bool isObscured(const QRectF &rect) const; // ### Qt 5: merge with isObscured(), add QRectF arg to isObscuredBy() + inline bool isObscured(qreal x, qreal y, qreal w, qreal h) const; + virtual bool isObscuredBy(const QGraphicsItem *item) const; + virtual QPainterPath opaqueArea() const; + + QRegion boundingRegion(const QTransform &itemToDeviceTransform) const; + qreal boundingRegionGranularity() const; + void setBoundingRegionGranularity(qreal granularity); + + // Drawing + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) = 0; + void update(const QRectF &rect = QRectF()); + inline void update(qreal x, qreal y, qreal width, qreal height); + void scroll(qreal dx, qreal dy, const QRectF &rect = QRectF()); + + // Coordinate mapping + QPointF mapToItem(const QGraphicsItem *item, const QPointF &point) const; + QPointF mapToParent(const QPointF &point) const; + QPointF mapToScene(const QPointF &point) const; + QPolygonF mapToItem(const QGraphicsItem *item, const QRectF &rect) const; + QPolygonF mapToParent(const QRectF &rect) const; + QPolygonF mapToScene(const QRectF &rect) const; + QRectF mapRectToItem(const QGraphicsItem *item, const QRectF &rect) const; + QRectF mapRectToParent(const QRectF &rect) const; + QRectF mapRectToScene(const QRectF &rect) const; + QPolygonF mapToItem(const QGraphicsItem *item, const QPolygonF &polygon) const; + QPolygonF mapToParent(const QPolygonF &polygon) const; + QPolygonF mapToScene(const QPolygonF &polygon) const; + QPainterPath mapToItem(const QGraphicsItem *item, const QPainterPath &path) const; + QPainterPath mapToParent(const QPainterPath &path) const; + QPainterPath mapToScene(const QPainterPath &path) const; + QPointF mapFromItem(const QGraphicsItem *item, const QPointF &point) const; + QPointF mapFromParent(const QPointF &point) const; + QPointF mapFromScene(const QPointF &point) const; + QPolygonF mapFromItem(const QGraphicsItem *item, const QRectF &rect) const; + QPolygonF mapFromParent(const QRectF &rect) const; + QPolygonF mapFromScene(const QRectF &rect) const; + QRectF mapRectFromItem(const QGraphicsItem *item, const QRectF &rect) const; + QRectF mapRectFromParent(const QRectF &rect) const; + QRectF mapRectFromScene(const QRectF &rect) const; + QPolygonF mapFromItem(const QGraphicsItem *item, const QPolygonF &polygon) const; + QPolygonF mapFromParent(const QPolygonF &polygon) const; + QPolygonF mapFromScene(const QPolygonF &polygon) const; + QPainterPath mapFromItem(const QGraphicsItem *item, const QPainterPath &path) const; + QPainterPath mapFromParent(const QPainterPath &path) const; + QPainterPath mapFromScene(const QPainterPath &path) const; + + inline QPointF mapToItem(const QGraphicsItem *item, qreal x, qreal y) const; + inline QPointF mapToParent(qreal x, qreal y) const; + inline QPointF mapToScene(qreal x, qreal y) const; + inline QPolygonF mapToItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const; + inline QPolygonF mapToParent(qreal x, qreal y, qreal w, qreal h) const; + inline QPolygonF mapToScene(qreal x, qreal y, qreal w, qreal h) const; + inline QRectF mapRectToItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const; + inline QRectF mapRectToParent(qreal x, qreal y, qreal w, qreal h) const; + inline QRectF mapRectToScene(qreal x, qreal y, qreal w, qreal h) const; + inline QPointF mapFromItem(const QGraphicsItem *item, qreal x, qreal y) const; + inline QPointF mapFromParent(qreal x, qreal y) const; + inline QPointF mapFromScene(qreal x, qreal y) const; + inline QPolygonF mapFromItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const; + inline QPolygonF mapFromParent(qreal x, qreal y, qreal w, qreal h) const; + inline QPolygonF mapFromScene(qreal x, qreal y, qreal w, qreal h) const; + inline QRectF mapRectFromItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const; + inline QRectF mapRectFromParent(qreal x, qreal y, qreal w, qreal h) const; + inline QRectF mapRectFromScene(qreal x, qreal y, qreal w, qreal h) const; + + bool isAncestorOf(const QGraphicsItem *child) const; + QGraphicsItem *commonAncestorItem(const QGraphicsItem *other) const; + bool isUnderMouse() const; + + // Custom data + QVariant data(int key) const; + void setData(int key, const QVariant &value); + + Qt::InputMethodHints inputMethodHints() const; + void setInputMethodHints(Qt::InputMethodHints hints); + + enum { + Type = 1, + UserType = 65536 + }; + virtual int type() const; + + void installSceneEventFilter(QGraphicsItem *filterItem); + void removeSceneEventFilter(QGraphicsItem *filterItem); + +protected: + void updateMicroFocus(); + virtual bool sceneEventFilter(QGraphicsItem *watched, QEvent *event); + virtual bool sceneEvent(QEvent *event); + virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); + virtual void dragEnterEvent(QGraphicsSceneDragDropEvent *event); + virtual void dragLeaveEvent(QGraphicsSceneDragDropEvent *event); + virtual void dragMoveEvent(QGraphicsSceneDragDropEvent *event); + virtual void dropEvent(QGraphicsSceneDragDropEvent *event); + virtual void focusInEvent(QFocusEvent *event); + virtual void focusOutEvent(QFocusEvent *event); + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + virtual void keyPressEvent(QKeyEvent *event); + virtual void keyReleaseEvent(QKeyEvent *event); + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + virtual void wheelEvent(QGraphicsSceneWheelEvent *event); + virtual void inputMethodEvent(QInputMethodEvent *event); + virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); + + enum Extension { + UserExtension = 0x80000000 + }; + virtual bool supportsExtension(Extension extension) const; + virtual void setExtension(Extension extension, const QVariant &variant); + virtual QVariant extension(const QVariant &variant) const; + +protected: + QGraphicsItem(QGraphicsItemPrivate &dd, + QGraphicsItem *parent, QGraphicsScene *scene); + QScopedPointer d_ptr; + + void addToIndex(); + void removeFromIndex(); + void prepareGeometryChange(); + +private: + Q_DISABLE_COPY(QGraphicsItem) + Q_DECLARE_PRIVATE(QGraphicsItem) + friend class QGraphicsItemGroup; + friend class QGraphicsScene; + friend class QGraphicsScenePrivate; + friend class QGraphicsSceneFindItemBspTreeVisitor; + friend class QGraphicsSceneBspTree; + friend class QGraphicsView; + friend class QGraphicsViewPrivate; + friend class QGraphicsObject; + friend class QGraphicsWidget; + friend class QGraphicsWidgetPrivate; + friend class QGraphicsProxyWidgetPrivate; + friend class QGraphicsSceneIndex; + friend class QGraphicsSceneIndexPrivate; + friend class QGraphicsSceneBspTreeIndex; + friend class QGraphicsSceneBspTreeIndexPrivate; + friend class QGraphicsItemEffectSourcePrivate; + friend class QGraphicsTransformPrivate; +#ifndef QT_NO_GESTURES + friend class QGestureManager; +#endif + friend class ::tst_QGraphicsItem; + friend bool qt_closestLeaf(const QGraphicsItem *, const QGraphicsItem *); + friend bool qt_closestItemFirst(const QGraphicsItem *, const QGraphicsItem *); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGraphicsItem::GraphicsItemFlags) +Q_DECLARE_INTERFACE(QGraphicsItem, "com.trolltech.Qt.QGraphicsItem") + +inline void QGraphicsItem::setPos(qreal ax, qreal ay) +{ setPos(QPointF(ax, ay)); } +inline void QGraphicsItem::ensureVisible(qreal ax, qreal ay, qreal w, qreal h, int xmargin, int ymargin) +{ ensureVisible(QRectF(ax, ay, w, h), xmargin, ymargin); } +inline void QGraphicsItem::update(qreal ax, qreal ay, qreal width, qreal height) +{ update(QRectF(ax, ay, width, height)); } +inline bool QGraphicsItem::isObscured(qreal ax, qreal ay, qreal w, qreal h) const +{ return isObscured(QRectF(ax, ay, w, h)); } +inline QPointF QGraphicsItem::mapToItem(const QGraphicsItem *item, qreal ax, qreal ay) const +{ return mapToItem(item, QPointF(ax, ay)); } +inline QPointF QGraphicsItem::mapToParent(qreal ax, qreal ay) const +{ return mapToParent(QPointF(ax, ay)); } +inline QPointF QGraphicsItem::mapToScene(qreal ax, qreal ay) const +{ return mapToScene(QPointF(ax, ay)); } +inline QPointF QGraphicsItem::mapFromItem(const QGraphicsItem *item, qreal ax, qreal ay) const +{ return mapFromItem(item, QPointF(ax, ay)); } +inline QPointF QGraphicsItem::mapFromParent(qreal ax, qreal ay) const +{ return mapFromParent(QPointF(ax, ay)); } +inline QPointF QGraphicsItem::mapFromScene(qreal ax, qreal ay) const +{ return mapFromScene(QPointF(ax, ay)); } +inline QPolygonF QGraphicsItem::mapToItem(const QGraphicsItem *item, qreal ax, qreal ay, qreal w, qreal h) const +{ return mapToItem(item, QRectF(ax, ay, w, h)); } +inline QPolygonF QGraphicsItem::mapToParent(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapToParent(QRectF(ax, ay, w, h)); } +inline QPolygonF QGraphicsItem::mapToScene(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapToScene(QRectF(ax, ay, w, h)); } +inline QRectF QGraphicsItem::mapRectToItem(const QGraphicsItem *item, qreal ax, qreal ay, qreal w, qreal h) const +{ return mapRectToItem(item, QRectF(ax, ay, w, h)); } +inline QRectF QGraphicsItem::mapRectToParent(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapRectToParent(QRectF(ax, ay, w, h)); } +inline QRectF QGraphicsItem::mapRectToScene(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapRectToScene(QRectF(ax, ay, w, h)); } +inline QPolygonF QGraphicsItem::mapFromItem(const QGraphicsItem *item, qreal ax, qreal ay, qreal w, qreal h) const +{ return mapFromItem(item, QRectF(ax, ay, w, h)); } +inline QPolygonF QGraphicsItem::mapFromParent(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapFromParent(QRectF(ax, ay, w, h)); } +inline QPolygonF QGraphicsItem::mapFromScene(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapFromScene(QRectF(ax, ay, w, h)); } +inline QRectF QGraphicsItem::mapRectFromItem(const QGraphicsItem *item, qreal ax, qreal ay, qreal w, qreal h) const +{ return mapRectFromItem(item, QRectF(ax, ay, w, h)); } +inline QRectF QGraphicsItem::mapRectFromParent(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapRectFromParent(QRectF(ax, ay, w, h)); } +inline QRectF QGraphicsItem::mapRectFromScene(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapRectFromScene(QRectF(ax, ay, w, h)); } + + +class Q_GUI_EXPORT QGraphicsObject : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_PROPERTY(QGraphicsObject * parent READ parentObject WRITE setParentItem NOTIFY parentChanged DESIGNABLE false) + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged FINAL) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged FINAL) + Q_PROPERTY(QPointF pos READ pos WRITE setPos FINAL) + Q_PROPERTY(qreal x READ x WRITE setX NOTIFY xChanged FINAL) + Q_PROPERTY(qreal y READ y WRITE setY NOTIFY yChanged FINAL) + Q_PROPERTY(qreal z READ zValue WRITE setZValue NOTIFY zChanged FINAL) + Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged) + Q_PROPERTY(qreal scale READ scale WRITE setScale NOTIFY scaleChanged) + Q_PROPERTY(QPointF transformOriginPoint READ transformOriginPoint WRITE setTransformOriginPoint) +#ifndef QT_NO_GRAPHICSEFFECT + Q_PROPERTY(QGraphicsEffect *effect READ graphicsEffect WRITE setGraphicsEffect) +#endif + Q_PRIVATE_PROPERTY(QGraphicsItem::d_func(), QDeclarativeListProperty children READ childrenList DESIGNABLE false NOTIFY childrenChanged) + Q_PRIVATE_PROPERTY(QGraphicsItem::d_func(), qreal width READ width WRITE setWidth NOTIFY widthChanged RESET resetWidth FINAL) + Q_PRIVATE_PROPERTY(QGraphicsItem::d_func(), qreal height READ height WRITE setHeight NOTIFY heightChanged RESET resetHeight FINAL) + Q_CLASSINFO("DefaultProperty", "children") + Q_INTERFACES(QGraphicsItem) +public: + QGraphicsObject(QGraphicsItem *parent = 0); + + // ### Qt 5: Disambiguate +#ifdef Q_NO_USING_KEYWORD + const QObjectList &children() const { return QObject::children(); } +#else + using QObject::children; +#endif + +#ifndef QT_NO_GESTURES + void grabGesture(Qt::GestureType type, Qt::GestureFlags flags = Qt::GestureFlags()); + void ungrabGesture(Qt::GestureType type); +#endif + +protected Q_SLOTS: + void updateMicroFocus(); + +Q_SIGNALS: + void parentChanged(); + void opacityChanged(); + void visibleChanged(); + void enabledChanged(); + void xChanged(); + void yChanged(); + void zChanged(); + void rotationChanged(); + void scaleChanged(); + void childrenChanged(); + void widthChanged(); + void heightChanged(); + +protected: + QGraphicsObject(QGraphicsItemPrivate &dd, QGraphicsItem *parent, QGraphicsScene *scene); +private: + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; +}; + + +class QAbstractGraphicsShapeItemPrivate; +class Q_GUI_EXPORT QAbstractGraphicsShapeItem : public QGraphicsItem +{ +public: + QAbstractGraphicsShapeItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QAbstractGraphicsShapeItem(); + + QPen pen() const; + void setPen(const QPen &pen); + + QBrush brush() const; + void setBrush(const QBrush &brush); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + +protected: + QAbstractGraphicsShapeItem(QAbstractGraphicsShapeItemPrivate &dd, + QGraphicsItem *parent, QGraphicsScene *scene); + +private: + Q_DISABLE_COPY(QAbstractGraphicsShapeItem) + Q_DECLARE_PRIVATE(QAbstractGraphicsShapeItem) +}; + +class QGraphicsPathItemPrivate; +class Q_GUI_EXPORT QGraphicsPathItem : public QAbstractGraphicsShapeItem +{ +public: + QGraphicsPathItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsPathItem(const QPainterPath &path, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsPathItem(); + + QPainterPath path() const; + void setPath(const QPainterPath &path); + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 2 }; + int type() const; + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsPathItem) + Q_DECLARE_PRIVATE(QGraphicsPathItem) +}; + +class QGraphicsRectItemPrivate; +class Q_GUI_EXPORT QGraphicsRectItem : public QAbstractGraphicsShapeItem +{ +public: + QGraphicsRectItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsRectItem(const QRectF &rect, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsRectItem(qreal x, qreal y, qreal w, qreal h, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsRectItem(); + + QRectF rect() const; + void setRect(const QRectF &rect); + inline void setRect(qreal x, qreal y, qreal w, qreal h); + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 3 }; + int type() const; + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsRectItem) + Q_DECLARE_PRIVATE(QGraphicsRectItem) +}; + +inline void QGraphicsRectItem::setRect(qreal ax, qreal ay, qreal w, qreal h) +{ setRect(QRectF(ax, ay, w, h)); } + +class QGraphicsEllipseItemPrivate; +class Q_GUI_EXPORT QGraphicsEllipseItem : public QAbstractGraphicsShapeItem +{ +public: + QGraphicsEllipseItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsEllipseItem(const QRectF &rect, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsEllipseItem(qreal x, qreal y, qreal w, qreal h, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsEllipseItem(); + + QRectF rect() const; + void setRect(const QRectF &rect); + inline void setRect(qreal x, qreal y, qreal w, qreal h); + + int startAngle() const; + void setStartAngle(int angle); + + int spanAngle() const; + void setSpanAngle(int angle); + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 4 }; + int type() const; + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsEllipseItem) + Q_DECLARE_PRIVATE(QGraphicsEllipseItem) +}; + +inline void QGraphicsEllipseItem::setRect(qreal ax, qreal ay, qreal w, qreal h) +{ setRect(QRectF(ax, ay, w, h)); } + +class QGraphicsPolygonItemPrivate; +class Q_GUI_EXPORT QGraphicsPolygonItem : public QAbstractGraphicsShapeItem +{ +public: + QGraphicsPolygonItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsPolygonItem(const QPolygonF &polygon, + QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsPolygonItem(); + + QPolygonF polygon() const; + void setPolygon(const QPolygonF &polygon); + + Qt::FillRule fillRule() const; + void setFillRule(Qt::FillRule rule); + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 5 }; + int type() const; + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsPolygonItem) + Q_DECLARE_PRIVATE(QGraphicsPolygonItem) +}; + +class QGraphicsLineItemPrivate; +class Q_GUI_EXPORT QGraphicsLineItem : public QGraphicsItem +{ +public: + QGraphicsLineItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsLineItem(const QLineF &line, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsLineItem(qreal x1, qreal y1, qreal x2, qreal y2, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsLineItem(); + + QPen pen() const; + void setPen(const QPen &pen); + + QLineF line() const; + void setLine(const QLineF &line); + inline void setLine(qreal x1, qreal y1, qreal x2, qreal y2) + { setLine(QLineF(x1, y1, x2, y2)); } + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 6 }; + int type() const; + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsLineItem) + Q_DECLARE_PRIVATE(QGraphicsLineItem) +}; + +class QGraphicsPixmapItemPrivate; +class Q_GUI_EXPORT QGraphicsPixmapItem : public QGraphicsItem +{ +public: + enum ShapeMode { + MaskShape, + BoundingRectShape, + HeuristicMaskShape + }; + + QGraphicsPixmapItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsPixmapItem(); + + QPixmap pixmap() const; + void setPixmap(const QPixmap &pixmap); + + Qt::TransformationMode transformationMode() const; + void setTransformationMode(Qt::TransformationMode mode); + + QPointF offset() const; + void setOffset(const QPointF &offset); + inline void setOffset(qreal x, qreal y); + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 7 }; + int type() const; + + ShapeMode shapeMode() const; + void setShapeMode(ShapeMode mode); + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsPixmapItem) + Q_DECLARE_PRIVATE(QGraphicsPixmapItem) +}; + +inline void QGraphicsPixmapItem::setOffset(qreal ax, qreal ay) +{ setOffset(QPointF(ax, ay)); } + +class QGraphicsTextItemPrivate; +class QTextDocument; +class QTextCursor; +class Q_GUI_EXPORT QGraphicsTextItem : public QGraphicsObject +{ + Q_OBJECT + QDOC_PROPERTY(bool openExternalLinks READ openExternalLinks WRITE setOpenExternalLinks) + QDOC_PROPERTY(QTextCursor textCursor READ textCursor WRITE setTextCursor) + +public: + QGraphicsTextItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsTextItem(const QString &text, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsTextItem(); + + QString toHtml() const; + void setHtml(const QString &html); + + QString toPlainText() const; + void setPlainText(const QString &text); + + QFont font() const; + void setFont(const QFont &font); + + void setDefaultTextColor(const QColor &c); + QColor defaultTextColor() const; + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 8 }; + int type() const; + + void setTextWidth(qreal width); + qreal textWidth() const; + + void adjustSize(); + + void setDocument(QTextDocument *document); + QTextDocument *document() const; + + void setTextInteractionFlags(Qt::TextInteractionFlags flags); + Qt::TextInteractionFlags textInteractionFlags() const; + + void setTabChangesFocus(bool b); + bool tabChangesFocus() const; + + void setOpenExternalLinks(bool open); + bool openExternalLinks() const; + + void setTextCursor(const QTextCursor &cursor); + QTextCursor textCursor() const; + +Q_SIGNALS: + void linkActivated(const QString &); + void linkHovered(const QString &); + +protected: + bool sceneEvent(QEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); + void keyPressEvent(QKeyEvent *event); + void keyReleaseEvent(QKeyEvent *event); + void focusInEvent(QFocusEvent *event); + void focusOutEvent(QFocusEvent *event); + void dragEnterEvent(QGraphicsSceneDragDropEvent *event); + void dragLeaveEvent(QGraphicsSceneDragDropEvent *event); + void dragMoveEvent(QGraphicsSceneDragDropEvent *event); + void dropEvent(QGraphicsSceneDragDropEvent *event); + void inputMethodEvent(QInputMethodEvent *event); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + + QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsTextItem) + Q_PRIVATE_SLOT(dd, void _q_updateBoundingRect(const QSizeF &)) + Q_PRIVATE_SLOT(dd, void _q_update(QRectF)) + Q_PRIVATE_SLOT(dd, void _q_ensureVisible(QRectF)) + QGraphicsTextItemPrivate *dd; + friend class QGraphicsTextItemPrivate; +}; + +class QGraphicsSimpleTextItemPrivate; +class Q_GUI_EXPORT QGraphicsSimpleTextItem : public QAbstractGraphicsShapeItem +{ +public: + QGraphicsSimpleTextItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsSimpleTextItem(const QString &text, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsSimpleTextItem(); + + void setText(const QString &text); + QString text() const; + + void setFont(const QFont &font); + QFont font() const; + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 9 }; + int type() const; + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsSimpleTextItem) + Q_DECLARE_PRIVATE(QGraphicsSimpleTextItem) +}; + +class QGraphicsItemGroupPrivate; +class Q_GUI_EXPORT QGraphicsItemGroup : public QGraphicsItem +{ +public: + QGraphicsItemGroup(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsItemGroup(); + + void addToGroup(QGraphicsItem *item); + void removeFromGroup(QGraphicsItem *item); + + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 10 }; + int type() const; + +private: + Q_DISABLE_COPY(QGraphicsItemGroup) + Q_DECLARE_PRIVATE(QGraphicsItemGroup) +}; + +template inline T qgraphicsitem_cast(QGraphicsItem *item) +{ + return int(static_cast(0)->Type) == int(QGraphicsItem::Type) + || (item && int(static_cast(0)->Type) == item->type()) ? static_cast(item) : 0; +} + +template inline T qgraphicsitem_cast(const QGraphicsItem *item) +{ + return int(static_cast(0)->Type) == int(QGraphicsItem::Type) + || (item && int(static_cast(0)->Type) == item->type()) ? static_cast(item) : 0; +} + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug debug, QGraphicsItem *item); +Q_GUI_EXPORT QDebug operator<<(QDebug debug, QGraphicsObject *item); +Q_GUI_EXPORT QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemChange change); +Q_GUI_EXPORT QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemFlag flag); +Q_GUI_EXPORT QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemFlags flags); +#endif + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGraphicsItem *) +Q_DECLARE_METATYPE(QGraphicsScene *) + +QT_BEGIN_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGRAPHICSITEM_H diff --git a/src/widgets/graphicsview/qgraphicsitem_p.h b/src/widgets/graphicsview/qgraphicsitem_p.h new file mode 100644 index 0000000000..90ff43f93c --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsitem_p.h @@ -0,0 +1,891 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSITEM_P_H +#define QGRAPHICSITEM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicsitem.h" +#include "qset.h" +#include "qpixmapcache.h" +#include +#include "qgraphicstransform.h" +#include + +#include +#include + +#include + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +QT_BEGIN_NAMESPACE + +class QGraphicsItemPrivate; + +#ifndef QDECLARATIVELISTPROPERTY +#define QDECLARATIVELISTPROPERTY +template +class QDeclarativeListProperty { +public: + typedef void (*AppendFunction)(QDeclarativeListProperty *, T*); + typedef int (*CountFunction)(QDeclarativeListProperty *); + typedef T *(*AtFunction)(QDeclarativeListProperty *, int); + typedef void (*ClearFunction)(QDeclarativeListProperty *); + + QDeclarativeListProperty() + : object(0), data(0), append(0), count(0), at(0), clear(0), dummy1(0), dummy2(0) {} + QDeclarativeListProperty(QObject *o, QList &list) + : object(o), data(&list), append(qlist_append), count(qlist_count), at(qlist_at), + clear(qlist_clear), dummy1(0), dummy2(0) {} + QDeclarativeListProperty(QObject *o, void *d, AppendFunction a, CountFunction c = 0, AtFunction t = 0, + ClearFunction r = 0) + : object(o), data(d), append(a), count(c), at(t), clear(r), dummy1(0), dummy2(0) {} + + bool operator==(const QDeclarativeListProperty &o) const { + return object == o.object && + data == o.data && + append == o.append && + count == o.count && + at == o.at && + clear == o.clear; + } + + QObject *object; + void *data; + + AppendFunction append; + + CountFunction count; + AtFunction at; + + ClearFunction clear; + + void *dummy1; + void *dummy2; + +private: + static void qlist_append(QDeclarativeListProperty *p, T *v) { + ((QList *)p->data)->append(v); + } + static int qlist_count(QDeclarativeListProperty *p) { + return ((QList *)p->data)->count(); + } + static T *qlist_at(QDeclarativeListProperty *p, int idx) { + return ((QList *)p->data)->at(idx); + } + static void qlist_clear(QDeclarativeListProperty *p) { + return ((QList *)p->data)->clear(); + } +}; +#endif + +class QGraphicsItemCache +{ +public: + QGraphicsItemCache() : allExposed(false) { } + + // ItemCoordinateCache only + QRect boundingRect; + QSize fixedSize; + QPixmapCache::Key key; + + // DeviceCoordinateCache only + struct DeviceData { + DeviceData() {} + QTransform lastTransform; + QPoint cacheIndent; + QPixmapCache::Key key; + }; + QMap deviceData; + + // List of logical exposed rects + QVector exposed; + bool allExposed; + + // Empty cache + void purge(); +}; + +class Q_GUI_EXPORT QGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsItem) +public: + enum Extra { + ExtraToolTip, + ExtraCursor, + ExtraCacheData, + ExtraMaxDeviceCoordCacheSize, + ExtraBoundingRegionGranularity + }; + + enum AncestorFlag { + NoFlag = 0, + AncestorHandlesChildEvents = 0x1, + AncestorClipsChildren = 0x2, + AncestorIgnoresTransformations = 0x4, + AncestorFiltersChildEvents = 0x8 + }; + + inline QGraphicsItemPrivate() + : z(0), + opacity(1.), + scene(0), + parent(0), + transformData(0), + graphicsEffect(0), + index(-1), + siblingIndex(-1), + itemDepth(-1), + focusProxy(0), + subFocusItem(0), + focusScopeItem(0), + imHints(Qt::ImhNone), + panelModality(QGraphicsItem::NonModal), + acceptedMouseButtons(0x1f), + visible(1), + explicitlyHidden(0), + enabled(1), + explicitlyDisabled(0), + selected(0), + acceptsHover(0), + acceptDrops(0), + isMemberOfGroup(0), + handlesChildEvents(0), + itemDiscovered(0), + hasCursor(0), + ancestorFlags(0), + cacheMode(0), + hasBoundingRegionGranularity(0), + isWidget(0), + dirty(0), + dirtyChildren(0), + localCollisionHack(0), + inSetPosHelper(0), + needSortChildren(0), + allChildrenDirty(0), + fullUpdatePending(0), + dirtyChildrenBoundingRect(1), + flags(0), + paintedViewBoundingRectsNeedRepaint(0), + dirtySceneTransform(1), + geometryChanged(1), + inDestructor(0), + isObject(0), + ignoreVisible(0), + ignoreOpacity(0), + acceptTouchEvents(0), + acceptedTouchBeginEvent(0), + filtersDescendantEvents(0), + sceneTransformTranslateOnly(0), + notifyBoundingRectChanged(0), + notifyInvalidated(0), + mouseSetsFocus(1), + explicitActivate(0), + wantsActive(0), + holesInSiblingIndex(0), + sequentialOrdering(1), + updateDueToGraphicsEffect(0), + scenePosDescendants(0), + pendingPolish(0), + mayHaveChildWithGraphicsEffect(0), + isDeclarativeItem(0), + sendParentChangeNotification(0), + globalStackingOrder(-1), + q_ptr(0) + { + } + + inline virtual ~QGraphicsItemPrivate() + { } + + static const QGraphicsItemPrivate *get(const QGraphicsItem *item) + { + return item->d_ptr.data(); + } + static QGraphicsItemPrivate *get(QGraphicsItem *item) + { + return item->d_ptr.data(); + } + + void updateChildWithGraphicsEffectFlagRecursively(); + void updateAncestorFlag(QGraphicsItem::GraphicsItemFlag childFlag, + AncestorFlag flag = NoFlag, bool enabled = false, bool root = true); + void updateAncestorFlags(); + void setIsMemberOfGroup(bool enabled); + void remapItemPos(QEvent *event, QGraphicsItem *item); + QPointF genericMapFromScene(const QPointF &pos, const QWidget *viewport) const; + inline bool itemIsUntransformable() const + { + return (flags & QGraphicsItem::ItemIgnoresTransformations) + || (ancestorFlags & AncestorIgnoresTransformations); + } + + void combineTransformToParent(QTransform *x, const QTransform *viewTransform = 0) const; + void combineTransformFromParent(QTransform *x, const QTransform *viewTransform = 0) const; + virtual void updateSceneTransformFromParent(); + + // ### Qt 5: Remove. Workaround for reimplementation added after Qt 4.4. + virtual QVariant inputMethodQueryHelper(Qt::InputMethodQuery query) const; + static bool movableAncestorIsSelected(const QGraphicsItem *item); + + virtual void setPosHelper(const QPointF &pos); + void setTransformHelper(const QTransform &transform); + void prependGraphicsTransform(QGraphicsTransform *t); + void appendGraphicsTransform(QGraphicsTransform *t); + void setVisibleHelper(bool newVisible, bool explicitly, bool update = true); + void setEnabledHelper(bool newEnabled, bool explicitly, bool update = true); + bool discardUpdateRequest(bool ignoreVisibleBit = false, + bool ignoreDirtyBit = false, bool ignoreOpacity = false) const; + virtual void transformChanged() {} + int depth() const; +#ifndef QT_NO_GRAPHICSEFFECT + enum InvalidateReason { + OpacityChanged + }; + void invalidateParentGraphicsEffectsRecursively(); + void invalidateChildGraphicsEffectsRecursively(InvalidateReason reason); +#endif //QT_NO_GRAPHICSEFFECT + void invalidateDepthRecursively(); + void resolveDepth(); + void addChild(QGraphicsItem *child); + void removeChild(QGraphicsItem *child); + QDeclarativeListProperty childrenList(); + void setParentItemHelper(QGraphicsItem *parent, const QVariant *newParentVariant, + const QVariant *thisPointerVariant); + void childrenBoundingRectHelper(QTransform *x, QRectF *rect, QGraphicsItem *topMostEffectItem); + void initStyleOption(QStyleOptionGraphicsItem *option, const QTransform &worldTransform, + const QRegion &exposedRegion, bool allItems = false) const; + QRectF effectiveBoundingRect(QGraphicsItem *topMostEffectItem = 0) const; + QRectF sceneEffectiveBoundingRect() const; + + QRectF effectiveBoundingRect(const QRectF &rect) const; + + virtual void resolveFont(uint inheritedMask) + { + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->resolveFont(inheritedMask); + } + + virtual void resolvePalette(uint inheritedMask) + { + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->resolveFont(inheritedMask); + } + + virtual bool isProxyWidget() const; + + inline QVariant extra(Extra type) const + { + for (int i = 0; i < extras.size(); ++i) { + const ExtraStruct &extra = extras.at(i); + if (extra.type == type) + return extra.value; + } + return QVariant(); + } + + inline void setExtra(Extra type, const QVariant &value) + { + int index = -1; + for (int i = 0; i < extras.size(); ++i) { + if (extras.at(i).type == type) { + index = i; + break; + } + } + + if (index == -1) { + extras << ExtraStruct(type, value); + } else { + extras[index].value = value; + } + } + + inline void unsetExtra(Extra type) + { + for (int i = 0; i < extras.size(); ++i) { + if (extras.at(i).type == type) { + extras.removeAt(i); + return; + } + } + } + + struct ExtraStruct { + ExtraStruct(Extra type, QVariant value) + : type(type), value(value) + { } + + Extra type; + QVariant value; + + bool operator<(Extra extra) const + { return type < extra; } + }; + + QList extras; + + QGraphicsItemCache *maybeExtraItemCache() const; + QGraphicsItemCache *extraItemCache() const; + void removeExtraItemCache(); + + void updatePaintedViewBoundingRects(bool updateChildren); + void ensureSceneTransformRecursive(QGraphicsItem **topMostDirtyItem); + inline void ensureSceneTransform() + { + QGraphicsItem *that = q_func(); + ensureSceneTransformRecursive(&that); + } + + inline bool hasTranslateOnlySceneTransform() + { + ensureSceneTransform(); + return sceneTransformTranslateOnly; + } + + inline void invalidateChildrenSceneTransform() + { + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->dirtySceneTransform = 1; + } + + inline qreal calcEffectiveOpacity() const + { + qreal o = opacity; + QGraphicsItem *p = parent; + int myFlags = flags; + while (p) { + int parentFlags = p->d_ptr->flags; + + // If I have a parent, and I don't ignore my parent's opacity, and my + // parent propagates to me, then combine my local opacity with my parent's + // effective opacity into my effective opacity. + if ((myFlags & QGraphicsItem::ItemIgnoresParentOpacity) + || (parentFlags & QGraphicsItem::ItemDoesntPropagateOpacityToChildren)) { + break; + } + + o *= p->d_ptr->opacity; + p = p->d_ptr->parent; + myFlags = parentFlags; + } + return o; + } + + inline bool isOpacityNull() const + { return (opacity < qreal(0.001)); } + + static inline bool isOpacityNull(qreal opacity) + { return (opacity < qreal(0.001)); } + + inline bool isFullyTransparent() const + { + if (isOpacityNull()) + return true; + if (!parent) + return false; + + return isOpacityNull(calcEffectiveOpacity()); + } + + inline qreal effectiveOpacity() const { + if (!parent || !opacity) + return opacity; + + return calcEffectiveOpacity(); + } + + inline qreal combineOpacityFromParent(qreal parentOpacity) const + { + if (parent && !(flags & QGraphicsItem::ItemIgnoresParentOpacity) + && !(parent->d_ptr->flags & QGraphicsItem::ItemDoesntPropagateOpacityToChildren)) { + return parentOpacity * opacity; + } + return opacity; + } + + inline bool childrenCombineOpacity() const + { + if (!children.size()) + return true; + if (flags & QGraphicsItem::ItemDoesntPropagateOpacityToChildren) + return false; + + for (int i = 0; i < children.size(); ++i) { + if (children.at(i)->d_ptr->flags & QGraphicsItem::ItemIgnoresParentOpacity) + return false; + } + return true; + } + + inline bool childrenClippedToShape() const + { return (flags & QGraphicsItem::ItemClipsChildrenToShape) || children.isEmpty(); } + + inline bool isInvisible() const + { + return !visible || (childrenCombineOpacity() && isFullyTransparent()); + } + + inline void markParentDirty(bool updateBoundingRect = false); + + void setFocusHelper(Qt::FocusReason focusReason, bool climb, bool focusFromHide); + void clearFocusHelper(bool giveFocusToParent); + void setSubFocus(QGraphicsItem *rootItem = 0, QGraphicsItem *stopItem = 0); + void clearSubFocus(QGraphicsItem *rootItem = 0, QGraphicsItem *stopItem = 0); + void resetFocusProxy(); + virtual void subFocusItemChange(); + virtual void focusScopeItemChange(bool isSubFocusItem); + + static void children_append(QDeclarativeListProperty *list, QGraphicsObject *item); + static int children_count(QDeclarativeListProperty *list); + static QGraphicsObject *children_at(QDeclarativeListProperty *list, int); + static void children_clear(QDeclarativeListProperty *list); + + inline QTransform transformToParent() const; + inline void ensureSortedChildren(); + static inline bool insertionOrder(QGraphicsItem *a, QGraphicsItem *b); + void ensureSequentialSiblingIndex(); + inline void sendScenePosChange(); + virtual void siblingOrderChange(); + + // Private Properties + virtual qreal width() const; + virtual void setWidth(qreal); + virtual void resetWidth(); + + virtual qreal height() const; + virtual void setHeight(qreal); + virtual void resetHeight(); + + QRectF childrenBoundingRect; + QRectF needsRepaint; + QMap paintedViewBoundingRects; + QPointF pos; + qreal z; + qreal opacity; + QGraphicsScene *scene; + QGraphicsItem *parent; + QList children; + struct TransformData; + TransformData *transformData; + QGraphicsEffect *graphicsEffect; + QTransform sceneTransform; + int index; + int siblingIndex; + int itemDepth; // Lazily calculated when calling depth(). + QGraphicsItem *focusProxy; + QList focusProxyRefs; + QGraphicsItem *subFocusItem; + QGraphicsItem *focusScopeItem; + Qt::InputMethodHints imHints; + QGraphicsItem::PanelModality panelModality; +#ifndef QT_NO_GESTURES + QMap gestureContext; +#endif + + // Packed 32 bits + quint32 acceptedMouseButtons : 5; + quint32 visible : 1; + quint32 explicitlyHidden : 1; + quint32 enabled : 1; + quint32 explicitlyDisabled : 1; + quint32 selected : 1; + quint32 acceptsHover : 1; + quint32 acceptDrops : 1; + quint32 isMemberOfGroup : 1; + quint32 handlesChildEvents : 1; + quint32 itemDiscovered : 1; + quint32 hasCursor : 1; + quint32 ancestorFlags : 4; + quint32 cacheMode : 2; + quint32 hasBoundingRegionGranularity : 1; + quint32 isWidget : 1; + quint32 dirty : 1; + quint32 dirtyChildren : 1; + quint32 localCollisionHack : 1; + quint32 inSetPosHelper : 1; + quint32 needSortChildren : 1; + quint32 allChildrenDirty : 1; + quint32 fullUpdatePending : 1; + quint32 dirtyChildrenBoundingRect : 1; + + // Packed 32 bits + quint32 flags : 19; + quint32 paintedViewBoundingRectsNeedRepaint : 1; + quint32 dirtySceneTransform : 1; + quint32 geometryChanged : 1; + quint32 inDestructor : 1; + quint32 isObject : 1; + quint32 ignoreVisible : 1; + quint32 ignoreOpacity : 1; + quint32 acceptTouchEvents : 1; + quint32 acceptedTouchBeginEvent : 1; + quint32 filtersDescendantEvents : 1; + quint32 sceneTransformTranslateOnly : 1; + quint32 notifyBoundingRectChanged : 1; + quint32 notifyInvalidated : 1; + + // New 32 bits + quint32 mouseSetsFocus : 1; + quint32 explicitActivate : 1; + quint32 wantsActive : 1; + quint32 holesInSiblingIndex : 1; + quint32 sequentialOrdering : 1; + quint32 updateDueToGraphicsEffect : 1; + quint32 scenePosDescendants : 1; + quint32 pendingPolish : 1; + quint32 mayHaveChildWithGraphicsEffect : 1; + quint32 isDeclarativeItem : 1; + quint32 sendParentChangeNotification : 1; + quint32 padding : 21; + + // Optional stacking order + int globalStackingOrder; + QGraphicsItem *q_ptr; +}; + +struct QGraphicsItemPrivate::TransformData +{ + QTransform transform; + qreal scale; + qreal rotation; + qreal xOrigin; + qreal yOrigin; + QList graphicsTransforms; + bool onlyTransform; + + TransformData() : + scale(1.0), rotation(0.0), + xOrigin(0.0), yOrigin(0.0), + onlyTransform(true) + { } + + QTransform computedFullTransform(QTransform *postmultiplyTransform = 0) const + { + if (onlyTransform) { + if (!postmultiplyTransform || postmultiplyTransform->isIdentity()) + return transform; + if (transform.isIdentity()) + return *postmultiplyTransform; + return transform * *postmultiplyTransform; + } + + QTransform x(transform); + if (!graphicsTransforms.isEmpty()) { + QMatrix4x4 m; + for (int i = 0; i < graphicsTransforms.size(); ++i) + graphicsTransforms.at(i)->applyTo(&m); + x *= m.toTransform(); + } + x.translate(xOrigin, yOrigin); + x.rotate(rotation); + x.scale(scale, scale); + x.translate(-xOrigin, -yOrigin); + if (postmultiplyTransform) + x *= *postmultiplyTransform; + return x; + } +}; + +struct QGraphicsItemPaintInfo +{ + inline QGraphicsItemPaintInfo(const QTransform *const xform1, const QTransform *const xform2, + const QTransform *const xform3, + QRegion *r, QWidget *w, QStyleOptionGraphicsItem *opt, + QPainter *p, qreal o, bool b1, bool b2) + : viewTransform(xform1), transformPtr(xform2), effectTransform(xform3), exposedRegion(r), widget(w), + option(opt), painter(p), opacity(o), wasDirtySceneTransform(b1), drawItem(b2) + {} + + const QTransform *viewTransform; + const QTransform *transformPtr; + const QTransform *effectTransform; + QRegion *exposedRegion; + QWidget *widget; + QStyleOptionGraphicsItem *option; + QPainter *painter; + qreal opacity; + quint32 wasDirtySceneTransform : 1; + quint32 drawItem : 1; +}; + +#ifndef QT_NO_GRAPHICSEFFECT +class QGraphicsItemEffectSourcePrivate : public QGraphicsEffectSourcePrivate +{ +public: + QGraphicsItemEffectSourcePrivate(QGraphicsItem *i) + : QGraphicsEffectSourcePrivate(), item(i), info(0) + {} + + inline void detach() + { + item->d_ptr->graphicsEffect = 0; + item->prepareGeometryChange(); + } + + inline const QGraphicsItem *graphicsItem() const + { return item; } + + inline const QWidget *widget() const + { return 0; } + + inline void update() { + item->d_ptr->updateDueToGraphicsEffect = true; + item->update(); + item->d_ptr->updateDueToGraphicsEffect = false; + } + + inline void effectBoundingRectChanged() + { item->prepareGeometryChange(); } + + inline bool isPixmap() const + { + return item->type() == QGraphicsPixmapItem::Type + && !(item->flags() & QGraphicsItem::ItemIsSelectable) + && item->d_ptr->children.size() == 0; + //|| (item->d_ptr->isObject && qobject_cast(q_func())); + } + + inline const QStyleOption *styleOption() const + { return info ? info->option : 0; } + + inline QRect deviceRect() const + { + if (!info || !info->widget) { + qWarning("QGraphicsEffectSource::deviceRect: Not yet implemented, lacking device context"); + return QRect(); + } + return info->widget->rect(); + } + + QRectF boundingRect(Qt::CoordinateSystem system) const; + void draw(QPainter *); + QPixmap pixmap(Qt::CoordinateSystem system, + QPoint *offset, + QGraphicsEffect::PixmapPadMode mode) const; + QRect paddedEffectRect(Qt::CoordinateSystem system, QGraphicsEffect::PixmapPadMode mode, const QRectF &sourceRect, bool *unpadded = 0) const; + + QGraphicsItem *item; + QGraphicsItemPaintInfo *info; + QTransform lastEffectTransform; +}; +#endif //QT_NO_GRAPHICSEFFECT + +/*! + Returns true if \a item1 is on top of \a item2. + The items don't need to be siblings. + + \internal +*/ +inline bool qt_closestItemFirst(const QGraphicsItem *item1, const QGraphicsItem *item2) +{ + // Siblings? Just check their z-values. + const QGraphicsItemPrivate *d1 = item1->d_ptr.data(); + const QGraphicsItemPrivate *d2 = item2->d_ptr.data(); + if (d1->parent == d2->parent) + return qt_closestLeaf(item1, item2); + + // Find common ancestor, and each item's ancestor closest to the common + // ancestor. + int item1Depth = d1->depth(); + int item2Depth = d2->depth(); + const QGraphicsItem *p = item1; + const QGraphicsItem *t1 = item1; + while (item1Depth > item2Depth && (p = p->d_ptr->parent)) { + if (p == item2) { + // item2 is one of item1's ancestors; item1 is on top + return !(t1->d_ptr->flags & QGraphicsItem::ItemStacksBehindParent); + } + t1 = p; + --item1Depth; + } + p = item2; + const QGraphicsItem *t2 = item2; + while (item2Depth > item1Depth && (p = p->d_ptr->parent)) { + if (p == item1) { + // item1 is one of item2's ancestors; item1 is not on top + return (t2->d_ptr->flags & QGraphicsItem::ItemStacksBehindParent); + } + t2 = p; + --item2Depth; + } + + // item1Ancestor is now at the same level as item2Ancestor, but not the same. + const QGraphicsItem *p1 = t1; + const QGraphicsItem *p2 = t2; + while (t1 && t1 != t2) { + p1 = t1; + p2 = t2; + t1 = t1->d_ptr->parent; + t2 = t2->d_ptr->parent; + } + + // in case we have a common ancestor, we compare the immediate children in the ancestor's path. + // otherwise we compare the respective items' topLevelItems directly. + return qt_closestLeaf(p1, p2); +} + +/*! + Returns true if \a item2 is on top of \a item1. + The items don't need to be siblings. + + \internal +*/ +inline bool qt_closestItemLast(const QGraphicsItem *item1, const QGraphicsItem *item2) +{ + return qt_closestItemFirst(item2, item1); +} + +/*! + \internal +*/ +inline bool qt_closestLeaf(const QGraphicsItem *item1, const QGraphicsItem *item2) +{ + // Return true if sibling item1 is on top of item2. + const QGraphicsItemPrivate *d1 = item1->d_ptr.data(); + const QGraphicsItemPrivate *d2 = item2->d_ptr.data(); + bool f1 = d1->flags & QGraphicsItem::ItemStacksBehindParent; + bool f2 = d2->flags & QGraphicsItem::ItemStacksBehindParent; + if (f1 != f2) + return f2; + if (d1->z != d2->z) + return d1->z > d2->z; + return d1->siblingIndex > d2->siblingIndex; +} + +/*! + \internal +*/ +inline bool qt_notclosestLeaf(const QGraphicsItem *item1, const QGraphicsItem *item2) +{ return qt_closestLeaf(item2, item1); } + +/* + return the full transform of the item to the parent. This include the position and all the transform data +*/ +inline QTransform QGraphicsItemPrivate::transformToParent() const +{ + QTransform matrix; + combineTransformToParent(&matrix); + return matrix; +} + +/*! + \internal +*/ +inline void QGraphicsItemPrivate::ensureSortedChildren() +{ + if (needSortChildren) { + needSortChildren = 0; + sequentialOrdering = 1; + if (children.isEmpty()) + return; + qSort(children.begin(), children.end(), qt_notclosestLeaf); + for (int i = 0; i < children.size(); ++i) { + if (children.at(i)->d_ptr->siblingIndex != i) { + sequentialOrdering = 0; + break; + } + } + } +} + +/*! + \internal +*/ +inline bool QGraphicsItemPrivate::insertionOrder(QGraphicsItem *a, QGraphicsItem *b) +{ + return a->d_ptr->siblingIndex < b->d_ptr->siblingIndex; +} + +/*! + \internal +*/ +inline void QGraphicsItemPrivate::markParentDirty(bool updateBoundingRect) +{ + QGraphicsItemPrivate *parentp = this; +#ifndef QT_NO_GRAPHICSEFFECT + if (updateBoundingRect && parentp->graphicsEffect && !parentp->inSetPosHelper) { + parentp->notifyInvalidated = 1; + static_cast(parentp->graphicsEffect->d_func() + ->source->d_func())->invalidateCache(); + } +#endif + while (parentp->parent) { + parentp = parentp->parent->d_ptr.data(); + parentp->dirtyChildren = 1; + + if (updateBoundingRect) { + parentp->dirtyChildrenBoundingRect = 1; + // ### Only do this if the parent's effect applies to the entire subtree. + parentp->notifyBoundingRectChanged = 1; + } +#ifndef QT_NO_GRAPHICSEFFECT + if (parentp->graphicsEffect) { + if (updateBoundingRect) { + static_cast(parentp->graphicsEffect->d_func() + ->source->d_func())->invalidateCache(); + parentp->notifyInvalidated = 1; + } + if (parentp->scene && parentp->graphicsEffect->isEnabled()) { + parentp->dirty = 1; + parentp->fullUpdatePending = 1; + } + } +#endif + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW + +#endif diff --git a/src/widgets/graphicsview/qgraphicsitemanimation.cpp b/src/widgets/graphicsview/qgraphicsitemanimation.cpp new file mode 100644 index 0000000000..7eedc8dd3d --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsitemanimation.cpp @@ -0,0 +1,599 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +/*! + \class QGraphicsItemAnimation + \brief The QGraphicsItemAnimation class provides simple animation + support for QGraphicsItem. + \since 4.2 + \ingroup graphicsview-api + \deprecated + + The QGraphicsItemAnimation class animates a QGraphicsItem. You can + schedule changes to the item's transformation matrix at + specified steps. The QGraphicsItemAnimation class has a + current step value. When this value changes the transformations + scheduled at that step are performed. The current step of the + animation is set with the \c setStep() function. + + QGraphicsItemAnimation will do a simple linear interpolation + between the nearest adjacent scheduled changes to calculate the + matrix. For instance, if you set the position of an item at values + 0.0 and 1.0, the animation will show the item moving in a straight + line between these positions. The same is true for scaling and + rotation. + + It is usual to use the class with a QTimeLine. The timeline's + \l{QTimeLine::}{valueChanged()} signal is then connected to the + \c setStep() slot. For example, you can set up an item for rotation + by calling \c setRotationAt() for different step values. + The animations timeline is set with the setTimeLine() function. + + An example animation with a timeline follows: + + \snippet doc/src/snippets/timeline/main.cpp 0 + + Note that steps lie between 0.0 and 1.0. It may be necessary to use + \l{QTimeLine::}{setUpdateInterval()}. The default update interval + is 40 ms. A scheduled transformation cannot be removed when set, + so scheduling several transformations of the same kind (e.g., + rotations) at the same step is not recommended. + + \sa QTimeLine, {Graphics View Framework} +*/ + +#include "qgraphicsitemanimation.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicsitem.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsItemAnimationPrivate +{ +public: + inline QGraphicsItemAnimationPrivate() + : q(0), timeLine(0), item(0), step(0) + { } + + QGraphicsItemAnimation *q; + + QPointer timeLine; + QGraphicsItem *item; + + QPointF startPos; + QMatrix startMatrix; + + qreal step; + + struct Pair { + Pair(qreal a, qreal b) : step(a), value(b) {} + bool operator <(const Pair &other) const + { return step < other.step; } + bool operator==(const Pair &other) const + { return step == other.step; } + qreal step; + qreal value; + }; + QList xPosition; + QList yPosition; + QList rotation; + QList verticalScale; + QList horizontalScale; + QList verticalShear; + QList horizontalShear; + QList xTranslation; + QList yTranslation; + + qreal linearValueForStep(qreal step, QList *source, qreal defaultValue = 0); + void insertUniquePair(qreal step, qreal value, QList *binList, const char* method); +}; + +qreal QGraphicsItemAnimationPrivate::linearValueForStep(qreal step, QList *source, qreal defaultValue) +{ + if (source->isEmpty()) + return defaultValue; + step = qMin(qMax(step, 0), 1); + + if (step == 1) + return source->last().value; + + qreal stepBefore = 0; + qreal stepAfter = 1; + qreal valueBefore = source->first().step == 0 ? source->first().value : defaultValue; + qreal valueAfter = source->last().value; + + // Find the closest step and value before the given step. + for (int i = 0; i < source->size() && step >= source->at(i).step; ++i) { + stepBefore = source->at(i).step; + valueBefore = source->at(i).value; + } + + // Find the closest step and value after the given step. + for (int j = source->size() - 1; j >= 0 && step < source->at(j).step; --j) { + stepAfter = source->at(j).step; + valueAfter = source->at(j).value; + } + + // Do a simple linear interpolation. + return valueBefore + (valueAfter - valueBefore) * ((step - stepBefore) / (stepAfter - stepBefore)); +} + +void QGraphicsItemAnimationPrivate::insertUniquePair(qreal step, qreal value, QList *binList, const char* method) +{ + if (step < 0.0 || step > 1.0) { + qWarning("QGraphicsItemAnimation::%s: invalid step = %f", method, step); + return; + } + + Pair pair(step, value); + + QList::iterator result = qBinaryFind(binList->begin(), binList->end(), pair); + if (result != binList->end()) + result->value = value; + else { + *binList << pair; + qSort(binList->begin(), binList->end()); + } +} + +/*! + Constructs an animation object with the given \a parent. +*/ +QGraphicsItemAnimation::QGraphicsItemAnimation(QObject *parent) + : QObject(parent), d(new QGraphicsItemAnimationPrivate) +{ + d->q = this; +} + +/*! + Destroys the animation object. +*/ +QGraphicsItemAnimation::~QGraphicsItemAnimation() +{ + delete d; +} + +/*! + Returns the item on which the animation object operates. + + \sa setItem() +*/ +QGraphicsItem *QGraphicsItemAnimation::item() const +{ + return d->item; +} + +/*! + Sets the specified \a item to be used in the animation. + + \sa item() +*/ +void QGraphicsItemAnimation::setItem(QGraphicsItem *item) +{ + d->item = item; + d->startPos = d->item->pos(); +} + +/*! + Returns the timeline object used to control the rate at which the animation + occurs. + + \sa setTimeLine() +*/ +QTimeLine *QGraphicsItemAnimation::timeLine() const +{ + return d->timeLine; +} + +/*! + Sets the timeline object used to control the rate of animation to the \a timeLine + specified. + + \sa timeLine() +*/ +void QGraphicsItemAnimation::setTimeLine(QTimeLine *timeLine) +{ + if (d->timeLine == timeLine) + return; + if (d->timeLine) + delete d->timeLine; + if (!timeLine) + return; + d->timeLine = timeLine; + connect(timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(setStep(qreal))); +} + +/*! + Returns the position of the item at the given \a step value. + + \sa setPosAt() +*/ +QPointF QGraphicsItemAnimation::posAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::posAt: invalid step = %f", step); + + return QPointF(d->linearValueForStep(step, &d->xPosition, d->startPos.x()), + d->linearValueForStep(step, &d->yPosition, d->startPos.y())); +} + +/*! + \fn void QGraphicsItemAnimation::setPosAt(qreal step, const QPointF &point) + + Sets the position of the item at the given \a step value to the \a point specified. + + \sa posAt() +*/ +void QGraphicsItemAnimation::setPosAt(qreal step, const QPointF &pos) +{ + d->insertUniquePair(step, pos.x(), &d->xPosition, "setPosAt"); + d->insertUniquePair(step, pos.y(), &d->yPosition, "setPosAt"); +} + +/*! + Returns all explicitly inserted positions. + + \sa posAt(), setPosAt() +*/ +QList > QGraphicsItemAnimation::posList() const +{ + QList > list; + for (int i = 0; i < d->xPosition.size(); ++i) + list << QPair(d->xPosition.at(i).step, QPointF(d->xPosition.at(i).value, d->yPosition.at(i).value)); + + return list; +} + +/*! + Returns the matrix used to transform the item at the specified \a step value. +*/ +QMatrix QGraphicsItemAnimation::matrixAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::matrixAt: invalid step = %f", step); + + QMatrix matrix; + if (!d->rotation.isEmpty()) + matrix.rotate(rotationAt(step)); + if (!d->verticalScale.isEmpty()) + matrix.scale(horizontalScaleAt(step), verticalScaleAt(step)); + if (!d->verticalShear.isEmpty()) + matrix.shear(horizontalShearAt(step), verticalShearAt(step)); + if (!d->xTranslation.isEmpty()) + matrix.translate(xTranslationAt(step), yTranslationAt(step)); + return matrix; +} + +/*! + Returns the angle at which the item is rotated at the specified \a step value. + + \sa setRotationAt() +*/ +qreal QGraphicsItemAnimation::rotationAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::rotationAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->rotation); +} + +/*! + Sets the rotation of the item at the given \a step value to the \a angle specified. + + \sa rotationAt() +*/ +void QGraphicsItemAnimation::setRotationAt(qreal step, qreal angle) +{ + d->insertUniquePair(step, angle, &d->rotation, "setRotationAt"); +} + +/*! + Returns all explicitly inserted rotations. + + \sa rotationAt(), setRotationAt() +*/ +QList > QGraphicsItemAnimation::rotationList() const +{ + QList > list; + for (int i = 0; i < d->rotation.size(); ++i) + list << QPair(d->rotation.at(i).step, d->rotation.at(i).value); + + return list; +} + +/*! + Returns the horizontal translation of the item at the specified \a step value. + + \sa setTranslationAt() +*/ +qreal QGraphicsItemAnimation::xTranslationAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::xTranslationAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->xTranslation); +} + +/*! + Returns the vertical translation of the item at the specified \a step value. + + \sa setTranslationAt() +*/ +qreal QGraphicsItemAnimation::yTranslationAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::yTranslationAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->yTranslation); +} + +/*! + Sets the translation of the item at the given \a step value using the horizontal + and vertical coordinates specified by \a dx and \a dy. + + \sa xTranslationAt(), yTranslationAt() +*/ +void QGraphicsItemAnimation::setTranslationAt(qreal step, qreal dx, qreal dy) +{ + d->insertUniquePair(step, dx, &d->xTranslation, "setTranslationAt"); + d->insertUniquePair(step, dy, &d->yTranslation, "setTranslationAt"); +} + +/*! + Returns all explicitly inserted translations. + + \sa xTranslationAt(), yTranslationAt(), setTranslationAt() +*/ +QList > QGraphicsItemAnimation::translationList() const +{ + QList > list; + for (int i = 0; i < d->xTranslation.size(); ++i) + list << QPair(d->xTranslation.at(i).step, QPointF(d->xTranslation.at(i).value, d->yTranslation.at(i).value)); + + return list; +} + +/*! + Returns the vertical scale for the item at the specified \a step value. + + \sa setScaleAt() +*/ +qreal QGraphicsItemAnimation::verticalScaleAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::verticalScaleAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->verticalScale, 1); +} + +/*! + Returns the horizontal scale for the item at the specified \a step value. + + \sa setScaleAt() +*/ +qreal QGraphicsItemAnimation::horizontalScaleAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::horizontalScaleAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->horizontalScale, 1); +} + +/*! + Sets the scale of the item at the given \a step value using the horizontal and + vertical scale factors specified by \a sx and \a sy. + + \sa verticalScaleAt(), horizontalScaleAt() +*/ +void QGraphicsItemAnimation::setScaleAt(qreal step, qreal sx, qreal sy) +{ + d->insertUniquePair(step, sx, &d->horizontalScale, "setScaleAt"); + d->insertUniquePair(step, sy, &d->verticalScale, "setScaleAt"); +} + +/*! + Returns all explicitly inserted scales. + + \sa verticalScaleAt(), horizontalScaleAt(), setScaleAt() +*/ +QList > QGraphicsItemAnimation::scaleList() const +{ + QList > list; + for (int i = 0; i < d->horizontalScale.size(); ++i) + list << QPair(d->horizontalScale.at(i).step, QPointF(d->horizontalScale.at(i).value, d->verticalScale.at(i).value)); + + return list; +} + +/*! + Returns the vertical shear for the item at the specified \a step value. + + \sa setShearAt() +*/ +qreal QGraphicsItemAnimation::verticalShearAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::verticalShearAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->verticalShear, 0); +} + +/*! + Returns the horizontal shear for the item at the specified \a step value. + + \sa setShearAt() +*/ +qreal QGraphicsItemAnimation::horizontalShearAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::horizontalShearAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->horizontalShear, 0); +} + +/*! + Sets the shear of the item at the given \a step value using the horizontal and + vertical shear factors specified by \a sh and \a sv. + + \sa verticalShearAt(), horizontalShearAt() +*/ +void QGraphicsItemAnimation::setShearAt(qreal step, qreal sh, qreal sv) +{ + d->insertUniquePair(step, sh, &d->horizontalShear, "setShearAt"); + d->insertUniquePair(step, sv, &d->verticalShear, "setShearAt"); +} + +/*! + Returns all explicitly inserted shears. + + \sa verticalShearAt(), horizontalShearAt(), setShearAt() +*/ +QList > QGraphicsItemAnimation::shearList() const +{ + QList > list; + for (int i = 0; i < d->horizontalShear.size(); ++i) + list << QPair(d->horizontalShear.at(i).step, QPointF(d->horizontalShear.at(i).value, d->verticalShear.at(i).value)); + + return list; +} + +/*! + Clears the scheduled transformations used for the animation, but + retains the item and timeline. +*/ +void QGraphicsItemAnimation::clear() +{ + d->xPosition.clear(); + d->yPosition.clear(); + d->rotation.clear(); + d->verticalScale.clear(); + d->horizontalScale.clear(); + d->verticalShear.clear(); + d->horizontalShear.clear(); + d->xTranslation.clear(); + d->yTranslation.clear(); +} + +/*! + \fn void QGraphicsItemAnimation::setStep(qreal step) + + Sets the current \a step value for the animation, causing the + transformations scheduled at this step to be performed. +*/ +void QGraphicsItemAnimation::setStep(qreal x) +{ + if (x < 0.0 || x > 1.0) { + qWarning("QGraphicsItemAnimation::setStep: invalid step = %f", x); + return; + } + + beforeAnimationStep(x); + + d->step = x; + if (d->item) { + if (!d->xPosition.isEmpty() || !d->yPosition.isEmpty()) + d->item->setPos(posAt(x)); + if (!d->rotation.isEmpty() + || !d->verticalScale.isEmpty() + || !d->horizontalScale.isEmpty() + || !d->verticalShear.isEmpty() + || !d->horizontalShear.isEmpty() + || !d->xTranslation.isEmpty() + || !d->yTranslation.isEmpty()) { + d->item->setMatrix(d->startMatrix * matrixAt(x)); + } + } + + afterAnimationStep(x); +} + +/*! + Resets the item to its starting position and transformation. + + \obsolete + + You can call setStep(0) instead. +*/ +void QGraphicsItemAnimation::reset() +{ + if (!d->item) + return; + d->startPos = d->item->pos(); + d->startMatrix = d->item->matrix(); +} + +/*! + \fn void QGraphicsItemAnimation::beforeAnimationStep(qreal step) + + This method is meant to be overridden by subclassed that needs to + execute additional code before a new step takes place. The + animation \a step is provided for use in cases where the action + depends on its value. +*/ +void QGraphicsItemAnimation::beforeAnimationStep(qreal step) +{ + Q_UNUSED(step); +} + +/*! + \fn void QGraphicsItemAnimation::afterAnimationStep(qreal step) + + This method is meant to be overridden in subclasses that need to + execute additional code after a new step has taken place. The + animation \a step is provided for use in cases where the action + depends on its value. +*/ +void QGraphicsItemAnimation::afterAnimationStep(qreal step) +{ + Q_UNUSED(step); +} + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicsitemanimation.h b/src/widgets/graphicsview/qgraphicsitemanimation.h new file mode 100644 index 0000000000..5dfbf4b81e --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsitemanimation.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSITEMANIMATION_H +#define QGRAPHICSITEMANIMATION_H + +#include + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QGraphicsItem; +class QMatrix; +class QPointF; +class QTimeLine; +template struct QPair; + +class QGraphicsItemAnimationPrivate; +class Q_GUI_EXPORT QGraphicsItemAnimation : public QObject +{ + Q_OBJECT +public: + QGraphicsItemAnimation(QObject *parent = 0); + virtual ~QGraphicsItemAnimation(); + + QGraphicsItem *item() const; + void setItem(QGraphicsItem *item); + + QTimeLine *timeLine() const; + void setTimeLine(QTimeLine *timeLine); + + QPointF posAt(qreal step) const; + QList > posList() const; + void setPosAt(qreal step, const QPointF &pos); + + QMatrix matrixAt(qreal step) const; + + qreal rotationAt(qreal step) const; + QList > rotationList() const; + void setRotationAt(qreal step, qreal angle); + + qreal xTranslationAt(qreal step) const; + qreal yTranslationAt(qreal step) const; + QList > translationList() const; + void setTranslationAt(qreal step, qreal dx, qreal dy); + + qreal verticalScaleAt(qreal step) const; + qreal horizontalScaleAt(qreal step) const; + QList > scaleList() const; + void setScaleAt(qreal step, qreal sx, qreal sy); + + qreal verticalShearAt(qreal step) const; + qreal horizontalShearAt(qreal step) const; + QList > shearList() const; + void setShearAt(qreal step, qreal sh, qreal sv); + + void clear(); + +public Q_SLOTS: + void setStep(qreal x); + void reset(); + +protected: + virtual void beforeAnimationStep(qreal step); + virtual void afterAnimationStep(qreal step); + +private: + Q_DISABLE_COPY(QGraphicsItemAnimation) + QGraphicsItemAnimationPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_GRAPHICSVIEW +#endif diff --git a/src/widgets/graphicsview/qgraphicslayout.cpp b/src/widgets/graphicsview/qgraphicslayout.cpp new file mode 100644 index 0000000000..904a3deda2 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicslayout.cpp @@ -0,0 +1,451 @@ +/**************************************************************************** +** +** 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 QtGui module 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 "qapplication.h" + +#ifndef QT_NO_GRAPHICSVIEW +#include "qgraphicslayout.h" +#include "qgraphicslayout_p.h" +#include "qgraphicslayoutitem.h" +#include "qgraphicslayoutitem_p.h" +#include "qgraphicswidget.h" +#include "qgraphicswidget_p.h" +#include "qgraphicsscene.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGraphicsLayout + \brief The QGraphicsLayout class provides the base class for all layouts + in Graphics View. + \since 4.4 + \ingroup graphicsview-api + + QGraphicsLayout is an abstract class that defines a virtual API for + arranging QGraphicsWidget children and other QGraphicsLayoutItem objects + for a QGraphicsWidget. QGraphicsWidget assigns responsibility to a + QGraphicsLayout through QGraphicsWidget::setLayout(). As the widget + is resized, the layout will automatically arrange the widget's children. + QGraphicsLayout inherits QGraphicsLayoutItem, so, it can be managed by + any layout, including its own subclasses. + + \section1 Writing a Custom Layout + + You can use QGraphicsLayout as a base to write your own custom layout + (e.g., a flowlayout), but it is more common to use one of its subclasses + instead - QGraphicsLinearLayout or QGraphicsGridLayout. When creating + a custom layout, the following functions must be reimplemented as a bare + minimum: + + \table + \header \o Function \o Description + \row \o QGraphicsLayoutItem::setGeometry() + \o Notifies you when the geometry of the layout is set. You can + store the geometry in your own layout class in a reimplementation + of this function. + \row \o QGraphicsLayoutItem::sizeHint() + \o Returns the layout's size hints. + \row \o QGraphicsLayout::count() + \o Returns the number of items in your layout. + \row \o QGraphicsLayout::itemAt() + \o Returns a pointer to an item in your layout. + \row \o QGraphicsLayout::removeAt() + \o Removes an item from your layout without destroying it. + \endtable + + For more details on how to implement each function, refer to the individual + function documentation. + + Each layout defines its own API for arranging widgets and layout items. + For example, with a grid layout, you require a row and a + column index with optional row and column spans, alignment, spacing, and more. + A linear layout, however, requires a single row or column index to position its + items. For a grid layout, the order of insertion does not affect the layout in + any way, but for a linear layout, the order is essential. When writing your own + layout subclass, you are free to choose the API that best suits your layout. + + \section1 Activating the Layout + + When the layout's geometry changes, QGraphicsLayout immediately rearranges + all of its managed items by calling setGeometry() on each item. This + rearrangement is called \e activating the layout. + + QGraphicsLayout updates its own geometry to match the contentsRect() of the + QGraphicsLayoutItem it is managing. Thus, it will automatically rearrange all + its items when the widget is resized. QGraphicsLayout caches the sizes of all + its managed items to avoid calling setGeometry() too often. + + \note A QGraphicsLayout will have the same geometry as the contentsRect() + of the widget (not the layout) it is assigned to. + + \section2 Activating the Layout Implicitly + + The layout can be activated implicitly using one of two ways: by calling + activate() or by calling invalidate(). Calling activate() activates the layout + immediately. In contrast, calling invalidate() is delayed, as it posts a + \l{QEvent::LayoutRequest}{LayoutRequest} event to the managed widget. Due + to event compression, the activate() will only be called once after control has + returned to the event loop. This is referred to as \e invalidating the layout. + Invalidating the layout also invalidates any cached information. Also, the + invalidate() function is a virtual function. So, you can invalidate your own + cache in a subclass of QGraphicsLayout by reimplementing this function. + + \section1 Event Handling + + QGraphicsLayout listens to events for the widget it manages through the + virtual widgetEvent() event handler. When the layout is assigned to a + widget, all events delivered to the widget are first processed by + widgetEvent(). This allows the layout to be aware of any relevant state + changes on the widget such as visibility changes or layout direction changes. + + \section1 Margin Handling + + The margins of a QGraphicsLayout can be modified by reimplementing + setContentsMargins() and getContentsMargins(). + +*/ + +/*! + Contructs a QGraphicsLayout object. + + \a parent is passed to QGraphicsLayoutItem's constructor and the + QGraphicsLayoutItem's isLayout argument is set to \e true. + + If \a parent is a QGraphicsWidget the layout will be installed + on that widget. (Note that installing a layout will delete the old one + installed.) +*/ +QGraphicsLayout::QGraphicsLayout(QGraphicsLayoutItem *parent) + : QGraphicsLayoutItem(*new QGraphicsLayoutPrivate) +{ + setParentLayoutItem(parent); + if (parent && !parent->isLayout()) { + // If a layout has a parent that is not a layout it must be a QGraphicsWidget. + QGraphicsItem *itemParent = parent->graphicsItem(); + if (itemParent && itemParent->isWidget()) { + static_cast(itemParent)->d_func()->setLayout_helper(this); + } else { + qWarning("QGraphicsLayout::QGraphicsLayout: Attempt to create a layout with a parent that is" + " neither a QGraphicsWidget nor QGraphicsLayout"); + } + } + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, QSizePolicy::DefaultType); + setOwnedByLayout(true); +} + +/*! + \internal +*/ +QGraphicsLayout::QGraphicsLayout(QGraphicsLayoutPrivate &dd, QGraphicsLayoutItem *parent) + : QGraphicsLayoutItem(dd) +{ + setParentLayoutItem(parent); + if (parent && !parent->isLayout()) { + // If a layout has a parent that is not a layout it must be a QGraphicsWidget. + QGraphicsItem *itemParent = parent->graphicsItem(); + if (itemParent && itemParent->isWidget()) { + static_cast(itemParent)->d_func()->setLayout_helper(this); + } else { + qWarning("QGraphicsLayout::QGraphicsLayout: Attempt to create a layout with a parent that is" + " neither a QGraphicsWidget nor QGraphicsLayout"); + } + } + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, QSizePolicy::DefaultType); + setOwnedByLayout(true); +} + +/*! + Destroys the QGraphicsLayout object. +*/ +QGraphicsLayout::~QGraphicsLayout() +{ +} + +/*! + Sets the contents margins to \a left, \a top, \a right and \a bottom. The + default contents margins for toplevel layouts are style dependent + (by querying the pixelMetric for QStyle::PM_LayoutLeftMargin, + QStyle::PM_LayoutTopMargin, QStyle::PM_LayoutRightMargin and + QStyle::PM_LayoutBottomMargin). + + For sublayouts the default margins are 0. + + Changing the contents margins automatically invalidates the layout. + + \sa invalidate() +*/ +void QGraphicsLayout::setContentsMargins(qreal left, qreal top, qreal right, qreal bottom) +{ + Q_D(QGraphicsLayout); + if (d->left == left && d->top == top && d->right == right && d->bottom == bottom) + return; + d->left = left; + d->right = right; + d->top = top; + d->bottom = bottom; + invalidate(); +} + +/*! + \reimp +*/ +void QGraphicsLayout::getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const +{ + Q_D(const QGraphicsLayout); + d->getMargin(left, d->left, QStyle::PM_LayoutLeftMargin); + d->getMargin(top, d->top, QStyle::PM_LayoutTopMargin); + d->getMargin(right, d->right, QStyle::PM_LayoutRightMargin); + d->getMargin(bottom, d->bottom, QStyle::PM_LayoutBottomMargin); +} + +/*! + Activates the layout, causing all items in the layout to be immediately + rearranged. This function is based on calling count() and itemAt(), and + then calling setGeometry() on all items sequentially. When activated, + the layout will adjust its geometry to its parent's contentsRect(). + The parent will then invalidate any layout of its own. + + If called in sequence or recursively, e.g., by one of the arranged items + in response to being resized, this function will do nothing. + + Note that the layout is free to use geometry caching to optimize this + process. To forcefully invalidate any such cache, you can call + invalidate() before calling activate(). + + \sa invalidate() +*/ +void QGraphicsLayout::activate() +{ + Q_D(QGraphicsLayout); + if (d->activated) + return; + + d->activateRecursive(this); + + // we don't call activate on a sublayout, but somebody might. + // Therefore, we walk to the parentitem of the toplevel layout. + QGraphicsLayoutItem *parentItem = this; + while (parentItem && parentItem->isLayout()) + parentItem = parentItem->parentLayoutItem(); + if (!parentItem) + return; + Q_ASSERT(!parentItem->isLayout()); + + setGeometry(parentItem->contentsRect()); // relayout children + + // ### bug, should be parentItem ? + parentLayoutItem()->updateGeometry(); // bubble up; will set activated to false + // ### too many resizes? maybe we should walk up the chain to the + // ### top-level layouted layoutItem and call activate there. +} + +/*! + Returns true if the layout is currently being activated; otherwise, + returns false. If the layout is being activated, this means that it is + currently in the process of rearranging its items (i.e., the activate() + function has been called, and has not yet returned). + + \sa activate(), invalidate() +*/ +bool QGraphicsLayout::isActivated() const +{ + Q_D(const QGraphicsLayout); + return d->activated; +} + +/*! + Clears any cached geometry and size hint information in the layout, and + posts a \l{QEvent::LayoutRequest}{LayoutRequest} event to the managed + parent QGraphicsLayoutItem. + + \sa activate(), setGeometry() +*/ +void QGraphicsLayout::invalidate() +{ + // only mark layouts as invalid (activated = false) if we can post a LayoutRequest event. + QGraphicsLayoutItem *layoutItem = this; + while (layoutItem && layoutItem->isLayout()) { + // we could call updateGeometry(), but what if that method + // does not call the base implementation? In addition, updateGeometry() + // does more than we need. + layoutItem->d_func()->sizeHintCacheDirty = true; + layoutItem->d_func()->sizeHintWithConstraintCacheDirty = true; + layoutItem = layoutItem->parentLayoutItem(); + } + if (layoutItem) { + layoutItem->d_func()->sizeHintCacheDirty = true; + layoutItem->d_func()->sizeHintWithConstraintCacheDirty = true; + } + + bool postIt = layoutItem ? !layoutItem->isLayout() : false; + if (postIt) { + layoutItem = this; + while (layoutItem && layoutItem->isLayout() + && static_cast(layoutItem)->d_func()->activated) { + static_cast(layoutItem)->d_func()->activated = false; + layoutItem = layoutItem->parentLayoutItem(); + } + if (layoutItem && !layoutItem->isLayout()) { + // If a layout has a parent that is not a layout it must be a QGraphicsWidget. + QApplication::postEvent(static_cast(layoutItem), new QEvent(QEvent::LayoutRequest)); + } + } +} + +/*! + \reimp +*/ +void QGraphicsLayout::updateGeometry() +{ + QGraphicsLayoutItem::updateGeometry(); + if (QGraphicsLayoutItem *parentItem = parentLayoutItem()) { + if (parentItem->isLayout()) { + parentItem->updateGeometry(); + } else { + invalidate(); + } + } +} + +/*! + This virtual event handler receives all events for the managed + widget. QGraphicsLayout uses this event handler to listen for layout + related events such as geometry changes, layout changes or layout + direction changes. + + \a e is a pointer to the event. + + You can reimplement this event handler to track similar events for your + own custom layout. + + \sa QGraphicsWidget::event(), QGraphicsItem::sceneEvent() +*/ +void QGraphicsLayout::widgetEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::GraphicsSceneResize: + if (isActivated()) { + setGeometry(parentLayoutItem()->contentsRect()); + } else { + activate(); // relies on that activate() will call updateGeometry() + } + break; + case QEvent::LayoutRequest: + activate(); + break; + case QEvent::LayoutDirectionChange: + invalidate(); + break; + default: + break; + } +} + +/*! + \fn virtual int QGraphicsLayout::count() const = 0 + + This pure virtual function must be reimplemented in a subclass of + QGraphicsLayout to return the number of items in the layout. + + The subclass is free to decide how to store the items. + + \sa itemAt(), removeAt() +*/ + +/*! + \fn virtual QGraphicsLayoutItem *QGraphicsLayout::itemAt(int i) const = 0 + + This pure virtual function must be reimplemented in a subclass of + QGraphicsLayout to return a pointer to the item at index \a i. The + reimplementation can assume that \a i is valid (i.e., it respects the + value of count()). + Together with count(), it is provided as a means of iterating over all items in a layout. + + The subclass is free to decide how to store the items, and the visual arrangement + does not have to be reflected through this function. + + \sa count(), removeAt() +*/ + +/*! + \fn virtual void QGraphicsLayout::removeAt(int index) = 0 + + This pure virtual function must be reimplemented in a subclass of + QGraphicsLayout to remove the item at \a index. The + reimplementation can assume that \a index is valid (i.e., it + respects the value of count()). + + The implementation must ensure that the parentLayoutItem() of + the removed item does not point to this layout, since the item is + considered to be removed from the layout hierarchy. + + If the layout is to be reused between applications, we recommend + that the layout deletes the item, but the graphics view framework + does not depend on this. + + The subclass is free to decide how to store the items. + + \sa itemAt(), count() +*/ + +/*! + \since 4.6 + + This function is a convenience function provided for custom layouts, and will go through + all items in the layout and reparent their graphics items to the closest QGraphicsWidget + ancestor of the layout. + + If \a layoutItem is already in a different layout, it will be removed from that layout. + + If custom layouts want special behaviour they can ignore to use this function, and implement + their own behaviour. + + \sa graphicsItem() + */ +void QGraphicsLayout::addChildLayoutItem(QGraphicsLayoutItem *layoutItem) +{ + Q_D(QGraphicsLayout); + d->addChildLayoutItem(layoutItem); +} + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicslayout.h b/src/widgets/graphicsview/qgraphicslayout.h new file mode 100644 index 0000000000..c622fb881e --- /dev/null +++ b/src/widgets/graphicsview/qgraphicslayout.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSLAYOUT_H +#define QGRAPHICSLAYOUT_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsLayoutPrivate; +class QGraphicsLayoutItem; +class QGraphicsWidget; + +class Q_GUI_EXPORT QGraphicsLayout : public QGraphicsLayoutItem +{ +public: + QGraphicsLayout(QGraphicsLayoutItem *parent = 0); + ~QGraphicsLayout(); + + void setContentsMargins(qreal left, qreal top, qreal right, qreal bottom); + void getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const; + + void activate(); + bool isActivated() const; + virtual void invalidate(); + virtual void updateGeometry(); + + virtual void widgetEvent(QEvent *e); + + virtual int count() const = 0; + virtual QGraphicsLayoutItem *itemAt(int i) const = 0; + virtual void removeAt(int index) = 0; + +protected: + QGraphicsLayout(QGraphicsLayoutPrivate &, QGraphicsLayoutItem *); + void addChildLayoutItem(QGraphicsLayoutItem *layoutItem); + +private: + Q_DISABLE_COPY(QGraphicsLayout) + Q_DECLARE_PRIVATE(QGraphicsLayout) + friend class QGraphicsWidget; +}; + +Q_DECLARE_INTERFACE(QGraphicsLayout, "com.trolltech.Qt.QGraphicsLayout") + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif + diff --git a/src/widgets/graphicsview/qgraphicslayout_p.cpp b/src/widgets/graphicsview/qgraphicslayout_p.cpp new file mode 100644 index 0000000000..c325602cc6 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicslayout_p.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** 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 QtGui module 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 "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicslayout_p.h" +#include "qgraphicslayout.h" +#include "qgraphicswidget.h" +#include "qapplication.h" + +QT_BEGIN_NAMESPACE + +/*! + \internal + + \a mw is the new parent. all items in the layout will be a child of \a mw. + */ +void QGraphicsLayoutPrivate::reparentChildItems(QGraphicsItem *newParent) +{ + Q_Q(QGraphicsLayout); + int n = q->count(); + //bool mwVisible = mw && mw->isVisible(); + for (int i = 0; i < n; ++i) { + QGraphicsLayoutItem *layoutChild = q->itemAt(i); + if (!layoutChild) { + // Skip stretch items + continue; + } + if (layoutChild->isLayout()) { + QGraphicsLayout *l = static_cast(layoutChild); + l->d_func()->reparentChildItems(newParent); + } else if (QGraphicsItem *itemChild = layoutChild->graphicsItem()){ + QGraphicsItem *childParent = itemChild->parentItem(); +#ifdef QT_DEBUG + if (childParent && childParent != newParent && itemChild->isWidget() && qt_graphicsLayoutDebug()) { + QGraphicsWidget *w = static_cast(layoutChild); + qWarning("QGraphicsLayout::addChildLayout: widget %s \"%s\" in wrong parent; moved to correct parent", + w->metaObject()->className(), w->objectName().toLocal8Bit().constData()); + } +#endif + if (childParent != newParent) + itemChild->setParentItem(newParent); + } + } +} + +void QGraphicsLayoutPrivate::getMargin(qreal *result, qreal userMargin, QStyle::PixelMetric pm) const +{ + if (!result) + return; + Q_Q(const QGraphicsLayout); + + QGraphicsLayoutItem *parent = q->parentLayoutItem(); + if (userMargin >= 0.0) { + *result = userMargin; + } else if (!parent) { + *result = 0.0; + } else if (parent->isLayout()) { // sublayouts have 0 margin by default + *result = 0.0; + } else { + *result = 0.0; + if (QGraphicsItem *layoutParentItem = parentItem()) { + if (layoutParentItem->isWidget()) + *result = (qreal)static_cast(layoutParentItem)->style()->pixelMetric(pm, 0); + } + } +} + +Qt::LayoutDirection QGraphicsLayoutPrivate::visualDirection() const +{ + if (QGraphicsItem *maybeWidget = parentItem()) { + if (maybeWidget->isWidget()) + return static_cast(maybeWidget)->layoutDirection(); + } + return QApplication::layoutDirection(); +} + +static bool removeLayoutItemFromLayout(QGraphicsLayout *lay, QGraphicsLayoutItem *layoutItem) +{ + if (!lay) + return false; + + for (int i = lay->count() - 1; i >= 0; --i) { + QGraphicsLayoutItem *child = lay->itemAt(i); + if (child && child->isLayout()) { + if (removeLayoutItemFromLayout(static_cast(child), layoutItem)) + return true; + } else if (child == layoutItem) { + lay->removeAt(i); + return true; + } + } + return false; +} + +/*! + \internal + + This function is called from subclasses to add a layout item \a layoutItem + to a layout. + + It takes care of automatically reparenting graphics items, if needed. + + If \a layoutItem is a is already in a layout, it will remove it from that layout. + +*/ +void QGraphicsLayoutPrivate::addChildLayoutItem(QGraphicsLayoutItem *layoutItem) +{ + Q_Q(QGraphicsLayout); + if (QGraphicsLayoutItem *maybeLayout = layoutItem->parentLayoutItem()) { + if (maybeLayout->isLayout()) + removeLayoutItemFromLayout(static_cast(maybeLayout), layoutItem); + } + layoutItem->setParentLayoutItem(q); + if (layoutItem->isLayout()) { + if (QGraphicsItem *parItem = parentItem()) { + static_cast(layoutItem)->d_func()->reparentChildItems(parItem); + } + } else { + if (QGraphicsItem *item = layoutItem->graphicsItem()) { + QGraphicsItem *newParent = parentItem(); + QGraphicsItem *oldParent = item->parentItem(); + if (oldParent == newParent || !newParent) + return; + +#ifdef QT_DEBUG + if (oldParent && item->isWidget()) { + QGraphicsWidget *w = static_cast(item); + qWarning("QGraphicsLayout::addChildLayoutItem: %s \"%s\" in wrong parent; moved to correct parent", + w->metaObject()->className(), w->objectName().toLocal8Bit().constData()); + } +#endif + + item->setParentItem(newParent); + } + } +} + +void QGraphicsLayoutPrivate::activateRecursive(QGraphicsLayoutItem *item) +{ + if (item->isLayout()) { + QGraphicsLayout *layout = static_cast(item); + if (layout->d_func()->activated) + layout->invalidate(); + + for (int i = layout->count() - 1; i >= 0; --i) { + QGraphicsLayoutItem *childItem = layout->itemAt(i); + if (childItem) + activateRecursive(childItem); + } + layout->d_func()->activated = true; + } +} + + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicslayout_p.h b/src/widgets/graphicsview/qgraphicslayout_p.h new file mode 100644 index 0000000000..5358ec3cf4 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicslayout_p.h @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSLAYOUT_P_H +#define QGRAPHICSLAYOUT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +#include "qgraphicslayout.h" +#include "qgraphicslayoutitem_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsLayoutItem; +class QGraphicsWidget; + +#ifdef QT_DEBUG +inline bool qt_graphicsLayoutDebug() +{ + static int checked_env = -1; + if(checked_env == -1) + checked_env = !!qgetenv("QT_GRAPHICSLAYOUT_DEBUG").toInt(); + return checked_env; +} +#endif + + +class QLayoutStyleInfo +{ +public: + inline QLayoutStyleInfo() { invalidate(); } + inline QLayoutStyleInfo(QStyle *style, QWidget *widget) + : m_valid(true), m_style(style), m_widget(widget) + { + Q_ASSERT(style); + if (widget) //### + m_styleOption.initFrom(widget); + m_defaultSpacing[0] = style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); + m_defaultSpacing[1] = style->pixelMetric(QStyle::PM_LayoutVerticalSpacing); + } + + inline void invalidate() { m_valid = false; m_style = 0; m_widget = 0; } + + inline QStyle *style() const { return m_style; } + inline QWidget *widget() const { return m_widget; } + + inline bool operator==(const QLayoutStyleInfo &other) + { return m_style == other.m_style && m_widget == other.m_widget; } + inline bool operator!=(const QLayoutStyleInfo &other) + { return !(*this == other); } + + inline void setDefaultSpacing(Qt::Orientation o, qreal spacing){ + if (spacing >= 0) + m_defaultSpacing[o - 1] = spacing; + } + + inline qreal defaultSpacing(Qt::Orientation o) const { + return m_defaultSpacing[o - 1]; + } + + inline qreal perItemSpacing(QSizePolicy::ControlType control1, + QSizePolicy::ControlType control2, + Qt::Orientation orientation) const + { + Q_ASSERT(style()); + return style()->layoutSpacing(control1, control2, orientation, &m_styleOption, widget()); + } +private: + bool m_valid; + QStyle *m_style; + QWidget *m_widget; + QStyleOption m_styleOption; + qreal m_defaultSpacing[2]; +}; + +class Q_AUTOTEST_EXPORT QGraphicsLayoutPrivate : public QGraphicsLayoutItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsLayout) + +public: + QGraphicsLayoutPrivate() : QGraphicsLayoutItemPrivate(0, true), left(-1.0), top(-1.0), right(-1.0), bottom(-1.0), + activated(true) { } + + void reparentChildItems(QGraphicsItem *newParent); + void getMargin(qreal *result, qreal userMargin, QStyle::PixelMetric pm) const; + Qt::LayoutDirection visualDirection() const; + + void addChildLayoutItem(QGraphicsLayoutItem *item); + void activateRecursive(QGraphicsLayoutItem *item); + + qreal left, top, right, bottom; + bool activated; +}; + + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW + +#endif diff --git a/src/widgets/graphicsview/qgraphicslayoutitem.cpp b/src/widgets/graphicsview/qgraphicslayoutitem.cpp new file mode 100644 index 0000000000..c064e01502 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicslayoutitem.cpp @@ -0,0 +1,935 @@ +/**************************************************************************** +** +** 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 QtGui module 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 "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicslayout.h" +#include "qgraphicsscene.h" +#include "qgraphicslayoutitem.h" +#include "qgraphicslayoutitem_p.h" +#include "qwidget.h" +#include "qgraphicswidget.h" + +#include + +QT_BEGIN_NAMESPACE + +/* + COMBINE_SIZE() is identical to combineSize(), except that it + doesn't evaluate 'size' unless necessary. +*/ +#define COMBINE_SIZE(result, size) \ + do { \ + if ((result).width() < 0 || (result).height() < 0) \ + combineSize((result), (size)); \ + } while (false) + +static void combineSize(QSizeF &result, const QSizeF &size) +{ + if (result.width() < 0) + result.setWidth(size.width()); + if (result.height() < 0) + result.setHeight(size.height()); +} + +static void boundSize(QSizeF &result, const QSizeF &size) +{ + if (size.width() >= 0 && size.width() < result.width()) + result.setWidth(size.width()); + if (size.height() >= 0 && size.height() < result.height()) + result.setHeight(size.height()); +} + +static void expandSize(QSizeF &result, const QSizeF &size) +{ + if (size.width() >= 0 && size.width() > result.width()) + result.setWidth(size.width()); + if (size.height() >= 0 && size.height() > result.height()) + result.setHeight(size.height()); +} + +static void normalizeHints(qreal &minimum, qreal &preferred, qreal &maximum, qreal &descent) +{ + if (minimum >= 0 && maximum >= 0 && minimum > maximum) + minimum = maximum; + + if (preferred >= 0) { + if (minimum >= 0 && preferred < minimum) { + preferred = minimum; + } else if (maximum >= 0 && preferred > maximum) { + preferred = maximum; + } + } + + if (minimum >= 0 && descent > minimum) + descent = minimum; +} + +/*! + \internal +*/ +QGraphicsLayoutItemPrivate::QGraphicsLayoutItemPrivate(QGraphicsLayoutItem *par, bool layout) + : parent(par), userSizeHints(0), isLayout(layout), ownedByLayout(false), graphicsItem(0) +{ +} + +/*! + \internal +*/ +QGraphicsLayoutItemPrivate::~QGraphicsLayoutItemPrivate() +{ + // Remove any lazily allocated data + delete[] userSizeHints; +} + +/*! + \internal +*/ +void QGraphicsLayoutItemPrivate::init() +{ + sizeHintCacheDirty = true; + sizeHintWithConstraintCacheDirty = true; +} + +/*! + \internal +*/ +QSizeF *QGraphicsLayoutItemPrivate::effectiveSizeHints(const QSizeF &constraint) const +{ + Q_Q(const QGraphicsLayoutItem); + QSizeF *sizeHintCache; + const bool hasConstraint = constraint.width() >= 0 || constraint.height() >= 0; + if (hasConstraint) { + if (!sizeHintWithConstraintCacheDirty && constraint == cachedConstraint) + return cachedSizeHintsWithConstraints; + sizeHintCache = cachedSizeHintsWithConstraints; + } else { + if (!sizeHintCacheDirty) + return cachedSizeHints; + sizeHintCache = cachedSizeHints; + } + + for (int i = 0; i < Qt::NSizeHints; ++i) { + sizeHintCache[i] = constraint; + if (userSizeHints) + combineSize(sizeHintCache[i], userSizeHints[i]); + } + + QSizeF &minS = sizeHintCache[Qt::MinimumSize]; + QSizeF &prefS = sizeHintCache[Qt::PreferredSize]; + QSizeF &maxS = sizeHintCache[Qt::MaximumSize]; + QSizeF &descentS = sizeHintCache[Qt::MinimumDescent]; + + normalizeHints(minS.rwidth(), prefS.rwidth(), maxS.rwidth(), descentS.rwidth()); + normalizeHints(minS.rheight(), prefS.rheight(), maxS.rheight(), descentS.rheight()); + + // if the minimum, preferred and maximum sizes contradict each other + // (e.g. the minimum is larger than the maximum) we give priority to + // the maximum size, then the minimum size and finally the preferred size + COMBINE_SIZE(maxS, q->sizeHint(Qt::MaximumSize, maxS)); + combineSize(maxS, QSizeF(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); + expandSize(maxS, prefS); + expandSize(maxS, minS); + boundSize(maxS, QSizeF(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); + + COMBINE_SIZE(minS, q->sizeHint(Qt::MinimumSize, minS)); + expandSize(minS, QSizeF(0, 0)); + boundSize(minS, prefS); + boundSize(minS, maxS); + + COMBINE_SIZE(prefS, q->sizeHint(Qt::PreferredSize, prefS)); + expandSize(prefS, minS); + boundSize(prefS, maxS); + + // Not supported yet + // COMBINE_SIZE(descentS, q->sizeHint(Qt::MinimumDescent, constraint)); + + if (hasConstraint) { + cachedConstraint = constraint; + sizeHintWithConstraintCacheDirty = false; + } else { + sizeHintCacheDirty = false; + } + return sizeHintCache; +} + + +/*! + \internal + + Returns the parent item of this layout, or 0 if this layout is + not installed on any widget. + + If this is the item that the layout is installed on, it will return "itself". + + If the layout is a sub-layout, this function returns the parent + widget of the parent layout. + + Note that it will traverse up the layout item hierarchy instead of just calling + QGraphicsItem::parentItem(). This is on purpose. + + \sa parent() +*/ +QGraphicsItem *QGraphicsLayoutItemPrivate::parentItem() const +{ + Q_Q(const QGraphicsLayoutItem); + + const QGraphicsLayoutItem *parent = q; + while (parent && parent->isLayout()) { + parent = parent->parentLayoutItem(); + } + return parent ? parent->graphicsItem() : 0; +} + +/*! + \internal + + Ensures that userSizeHints is allocated. + This function must be called before any dereferencing. +*/ +void QGraphicsLayoutItemPrivate::ensureUserSizeHints() +{ + if (!userSizeHints) + userSizeHints = new QSizeF[Qt::NSizeHints]; +} + +/*! + \internal + + Sets the user size hint \a which to \a size. Use an invalid size to unset the size hint. + */ +void QGraphicsLayoutItemPrivate::setSize(Qt::SizeHint which, const QSizeF &size) +{ + Q_Q(QGraphicsLayoutItem); + + if (userSizeHints) { + if (size == userSizeHints[which]) + return; + } else if (size.width() < 0 && size.height() < 0) { + return; + } + + ensureUserSizeHints(); + userSizeHints[which] = size; + q->updateGeometry(); +} + +/*! + \internal + + Sets the width of the user size hint \a which to \a width. + */ +void QGraphicsLayoutItemPrivate::setSizeComponent( + Qt::SizeHint which, SizeComponent component, qreal value) +{ + Q_Q(QGraphicsLayoutItem); + ensureUserSizeHints(); + qreal &userValue = (component == Width) + ? userSizeHints[which].rwidth() + : userSizeHints[which].rheight(); + if (value == userValue) + return; + userValue = value; + q->updateGeometry(); +} + + +bool QGraphicsLayoutItemPrivate::hasHeightForWidth() const +{ + Q_Q(const QGraphicsLayoutItem); + if (isLayout) { + const QGraphicsLayout *l = static_cast(q); + for (int i = l->count() - 1; i >= 0; --i) { + if (QGraphicsLayoutItemPrivate::get(l->itemAt(i))->hasHeightForWidth()) + return true; + } + } else if (QGraphicsItem *item = q->graphicsItem()) { + if (item->isWidget()) { + QGraphicsWidget *w = static_cast(item); + if (w->layout()) { + return QGraphicsLayoutItemPrivate::get(w->layout())->hasHeightForWidth(); + } + } + } + return q->sizePolicy().hasHeightForWidth(); +} + +bool QGraphicsLayoutItemPrivate::hasWidthForHeight() const +{ + Q_Q(const QGraphicsLayoutItem); + if (isLayout) { + const QGraphicsLayout *l = static_cast(q); + for (int i = l->count() - 1; i >= 0; --i) { + if (QGraphicsLayoutItemPrivate::get(l->itemAt(i))->hasWidthForHeight()) + return true; + } + } else if (QGraphicsItem *item = q->graphicsItem()) { + if (item->isWidget()) { + QGraphicsWidget *w = static_cast(item); + if (w->layout()) { + return QGraphicsLayoutItemPrivate::get(w->layout())->hasWidthForHeight(); + } + } + } + return q->sizePolicy().hasWidthForHeight(); +} + +/*! + \class QGraphicsLayoutItem + \brief The QGraphicsLayoutItem class can be inherited to allow your custom + items to be managed by layouts. + \since 4.4 + \ingroup graphicsview-api + + QGraphicsLayoutItem is an abstract class that defines a set of virtual + functions describing sizes, size policies, and size hints for any object + arranged by QGraphicsLayout. The API contains functions relevant + for both the item itself and for the user of the item as most of + QGraphicsLayoutItem's functions are also part of the subclass' public API. + + In most cases, existing layout-aware classes such as QGraphicsWidget and + QGraphicsLayout already provide the functionality you require. However, + subclassing these classes will enable you to create both graphical + elements that work well with layouts (QGraphicsWidget) or custom layouts + (QGraphicsLayout). + + \section1 Subclassing QGraphicsLayoutItem + + If you create a subclass of QGraphicsLayoutItem and reimplement its + virtual functions, you will enable the layout to resize and position your + item along with other QGraphicsLayoutItems including QGraphicsWidget + and QGraphicsLayout. + + You can start by reimplementing important functions: the protected + sizeHint() function, as well as the public setGeometry() + function. If you want your items to be aware of immediate geometry + changes, you can also reimplement updateGeometry(). + + The geometry, size hint, and size policy affect the item's size and + position. Calling setGeometry() will always resize and reposition the item + immediately. Normally, this function is called by QGraphicsLayout after + the layout has been activated, but it can also be called by the item's user + at any time. + + The sizeHint() function returns the item' minimum, preferred and maximum + size hints. You can override these properties by calling setMinimumSize(), + setPreferredSize() or setMaximumSize(). You can also use functions such as + setMinimumWidth() or setMaximumHeight() to set only the width or height + component if desired. + + The effectiveSizeHint() function, on the other hand, returns a size hint + for any given Qt::SizeHint, and guarantees that the returned size is bound + to the minimum and maximum sizes and size hints. You can set the item's + vertical and horizontal size policy by calling setSizePolicy(). The + sizePolicy property is used by the layout system to describe how this item + prefers to grow or shrink. + + \section1 Nesting QGraphicsLayoutItems + + QGraphicsLayoutItems can be nested within other QGraphicsLayoutItems, + similar to layouts that can contain sublayouts. This is done either by + passing a QGraphicsLayoutItem pointer to QGraphicsLayoutItem's + protected constructor, or by calling setParentLayoutItem(). The + parentLayoutItem() function returns a pointer to the item's layoutItem + parent. If the item's parent is 0 or if the parent does not inherit + from QGraphicsItem, the parentLayoutItem() function then returns 0. + isLayout() returns true if the QGraphicsLayoutItem subclass is itself a + layout, or false otherwise. + + Qt uses QGraphicsLayoutItem to provide layout functionality in the + \l{Graphics View Framework}, but in the future its use may spread + throughout Qt itself. + + \sa QGraphicsWidget, QGraphicsLayout, QGraphicsLinearLayout, + QGraphicsGridLayout +*/ + +/*! + Constructs the QGraphicsLayoutItem object. \a parent becomes the object's + parent. If \a isLayout is true the item is a layout, otherwise + \a isLayout is false. +*/ +QGraphicsLayoutItem::QGraphicsLayoutItem(QGraphicsLayoutItem *parent, bool isLayout) + : d_ptr(new QGraphicsLayoutItemPrivate(parent, isLayout)) +{ + Q_D(QGraphicsLayoutItem); + d->init(); + d->sizePolicy = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + d->q_ptr = this; +} + +/*! + \internal +*/ +QGraphicsLayoutItem::QGraphicsLayoutItem(QGraphicsLayoutItemPrivate &dd) + : d_ptr(&dd) +{ + Q_D(QGraphicsLayoutItem); + d->init(); + d->q_ptr = this; +} + +/*! + Destroys the QGraphicsLayoutItem object. +*/ +QGraphicsLayoutItem::~QGraphicsLayoutItem() +{ + QGraphicsLayoutItem *parentLI = parentLayoutItem(); + if (parentLI && parentLI->isLayout()) { + QGraphicsLayout *lay = static_cast(parentLI); + // this is not optimal + for (int i = lay->count() - 1; i >= 0; --i) { + if (lay->itemAt(i) == this) { + lay->removeAt(i); + break; + } + } + } +} + +/*! + \fn virtual QSizeF QGraphicsLayoutItem::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const = 0; + + This pure virtual function returns the size hint for \a which of the + QGraphicsLayoutItem, using the width or height of \a constraint to + constrain the output. + + Reimplement this function in a subclass of QGraphicsLayoutItem to + provide the necessary size hints for your items. + + \sa effectiveSizeHint() +*/ + +/*! + Sets the size policy to \a policy. The size policy describes how the item + should grow horizontally and vertically when arranged in a layout. + + QGraphicsLayoutItem's default size policy is (QSizePolicy::Fixed, + QSizePolicy::Fixed, QSizePolicy::DefaultType), but it is common for + subclasses to change the default. For example, QGraphicsWidget defaults + to (QSizePolicy::Preferred, QSizePolicy::Preferred, + QSizePolicy::DefaultType). + + \sa sizePolicy(), QWidget::sizePolicy() +*/ +void QGraphicsLayoutItem::setSizePolicy(const QSizePolicy &policy) +{ + Q_D(QGraphicsLayoutItem); + if (d->sizePolicy == policy) + return; + d->sizePolicy = policy; + updateGeometry(); +} + +/*! + \overload + + This function is equivalent to calling + setSizePolicy(QSizePolicy(\a hPolicy, \a vPolicy, \a controlType)). + + \sa sizePolicy(), QWidget::sizePolicy() +*/ +void QGraphicsLayoutItem::setSizePolicy(QSizePolicy::Policy hPolicy, + QSizePolicy::Policy vPolicy, + QSizePolicy::ControlType controlType) +{ + setSizePolicy(QSizePolicy(hPolicy, vPolicy, controlType)); +} + +/*! + Returns the current size policy. + + \sa setSizePolicy(), QWidget::sizePolicy() +*/ +QSizePolicy QGraphicsLayoutItem::sizePolicy() const +{ + Q_D(const QGraphicsLayoutItem); + return d->sizePolicy; +} + +/*! + Sets the minimum size to \a size. This property overrides sizeHint() for + Qt::MinimumSize and ensures that effectiveSizeHint() will never return + a size smaller than \a size. In order to unset the minimum size, use an + invalid size. + + \sa minimumSize(), maximumSize(), preferredSize(), Qt::MinimumSize, + sizeHint(), setMinimumWidth(), setMinimumHeight() +*/ +void QGraphicsLayoutItem::setMinimumSize(const QSizeF &size) +{ + d_ptr->setSize(Qt::MinimumSize, size); +} + +/*! + \fn QGraphicsLayoutItem::setMinimumSize(qreal w, qreal h) + + This convenience function is equivalent to calling + setMinimumSize(QSizeF(\a w, \a h)). + + \sa minimumSize(), setMaximumSize(), setPreferredSize(), sizeHint() +*/ + +/*! + Returns the minimum size. + + \sa setMinimumSize(), preferredSize(), maximumSize(), Qt::MinimumSize, + sizeHint() +*/ +QSizeF QGraphicsLayoutItem::minimumSize() const +{ + return effectiveSizeHint(Qt::MinimumSize); +} + +/*! + Sets the minimum width to \a width. + + \sa minimumWidth(), setMinimumSize(), minimumSize() +*/ +void QGraphicsLayoutItem::setMinimumWidth(qreal width) +{ + d_ptr->setSizeComponent(Qt::MinimumSize, d_ptr->Width, width); +} + +/*! + Sets the minimum height to \a height. + + \sa minimumHeight(), setMinimumSize(), minimumSize() +*/ +void QGraphicsLayoutItem::setMinimumHeight(qreal height) +{ + d_ptr->setSizeComponent(Qt::MinimumSize, d_ptr->Height, height); +} + + +/*! + Sets the preferred size to \a size. This property overrides sizeHint() for + Qt::PreferredSize and provides the default value for effectiveSizeHint(). + In order to unset the preferred size, use an invalid size. + + \sa preferredSize(), minimumSize(), maximumSize(), Qt::PreferredSize, + sizeHint() +*/ +void QGraphicsLayoutItem::setPreferredSize(const QSizeF &size) +{ + d_ptr->setSize(Qt::PreferredSize, size); +} + +/*! + \fn QGraphicsLayoutItem::setPreferredSize(qreal w, qreal h) + + This convenience function is equivalent to calling + setPreferredSize(QSizeF(\a w, \a h)). + + \sa preferredSize(), setMaximumSize(), setMinimumSize(), sizeHint() +*/ + +/*! + Returns the preferred size. + + \sa setPreferredSize(), minimumSize(), maximumSize(), Qt::PreferredSize, + sizeHint() +*/ +QSizeF QGraphicsLayoutItem::preferredSize() const +{ + return effectiveSizeHint(Qt::PreferredSize); +} + +/*! + Sets the preferred height to \a height. + + \sa preferredWidth(), setPreferredSize(), preferredSize() +*/ +void QGraphicsLayoutItem::setPreferredHeight(qreal height) +{ + d_ptr->setSizeComponent(Qt::PreferredSize, d_ptr->Height, height); +} + +/*! + Sets the preferred width to \a width. + + \sa preferredHeight(), setPreferredSize(), preferredSize() +*/ +void QGraphicsLayoutItem::setPreferredWidth(qreal width) +{ + d_ptr->setSizeComponent(Qt::PreferredSize, d_ptr->Width, width); +} + +/*! + Sets the maximum size to \a size. This property overrides sizeHint() for + Qt::MaximumSize and ensures that effectiveSizeHint() will never return a + size larger than \a size. In order to unset the maximum size, use an + invalid size. + + \sa maximumSize(), minimumSize(), preferredSize(), Qt::MaximumSize, + sizeHint() +*/ +void QGraphicsLayoutItem::setMaximumSize(const QSizeF &size) +{ + d_ptr->setSize(Qt::MaximumSize, size); +} + +/*! + \fn QGraphicsLayoutItem::setMaximumSize(qreal w, qreal h) + + This convenience function is equivalent to calling + setMaximumSize(QSizeF(\a w, \a h)). + + \sa maximumSize(), setMinimumSize(), setPreferredSize(), sizeHint() +*/ + +/*! + Returns the maximum size. + + \sa setMaximumSize(), minimumSize(), preferredSize(), Qt::MaximumSize, + sizeHint() +*/ +QSizeF QGraphicsLayoutItem::maximumSize() const +{ + return effectiveSizeHint(Qt::MaximumSize); +} + +/*! + Sets the maximum width to \a width. + + \sa maximumWidth(), setMaximumSize(), maximumSize() +*/ +void QGraphicsLayoutItem::setMaximumWidth(qreal width) +{ + d_ptr->setSizeComponent(Qt::MaximumSize, d_ptr->Width, width); +} + +/*! + Sets the maximum height to \a height. + + \sa maximumHeight(), setMaximumSize(), maximumSize() +*/ +void QGraphicsLayoutItem::setMaximumHeight(qreal height) +{ + d_ptr->setSizeComponent(Qt::MaximumSize, d_ptr->Height, height); +} + +/*! + \fn qreal QGraphicsLayoutItem::minimumWidth() const + + Returns the minimum width. + + \sa setMinimumWidth(), setMinimumSize(), minimumSize() +*/ + +/*! + \fn qreal QGraphicsLayoutItem::minimumHeight() const + + Returns the minimum height. + + \sa setMinimumHeight(), setMinimumSize(), minimumSize() +*/ + +/*! + \fn qreal QGraphicsLayoutItem::preferredWidth() const + + Returns the preferred width. + + \sa setPreferredWidth(), setPreferredSize(), preferredSize() +*/ + +/*! + \fn qreal QGraphicsLayoutItem::preferredHeight() const + + Returns the preferred height. + + \sa setPreferredHeight(), setPreferredSize(), preferredSize() +*/ + +/*! + \fn qreal QGraphicsLayoutItem::maximumWidth() const + + Returns the maximum width. + + \sa setMaximumWidth(), setMaximumSize(), maximumSize() +*/ + +/*! + \fn qreal QGraphicsLayoutItem::maximumHeight() const + + Returns the maximum height. + + \sa setMaximumHeight(), setMaximumSize(), maximumSize() +*/ + +/*! + \fn virtual void QGraphicsLayoutItem::setGeometry(const QRectF &rect) + + This virtual function sets the geometry of the QGraphicsLayoutItem to + \a rect, which is in parent coordinates (e.g., the top-left corner of \a rect + is equivalent to the item's position in parent coordinates). + + You must reimplement this function in a subclass of QGraphicsLayoutItem to + receive geometry updates. The layout will call this function when it does a + rearrangement. + + If \a rect is outside of the bounds of minimumSize and maximumSize, it + will be adjusted to its closest size so that it is within the legal + bounds. + + \sa geometry() +*/ +void QGraphicsLayoutItem::setGeometry(const QRectF &rect) +{ + Q_D(QGraphicsLayoutItem); + QSizeF effectiveSize = rect.size().expandedTo(effectiveSizeHint(Qt::MinimumSize)) + .boundedTo(effectiveSizeHint(Qt::MaximumSize)); + d->geom = QRectF(rect.topLeft(), effectiveSize); +} + +/*! + \fn QRectF QGraphicsLayoutItem::geometry() const + + Returns the item's geometry (e.g., position and size) as a + QRectF. This function is equivalent to QRectF(pos(), size()). + + \sa setGeometry() +*/ +QRectF QGraphicsLayoutItem::geometry() const +{ + Q_D(const QGraphicsLayoutItem); + return d->geom; +} + +/*! + This virtual function provides the \a left, \a top, \a right and \a bottom + contents margins for this QGraphicsLayoutItem. The default implementation + assumes all contents margins are 0. The parameters point to values stored + in qreals. If any of the pointers is 0, that value will not be updated. + + \sa QGraphicsWidget::setContentsMargins() +*/ +void QGraphicsLayoutItem::getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const +{ + if (left) + *left = 0; + if (top) + *top = 0; + if (right) + *right = 0; + if (bottom) + *bottom = 0; +} + +/*! + Returns the contents rect in local coordinates. + + The contents rect defines the subrectangle used by an associated layout + when arranging subitems. This function is a convenience function that + adjusts the item's geometry() by its contents margins. Note that + getContentsMargins() is a virtual function that you can reimplement to + return the item's contents margins. + + \sa getContentsMargins(), geometry() +*/ +QRectF QGraphicsLayoutItem::contentsRect() const +{ + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + return QRectF(QPointF(), geometry().size()).adjusted(+left, +top, -right, -bottom); +} + +/*! + Returns the effective size hint for this QGraphicsLayoutItem. + + \a which is the size hint in question. + \a constraint is an optional argument that defines a special constrain + when calculating the effective size hint. By default, \a constraint is + QSizeF(-1, -1), which means there is no constraint to the size hint. + + If you want to specify the widget's size hint for a given width or height, + you can provide the fixed dimension in \a constraint. This is useful for + widgets that can grow only either vertically or horizontally, and need to + set either their width or their height to a special value. + + For example, a text paragraph item fit into a column width of 200 may + grow vertically. You can pass QSizeF(200, -1) as a constraint to get a + suitable minimum, preferred and maximum height). + + You can adjust the effective size hint by reimplementing sizeHint() + in a QGraphicsLayoutItem subclass, or by calling one of the following + functions: setMinimumSize(), setPreferredSize, or setMaximumSize() + (or a combination of both). + + This function caches each of the size hints and guarantees that + sizeHint() will be called only once for each value of \a which - unless + \a constraint is not specified and updateGeometry() has been called. + + \sa sizeHint() +*/ +QSizeF QGraphicsLayoutItem::effectiveSizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_D(const QGraphicsLayoutItem); + + if (!d->userSizeHints && constraint.isValid()) + return constraint; + + // ### should respect size policy??? + return d_ptr->effectiveSizeHints(constraint)[which]; +} + +/*! + This virtual function discards any cached size hint information. You + should always call this function if you change the return value of the + sizeHint() function. Subclasses must always call the base implementation + when reimplementing this function. + + \sa effectiveSizeHint() +*/ +void QGraphicsLayoutItem::updateGeometry() +{ + Q_D(QGraphicsLayoutItem); + d->sizeHintCacheDirty = true; + d->sizeHintWithConstraintCacheDirty = true; +} + +/*! + Returns the parent of this QGraphicsLayoutItem, or 0 if there is no parent, + or if the parent does not inherit from QGraphicsLayoutItem + (QGraphicsLayoutItem is often used through multiple inheritance with + QObject-derived classes). + + \sa setParentLayoutItem() +*/ +QGraphicsLayoutItem *QGraphicsLayoutItem::parentLayoutItem() const +{ + return d_func()->parent; +} + +/*! + Sets the parent of this QGraphicsLayoutItem to \a parent. + + \sa parentLayoutItem() +*/ +void QGraphicsLayoutItem::setParentLayoutItem(QGraphicsLayoutItem *parent) +{ + d_func()->parent = parent; +} + +/*! + Returns true if this QGraphicsLayoutItem is a layout (e.g., is inherited + by an object that arranges other QGraphicsLayoutItem objects); otherwise + returns false. + + \sa QGraphicsLayout +*/ +bool QGraphicsLayoutItem::isLayout() const +{ + return d_func()->isLayout; +} + +/*! + \since 4.6 + + Returns whether a layout should delete this item in its destructor. + If its true, then the layout will delete it. If its false, then it is + assumed that another object has the ownership of it, and the layout won't + delete this item. + + If the item inherits both QGraphicsItem and QGraphicsLayoutItem (such + as QGraphicsWidget does) the item is really part of two ownership + hierarchies. This property informs what the layout should do with its + child items when it is destructed. In the case of QGraphicsWidget, it + is preferred that when the layout is deleted it won't delete its children + (since they are also part of the graphics item hierarchy). + + By default this value is initialized to false in QGraphicsLayoutItem, + but it is overridden by QGraphicsLayout to return true. This is because + QGraphicsLayout is not normally part of the QGraphicsItem hierarchy, so the + parent layout should delete it. + Subclasses might override this default behaviour by calling + setOwnedByLayout(true). + + \sa setOwnedByLayout() +*/ +bool QGraphicsLayoutItem::ownedByLayout() const +{ + return d_func()->ownedByLayout; +} +/*! + \since 4.6 + + Sets whether a layout should delete this item in its destructor or not. + \a ownership must be true to in order for the layout to delete it. + \sa ownedByLayout() +*/ +void QGraphicsLayoutItem::setOwnedByLayout(bool ownership) +{ + d_func()->ownedByLayout = ownership; +} + +/*! + * Returns the QGraphicsItem that this layout item represents. + * For QGraphicsWidget it will return itself. For custom items it can return an + * aggregated value. + * + * \sa setGraphicsItem() + */ +QGraphicsItem *QGraphicsLayoutItem::graphicsItem() const +{ + return d_func()->graphicsItem; +} + +/*! + * If the QGraphicsLayoutItem represents a QGraphicsItem, and it wants to take + * advantage of the automatic reparenting capabilities of QGraphicsLayout it + * should set this value. + * Note that if you delete \a item and not delete the layout item, you are + * responsible of calling setGraphicsItem(0) in order to avoid having a + * dangling pointer. + * + * \sa graphicsItem() + */ +void QGraphicsLayoutItem::setGraphicsItem(QGraphicsItem *item) +{ + d_func()->graphicsItem = item; +} + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicslayoutitem.h b/src/widgets/graphicsview/qgraphicslayoutitem.h new file mode 100644 index 0000000000..7112dd06fc --- /dev/null +++ b/src/widgets/graphicsview/qgraphicslayoutitem.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSLAYOUTITEM_H +#define QGRAPHICSLAYOUTITEM_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsLayoutItemPrivate; +class QGraphicsItem; +class Q_GUI_EXPORT QGraphicsLayoutItem +{ +public: + QGraphicsLayoutItem(QGraphicsLayoutItem *parent = 0, bool isLayout = false); + virtual ~QGraphicsLayoutItem(); + + void setSizePolicy(const QSizePolicy &policy); + void setSizePolicy(QSizePolicy::Policy hPolicy, QSizePolicy::Policy vPolicy, QSizePolicy::ControlType controlType = QSizePolicy::DefaultType); + QSizePolicy sizePolicy() const; + + void setMinimumSize(const QSizeF &size); + inline void setMinimumSize(qreal w, qreal h); + QSizeF minimumSize() const; + void setMinimumWidth(qreal width); + inline qreal minimumWidth() const; + void setMinimumHeight(qreal height); + inline qreal minimumHeight() const; + + void setPreferredSize(const QSizeF &size); + inline void setPreferredSize(qreal w, qreal h); + QSizeF preferredSize() const; + void setPreferredWidth(qreal width); + inline qreal preferredWidth() const; + void setPreferredHeight(qreal height); + inline qreal preferredHeight() const; + + void setMaximumSize(const QSizeF &size); + inline void setMaximumSize(qreal w, qreal h); + QSizeF maximumSize() const; + void setMaximumWidth(qreal width); + inline qreal maximumWidth() const; + void setMaximumHeight(qreal height); + inline qreal maximumHeight() const; + + virtual void setGeometry(const QRectF &rect); + QRectF geometry() const; + virtual void getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const; + QRectF contentsRect() const; + + QSizeF effectiveSizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + + virtual void updateGeometry(); //### rename to sizeHintChanged() + + QGraphicsLayoutItem *parentLayoutItem() const; + void setParentLayoutItem(QGraphicsLayoutItem *parent); + + bool isLayout() const; + // ###Qt5: Make automatic reparenting work regardless of item/object/widget type. + QGraphicsItem *graphicsItem() const; + bool ownedByLayout() const; + +protected: + void setGraphicsItem(QGraphicsItem *item); + void setOwnedByLayout(bool ownedByLayout); + QGraphicsLayoutItem(QGraphicsLayoutItemPrivate &dd); + + virtual QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const = 0; + QScopedPointer d_ptr; + +private: + QSizeF *effectiveSizeHints(const QSizeF &constraint) const; + Q_DECLARE_PRIVATE(QGraphicsLayoutItem) + + friend class QGraphicsLayout; +}; + +Q_DECLARE_INTERFACE(QGraphicsLayoutItem, "com.trolltech.Qt.QGraphicsLayoutItem") + +inline void QGraphicsLayoutItem::setMinimumSize(qreal aw, qreal ah) +{ setMinimumSize(QSizeF(aw, ah)); } +inline void QGraphicsLayoutItem::setPreferredSize(qreal aw, qreal ah) +{ setPreferredSize(QSizeF(aw, ah)); } +inline void QGraphicsLayoutItem::setMaximumSize(qreal aw, qreal ah) +{ setMaximumSize(QSizeF(aw, ah)); } + +inline qreal QGraphicsLayoutItem::minimumWidth() const +{ return effectiveSizeHint(Qt::MinimumSize).width(); } +inline qreal QGraphicsLayoutItem::minimumHeight() const +{ return effectiveSizeHint(Qt::MinimumSize).height(); } + +inline qreal QGraphicsLayoutItem::preferredWidth() const +{ return effectiveSizeHint(Qt::PreferredSize).width(); } +inline qreal QGraphicsLayoutItem::preferredHeight() const +{ return effectiveSizeHint(Qt::PreferredSize).height(); } + +inline qreal QGraphicsLayoutItem::maximumWidth() const +{ return effectiveSizeHint(Qt::MaximumSize).width(); } +inline qreal QGraphicsLayoutItem::maximumHeight() const +{ return effectiveSizeHint(Qt::MaximumSize).height(); } + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/widgets/graphicsview/qgraphicslayoutitem_p.h b/src/widgets/graphicsview/qgraphicslayoutitem_p.h new file mode 100644 index 0000000000..85481b7a46 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicslayoutitem_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSLAYOUTITEM_P_H +#define QGRAPHICSLAYOUTITEM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsLayoutItem; +class Q_AUTOTEST_EXPORT QGraphicsLayoutItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsLayoutItem) +public: + virtual ~QGraphicsLayoutItemPrivate(); + QGraphicsLayoutItemPrivate(QGraphicsLayoutItem *parent, bool isLayout); + static QGraphicsLayoutItemPrivate *get(QGraphicsLayoutItem *q) { return q->d_func();} + static const QGraphicsLayoutItemPrivate *get(const QGraphicsLayoutItem *q) { return q->d_func();} + + void init(); + QSizeF *effectiveSizeHints(const QSizeF &constraint) const; + QGraphicsItem *parentItem() const; + void ensureUserSizeHints(); + void setSize(Qt::SizeHint which, const QSizeF &size); + enum SizeComponent { Width, Height }; + void setSizeComponent(Qt::SizeHint which, SizeComponent component, qreal value); + + bool hasHeightForWidth() const; + bool hasWidthForHeight() const; + + QSizePolicy sizePolicy; + QGraphicsLayoutItem *parent; + + QSizeF *userSizeHints; + mutable QSizeF cachedSizeHints[Qt::NSizeHints]; + mutable QSizeF cachedConstraint; + mutable QSizeF cachedSizeHintsWithConstraints[Qt::NSizeHints]; + + mutable quint32 sizeHintCacheDirty : 1; + mutable quint32 sizeHintWithConstraintCacheDirty : 1; + quint32 isLayout : 1; + quint32 ownedByLayout : 1; + + QGraphicsLayoutItem *q_ptr; + QRectF geom; + QGraphicsItem *graphicsItem; +}; + +QT_END_NAMESPACE + +#endif //QGRAPHICSLAYOUTITEM_P_H + diff --git a/src/widgets/graphicsview/qgraphicslinearlayout.cpp b/src/widgets/graphicsview/qgraphicslinearlayout.cpp new file mode 100644 index 0000000000..5591638395 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicslinearlayout.cpp @@ -0,0 +1,568 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +/*! + \class QGraphicsLinearLayout + \brief The QGraphicsLinearLayout class provides a horizontal or vertical + layout for managing widgets in Graphics View. + \since 4.4 + \ingroup graphicsview-api + + The default orientation for a linear layout is Qt::Horizontal. You can + choose a vertical orientation either by calling setOrientation(), or by + passing Qt::Vertical to QGraphicsLinearLayout's constructor. + + The most common way to use QGraphicsLinearLayout is to construct an object + on the heap with no parent, add widgets and layouts by calling addItem(), + and finally assign the layout to a widget by calling + QGraphicsWidget::setLayout(). + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicslinearlayout.cpp 0 + + You can add widgets, layouts, stretches (addStretch(), insertStretch() or + setStretchFactor()), and spacings (setItemSpacing()) to a linear + layout. The layout takes ownership of the items. In some cases when the layout + item also inherits from QGraphicsItem (such as QGraphicsWidget) there will be a + ambiguity in ownership because the layout item belongs to two ownership hierarchies. + See the documentation of QGraphicsLayoutItem::setOwnedByLayout() how to handle + this. + You can access each item in the layout by calling count() and itemAt(). Calling + removeAt() or removeItem() will remove an item from the layout, without + destroying it. + + \section1 Size Hints and Size Policies in QGraphicsLinearLayout + + QGraphicsLinearLayout respects each item's size hints and size policies, + and when the layout contains more space than the items can fill, each item + is arranged according to the layout's alignment for that item. You can set + an alignment for each item by calling setAlignment(), and check the + alignment for any item by calling alignment(). By default, items are + aligned to the top left. + + \section1 Spacing within QGraphicsLinearLayout + + Between the items, the layout distributes some space. The actual amount of + space depends on the managed widget's current style, but the common + spacing is 4. You can also set your own spacing by calling setSpacing(), + and get the current spacing value by calling spacing(). If you want to + configure individual spacing for your items, you can call setItemSpacing(). + + \section1 Stretch Factor in QGraphicsLinearLayout + + You can assign a stretch factor to each item to control how much space it + will get compared to the other items. By default, two identical widgets + arranged in a linear layout will have the same size, but if the first + widget has a stretch factor of 1 and the second widget has a stretch + factor of 2, the first widget will get 1/3 of the available space, and the + second will get 2/3. + + QGraphicsLinearLayout calculates the distribution of sizes by adding up + the stretch factors of all items, and then dividing the available space + accordingly. The default stretch factor is 0 for all items; a factor of 0 + means the item does not have any defined stretch factor; effectively this + is the same as setting the stretch factor to 1. The stretch factor only + applies to the available space in the lengthwise direction of the layout + (following its orientation). If you want to control both the item's + horizontal and vertical stretch, you can use QGraphicsGridLayout instead. + + \section1 QGraphicsLinearLayout Compared to Other Layouts + + QGraphicsLinearLayout is very similar to QVBoxLayout and QHBoxLayout, but + in contrast to these classes, it is used to manage QGraphicsWidget and + QGraphicsLayout instead of QWidget and QLayout. + + \sa QGraphicsGridLayout, QGraphicsWidget +*/ + +#include "qapplication.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qwidget.h" +#include "qgraphicslayout_p.h" +#include "qgraphicslayoutitem.h" +#include "qgraphicslinearlayout.h" +#include "qgraphicswidget.h" +#include "qgridlayoutengine_p.h" +#ifdef QT_DEBUG +#include +#endif + +QT_BEGIN_NAMESPACE + +class QGraphicsLinearLayoutPrivate : public QGraphicsLayoutPrivate +{ +public: + QGraphicsLinearLayoutPrivate(Qt::Orientation orientation) : orientation(orientation) { } + + void removeGridItem(QGridLayoutItem *gridItem); + QLayoutStyleInfo styleInfo() const; + void fixIndex(int *index) const; + int gridRow(int index) const; + int gridColumn(int index) const; + + Qt::Orientation orientation; + QGridLayoutEngine engine; +}; + +void QGraphicsLinearLayoutPrivate::removeGridItem(QGridLayoutItem *gridItem) +{ + int index = gridItem->firstRow(orientation); + engine.removeItem(gridItem); + engine.removeRows(index, 1, orientation); +} + +void QGraphicsLinearLayoutPrivate::fixIndex(int *index) const +{ + int count = engine.rowCount(orientation); + if (uint(*index) > uint(count)) + *index = count; +} + +int QGraphicsLinearLayoutPrivate::gridRow(int index) const +{ + if (orientation == Qt::Horizontal) + return 0; + return int(qMin(uint(index), uint(engine.rowCount()))); +} + +int QGraphicsLinearLayoutPrivate::gridColumn(int index) const +{ + if (orientation == Qt::Vertical) + return 0; + return int(qMin(uint(index), uint(engine.columnCount()))); +} + +Q_GLOBAL_STATIC(QWidget, globalStyleInfoWidget) + +QLayoutStyleInfo QGraphicsLinearLayoutPrivate::styleInfo() const +{ + QGraphicsItem *item = parentItem(); + QStyle *style = (item && item->isWidget()) ? static_cast(item)->style() : QApplication::style(); + return QLayoutStyleInfo(style, globalStyleInfoWidget()); +} + +/*! + Constructs a QGraphicsLinearLayout instance. You can pass the + \a orientation for the layout, either horizontal or vertical, and + \a parent is passed to QGraphicsLayout's constructor. +*/ +QGraphicsLinearLayout::QGraphicsLinearLayout(Qt::Orientation orientation, QGraphicsLayoutItem *parent) + : QGraphicsLayout(*new QGraphicsLinearLayoutPrivate(orientation), parent) +{ +} + +/*! + Constructs a QGraphicsLinearLayout instance using Qt::Horizontal + orientation. \a parent is passed to QGraphicsLayout's constructor. +*/ +QGraphicsLinearLayout::QGraphicsLinearLayout(QGraphicsLayoutItem *parent) + : QGraphicsLayout(*new QGraphicsLinearLayoutPrivate(Qt::Horizontal), parent) +{ +} + +/*! + Destroys the QGraphicsLinearLayout object. +*/ +QGraphicsLinearLayout::~QGraphicsLinearLayout() +{ + for (int i = count() - 1; i >= 0; --i) { + QGraphicsLayoutItem *item = itemAt(i); + // The following lines can be removed, but this removes the item + // from the layout more efficiently than the implementation of + // ~QGraphicsLayoutItem. + removeAt(i); + if (item) { + item->setParentLayoutItem(0); + if (item->ownedByLayout()) + delete item; + } + } +} + +/*! + Change the layout orientation to \a orientation. Changing the layout + orientation will automatically invalidate the layout. + + \sa orientation() +*/ +void QGraphicsLinearLayout::setOrientation(Qt::Orientation orientation) +{ + Q_D(QGraphicsLinearLayout); + if (orientation != d->orientation) { + d->engine.transpose(); + d->orientation = orientation; + invalidate(); + } +} + +/*! + Returns the layout orientation. + \sa setOrientation() + */ +Qt::Orientation QGraphicsLinearLayout::orientation() const +{ + Q_D(const QGraphicsLinearLayout); + return d->orientation; +} + +/*! + \fn void QGraphicsLinearLayout::addItem(QGraphicsLayoutItem *item) + + This convenience function is equivalent to calling + insertItem(-1, \a item). +*/ + +/*! + \fn void QGraphicsLinearLayout::addStretch(int stretch) + + This convenience function is equivalent to calling + insertStretch(-1, \a stretch). +*/ + +/*! + Inserts \a item into the layout at \a index, or before any item that is + currently at \a index. + + \sa addItem(), itemAt(), insertStretch(), setItemSpacing() +*/ +void QGraphicsLinearLayout::insertItem(int index, QGraphicsLayoutItem *item) +{ + Q_D(QGraphicsLinearLayout); + if (!item) { + qWarning("QGraphicsLinearLayout::insertItem: cannot insert null item"); + return; + } + if (item == this) { + qWarning("QGraphicsLinearLayout::insertItem: cannot insert itself"); + return; + } + Q_ASSERT(item); + + //the order of the following instructions is very important because + //invalidating the layout before adding the child item will make the layout happen + //before we try to paint the item + invalidate(); + d->addChildLayoutItem(item); + + d->fixIndex(&index); + d->engine.insertRow(index, d->orientation); + new QGridLayoutItem(&d->engine, item, d->gridRow(index), d->gridColumn(index), 1, 1, 0, index); +} + +/*! + Inserts a stretch of \a stretch at \a index, or before any item that is + currently at \a index. + + \sa addStretch(), setStretchFactor(), setItemSpacing(), insertItem() +*/ +void QGraphicsLinearLayout::insertStretch(int index, int stretch) +{ + Q_D(QGraphicsLinearLayout); + d->fixIndex(&index); + d->engine.insertRow(index, d->orientation); + d->engine.setRowStretchFactor(index, stretch, d->orientation); + invalidate(); +} + +/*! + Removes \a item from the layout without destroying it. Ownership of + \a item is transferred to the caller. + + \sa removeAt(), insertItem() +*/ +void QGraphicsLinearLayout::removeItem(QGraphicsLayoutItem *item) +{ + Q_D(QGraphicsLinearLayout); + if (QGridLayoutItem *gridItem = d->engine.findLayoutItem(item)) { + item->setParentLayoutItem(0); + d->removeGridItem(gridItem); + delete gridItem; + invalidate(); + } +} + +/*! + Removes the item at \a index without destroying it. Ownership of the item + is transferred to the caller. + + \sa removeItem(), insertItem() +*/ +void QGraphicsLinearLayout::removeAt(int index) +{ + Q_D(QGraphicsLinearLayout); + if (index < 0 || index >= d->engine.itemCount()) { + qWarning("QGraphicsLinearLayout::removeAt: invalid index %d", index); + return; + } + if (QGridLayoutItem *gridItem = d->engine.itemAt(index)) { + if (QGraphicsLayoutItem *layoutItem = gridItem->layoutItem()) + layoutItem->setParentLayoutItem(0); + d->removeGridItem(gridItem); + delete gridItem; + invalidate(); + } +} + +/*! + Sets the layout's spacing to \a spacing. Spacing refers to the + vertical and horizontal distances between items. + + \sa setItemSpacing(), setStretchFactor(), QGraphicsGridLayout::setSpacing() +*/ +void QGraphicsLinearLayout::setSpacing(qreal spacing) +{ + Q_D(QGraphicsLinearLayout); + if (spacing < 0) { + qWarning("QGraphicsLinearLayout::setSpacing: invalid spacing %g", spacing); + return; + } + d->engine.setSpacing(spacing, Qt::Horizontal | Qt::Vertical); + invalidate(); +} + +/*! + Returns the layout's spacing. Spacing refers to the + vertical and horizontal distances between items. + + \sa setSpacing() + */ +qreal QGraphicsLinearLayout::spacing() const +{ + Q_D(const QGraphicsLinearLayout); + return d->engine.spacing(d->styleInfo(), d->orientation); +} + +/*! + Sets the spacing after item at \a index to \a spacing. +*/ +void QGraphicsLinearLayout::setItemSpacing(int index, qreal spacing) +{ + Q_D(QGraphicsLinearLayout); + d->engine.setRowSpacing(index, spacing, d->orientation); + invalidate(); +} +/*! + Returns the spacing after item at \a index. +*/ +qreal QGraphicsLinearLayout::itemSpacing(int index) const +{ + Q_D(const QGraphicsLinearLayout); + return d->engine.rowSpacing(index, d->orientation); +} + +/*! + Sets the stretch factor for \a item to \a stretch. If an item's stretch + factor changes, this function will invalidate the layout. + + Setting \a stretch to 0 removes the stretch factor from the item, and is + effectively equivalent to setting \a stretch to 1. + + \sa stretchFactor() +*/ +void QGraphicsLinearLayout::setStretchFactor(QGraphicsLayoutItem *item, int stretch) +{ + Q_D(QGraphicsLinearLayout); + if (!item) { + qWarning("QGraphicsLinearLayout::setStretchFactor: cannot assign" + " a stretch factor to a null item"); + return; + } + if (stretchFactor(item) == stretch) + return; + d->engine.setStretchFactor(item, stretch, d->orientation); + invalidate(); +} + +/*! + Returns the stretch factor for \a item. The default stretch factor is 0, + meaning that the item has no assigned stretch factor. + + \sa setStretchFactor() +*/ +int QGraphicsLinearLayout::stretchFactor(QGraphicsLayoutItem *item) const +{ + Q_D(const QGraphicsLinearLayout); + if (!item) { + qWarning("QGraphicsLinearLayout::setStretchFactor: cannot return" + " a stretch factor for a null item"); + return 0; + } + return d->engine.stretchFactor(item, d->orientation); +} + +/*! + Sets the alignment of \a item to \a alignment. If \a item's alignment + changes, the layout is automatically invalidated. + + \sa alignment(), invalidate() +*/ +void QGraphicsLinearLayout::setAlignment(QGraphicsLayoutItem *item, Qt::Alignment alignment) +{ + Q_D(QGraphicsLinearLayout); + if (this->alignment(item) == alignment) + return; + d->engine.setAlignment(item, alignment); + invalidate(); +} + +/*! + Returns the alignment for \a item. The default alignment is + Qt::AlignTop | Qt::AlignLeft. + + The alignment decides how the item is positioned within its assigned space + in the case where there's more space available in the layout than the + widgets can occupy. + + \sa setAlignment() +*/ +Qt::Alignment QGraphicsLinearLayout::alignment(QGraphicsLayoutItem *item) const +{ + Q_D(const QGraphicsLinearLayout); + return d->engine.alignment(item); +} + +#if 0 // ### +QSizePolicy::ControlTypes QGraphicsLinearLayout::controlTypes(LayoutSide side) const +{ + return d->engine.controlTypes(side); +} +#endif + +/*! + \reimp +*/ +int QGraphicsLinearLayout::count() const +{ + Q_D(const QGraphicsLinearLayout); + return d->engine.itemCount(); +} + +/*! + \reimp + When iterating from 0 and up, it will return the items in the visual arranged order. +*/ +QGraphicsLayoutItem *QGraphicsLinearLayout::itemAt(int index) const +{ + Q_D(const QGraphicsLinearLayout); + if (index < 0 || index >= d->engine.itemCount()) { + qWarning("QGraphicsLinearLayout::itemAt: invalid index %d", index); + return 0; + } + QGraphicsLayoutItem *item = 0; + if (QGridLayoutItem *gridItem = d->engine.itemAt(index)) + item = gridItem->layoutItem(); + return item; +} + +/*! + \reimp +*/ +void QGraphicsLinearLayout::setGeometry(const QRectF &rect) +{ + Q_D(QGraphicsLinearLayout); + QGraphicsLayout::setGeometry(rect); + QRectF effectiveRect = geometry(); + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + Qt::LayoutDirection visualDir = d->visualDirection(); + d->engine.setVisualDirection(visualDir); + if (visualDir == Qt::RightToLeft) + qSwap(left, right); + effectiveRect.adjust(+left, +top, -right, -bottom); +#ifdef QT_DEBUG + if (qt_graphicsLayoutDebug()) { + static int counter = 0; + qDebug() << counter++ << "QGraphicsLinearLayout::setGeometry - " << rect; + dump(1); + } +#endif + d->engine.setGeometries(d->styleInfo(), effectiveRect); +#ifdef QT_DEBUG + if (qt_graphicsLayoutDebug()) { + qDebug() << "post dump"; + dump(1); + } +#endif +} + +/*! + \reimp +*/ +QSizeF QGraphicsLinearLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_D(const QGraphicsLinearLayout); + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + const QSizeF extraMargins(left + right, top + bottom); + return d->engine.sizeHint(d->styleInfo(), which , constraint - extraMargins) + extraMargins; +} + +/*! + \reimp +*/ +void QGraphicsLinearLayout::invalidate() +{ + Q_D(QGraphicsLinearLayout); + d->engine.invalidate(); + QGraphicsLayout::invalidate(); +} + +/*! + \internal +*/ +void QGraphicsLinearLayout::dump(int indent) const +{ +#ifdef QT_DEBUG + if (qt_graphicsLayoutDebug()) { + Q_D(const QGraphicsLinearLayout); + qDebug("%*s%s layout", indent, "", + d->orientation == Qt::Horizontal ? "Horizontal" : "Vertical"); + d->engine.dump(indent + 1); + } +#else + Q_UNUSED(indent); +#endif +} + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicslinearlayout.h b/src/widgets/graphicsview/qgraphicslinearlayout.h new file mode 100644 index 0000000000..60749dbdaf --- /dev/null +++ b/src/widgets/graphicsview/qgraphicslinearlayout.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSLINEARLAYOUT_H +#define QGRAPHICSLINEARLAYOUT_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsLinearLayoutPrivate; + +class Q_GUI_EXPORT QGraphicsLinearLayout : public QGraphicsLayout +{ +public: + QGraphicsLinearLayout(QGraphicsLayoutItem *parent = 0); + QGraphicsLinearLayout(Qt::Orientation orientation, QGraphicsLayoutItem *parent = 0); + virtual ~QGraphicsLinearLayout(); + + void setOrientation(Qt::Orientation orientation); + Qt::Orientation orientation() const; + + inline void addItem(QGraphicsLayoutItem *item) { insertItem(-1, item); } + inline void addStretch(int stretch = 1) { insertStretch(-1, stretch); } + + void insertItem(int index, QGraphicsLayoutItem *item); + void insertStretch(int index, int stretch = 1); + + void removeItem(QGraphicsLayoutItem *item); + void removeAt(int index); + + void setSpacing(qreal spacing); + qreal spacing() const; + void setItemSpacing(int index, qreal spacing); + qreal itemSpacing(int index) const; + + void setStretchFactor(QGraphicsLayoutItem *item, int stretch); + int stretchFactor(QGraphicsLayoutItem *item) const; + + void setAlignment(QGraphicsLayoutItem *item, Qt::Alignment alignment); + Qt::Alignment alignment(QGraphicsLayoutItem *item) const; + + void setGeometry(const QRectF &rect); + + int count() const; + QGraphicsLayoutItem *itemAt(int index) const; + + void invalidate(); + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + +#if 0 // ### + Q5SizePolicy::ControlTypes controlTypes(LayoutSide side) const; +#endif + + void dump(int indent = 0) const; + +protected: +#if 0 + QSize contentsSizeHint(Qt::SizeHint which, const QSize &constraint = QSize()) const; +#endif + +private: + Q_DISABLE_COPY(QGraphicsLinearLayout) + Q_DECLARE_PRIVATE(QGraphicsLinearLayout) +}; + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif + diff --git a/src/widgets/graphicsview/qgraphicsproxywidget.cpp b/src/widgets/graphicsview/qgraphicsproxywidget.cpp new file mode 100644 index 0000000000..e6142e66ac --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsproxywidget.cpp @@ -0,0 +1,1570 @@ +/**************************************************************************** +** +** 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 QtGui module 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 "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicslayout.h" +#include "qgraphicsproxywidget.h" +#include "private/qgraphicsproxywidget_p.h" +#include "private/qwidget_p.h" +#include "private/qapplication_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +//#define GRAPHICSPROXYWIDGET_DEBUG + +/*! + \class QGraphicsProxyWidget + \brief The QGraphicsProxyWidget class provides a proxy layer for embedding + a QWidget in a QGraphicsScene. + \since 4.4 + \ingroup graphicsview-api + + QGraphicsProxyWidget embeds QWidget-based widgets, for example, a + QPushButton, QFontComboBox, or even QFileDialog, into + QGraphicsScene. It forwards events between the two objects and + translates between QWidget's integer-based geometry and + QGraphicsWidget's qreal-based geometry. QGraphicsProxyWidget + supports all core features of QWidget, including tab focus, + keyboard input, Drag & Drop, and popups. You can also embed + complex widgets, e.g., widgets with subwidgets. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsproxywidget.cpp 0 + + QGraphicsProxyWidget takes care of automatically embedding popup children + of embedded widgets through creating a child proxy for each popup. This + means that when an embedded QComboBox shows its popup list, a new + QGraphicsProxyWidget is created automatically, embedding the popup, and + positioning it correctly. This only works if the popup is child of the + embedded widget (for example QToolButton::setMenu() requires the QMenu instance + to be child of the QToolButton). + + \section1 Embedding a Widget with QGraphicsProxyWidget + + There are two ways to embed a widget using QGraphicsProxyWidget. The most + common way is to pass a widget pointer to QGraphicsScene::addWidget() + together with any relevant \l Qt::WindowFlags. This function returns a + pointer to a QGraphicsProxyWidget. You can then choose to reparent or + position either the proxy, or the embedded widget itself. + + For example, in the code snippet below, we embed a group box into the proxy: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsproxywidget.cpp 1 + + The image below is the output obtained with its contents margin and + contents rect labeled. + + \image qgraphicsproxywidget-embed.png + + Alternatively, you can start by creating a new QGraphicsProxyWidget item, + and then call setWidget() to embed a QWidget later. The widget() function + returns a pointer to the embedded widget. QGraphicsProxyWidget shares + ownership with QWidget, so if either of the two widgets are destroyed, the + other widget will be automatically destroyed as well. + + \section1 Synchronizing Widget States + + QGraphicsProxyWidget keeps its state in sync with the embedded widget. For + example, if the proxy is hidden or disabled, the embedded widget will be + hidden or disabled as well, and vice versa. When the widget is embedded by + calling addWidget(), QGraphicsProxyWidget copies the state from the widget + into the proxy, and after that, the two will stay synchronized where + possible. By default, when you embed a widget into a proxy, both the widget + and the proxy will be visible because a QGraphicsWidget is visible when + created (you do not have to call show()). If you explicitly hide the + embedded widget, the proxy will also become invisible. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsproxywidget.cpp 2 + + QGraphicsProxyWidget maintains symmetry for the following states: + + \table + \header \o QWidget state \o QGraphicsProxyWidget state \o Notes + \row \o QWidget::enabled + \o QGraphicsProxyWidget::enabled + \o + \row \o QWidget::visible + \o QGraphicsProxyWidget::visible + \o The explicit state is also symmetric. + \row \o QWidget::geometry + \o QGraphicsProxyWidget::geometry + \o Geometry is only guaranteed to be symmetric while + the embedded widget is visible. + \row \o QWidget::layoutDirection + \o QGraphicsProxyWidget::layoutDirection + \o + \row \o QWidget::style + \o QGraphicsProxyWidget::style + \o + \row \o QWidget::palette + \o QGraphicsProxyWidget::palette + \o + \row \o QWidget::font + \o QGraphicsProxyWidget::font + \o + \row \o QWidget::cursor + \o QGraphicsProxyWidget::cursor + \o The embedded widget overrides the proxy widget + cursor. The proxy cursor changes depending on + which embedded subwidget is currently under the + mouse. + \row \o QWidget::sizeHint() + \o QGraphicsProxyWidget::sizeHint() + \o All size hint functionality from the embedded + widget is forwarded by the proxy. + \row \o QWidget::getContentsMargins() + \o QGraphicsProxyWidget::getContentsMargins() + \o Updated once by setWidget(). + \row \o QWidget::windowTitle + \o QGraphicsProxyWidget::windowTitle + \o Updated once by setWidget(). + \endtable + + \note QGraphicsScene keeps the embedded widget in a special state that + prevents it from disturbing other widgets (both embedded and not embedded) + while the widget is embedded. In this state, the widget may differ slightly + in behavior from when it is not embedded. + + \warning This class is provided for convenience when bridging + QWidgets and QGraphicsItems, it should not be used for + high-performance scenarios. + + \sa QGraphicsScene::addWidget(), QGraphicsWidget +*/ + +extern bool qt_sendSpontaneousEvent(QObject *, QEvent *); +Q_GUI_EXPORT extern bool qt_tab_all_widgets; + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::init() +{ + Q_Q(QGraphicsProxyWidget); + q->setFocusPolicy(Qt::WheelFocus); + q->setAcceptDrops(true); +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::sendWidgetMouseEvent(QGraphicsSceneHoverEvent *event) +{ + QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseMove); + mouseEvent.setPos(event->pos()); + mouseEvent.setScreenPos(event->screenPos()); + mouseEvent.setButton(Qt::NoButton); + mouseEvent.setButtons(0); + mouseEvent.setModifiers(event->modifiers()); + sendWidgetMouseEvent(&mouseEvent); + event->setAccepted(mouseEvent.isAccepted()); +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::sendWidgetMouseEvent(QGraphicsSceneMouseEvent *event) +{ + if (!event || !widget || !widget->isVisible()) + return; + Q_Q(QGraphicsProxyWidget); + + // Find widget position and receiver. + QPointF pos = event->pos(); + QPointer alienWidget = widget->childAt(pos.toPoint()); + QPointer receiver = alienWidget ? alienWidget : widget; + + if (QWidgetPrivate::nearestGraphicsProxyWidget(receiver) != q) + return; //another proxywidget will handle the events + + // Translate QGraphicsSceneMouse events to QMouseEvents. + QEvent::Type type = QEvent::None; + switch (event->type()) { + case QEvent::GraphicsSceneMousePress: + type = QEvent::MouseButtonPress; + if (!embeddedMouseGrabber) + embeddedMouseGrabber = receiver; + else + receiver = embeddedMouseGrabber; + break; + case QEvent::GraphicsSceneMouseRelease: + type = QEvent::MouseButtonRelease; + if (embeddedMouseGrabber) + receiver = embeddedMouseGrabber; + break; + case QEvent::GraphicsSceneMouseDoubleClick: + type = QEvent::MouseButtonDblClick; + if (!embeddedMouseGrabber) + embeddedMouseGrabber = receiver; + else + receiver = embeddedMouseGrabber; + break; + case QEvent::GraphicsSceneMouseMove: + type = QEvent::MouseMove; + if (embeddedMouseGrabber) + receiver = embeddedMouseGrabber; + break; + default: + Q_ASSERT_X(false, "QGraphicsProxyWidget", "internal error"); + break; + } + + if (!lastWidgetUnderMouse) { + QApplicationPrivate::dispatchEnterLeave(embeddedMouseGrabber ? embeddedMouseGrabber : receiver, 0); + lastWidgetUnderMouse = receiver; + } + + // Map event position from us to the receiver + pos = mapToReceiver(pos, receiver); + + // Send mouse event. + QMouseEvent *mouseEvent = QMouseEvent::createExtendedMouseEvent(type, pos, + receiver->mapToGlobal(pos.toPoint()), event->button(), + event->buttons(), event->modifiers()); + + QWidget *embeddedMouseGrabberPtr = (QWidget *)embeddedMouseGrabber; + QApplicationPrivate::sendMouseEvent(receiver, mouseEvent, alienWidget, widget, + &embeddedMouseGrabberPtr, lastWidgetUnderMouse, event->spontaneous()); + embeddedMouseGrabber = embeddedMouseGrabberPtr; + + // Handle enter/leave events when last button is released from mouse + // grabber child widget. + if (embeddedMouseGrabber && type == QEvent::MouseButtonRelease && !event->buttons()) { + Q_Q(QGraphicsProxyWidget); + if (q->rect().contains(event->pos()) && q->acceptsHoverEvents()) + lastWidgetUnderMouse = alienWidget ? alienWidget : widget; + else // released on the frame our outside the item, or doesn't accept hover events. + lastWidgetUnderMouse = 0; + + QApplicationPrivate::dispatchEnterLeave(lastWidgetUnderMouse, embeddedMouseGrabber); + embeddedMouseGrabber = 0; + +#ifndef QT_NO_CURSOR + // ### Restore the cursor, don't override it. + if (!lastWidgetUnderMouse) + q->unsetCursor(); +#endif + } + + event->setAccepted(mouseEvent->isAccepted()); + delete mouseEvent; +} + +void QGraphicsProxyWidgetPrivate::sendWidgetKeyEvent(QKeyEvent *event) +{ + Q_Q(QGraphicsProxyWidget); + if (!event || !widget || !widget->isVisible()) + return; + + QPointer receiver = widget->focusWidget(); + if (!receiver) + receiver = widget; + Q_ASSERT(receiver); + + do { + bool res = QApplication::sendEvent(receiver, event); + if ((res && event->isAccepted()) || (q->isWindow() && receiver == widget)) + break; + receiver = receiver->parentWidget(); + } while (receiver); +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::removeSubFocusHelper(QWidget *widget, Qt::FocusReason reason) +{ + QFocusEvent event(QEvent::FocusOut, reason); + QPointer widgetGuard = widget; + QApplication::sendEvent(widget, &event); + if (widgetGuard && event.isAccepted()) + QApplication::sendEvent(widget->style(), &event); +} + +/*! + \internal + + Reimplemented from QGraphicsItemPrivate. ### Qt 5: Move impl to + reimplementation QGraphicsProxyWidget::inputMethodQuery(). +*/ +QVariant QGraphicsProxyWidgetPrivate::inputMethodQueryHelper(Qt::InputMethodQuery query) const +{ + Q_Q(const QGraphicsProxyWidget); + if (!widget || !q->hasFocus()) + return QVariant(); + + QWidget *focusWidget = widget->focusWidget(); + if (!focusWidget) + focusWidget = widget; + QVariant v = focusWidget->inputMethodQuery(query); + QPointF focusWidgetPos = q->subWidgetRect(focusWidget).topLeft(); + switch (v.type()) { + case QVariant::RectF: + v = v.toRectF().translated(focusWidgetPos); + break; + case QVariant::PointF: + v = v.toPointF() + focusWidgetPos; + break; + case QVariant::Rect: + v = v.toRect().translated(focusWidgetPos.toPoint()); + break; + case QVariant::Point: + v = v.toPoint() + focusWidgetPos.toPoint(); + break; + default: + break; + } + return v; +} + +/*! + \internal + Some of the logic is shared with QApplicationPrivate::focusNextPrevChild_helper +*/ +QWidget *QGraphicsProxyWidgetPrivate::findFocusChild(QWidget *child, bool next) const +{ + if (!widget) + return 0; + + // Run around the focus chain until we find a widget that can take tab focus. + if (!child) { + child = next ? (QWidget *)widget : widget->d_func()->focus_prev; + } else { + child = next ? child->d_func()->focus_next : child->d_func()->focus_prev; + if ((next && child == widget) || (!next && child == widget->d_func()->focus_prev)) { + return 0; + } + } + + QWidget *oldChild = child; + uint focus_flag = qt_tab_all_widgets ? Qt::TabFocus : Qt::StrongFocus; + do { + if (child->isEnabled() + && child->isVisibleTo(widget) + && ((child->focusPolicy() & focus_flag) == focus_flag) + && !(child->d_func()->extra && child->d_func()->extra->focus_proxy)) { + return child; + } + child = next ? child->d_func()->focus_next : child->d_func()->focus_prev; + } while (child != oldChild && !(next && child == widget) && !(!next && child == widget->d_func()->focus_prev)); + return 0; +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::_q_removeWidgetSlot() +{ + Q_Q(QGraphicsProxyWidget); + widget = 0; + delete q; +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::updateWidgetGeometryFromProxy() +{ +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::updateProxyGeometryFromWidget() +{ + Q_Q(QGraphicsProxyWidget); + if (!widget) + return; + + QRectF widgetGeometry = widget->geometry(); + QWidget *parentWidget = widget->parentWidget(); + if (widget->isWindow()) { + QGraphicsProxyWidget *proxyParent = 0; + if (parentWidget && (proxyParent = qobject_cast(q->parentWidget()))) { + // Nested window proxy (e.g., combobox popup), map widget to the + // parent widget's global coordinates, and map that to the parent + // proxy's child coordinates. + widgetGeometry.moveTo(proxyParent->subWidgetRect(parentWidget).topLeft() + + parentWidget->mapFromGlobal(widget->pos())); + } + } + + // Adjust to size hint if the widget has never been resized. + if (!widget->size().isValid()) + widgetGeometry.setSize(widget->sizeHint()); + + // Assign new geometry. + posChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + sizeChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + q->setGeometry(widgetGeometry); + posChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + sizeChangeMode = QGraphicsProxyWidgetPrivate::NoMode; +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::updateProxyInputMethodAcceptanceFromWidget() +{ + Q_Q(QGraphicsProxyWidget); + if (!widget) + return; + + QWidget *focusWidget = widget->focusWidget(); + if (!focusWidget) + focusWidget = widget; + q->setFlag(QGraphicsItem::ItemAcceptsInputMethod, + focusWidget->testAttribute(Qt::WA_InputMethodEnabled)); +} + +/*! + \internal + + Embeds \a subWin as a subwindow of this proxy widget. \a subWin must be a top-level + widget and a descendant of the widget managed by this proxy. A separate subproxy + will be created as a child of this proxy widget to manage \a subWin. +*/ +void QGraphicsProxyWidgetPrivate::embedSubWindow(QWidget *subWin) +{ + QWExtra *extra; + if (!((extra = subWin->d_func()->extra) && extra->proxyWidget)) { + QGraphicsProxyWidget *subProxy = new QGraphicsProxyWidget(q_func(), subWin->windowFlags()); + subProxy->d_func()->setWidget_helper(subWin, false); + } +} + +/*! + \internal + + Removes ("unembeds") \a subWin and deletes the proxy holder item. This can + happen when QWidget::setParent() reparents the embedded window out of + "embedded space". +*/ +void QGraphicsProxyWidgetPrivate::unembedSubWindow(QWidget *subWin) +{ + foreach (QGraphicsItem *child, children) { + if (child->isWidget()) { + if (QGraphicsProxyWidget *proxy = qobject_cast(static_cast(child))) { + if (proxy->widget() == subWin) { + proxy->setWidget(0); + scene->removeItem(proxy); + delete proxy; + return; + } + } + } + } +} + +bool QGraphicsProxyWidgetPrivate::isProxyWidget() const +{ + return true; +} + +/*! + \internal +*/ +QPointF QGraphicsProxyWidgetPrivate::mapToReceiver(const QPointF &pos, const QWidget *receiver) const +{ + QPointF p = pos; + // Map event position from us to the receiver, preserving its + // precision (don't use QWidget::mapFrom here). + while (receiver && receiver != widget) { + p -= QPointF(receiver->pos()); + receiver = receiver->parentWidget(); + } + return p; +} + +/*! + Constructs a new QGraphicsProxy widget. \a parent and \a wFlags are passed + to QGraphicsItem's constructor. +*/ +QGraphicsProxyWidget::QGraphicsProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) + : QGraphicsWidget(*new QGraphicsProxyWidgetPrivate, parent, 0, wFlags) +{ + Q_D(QGraphicsProxyWidget); + d->init(); +} + +/*! + Destroys the proxy widget and any embedded widget. +*/ +QGraphicsProxyWidget::~QGraphicsProxyWidget() +{ + Q_D(QGraphicsProxyWidget); + if (d->widget) { + QObject::disconnect(d->widget, SIGNAL(destroyed()), this, SLOT(_q_removeWidgetSlot())); + delete d->widget; + } +} + +/*! + Embeds \a widget into this proxy widget. The embedded widget must reside + exclusively either inside or outside of Graphics View. You cannot embed a + widget as long as it is is visible elsewhere in the UI, at the same time. + + \a widget must be a top-level widget whose parent is 0. + + When the widget is embedded, its state (e.g., visible, enabled, geometry, + size hints) is copied into the proxy widget. If the embedded widget is + explicitly hidden or disabled, the proxy widget will become explicitly + hidden or disabled after embedding is complete. The class documentation + has a full overview over the shared state. + + QGraphicsProxyWidget's window flags determine whether the widget, after + embedding, will be given window decorations or not. + + After this function returns, QGraphicsProxyWidget will keep its state + synchronized with that of \a widget whenever possible. + + If a widget is already embedded by this proxy when this function is + called, that widget will first be automatically unembedded. Passing 0 for + the \a widget argument will only unembed the widget, and the ownership of + the currently embedded widget will be passed on to the caller. + Every child widget that are embedded will also be embedded and their proxy + widget destroyed. + + Note that widgets with the Qt::WA_PaintOnScreen widget attribute + set and widgets that wrap an external application or controller + cannot be embedded. Examples are QGLWidget and QAxWidget. + + \sa widget() +*/ +void QGraphicsProxyWidget::setWidget(QWidget *widget) +{ + Q_D(QGraphicsProxyWidget); + d->setWidget_helper(widget, true); +} + +void QGraphicsProxyWidgetPrivate::setWidget_helper(QWidget *newWidget, bool autoShow) +{ + Q_Q(QGraphicsProxyWidget); + if (newWidget == widget) + return; + if (widget) { + QObject::disconnect(widget, SIGNAL(destroyed()), q, SLOT(_q_removeWidgetSlot())); + widget->removeEventFilter(q); + widget->setAttribute(Qt::WA_DontShowOnScreen, false); + widget->d_func()->extra->proxyWidget = 0; + resolveFont(inheritedFontResolveMask); + resolvePalette(inheritedPaletteResolveMask); + widget->update(); + + foreach (QGraphicsItem *child, q->childItems()) { + if (child->d_ptr->isProxyWidget()) { + QGraphicsProxyWidget *childProxy = static_cast(child); + QWidget * parent = childProxy->widget(); + while (parent->parentWidget() != 0) { + if (parent == widget) + break; + parent = parent->parentWidget(); + } + if (!childProxy->widget() || parent != widget) + continue; + childProxy->setWidget(0); + delete childProxy; + } + } + + widget = 0; +#ifndef QT_NO_CURSOR + q->unsetCursor(); +#endif + q->setAcceptHoverEvents(false); + if (!newWidget) + q->update(); + } + if (!newWidget) + return; + if (!newWidget->isWindow()) { + QWExtra *extra = newWidget->parentWidget()->d_func()->extra; + if (!extra || !extra->proxyWidget) { + qWarning("QGraphicsProxyWidget::setWidget: cannot embed widget %p " + "which is not a toplevel widget, and is not a child of an embedded widget", newWidget); + return; + } + } + + // Register this proxy within the widget's private. + // ### This is a bit backdoorish + QWExtra *extra = newWidget->d_func()->extra; + if (!extra) { + newWidget->d_func()->createExtra(); + extra = newWidget->d_func()->extra; + } + QGraphicsProxyWidget **proxyWidget = &extra->proxyWidget; + if (*proxyWidget) { + if (*proxyWidget != q) { + qWarning("QGraphicsProxyWidget::setWidget: cannot embed widget %p" + "; already embedded", newWidget); + } + return; + } + *proxyWidget = q; + + newWidget->setAttribute(Qt::WA_DontShowOnScreen); + newWidget->ensurePolished(); + // Do not wait for this widget to close before the app closes ### + // shouldn't this widget inherit the attribute? + newWidget->setAttribute(Qt::WA_QuitOnClose, false); + q->setAcceptHoverEvents(true); + + if (newWidget->testAttribute(Qt::WA_NoSystemBackground)) + q->setAttribute(Qt::WA_NoSystemBackground); + if (newWidget->testAttribute(Qt::WA_OpaquePaintEvent)) + q->setAttribute(Qt::WA_OpaquePaintEvent); + + widget = newWidget; + + // Changes only go from the widget to the proxy. + enabledChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + visibleChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + posChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + sizeChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + + if ((autoShow && !newWidget->testAttribute(Qt::WA_WState_ExplicitShowHide)) || !newWidget->testAttribute(Qt::WA_WState_Hidden)) { + newWidget->show(); + } + + // Copy the state from the widget onto the proxy. +#ifndef QT_NO_CURSOR + if (newWidget->testAttribute(Qt::WA_SetCursor)) + q->setCursor(widget->cursor()); +#endif + q->setEnabled(newWidget->isEnabled()); + q->setVisible(newWidget->isVisible()); + q->setLayoutDirection(newWidget->layoutDirection()); + if (newWidget->testAttribute(Qt::WA_SetStyle)) + q->setStyle(widget->style()); + + resolveFont(inheritedFontResolveMask); + resolvePalette(inheritedPaletteResolveMask); + + if (!newWidget->testAttribute(Qt::WA_Resized)) + newWidget->adjustSize(); + + int left, top, right, bottom; + newWidget->getContentsMargins(&left, &top, &right, &bottom); + q->setContentsMargins(left, top, right, bottom); + q->setWindowTitle(newWidget->windowTitle()); + + // size policies and constraints.. + q->setSizePolicy(newWidget->sizePolicy()); + QSize sz = newWidget->minimumSize(); + q->setMinimumSize(sz.isNull() ? QSizeF() : QSizeF(sz)); + sz = newWidget->maximumSize(); + q->setMaximumSize(sz.isNull() ? QSizeF() : QSizeF(sz)); + + updateProxyGeometryFromWidget(); + + updateProxyInputMethodAcceptanceFromWidget(); + + // Hook up the event filter to keep the state up to date. + newWidget->installEventFilter(q); + QObject::connect(newWidget, SIGNAL(destroyed()), q, SLOT(_q_removeWidgetSlot())); + + // Changes no longer go only from the widget to the proxy. + enabledChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + visibleChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + posChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + sizeChangeMode = QGraphicsProxyWidgetPrivate::NoMode; +} + +/*! + Returns a pointer to the embedded widget. + + \sa setWidget() +*/ +QWidget *QGraphicsProxyWidget::widget() const +{ + Q_D(const QGraphicsProxyWidget); + return d->widget; +} + +/*! + Returns the rectangle for \a widget, which must be a descendant of + widget(), or widget() itself, in this proxy item's local coordinates. + + If no widget is embedded, \a widget is 0, or \a widget is not a + descendant of the embedded widget, this function returns an empty QRectF. + + \sa widget() +*/ +QRectF QGraphicsProxyWidget::subWidgetRect(const QWidget *widget) const +{ + Q_D(const QGraphicsProxyWidget); + if (!widget || !d->widget) + return QRectF(); + if (d->widget == widget || d->widget->isAncestorOf(widget)) + return QRectF(widget->mapTo(d->widget, QPoint(0, 0)), widget->size()); + return QRectF(); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::setGeometry(const QRectF &rect) +{ + Q_D(QGraphicsProxyWidget); + bool proxyResizesWidget = !d->posChangeMode && !d->sizeChangeMode; + if (proxyResizesWidget) { + d->posChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + d->sizeChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + } + QGraphicsWidget::setGeometry(rect); + if (proxyResizesWidget) { + d->posChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + d->sizeChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } +} + +/*! + \reimp +*/ +QVariant QGraphicsProxyWidget::itemChange(GraphicsItemChange change, + const QVariant &value) +{ + Q_D(QGraphicsProxyWidget); + + switch (change) { + case ItemPositionChange: + // The item's position is either changed directly on the proxy, in + // which case the position change should propagate to the widget, + // otherwise it happens as a side effect when filtering QEvent::Move. + if (!d->posChangeMode) + d->posChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + break; + case ItemPositionHasChanged: + // Move the internal widget if we're in widget-to-proxy + // mode. Otherwise the widget has already moved. + if (d->widget && d->posChangeMode != QGraphicsProxyWidgetPrivate::WidgetToProxyMode) + d->widget->move(value.toPoint()); + if (d->posChangeMode == QGraphicsProxyWidgetPrivate::ProxyToWidgetMode) + d->posChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + break; + case ItemVisibleChange: + if (!d->visibleChangeMode) + d->visibleChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + break; + case ItemVisibleHasChanged: + if (d->widget && d->visibleChangeMode != QGraphicsProxyWidgetPrivate::WidgetToProxyMode) + d->widget->setVisible(isVisible()); + if (d->visibleChangeMode == QGraphicsProxyWidgetPrivate::ProxyToWidgetMode) + d->visibleChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + break; + case ItemEnabledChange: + if (!d->enabledChangeMode) + d->enabledChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + break; + case ItemEnabledHasChanged: + if (d->widget && d->enabledChangeMode != QGraphicsProxyWidgetPrivate::WidgetToProxyMode) + d->widget->setEnabled(isEnabled()); + if (d->enabledChangeMode == QGraphicsProxyWidgetPrivate::ProxyToWidgetMode) + d->enabledChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + break; + default: + break; + } + return QGraphicsWidget::itemChange(change, value); +} + +/*! + \reimp +*/ +bool QGraphicsProxyWidget::event(QEvent *event) +{ + Q_D(QGraphicsProxyWidget); + if (!d->widget) + return QGraphicsWidget::event(event); + + switch (event->type()) { + case QEvent::StyleChange: + // Propagate style changes to the embedded widget. + if (!d->styleChangeMode) { + d->styleChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + d->widget->setStyle(style()); + d->styleChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } + break; + case QEvent::FontChange: { + // Propagate to widget. + QWidgetPrivate *wd = d->widget->d_func(); + int mask = d->font.resolve() | d->inheritedFontResolveMask; + wd->inheritedFontResolveMask = mask; + wd->resolveFont(); + break; + } + case QEvent::PaletteChange: { + // Propagate to widget. + QWidgetPrivate *wd = d->widget->d_func(); + int mask = d->palette.resolve() | d->inheritedPaletteResolveMask; + wd->inheritedPaletteResolveMask = mask; + wd->resolvePalette(); + break; + } + case QEvent::InputMethod: { + // Forward input method events if the focus widget enables + // input methods. + // ### Qt 4.5: this code must also go into a reimplementation + // of inputMethodEvent(). + QWidget *focusWidget = d->widget->focusWidget(); + if (focusWidget && focusWidget->testAttribute(Qt::WA_InputMethodEnabled)) + QApplication::sendEvent(focusWidget, event); + break; + } + case QEvent::ShortcutOverride: { + QWidget *focusWidget = d->widget->focusWidget(); + while (focusWidget) { + QApplication::sendEvent(focusWidget, event); + if (event->isAccepted()) + return true; + focusWidget = focusWidget->parentWidget(); + } + return false; + } + case QEvent::KeyPress: { + QKeyEvent *k = static_cast(event); + if (k->key() == Qt::Key_Tab || k->key() == Qt::Key_Backtab) { + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + QWidget *focusWidget = d->widget->focusWidget(); + while (focusWidget) { + bool res = QApplication::sendEvent(focusWidget, event); + if ((res && event->isAccepted()) || (isWindow() && focusWidget == d->widget)) { + event->accept(); + break; + } + focusWidget = focusWidget->parentWidget(); + } + return true; + } + } + break; + } +#ifndef QT_NO_TOOLTIP + case QEvent::GraphicsSceneHelp: { + // Propagate the help event (for tooltip) to the widget under mouse + if (d->lastWidgetUnderMouse) { + QGraphicsSceneHelpEvent *he = static_cast(event); + QPoint pos = d->mapToReceiver(mapFromScene(he->scenePos()), d->lastWidgetUnderMouse).toPoint(); + QHelpEvent e(QEvent::ToolTip, pos, he->screenPos()); + QApplication::sendEvent(d->lastWidgetUnderMouse, &e); + event->setAccepted(e.isAccepted()); + return e.isAccepted(); + } + break; + } + case QEvent::ToolTipChange: { + // Propagate tooltip change to the widget + if (!d->tooltipChangeMode) { + d->tooltipChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + d->widget->setToolTip(toolTip()); + d->tooltipChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } + break; + } +#endif + default: + break; + } + return QGraphicsWidget::event(event); +} + +/*! + \reimp +*/ +bool QGraphicsProxyWidget::eventFilter(QObject *object, QEvent *event) +{ + Q_D(QGraphicsProxyWidget); + + if (object == d->widget) { + switch (event->type()) { + case QEvent::LayoutRequest: + updateGeometry(); + break; + case QEvent::Resize: + // If the widget resizes itself, we resize the proxy too. + // Prevent feed-back by checking the geometry change mode. + if (!d->sizeChangeMode) + d->updateProxyGeometryFromWidget(); + break; + case QEvent::Move: + // If the widget moves itself, we move the proxy too. Prevent + // feed-back by checking the geometry change mode. + if (!d->posChangeMode) + d->updateProxyGeometryFromWidget(); + break; + case QEvent::Hide: + case QEvent::Show: + // If the widget toggles its visible state, the proxy will follow. + if (!d->visibleChangeMode) { + d->visibleChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + setVisible(event->type() == QEvent::Show); + d->visibleChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } + break; + case QEvent::EnabledChange: + // If the widget toggles its enabled state, the proxy will follow. + if (!d->enabledChangeMode) { + d->enabledChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + setEnabled(d->widget->isEnabled()); + d->enabledChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } + break; + case QEvent::StyleChange: + // Propagate style changes to the proxy. + if (!d->styleChangeMode) { + d->styleChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + setStyle(d->widget->style()); + d->styleChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } + break; +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTipChange: + // Propagate tooltip change to the proxy. + if (!d->tooltipChangeMode) { + d->tooltipChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + setToolTip(d->widget->toolTip()); + d->tooltipChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } + break; +#endif + default: + break; + } + } + return QGraphicsWidget::eventFilter(object, event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::showEvent(QShowEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::hideEvent(QHideEvent *event) +{ + Q_UNUSED(event); +} + +#ifndef QT_NO_CONTEXTMENU +/*! + \reimp +*/ +void QGraphicsProxyWidget::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + Q_D(QGraphicsProxyWidget); + if (!event || !d->widget || !d->widget->isVisible() || !hasFocus()) + return; + + // Find widget position and receiver. + QPointF pos = event->pos(); + QPointer alienWidget = d->widget->childAt(pos.toPoint()); + QPointer receiver = alienWidget ? alienWidget : d->widget; + + // Map event position from us to the receiver + pos = d->mapToReceiver(pos, receiver); + + QPoint globalPos = receiver->mapToGlobal(pos.toPoint()); + //If the receiver by-pass the proxy its popups + //will be top level QWidgets therefore they need + //the screen position. mapToGlobal expect the widget to + //have proper coordinates in regards of the windowing system + //but it's not true because the widget is embedded. + if (bypassGraphicsProxyWidget(receiver)) + globalPos = event->screenPos(); + + // Send mouse event. ### Doesn't propagate the event. + QContextMenuEvent contextMenuEvent(QContextMenuEvent::Reason(event->reason()), + pos.toPoint(), globalPos, event->modifiers()); + QApplication::sendEvent(receiver, &contextMenuEvent); + + event->setAccepted(contextMenuEvent.isAccepted()); +} +#endif // QT_NO_CONTEXTMENU + +#ifndef QT_NO_DRAGANDDROP +/*! + \reimp +*/ +void QGraphicsProxyWidget::dragEnterEvent(QGraphicsSceneDragDropEvent *event) +{ +#ifdef QT_NO_DRAGANDDROP + Q_UNUSED(event); +#else + Q_D(QGraphicsProxyWidget); + if (!d->widget) + return; + + QDragEnterEvent proxyDragEnter(event->pos().toPoint(), event->dropAction(), event->mimeData(), event->buttons(), event->modifiers()); + proxyDragEnter.setAccepted(event->isAccepted()); + QApplication::sendEvent(d->widget, &proxyDragEnter); + event->setAccepted(proxyDragEnter.isAccepted()); + if (proxyDragEnter.isAccepted()) // we discard answerRect + event->setDropAction(proxyDragEnter.dropAction()); +#endif +} +/*! + \reimp +*/ +void QGraphicsProxyWidget::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_UNUSED(event); +#ifndef QT_NO_DRAGANDDROP + Q_D(QGraphicsProxyWidget); + if (!d->widget || !d->dragDropWidget) + return; + QDragLeaveEvent proxyDragLeave; + QApplication::sendEvent(d->dragDropWidget, &proxyDragLeave); + d->dragDropWidget = 0; +#endif +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::dragMoveEvent(QGraphicsSceneDragDropEvent *event) +{ +#ifdef QT_NO_DRAGANDDROP + Q_UNUSED(event); +#else + Q_D(QGraphicsProxyWidget); + if (!d->widget) + return; + QPointF p = event->pos(); + event->ignore(); + QPointer subWidget = d->widget->childAt(p.toPoint()); + QPointer receiver = subWidget ? subWidget : d->widget; + bool eventDelivered = false; + for (; receiver; receiver = receiver->parentWidget()) { + if (!receiver->isEnabled() || !receiver->acceptDrops()) + continue; + // Map event position from us to the receiver + QPoint receiverPos = d->mapToReceiver(p, receiver).toPoint(); + if (receiver != d->dragDropWidget) { + // Try to enter before we leave + QDragEnterEvent dragEnter(receiverPos, event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers()); + dragEnter.setDropAction(event->proposedAction()); + QApplication::sendEvent(receiver, &dragEnter); + event->setAccepted(dragEnter.isAccepted()); + event->setDropAction(dragEnter.dropAction()); + if (!event->isAccepted()) { + // propagate to the parent widget + continue; + } + + d->lastDropAction = event->dropAction(); + + if (d->dragDropWidget) { + QDragLeaveEvent dragLeave; + QApplication::sendEvent(d->dragDropWidget, &dragLeave); + } + d->dragDropWidget = receiver; + } + + QDragMoveEvent dragMove(receiverPos, event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers()); + event->setDropAction(d->lastDropAction); + QApplication::sendEvent(receiver, &dragMove); + event->setAccepted(dragMove.isAccepted()); + event->setDropAction(dragMove.dropAction()); + if (event->isAccepted()) + d->lastDropAction = event->dropAction(); + eventDelivered = true; + break; + } + + if (!eventDelivered) { + if (d->dragDropWidget) { + // Leave the last drag drop item + QDragLeaveEvent dragLeave; + QApplication::sendEvent(d->dragDropWidget, &dragLeave); + d->dragDropWidget = 0; + } + // Propagate + event->setDropAction(Qt::IgnoreAction); + } +#endif +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::dropEvent(QGraphicsSceneDragDropEvent *event) +{ +#ifdef QT_NO_DRAGANDDROP + Q_UNUSED(event); +#else + Q_D(QGraphicsProxyWidget); + if (d->widget && d->dragDropWidget) { + QPoint widgetPos = d->mapToReceiver(event->pos(), d->dragDropWidget).toPoint(); + QDropEvent dropEvent(widgetPos, event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers()); + QApplication::sendEvent(d->dragDropWidget, &dropEvent); + event->setAccepted(dropEvent.isAccepted()); + d->dragDropWidget = 0; + } +#endif +} +#endif + +/*! + \reimp +*/ +void QGraphicsProxyWidget::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + Q_D(QGraphicsProxyWidget); + // If hoverMove was compressed away, make sure we update properly here. + if (d->lastWidgetUnderMouse) { + QApplicationPrivate::dispatchEnterLeave(0, d->lastWidgetUnderMouse); + d->lastWidgetUnderMouse = 0; + } +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::hoverMoveEvent"; +#endif + // Ignore events on the window frame. + if (!d->widget || !rect().contains(event->pos())) { + if (d->lastWidgetUnderMouse) { + QApplicationPrivate::dispatchEnterLeave(0, d->lastWidgetUnderMouse); + d->lastWidgetUnderMouse = 0; + } + return; + } + + d->embeddedMouseGrabber = 0; + d->sendWidgetMouseEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::grabMouseEvent(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::ungrabMouseEvent(QEvent *event) +{ + Q_D(QGraphicsProxyWidget); + Q_UNUSED(event); + d->embeddedMouseGrabber = 0; +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::mouseMoveEvent"; +#endif + d->sendWidgetMouseEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::mousePressEvent"; +#endif + d->sendWidgetMouseEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::mouseDoubleClickEvent"; +#endif + d->sendWidgetMouseEvent(event); +} + +/*! + \reimp +*/ +#ifndef QT_NO_WHEELEVENT +void QGraphicsProxyWidget::wheelEvent(QGraphicsSceneWheelEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::wheelEvent"; +#endif + if (!d->widget) + return; + + QPointF pos = event->pos(); + QPointer receiver = d->widget->childAt(pos.toPoint()); + if (!receiver) + receiver = d->widget; + + // Map event position from us to the receiver + pos = d->mapToReceiver(pos, receiver); + + // Send mouse event. + QWheelEvent wheelEvent(pos.toPoint(), event->screenPos(), event->delta(), + event->buttons(), event->modifiers(), event->orientation()); + QPointer focusWidget = d->widget->focusWidget(); + extern bool qt_sendSpontaneousEvent(QObject *, QEvent *); + qt_sendSpontaneousEvent(receiver, &wheelEvent); + event->setAccepted(wheelEvent.isAccepted()); + + // ### Remove, this should be done by proper focusIn/focusOut events. + if (focusWidget && !focusWidget->hasFocus()) { + focusWidget->update(); + focusWidget = d->widget->focusWidget(); + if (focusWidget && focusWidget->hasFocus()) + focusWidget->update(); + } +} +#endif + +/*! + \reimp +*/ +void QGraphicsProxyWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::mouseReleaseEvent"; +#endif + d->sendWidgetMouseEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::keyPressEvent(QKeyEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::keyPressEvent"; +#endif + d->sendWidgetKeyEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::keyReleaseEvent"; +#endif + d->sendWidgetKeyEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::focusInEvent(QFocusEvent *event) +{ +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::focusInEvent"; +#endif + Q_D(QGraphicsProxyWidget); + + if (d->focusFromWidgetToProxy) { + // Prevent recursion when the proxy autogains focus through the + // embedded widget calling setFocus(). ### Could be done with event + // filter on FocusIn instead? + return; + } + + d->proxyIsGivingFocus = true; + + switch (event->reason()) { + case Qt::TabFocusReason: { + if (QWidget *focusChild = d->findFocusChild(0, true)) + focusChild->setFocus(event->reason()); + break; + } + case Qt::BacktabFocusReason: + if (QWidget *focusChild = d->findFocusChild(0, false)) + focusChild->setFocus(event->reason()); + break; + default: + if (d->widget && d->widget->focusWidget()) { + d->widget->focusWidget()->setFocus(event->reason()); + } + break; + } + + d->proxyIsGivingFocus = false; +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::focusOutEvent(QFocusEvent *event) +{ +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::focusOutEvent"; +#endif + Q_D(QGraphicsProxyWidget); + if (d->widget) { + // We need to explicitly remove subfocus from the embedded widget's + // focus widget. + if (QWidget *focusWidget = d->widget->focusWidget()) + d->removeSubFocusHelper(focusWidget, event->reason()); + } +} + +/*! + \reimp +*/ +bool QGraphicsProxyWidget::focusNextPrevChild(bool next) +{ + Q_D(QGraphicsProxyWidget); + if (!d->widget || !d->scene) + return QGraphicsWidget::focusNextPrevChild(next); + + Qt::FocusReason reason = next ? Qt::TabFocusReason : Qt::BacktabFocusReason; + QWidget *lastFocusChild = d->widget->focusWidget(); + if (QWidget *newFocusChild = d->findFocusChild(lastFocusChild, next)) { + newFocusChild->setFocus(reason); + return true; + } + + return QGraphicsWidget::focusNextPrevChild(next); +} + +/*! + \reimp +*/ +QSizeF QGraphicsProxyWidget::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_D(const QGraphicsProxyWidget); + if (!d->widget) + return QGraphicsWidget::sizeHint(which, constraint); + + QSizeF sh; + switch (which) { + case Qt::PreferredSize: + if (QLayout *l = d->widget->layout()) + sh = l->sizeHint(); + else + sh = d->widget->sizeHint(); + break; + case Qt::MinimumSize: + if (QLayout *l = d->widget->layout()) + sh = l->minimumSize(); + else + sh = d->widget->minimumSizeHint(); + break; + case Qt::MaximumSize: + if (QLayout *l = d->widget->layout()) + sh = l->maximumSize(); + else + sh = QSizeF(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + break; + case Qt::MinimumDescent: + sh = constraint; + break; + default: + break; + } + return sh; +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + Q_D(QGraphicsProxyWidget); + if (d->widget) { + if (d->sizeChangeMode != QGraphicsProxyWidgetPrivate::WidgetToProxyMode) + d->widget->resize(event->newSize().toSize()); + } + QGraphicsWidget::resizeEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_D(QGraphicsProxyWidget); + Q_UNUSED(widget); + if (!d->widget || !d->widget->isVisible()) + return; + + // Filter out repaints on the window frame. + const QRect exposedWidgetRect = (option->exposedRect & rect()).toAlignedRect(); + if (exposedWidgetRect.isEmpty()) + return; + + // Disable QPainter's default pen being cosmetic. This allows widgets and + // styles to follow Qt's existing defaults without getting ugly cosmetic + // lines when scaled. + bool restore = !(painter->renderHints() & QPainter::NonCosmeticDefaultPen); + painter->setRenderHints(QPainter::NonCosmeticDefaultPen, true); + + d->widget->render(painter, exposedWidgetRect.topLeft(), exposedWidgetRect); + + // Restore the render hints if necessary. + if (restore) + painter->setRenderHints(QPainter::NonCosmeticDefaultPen, false); +} + +/*! + \reimp +*/ +int QGraphicsProxyWidget::type() const +{ + return Type; +} + +/*! + \since 4.5 + + Creates a proxy widget for the given \a child of the widget + contained in this proxy. + + This function makes it possible to acquire proxies for + non top-level widgets. For instance, you can embed a dialog, + and then transform only one of its widgets. + + If the widget is already embedded, return the existing proxy widget. + + \sa newProxyWidget(), QGraphicsScene::addWidget() +*/ +QGraphicsProxyWidget *QGraphicsProxyWidget::createProxyForChildWidget(QWidget *child) +{ + QGraphicsProxyWidget *proxy = child->graphicsProxyWidget(); + if (proxy) + return proxy; + if (!child->parentWidget()) { + qWarning("QGraphicsProxyWidget::createProxyForChildWidget: top-level widget not in a QGraphicsScene"); + return 0; + } + + QGraphicsProxyWidget *parentProxy = createProxyForChildWidget(child->parentWidget()); + if (!parentProxy) + return 0; + + if (!QMetaObject::invokeMethod(parentProxy, "newProxyWidget", Qt::DirectConnection, + Q_RETURN_ARG(QGraphicsProxyWidget*, proxy), Q_ARG(const QWidget*, child))) + return 0; + proxy->setParent(parentProxy); + proxy->setWidget(child); + return proxy; +} + +/*! + \fn QGraphicsProxyWidget *QGraphicsProxyWidget::newProxyWidget(const QWidget *child) + \since 4.5 + + Creates a proxy widget for the given \a child of the widget contained in this + proxy. + + You should not call this function directly; use + QGraphicsProxyWidget::createProxyForChildWidget() instead. + + This function is a fake virtual slot that you can reimplement in + your subclass in order to control how new proxy widgets are + created. The default implementation returns a proxy created with + the QGraphicsProxyWidget() constructor with this proxy widget as + the parent. + + \sa createProxyForChildWidget() +*/ +QGraphicsProxyWidget *QGraphicsProxyWidget::newProxyWidget(const QWidget *) +{ + return new QGraphicsProxyWidget(this); +} + + + +QT_END_NAMESPACE + +#include "moc_qgraphicsproxywidget.cpp" + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicsproxywidget.h b/src/widgets/graphicsview/qgraphicsproxywidget.h new file mode 100644 index 0000000000..b2ffe2f7d1 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsproxywidget.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSPROXYWIDGET_H +#define QGRAPHICSPROXYWIDGET_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsProxyWidgetPrivate; + +class Q_GUI_EXPORT QGraphicsProxyWidget : public QGraphicsWidget +{ + Q_OBJECT +public: + QGraphicsProxyWidget(QGraphicsItem *parent = 0, Qt::WindowFlags wFlags = 0); + ~QGraphicsProxyWidget(); + + void setWidget(QWidget *widget); + QWidget *widget() const; + + QRectF subWidgetRect(const QWidget *widget) const; + + void setGeometry(const QRectF &rect); + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + enum { + Type = 12 + }; + int type() const; + + QGraphicsProxyWidget *createProxyForChildWidget(QWidget *child); + +protected: + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + + bool event(QEvent *event); + bool eventFilter(QObject *object, QEvent *event); + + void showEvent(QShowEvent *event); + void hideEvent(QHideEvent *event); + +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); +#endif + +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QGraphicsSceneDragDropEvent *event); + void dragLeaveEvent(QGraphicsSceneDragDropEvent *event); + void dragMoveEvent(QGraphicsSceneDragDropEvent *event); + void dropEvent(QGraphicsSceneDragDropEvent *event); +#endif + + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + void grabMouseEvent(QEvent *event); + void ungrabMouseEvent(QEvent *event); + + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); +#ifndef QT_NO_WHEELEVENT + void wheelEvent(QGraphicsSceneWheelEvent *event); +#endif + + void keyPressEvent(QKeyEvent *event); + void keyReleaseEvent(QKeyEvent *event); + + void focusInEvent(QFocusEvent *event); + void focusOutEvent(QFocusEvent *event); + bool focusNextPrevChild(bool next); + // ### Qt 4.5: + // QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + // void inputMethodEvent(QInputMethodEvent *event); + + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + void resizeEvent(QGraphicsSceneResizeEvent *event); + +protected Q_SLOTS: + QGraphicsProxyWidget *newProxyWidget(const QWidget *); + +private: + Q_DISABLE_COPY(QGraphicsProxyWidget) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QGraphicsProxyWidget) + Q_PRIVATE_SLOT(d_func(), void _q_removeWidgetSlot()) + + friend class QWidget; + friend class QWidgetPrivate; + friend class QGraphicsItem; +}; + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif + diff --git a/src/widgets/graphicsview/qgraphicsproxywidget_p.h b/src/widgets/graphicsview/qgraphicsproxywidget_p.h new file mode 100644 index 0000000000..e1a96b0021 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsproxywidget_p.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSPROXYWIDGET_P_H +#define QGRAPHICSPROXYWIDGET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicsproxywidget.h" +#include "private/qgraphicswidget_p.h" + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +QT_BEGIN_NAMESPACE + +class QGraphicsProxyWidgetPrivate : public QGraphicsWidgetPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsProxyWidget) +public: + QGraphicsProxyWidgetPrivate() + : dragDropWidget(0), + posChangeMode(NoMode), + sizeChangeMode(NoMode), + visibleChangeMode(NoMode), + enabledChangeMode(NoMode), + styleChangeMode(NoMode), + paletteChangeMode(NoMode), + tooltipChangeMode(NoMode), + focusFromWidgetToProxy(0) + { } + void init(); + void sendWidgetMouseEvent(QGraphicsSceneMouseEvent *event); + void sendWidgetMouseEvent(QGraphicsSceneHoverEvent *event); + void sendWidgetKeyEvent(QKeyEvent *event); + void setWidget_helper(QWidget *widget, bool autoShow); + + QWidget *findFocusChild(QWidget *child, bool next) const; + void removeSubFocusHelper(QWidget *widget, Qt::FocusReason reason); + + // ### Qt 5: Remove. Workaround for reimplementation added after Qt 4.4. + QVariant inputMethodQueryHelper(Qt::InputMethodQuery query) const; + + void _q_removeWidgetSlot(); + + void embedSubWindow(QWidget *); + void unembedSubWindow(QWidget *); + + bool isProxyWidget() const; + + QPointer widget; + QPointer lastWidgetUnderMouse; + QPointer embeddedMouseGrabber; + QWidget *dragDropWidget; + Qt::DropAction lastDropAction; + + void updateWidgetGeometryFromProxy(); + void updateProxyGeometryFromWidget(); + + void updateProxyInputMethodAcceptanceFromWidget(); + + QPointF mapToReceiver(const QPointF &pos, const QWidget *receiver) const; + + enum ChangeMode { + NoMode, + ProxyToWidgetMode, + WidgetToProxyMode + }; + quint32 posChangeMode : 2; + quint32 sizeChangeMode : 2; + quint32 visibleChangeMode : 2; + quint32 enabledChangeMode : 2; + quint32 styleChangeMode : 2; + quint32 paletteChangeMode : 2; + quint32 tooltipChangeMode : 2; + quint32 focusFromWidgetToProxy : 1; + quint32 proxyIsGivingFocus : 1; +}; + +QT_END_NAMESPACE + +#endif + +#endif diff --git a/src/widgets/graphicsview/qgraphicsscene.cpp b/src/widgets/graphicsview/qgraphicsscene.cpp new file mode 100644 index 0000000000..0713d09296 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsscene.cpp @@ -0,0 +1,6491 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +/*! + \class QGraphicsScene + \brief The QGraphicsScene class provides a surface for managing a large + number of 2D graphical items. + \since 4.2 + \ingroup graphicsview-api + + + The class serves as a container for QGraphicsItems. It is used together + with QGraphicsView for visualizing graphical items, such as lines, + rectangles, text, or even custom items, on a 2D surface. QGraphicsScene is + part of the \l{Graphics View Framework}. + + QGraphicsScene also provides functionality that lets you efficiently + determine both the location of items, and for determining what items are + visible within an arbitrary area on the scene. With the QGraphicsView + widget, you can either visualize the whole scene, or zoom in and view only + parts of the scene. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 0 + + Note that QGraphicsScene has no visual appearance of its own; it only + manages the items. You need to create a QGraphicsView widget to visualize + the scene. + + To add items to a scene, you start off by constructing a QGraphicsScene + object. Then, you have two options: either add your existing QGraphicsItem + objects by calling addItem(), or you can call one of the convenience + functions addEllipse(), addLine(), addPath(), addPixmap(), addPolygon(), + addRect(), or addText(), which all return a pointer to the newly added item. + The dimensions of the items added with these functions are relative to the + item's coordinate system, and the items position is initialized to (0, + 0) in the scene. + + You can then visualize the scene using QGraphicsView. When the scene + changes, (e.g., when an item moves or is transformed) QGraphicsScene + emits the changed() signal. To remove an item, call removeItem(). + + QGraphicsScene uses an indexing algorithm to manage the location of items + efficiently. By default, a BSP (Binary Space Partitioning) tree is used; an + algorithm suitable for large scenes where most items remain static (i.e., + do not move around). You can choose to disable this index by calling + setItemIndexMethod(). For more information about the available indexing + algorithms, see the itemIndexMethod property. + + The scene's bounding rect is set by calling setSceneRect(). Items can be + placed at any position on the scene, and the size of the scene is by + default unlimited. The scene rect is used only for internal bookkeeping, + maintaining the scene's item index. If the scene rect is unset, + QGraphicsScene will use the bounding area of all items, as returned by + itemsBoundingRect(), as the scene rect. However, itemsBoundingRect() is a + relatively time consuming function, as it operates by collecting + positional information for every item on the scene. Because of this, you + should always set the scene rect when operating on large scenes. + + One of QGraphicsScene's greatest strengths is its ability to efficiently + determine the location of items. Even with millions of items on the scene, + the items() functions can determine the location of an item within few + milliseconds. There are several overloads to items(): one that finds items + at a certain position, one that finds items inside or intersecting with a + polygon or a rectangle, and more. The list of returned items is sorted by + stacking order, with the topmost item being the first item in the list. + For convenience, there is also an itemAt() function that returns the + topmost item at a given position. + + QGraphicsScene maintains selection information for the scene. To select + items, call setSelectionArea(), and to clear the current selection, call + clearSelection(). Call selectedItems() to get the list of all selected + items. + + \section1 Event Handling and Propagation + + Another responsibility that QGraphicsScene has, is to propagate events + from QGraphicsView. To send an event to a scene, you construct an event + that inherits QEvent, and then send it using, for example, + QApplication::sendEvent(). event() is responsible for dispatching + the event to the individual items. Some common events are handled by + convenience event handlers. For example, key press events are handled by + keyPressEvent(), and mouse press events are handled by mousePressEvent(). + + Key events are delivered to the \e {focus item}. To set the focus item, + you can either call setFocusItem(), passing an item that accepts focus, or + the item itself can call QGraphicsItem::setFocus(). Call focusItem() to + get the current focus item. For compatibility with widgets, the scene also + maintains its own focus information. By default, the scene does not have + focus, and all key events are discarded. If setFocus() is called, or if an + item on the scene gains focus, the scene automatically gains focus. If the + scene has focus, hasFocus() will return true, and key events will be + forwarded to the focus item, if any. If the scene loses focus, (i.e., + someone calls clearFocus()) while an item has focus, the scene will + maintain its item focus information, and once the scene regains focus, it + will make sure the last focus item regains focus. + + For mouse-over effects, QGraphicsScene dispatches \e {hover + events}. If an item accepts hover events (see + QGraphicsItem::acceptHoverEvents()), it will receive a \l + {QEvent::}{GraphicsSceneHoverEnter} event when the mouse enters + its area. As the mouse continues moving inside the item's area, + QGraphicsScene will send it \l {QEvent::}{GraphicsSceneHoverMove} + events. When the mouse leaves the item's area, the item will + receive a \l {QEvent::}{GraphicsSceneHoverLeave} event. + + All mouse events are delivered to the current \e {mouse grabber} + item. An item becomes the scene's mouse grabber if it accepts + mouse events (see QGraphicsItem::acceptedMouseButtons()) and it + receives a mouse press. It stays the mouse grabber until it + receives a mouse release when no other mouse buttons are + pressed. You can call mouseGrabberItem() to determine what item is + currently grabbing the mouse. + + \sa QGraphicsItem, QGraphicsView +*/ + +/*! + \enum QGraphicsScene::SceneLayer + \since 4.3 + + This enum describes the rendering layers in a QGraphicsScene. When + QGraphicsScene draws the scene contents, it renders each of these layers + separately, in order. + + Each layer represents a flag that can be OR'ed together when calling + functions such as invalidate() or QGraphicsView::invalidateScene(). + + \value ItemLayer The item layer. QGraphicsScene renders all items are in + this layer by calling the virtual function drawItems(). The item layer is + drawn after the background layer, but before the foreground layer. + + \value BackgroundLayer The background layer. QGraphicsScene renders the + scene's background in this layer by calling the virtual function + drawBackground(). The background layer is drawn first of all layers. + + \value ForegroundLayer The foreground layer. QGraphicsScene renders the + scene's foreground in this layer by calling the virtual function + drawForeground(). The foreground layer is drawn last of all layers. + + \value AllLayers All layers; this value represents a combination of all + three layers. + + \sa invalidate(), QGraphicsView::invalidateScene() +*/ + +/*! + \enum QGraphicsScene::ItemIndexMethod + + This enum describes the indexing algorithms QGraphicsScene provides for + managing positional information about items on the scene. + + \value BspTreeIndex A Binary Space Partitioning tree is applied. All + QGraphicsScene's item location algorithms are of an order close to + logarithmic complexity, by making use of binary search. Adding, moving and + removing items is logarithmic. This approach is best for static scenes + (i.e., scenes where most items do not move). + + \value NoIndex No index is applied. Item location is of linear complexity, + as all items on the scene are searched. Adding, moving and removing items, + however, is done in constant time. This approach is ideal for dynamic + scenes, where many items are added, moved or removed continuously. + + \sa setItemIndexMethod(), bspTreeDepth +*/ + +#include "qgraphicsscene.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicsitem.h" +#include "qgraphicsitem_p.h" +#include "qgraphicslayout.h" +#include "qgraphicsscene_p.h" +#include "qgraphicssceneevent.h" +#include "qgraphicsview.h" +#include "qgraphicsview_p.h" +#include "qgraphicswidget.h" +#include "qgraphicswidget_p.h" +#include "qgraphicssceneindex_p.h" +#include "qgraphicsscenebsptreeindex_p.h" +#include "qgraphicsscenelinearindex_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef Q_WS_X11 +#include +#endif +#include +#include +#include + +// #define GESTURE_DEBUG +#ifndef GESTURE_DEBUG +# define DEBUG if (0) qDebug +#else +# define DEBUG qDebug +#endif + +QT_BEGIN_NAMESPACE + +bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); + +static void _q_hoverFromMouseEvent(QGraphicsSceneHoverEvent *hover, const QGraphicsSceneMouseEvent *mouseEvent) +{ + hover->setWidget(mouseEvent->widget()); + hover->setPos(mouseEvent->pos()); + hover->setScenePos(mouseEvent->scenePos()); + hover->setScreenPos(mouseEvent->screenPos()); + hover->setLastPos(mouseEvent->lastPos()); + hover->setLastScenePos(mouseEvent->lastScenePos()); + hover->setLastScreenPos(mouseEvent->lastScreenPos()); + hover->setModifiers(mouseEvent->modifiers()); + hover->setAccepted(mouseEvent->isAccepted()); +} + +/*! + \internal +*/ +QGraphicsScenePrivate::QGraphicsScenePrivate() + : indexMethod(QGraphicsScene::BspTreeIndex), + index(0), + lastItemCount(0), + hasSceneRect(false), + dirtyGrowingItemsBoundingRect(true), + updateAll(false), + calledEmitUpdated(false), + processDirtyItemsEmitted(false), + needSortTopLevelItems(true), + holesInTopLevelSiblingIndex(false), + topLevelSequentialOrdering(true), + scenePosDescendantsUpdatePending(false), + stickyFocus(false), + hasFocus(false), + lastMouseGrabberItemHasImplicitMouseGrab(false), + allItemsIgnoreHoverEvents(true), + allItemsUseDefaultCursor(true), + painterStateProtection(true), + sortCacheEnabled(false), + allItemsIgnoreTouchEvents(true), + selectionChanging(0), + rectAdjust(2), + focusItem(0), + lastFocusItem(0), + passiveFocusItem(0), + tabFocusFirst(0), + activePanel(0), + lastActivePanel(0), + activationRefCount(0), + childExplicitActivation(0), + lastMouseGrabberItem(0), + dragDropItem(0), + enterWidget(0), + lastDropAction(Qt::IgnoreAction), + style(0) +{ +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::init() +{ + Q_Q(QGraphicsScene); + + index = new QGraphicsSceneBspTreeIndex(q); + + // Keep this index so we can check for connected slots later on. + changedSignalIndex = signalIndex("changed(QList)"); + processDirtyItemsIndex = q->metaObject()->indexOfSlot("_q_processDirtyItems()"); + polishItemsIndex = q->metaObject()->indexOfSlot("_q_polishItems()"); + + qApp->d_func()->scene_list.append(q); + q->update(); +} + +/*! + \internal +*/ +QGraphicsScenePrivate *QGraphicsScenePrivate::get(QGraphicsScene *q) +{ + return q->d_func(); +} + +void QGraphicsScenePrivate::_q_emitUpdated() +{ + Q_Q(QGraphicsScene); + calledEmitUpdated = false; + + if (dirtyGrowingItemsBoundingRect) { + if (!hasSceneRect) { + const QRectF oldGrowingItemsBoundingRect = growingItemsBoundingRect; + growingItemsBoundingRect |= q->itemsBoundingRect(); + if (oldGrowingItemsBoundingRect != growingItemsBoundingRect) + emit q->sceneRectChanged(growingItemsBoundingRect); + } + dirtyGrowingItemsBoundingRect = false; + } + + // Ensure all views are connected if anything is connected. This disables + // the optimization that items send updates directly to the views, but it + // needs to happen in order to keep compatibility with the behavior from + // Qt 4.4 and backward. + if (isSignalConnected(changedSignalIndex)) { + for (int i = 0; i < views.size(); ++i) { + QGraphicsView *view = views.at(i); + if (!view->d_func()->connectedToScene) { + view->d_func()->connectedToScene = true; + q->connect(q, SIGNAL(changed(QList)), + views.at(i), SLOT(updateScene(QList))); + } + } + } else { + if (views.isEmpty()) { + updateAll = false; + return; + } + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->processPendingUpdates(); + // It's important that we update all views before we dispatch, hence two for-loops. + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->dispatchPendingUpdateRequests(); + return; + } + + // Notify the changes to anybody interested. + QList oldUpdatedRects; + oldUpdatedRects = updateAll ? (QList() << q->sceneRect()) : updatedRects; + updateAll = false; + updatedRects.clear(); + emit q->changed(oldUpdatedRects); +} + +/*! + \internal + + ### This function is almost identical to QGraphicsItemPrivate::addChild(). +*/ +void QGraphicsScenePrivate::registerTopLevelItem(QGraphicsItem *item) +{ + item->d_ptr->ensureSequentialSiblingIndex(); + needSortTopLevelItems = true; // ### maybe false + item->d_ptr->siblingIndex = topLevelItems.size(); + topLevelItems.append(item); +} + +/*! + \internal + + ### This function is almost identical to QGraphicsItemPrivate::removeChild(). +*/ +void QGraphicsScenePrivate::unregisterTopLevelItem(QGraphicsItem *item) +{ + if (!holesInTopLevelSiblingIndex) + holesInTopLevelSiblingIndex = item->d_ptr->siblingIndex != topLevelItems.size() - 1; + if (topLevelSequentialOrdering && !holesInTopLevelSiblingIndex) + topLevelItems.removeAt(item->d_ptr->siblingIndex); + else + topLevelItems.removeOne(item); + // NB! Do not use topLevelItems.removeAt(item->d_ptr->siblingIndex) because + // the item is not guaranteed to be at the index after the list is sorted + // (see ensureSortedTopLevelItems()). + item->d_ptr->siblingIndex = -1; + if (topLevelSequentialOrdering) + topLevelSequentialOrdering = !holesInTopLevelSiblingIndex; +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::_q_polishItems() +{ + if (unpolishedItems.isEmpty()) + return; + + const QVariant booleanTrueVariant(true); + QGraphicsItem *item = 0; + QGraphicsItemPrivate *itemd = 0; + const int oldUnpolishedCount = unpolishedItems.count(); + + for (int i = 0; i < oldUnpolishedCount; ++i) { + item = unpolishedItems.at(i); + if (!item) + continue; + itemd = item->d_ptr.data(); + itemd->pendingPolish = false; + if (!itemd->explicitlyHidden) { + item->itemChange(QGraphicsItem::ItemVisibleChange, booleanTrueVariant); + item->itemChange(QGraphicsItem::ItemVisibleHasChanged, booleanTrueVariant); + } + if (itemd->isWidget) { + QEvent event(QEvent::Polish); + QApplication::sendEvent((QGraphicsWidget *)item, &event); + } + } + + if (unpolishedItems.count() == oldUnpolishedCount) { + // No new items were added to the vector. + unpolishedItems.clear(); + } else { + // New items were appended; keep them and remove the old ones. + unpolishedItems.remove(0, oldUnpolishedCount); + unpolishedItems.squeeze(); + QMetaObject::invokeMethod(q_ptr, "_q_polishItems", Qt::QueuedConnection); + } +} + +void QGraphicsScenePrivate::_q_processDirtyItems() +{ + processDirtyItemsEmitted = false; + + if (updateAll) { + Q_ASSERT(calledEmitUpdated); + // No need for further processing (except resetting the dirty states). + // The growingItemsBoundingRect is updated in _q_emitUpdated. + for (int i = 0; i < topLevelItems.size(); ++i) + resetDirtyItem(topLevelItems.at(i), /*recursive=*/true); + return; + } + + const bool wasPendingSceneUpdate = calledEmitUpdated; + const QRectF oldGrowingItemsBoundingRect = growingItemsBoundingRect; + + // Process items recursively. + for (int i = 0; i < topLevelItems.size(); ++i) + processDirtyItemsRecursive(topLevelItems.at(i)); + + dirtyGrowingItemsBoundingRect = false; + if (!hasSceneRect && oldGrowingItemsBoundingRect != growingItemsBoundingRect) + emit q_func()->sceneRectChanged(growingItemsBoundingRect); + + if (wasPendingSceneUpdate) + return; + + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->processPendingUpdates(); + + if (calledEmitUpdated) { + // We did a compatibility QGraphicsScene::update in processDirtyItemsRecursive + // and we cannot wait for the control to reach the eventloop before the + // changed signal is emitted, so we emit it now. + _q_emitUpdated(); + } + + // Immediately dispatch all pending update requests on the views. + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->dispatchPendingUpdateRequests(); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::setScenePosItemEnabled(QGraphicsItem *item, bool enabled) +{ + QGraphicsItem *p = item->d_ptr->parent; + while (p) { + p->d_ptr->scenePosDescendants = enabled; + p = p->d_ptr->parent; + } + if (!enabled && !scenePosDescendantsUpdatePending) { + scenePosDescendantsUpdatePending = true; + QMetaObject::invokeMethod(q_func(), "_q_updateScenePosDescendants", Qt::QueuedConnection); + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::registerScenePosItem(QGraphicsItem *item) +{ + scenePosItems.insert(item); + setScenePosItemEnabled(item, true); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::unregisterScenePosItem(QGraphicsItem *item) +{ + scenePosItems.remove(item); + setScenePosItemEnabled(item, false); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::_q_updateScenePosDescendants() +{ + foreach (QGraphicsItem *item, scenePosItems) { + QGraphicsItem *p = item->d_ptr->parent; + while (p) { + p->d_ptr->scenePosDescendants = 1; + p = p->d_ptr->parent; + } + } + scenePosDescendantsUpdatePending = false; +} + +/*! + \internal + + Schedules an item for removal. This function leaves some stale indexes + around in the BSP tree if called from the item's destructor; these will + be cleaned up the next time someone triggers purgeRemovedItems(). + + Note: This function might get called from QGraphicsItem's destructor. \a item is + being destroyed, so we cannot call any pure virtual functions on it (such + as boundingRect()). Also, it is unnecessary to update the item's own state + in any way. +*/ +void QGraphicsScenePrivate::removeItemHelper(QGraphicsItem *item) +{ + Q_Q(QGraphicsScene); + + // Clear focus on the item to remove any reference in the focusWidget chain. + item->clearFocus(); + + markDirty(item, QRectF(), /*invalidateChildren=*/false, /*force=*/false, + /*ignoreOpacity=*/false, /*removingItemFromScene=*/true); + + if (item->d_ptr->inDestructor) { + // The item is actually in its destructor, we call the special method in the index. + index->deleteItem(item); + } else { + // Can potentially call item->boundingRect() (virtual function), that's why + // we only can call this function if the item is not in its destructor. + index->removeItem(item); + } + + item->d_ptr->clearSubFocus(); + + if (item->flags() & QGraphicsItem::ItemSendsScenePositionChanges) + unregisterScenePosItem(item); + + QGraphicsScene *oldScene = item->d_func()->scene; + item->d_func()->scene = 0; + + //We need to remove all children first because they might use their parent + //attributes (e.g. sceneTransform). + if (!item->d_ptr->inDestructor) { + // Remove all children recursively + for (int i = 0; i < item->d_ptr->children.size(); ++i) + q->removeItem(item->d_ptr->children.at(i)); + } + + if (!item->d_ptr->inDestructor && item == tabFocusFirst) { + QGraphicsWidget *widget = static_cast(item); + widget->d_func()->fixFocusChainBeforeReparenting(0, oldScene, 0); + } + + // Unregister focus proxy. + item->d_ptr->resetFocusProxy(); + + // Remove from parent, or unregister from toplevels. + if (QGraphicsItem *parentItem = item->parentItem()) { + if (parentItem->scene()) { + Q_ASSERT_X(parentItem->scene() == q, "QGraphicsScene::removeItem", + "Parent item's scene is different from this item's scene"); + item->setParentItem(0); + } + } else { + unregisterTopLevelItem(item); + } + + // Reset the mouse grabber and focus item data. + if (item == focusItem) + focusItem = 0; + if (item == lastFocusItem) + lastFocusItem = 0; + if (item == passiveFocusItem) + passiveFocusItem = 0; + if (item == activePanel) { + // ### deactivate... + activePanel = 0; + } + if (item == lastActivePanel) + lastActivePanel = 0; + + // Cancel active touches + { + QMap::iterator it = itemForTouchPointId.begin(); + while (it != itemForTouchPointId.end()) { + if (it.value() == item) { + sceneCurrentTouchPoints.remove(it.key()); + it = itemForTouchPointId.erase(it); + } else { + ++it; + } + } + } + + // Disable selectionChanged() for individual items + ++selectionChanging; + int oldSelectedItemsSize = selectedItems.size(); + + // Update selected & hovered item bookkeeping + selectedItems.remove(item); + hoverItems.removeAll(item); + cachedItemsUnderMouse.removeAll(item); + if (item->d_ptr->pendingPolish) { + const int unpolishedIndex = unpolishedItems.indexOf(item); + if (unpolishedIndex != -1) + unpolishedItems[unpolishedIndex] = 0; + item->d_ptr->pendingPolish = false; + } + resetDirtyItem(item); + + //We remove all references of item from the sceneEventFilter arrays + QMultiMap::iterator iterator = sceneEventFilters.begin(); + while (iterator != sceneEventFilters.end()) { + if (iterator.value() == item || iterator.key() == item) + iterator = sceneEventFilters.erase(iterator); + else + ++iterator; + } + + if (item->isPanel() && item->isVisible() && item->panelModality() != QGraphicsItem::NonModal) + leaveModal(item); + + // Reset the mouse grabber and focus item data. + if (mouseGrabberItems.contains(item)) + ungrabMouse(item, /* item is dying */ item->d_ptr->inDestructor); + + // Reset the keyboard grabber + if (keyboardGrabberItems.contains(item)) + ungrabKeyboard(item, /* item is dying */ item->d_ptr->inDestructor); + + // Reset the last mouse grabber item + if (item == lastMouseGrabberItem) + lastMouseGrabberItem = 0; + + // Reset the current drop item + if (item == dragDropItem) + dragDropItem = 0; + + // Reenable selectionChanged() for individual items + --selectionChanging; + if (!selectionChanging && selectedItems.size() != oldSelectedItemsSize) + emit q->selectionChanged(); + +#ifndef QT_NO_GESTURES + QHash::iterator it; + for (it = gestureTargets.begin(); it != gestureTargets.end();) { + if (it.value() == item) + it = gestureTargets.erase(it); + else + ++it; + } + + QGraphicsObject *dummy = static_cast(item); + cachedTargetItems.removeOne(dummy); + cachedItemGestures.remove(dummy); + cachedAlreadyDeliveredGestures.remove(dummy); + + foreach (Qt::GestureType gesture, item->d_ptr->gestureContext.keys()) + ungrabGesture(item, gesture); +#endif // QT_NO_GESTURES +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::setActivePanelHelper(QGraphicsItem *item, bool duringActivationEvent) +{ + Q_Q(QGraphicsScene); + if (item && item->scene() != q) { + qWarning("QGraphicsScene::setActivePanel: item %p must be part of this scene", + item); + return; + } + + // Ensure the scene has focus when we change panel activation. + q->setFocus(Qt::ActiveWindowFocusReason); + + // Find the item's panel. + QGraphicsItem *panel = item ? item->panel() : 0; + lastActivePanel = panel ? activePanel : 0; + if (panel == activePanel || (!q->isActive() && !duringActivationEvent)) + return; + + // Deactivate the last active panel. + if (activePanel) { + if (QGraphicsItem *fi = activePanel->focusItem()) { + // Remove focus from the current focus item. + if (fi == q->focusItem()) + q->setFocusItem(0, Qt::ActiveWindowFocusReason); + } + + QEvent event(QEvent::WindowDeactivate); + q->sendEvent(activePanel, &event); + } else if (panel && !duringActivationEvent) { + // Deactivate the scene if changing activation to a panel. + QEvent event(QEvent::WindowDeactivate); + foreach (QGraphicsItem *item, q->items()) { + if (item->isVisible() && !item->isPanel() && !item->parentItem()) + q->sendEvent(item, &event); + } + } + + // Update activate state. + activePanel = panel; + QEvent event(QEvent::ActivationChange); + QApplication::sendEvent(q, &event); + + // Activate + if (panel) { + QEvent event(QEvent::WindowActivate); + q->sendEvent(panel, &event); + + // Set focus on the panel's focus item. + if (QGraphicsItem *focusItem = panel->focusItem()) + focusItem->setFocus(Qt::ActiveWindowFocusReason); + } else if (q->isActive()) { + // Activate the scene + QEvent event(QEvent::WindowActivate); + foreach (QGraphicsItem *item, q->items()) { + if (item->isVisible() && !item->isPanel() && !item->parentItem()) + q->sendEvent(item, &event); + } + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::setFocusItemHelper(QGraphicsItem *item, + Qt::FocusReason focusReason) +{ + Q_Q(QGraphicsScene); + if (item == focusItem) + return; + + // Clear focus if asked to set focus on something that can't + // accept input focus. + if (item && (!(item->flags() & QGraphicsItem::ItemIsFocusable) + || !item->isVisible() || !item->isEnabled())) { + item = 0; + } + + // Set focus on the scene if an item requests focus. + if (item) { + q->setFocus(focusReason); + if (item == focusItem) + return; + } + + if (focusItem) { + lastFocusItem = focusItem; + +#ifndef QT_NO_IM + if (lastFocusItem + && (lastFocusItem->flags() & QGraphicsItem::ItemAcceptsInputMethod)) { + // Close any external input method panel. This happens + // automatically by removing WA_InputMethodEnabled on + // the views, but if we are changing focus, we have to + // do it ourselves. + for (int i = 0; i < views.size(); ++i) + if (views.at(i)->inputContext()) + views.at(i)->inputContext()->reset(); + } + + focusItem = 0; + QFocusEvent event(QEvent::FocusOut, focusReason); + sendEvent(lastFocusItem, &event); +#endif //QT_NO_IM + } + + // This handles the case that the item has been removed from the + // scene in response to the FocusOut event. + if (item && item->scene() != q) + item = 0; + + if (item) + focusItem = item; + updateInputMethodSensitivityInViews(); + if (item) { + QFocusEvent event(QEvent::FocusIn, focusReason); + sendEvent(item, &event); + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::addPopup(QGraphicsWidget *widget) +{ + Q_ASSERT(widget); + Q_ASSERT(!popupWidgets.contains(widget)); + popupWidgets << widget; + if (QGraphicsWidget *focusWidget = widget->focusWidget()) { + focusWidget->setFocus(Qt::PopupFocusReason); + } else { + grabKeyboard((QGraphicsItem *)widget); + if (focusItem && popupWidgets.size() == 1) { + QFocusEvent event(QEvent::FocusOut, Qt::PopupFocusReason); + sendEvent(focusItem, &event); + } + } + grabMouse((QGraphicsItem *)widget); +} + +/*! + \internal + + Remove \a widget from the popup list. Important notes: + + \a widget is guaranteed to be in the list of popups, but it might not be + the last entry; you can hide any item in the pop list before the others, + and this must cause all later mouse grabbers to lose the grab. +*/ +void QGraphicsScenePrivate::removePopup(QGraphicsWidget *widget, bool itemIsDying) +{ + Q_ASSERT(widget); + int index = popupWidgets.indexOf(widget); + Q_ASSERT(index != -1); + + for (int i = popupWidgets.size() - 1; i >= index; --i) { + QGraphicsWidget *widget = popupWidgets.takeLast(); + ungrabMouse(widget, itemIsDying); + if (focusItem && popupWidgets.isEmpty()) { + QFocusEvent event(QEvent::FocusIn, Qt::PopupFocusReason); + sendEvent(focusItem, &event); + } else if (keyboardGrabberItems.contains(static_cast(widget))) { + ungrabKeyboard(static_cast(widget), itemIsDying); + } + if (!itemIsDying && widget->isVisible()) { + widget->QGraphicsItem::d_ptr->setVisibleHelper(false, /* explicit = */ false); + } + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::grabMouse(QGraphicsItem *item, bool implicit) +{ + // Append to list of mouse grabber items, and send a mouse grab event. + if (mouseGrabberItems.contains(item)) { + if (mouseGrabberItems.last() == item) { + Q_ASSERT(!implicit); + if (!lastMouseGrabberItemHasImplicitMouseGrab) { + qWarning("QGraphicsItem::grabMouse: already a mouse grabber"); + } else { + // Upgrade to an explicit mouse grab + lastMouseGrabberItemHasImplicitMouseGrab = false; + } + } else { + qWarning("QGraphicsItem::grabMouse: already blocked by mouse grabber: %p", + mouseGrabberItems.last()); + } + return; + } + + // Send ungrab event to the last grabber. + if (!mouseGrabberItems.isEmpty()) { + QGraphicsItem *last = mouseGrabberItems.last(); + if (lastMouseGrabberItemHasImplicitMouseGrab) { + // Implicit mouse grab is immediately lost. + last->ungrabMouse(); + } else { + // Just send ungrab event to current grabber. + QEvent ungrabEvent(QEvent::UngrabMouse); + sendEvent(last, &ungrabEvent); + } + } + + mouseGrabberItems << item; + lastMouseGrabberItemHasImplicitMouseGrab = implicit; + + // Send grab event to current grabber. + QEvent grabEvent(QEvent::GrabMouse); + sendEvent(item, &grabEvent); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::ungrabMouse(QGraphicsItem *item, bool itemIsDying) +{ + int index = mouseGrabberItems.indexOf(item); + if (index == -1) { + qWarning("QGraphicsItem::ungrabMouse: not a mouse grabber"); + return; + } + + if (item != mouseGrabberItems.last()) { + // Recursively ungrab the next mouse grabber until we reach this item + // to ensure state consistency. + ungrabMouse(mouseGrabberItems.at(index + 1), itemIsDying); + } + if (!popupWidgets.isEmpty() && item == popupWidgets.last()) { + // If the item is a popup, go via removePopup to ensure state + // consistency and that it gets hidden correctly - beware that + // removePopup() reenters this function to continue removing the grab. + removePopup((QGraphicsWidget *)item, itemIsDying); + return; + } + + // Send notification about mouse ungrab. + if (!itemIsDying) { + QEvent event(QEvent::UngrabMouse); + sendEvent(item, &event); + } + + // Remove the item from the list of grabbers. Whenever this happens, we + // reset the implicitGrab (there can be only ever be one implicit grabber + // in a scene, and it is always the latest grabber; if the implicit grab + // is lost, it is not automatically regained. + mouseGrabberItems.takeLast(); + lastMouseGrabberItemHasImplicitMouseGrab = false; + + // Send notification about mouse regrab. ### It's unfortunate that all the + // items get a GrabMouse event, but this is a rare case with a simple + // implementation and it does ensure a consistent state. + if (!itemIsDying && !mouseGrabberItems.isEmpty()) { + QGraphicsItem *last = mouseGrabberItems.last(); + QEvent event(QEvent::GrabMouse); + sendEvent(last, &event); + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::clearMouseGrabber() +{ + if (!mouseGrabberItems.isEmpty()) + mouseGrabberItems.first()->ungrabMouse(); + lastMouseGrabberItem = 0; +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::grabKeyboard(QGraphicsItem *item) +{ + if (keyboardGrabberItems.contains(item)) { + if (keyboardGrabberItems.last() == item) + qWarning("QGraphicsItem::grabKeyboard: already a keyboard grabber"); + else + qWarning("QGraphicsItem::grabKeyboard: already blocked by keyboard grabber: %p", + keyboardGrabberItems.last()); + return; + } + + // Send ungrab event to the last grabber. + if (!keyboardGrabberItems.isEmpty()) { + // Just send ungrab event to current grabber. + QEvent ungrabEvent(QEvent::UngrabKeyboard); + sendEvent(keyboardGrabberItems.last(), &ungrabEvent); + } + + keyboardGrabberItems << item; + + // Send grab event to current grabber. + QEvent grabEvent(QEvent::GrabKeyboard); + sendEvent(item, &grabEvent); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::ungrabKeyboard(QGraphicsItem *item, bool itemIsDying) +{ + int index = keyboardGrabberItems.lastIndexOf(item); + if (index == -1) { + qWarning("QGraphicsItem::ungrabKeyboard: not a keyboard grabber"); + return; + } + if (item != keyboardGrabberItems.last()) { + // Recursively ungrab the topmost keyboard grabber until we reach this + // item to ensure state consistency. + ungrabKeyboard(keyboardGrabberItems.at(index + 1), itemIsDying); + } + + // Send notification about keyboard ungrab. + if (!itemIsDying) { + QEvent event(QEvent::UngrabKeyboard); + sendEvent(item, &event); + } + + // Remove the item from the list of grabbers. + keyboardGrabberItems.takeLast(); + + // Send notification about mouse regrab. + if (!itemIsDying && !keyboardGrabberItems.isEmpty()) { + QGraphicsItem *last = keyboardGrabberItems.last(); + QEvent event(QEvent::GrabKeyboard); + sendEvent(last, &event); + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::clearKeyboardGrabber() +{ + if (!keyboardGrabberItems.isEmpty()) + ungrabKeyboard(keyboardGrabberItems.first()); +} + +void QGraphicsScenePrivate::enableMouseTrackingOnViews() +{ + foreach (QGraphicsView *view, views) + view->viewport()->setMouseTracking(true); +} + +/*! + Returns all items for the screen position in \a event. +*/ +QList QGraphicsScenePrivate::itemsAtPosition(const QPoint &/*screenPos*/, + const QPointF &scenePos, + QWidget *widget) const +{ + Q_Q(const QGraphicsScene); + QGraphicsView *view = widget ? qobject_cast(widget->parentWidget()) : 0; + if (!view) + return q->items(scenePos, Qt::IntersectsItemShape, Qt::DescendingOrder, QTransform()); + + const QRectF pointRect(scenePos, QSizeF(1, 1)); + if (!view->isTransformed()) + return q->items(pointRect, Qt::IntersectsItemShape, Qt::DescendingOrder); + + const QTransform viewTransform = view->viewportTransform(); + return q->items(pointRect, Qt::IntersectsItemShape, + Qt::DescendingOrder, viewTransform); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::storeMouseButtonsForMouseGrabber(QGraphicsSceneMouseEvent *event) +{ + for (int i = 0x1; i <= 0x10; i <<= 1) { + if (event->buttons() & i) { + mouseGrabberButtonDownPos.insert(Qt::MouseButton(i), + mouseGrabberItems.last()->d_ptr->genericMapFromScene(event->scenePos(), + event->widget())); + mouseGrabberButtonDownScenePos.insert(Qt::MouseButton(i), event->scenePos()); + mouseGrabberButtonDownScreenPos.insert(Qt::MouseButton(i), event->screenPos()); + } + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::installSceneEventFilter(QGraphicsItem *watched, QGraphicsItem *filter) +{ + sceneEventFilters.insert(watched, filter); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::removeSceneEventFilter(QGraphicsItem *watched, QGraphicsItem *filter) +{ + if (!sceneEventFilters.contains(watched)) + return; + + QMultiMap::Iterator it = sceneEventFilters.lowerBound(watched); + QMultiMap::Iterator end = sceneEventFilters.upperBound(watched); + do { + if (it.value() == filter) + it = sceneEventFilters.erase(it); + else + ++it; + } while (it != end); +} + +/*! + \internal +*/ +bool QGraphicsScenePrivate::filterDescendantEvent(QGraphicsItem *item, QEvent *event) +{ + if (item && (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorFiltersChildEvents)) { + QGraphicsItem *parent = item->parentItem(); + while (parent) { + if (parent->d_ptr->filtersDescendantEvents && parent->sceneEventFilter(item, event)) + return true; + if (!(parent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorFiltersChildEvents)) + return false; + parent = parent->parentItem(); + } + } + return false; +} + +/*! + \internal +*/ +bool QGraphicsScenePrivate::filterEvent(QGraphicsItem *item, QEvent *event) +{ + if (item && !sceneEventFilters.contains(item)) + return false; + + QMultiMap::Iterator it = sceneEventFilters.lowerBound(item); + QMultiMap::Iterator end = sceneEventFilters.upperBound(item); + while (it != end) { + // ### The filterer and filteree might both be deleted. + if (it.value()->sceneEventFilter(it.key(), event)) + return true; + ++it; + } + return false; +} + +/*! + \internal + + This is the final dispatch point for any events from the scene to the + item. It filters the event first - if the filter returns true, the event + is considered to have been eaten by the filter, and is therefore stopped + (the default filter returns false). Then/otherwise, if the item is + enabled, the event is sent; otherwise it is stopped. +*/ +bool QGraphicsScenePrivate::sendEvent(QGraphicsItem *item, QEvent *event) +{ + if (QGraphicsObject *object = item->toGraphicsObject()) { +#ifndef QT_NO_GESTURES + QGestureManager *gestureManager = QApplicationPrivate::instance()->gestureManager; + if (gestureManager) { + if (gestureManager->filterEvent(object, event)) + return true; + } +#endif // QT_NO_GESTURES + } + + if (filterEvent(item, event)) + return false; + if (filterDescendantEvent(item, event)) + return false; + if (!item || !item->isEnabled()) + return false; + if (QGraphicsObject *o = item->toGraphicsObject()) { + bool spont = event->spontaneous(); + if (spont ? qt_sendSpontaneousEvent(o, event) : QApplication::sendEvent(o, event)) + return true; + event->spont = spont; + } + return item->sceneEvent(event); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::cloneDragDropEvent(QGraphicsSceneDragDropEvent *dest, + QGraphicsSceneDragDropEvent *source) +{ + dest->setWidget(source->widget()); + dest->setPos(source->pos()); + dest->setScenePos(source->scenePos()); + dest->setScreenPos(source->screenPos()); + dest->setButtons(source->buttons()); + dest->setModifiers(source->modifiers()); + dest->setPossibleActions(source->possibleActions()); + dest->setProposedAction(source->proposedAction()); + dest->setDropAction(source->dropAction()); + dest->setSource(source->source()); + dest->setMimeData(source->mimeData()); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::sendDragDropEvent(QGraphicsItem *item, + QGraphicsSceneDragDropEvent *dragDropEvent) +{ + dragDropEvent->setPos(item->d_ptr->genericMapFromScene(dragDropEvent->scenePos(), dragDropEvent->widget())); + sendEvent(item, dragDropEvent); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::sendHoverEvent(QEvent::Type type, QGraphicsItem *item, + QGraphicsSceneHoverEvent *hoverEvent) +{ + QGraphicsSceneHoverEvent event(type); + event.setWidget(hoverEvent->widget()); + event.setPos(item->d_ptr->genericMapFromScene(hoverEvent->scenePos(), hoverEvent->widget())); + event.setScenePos(hoverEvent->scenePos()); + event.setScreenPos(hoverEvent->screenPos()); + event.setLastPos(item->d_ptr->genericMapFromScene(hoverEvent->lastScenePos(), hoverEvent->widget())); + event.setLastScenePos(hoverEvent->lastScenePos()); + event.setLastScreenPos(hoverEvent->lastScreenPos()); + event.setModifiers(hoverEvent->modifiers()); + sendEvent(item, &event); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::sendMouseEvent(QGraphicsSceneMouseEvent *mouseEvent) +{ + if (mouseEvent->button() == 0 && mouseEvent->buttons() == 0 && lastMouseGrabberItemHasImplicitMouseGrab) { + // ### This is a temporary fix for until we get proper mouse + // grab events. + clearMouseGrabber(); + return; + } + + QGraphicsItem *item = mouseGrabberItems.last(); + if (item->isBlockedByModalPanel()) + return; + + for (int i = 0x1; i <= 0x10; i <<= 1) { + Qt::MouseButton button = Qt::MouseButton(i); + mouseEvent->setButtonDownPos(button, mouseGrabberButtonDownPos.value(button, item->d_ptr->genericMapFromScene(mouseEvent->scenePos(), mouseEvent->widget()))); + mouseEvent->setButtonDownScenePos(button, mouseGrabberButtonDownScenePos.value(button, mouseEvent->scenePos())); + mouseEvent->setButtonDownScreenPos(button, mouseGrabberButtonDownScreenPos.value(button, mouseEvent->screenPos())); + } + mouseEvent->setPos(item->d_ptr->genericMapFromScene(mouseEvent->scenePos(), mouseEvent->widget())); + mouseEvent->setLastPos(item->d_ptr->genericMapFromScene(mouseEvent->lastScenePos(), mouseEvent->widget())); + sendEvent(item, mouseEvent); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::mousePressEventHandler(QGraphicsSceneMouseEvent *mouseEvent) +{ + Q_Q(QGraphicsScene); + + // Ignore by default, unless we find a mouse grabber that accepts it. + mouseEvent->ignore(); + + // Deliver to any existing mouse grabber. + if (!mouseGrabberItems.isEmpty()) { + if (mouseGrabberItems.last()->isBlockedByModalPanel()) + return; + // The event is ignored by default, but we disregard the event's + // accepted state after delivery; the mouse is grabbed, after all. + sendMouseEvent(mouseEvent); + return; + } + + // Start by determining the number of items at the current position. + // Reuse value from earlier calculations if possible. + if (cachedItemsUnderMouse.isEmpty()) { + cachedItemsUnderMouse = itemsAtPosition(mouseEvent->screenPos(), + mouseEvent->scenePos(), + mouseEvent->widget()); + } + + // Update window activation. + QGraphicsItem *topItem = cachedItemsUnderMouse.value(0); + QGraphicsWidget *newActiveWindow = topItem ? topItem->window() : 0; + if (newActiveWindow && newActiveWindow->isBlockedByModalPanel(&topItem)) { + // pass activation to the blocking modal window + newActiveWindow = topItem ? topItem->window() : 0; + } + + if (newActiveWindow != q->activeWindow()) + q->setActiveWindow(newActiveWindow); + + // Set focus on the topmost enabled item that can take focus. + bool setFocus = false; + + foreach (QGraphicsItem *item, cachedItemsUnderMouse) { + if (item->isBlockedByModalPanel() + || (item->d_ptr->flags & QGraphicsItem::ItemStopsFocusHandling)) { + // Make sure we don't clear focus. + setFocus = true; + break; + } + if (item->isEnabled() && ((item->flags() & QGraphicsItem::ItemIsFocusable))) { + if (!item->isWidget() || ((QGraphicsWidget *)item)->focusPolicy() & Qt::ClickFocus) { + setFocus = true; + if (item != q->focusItem() && item->d_ptr->mouseSetsFocus) + q->setFocusItem(item, Qt::MouseFocusReason); + break; + } + } + if (item->isPanel()) + break; + if (item->d_ptr->flags & QGraphicsItem::ItemStopsClickFocusPropagation) + break; + } + + // Check for scene modality. + bool sceneModality = false; + for (int i = 0; i < modalPanels.size(); ++i) { + if (modalPanels.at(i)->panelModality() == QGraphicsItem::SceneModal) { + sceneModality = true; + break; + } + } + + // If nobody could take focus, clear it. + if (!stickyFocus && !setFocus && !sceneModality) + q->setFocusItem(0, Qt::MouseFocusReason); + + // Any item will do. + if (sceneModality && cachedItemsUnderMouse.isEmpty()) + cachedItemsUnderMouse << modalPanels.first(); + + // Find a mouse grabber by sending mouse press events to all mouse grabber + // candidates one at a time, until the event is accepted. It's accepted by + // default, so the receiver has to explicitly ignore it for it to pass + // through. + foreach (QGraphicsItem *item, cachedItemsUnderMouse) { + if (!(item->acceptedMouseButtons() & mouseEvent->button())) { + // Skip items that don't accept the event's mouse button. + continue; + } + + // Check if this item is blocked by a modal panel and deliver the mouse event to the + // blocking panel instead of this item if blocked. + (void) item->isBlockedByModalPanel(&item); + + grabMouse(item, /* implicit = */ true); + mouseEvent->accept(); + + // check if the item we are sending to are disabled (before we send the event) + bool disabled = !item->isEnabled(); + bool isPanel = item->isPanel(); + if (mouseEvent->type() == QEvent::GraphicsSceneMouseDoubleClick + && item != lastMouseGrabberItem && lastMouseGrabberItem) { + // If this item is different from the item that received the last + // mouse event, and mouseEvent is a doubleclick event, then the + // event is converted to a press. Known limitation: + // Triple-clicking will not generate a doubleclick, though. + QGraphicsSceneMouseEvent mousePress(QEvent::GraphicsSceneMousePress); + mousePress.spont = mouseEvent->spont; + mousePress.accept(); + mousePress.setButton(mouseEvent->button()); + mousePress.setButtons(mouseEvent->buttons()); + mousePress.setScreenPos(mouseEvent->screenPos()); + mousePress.setScenePos(mouseEvent->scenePos()); + mousePress.setModifiers(mouseEvent->modifiers()); + mousePress.setWidget(mouseEvent->widget()); + mousePress.setButtonDownPos(mouseEvent->button(), + mouseEvent->buttonDownPos(mouseEvent->button())); + mousePress.setButtonDownScenePos(mouseEvent->button(), + mouseEvent->buttonDownScenePos(mouseEvent->button())); + mousePress.setButtonDownScreenPos(mouseEvent->button(), + mouseEvent->buttonDownScreenPos(mouseEvent->button())); + sendMouseEvent(&mousePress); + mouseEvent->setAccepted(mousePress.isAccepted()); + } else { + sendMouseEvent(mouseEvent); + } + + bool dontSendUngrabEvents = mouseGrabberItems.isEmpty() || mouseGrabberItems.last() != item; + if (disabled) { + ungrabMouse(item, /* itemIsDying = */ dontSendUngrabEvents); + break; + } + if (mouseEvent->isAccepted()) { + if (!mouseGrabberItems.isEmpty()) + storeMouseButtonsForMouseGrabber(mouseEvent); + lastMouseGrabberItem = item; + return; + } + ungrabMouse(item, /* itemIsDying = */ dontSendUngrabEvents); + + // Don't propagate through panels. + if (isPanel) + break; + } + + // Is the event still ignored? Then the mouse press goes to the scene. + // Reset the mouse grabber, clear the selection, clear focus, and leave + // the event ignored so that it can propagate through the originating + // view. + if (!mouseEvent->isAccepted()) { + clearMouseGrabber(); + + QGraphicsView *view = mouseEvent->widget() ? qobject_cast(mouseEvent->widget()->parentWidget()) : 0; + bool dontClearSelection = view && view->dragMode() == QGraphicsView::ScrollHandDrag; + if (!dontClearSelection) { + // Clear the selection if the originating view isn't in scroll + // hand drag mode. The view will clear the selection if no drag + // happened. + q->clearSelection(); + } + } +} + +/*! + \internal + + Ensures that the list of toplevels is sorted by insertion order, and that + the siblingIndexes are packed (no gaps), and start at 0. + + ### This function is almost identical to + QGraphicsItemPrivate::ensureSequentialSiblingIndex(). +*/ +void QGraphicsScenePrivate::ensureSequentialTopLevelSiblingIndexes() +{ + if (!topLevelSequentialOrdering) { + qSort(topLevelItems.begin(), topLevelItems.end(), QGraphicsItemPrivate::insertionOrder); + topLevelSequentialOrdering = true; + needSortTopLevelItems = 1; + } + if (holesInTopLevelSiblingIndex) { + holesInTopLevelSiblingIndex = 0; + for (int i = 0; i < topLevelItems.size(); ++i) + topLevelItems[i]->d_ptr->siblingIndex = i; + } +} + +/*! + \internal + + Set the font and propagate the changes if the font is different from the + current font. +*/ +void QGraphicsScenePrivate::setFont_helper(const QFont &font) +{ + if (this->font == font && this->font.resolve() == font.resolve()) + return; + updateFont(font); +} + +/*! + \internal + + Resolve the scene's font against the application font, and propagate the + changes too all items in the scene. +*/ +void QGraphicsScenePrivate::resolveFont() +{ + QFont naturalFont = QApplication::font(); + naturalFont.resolve(0); + QFont resolvedFont = font.resolve(naturalFont); + updateFont(resolvedFont); +} + +/*! + \internal + + Update the font, and whether or not it has changed, reresolve all fonts in + the scene. +*/ +void QGraphicsScenePrivate::updateFont(const QFont &font) +{ + Q_Q(QGraphicsScene); + + // Update local font setting. + this->font = font; + + // Resolve the fonts of all top-level widget items, or widget items + // whose parent is not a widget. + foreach (QGraphicsItem *item, q->items()) { + if (!item->parentItem()) { + // Resolvefont for an item is a noop operation, but + // every item can be a widget, or can have a widget + // childre. + item->d_ptr->resolveFont(font.resolve()); + } + } + + // Send the scene a FontChange event. + QEvent event(QEvent::FontChange); + QApplication::sendEvent(q, &event); +} + +/*! + \internal + + Set the palette and propagate the changes if the palette is different from + the current palette. +*/ +void QGraphicsScenePrivate::setPalette_helper(const QPalette &palette) +{ + if (this->palette == palette && this->palette.resolve() == palette.resolve()) + return; + updatePalette(palette); +} + +/*! + \internal + + Resolve the scene's palette against the application palette, and propagate + the changes too all items in the scene. +*/ +void QGraphicsScenePrivate::resolvePalette() +{ + QPalette naturalPalette = QApplication::palette(); + naturalPalette.resolve(0); + QPalette resolvedPalette = palette.resolve(naturalPalette); + updatePalette(resolvedPalette); +} + +/*! + \internal + + Update the palette, and whether or not it has changed, reresolve all + palettes in the scene. +*/ +void QGraphicsScenePrivate::updatePalette(const QPalette &palette) +{ + Q_Q(QGraphicsScene); + + // Update local palette setting. + this->palette = palette; + + // Resolve the palettes of all top-level widget items, or widget items + // whose parent is not a widget. + foreach (QGraphicsItem *item, q->items()) { + if (!item->parentItem()) { + // Resolvefont for an item is a noop operation, but + // every item can be a widget, or can have a widget + // childre. + item->d_ptr->resolvePalette(palette.resolve()); + } + } + + // Send the scene a PaletteChange event. + QEvent event(QEvent::PaletteChange); + QApplication::sendEvent(q, &event); +} + +/*! + Constructs a QGraphicsScene object. The \a parent parameter is + passed to QObject's constructor. +*/ +QGraphicsScene::QGraphicsScene(QObject *parent) + : QObject(*new QGraphicsScenePrivate, parent) +{ + d_func()->init(); +} + +/*! + Constructs a QGraphicsScene object, using \a sceneRect for its + scene rectangle. The \a parent parameter is passed to QObject's + constructor. + + \sa sceneRect +*/ +QGraphicsScene::QGraphicsScene(const QRectF &sceneRect, QObject *parent) + : QObject(*new QGraphicsScenePrivate, parent) +{ + d_func()->init(); + setSceneRect(sceneRect); +} + +/*! + Constructs a QGraphicsScene object, using the rectangle specified + by (\a x, \a y), and the given \a width and \a height for its + scene rectangle. The \a parent parameter is passed to QObject's + constructor. + + \sa sceneRect +*/ +QGraphicsScene::QGraphicsScene(qreal x, qreal y, qreal width, qreal height, QObject *parent) + : QObject(*new QGraphicsScenePrivate, parent) +{ + d_func()->init(); + setSceneRect(x, y, width, height); +} + +/*! + Removes and deletes all items from the scene object + before destroying the scene object. The scene object + is removed from the application's global scene list, + and it is removed from all associated views. +*/ +QGraphicsScene::~QGraphicsScene() +{ + Q_D(QGraphicsScene); + + // Remove this scene from qApp's global scene list. + qApp->d_func()->scene_list.removeAll(this); + + clear(); + + // Remove this scene from all associated views. + for (int j = 0; j < d->views.size(); ++j) + d->views.at(j)->setScene(0); +} + +/*! + \property QGraphicsScene::sceneRect + \brief the scene rectangle; the bounding rectangle of the scene + + The scene rectangle defines the extent of the scene. It is + primarily used by QGraphicsView to determine the view's default + scrollable area, and by QGraphicsScene to manage item indexing. + + If unset, or if set to a null QRectF, sceneRect() will return the largest + bounding rect of all items on the scene since the scene was created (i.e., + a rectangle that grows when items are added to or moved in the scene, but + never shrinks). + + \sa width(), height(), QGraphicsView::sceneRect +*/ +QRectF QGraphicsScene::sceneRect() const +{ + Q_D(const QGraphicsScene); + if (d->hasSceneRect) + return d->sceneRect; + + if (d->dirtyGrowingItemsBoundingRect) { + // Lazily update the growing items bounding rect + QGraphicsScenePrivate *thatd = const_cast(d); + QRectF oldGrowingBoundingRect = thatd->growingItemsBoundingRect; + thatd->growingItemsBoundingRect |= itemsBoundingRect(); + thatd->dirtyGrowingItemsBoundingRect = false; + if (oldGrowingBoundingRect != thatd->growingItemsBoundingRect) + emit const_cast(this)->sceneRectChanged(thatd->growingItemsBoundingRect); + } + return d->growingItemsBoundingRect; +} +void QGraphicsScene::setSceneRect(const QRectF &rect) +{ + Q_D(QGraphicsScene); + if (rect != d->sceneRect) { + d->hasSceneRect = !rect.isNull(); + d->sceneRect = rect; + emit sceneRectChanged(d->hasSceneRect ? rect : d->growingItemsBoundingRect); + } +} + +/*! + \fn qreal QGraphicsScene::width() const + + This convenience function is equivalent to calling sceneRect().width(). + + \sa height() +*/ + +/*! + \fn qreal QGraphicsScene::height() const + + This convenience function is equivalent to calling \c sceneRect().height(). + + \sa width() +*/ + +/*! + Renders the \a source rect from scene into \a target, using \a painter. This + function is useful for capturing the contents of the scene onto a paint + device, such as a QImage (e.g., to take a screenshot), or for printing + with QPrinter. For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 1 + + If \a source is a null rect, this function will use sceneRect() to + determine what to render. If \a target is a null rect, the dimensions of \a + painter's paint device will be used. + + The source rect contents will be transformed according to \a + aspectRatioMode to fit into the target rect. By default, the aspect ratio + is kept, and \a source is scaled to fit in \a target. + + \sa QGraphicsView::render() +*/ +void QGraphicsScene::render(QPainter *painter, const QRectF &target, const QRectF &source, + Qt::AspectRatioMode aspectRatioMode) +{ + // ### Switch to using the recursive rendering algorithm instead. + + // Default source rect = scene rect + QRectF sourceRect = source; + if (sourceRect.isNull()) + sourceRect = sceneRect(); + + // Default target rect = device rect + QRectF targetRect = target; + if (targetRect.isNull()) { + if (painter->device()->devType() == QInternal::Picture) + targetRect = sourceRect; + else + targetRect.setRect(0, 0, painter->device()->width(), painter->device()->height()); + } + + // Find the ideal x / y scaling ratio to fit \a source into \a target. + qreal xratio = targetRect.width() / sourceRect.width(); + qreal yratio = targetRect.height() / sourceRect.height(); + + // Scale according to the aspect ratio mode. + switch (aspectRatioMode) { + case Qt::KeepAspectRatio: + xratio = yratio = qMin(xratio, yratio); + break; + case Qt::KeepAspectRatioByExpanding: + xratio = yratio = qMax(xratio, yratio); + break; + case Qt::IgnoreAspectRatio: + break; + } + + // Find all items to draw, and reverse the list (we want to draw + // in reverse order). + QList itemList = items(sourceRect, Qt::IntersectsItemBoundingRect); + QGraphicsItem **itemArray = new QGraphicsItem *[itemList.size()]; + int numItems = itemList.size(); + for (int i = 0; i < numItems; ++i) + itemArray[numItems - i - 1] = itemList.at(i); + itemList.clear(); + + painter->save(); + + // Transform the painter. + painter->setClipRect(targetRect, Qt::IntersectClip); + QTransform painterTransform; + painterTransform *= QTransform() + .translate(targetRect.left(), targetRect.top()) + .scale(xratio, yratio) + .translate(-sourceRect.left(), -sourceRect.top()); + painter->setWorldTransform(painterTransform, true); + + // Two unit vectors. + QLineF v1(0, 0, 1, 0); + QLineF v2(0, 0, 0, 1); + + // Generate the style options + QStyleOptionGraphicsItem *styleOptionArray = new QStyleOptionGraphicsItem[numItems]; + for (int i = 0; i < numItems; ++i) + itemArray[i]->d_ptr->initStyleOption(&styleOptionArray[i], painterTransform, targetRect.toRect()); + + // Render the scene. + drawBackground(painter, sourceRect); + drawItems(painter, numItems, itemArray, styleOptionArray); + drawForeground(painter, sourceRect); + + delete [] itemArray; + delete [] styleOptionArray; + + painter->restore(); +} + +/*! + \property QGraphicsScene::itemIndexMethod + \brief the item indexing method. + + QGraphicsScene applies an indexing algorithm to the scene, to speed up + item discovery functions like items() and itemAt(). Indexing is most + efficient for static scenes (i.e., where items don't move around). For + dynamic scenes, or scenes with many animated items, the index bookkeeping + can outweight the fast lookup speeds. + + For the common case, the default index method BspTreeIndex works fine. If + your scene uses many animations and you are experiencing slowness, you can + disable indexing by calling \c setItemIndexMethod(NoIndex). + + \sa bspTreeDepth +*/ +QGraphicsScene::ItemIndexMethod QGraphicsScene::itemIndexMethod() const +{ + Q_D(const QGraphicsScene); + return d->indexMethod; +} +void QGraphicsScene::setItemIndexMethod(ItemIndexMethod method) +{ + Q_D(QGraphicsScene); + if (d->indexMethod == method) + return; + + d->indexMethod = method; + + QList oldItems = d->index->items(Qt::DescendingOrder); + delete d->index; + if (method == BspTreeIndex) + d->index = new QGraphicsSceneBspTreeIndex(this); + else + d->index = new QGraphicsSceneLinearIndex(this); + for (int i = oldItems.size() - 1; i >= 0; --i) + d->index->addItem(oldItems.at(i)); +} + +/*! + \property QGraphicsScene::bspTreeDepth + \brief the depth of QGraphicsScene's BSP index tree + \since 4.3 + + This property has no effect when NoIndex is used. + + This value determines the depth of QGraphicsScene's BSP tree. The depth + directly affects QGraphicsScene's performance and memory usage; the latter + growing exponentially with the depth of the tree. With an optimal tree + depth, QGraphicsScene can instantly determine the locality of items, even + for scenes with thousands or millions of items. This also greatly improves + rendering performance. + + By default, the value is 0, in which case Qt will guess a reasonable + default depth based on the size, location and number of items in the + scene. If these parameters change frequently, however, you may experience + slowdowns as QGraphicsScene retunes the depth internally. You can avoid + potential slowdowns by fixating the tree depth through setting this + property. + + The depth of the tree and the size of the scene rectangle decide the + granularity of the scene's partitioning. The size of each scene segment is + determined by the following algorithm: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 2 + + The BSP tree has an optimal size when each segment contains between 0 and + 10 items. + + \sa itemIndexMethod +*/ +int QGraphicsScene::bspTreeDepth() const +{ + Q_D(const QGraphicsScene); + QGraphicsSceneBspTreeIndex *bspTree = qobject_cast(d->index); + return bspTree ? bspTree->bspTreeDepth() : 0; +} +void QGraphicsScene::setBspTreeDepth(int depth) +{ + Q_D(QGraphicsScene); + if (depth < 0) { + qWarning("QGraphicsScene::setBspTreeDepth: invalid depth %d ignored; must be >= 0", depth); + return; + } + + QGraphicsSceneBspTreeIndex *bspTree = qobject_cast(d->index); + if (!bspTree) { + qWarning("QGraphicsScene::setBspTreeDepth: can not apply if indexing method is not BSP"); + return; + } + bspTree->setBspTreeDepth(depth); +} + +/*! + \property QGraphicsScene::sortCacheEnabled + \brief whether sort caching is enabled + \since 4.5 + \obsolete + + Since Qt 4.6, this property has no effect. +*/ +bool QGraphicsScene::isSortCacheEnabled() const +{ + Q_D(const QGraphicsScene); + return d->sortCacheEnabled; +} +void QGraphicsScene::setSortCacheEnabled(bool enabled) +{ + Q_D(QGraphicsScene); + if (d->sortCacheEnabled == enabled) + return; + d->sortCacheEnabled = enabled; +} + +/*! + Calculates and returns the bounding rect of all items on the scene. This + function works by iterating over all items, and because if this, it can + be slow for large scenes. + + \sa sceneRect() +*/ +QRectF QGraphicsScene::itemsBoundingRect() const +{ + // Does not take untransformable items into account. + QRectF boundingRect; + foreach (QGraphicsItem *item, items()) + boundingRect |= item->sceneBoundingRect(); + return boundingRect; +} + +/*! + Returns a list of all items in the scene in descending stacking order. + + \sa addItem(), removeItem(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items() const +{ + Q_D(const QGraphicsScene); + return d->index->items(Qt::DescendingOrder); +} + +/*! + Returns an ordered list of all items on the scene. \a order decides the + stacking order. + + \sa addItem(), removeItem(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(Qt::SortOrder order) const +{ + Q_D(const QGraphicsScene); + return d->index->items(order); +} + +/*! + \obsolete + + Returns all visible items at position \a pos in the scene. The items are + listed in descending stacking order (i.e., the first item in the list is the + top-most item, and the last item is the bottom-most item). + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPointF &pos) const +{ + Q_D(const QGraphicsScene); + return d->index->items(pos, Qt::IntersectsItemShape, Qt::DescendingOrder); +} + +/*! + \overload + \obsolete + + Returns all visible items that, depending on \a mode, are either inside or + intersect with the specified \a rectangle. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a rectangle are returned. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QRectF &rectangle, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsScene); + return d->index->items(rectangle, mode, Qt::DescendingOrder); +} + +/*! + \fn QList QGraphicsScene::items(qreal x, qreal y, qreal w, qreal h, Qt::ItemSelectionMode mode) const + \obsolete + \since 4.3 + + This convenience function is equivalent to calling items(QRectF(\a x, \a y, \a w, \a h), \a mode). + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. +*/ + +/*! + \fn QList QGraphicsScene::items(qreal x, qreal y, qreal w, qreal h, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \overload + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are + either inside or intersect with the rectangle defined by \a x, \a y, + \a w and \a h, in a list sorted using \a order. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. +*/ + +/*! + \fn QList QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode) const + \overload + \obsolete + + Returns all visible items that, depending on \a mode, are either inside or + intersect with the polygon \a polygon. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a polygon are returned. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsScene); + return d->index->items(polygon, mode, Qt::DescendingOrder); +} + +/*! + \fn QList QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode) const + \overload + \obsolete + + Returns all visible items that, depending on \a path, are either inside or + intersect with the path \a path. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a path are returned. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsScene); + return d->index->items(path, mode, Qt::DescendingOrder); +} + +/*! + \fn QList QGraphicsScene::items(const QPointF &pos, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are at + the specified \a pos in a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with \a pos are returned. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPointF &pos, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsScene); + return d->index->items(pos, mode, order, deviceTransform); +} + +/*! + \fn QList QGraphicsScene::items(const QRectF &rect, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \overload + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are + either inside or intersect with the specified \a rect and return a + list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a rect are returned. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QRectF &rect, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsScene); + return d->index->items(rect, mode, order, deviceTransform); +} + +/*! + \fn QList QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \overload + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are + either inside or intersect with the specified \a polygon and return + a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a polygon are returned. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsScene); + return d->index->items(polygon, mode, order, deviceTransform); +} + +/*! + \fn QList QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \overload + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are + either inside or intersect with the specified \a path and return a + list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a path are returned. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsScene); + return d->index->items(path, mode, order, deviceTransform); +} + +/*! + Returns a list of all items that collide with \a item. Collisions are + determined by calling QGraphicsItem::collidesWithItem(); the collision + detection is determined by \a mode. By default, all items whose shape + intersects \a item or is contained inside \a item's shape are returned. + + The items are returned in descending stacking order (i.e., the first item + in the list is the uppermost item, and the last item is the lowermost + item). + + \sa items(), itemAt(), QGraphicsItem::collidesWithItem(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::collidingItems(const QGraphicsItem *item, + Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsScene); + if (!item) { + qWarning("QGraphicsScene::collidingItems: cannot find collisions for null item"); + return QList(); + } + + // Does not support ItemIgnoresTransformations. + QList tmp; + foreach (QGraphicsItem *itemInVicinity, d->index->estimateItems(item->sceneBoundingRect(), Qt::DescendingOrder)) { + if (item != itemInVicinity && item->collidesWithItem(itemInVicinity, mode)) + tmp << itemInVicinity; + } + return tmp; +} + +/*! + \overload + \obsolete + + Returns the topmost visible item at the specified \a position, or 0 if + there are no items at this position. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa items(), collidingItems(), {QGraphicsItem#Sorting}{Sorting} +*/ +QGraphicsItem *QGraphicsScene::itemAt(const QPointF &position) const +{ + QList itemsAtPoint = items(position); + return itemsAtPoint.isEmpty() ? 0 : itemsAtPoint.first(); +} + +/*! + \since 4.6 + + Returns the topmost visible item at the specified \a position, or 0 + if there are no items at this position. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa items(), collidingItems(), {QGraphicsItem#Sorting}{Sorting} +*/ +QGraphicsItem *QGraphicsScene::itemAt(const QPointF &position, const QTransform &deviceTransform) const +{ + QList itemsAtPoint = items(position, Qt::IntersectsItemShape, + Qt::DescendingOrder, deviceTransform); + return itemsAtPoint.isEmpty() ? 0 : itemsAtPoint.first(); +} + +/*! + \fn QGraphicsScene::itemAt(qreal x, qreal y, const QTransform &deviceTransform) const + \overload + \since 4.6 + + Returns the topmost item at the position specified by (\a x, \a + y), or 0 if there are no items at this position. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + This convenience function is equivalent to calling \c + {itemAt(QPointF(x, y), deviceTransform)}. +*/ + +/*! + \fn QGraphicsScene::itemAt(qreal x, qreal y) const + \overload + \obsolete + + Returns the topmost item at the position specified by (\a x, \a + y), or 0 if there are no items at this position. + + This convenience function is equivalent to calling \c + {itemAt(QPointF(x, y))}. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. +*/ + +/*! + Returns a list of all currently selected items. The items are + returned in no particular order. + + \sa setSelectionArea() +*/ +QList QGraphicsScene::selectedItems() const +{ + Q_D(const QGraphicsScene); + + // Optimization: Lazily removes items that are not selected. + QGraphicsScene *that = const_cast(this); + QSet actuallySelectedSet; + foreach (QGraphicsItem *item, that->d_func()->selectedItems) { + if (item->isSelected()) + actuallySelectedSet << item; + } + + that->d_func()->selectedItems = actuallySelectedSet; + + return d->selectedItems.values(); +} + +/*! + Returns the selection area that was previously set with + setSelectionArea(), or an empty QPainterPath if no selection area has been + set. + + \sa setSelectionArea() +*/ +QPainterPath QGraphicsScene::selectionArea() const +{ + Q_D(const QGraphicsScene); + return d->selectionArea; +} + +/*! + \since 4.6 + + Sets the selection area to \a path. All items within this area are + immediately selected, and all items outside are unselected. You can get + the list of all selected items by calling selectedItems(). + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + For an item to be selected, it must be marked as \e selectable + (QGraphicsItem::ItemIsSelectable). + + \sa clearSelection(), selectionArea() +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path, const QTransform &deviceTransform) +{ + setSelectionArea(path, Qt::IntersectsItemShape, deviceTransform); +} + +/*! + \obsolete + \overload + + Sets the selection area to \a path. + + This function is deprecated and leads to incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path) +{ + setSelectionArea(path, Qt::IntersectsItemShape, QTransform()); +} + +/*! + \obsolete + \overload + \since 4.3 + + Sets the selection area to \a path using \a mode to determine if items are + included in the selection area. + + \sa clearSelection(), selectionArea() +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode) +{ + setSelectionArea(path, mode, QTransform()); +} + +/*! + \overload + \since 4.6 + + Sets the selection area to \a path using \a mode to determine if items are + included in the selection area. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa clearSelection(), selectionArea() +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode, + const QTransform &deviceTransform) +{ + Q_D(QGraphicsScene); + + // Note: with boolean path operations, we can improve performance here + // quite a lot by "growing" the old path instead of replacing it. That + // allows us to only check the intersect area for changes, instead of + // reevaluating the whole path over again. + d->selectionArea = path; + + QSet unselectItems = d->selectedItems; + + // Disable emitting selectionChanged() for individual items. + ++d->selectionChanging; + bool changed = false; + + // Set all items in path to selected. + foreach (QGraphicsItem *item, items(path, mode, Qt::DescendingOrder, deviceTransform)) { + if (item->flags() & QGraphicsItem::ItemIsSelectable) { + if (!item->isSelected()) + changed = true; + unselectItems.remove(item); + item->setSelected(true); + } + } + + // Unselect all items outside path. + foreach (QGraphicsItem *item, unselectItems) { + item->setSelected(false); + changed = true; + } + + // Reenable emitting selectionChanged() for individual items. + --d->selectionChanging; + + if (!d->selectionChanging && changed) + emit selectionChanged(); +} + +/*! + Clears the current selection. + + \sa setSelectionArea(), selectedItems() +*/ +void QGraphicsScene::clearSelection() +{ + Q_D(QGraphicsScene); + + // Disable emitting selectionChanged + ++d->selectionChanging; + bool changed = !d->selectedItems.isEmpty(); + + foreach (QGraphicsItem *item, d->selectedItems) + item->setSelected(false); + d->selectedItems.clear(); + + // Reenable emitting selectionChanged() for individual items. + --d->selectionChanging; + + if (!d->selectionChanging && changed) + emit selectionChanged(); +} + +/*! + \since 4.4 + + Removes and deletes all items from the scene, but otherwise leaves the + state of the scene unchanged. + + \sa addItem() +*/ +void QGraphicsScene::clear() +{ + Q_D(QGraphicsScene); + // NB! We have to clear the index before deleting items; otherwise the + // index might try to access dangling item pointers. + d->index->clear(); + // NB! QGraphicsScenePrivate::unregisterTopLevelItem() removes items + while (!d->topLevelItems.isEmpty()) + delete d->topLevelItems.first(); + Q_ASSERT(d->topLevelItems.isEmpty()); + d->lastItemCount = 0; + d->allItemsIgnoreHoverEvents = true; + d->allItemsUseDefaultCursor = true; + d->allItemsIgnoreTouchEvents = true; +} + +/*! + Groups all items in \a items into a new QGraphicsItemGroup, and returns a + pointer to the group. The group is created with the common ancestor of \a + items as its parent, and with position (0, 0). The items are all + reparented to the group, and their positions and transformations are + mapped to the group. If \a items is empty, this function will return an + empty top-level QGraphicsItemGroup. + + QGraphicsScene has ownership of the group item; you do not need to delete + it. To dismantle (ungroup) a group, call destroyItemGroup(). + + \sa destroyItemGroup(), QGraphicsItemGroup::addToGroup() +*/ +QGraphicsItemGroup *QGraphicsScene::createItemGroup(const QList &items) +{ + // Build a list of the first item's ancestors + QList ancestors; + int n = 0; + if (!items.isEmpty()) { + QGraphicsItem *parent = items.at(n++); + while ((parent = parent->parentItem())) + ancestors.append(parent); + } + + // Find the common ancestor for all items + QGraphicsItem *commonAncestor = 0; + if (!ancestors.isEmpty()) { + while (n < items.size()) { + int commonIndex = -1; + QGraphicsItem *parent = items.at(n++); + do { + int index = ancestors.indexOf(parent, qMax(0, commonIndex)); + if (index != -1) { + commonIndex = index; + break; + } + } while ((parent = parent->parentItem())); + + if (commonIndex == -1) { + commonAncestor = 0; + break; + } + + commonAncestor = ancestors.at(commonIndex); + } + } + + // Create a new group at that level + QGraphicsItemGroup *group = new QGraphicsItemGroup(commonAncestor); + if (!commonAncestor) + addItem(group); + foreach (QGraphicsItem *item, items) + group->addToGroup(item); + return group; +} + +/*! + Reparents all items in \a group to \a group's parent item, then removes \a + group from the scene, and finally deletes it. The items' positions and + transformations are mapped from the group to the group's parent. + + \sa createItemGroup(), QGraphicsItemGroup::removeFromGroup() +*/ +void QGraphicsScene::destroyItemGroup(QGraphicsItemGroup *group) +{ + foreach (QGraphicsItem *item, group->children()) + group->removeFromGroup(item); + removeItem(group); + delete group; +} + +/*! + Adds or moves the \a item and all its childen to this scene. + This scene takes ownership of the \a item. + + If the item is visible (i.e., QGraphicsItem::isVisible() returns + true), QGraphicsScene will emit changed() once control goes back + to the event loop. + + If the item is already in a different scene, it will first be + removed from its old scene, and then added to this scene as a + top-level. + + QGraphicsScene will send ItemSceneChange notifications to \a item + while it is added to the scene. If item does not currently belong + to a scene, only one notification is sent. If it does belong to + scene already (i.e., it is moved to this scene), QGraphicsScene + will send an addition notification as the item is removed from its + previous scene. + + If the item is a panel, the scene is active, and there is no + active panel in the scene, then the item will be activated. + + \sa removeItem(), addEllipse(), addLine(), addPath(), addPixmap(), + addRect(), addText(), addWidget(), {QGraphicsItem#Sorting}{Sorting} +*/ +void QGraphicsScene::addItem(QGraphicsItem *item) +{ + Q_D(QGraphicsScene); + if (!item) { + qWarning("QGraphicsScene::addItem: cannot add null item"); + return; + } + if (item->d_ptr->scene == this) { + qWarning("QGraphicsScene::addItem: item has already been added to this scene"); + return; + } + // Remove this item from its existing scene + if (QGraphicsScene *oldScene = item->d_ptr->scene) + oldScene->removeItem(item); + + // Notify the item that its scene is changing, and allow the item to + // react. + const QVariant newSceneVariant(item->itemChange(QGraphicsItem::ItemSceneChange, + QVariant::fromValue(this))); + QGraphicsScene *targetScene = qvariant_cast(newSceneVariant); + if (targetScene != this) { + if (targetScene && item->d_ptr->scene != targetScene) + targetScene->addItem(item); + return; + } + + // QDeclarativeItems do not rely on initial itemChanged message, as the componentComplete + // function allows far more opportunity for delayed-construction optimization. + if (!item->d_ptr->isDeclarativeItem) { + if (d->unpolishedItems.isEmpty()) { + QMetaMethod method = metaObject()->method(d->polishItemsIndex); + method.invoke(this, Qt::QueuedConnection); + } + d->unpolishedItems.append(item); + item->d_ptr->pendingPolish = true; + } + + // Detach this item from its parent if the parent's scene is different + // from this scene. + if (QGraphicsItem *itemParent = item->d_ptr->parent) { + if (itemParent->d_ptr->scene != this) + item->setParentItem(0); + } + + // Add the item to this scene + item->d_func()->scene = targetScene; + + // Add the item in the index + d->index->addItem(item); + + // Add to list of toplevels if this item is a toplevel. + if (!item->d_ptr->parent) + d->registerTopLevelItem(item); + + // Add to list of items that require an update. We cannot assume that the + // item is fully constructed, so calling item->update() can lead to a pure + // virtual function call to boundingRect(). + d->markDirty(item); + d->dirtyGrowingItemsBoundingRect = true; + + // Disable selectionChanged() for individual items + ++d->selectionChanging; + int oldSelectedItemSize = d->selectedItems.size(); + + // Enable mouse tracking if the item accepts hover events or has a cursor set. + if (d->allItemsIgnoreHoverEvents && d->itemAcceptsHoverEvents_helper(item)) { + d->allItemsIgnoreHoverEvents = false; + d->enableMouseTrackingOnViews(); + } +#ifndef QT_NO_CURSOR + if (d->allItemsUseDefaultCursor && item->d_ptr->hasCursor) { + d->allItemsUseDefaultCursor = false; + if (d->allItemsIgnoreHoverEvents) // already enabled otherwise + d->enableMouseTrackingOnViews(); + } +#endif //QT_NO_CURSOR + + // Enable touch events if the item accepts touch events. + if (d->allItemsIgnoreTouchEvents && item->d_ptr->acceptTouchEvents) { + d->allItemsIgnoreTouchEvents = false; + d->enableTouchEventsOnViews(); + } + +#ifndef QT_NO_GESTURES + foreach (Qt::GestureType gesture, item->d_ptr->gestureContext.keys()) + d->grabGesture(item, gesture); +#endif + + // Update selection lists + if (item->isSelected()) + d->selectedItems << item; + if (item->isWidget() && item->isVisible() && static_cast(item)->windowType() == Qt::Popup) + d->addPopup(static_cast(item)); + if (item->isPanel() && item->isVisible() && item->panelModality() != QGraphicsItem::NonModal) + d->enterModal(item); + + // Update creation order focus chain. Make sure to leave the widget's + // internal tab order intact. + if (item->isWidget()) { + QGraphicsWidget *widget = static_cast(item); + if (!d->tabFocusFirst) { + // No first tab focus widget - make this the first tab focus + // widget. + d->tabFocusFirst = widget; + } else if (!widget->parentWidget()) { + // Adding a widget that is not part of a tab focus chain. + QGraphicsWidget *last = d->tabFocusFirst->d_func()->focusPrev; + QGraphicsWidget *lastNew = widget->d_func()->focusPrev; + last->d_func()->focusNext = widget; + widget->d_func()->focusPrev = last; + d->tabFocusFirst->d_func()->focusPrev = lastNew; + lastNew->d_func()->focusNext = d->tabFocusFirst; + } + } + + // Add all children recursively + item->d_ptr->ensureSortedChildren(); + for (int i = 0; i < item->d_ptr->children.size(); ++i) + addItem(item->d_ptr->children.at(i)); + + // Resolve font and palette. + item->d_ptr->resolveFont(d->font.resolve()); + item->d_ptr->resolvePalette(d->palette.resolve()); + + + // Reenable selectionChanged() for individual items + --d->selectionChanging; + if (!d->selectionChanging && d->selectedItems.size() != oldSelectedItemSize) + emit selectionChanged(); + + // Deliver post-change notification + item->itemChange(QGraphicsItem::ItemSceneHasChanged, newSceneVariant); + + // Update explicit activation + bool autoActivate = true; + if (!d->childExplicitActivation && item->d_ptr->explicitActivate) + d->childExplicitActivation = item->d_ptr->wantsActive ? 1 : 2; + if (d->childExplicitActivation && item->isPanel()) { + if (d->childExplicitActivation == 1) + setActivePanel(item); + else + autoActivate = false; + d->childExplicitActivation = 0; + } else if (!item->d_ptr->parent) { + d->childExplicitActivation = 0; + } + + // Auto-activate this item's panel if nothing else has been activated + if (autoActivate) { + if (!d->lastActivePanel && !d->activePanel && item->isPanel()) { + if (isActive()) + setActivePanel(item); + else + d->lastActivePanel = item; + } + } + + if (item->d_ptr->flags & QGraphicsItem::ItemSendsScenePositionChanges) + d->registerScenePosItem(item); + + // Ensure that newly added items that have subfocus set, gain + // focus automatically if there isn't a focus item already. + if (!d->focusItem && item != d->lastFocusItem && item->focusItem() == item) + item->focusItem()->setFocus(); + + d->updateInputMethodSensitivityInViews(); +} + +/*! + Creates and adds an ellipse item to the scene, and returns the item + pointer. The geometry of the ellipse is defined by \a rect, and its pen + and brush are initialized to \a pen and \a brush. + + Note that the item's geometry is provided in item coordinates, and its + position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addLine(), addPath(), addPixmap(), addRect(), addText(), addItem(), + addWidget() +*/ +QGraphicsEllipseItem *QGraphicsScene::addEllipse(const QRectF &rect, const QPen &pen, const QBrush &brush) +{ + QGraphicsEllipseItem *item = new QGraphicsEllipseItem(rect); + item->setPen(pen); + item->setBrush(brush); + addItem(item); + return item; +} + +/*! + \fn QGraphicsEllipseItem *QGraphicsScene::addEllipse(qreal x, qreal y, qreal w, qreal h, const QPen &pen, const QBrush &brush) + \since 4.3 + + This convenience function is equivalent to calling addEllipse(QRectF(\a x, + \a y, \a w, \a h), \a pen, \a brush). +*/ + +/*! + Creates and adds a line item to the scene, and returns the item + pointer. The geometry of the line is defined by \a line, and its pen + is initialized to \a pen. + + Note that the item's geometry is provided in item coordinates, and its + position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addPath(), addPixmap(), addRect(), addText(), addItem(), + addWidget() +*/ +QGraphicsLineItem *QGraphicsScene::addLine(const QLineF &line, const QPen &pen) +{ + QGraphicsLineItem *item = new QGraphicsLineItem(line); + item->setPen(pen); + addItem(item); + return item; +} + +/*! + \fn QGraphicsLineItem *QGraphicsScene::addLine(qreal x1, qreal y1, qreal x2, qreal y2, const QPen &pen) + \since 4.3 + + This convenience function is equivalent to calling addLine(QLineF(\a x1, + \a y1, \a x2, \a y2), \a pen). +*/ + +/*! + Creates and adds a path item to the scene, and returns the item + pointer. The geometry of the path is defined by \a path, and its pen and + brush are initialized to \a pen and \a brush. + + Note that the item's geometry is provided in item coordinates, and its + position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addLine(), addPixmap(), addRect(), addText(), addItem(), + addWidget() +*/ +QGraphicsPathItem *QGraphicsScene::addPath(const QPainterPath &path, const QPen &pen, const QBrush &brush) +{ + QGraphicsPathItem *item = new QGraphicsPathItem(path); + item->setPen(pen); + item->setBrush(brush); + addItem(item); + return item; +} + +/*! + Creates and adds a pixmap item to the scene, and returns the item + pointer. The pixmap is defined by \a pixmap. + + Note that the item's geometry is provided in item coordinates, and its + position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addLine(), addPath(), addRect(), addText(), addItem(), + addWidget() +*/ +QGraphicsPixmapItem *QGraphicsScene::addPixmap(const QPixmap &pixmap) +{ + QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); + addItem(item); + return item; +} + +/*! + Creates and adds a polygon item to the scene, and returns the item + pointer. The polygon is defined by \a polygon, and its pen and + brush are initialized to \a pen and \a brush. + + Note that the item's geometry is provided in item coordinates, and its + position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addLine(), addPath(), addRect(), addText(), addItem(), + addWidget() +*/ +QGraphicsPolygonItem *QGraphicsScene::addPolygon(const QPolygonF &polygon, + const QPen &pen, const QBrush &brush) +{ + QGraphicsPolygonItem *item = new QGraphicsPolygonItem(polygon); + item->setPen(pen); + item->setBrush(brush); + addItem(item); + return item; +} + +/*! + Creates and adds a rectangle item to the scene, and returns the item + pointer. The geometry of the rectangle is defined by \a rect, and its pen + and brush are initialized to \a pen and \a brush. + + Note that the item's geometry is provided in item coordinates, and its + position is initialized to (0, 0). For example, if a QRect(50, 50, 100, + 100) is added, its top-left corner will be at (50, 50) relative to the + origin in the items coordinate system. + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addLine(), addPixmap(), addPixmap(), addText(), + addItem(), addWidget() +*/ +QGraphicsRectItem *QGraphicsScene::addRect(const QRectF &rect, const QPen &pen, const QBrush &brush) +{ + QGraphicsRectItem *item = new QGraphicsRectItem(rect); + item->setPen(pen); + item->setBrush(brush); + addItem(item); + return item; +} + +/*! + \fn QGraphicsRectItem *QGraphicsScene::addRect(qreal x, qreal y, qreal w, qreal h, const QPen &pen, const QBrush &brush) + \since 4.3 + + This convenience function is equivalent to calling addRect(QRectF(\a x, + \a y, \a w, \a h), \a pen, \a brush). +*/ + +/*! + Creates and adds a text item to the scene, and returns the item + pointer. The text string is initialized to \a text, and its font + is initialized to \a font. + + The item's position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addLine(), addPixmap(), addPixmap(), addRect(), + addItem(), addWidget() +*/ +QGraphicsTextItem *QGraphicsScene::addText(const QString &text, const QFont &font) +{ + QGraphicsTextItem *item = new QGraphicsTextItem(text); + item->setFont(font); + addItem(item); + return item; +} + +/*! + Creates and adds a QGraphicsSimpleTextItem to the scene, and returns the + item pointer. The text string is initialized to \a text, and its font is + initialized to \a font. + + The item's position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addLine(), addPixmap(), addPixmap(), addRect(), + addItem(), addWidget() +*/ +QGraphicsSimpleTextItem *QGraphicsScene::addSimpleText(const QString &text, const QFont &font) +{ + QGraphicsSimpleTextItem *item = new QGraphicsSimpleTextItem(text); + item->setFont(font); + addItem(item); + return item; +} + +/*! + Creates a new QGraphicsProxyWidget for \a widget, adds it to the scene, + and returns a pointer to the proxy. \a wFlags set the default window flags + for the embedding proxy widget. + + The item's position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + Note that widgets with the Qt::WA_PaintOnScreen widget attribute + set and widgets that wrap an external application or controller + are not supported. Examples are QGLWidget and QAxWidget. + + \sa addEllipse(), addLine(), addPixmap(), addPixmap(), addRect(), + addText(), addSimpleText(), addItem() +*/ +QGraphicsProxyWidget *QGraphicsScene::addWidget(QWidget *widget, Qt::WindowFlags wFlags) +{ + QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget(0, wFlags); + proxy->setWidget(widget); + addItem(proxy); + return proxy; +} + +/*! + Removes the item \a item and all its children from the scene. The + ownership of \a item is passed on to the caller (i.e., + QGraphicsScene will no longer delete \a item when destroyed). + + \sa addItem() +*/ +void QGraphicsScene::removeItem(QGraphicsItem *item) +{ + // ### Refactoring: This function shares much functionality with _q_removeItemLater() + Q_D(QGraphicsScene); + if (!item) { + qWarning("QGraphicsScene::removeItem: cannot remove 0-item"); + return; + } + if (item->scene() != this) { + qWarning("QGraphicsScene::removeItem: item %p's scene (%p)" + " is different from this scene (%p)", + item, item->scene(), this); + return; + } + + // Notify the item that it's scene is changing to 0, allowing the item to + // react. + const QVariant newSceneVariant(item->itemChange(QGraphicsItem::ItemSceneChange, + QVariant::fromValue(0))); + QGraphicsScene *targetScene = qvariant_cast(newSceneVariant); + if (targetScene != 0 && targetScene != this) { + targetScene->addItem(item); + return; + } + + d->removeItemHelper(item); + + // Deliver post-change notification + item->itemChange(QGraphicsItem::ItemSceneHasChanged, newSceneVariant); + + d->updateInputMethodSensitivityInViews(); +} + +/*! + When the scene is active, this functions returns the scene's current focus + item, or 0 if no item currently has focus. When the scene is inactive, this + functions returns the item that will gain input focus when the scene becomes + active. + + The focus item receives keyboard input when the scene receives a + key event. + + \sa setFocusItem(), QGraphicsItem::hasFocus(), isActive() +*/ +QGraphicsItem *QGraphicsScene::focusItem() const +{ + Q_D(const QGraphicsScene); + return isActive() ? d->focusItem : d->passiveFocusItem; +} + +/*! + Sets the scene's focus item to \a item, with the focus reason \a + focusReason, after removing focus from any previous item that may have had + focus. + + If \a item is 0, or if it either does not accept focus (i.e., it does not + have the QGraphicsItem::ItemIsFocusable flag enabled), or is not visible + or not enabled, this function only removes focus from any previous + focusitem. + + If item is not 0, and the scene does not currently have focus (i.e., + hasFocus() returns false), this function will call setFocus() + automatically. + + \sa focusItem(), hasFocus(), setFocus() +*/ +void QGraphicsScene::setFocusItem(QGraphicsItem *item, Qt::FocusReason focusReason) +{ + Q_D(QGraphicsScene); + if (item) + item->setFocus(focusReason); + else + d->setFocusItemHelper(item, focusReason); +} + +/*! + Returns true if the scene has focus; otherwise returns false. If the scene + has focus, it will will forward key events from QKeyEvent to any item that + has focus. + + \sa setFocus(), setFocusItem() +*/ +bool QGraphicsScene::hasFocus() const +{ + Q_D(const QGraphicsScene); + return d->hasFocus; +} + +/*! + Sets focus on the scene by sending a QFocusEvent to the scene, passing \a + focusReason as the reason. If the scene regains focus after having + previously lost it while an item had focus, the last focus item will + receive focus with \a focusReason as the reason. + + If the scene already has focus, this function does nothing. + + \sa hasFocus(), clearFocus(), setFocusItem() +*/ +void QGraphicsScene::setFocus(Qt::FocusReason focusReason) +{ + Q_D(QGraphicsScene); + if (d->hasFocus || !isActive()) + return; + QFocusEvent event(QEvent::FocusIn, focusReason); + QCoreApplication::sendEvent(this, &event); +} + +/*! + Clears focus from the scene. If any item has focus when this function is + called, it will lose focus, and regain focus again once the scene regains + focus. + + A scene that does not have focus ignores key events. + + \sa hasFocus(), setFocus(), setFocusItem() +*/ +void QGraphicsScene::clearFocus() +{ + Q_D(QGraphicsScene); + if (d->hasFocus) { + d->hasFocus = false; + d->passiveFocusItem = d->focusItem; + setFocusItem(0, Qt::OtherFocusReason); + } +} + +/*! + \property QGraphicsScene::stickyFocus + \brief whether clicking into the scene background will clear focus + + \since 4.6 + + In a QGraphicsScene with stickyFocus set to true, focus will remain + unchanged when the user clicks into the scene background or on an item + that does not accept focus. Otherwise, focus will be cleared. + + By default, this property is false. + + Focus changes in response to a mouse press. You can reimplement + mousePressEvent() in a subclass of QGraphicsScene to toggle this property + based on where the user has clicked. + + \sa clearFocus(), setFocusItem() +*/ +void QGraphicsScene::setStickyFocus(bool enabled) +{ + Q_D(QGraphicsScene); + d->stickyFocus = enabled; +} +bool QGraphicsScene::stickyFocus() const +{ + Q_D(const QGraphicsScene); + return d->stickyFocus; +} + +/*! + Returns the current mouse grabber item, or 0 if no item is currently + grabbing the mouse. The mouse grabber item is the item that receives all + mouse events sent to the scene. + + An item becomes a mouse grabber when it receives and accepts a + mouse press event, and it stays the mouse grabber until either of + the following events occur: + + \list + \o If the item receives a mouse release event when there are no other + buttons pressed, it loses the mouse grab. + \o If the item becomes invisible (i.e., someone calls \c {item->setVisible(false)}), + or if it becomes disabled (i.e., someone calls \c {item->setEnabled(false)}), + it loses the mouse grab. + \o If the item is removed from the scene, it loses the mouse grab. + \endlist + + If the item loses its mouse grab, the scene will ignore all mouse events + until a new item grabs the mouse (i.e., until a new item receives a mouse + press event). +*/ +QGraphicsItem *QGraphicsScene::mouseGrabberItem() const +{ + Q_D(const QGraphicsScene); + return !d->mouseGrabberItems.isEmpty() ? d->mouseGrabberItems.last() : 0; +} + +/*! + \property QGraphicsScene::backgroundBrush + \brief the background brush of the scene. + + Set this property to changes the scene's background to a different color, + gradient or texture. The default background brush is Qt::NoBrush. The + background is drawn before (behind) the items. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 3 + + QGraphicsScene::render() calls drawBackground() to draw the scene + background. For more detailed control over how the background is drawn, + you can reimplement drawBackground() in a subclass of QGraphicsScene. +*/ +QBrush QGraphicsScene::backgroundBrush() const +{ + Q_D(const QGraphicsScene); + return d->backgroundBrush; +} +void QGraphicsScene::setBackgroundBrush(const QBrush &brush) +{ + Q_D(QGraphicsScene); + d->backgroundBrush = brush; + foreach (QGraphicsView *view, d->views) { + view->resetCachedContent(); + view->viewport()->update(); + } + update(); +} + +/*! + \property QGraphicsScene::foregroundBrush + \brief the foreground brush of the scene. + + Change this property to set the scene's foreground to a different + color, gradient or texture. + + The foreground is drawn after (on top of) the items. The default + foreground brush is Qt::NoBrush ( i.e. the foreground is not + drawn). + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 4 + + QGraphicsScene::render() calls drawForeground() to draw the scene + foreground. For more detailed control over how the foreground is + drawn, you can reimplement the drawForeground() function in a + QGraphicsScene subclass. +*/ +QBrush QGraphicsScene::foregroundBrush() const +{ + Q_D(const QGraphicsScene); + return d->foregroundBrush; +} +void QGraphicsScene::setForegroundBrush(const QBrush &brush) +{ + Q_D(QGraphicsScene); + d->foregroundBrush = brush; + foreach (QGraphicsView *view, views()) + view->viewport()->update(); + update(); +} + +/*! + This method is used by input methods to query a set of properties of + the scene to be able to support complex input method operations as support + for surrounding text and reconversions. + + The \a query parameter specifies which property is queried. + + \sa QWidget::inputMethodQuery() +*/ +QVariant QGraphicsScene::inputMethodQuery(Qt::InputMethodQuery query) const +{ + Q_D(const QGraphicsScene); + if (!d->focusItem || !(d->focusItem->flags() & QGraphicsItem::ItemAcceptsInputMethod)) + return QVariant(); + const QTransform matrix = d->focusItem->sceneTransform(); + QVariant value = d->focusItem->inputMethodQuery(query); + if (value.type() == QVariant::RectF) + value = matrix.mapRect(value.toRectF()); + else if (value.type() == QVariant::PointF) + value = matrix.map(value.toPointF()); + else if (value.type() == QVariant::Rect) + value = matrix.mapRect(value.toRect()); + else if (value.type() == QVariant::Point) + value = matrix.map(value.toPoint()); + return value; +} + +/*! + \fn void QGraphicsScene::update(const QRectF &rect) + Schedules a redraw of the area \a rect on the scene. + + \sa sceneRect(), changed() +*/ +void QGraphicsScene::update(const QRectF &rect) +{ + Q_D(QGraphicsScene); + if (d->updateAll || (rect.isEmpty() && !rect.isNull())) + return; + + // Check if anyone's connected; if not, we can send updates directly to + // the views. Otherwise or if there are no views, use old behavior. + bool directUpdates = !(d->isSignalConnected(d->changedSignalIndex)) && !d->views.isEmpty(); + if (rect.isNull()) { + d->updateAll = true; + d->updatedRects.clear(); + if (directUpdates) { + // Update all views. + for (int i = 0; i < d->views.size(); ++i) + d->views.at(i)->d_func()->fullUpdatePending = true; + } + } else { + if (directUpdates) { + // Update all views. + for (int i = 0; i < d->views.size(); ++i) { + QGraphicsView *view = d->views.at(i); + if (view->isTransformed()) + view->d_func()->updateRectF(view->viewportTransform().mapRect(rect)); + else + view->d_func()->updateRectF(rect); + } + } else { + d->updatedRects << rect; + } + } + + if (!d->calledEmitUpdated) { + d->calledEmitUpdated = true; + QMetaObject::invokeMethod(this, "_q_emitUpdated", Qt::QueuedConnection); + } +} + +/*! + \fn void QGraphicsScene::update(qreal x, qreal y, qreal w, qreal h) + \overload + \since 4.3 + + This function is equivalent to calling update(QRectF(\a x, \a y, \a w, + \a h)); +*/ + +/*! + Invalidates and schedules a redraw of the \a layers in \a rect on the + scene. Any cached content in \a layers is unconditionally invalidated and + redrawn. + + You can use this function overload to notify QGraphicsScene of changes to + the background or the foreground of the scene. This function is commonly + used for scenes with tile-based backgrounds to notify changes when + QGraphicsView has enabled + \l{QGraphicsView::CacheBackground}{CacheBackground}. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 5 + + Note that QGraphicsView currently supports background caching only (see + QGraphicsView::CacheBackground). This function is equivalent to calling + update() if any layer but BackgroundLayer is passed. + + \sa QGraphicsView::resetCachedContent() +*/ +void QGraphicsScene::invalidate(const QRectF &rect, SceneLayers layers) +{ + foreach (QGraphicsView *view, views()) + view->invalidateScene(rect, layers); + update(rect); +} + +/*! + \fn void QGraphicsScene::invalidate(qreal x, qreal y, qreal w, qreal h, SceneLayers layers) + \overload + \since 4.3 + + This convenience function is equivalent to calling invalidate(QRectF(\a x, \a + y, \a w, \a h), \a layers); +*/ + +/*! + Returns a list of all the views that display this scene. + + \sa QGraphicsView::scene() +*/ +QList QGraphicsScene::views() const +{ + Q_D(const QGraphicsScene); + return d->views; +} + +/*! + This slot \e advances the scene by one step, by calling + QGraphicsItem::advance() for all items on the scene. This is done in two + phases: in the first phase, all items are notified that the scene is about + to change, and in the second phase all items are notified that they can + move. In the first phase, QGraphicsItem::advance() is called passing a + value of 0 as an argument, and 1 is passed in the second phase. + + \sa QGraphicsItem::advance(), QGraphicsItemAnimation, QTimeLine +*/ +void QGraphicsScene::advance() +{ + for (int i = 0; i < 2; ++i) { + foreach (QGraphicsItem *item, items()) + item->advance(i); + } +} + +/*! + Processes the event \a event, and dispatches it to the respective + event handlers. + + In addition to calling the convenience event handlers, this + function is responsible for converting mouse move events to hover + events for when there is no mouse grabber item. Hover events are + delivered directly to items; there is no convenience function for + them. + + Unlike QWidget, QGraphicsScene does not have the convenience functions + \l{QWidget::}{enterEvent()} and \l{QWidget::}{leaveEvent()}. Use this + function to obtain those events instead. + + \sa contextMenuEvent(), keyPressEvent(), keyReleaseEvent(), + mousePressEvent(), mouseMoveEvent(), mouseReleaseEvent(), + mouseDoubleClickEvent(), focusInEvent(), focusOutEvent() +*/ +bool QGraphicsScene::event(QEvent *event) +{ + Q_D(QGraphicsScene); + + switch (event->type()) { + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverLeave: + case QEvent::GraphicsSceneHoverMove: + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + // Reset the under-mouse list to ensure that this event gets fresh + // item-under-mouse data. Be careful about this list; if people delete + // items from inside event handlers, this list can quickly end up + // having stale pointers in it. We need to clear it before dispatching + // events that use it. + // ### this should only be cleared if we received a new mouse move event, + // which relies on us fixing the replay mechanism in QGraphicsView. + d->cachedItemsUnderMouse.clear(); + default: + break; + } + + switch (event->type()) { + case QEvent::GraphicsSceneDragEnter: + dragEnterEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDragMove: + dragMoveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDragLeave: + dragLeaveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDrop: + dropEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneContextMenu: + contextMenuEvent(static_cast(event)); + break; + case QEvent::KeyPress: + if (!d->focusItem) { + QKeyEvent *k = static_cast(event); + if (k->key() == Qt::Key_Tab || k->key() == Qt::Key_Backtab) { + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + bool res = false; + if (k->key() == Qt::Key_Backtab + || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) { + res = focusNextPrevChild(false); + } else if (k->key() == Qt::Key_Tab) { + res = focusNextPrevChild(true); + } + if (!res) + event->ignore(); + return true; + } + } + } + keyPressEvent(static_cast(event)); + break; + case QEvent::KeyRelease: + keyReleaseEvent(static_cast(event)); + break; + case QEvent::ShortcutOverride: { + QGraphicsItem *parent = focusItem(); + while (parent) { + d->sendEvent(parent, event); + if (event->isAccepted()) + return true; + parent = parent->parentItem(); + } + } + return false; + case QEvent::GraphicsSceneMouseMove: + { + QGraphicsSceneMouseEvent *mouseEvent = static_cast(event); + d->lastSceneMousePos = mouseEvent->scenePos(); + mouseMoveEvent(mouseEvent); + break; + } + case QEvent::GraphicsSceneMousePress: + mousePressEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseRelease: + mouseReleaseEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseDoubleClick: + mouseDoubleClickEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneWheel: + wheelEvent(static_cast(event)); + break; + case QEvent::FocusIn: + focusInEvent(static_cast(event)); + break; + case QEvent::FocusOut: + focusOutEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverLeave: + case QEvent::GraphicsSceneHoverMove: + { + QGraphicsSceneHoverEvent *hoverEvent = static_cast(event); + d->lastSceneMousePos = hoverEvent->scenePos(); + d->dispatchHoverEvent(hoverEvent); + break; + } + case QEvent::Leave: + // hackieshly unpacking the viewport pointer from the leave event. + d->leaveScene(reinterpret_cast(event->d)); + break; + case QEvent::GraphicsSceneHelp: + helpEvent(static_cast(event)); + break; + case QEvent::InputMethod: + inputMethodEvent(static_cast(event)); + break; + case QEvent::WindowActivate: + if (!d->activationRefCount++) { + if (d->lastActivePanel) { + // Activate the last panel. + d->setActivePanelHelper(d->lastActivePanel, true); + } else if (d->tabFocusFirst && d->tabFocusFirst->isPanel()) { + // Activate the panel of the first item in the tab focus + // chain. + d->setActivePanelHelper(d->tabFocusFirst, true); + } else { + // Activate all toplevel items. + QEvent event(QEvent::WindowActivate); + foreach (QGraphicsItem *item, items()) { + if (item->isVisible() && !item->isPanel() && !item->parentItem()) + sendEvent(item, &event); + } + } + } + break; + case QEvent::WindowDeactivate: + if (!--d->activationRefCount) { + if (d->activePanel) { + // Deactivate the active panel (but keep it so we can + // reactivate it later). + QGraphicsItem *lastActivePanel = d->activePanel; + d->setActivePanelHelper(0, true); + d->lastActivePanel = lastActivePanel; + } else { + // Activate all toplevel items. + QEvent event(QEvent::WindowDeactivate); + foreach (QGraphicsItem *item, items()) { + if (item->isVisible() && !item->isPanel() && !item->parentItem()) + sendEvent(item, &event); + } + } + } + break; + case QEvent::ApplicationFontChange: { + // Resolve the existing scene font. + d->resolveFont(); + break; + } + case QEvent::FontChange: + // Update the entire scene when the font changes. + update(); + break; + case QEvent::ApplicationPaletteChange: { + // Resolve the existing scene palette. + d->resolvePalette(); + break; + } + case QEvent::PaletteChange: + // Update the entire scene when the palette changes. + update(); + break; + case QEvent::StyleChange: + // Reresolve all widgets' styles. Update all top-level widgets' + // geometries that do not have an explicit style set. + update(); + break; + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + d->touchEventHandler(static_cast(event)); + break; +#ifndef QT_NO_GESTURES + case QEvent::Gesture: + case QEvent::GestureOverride: + d->gestureEventHandler(static_cast(event)); + break; +#endif // QT_NO_GESTURES + default: + return QObject::event(event); + } + return true; +} + +/*! + \reimp + + QGraphicsScene filters QApplication's events to detect palette and font + changes. +*/ +bool QGraphicsScene::eventFilter(QObject *watched, QEvent *event) +{ + if (watched != qApp) + return false; + + switch (event->type()) { + case QEvent::ApplicationPaletteChange: + QApplication::postEvent(this, new QEvent(QEvent::ApplicationPaletteChange)); + break; + case QEvent::ApplicationFontChange: + QApplication::postEvent(this, new QEvent(QEvent::ApplicationFontChange)); + break; + default: + break; + } + return false; +} + +/*! + This event handler, for event \a contextMenuEvent, can be reimplemented in + a subclass to receive context menu events. The default implementation + forwards the event to the topmost item that accepts context menu events at + the position of the event. If no items accept context menu events at this + position, the event is ignored. + + \sa QGraphicsItem::contextMenuEvent() +*/ +void QGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *contextMenuEvent) +{ + Q_D(QGraphicsScene); + // Ignore by default. + contextMenuEvent->ignore(); + + // Send the event to all items at this position until one item accepts the + // event. + foreach (QGraphicsItem *item, d->itemsAtPosition(contextMenuEvent->screenPos(), + contextMenuEvent->scenePos(), + contextMenuEvent->widget())) { + contextMenuEvent->setPos(item->d_ptr->genericMapFromScene(contextMenuEvent->scenePos(), + contextMenuEvent->widget())); + contextMenuEvent->accept(); + if (!d->sendEvent(item, contextMenuEvent)) + break; + + if (contextMenuEvent->isAccepted()) + break; + } +} + +/*! + This event handler, for event \a event, can be reimplemented in a subclass + to receive drag enter events for the scene. + + The default implementation accepts the event and prepares the scene to + accept drag move events. + + \sa QGraphicsItem::dragEnterEvent(), dragMoveEvent(), dragLeaveEvent(), + dropEvent() +*/ +void QGraphicsScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsScene); + d->dragDropItem = 0; + d->lastDropAction = Qt::IgnoreAction; + event->accept(); +} + +/*! + This event handler, for event \a event, can be reimplemented in a subclass + to receive drag move events for the scene. + + \sa QGraphicsItem::dragMoveEvent(), dragEnterEvent(), dragLeaveEvent(), + dropEvent() +*/ +void QGraphicsScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsScene); + event->ignore(); + + if (!d->mouseGrabberItems.isEmpty()) { + // Mouse grabbers that start drag events lose the mouse grab. + d->clearMouseGrabber(); + d->mouseGrabberButtonDownPos.clear(); + d->mouseGrabberButtonDownScenePos.clear(); + d->mouseGrabberButtonDownScreenPos.clear(); + } + + bool eventDelivered = false; + + // Find the topmost enabled items under the cursor. They are all + // candidates for accepting drag & drop events. + foreach (QGraphicsItem *item, d->itemsAtPosition(event->screenPos(), + event->scenePos(), + event->widget())) { + if (!item->isEnabled() || !item->acceptDrops()) + continue; + + if (item != d->dragDropItem) { + // Enter the new drag drop item. If it accepts the event, we send + // the leave to the parent item. + QGraphicsSceneDragDropEvent dragEnter(QEvent::GraphicsSceneDragEnter); + d->cloneDragDropEvent(&dragEnter, event); + dragEnter.setDropAction(event->proposedAction()); + d->sendDragDropEvent(item, &dragEnter); + event->setAccepted(dragEnter.isAccepted()); + event->setDropAction(dragEnter.dropAction()); + if (!event->isAccepted()) { + // Propagate to the item under + continue; + } + + d->lastDropAction = event->dropAction(); + + if (d->dragDropItem) { + // Leave the last drag drop item. A perfect implementation + // would set the position of this event to the point where + // this event and the last event intersect with the item's + // shape, but that's not easy to do. :-) + QGraphicsSceneDragDropEvent dragLeave(QEvent::GraphicsSceneDragLeave); + d->cloneDragDropEvent(&dragLeave, event); + d->sendDragDropEvent(d->dragDropItem, &dragLeave); + } + + // We've got a new drag & drop item + d->dragDropItem = item; + } + + // Send the move event. + event->setDropAction(d->lastDropAction); + event->accept(); + d->sendDragDropEvent(item, event); + if (event->isAccepted()) + d->lastDropAction = event->dropAction(); + eventDelivered = true; + break; + } + + if (!eventDelivered) { + if (d->dragDropItem) { + // Leave the last drag drop item + QGraphicsSceneDragDropEvent dragLeave(QEvent::GraphicsSceneDragLeave); + d->cloneDragDropEvent(&dragLeave, event); + d->sendDragDropEvent(d->dragDropItem, &dragLeave); + d->dragDropItem = 0; + } + // Propagate + event->setDropAction(Qt::IgnoreAction); + } +} + +/*! + This event handler, for event \a event, can be reimplemented in a subclass + to receive drag leave events for the scene. + + \sa QGraphicsItem::dragLeaveEvent(), dragEnterEvent(), dragMoveEvent(), + dropEvent() +*/ +void QGraphicsScene::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsScene); + if (d->dragDropItem) { + // Leave the last drag drop item + d->sendDragDropEvent(d->dragDropItem, event); + d->dragDropItem = 0; + } +} + +/*! + This event handler, for event \a event, can be reimplemented in a subclass + to receive drop events for the scene. + + \sa QGraphicsItem::dropEvent(), dragEnterEvent(), dragMoveEvent(), + dragLeaveEvent() +*/ +void QGraphicsScene::dropEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_UNUSED(event); + Q_D(QGraphicsScene); + if (d->dragDropItem) { + // Drop on the last drag drop item + d->sendDragDropEvent(d->dragDropItem, event); + d->dragDropItem = 0; + } +} + +/*! + This event handler, for event \a focusEvent, can be reimplemented in a + subclass to receive focus in events. + + The default implementation sets focus on the scene, and then on the last + focus item. + + \sa QGraphicsItem::focusOutEvent() +*/ +void QGraphicsScene::focusInEvent(QFocusEvent *focusEvent) +{ + Q_D(QGraphicsScene); + + d->hasFocus = true; + switch (focusEvent->reason()) { + case Qt::TabFocusReason: + if (!focusNextPrevChild(true)) + focusEvent->ignore(); + break; + case Qt::BacktabFocusReason: + if (!focusNextPrevChild(false)) + focusEvent->ignore(); + break; + default: + if (d->passiveFocusItem) { + // Set focus on the last focus item + setFocusItem(d->passiveFocusItem, focusEvent->reason()); + } + break; + } +} + +/*! + This event handler, for event \a focusEvent, can be reimplemented in a + subclass to receive focus out events. + + The default implementation removes focus from any focus item, then removes + focus from the scene. + + \sa QGraphicsItem::focusInEvent() +*/ +void QGraphicsScene::focusOutEvent(QFocusEvent *focusEvent) +{ + Q_D(QGraphicsScene); + d->hasFocus = false; + d->passiveFocusItem = d->focusItem; + setFocusItem(0, focusEvent->reason()); + + // Remove all popups when the scene loses focus. + if (!d->popupWidgets.isEmpty()) + d->removePopup(d->popupWidgets.first()); +} + +/*! + This event handler, for event \a helpEvent, can be + reimplemented in a subclass to receive help events. The events + are of type QEvent::ToolTip, which are created when a tooltip is + requested. + + The default implementation shows the tooltip of the topmost + item, i.e., the item with the highest z-value, at the mouse + cursor position. If no item has a tooltip set, this function + does nothing. + + \sa QGraphicsItem::toolTip(), QGraphicsSceneHelpEvent +*/ +void QGraphicsScene::helpEvent(QGraphicsSceneHelpEvent *helpEvent) +{ +#ifdef QT_NO_TOOLTIP + Q_UNUSED(helpEvent); +#else + // Find the first item that does tooltips + Q_D(QGraphicsScene); + QList itemsAtPos = d->itemsAtPosition(helpEvent->screenPos(), + helpEvent->scenePos(), + helpEvent->widget()); + QGraphicsItem *toolTipItem = 0; + for (int i = 0; i < itemsAtPos.size(); ++i) { + QGraphicsItem *tmp = itemsAtPos.at(i); + if (tmp->d_func()->isProxyWidget()) { + // if the item is a proxy widget, the event is forwarded to it + sendEvent(tmp, helpEvent); + if (helpEvent->isAccepted()) + return; + } + if (!tmp->toolTip().isEmpty()) { + toolTipItem = tmp; + break; + } + } + + // Show or hide the tooltip + QString text; + QPoint point; + if (toolTipItem && !toolTipItem->toolTip().isEmpty()) { + text = toolTipItem->toolTip(); + point = helpEvent->screenPos(); + } + QToolTip::showText(point, text, helpEvent->widget()); + helpEvent->setAccepted(!text.isEmpty()); +#endif +} + +bool QGraphicsScenePrivate::itemAcceptsHoverEvents_helper(const QGraphicsItem *item) const +{ + return (item->d_ptr->acceptsHover + || (item->d_ptr->isWidget + && static_cast(item)->d_func()->hasDecoration())) + && !item->isBlockedByModalPanel(); +} + +/*! + This event handler, for event \a hoverEvent, can be reimplemented in a + subclass to receive hover enter events. The default implementation + forwards the event to the topmost item that accepts hover events at the + scene position from the event. + + \sa QGraphicsItem::hoverEvent(), QGraphicsItem::setAcceptHoverEvents() +*/ +bool QGraphicsScenePrivate::dispatchHoverEvent(QGraphicsSceneHoverEvent *hoverEvent) +{ + if (allItemsIgnoreHoverEvents) + return false; + + // Find the first item that accepts hover events, reusing earlier + // calculated data is possible. + if (cachedItemsUnderMouse.isEmpty()) { + cachedItemsUnderMouse = itemsAtPosition(hoverEvent->screenPos(), + hoverEvent->scenePos(), + hoverEvent->widget()); + } + + QGraphicsItem *item = 0; + for (int i = 0; i < cachedItemsUnderMouse.size(); ++i) { + QGraphicsItem *tmp = cachedItemsUnderMouse.at(i); + if (itemAcceptsHoverEvents_helper(tmp)) { + item = tmp; + break; + } + } + + // Find the common ancestor item for the new topmost hoverItem and the + // last item in the hoverItem list. + QGraphicsItem *commonAncestorItem = (item && !hoverItems.isEmpty()) ? item->commonAncestorItem(hoverItems.last()) : 0; + while (commonAncestorItem && !itemAcceptsHoverEvents_helper(commonAncestorItem)) + commonAncestorItem = commonAncestorItem->parentItem(); + if (commonAncestorItem && commonAncestorItem->panel() != item->panel()) { + // The common ancestor isn't in the same panel as the two hovered + // items. + commonAncestorItem = 0; + } + + // Check if the common ancestor item is known. + int index = commonAncestorItem ? hoverItems.indexOf(commonAncestorItem) : -1; + // Send hover leaves to any existing hovered children of the common + // ancestor item. + for (int i = hoverItems.size() - 1; i > index; --i) { + QGraphicsItem *lastItem = hoverItems.takeLast(); + if (itemAcceptsHoverEvents_helper(lastItem)) + sendHoverEvent(QEvent::GraphicsSceneHoverLeave, lastItem, hoverEvent); + } + + // Item is a child of a known item. Generate enter events for the + // missing links. + QList parents; + QGraphicsItem *parent = item; + while (parent && parent != commonAncestorItem) { + parents.prepend(parent); + if (parent->isPanel()) { + // Stop at the panel - we don't deliver beyond this point. + break; + } + parent = parent->parentItem(); + } + for (int i = 0; i < parents.size(); ++i) { + parent = parents.at(i); + hoverItems << parent; + if (itemAcceptsHoverEvents_helper(parent)) + sendHoverEvent(QEvent::GraphicsSceneHoverEnter, parent, hoverEvent); + } + + // Generate a move event for the item itself + if (item + && !hoverItems.isEmpty() + && item == hoverItems.last()) { + sendHoverEvent(QEvent::GraphicsSceneHoverMove, item, hoverEvent); + return true; + } + return false; +} + +/*! + \internal + + Handles all actions necessary to clean up the scene when the mouse leaves + the view. +*/ +void QGraphicsScenePrivate::leaveScene(QWidget *viewport) +{ +#ifndef QT_NO_TOOLTIP + QToolTip::hideText(); +#endif + QGraphicsView *view = qobject_cast(viewport->parent()); + // Send HoverLeave events to all existing hover items, topmost first. + QGraphicsSceneHoverEvent hoverEvent; + hoverEvent.setWidget(viewport); + + if (view) { + QPoint cursorPos = QCursor::pos(); + hoverEvent.setScenePos(view->mapToScene(viewport->mapFromGlobal(cursorPos))); + hoverEvent.setLastScenePos(hoverEvent.scenePos()); + hoverEvent.setScreenPos(cursorPos); + hoverEvent.setLastScreenPos(hoverEvent.screenPos()); + } + + while (!hoverItems.isEmpty()) { + QGraphicsItem *lastItem = hoverItems.takeLast(); + if (itemAcceptsHoverEvents_helper(lastItem)) + sendHoverEvent(QEvent::GraphicsSceneHoverLeave, lastItem, &hoverEvent); + } +} + +/*! + This event handler, for event \a keyEvent, can be reimplemented in a + subclass to receive keypress events. The default implementation forwards + the event to current focus item. + + \sa QGraphicsItem::keyPressEvent(), focusItem() +*/ +void QGraphicsScene::keyPressEvent(QKeyEvent *keyEvent) +{ + // ### Merge this function with keyReleaseEvent; they are identical + // ### (except this comment). + Q_D(QGraphicsScene); + QGraphicsItem *item = !d->keyboardGrabberItems.isEmpty() ? d->keyboardGrabberItems.last() : 0; + if (!item) + item = focusItem(); + if (item) { + QGraphicsItem *p = item; + do { + // Accept the event by default + keyEvent->accept(); + // Send it; QGraphicsItem::keyPressEvent ignores it. If the event + // is filtered out, stop propagating it. + if (p->isBlockedByModalPanel()) + break; + if (!d->sendEvent(p, keyEvent)) + break; + } while (!keyEvent->isAccepted() && !p->isPanel() && (p = p->parentItem())); + } else { + keyEvent->ignore(); + } +} + +/*! + This event handler, for event \a keyEvent, can be reimplemented in a + subclass to receive key release events. The default implementation + forwards the event to current focus item. + + \sa QGraphicsItem::keyReleaseEvent(), focusItem() +*/ +void QGraphicsScene::keyReleaseEvent(QKeyEvent *keyEvent) +{ + // ### Merge this function with keyPressEvent; they are identical (except + // ### this comment). + Q_D(QGraphicsScene); + QGraphicsItem *item = !d->keyboardGrabberItems.isEmpty() ? d->keyboardGrabberItems.last() : 0; + if (!item) + item = focusItem(); + if (item) { + QGraphicsItem *p = item; + do { + // Accept the event by default + keyEvent->accept(); + // Send it; QGraphicsItem::keyPressEvent ignores it. If the event + // is filtered out, stop propagating it. + if (p->isBlockedByModalPanel()) + break; + if (!d->sendEvent(p, keyEvent)) + break; + } while (!keyEvent->isAccepted() && !p->isPanel() && (p = p->parentItem())); + } else { + keyEvent->ignore(); + } +} + +/*! + This event handler, for event \a mouseEvent, can be reimplemented + in a subclass to receive mouse press events for the scene. + + The default implementation depends on the state of the scene. If + there is a mouse grabber item, then the event is sent to the mouse + grabber. Otherwise, it is forwarded to the topmost item that + accepts mouse events at the scene position from the event, and + that item promptly becomes the mouse grabber item. + + If there is no item at the given position on the scene, the + selection area is reset, any focus item loses its input focus, and + the event is then ignored. + + \sa QGraphicsItem::mousePressEvent(), + QGraphicsItem::setAcceptedMouseButtons() +*/ +void QGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) +{ + Q_D(QGraphicsScene); + if (d->mouseGrabberItems.isEmpty()) { + // Dispatch hover events + QGraphicsSceneHoverEvent hover; + _q_hoverFromMouseEvent(&hover, mouseEvent); + d->dispatchHoverEvent(&hover); + } + + d->mousePressEventHandler(mouseEvent); +} + +/*! + This event handler, for event \a mouseEvent, can be reimplemented + in a subclass to receive mouse move events for the scene. + + The default implementation depends on the mouse grabber state. If there is + a mouse grabber item, the event is sent to the mouse grabber. If there + are any items that accept hover events at the current position, the event + is translated into a hover event and accepted; otherwise it's ignored. + + \sa QGraphicsItem::mousePressEvent(), QGraphicsItem::mouseReleaseEvent(), + QGraphicsItem::mouseDoubleClickEvent(), QGraphicsItem::setAcceptedMouseButtons() +*/ +void QGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) +{ + Q_D(QGraphicsScene); + if (d->mouseGrabberItems.isEmpty()) { + if (mouseEvent->buttons()) + return; + QGraphicsSceneHoverEvent hover; + _q_hoverFromMouseEvent(&hover, mouseEvent); + mouseEvent->setAccepted(d->dispatchHoverEvent(&hover)); + return; + } + + // Forward the event to the mouse grabber + d->sendMouseEvent(mouseEvent); + mouseEvent->accept(); +} + +/*! + This event handler, for event \a mouseEvent, can be reimplemented + in a subclass to receive mouse release events for the scene. + + The default implementation depends on the mouse grabber state. If + there is no mouse grabber, the event is ignored. Otherwise, if + there is a mouse grabber item, the event is sent to the mouse + grabber. If this mouse release represents the last pressed button + on the mouse, the mouse grabber item then loses the mouse grab. + + \sa QGraphicsItem::mousePressEvent(), QGraphicsItem::mouseMoveEvent(), + QGraphicsItem::mouseDoubleClickEvent(), QGraphicsItem::setAcceptedMouseButtons() +*/ +void QGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) +{ + Q_D(QGraphicsScene); + if (d->mouseGrabberItems.isEmpty()) { + mouseEvent->ignore(); + return; + } + + // Forward the event to the mouse grabber + d->sendMouseEvent(mouseEvent); + mouseEvent->accept(); + + // Reset the mouse grabber when the last mouse button has been released. + if (!mouseEvent->buttons()) { + if (!d->mouseGrabberItems.isEmpty()) { + d->lastMouseGrabberItem = d->mouseGrabberItems.last(); + if (d->lastMouseGrabberItemHasImplicitMouseGrab) + d->mouseGrabberItems.last()->ungrabMouse(); + } else { + d->lastMouseGrabberItem = 0; + } + + // Generate a hoverevent + QGraphicsSceneHoverEvent hoverEvent; + _q_hoverFromMouseEvent(&hoverEvent, mouseEvent); + d->dispatchHoverEvent(&hoverEvent); + } +} + +/*! + This event handler, for event \a mouseEvent, can be reimplemented + in a subclass to receive mouse doubleclick events for the scene. + + If someone doubleclicks on the scene, the scene will first receive + a mouse press event, followed by a release event (i.e., a click), + then a doubleclick event, and finally a release event. If the + doubleclick event is delivered to a different item than the one + that received the first press and release, it will be delivered as + a press event. However, tripleclick events are not delivered as + doubleclick events in this case. + + The default implementation is similar to mousePressEvent(). + + \sa QGraphicsItem::mousePressEvent(), QGraphicsItem::mouseMoveEvent(), + QGraphicsItem::mouseReleaseEvent(), QGraphicsItem::setAcceptedMouseButtons() +*/ +void QGraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvent) +{ + Q_D(QGraphicsScene); + d->mousePressEventHandler(mouseEvent); +} + +/*! + This event handler, for event \a wheelEvent, can be reimplemented in a + subclass to receive mouse wheel events for the scene. + + By default, the event is delivered to the topmost visible item under the + cursor. If ignored, the event propagates to the item beneath, and again + until the event is accepted, or it reaches the scene. If no items accept + the event, it is ignored. + + \sa QGraphicsItem::wheelEvent() +*/ +void QGraphicsScene::wheelEvent(QGraphicsSceneWheelEvent *wheelEvent) +{ + Q_D(QGraphicsScene); + QList wheelCandidates = d->itemsAtPosition(wheelEvent->screenPos(), + wheelEvent->scenePos(), + wheelEvent->widget()); + +#ifdef Q_WS_MAC + // On Mac, ignore the event if the first item under the mouse is not the last opened + // popup (or one of its descendant) + if (!d->popupWidgets.isEmpty() && !wheelCandidates.isEmpty() && wheelCandidates.first() != d->popupWidgets.back() && !d->popupWidgets.back()->isAncestorOf(wheelCandidates.first())) { + wheelEvent->accept(); + return; + } +#else + // Find the first popup under the mouse (including the popup's descendants) starting from the last. + // Remove all popups after the one found, or all or them if no popup is under the mouse. + // Then continue with the event. + QList::const_iterator iter = d->popupWidgets.end(); + while (--iter >= d->popupWidgets.begin() && !wheelCandidates.isEmpty()) { + if (wheelCandidates.first() == *iter || (*iter)->isAncestorOf(wheelCandidates.first())) + break; + d->removePopup(*iter); + } +#endif + + bool hasSetFocus = false; + foreach (QGraphicsItem *item, wheelCandidates) { + if (!hasSetFocus && item->isEnabled() + && ((item->flags() & QGraphicsItem::ItemIsFocusable) && item->d_ptr->mouseSetsFocus)) { + if (item->isWidget() && static_cast(item)->focusPolicy() == Qt::WheelFocus) { + hasSetFocus = true; + if (item != focusItem()) + setFocusItem(item, Qt::MouseFocusReason); + } + } + + wheelEvent->setPos(item->d_ptr->genericMapFromScene(wheelEvent->scenePos(), + wheelEvent->widget())); + wheelEvent->accept(); + bool isPanel = item->isPanel(); + d->sendEvent(item, wheelEvent); + if (isPanel || wheelEvent->isAccepted()) + break; + } +} + +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive input method events for the scene. + + The default implementation forwards the event to the focusItem(). + If no item currently has focus or the current focus item does not + accept input methods, this function does nothing. + + \sa QGraphicsItem::inputMethodEvent() +*/ +void QGraphicsScene::inputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QGraphicsScene); + if (d->focusItem && (d->focusItem->flags() & QGraphicsItem::ItemAcceptsInputMethod)) + d->sendEvent(d->focusItem, event); +} + +/*! + Draws the background of the scene using \a painter, before any items and + the foreground are drawn. Reimplement this function to provide a custom + background for the scene. + + All painting is done in \e scene coordinates. The \a rect + parameter is the exposed rectangle. + + If all you want is to define a color, texture, or gradient for the + background, you can call setBackgroundBrush() instead. + + \sa drawForeground(), drawItems() +*/ +void QGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect) +{ + Q_D(QGraphicsScene); + + if (d->backgroundBrush.style() != Qt::NoBrush) { + if (d->painterStateProtection) + painter->save(); + painter->setBrushOrigin(0, 0); + painter->fillRect(rect, backgroundBrush()); + if (d->painterStateProtection) + painter->restore(); + } +} + +/*! + Draws the foreground of the scene using \a painter, after the background + and all items have been drawn. Reimplement this function to provide a + custom foreground for the scene. + + All painting is done in \e scene coordinates. The \a rect + parameter is the exposed rectangle. + + If all you want is to define a color, texture or gradient for the + foreground, you can call setForegroundBrush() instead. + + \sa drawBackground(), drawItems() +*/ +void QGraphicsScene::drawForeground(QPainter *painter, const QRectF &rect) +{ + Q_D(QGraphicsScene); + + if (d->foregroundBrush.style() != Qt::NoBrush) { + if (d->painterStateProtection) + painter->save(); + painter->setBrushOrigin(0, 0); + painter->fillRect(rect, foregroundBrush()); + if (d->painterStateProtection) + painter->restore(); + } +} + +static void _q_paintItem(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget, + bool useWindowOpacity, bool painterStateProtection) +{ + if (!item->isWidget()) { + item->paint(painter, option, widget); + return; + } + QGraphicsWidget *widgetItem = static_cast(item); + QGraphicsProxyWidget *proxy = qobject_cast(widgetItem); + const qreal windowOpacity = (proxy && proxy->widget() && useWindowOpacity) + ? proxy->widget()->windowOpacity() : 1.0; + const qreal oldPainterOpacity = painter->opacity(); + + if (qFuzzyIsNull(windowOpacity)) + return; + // Set new painter opacity. + if (windowOpacity < 1.0) + painter->setOpacity(oldPainterOpacity * windowOpacity); + + // set layoutdirection on the painter + Qt::LayoutDirection oldLayoutDirection = painter->layoutDirection(); + painter->setLayoutDirection(widgetItem->layoutDirection()); + + if (widgetItem->isWindow() && widgetItem->windowType() != Qt::Popup && widgetItem->windowType() != Qt::ToolTip + && !(widgetItem->windowFlags() & Qt::FramelessWindowHint)) { + if (painterStateProtection) + painter->save(); + widgetItem->paintWindowFrame(painter, option, widget); + if (painterStateProtection) + painter->restore(); + } else if (widgetItem->autoFillBackground()) { + painter->fillRect(option->exposedRect, widgetItem->palette().window()); + } + + widgetItem->paint(painter, option, widget); + + // Restore layoutdirection on the painter. + painter->setLayoutDirection(oldLayoutDirection); + // Restore painter opacity. + if (windowOpacity < 1.0) + painter->setOpacity(oldPainterOpacity); +} + +static void _q_paintIntoCache(QPixmap *pix, QGraphicsItem *item, const QRegion &pixmapExposed, + const QTransform &itemToPixmap, QPainter::RenderHints renderHints, + const QStyleOptionGraphicsItem *option, bool painterStateProtection) +{ + QPixmap subPix; + QPainter pixmapPainter; + QRect br = pixmapExposed.boundingRect(); + + // Don't use subpixmap if we get a full update. + if (pixmapExposed.isEmpty() || (pixmapExposed.rectCount() == 1 && br.contains(pix->rect()))) { + pix->fill(Qt::transparent); + pixmapPainter.begin(pix); + } else { + subPix = QPixmap(br.size()); + subPix.fill(Qt::transparent); + pixmapPainter.begin(&subPix); + pixmapPainter.translate(-br.topLeft()); + if (!pixmapExposed.isEmpty()) { + // Applied to subPix; paint is adjusted to the coordinate space is + // correct. + pixmapPainter.setClipRegion(pixmapExposed); + } + } + + pixmapPainter.setRenderHints(pixmapPainter.renderHints(), false); + pixmapPainter.setRenderHints(renderHints, true); + pixmapPainter.setWorldTransform(itemToPixmap, true); + + // Render. + _q_paintItem(item, &pixmapPainter, option, 0, false, painterStateProtection); + pixmapPainter.end(); + + if (!subPix.isNull()) { + // Blit the subpixmap into the main pixmap. + pixmapPainter.begin(pix); + pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source); + pixmapPainter.setClipRegion(pixmapExposed); + pixmapPainter.drawPixmap(br.topLeft(), subPix); + pixmapPainter.end(); + } +} + +// Copied from qpaintengine_vg.cpp +// Returns true for 90, 180, and 270 degree rotations. +static inline bool transformIsSimple(const QTransform& transform) +{ + QTransform::TransformationType type = transform.type(); + if (type <= QTransform::TxScale) { + return true; + } else if (type == QTransform::TxRotate) { + // Check for 90, and 270 degree rotations. + qreal m11 = transform.m11(); + qreal m12 = transform.m12(); + qreal m21 = transform.m21(); + qreal m22 = transform.m22(); + if (m11 == 0.0f && m22 == 0.0f) { + if (m12 == 1.0f && m21 == -1.0f) + return true; // 90 degrees. + else if (m12 == -1.0f && m21 == 1.0f) + return true; // 270 degrees. + else if (m12 == -1.0f && m21 == -1.0f) + return true; // 90 degrees inverted y. + else if (m12 == 1.0f && m21 == 1.0f) + return true; // 270 degrees inverted y. + } + } + return false; +} + +/*! + \internal + + Draws items directly, or using cache. +*/ +void QGraphicsScenePrivate::drawItemHelper(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget, + bool painterStateProtection) +{ + QGraphicsItemPrivate *itemd = item->d_ptr.data(); + QGraphicsItem::CacheMode cacheMode = QGraphicsItem::CacheMode(itemd->cacheMode); + + // Render directly, using no cache. + if (cacheMode == QGraphicsItem::NoCache +#ifdef Q_WS_X11 + || !X11->use_xrender +#endif + ) { + _q_paintItem(static_cast(item), painter, option, widget, true, painterStateProtection); + return; + } + + const qreal oldPainterOpacity = painter->opacity(); + qreal newPainterOpacity = oldPainterOpacity; + QGraphicsProxyWidget *proxy = item->isWidget() ? qobject_cast(static_cast(item)) : 0; + if (proxy && proxy->widget()) { + const qreal windowOpacity = proxy->widget()->windowOpacity(); + if (windowOpacity < 1.0) + newPainterOpacity *= windowOpacity; + } + + // Item's (local) bounding rect + QRectF brect = item->boundingRect(); + QRectF adjustedBrect(brect); + _q_adjustRect(&adjustedBrect); + if (adjustedBrect.isEmpty()) + return; + + // Fetch the off-screen transparent buffer and exposed area info. + QPixmapCache::Key pixmapKey; + QPixmap pix; + bool pixmapFound; + QGraphicsItemCache *itemCache = itemd->extraItemCache(); + if (cacheMode == QGraphicsItem::ItemCoordinateCache) { + pixmapKey = itemCache->key; + } else { + pixmapKey = itemCache->deviceData.value(widget).key; + } + + // Find pixmap in cache. + pixmapFound = QPixmapCache::find(pixmapKey, &pix); + + // Render using item coordinate cache mode. + if (cacheMode == QGraphicsItem::ItemCoordinateCache) { + QSize pixmapSize; + bool fixedCacheSize = false; + QRect br = brect.toAlignedRect(); + if ((fixedCacheSize = itemCache->fixedSize.isValid())) { + pixmapSize = itemCache->fixedSize; + } else { + pixmapSize = br.size(); + } + + // Create or recreate the pixmap. + int adjust = itemCache->fixedSize.isValid() ? 0 : 2; + QSize adjustSize(adjust*2, adjust*2); + br.adjust(-adjust, -adjust, adjust, adjust); + if (pix.isNull() || (!fixedCacheSize && (pixmapSize + adjustSize) != pix.size())) { + pix = QPixmap(pixmapSize + adjustSize); + itemCache->boundingRect = br; + itemCache->exposed.clear(); + itemCache->allExposed = true; + } else if (itemCache->boundingRect != br) { + itemCache->boundingRect = br; + itemCache->exposed.clear(); + itemCache->allExposed = true; + } + + // Redraw any newly exposed areas. + if (itemCache->allExposed || !itemCache->exposed.isEmpty()) { + + //We know that we will modify the pixmap, removing it from the cache + //will detach the one we have and avoid a deep copy + if (pixmapFound) + QPixmapCache::remove(pixmapKey); + + // Fit the item's bounding rect into the pixmap's coordinates. + QTransform itemToPixmap; + if (fixedCacheSize) { + const QPointF scale(pixmapSize.width() / brect.width(), pixmapSize.height() / brect.height()); + itemToPixmap.scale(scale.x(), scale.y()); + } + itemToPixmap.translate(-br.x(), -br.y()); + + // Generate the item's exposedRect and map its list of expose + // rects to device coordinates. + styleOptionTmp = *option; + QRegion pixmapExposed; + QRectF exposedRect; + if (!itemCache->allExposed) { + for (int i = 0; i < itemCache->exposed.size(); ++i) { + QRectF r = itemCache->exposed.at(i); + exposedRect |= r; + pixmapExposed += itemToPixmap.mapRect(r).toAlignedRect(); + } + } else { + exposedRect = brect; + } + styleOptionTmp.exposedRect = exposedRect; + + // Render. + _q_paintIntoCache(&pix, item, pixmapExposed, itemToPixmap, painter->renderHints(), + &styleOptionTmp, painterStateProtection); + + // insert this pixmap into the cache. + itemCache->key = QPixmapCache::insert(pix); + + // Reset expose data. + itemCache->allExposed = false; + itemCache->exposed.clear(); + } + + // Redraw the exposed area using the transformed painter. Depending on + // the hardware, this may be a server-side operation, or an expensive + // qpixmap-image-transform-pixmap roundtrip. + if (newPainterOpacity != oldPainterOpacity) { + painter->setOpacity(newPainterOpacity); + painter->drawPixmap(br.topLeft(), pix); + painter->setOpacity(oldPainterOpacity); + } else { + painter->drawPixmap(br.topLeft(), pix); + } + return; + } + + // Render using device coordinate cache mode. + if (cacheMode == QGraphicsItem::DeviceCoordinateCache) { + // Find the item's bounds in device coordinates. + QRectF deviceBounds = painter->worldTransform().mapRect(brect); + QRect deviceRect = deviceBounds.toRect().adjusted(-1, -1, 1, 1); + if (deviceRect.isEmpty()) + return; + QRect viewRect = widget ? widget->rect() : QRect(); + if (widget && !viewRect.intersects(deviceRect)) + return; + + // Resort to direct rendering if the device rect exceeds the + // (optional) maximum bounds. (QGraphicsSvgItem uses this). + QSize maximumCacheSize = + itemd->extra(QGraphicsItemPrivate::ExtraMaxDeviceCoordCacheSize).toSize(); + if (!maximumCacheSize.isEmpty() + && (deviceRect.width() > maximumCacheSize.width() + || deviceRect.height() > maximumCacheSize.height())) { + _q_paintItem(static_cast(item), painter, option, widget, + oldPainterOpacity != newPainterOpacity, painterStateProtection); + return; + } + + // Create or reuse offscreen pixmap, possibly scroll/blit from the old one. + // If the world transform is rotated we always recreate the cache to avoid + // wrong blending. + bool pixModified = false; + QGraphicsItemCache::DeviceData *deviceData = &itemCache->deviceData[widget]; + bool invertable = true; + QTransform diff = deviceData->lastTransform.inverted(&invertable); + if (invertable) + diff *= painter->worldTransform(); + deviceData->lastTransform = painter->worldTransform(); + bool allowPartialCacheExposure = false; + bool simpleTransform = invertable && diff.type() <= QTransform::TxTranslate + && transformIsSimple(painter->worldTransform()); + if (!simpleTransform) { + pixModified = true; + itemCache->allExposed = true; + itemCache->exposed.clear(); + deviceData->cacheIndent = QPoint(); + pix = QPixmap(); + } else if (!viewRect.isNull()) { + allowPartialCacheExposure = deviceData->cacheIndent != QPoint(); + } + + // Allow partial cache exposure if the device rect isn't fully contained and + // deviceRect is 20% taller or wider than the viewRect. + if (!allowPartialCacheExposure && !viewRect.isNull() && !viewRect.contains(deviceRect)) { + allowPartialCacheExposure = (viewRect.width() * 1.2 < deviceRect.width()) + || (viewRect.height() * 1.2 < deviceRect.height()); + } + + QRegion scrollExposure; + if (allowPartialCacheExposure) { + // Part of pixmap is drawn. Either device contains viewrect (big + // item covers whole screen) or parts of device are outside the + // viewport. In either case the device rect must be the intersect + // between the two. + int dx = deviceRect.left() < viewRect.left() ? viewRect.left() - deviceRect.left() : 0; + int dy = deviceRect.top() < viewRect.top() ? viewRect.top() - deviceRect.top() : 0; + QPoint newCacheIndent(dx, dy); + deviceRect &= viewRect; + + if (pix.isNull()) { + deviceData->cacheIndent = QPoint(); + itemCache->allExposed = true; + itemCache->exposed.clear(); + pixModified = true; + } + + // Copy / "scroll" the old pixmap onto the new ole and calculate + // scrolled exposure. + if (newCacheIndent != deviceData->cacheIndent || deviceRect.size() != pix.size()) { + QPoint diff = newCacheIndent - deviceData->cacheIndent; + QPixmap newPix(deviceRect.size()); + // ### Investigate removing this fill (test with Plasma and + // graphicssystem raster). + newPix.fill(Qt::transparent); + if (!pix.isNull()) { + QPainter newPixPainter(&newPix); + newPixPainter.drawPixmap(-diff, pix); + newPixPainter.end(); + } + QRegion exposed; + exposed += newPix.rect(); + if (!pix.isNull()) + exposed -= QRect(-diff, pix.size()); + scrollExposure = exposed; + + pix = newPix; + pixModified = true; + } + deviceData->cacheIndent = newCacheIndent; + } else { + // Full pixmap is drawn. + deviceData->cacheIndent = QPoint(); + + // Auto-adjust the pixmap size. + if (deviceRect.size() != pix.size()) { + // exposed needs to cover the whole pixmap + pix = QPixmap(deviceRect.size()); + pixModified = true; + itemCache->allExposed = true; + itemCache->exposed.clear(); + } + } + + // Check for newly invalidated areas. + if (itemCache->allExposed || !itemCache->exposed.isEmpty() || !scrollExposure.isEmpty()) { + //We know that we will modify the pixmap, removing it from the cache + //will detach the one we have and avoid a deep copy + if (pixmapFound) + QPixmapCache::remove(pixmapKey); + + // Construct an item-to-pixmap transform. + QPointF p = deviceRect.topLeft(); + QTransform itemToPixmap = painter->worldTransform(); + if (!p.isNull()) + itemToPixmap *= QTransform::fromTranslate(-p.x(), -p.y()); + + // Map the item's logical expose to pixmap coordinates. + QRegion pixmapExposed = scrollExposure; + if (!itemCache->allExposed) { + const QVector &exposed = itemCache->exposed; + for (int i = 0; i < exposed.size(); ++i) + pixmapExposed += itemToPixmap.mapRect(exposed.at(i)).toRect().adjusted(-1, -1, 1, 1); + } + + // Calculate the style option's exposedRect. + QRectF br; + if (itemCache->allExposed) { + br = item->boundingRect(); + } else { + const QVector &exposed = itemCache->exposed; + for (int i = 0; i < exposed.size(); ++i) + br |= exposed.at(i); + QTransform pixmapToItem = itemToPixmap.inverted(); + foreach (QRect r, scrollExposure.rects()) + br |= pixmapToItem.mapRect(r); + } + styleOptionTmp = *option; + styleOptionTmp.exposedRect = br.adjusted(-1, -1, 1, 1); + + // Render the exposed areas. + _q_paintIntoCache(&pix, item, pixmapExposed, itemToPixmap, painter->renderHints(), + &styleOptionTmp, painterStateProtection); + + // Reset expose data. + pixModified = true; + itemCache->allExposed = false; + itemCache->exposed.clear(); + } + + if (pixModified) { + // Insert this pixmap into the cache. + deviceData->key = QPixmapCache::insert(pix); + } + + // Redraw the exposed area using an untransformed painter. This + // effectively becomes a bitblit that does not transform the cache. + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + if (newPainterOpacity != oldPainterOpacity) { + painter->setOpacity(newPainterOpacity); + painter->drawPixmap(deviceRect.topLeft(), pix); + painter->setOpacity(oldPainterOpacity); + } else { + painter->drawPixmap(deviceRect.topLeft(), pix); + } + painter->setWorldTransform(restoreTransform); + return; + } +} + +void QGraphicsScenePrivate::drawItems(QPainter *painter, const QTransform *const viewTransform, + QRegion *exposedRegion, QWidget *widget) +{ + // Make sure we don't have unpolished items before we draw. + if (!unpolishedItems.isEmpty()) + _q_polishItems(); + + updateAll = false; + QRectF exposedSceneRect; + if (exposedRegion && indexMethod != QGraphicsScene::NoIndex) { + exposedSceneRect = exposedRegion->boundingRect().adjusted(-1, -1, 1, 1); + if (viewTransform) + exposedSceneRect = viewTransform->inverted().mapRect(exposedSceneRect); + } + const QList tli = index->estimateTopLevelItems(exposedSceneRect, Qt::AscendingOrder); + for (int i = 0; i < tli.size(); ++i) + drawSubtreeRecursive(tli.at(i), painter, viewTransform, exposedRegion, widget); +} + +void QGraphicsScenePrivate::drawSubtreeRecursive(QGraphicsItem *item, QPainter *painter, + const QTransform *const viewTransform, + QRegion *exposedRegion, QWidget *widget, + qreal parentOpacity, const QTransform *const effectTransform) +{ + Q_ASSERT(item); + + if (!item->d_ptr->visible) + return; + + const bool itemHasContents = !(item->d_ptr->flags & QGraphicsItem::ItemHasNoContents); + const bool itemHasChildren = !item->d_ptr->children.isEmpty(); + if (!itemHasContents && !itemHasChildren) + return; // Item has neither contents nor children!(?) + + const qreal opacity = item->d_ptr->combineOpacityFromParent(parentOpacity); + const bool itemIsFullyTransparent = QGraphicsItemPrivate::isOpacityNull(opacity); + if (itemIsFullyTransparent && (!itemHasChildren || item->d_ptr->childrenCombineOpacity())) + return; + + QTransform transform(Qt::Uninitialized); + QTransform *transformPtr = 0; + bool translateOnlyTransform = false; +#define ENSURE_TRANSFORM_PTR \ + if (!transformPtr) { \ + Q_ASSERT(!itemIsUntransformable); \ + if (viewTransform) { \ + transform = item->d_ptr->sceneTransform; \ + transform *= *viewTransform; \ + transformPtr = &transform; \ + } else { \ + transformPtr = &item->d_ptr->sceneTransform; \ + translateOnlyTransform = item->d_ptr->sceneTransformTranslateOnly; \ + } \ + } + + // Update the item's scene transform if the item is transformable; + // otherwise calculate the full transform, + bool wasDirtyParentSceneTransform = false; + const bool itemIsUntransformable = item->d_ptr->itemIsUntransformable(); + if (itemIsUntransformable) { + transform = item->deviceTransform(viewTransform ? *viewTransform : QTransform()); + transformPtr = &transform; + } else if (item->d_ptr->dirtySceneTransform) { + item->d_ptr->updateSceneTransformFromParent(); + Q_ASSERT(!item->d_ptr->dirtySceneTransform); + wasDirtyParentSceneTransform = true; + } + + const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape); + bool drawItem = itemHasContents && !itemIsFullyTransparent; + if (drawItem) { + const QRectF brect = adjustedItemEffectiveBoundingRect(item); + ENSURE_TRANSFORM_PTR + QRect viewBoundingRect = translateOnlyTransform ? brect.translated(transformPtr->dx(), transformPtr->dy()).toAlignedRect() + : transformPtr->mapRect(brect).toAlignedRect(); + viewBoundingRect.adjust(-int(rectAdjust), -int(rectAdjust), rectAdjust, rectAdjust); + if (widget) + item->d_ptr->paintedViewBoundingRects.insert(widget, viewBoundingRect); + drawItem = exposedRegion ? exposedRegion->intersects(viewBoundingRect) + : !viewBoundingRect.normalized().isEmpty(); + if (!drawItem) { + if (!itemHasChildren) + return; + if (itemClipsChildrenToShape) { + if (wasDirtyParentSceneTransform) + item->d_ptr->invalidateChildrenSceneTransform(); + return; + } + } + } // else we know for sure this item has children we must process. + + if (itemHasChildren && itemClipsChildrenToShape) + ENSURE_TRANSFORM_PTR; + +#ifndef QT_NO_GRAPHICSEFFECT + if (item->d_ptr->graphicsEffect && item->d_ptr->graphicsEffect->isEnabled()) { + ENSURE_TRANSFORM_PTR; + QGraphicsItemPaintInfo info(viewTransform, transformPtr, effectTransform, exposedRegion, widget, &styleOptionTmp, + painter, opacity, wasDirtyParentSceneTransform, itemHasContents && !itemIsFullyTransparent); + QGraphicsEffectSource *source = item->d_ptr->graphicsEffect->d_func()->source; + QGraphicsItemEffectSourcePrivate *sourced = static_cast + (source->d_func()); + sourced->info = &info; + const QTransform restoreTransform = painter->worldTransform(); + if (effectTransform) + painter->setWorldTransform(*transformPtr * *effectTransform); + else + painter->setWorldTransform(*transformPtr); + painter->setOpacity(opacity); + + if (sourced->currentCachedSystem() != Qt::LogicalCoordinates + && sourced->lastEffectTransform != painter->worldTransform()) + { + if (sourced->lastEffectTransform.type() <= QTransform::TxTranslate + && painter->worldTransform().type() <= QTransform::TxTranslate) + { + QRectF sourceRect = sourced->boundingRect(Qt::DeviceCoordinates); + QRect effectRect = sourced->paddedEffectRect(Qt::DeviceCoordinates, sourced->currentCachedMode(), sourceRect); + + sourced->setCachedOffset(effectRect.topLeft()); + } else { + sourced->invalidateCache(QGraphicsEffectSourcePrivate::TransformChanged); + } + + sourced->lastEffectTransform = painter->worldTransform(); + } + + item->d_ptr->graphicsEffect->draw(painter); + painter->setWorldTransform(restoreTransform); + sourced->info = 0; + } else +#endif //QT_NO_GRAPHICSEFFECT + { + draw(item, painter, viewTransform, transformPtr, exposedRegion, widget, opacity, + effectTransform, wasDirtyParentSceneTransform, drawItem); + } +} + +static inline void setClip(QPainter *painter, QGraphicsItem *item) +{ + painter->save(); + QRectF clipRect; + const QPainterPath clipPath(item->shape()); + if (QPathClipper::pathToRect(clipPath, &clipRect)) + painter->setClipRect(clipRect, Qt::IntersectClip); + else + painter->setClipPath(clipPath, Qt::IntersectClip); +} + +static inline void setWorldTransform(QPainter *painter, const QTransform *const transformPtr, + const QTransform *effectTransform) +{ + Q_ASSERT(transformPtr); + if (effectTransform) + painter->setWorldTransform(*transformPtr * *effectTransform); + else + painter->setWorldTransform(*transformPtr); +} + +void QGraphicsScenePrivate::draw(QGraphicsItem *item, QPainter *painter, const QTransform *const viewTransform, + const QTransform *const transformPtr, QRegion *exposedRegion, QWidget *widget, + qreal opacity, const QTransform *effectTransform, + bool wasDirtyParentSceneTransform, bool drawItem) +{ + const bool itemIsFullyTransparent = QGraphicsItemPrivate::isOpacityNull(opacity); + const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape); + const bool itemHasChildren = !item->d_ptr->children.isEmpty(); + bool setChildClip = itemClipsChildrenToShape; + bool itemHasChildrenStackedBehind = false; + + int i = 0; + if (itemHasChildren) { + if (itemClipsChildrenToShape) + setWorldTransform(painter, transformPtr, effectTransform); + + item->d_ptr->ensureSortedChildren(); + // Items with the 'ItemStacksBehindParent' flag are put in front of the list + // so all we have to do is to check the first item. + itemHasChildrenStackedBehind = (item->d_ptr->children.at(0)->d_ptr->flags + & QGraphicsItem::ItemStacksBehindParent); + + if (itemHasChildrenStackedBehind) { + if (itemClipsChildrenToShape) { + setClip(painter, item); + setChildClip = false; + } + + // Draw children behind + for (i = 0; i < item->d_ptr->children.size(); ++i) { + QGraphicsItem *child = item->d_ptr->children.at(i); + if (wasDirtyParentSceneTransform) + child->d_ptr->dirtySceneTransform = 1; + if (!(child->d_ptr->flags & QGraphicsItem::ItemStacksBehindParent)) + break; + if (itemIsFullyTransparent && !(child->d_ptr->flags & QGraphicsItem::ItemIgnoresParentOpacity)) + continue; + drawSubtreeRecursive(child, painter, viewTransform, exposedRegion, widget, opacity, effectTransform); + } + } + } + + // Draw item + if (drawItem) { + Q_ASSERT(!itemIsFullyTransparent); + Q_ASSERT(!(item->d_ptr->flags & QGraphicsItem::ItemHasNoContents)); + Q_ASSERT(transformPtr); + item->d_ptr->initStyleOption(&styleOptionTmp, *transformPtr, exposedRegion + ? *exposedRegion : QRegion(), exposedRegion == 0); + + const bool itemClipsToShape = item->d_ptr->flags & QGraphicsItem::ItemClipsToShape; + bool restorePainterClip = false; + + if (!itemHasChildren || !itemClipsChildrenToShape) { + // Item does not have children or clip children to shape. + setWorldTransform(painter, transformPtr, effectTransform); + if ((restorePainterClip = itemClipsToShape)) + setClip(painter, item); + } else if (itemHasChildrenStackedBehind){ + // Item clips children to shape and has children stacked behind, which means + // the painter is already clipped to the item's shape. + if (itemClipsToShape) { + // The clip is already correct. Ensure correct world transform. + setWorldTransform(painter, transformPtr, effectTransform); + } else { + // Remove clip (this also ensures correct world transform). + painter->restore(); + setChildClip = true; + } + } else if (itemClipsToShape) { + // Item clips children and itself to shape. It does not have hildren stacked + // behind, which means the clip has not yet been set. We set it now and re-use it + // for the children. + setClip(painter, item); + setChildClip = false; + } + + if (painterStateProtection && !restorePainterClip) + painter->save(); + + painter->setOpacity(opacity); + if (!item->d_ptr->cacheMode && !item->d_ptr->isWidget) + item->paint(painter, &styleOptionTmp, widget); + else + drawItemHelper(item, painter, &styleOptionTmp, widget, painterStateProtection); + + if (painterStateProtection || restorePainterClip) + painter->restore(); + + static int drawRect = qgetenv("QT_DRAW_SCENE_ITEM_RECTS").toInt(); + if (drawRect) { + QPen oldPen = painter->pen(); + QBrush oldBrush = painter->brush(); + quintptr ptr = reinterpret_cast(item); + const QColor color = QColor::fromHsv(ptr % 255, 255, 255); + painter->setPen(color); + painter->setBrush(Qt::NoBrush); + painter->drawRect(adjustedItemBoundingRect(item)); + painter->setPen(oldPen); + painter->setBrush(oldBrush); + } + } + + // Draw children in front + if (itemHasChildren) { + if (setChildClip) + setClip(painter, item); + + for (; i < item->d_ptr->children.size(); ++i) { + QGraphicsItem *child = item->d_ptr->children.at(i); + if (wasDirtyParentSceneTransform) + child->d_ptr->dirtySceneTransform = 1; + if (itemIsFullyTransparent && !(child->d_ptr->flags & QGraphicsItem::ItemIgnoresParentOpacity)) + continue; + drawSubtreeRecursive(child, painter, viewTransform, exposedRegion, widget, opacity, effectTransform); + } + + // Restore child clip + if (itemClipsChildrenToShape) + painter->restore(); + } +} + +void QGraphicsScenePrivate::markDirty(QGraphicsItem *item, const QRectF &rect, bool invalidateChildren, + bool force, bool ignoreOpacity, bool removingItemFromScene, + bool updateBoundingRect) +{ + Q_ASSERT(item); + if (updateAll) + return; + + if (removingItemFromScene && !ignoreOpacity && !item->d_ptr->ignoreOpacity) { + // If any of the item's ancestors ignore opacity, it means that the opacity + // was set to 0 (and the update request has not yet been processed). That + // also means that we have to ignore the opacity for the item itself; otherwise + // things like: parent->setOpacity(0); scene->removeItem(child) won't work. + // Note that we only do this when removing items from the scene. In all other + // cases the ignoreOpacity bit propagates properly in processDirtyItems, but + // since the item is removed immediately it won't be processed there. + QGraphicsItem *p = item->d_ptr->parent; + while (p) { + if (p->d_ptr->ignoreOpacity) { + item->d_ptr->ignoreOpacity = true; + break; + } + p = p->d_ptr->parent; + } + } + + if (item->d_ptr->discardUpdateRequest(/*ignoreVisibleBit=*/force, + /*ignoreDirtyBit=*/removingItemFromScene || invalidateChildren, + /*ignoreOpacity=*/ignoreOpacity)) { + if (item->d_ptr->dirty) { + // The item is already marked as dirty and will be processed later. However, + // we have to make sure ignoreVisible and ignoreOpacity are set properly; + // otherwise things like: item->update(); item->hide() (force is now true) + // won't work as expected. + if (force) + item->d_ptr->ignoreVisible = 1; + if (ignoreOpacity) + item->d_ptr->ignoreOpacity = 1; + } + return; + } + + const bool fullItemUpdate = rect.isNull(); + if (!fullItemUpdate && rect.isEmpty()) + return; + + if (!processDirtyItemsEmitted) { + QMetaMethod method = q_ptr->metaObject()->method(processDirtyItemsIndex); + method.invoke(q_ptr, Qt::QueuedConnection); +// QMetaObject::invokeMethod(q_ptr, "_q_processDirtyItems", Qt::QueuedConnection); + processDirtyItemsEmitted = true; + } + + if (removingItemFromScene) { + // Note that this function can be called from the item's destructor, so + // do NOT call any virtual functions on it within this block. + if (isSignalConnected(changedSignalIndex) || views.isEmpty()) { + // This block of code is kept for compatibility. Since 4.5, by default + // QGraphicsView does not connect the signal and we use the below + // method of delivering updates. + q_func()->update(); + return; + } + + for (int i = 0; i < views.size(); ++i) { + QGraphicsViewPrivate *viewPrivate = views.at(i)->d_func(); + QRect rect = item->d_ptr->paintedViewBoundingRects.value(viewPrivate->viewport); + rect.translate(viewPrivate->dirtyScrollOffset); + viewPrivate->updateRect(rect); + } + return; + } + + bool hasNoContents = item->d_ptr->flags & QGraphicsItem::ItemHasNoContents; + if (!hasNoContents) { + item->d_ptr->dirty = 1; + if (fullItemUpdate) + item->d_ptr->fullUpdatePending = 1; + else if (!item->d_ptr->fullUpdatePending) + item->d_ptr->needsRepaint |= rect; + } else if (item->d_ptr->graphicsEffect) { + invalidateChildren = true; + } + + if (invalidateChildren) { + item->d_ptr->allChildrenDirty = 1; + item->d_ptr->dirtyChildren = 1; + } + + if (force) + item->d_ptr->ignoreVisible = 1; + if (ignoreOpacity) + item->d_ptr->ignoreOpacity = 1; + + if (!updateBoundingRect) + item->d_ptr->markParentDirty(); +} + +static inline bool updateHelper(QGraphicsViewPrivate *view, QGraphicsItemPrivate *item, + const QRectF &rect, bool itemIsUntransformable) +{ + Q_ASSERT(view); + Q_ASSERT(item); + + QGraphicsItem *itemq = static_cast(item->q_ptr); + QGraphicsView *viewq = static_cast(view->q_ptr); + + if (itemIsUntransformable) { + const QTransform xform = itemq->deviceTransform(viewq->viewportTransform()); + if (!item->hasBoundingRegionGranularity) + return view->updateRectF(xform.mapRect(rect)); + return view->updateRegion(rect, xform); + } + + if (item->sceneTransformTranslateOnly && view->identityMatrix) { + const qreal dx = item->sceneTransform.dx(); + const qreal dy = item->sceneTransform.dy(); + QRectF r(rect); + r.translate(dx - view->horizontalScroll(), dy - view->verticalScroll()); + return view->updateRectF(r); + } + + if (!viewq->isTransformed()) { + if (!item->hasBoundingRegionGranularity) + return view->updateRectF(item->sceneTransform.mapRect(rect)); + return view->updateRegion(rect, item->sceneTransform); + } + + QTransform xform = item->sceneTransform; + xform *= viewq->viewportTransform(); + if (!item->hasBoundingRegionGranularity) + return view->updateRectF(xform.mapRect(rect)); + return view->updateRegion(rect, xform); +} + +void QGraphicsScenePrivate::processDirtyItemsRecursive(QGraphicsItem *item, bool dirtyAncestorContainsChildren, + qreal parentOpacity) +{ + Q_Q(QGraphicsScene); + Q_ASSERT(item); + Q_ASSERT(!updateAll); + + if (!item->d_ptr->dirty && !item->d_ptr->dirtyChildren) { + resetDirtyItem(item); + return; + } + + const bool itemIsHidden = !item->d_ptr->ignoreVisible && !item->d_ptr->visible; + if (itemIsHidden) { + resetDirtyItem(item, /*recursive=*/true); + return; + } + + bool itemHasContents = !(item->d_ptr->flags & QGraphicsItem::ItemHasNoContents); + const bool itemHasChildren = !item->d_ptr->children.isEmpty(); + if (!itemHasContents) { + if (!itemHasChildren) { + resetDirtyItem(item); + return; // Item has neither contents nor children!(?) + } + if (item->d_ptr->graphicsEffect) + itemHasContents = true; + } + + const qreal opacity = item->d_ptr->combineOpacityFromParent(parentOpacity); + const bool itemIsFullyTransparent = !item->d_ptr->ignoreOpacity + && QGraphicsItemPrivate::isOpacityNull(opacity); + if (itemIsFullyTransparent && (!itemHasChildren || item->d_ptr->childrenCombineOpacity())) { + resetDirtyItem(item, /*recursive=*/itemHasChildren); + return; + } + + bool wasDirtyParentSceneTransform = item->d_ptr->dirtySceneTransform; + const bool itemIsUntransformable = item->d_ptr->itemIsUntransformable(); + if (wasDirtyParentSceneTransform && !itemIsUntransformable) { + item->d_ptr->updateSceneTransformFromParent(); + Q_ASSERT(!item->d_ptr->dirtySceneTransform); + } + + const bool wasDirtyParentViewBoundingRects = item->d_ptr->paintedViewBoundingRectsNeedRepaint; + if (itemIsFullyTransparent || !itemHasContents || dirtyAncestorContainsChildren) { + // Make sure we don't process invisible items or items with no content. + item->d_ptr->dirty = 0; + item->d_ptr->fullUpdatePending = 0; + // Might have a dirty view bounding rect otherwise. + if (itemIsFullyTransparent || !itemHasContents) + item->d_ptr->paintedViewBoundingRectsNeedRepaint = 0; + } + + if (!hasSceneRect && item->d_ptr->geometryChanged && item->d_ptr->visible) { + // Update growingItemsBoundingRect. + if (item->d_ptr->sceneTransformTranslateOnly) { + growingItemsBoundingRect |= item->boundingRect().translated(item->d_ptr->sceneTransform.dx(), + item->d_ptr->sceneTransform.dy()); + } else { + growingItemsBoundingRect |= item->d_ptr->sceneTransform.mapRect(item->boundingRect()); + } + } + + // Process item. + if (item->d_ptr->dirty || item->d_ptr->paintedViewBoundingRectsNeedRepaint) { + const bool useCompatUpdate = views.isEmpty() || isSignalConnected(changedSignalIndex); + const QRectF itemBoundingRect = adjustedItemEffectiveBoundingRect(item); + + if (useCompatUpdate && !itemIsUntransformable && qFuzzyIsNull(item->boundingRegionGranularity())) { + // This block of code is kept for compatibility. Since 4.5, by default + // QGraphicsView does not connect the signal and we use the below + // method of delivering updates. + if (item->d_ptr->sceneTransformTranslateOnly) { + q->update(itemBoundingRect.translated(item->d_ptr->sceneTransform.dx(), + item->d_ptr->sceneTransform.dy())); + } else { + QRectF rect = item->d_ptr->sceneTransform.mapRect(itemBoundingRect); + if (!rect.isEmpty()) + q->update(rect); + } + } else { + QRectF dirtyRect; + bool uninitializedDirtyRect = true; + + for (int j = 0; j < views.size(); ++j) { + QGraphicsView *view = views.at(j); + QGraphicsViewPrivate *viewPrivate = view->d_func(); + QRect &paintedViewBoundingRect = item->d_ptr->paintedViewBoundingRects[viewPrivate->viewport]; + if (viewPrivate->fullUpdatePending + || viewPrivate->viewportUpdateMode == QGraphicsView::NoViewportUpdate) { + // Okay, if we have a full update pending or no viewport update, this item's + // paintedViewBoundingRect will be updated correctly in the next paintEvent if + // it is inside the viewport, but for now we can pretend that it is outside. + paintedViewBoundingRect = QRect(-1, -1, -1, -1); + continue; + } + + if (item->d_ptr->paintedViewBoundingRectsNeedRepaint) { + paintedViewBoundingRect.translate(viewPrivate->dirtyScrollOffset); + if (!viewPrivate->updateRect(paintedViewBoundingRect)) + paintedViewBoundingRect = QRect(-1, -1, -1, -1); // Outside viewport. + } + + if (!item->d_ptr->dirty) + continue; + + if (!item->d_ptr->paintedViewBoundingRectsNeedRepaint + && paintedViewBoundingRect.x() == -1 && paintedViewBoundingRect.y() == -1 + && paintedViewBoundingRect.width() == -1 && paintedViewBoundingRect.height() == -1) { + continue; // Outside viewport. + } + + if (uninitializedDirtyRect) { + dirtyRect = itemBoundingRect; + if (!item->d_ptr->fullUpdatePending) { + _q_adjustRect(&item->d_ptr->needsRepaint); + dirtyRect &= item->d_ptr->needsRepaint; + } + uninitializedDirtyRect = false; + } + + if (dirtyRect.isEmpty()) + continue; // Discard updates outside the bounding rect. + + if (!updateHelper(viewPrivate, item->d_ptr.data(), dirtyRect, itemIsUntransformable) + && item->d_ptr->paintedViewBoundingRectsNeedRepaint) { + paintedViewBoundingRect = QRect(-1, -1, -1, -1); // Outside viewport. + } + } + } + } + + // Process children. + if (itemHasChildren && item->d_ptr->dirtyChildren) { + const bool itemClipsChildrenToShape = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape; + // Items with no content are threated as 'dummy' items which means they are never drawn and + // 'processed', so the painted view bounding rect is never up-to-date. This means that whenever + // such an item changes geometry, its children have to take care of the update regardless + // of whether the item clips children to shape or not. + const bool bypassUpdateClip = !itemHasContents && wasDirtyParentViewBoundingRects; + if (itemClipsChildrenToShape && !bypassUpdateClip) { + // Make sure child updates are clipped to the item's bounding rect. + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->setUpdateClip(item); + } + if (!dirtyAncestorContainsChildren) { + dirtyAncestorContainsChildren = item->d_ptr->fullUpdatePending + && itemClipsChildrenToShape; + } + const bool allChildrenDirty = item->d_ptr->allChildrenDirty; + const bool parentIgnoresVisible = item->d_ptr->ignoreVisible; + const bool parentIgnoresOpacity = item->d_ptr->ignoreOpacity; + for (int i = 0; i < item->d_ptr->children.size(); ++i) { + QGraphicsItem *child = item->d_ptr->children.at(i); + if (wasDirtyParentSceneTransform) + child->d_ptr->dirtySceneTransform = 1; + if (wasDirtyParentViewBoundingRects) + child->d_ptr->paintedViewBoundingRectsNeedRepaint = 1; + if (parentIgnoresVisible) + child->d_ptr->ignoreVisible = 1; + if (parentIgnoresOpacity) + child->d_ptr->ignoreOpacity = 1; + if (allChildrenDirty) { + child->d_ptr->dirty = 1; + child->d_ptr->fullUpdatePending = 1; + child->d_ptr->dirtyChildren = 1; + child->d_ptr->allChildrenDirty = 1; + } + processDirtyItemsRecursive(child, dirtyAncestorContainsChildren, opacity); + } + + if (itemClipsChildrenToShape) { + // Reset updateClip. + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->setUpdateClip(0); + } + } else if (wasDirtyParentSceneTransform) { + item->d_ptr->invalidateChildrenSceneTransform(); + } + + resetDirtyItem(item); +} + +/*! + \obsolete + + Paints the given \a items using the provided \a painter, after the + background has been drawn, and before the foreground has been + drawn. All painting is done in \e scene coordinates. Before + drawing each item, the painter must be transformed using + QGraphicsItem::sceneTransform(). + + The \a options parameter is the list of style option objects for + each item in \a items. The \a numItems parameter is the number of + items in \a items and options in \a options. The \a widget + parameter is optional; if specified, it should point to the widget + that is being painted on. + + The default implementation prepares the painter matrix, and calls + QGraphicsItem::paint() on all items. Reimplement this function to + provide custom painting of all items for the scene; gaining + complete control over how each item is drawn. In some cases this + can increase drawing performance significantly. + + Example: + + \snippet doc/src/snippets/graphicssceneadditemsnippet.cpp 0 + + Since Qt 4.6, this function is not called anymore unless + the QGraphicsView::IndirectPainting flag is given as an Optimization + flag. + + \sa drawBackground(), drawForeground() +*/ +void QGraphicsScene::drawItems(QPainter *painter, + int numItems, + QGraphicsItem *items[], + const QStyleOptionGraphicsItem options[], QWidget *widget) +{ + Q_D(QGraphicsScene); + // Make sure we don't have unpolished items before we draw. + if (!d->unpolishedItems.isEmpty()) + d->_q_polishItems(); + + const qreal opacity = painter->opacity(); + QTransform viewTransform = painter->worldTransform(); + Q_UNUSED(options); + + // Determine view, expose and flags. + QGraphicsView *view = widget ? qobject_cast(widget->parentWidget()) : 0; + QRegion *expose = 0; + const quint32 oldRectAdjust = d->rectAdjust; + if (view) { + d->updateAll = false; + expose = &view->d_func()->exposedRegion; + if (view->d_func()->optimizationFlags & QGraphicsView::DontAdjustForAntialiasing) + d->rectAdjust = 1; + else + d->rectAdjust = 2; + } + + // Find all toplevels, they are already sorted. + QList topLevelItems; + for (int i = 0; i < numItems; ++i) { + QGraphicsItem *item = items[i]->topLevelItem(); + if (!item->d_ptr->itemDiscovered) { + topLevelItems << item; + item->d_ptr->itemDiscovered = 1; + d->drawSubtreeRecursive(item, painter, &viewTransform, expose, widget); + } + } + + d->rectAdjust = oldRectAdjust; + // Reset discovery bits. + for (int i = 0; i < topLevelItems.size(); ++i) + topLevelItems.at(i)->d_ptr->itemDiscovered = 0; + + painter->setWorldTransform(viewTransform); + painter->setOpacity(opacity); +} + +/*! + \since 4.4 + + Finds a new widget to give the keyboard focus to, as appropriate for Tab + and Shift+Tab, and returns true if it can find a new widget, or false if + it cannot. If \a next is true, this function searches forward; if \a next + is false, it searches backward. + + You can reimplement this function in a subclass of QGraphicsScene to + provide fine-grained control over how tab focus passes inside your + scene. The default implementation is based on the tab focus chain defined + by QGraphicsWidget::setTabOrder(). +*/ +bool QGraphicsScene::focusNextPrevChild(bool next) +{ + Q_D(QGraphicsScene); + + QGraphicsItem *item = focusItem(); + if (item && !item->isWidget()) { + // Tab out of the scene. + return false; + } + if (!item) { + if (d->lastFocusItem && !d->lastFocusItem->isWidget()) { + // Restore focus to the last focusable non-widget item that had + // focus. + setFocusItem(d->lastFocusItem, next ? Qt::TabFocusReason : Qt::BacktabFocusReason); + return true; + } + } + if (!d->tabFocusFirst) { + // No widgets... + return false; + } + + // The item must be a widget. + QGraphicsWidget *widget = 0; + if (!item) { + widget = next ? d->tabFocusFirst : d->tabFocusFirst->d_func()->focusPrev; + } else { + QGraphicsWidget *test = static_cast(item); + widget = next ? test->d_func()->focusNext : test->d_func()->focusPrev; + if ((next && widget == d->tabFocusFirst) || (!next && widget == d->tabFocusFirst->d_func()->focusPrev)) + return false; + } + QGraphicsWidget *widgetThatHadFocus = widget; + + // Run around the focus chain until we find a widget that can take tab focus. + do { + if (widget->flags() & QGraphicsItem::ItemIsFocusable + && widget->isEnabled() && widget->isVisibleTo(0) + && (widget->focusPolicy() & Qt::TabFocus) + && (!item || !item->isPanel() || item->isAncestorOf(widget)) + ) { + setFocusItem(widget, next ? Qt::TabFocusReason : Qt::BacktabFocusReason); + return true; + } + widget = next ? widget->d_func()->focusNext : widget->d_func()->focusPrev; + if ((next && widget == d->tabFocusFirst) || (!next && widget == d->tabFocusFirst->d_func()->focusPrev)) + return false; + } while (widget != widgetThatHadFocus); + + return false; +} + +/*! + \fn QGraphicsScene::changed(const QList ®ion) + + This signal is emitted by QGraphicsScene when control reaches the + event loop, if the scene content changes. The \a region parameter + contains a list of scene rectangles that indicate the area that + has been changed. + + \sa QGraphicsView::updateScene() +*/ + +/*! + \fn QGraphicsScene::sceneRectChanged(const QRectF &rect) + + This signal is emitted by QGraphicsScene whenever the scene rect changes. + The \a rect parameter is the new scene rectangle. + + \sa QGraphicsView::updateSceneRect() +*/ + +/*! + \fn QGraphicsScene::selectionChanged() + \since 4.3 + + This signal is emitted by QGraphicsScene whenever the selection + changes. You can call selectedItems() to get the new list of selected + items. + + The selection changes whenever an item is selected or unselected, a + selection area is set, cleared or otherwise changed, if a preselected item + is added to the scene, or if a selected item is removed from the scene. + + QGraphicsScene emits this signal only once for group selection operations. + For example, if you set a selection area, select or unselect a + QGraphicsItemGroup, or if you add or remove from the scene a parent item + that contains several selected items, selectionChanged() is emitted only + once after the operation has completed (instead of once for each item). + + \sa setSelectionArea(), selectedItems(), QGraphicsItem::setSelected() +*/ + +/*! + \since 4.4 + + Returns the scene's style, or the same as QApplication::style() if the + scene has not been explicitly assigned a style. + + \sa setStyle() +*/ +QStyle *QGraphicsScene::style() const +{ + Q_D(const QGraphicsScene); + // ### This function, and the use of styles in general, is non-reentrant. + return d->style ? d->style : QApplication::style(); +} + +/*! + \since 4.4 + + Sets or replaces the style of the scene to \a style, and reparents the + style to this scene. Any previously assigned style is deleted. The scene's + style defaults to QApplication::style(), and serves as the default for all + QGraphicsWidget items in the scene. + + Changing the style, either directly by calling this function, or + indirectly by calling QApplication::setStyle(), will automatically update + the style for all widgets in the scene that do not have a style explicitly + assigned to them. + + If \a style is 0, QGraphicsScene will revert to QApplication::style(). + + \sa style() +*/ +void QGraphicsScene::setStyle(QStyle *style) +{ + Q_D(QGraphicsScene); + // ### This function, and the use of styles in general, is non-reentrant. + if (style == d->style) + return; + + // Delete the old style, + delete d->style; + if ((d->style = style)) + d->style->setParent(this); + + // Notify the scene. + QEvent event(QEvent::StyleChange); + QApplication::sendEvent(this, &event); + + // Notify all widgets that don't have a style explicitly set. + foreach (QGraphicsItem *item, items()) { + if (item->isWidget()) { + QGraphicsWidget *widget = static_cast(item); + if (!widget->testAttribute(Qt::WA_SetStyle)) + QApplication::sendEvent(widget, &event); + } + } +} + +/*! + \property QGraphicsScene::font + \since 4.4 + \brief the scene's default font + + This property provides the scene's font. The scene font defaults to, + and resolves all its entries from, QApplication::font. + + If the scene's font changes, either directly through setFont() or + indirectly when the application font changes, QGraphicsScene first + sends itself a \l{QEvent::FontChange}{FontChange} event, and it then + sends \l{QEvent::FontChange}{FontChange} events to all top-level + widget items in the scene. These items respond by resolving their own + fonts to the scene, and they then notify their children, who again + notify their children, and so on, until all widget items have updated + their fonts. + + Changing the scene font, (directly or indirectly through + QApplication::setFont(),) automatically schedules a redraw the entire + scene. + + \sa QWidget::font, QApplication::setFont(), palette, style() +*/ +QFont QGraphicsScene::font() const +{ + Q_D(const QGraphicsScene); + return d->font; +} +void QGraphicsScene::setFont(const QFont &font) +{ + Q_D(QGraphicsScene); + QFont naturalFont = QApplication::font(); + naturalFont.resolve(0); + QFont resolvedFont = font.resolve(naturalFont); + d->setFont_helper(resolvedFont); +} + +/*! + \property QGraphicsScene::palette + \since 4.4 + \brief the scene's default palette + + This property provides the scene's palette. The scene palette defaults to, + and resolves all its entries from, QApplication::palette. + + If the scene's palette changes, either directly through setPalette() or + indirectly when the application palette changes, QGraphicsScene first + sends itself a \l{QEvent::PaletteChange}{PaletteChange} event, and it then + sends \l{QEvent::PaletteChange}{PaletteChange} events to all top-level + widget items in the scene. These items respond by resolving their own + palettes to the scene, and they then notify their children, who again + notify their children, and so on, until all widget items have updated + their palettes. + + Changing the scene palette, (directly or indirectly through + QApplication::setPalette(),) automatically schedules a redraw the entire + scene. + + \sa QWidget::palette, QApplication::setPalette(), font, style() +*/ +QPalette QGraphicsScene::palette() const +{ + Q_D(const QGraphicsScene); + return d->palette; +} +void QGraphicsScene::setPalette(const QPalette &palette) +{ + Q_D(QGraphicsScene); + QPalette naturalPalette = QApplication::palette(); + naturalPalette.resolve(0); + QPalette resolvedPalette = palette.resolve(naturalPalette); + d->setPalette_helper(resolvedPalette); +} + +/*! + \since 4.6 + + Returns true if the scene is active (e.g., it's viewed by + at least one QGraphicsView that is active); otherwise returns false. + + \sa QGraphicsItem::isActive(), QWidget::isActiveWindow() +*/ +bool QGraphicsScene::isActive() const +{ + Q_D(const QGraphicsScene); + return d->activationRefCount > 0; +} + +/*! + \since 4.6 + Returns the current active panel, or 0 if no panel is currently active. + + \sa QGraphicsScene::setActivePanel() +*/ +QGraphicsItem *QGraphicsScene::activePanel() const +{ + Q_D(const QGraphicsScene); + return d->activePanel; +} + +/*! + \since 4.6 + Activates \a item, which must be an item in this scene. You + can also pass 0 for \a item, in which case QGraphicsScene will + deactivate any currently active panel. + + If the scene is currently inactive, \a item remains inactive until the + scene becomes active (or, ir \a item is 0, no item will be activated). + + \sa activePanel(), isActive(), QGraphicsItem::isActive() +*/ +void QGraphicsScene::setActivePanel(QGraphicsItem *item) +{ + Q_D(QGraphicsScene); + d->setActivePanelHelper(item, false); +} + +/*! + \since 4.4 + + Returns the current active window, or 0 if no window is currently + active. + + \sa QGraphicsScene::setActiveWindow() +*/ +QGraphicsWidget *QGraphicsScene::activeWindow() const +{ + Q_D(const QGraphicsScene); + if (d->activePanel && d->activePanel->isWindow()) + return static_cast(d->activePanel); + return 0; +} + +/*! + \since 4.4 + Activates \a widget, which must be a widget in this scene. You can also + pass 0 for \a widget, in which case QGraphicsScene will deactivate any + currently active window. + + \sa activeWindow(), QGraphicsWidget::isActiveWindow() +*/ +void QGraphicsScene::setActiveWindow(QGraphicsWidget *widget) +{ + if (widget && widget->scene() != this) { + qWarning("QGraphicsScene::setActiveWindow: widget %p must be part of this scene", + widget); + return; + } + + // Activate the widget's panel (all windows are panels). + QGraphicsItem *panel = widget ? widget->panel() : 0; + setActivePanel(panel); + + // Raise + if (panel) { + QList siblingWindows; + QGraphicsItem *parent = panel->parentItem(); + // Raise ### inefficient for toplevels + foreach (QGraphicsItem *sibling, parent ? parent->children() : items()) { + if (sibling != panel && sibling->isWindow()) + siblingWindows << sibling; + } + + // Find the highest z value. + qreal z = panel->zValue(); + for (int i = 0; i < siblingWindows.size(); ++i) + z = qMax(z, siblingWindows.at(i)->zValue()); + + // This will probably never overflow. + const qreal litt = qreal(0.001); + panel->setZValue(z + litt); + } +} + +/*! + \since 4.6 + + Sends event \a event to item \a item through possible event filters. + + The event is sent only if the item is enabled. + + Returns \c false if the event was filtered or if the item is disabled. + Otherwise returns the value that was returned from the event handler. + + \sa QGraphicsItem::sceneEvent(), QGraphicsItem::sceneEventFilter() +*/ +bool QGraphicsScene::sendEvent(QGraphicsItem *item, QEvent *event) +{ + Q_D(QGraphicsScene); + if (!item) { + qWarning("QGraphicsScene::sendEvent: cannot send event to a null item"); + return false; + } + if (item->scene() != this) { + qWarning("QGraphicsScene::sendEvent: item %p's scene (%p)" + " is different from this scene (%p)", + item, item->scene(), this); + return false; + } + return d->sendEvent(item, event); +} + +void QGraphicsScenePrivate::addView(QGraphicsView *view) +{ + views << view; +#ifndef QT_NO_GESTURES + foreach (Qt::GestureType gesture, grabbedGestures.keys()) + view->viewport()->grabGesture(gesture); +#endif +} + +void QGraphicsScenePrivate::removeView(QGraphicsView *view) +{ + views.removeAll(view); +} + +void QGraphicsScenePrivate::updateTouchPointsForItem(QGraphicsItem *item, QTouchEvent *touchEvent) +{ + QList touchPoints = touchEvent->touchPoints(); + for (int i = 0; i < touchPoints.count(); ++i) { + QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; + touchPoint.setRect(item->mapFromScene(touchPoint.sceneRect()).boundingRect()); + touchPoint.setStartPos(item->d_ptr->genericMapFromScene(touchPoint.startScenePos(), touchEvent->widget())); + touchPoint.setLastPos(item->d_ptr->genericMapFromScene(touchPoint.lastScenePos(), touchEvent->widget())); + } + touchEvent->setTouchPoints(touchPoints); +} + +int QGraphicsScenePrivate::findClosestTouchPointId(const QPointF &scenePos) +{ + int closestTouchPointId = -1; + qreal closestDistance = qreal(0.); + foreach (const QTouchEvent::TouchPoint &touchPoint, sceneCurrentTouchPoints) { + qreal distance = QLineF(scenePos, touchPoint.scenePos()).length(); + if (closestTouchPointId == -1|| distance < closestDistance) { + closestTouchPointId = touchPoint.id(); + closestDistance = distance; + } + } + return closestTouchPointId; +} + +void QGraphicsScenePrivate::touchEventHandler(QTouchEvent *sceneTouchEvent) +{ + typedef QPair > StatesAndTouchPoints; + QHash itemsNeedingEvents; + + for (int i = 0; i < sceneTouchEvent->touchPoints().count(); ++i) { + const QTouchEvent::TouchPoint &touchPoint = sceneTouchEvent->touchPoints().at(i); + + // update state + QGraphicsItem *item = 0; + if (touchPoint.state() == Qt::TouchPointPressed) { + if (sceneTouchEvent->deviceType() == QTouchEvent::TouchPad) { + // on touch-pad devices, send all touch points to the same item + item = itemForTouchPointId.isEmpty() + ? 0 + : itemForTouchPointId.constBegin().value(); + } + + if (!item) { + // determine which item this touch point will go to + cachedItemsUnderMouse = itemsAtPosition(touchPoint.screenPos().toPoint(), + touchPoint.scenePos(), + sceneTouchEvent->widget()); + item = cachedItemsUnderMouse.isEmpty() ? 0 : cachedItemsUnderMouse.first(); + } + + if (sceneTouchEvent->deviceType() == QTouchEvent::TouchScreen) { + // on touch-screens, combine this touch point with the closest one we find + int closestTouchPointId = findClosestTouchPointId(touchPoint.scenePos()); + QGraphicsItem *closestItem = itemForTouchPointId.value(closestTouchPointId); + if (!item || (closestItem && cachedItemsUnderMouse.contains(closestItem))) + item = closestItem; + } + if (!item) + continue; + + itemForTouchPointId.insert(touchPoint.id(), item); + sceneCurrentTouchPoints.insert(touchPoint.id(), touchPoint); + } else if (touchPoint.state() == Qt::TouchPointReleased) { + item = itemForTouchPointId.take(touchPoint.id()); + if (!item) + continue; + + sceneCurrentTouchPoints.remove(touchPoint.id()); + } else { + item = itemForTouchPointId.value(touchPoint.id()); + if (!item) + continue; + Q_ASSERT(sceneCurrentTouchPoints.contains(touchPoint.id())); + sceneCurrentTouchPoints[touchPoint.id()] = touchPoint; + } + + StatesAndTouchPoints &statesAndTouchPoints = itemsNeedingEvents[item]; + statesAndTouchPoints.first |= touchPoint.state(); + statesAndTouchPoints.second.append(touchPoint); + } + + if (itemsNeedingEvents.isEmpty()) { + sceneTouchEvent->accept(); + return; + } + + bool ignoreSceneTouchEvent = true; + QHash::ConstIterator it = itemsNeedingEvents.constBegin(); + const QHash::ConstIterator end = itemsNeedingEvents.constEnd(); + for (; it != end; ++it) { + QGraphicsItem *item = it.key(); + + (void) item->isBlockedByModalPanel(&item); + + // determine event type from the state mask + QEvent::Type eventType; + switch (it.value().first) { + case Qt::TouchPointPressed: + // all touch points have pressed state + eventType = QEvent::TouchBegin; + break; + case Qt::TouchPointReleased: + // all touch points have released state + eventType = QEvent::TouchEnd; + break; + case Qt::TouchPointStationary: + // don't send the event if nothing changed + continue; + default: + // all other combinations + eventType = QEvent::TouchUpdate; + break; + } + + QTouchEvent touchEvent(eventType); + touchEvent.setWidget(sceneTouchEvent->widget()); + touchEvent.setDeviceType(sceneTouchEvent->deviceType()); + touchEvent.setModifiers(sceneTouchEvent->modifiers()); + touchEvent.setTouchPointStates(it.value().first); + touchEvent.setTouchPoints(it.value().second); + + switch (touchEvent.type()) { + case QEvent::TouchBegin: + { + // if the TouchBegin handler recurses, we assume that means the event + // has been implicitly accepted and continue to send touch events + item->d_ptr->acceptedTouchBeginEvent = true; + bool res = sendTouchBeginEvent(item, &touchEvent) + && touchEvent.isAccepted(); + if (!res) { + // forget about these touch points, we didn't handle them + for (int i = 0; i < touchEvent.touchPoints().count(); ++i) { + const QTouchEvent::TouchPoint &touchPoint = touchEvent.touchPoints().at(i); + itemForTouchPointId.remove(touchPoint.id()); + sceneCurrentTouchPoints.remove(touchPoint.id()); + } + ignoreSceneTouchEvent = false; + } + break; + } + default: + if (item->d_ptr->acceptedTouchBeginEvent) { + updateTouchPointsForItem(item, &touchEvent); + (void) sendEvent(item, &touchEvent); + ignoreSceneTouchEvent = false; + } + break; + } + } + sceneTouchEvent->setAccepted(ignoreSceneTouchEvent); +} + +bool QGraphicsScenePrivate::sendTouchBeginEvent(QGraphicsItem *origin, QTouchEvent *touchEvent) +{ + Q_Q(QGraphicsScene); + + if (cachedItemsUnderMouse.isEmpty() || cachedItemsUnderMouse.first() != origin) { + const QTouchEvent::TouchPoint &firstTouchPoint = touchEvent->touchPoints().first(); + cachedItemsUnderMouse = itemsAtPosition(firstTouchPoint.screenPos().toPoint(), + firstTouchPoint.scenePos(), + touchEvent->widget()); + } + Q_ASSERT(cachedItemsUnderMouse.first() == origin); + + // Set focus on the topmost enabled item that can take focus. + bool setFocus = false; + + foreach (QGraphicsItem *item, cachedItemsUnderMouse) { + if (item->isEnabled() && ((item->flags() & QGraphicsItem::ItemIsFocusable) && item->d_ptr->mouseSetsFocus)) { + if (!item->isWidget() || ((QGraphicsWidget *)item)->focusPolicy() & Qt::ClickFocus) { + setFocus = true; + if (item != q->focusItem()) + q->setFocusItem(item, Qt::MouseFocusReason); + break; + } + } + if (item->isPanel()) + break; + if (item->d_ptr->flags & QGraphicsItem::ItemStopsClickFocusPropagation) + break; + if (item->d_ptr->flags & QGraphicsItem::ItemStopsFocusHandling) { + // Make sure we don't clear focus. + setFocus = true; + break; + } + } + + // If nobody could take focus, clear it. + if (!stickyFocus && !setFocus) + q->setFocusItem(0, Qt::MouseFocusReason); + + bool res = false; + bool eventAccepted = touchEvent->isAccepted(); + foreach (QGraphicsItem *item, cachedItemsUnderMouse) { + // first, try to deliver the touch event + updateTouchPointsForItem(item, touchEvent); + bool acceptTouchEvents = item->acceptTouchEvents(); + touchEvent->setAccepted(acceptTouchEvents); + res = acceptTouchEvents && sendEvent(item, touchEvent); + eventAccepted = touchEvent->isAccepted(); + if (itemForTouchPointId.value(touchEvent->touchPoints().first().id()) == 0) { + // item was deleted + item = 0; + } else { + item->d_ptr->acceptedTouchBeginEvent = (res && eventAccepted); + } + touchEvent->spont = false; + if (res && eventAccepted) { + // the first item to accept the TouchBegin gets an implicit grab. + for (int i = 0; i < touchEvent->touchPoints().count(); ++i) { + const QTouchEvent::TouchPoint &touchPoint = touchEvent->touchPoints().at(i); + itemForTouchPointId[touchPoint.id()] = item; // can be zero + } + break; + } + if (item && item->isPanel()) + break; + } + + touchEvent->setAccepted(eventAccepted); + return res; +} + +void QGraphicsScenePrivate::enableTouchEventsOnViews() +{ + foreach (QGraphicsView *view, views) + view->viewport()->setAttribute(Qt::WA_AcceptTouchEvents, true); +} + +void QGraphicsScenePrivate::updateInputMethodSensitivityInViews() +{ + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->updateInputMethodSensitivity(); +} + +void QGraphicsScenePrivate::enterModal(QGraphicsItem *panel, QGraphicsItem::PanelModality previousModality) +{ + Q_Q(QGraphicsScene); + Q_ASSERT(panel && panel->isPanel()); + + QGraphicsItem::PanelModality panelModality = panel->d_ptr->panelModality; + if (previousModality != QGraphicsItem::NonModal) { + // the panel is changing from one modality type to another... temporarily set it back so + // that blockedPanels is populated correctly + panel->d_ptr->panelModality = previousModality; + } + + QSet blockedPanels; + QList items = q->items(); // ### store panels separately + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel() && item->isBlockedByModalPanel()) + blockedPanels.insert(item); + } + // blockedPanels contains all currently blocked panels + + if (previousModality != QGraphicsItem::NonModal) { + // reset the modality to the proper value, since we changed it above + panel->d_ptr->panelModality = panelModality; + // remove this panel so that it will be reinserted at the front of the stack + modalPanels.removeAll(panel); + } + + modalPanels.prepend(panel); + + if (!hoverItems.isEmpty()) { + // send GraphicsSceneHoverLeave events to newly blocked hoverItems + QGraphicsSceneHoverEvent hoverEvent; + hoverEvent.setScenePos(lastSceneMousePos); + dispatchHoverEvent(&hoverEvent); + } + + if (!mouseGrabberItems.isEmpty() && lastMouseGrabberItemHasImplicitMouseGrab) { + QGraphicsItem *item = mouseGrabberItems.last(); + if (item->isBlockedByModalPanel()) + ungrabMouse(item, /*itemIsDying =*/ false); + } + + QEvent windowBlockedEvent(QEvent::WindowBlocked); + QEvent windowUnblockedEvent(QEvent::WindowUnblocked); + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel()) { + if (!blockedPanels.contains(item) && item->isBlockedByModalPanel()) { + // send QEvent::WindowBlocked to newly blocked panels + sendEvent(item, &windowBlockedEvent); + } else if (blockedPanels.contains(item) && !item->isBlockedByModalPanel()) { + // send QEvent::WindowUnblocked to unblocked panels when downgrading + // a panel from SceneModal to PanelModal + sendEvent(item, &windowUnblockedEvent); + } + } + } +} + +void QGraphicsScenePrivate::leaveModal(QGraphicsItem *panel) +{ + Q_Q(QGraphicsScene); + Q_ASSERT(panel && panel->isPanel()); + + QSet blockedPanels; + QList items = q->items(); // ### same as above + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel() && item->isBlockedByModalPanel()) + blockedPanels.insert(item); + } + + modalPanels.removeAll(panel); + + QEvent e(QEvent::WindowUnblocked); + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel() && blockedPanels.contains(item) && !item->isBlockedByModalPanel()) + sendEvent(item, &e); + } + + // send GraphicsSceneHoverEnter events to newly unblocked items + QGraphicsSceneHoverEvent hoverEvent; + hoverEvent.setScenePos(lastSceneMousePos); + dispatchHoverEvent(&hoverEvent); +} + +#ifndef QT_NO_GESTURES +void QGraphicsScenePrivate::gestureTargetsAtHotSpots(const QSet &gestures, + Qt::GestureFlag flag, + QHash > *targets, + QSet *itemsSet, + QSet *normal, + QSet *conflicts) +{ + QSet normalGestures; // that are not in conflicted state. + foreach (QGesture *gesture, gestures) { + if (!gesture->hasHotSpot()) + continue; + const Qt::GestureType gestureType = gesture->gestureType(); + QList items = itemsAtPosition(QPoint(), gesture->d_func()->sceneHotSpot, 0); + for (int j = 0; j < items.size(); ++j) { + QGraphicsItem *item = items.at(j); + + // Check if the item is blocked by a modal panel and use it as + // a target instead of this item. + (void) item->isBlockedByModalPanel(&item); + + if (QGraphicsObject *itemobj = item->toGraphicsObject()) { + QGraphicsItemPrivate *d = item->QGraphicsItem::d_func(); + QMap::const_iterator it = + d->gestureContext.find(gestureType); + if (it != d->gestureContext.end() && (!flag || (it.value() & flag))) { + if (normalGestures.contains(gesture)) { + normalGestures.remove(gesture); + if (conflicts) + conflicts->insert(gesture); + } else { + normalGestures.insert(gesture); + } + if (targets) + (*targets)[itemobj].insert(gesture); + if (itemsSet) + (*itemsSet).insert(itemobj); + } + } + // Don't propagate through panels. + if (item->isPanel()) + break; + } + } + if (normal) + *normal = normalGestures; +} + +void QGraphicsScenePrivate::gestureEventHandler(QGestureEvent *event) +{ + QWidget *viewport = event->widget(); + if (!viewport) + return; + QGraphicsView *graphicsView = qobject_cast(viewport->parent()); + if (!graphicsView) + return; + + QList allGestures = event->gestures(); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "Gestures:" << allGestures; + + QSet startedGestures; + QPoint delta = viewport->mapFromGlobal(QPoint()); + QTransform toScene = QTransform::fromTranslate(delta.x(), delta.y()) + * graphicsView->viewportTransform().inverted(); + foreach (QGesture *gesture, allGestures) { + // cache scene coordinates of the hot spot + if (gesture->hasHotSpot()) { + gesture->d_func()->sceneHotSpot = toScene.map(gesture->hotSpot()); + } else { + gesture->d_func()->sceneHotSpot = QPointF(); + } + + QGraphicsObject *target = gestureTargets.value(gesture, 0); + if (!target) { + // when we are not in started mode but don't have a target + // then the only one interested in gesture is the view/scene + if (gesture->state() == Qt::GestureStarted) + startedGestures.insert(gesture); + } + } + + if (!startedGestures.isEmpty()) { + QSet normalGestures; // that have just one target + QSet conflictedGestures; // that have multiple possible targets + gestureTargetsAtHotSpots(startedGestures, Qt::GestureFlag(0), &cachedItemGestures, 0, + &normalGestures, &conflictedGestures); + cachedTargetItems = cachedItemGestures.keys(); + qSort(cachedTargetItems.begin(), cachedTargetItems.end(), qt_closestItemFirst); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "Normal gestures:" << normalGestures + << "Conflicting gestures:" << conflictedGestures; + + // deliver conflicted gestures as override events AND remember + // initial gesture targets + if (!conflictedGestures.isEmpty()) { + for (int i = 0; i < cachedTargetItems.size(); ++i) { + QWeakPointer item = cachedTargetItems.at(i); + + // get gestures to deliver to the current item + QSet gestures = conflictedGestures & cachedItemGestures.value(item.data()); + if (gestures.isEmpty()) + continue; + + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "delivering override to" + << item.data() << gestures; + // send gesture override + QGestureEvent ev(gestures.toList()); + ev.t = QEvent::GestureOverride; + ev.setWidget(event->widget()); + // mark event and individual gestures as ignored + ev.ignore(); + foreach(QGesture *g, gestures) + ev.setAccepted(g, false); + sendEvent(item.data(), &ev); + // mark all accepted gestures to deliver them as normal gesture events + foreach (QGesture *g, gestures) { + if (ev.isAccepted() || ev.isAccepted(g)) { + conflictedGestures.remove(g); + // mark the item as a gesture target + if (item) { + gestureTargets.insert(g, item.data()); + QHash >::iterator it, e; + it = cachedItemGestures.begin(); + e = cachedItemGestures.end(); + for(; it != e; ++it) + it.value().remove(g); + cachedItemGestures[item.data()].insert(g); + } + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "override was accepted:" + << g << item.data(); + } + // remember the first item that received the override event + // as it most likely become a target if no one else accepts + // the override event + if (!gestureTargets.contains(g) && item) + gestureTargets.insert(g, item.data()); + + } + if (conflictedGestures.isEmpty()) + break; + } + } + // remember the initial target item for each gesture that was not in + // the conflicted state. + if (!normalGestures.isEmpty()) { + for (int i = 0; i < cachedTargetItems.size() && !normalGestures.isEmpty(); ++i) { + QGraphicsObject *item = cachedTargetItems.at(i); + + // get gestures to deliver to the current item + foreach (QGesture *g, cachedItemGestures.value(item)) { + if (!gestureTargets.contains(g)) { + gestureTargets.insert(g, item); + normalGestures.remove(g); + } + } + } + } + } + + + // deliver all gesture events + QSet undeliveredGestures; + QSet parentPropagatedGestures; + foreach (QGesture *gesture, allGestures) { + if (QGraphicsObject *target = gestureTargets.value(gesture, 0)) { + cachedItemGestures[target].insert(gesture); + cachedTargetItems.append(target); + undeliveredGestures.insert(gesture); + QGraphicsItemPrivate *d = target->QGraphicsItem::d_func(); + const Qt::GestureFlags flags = d->gestureContext.value(gesture->gestureType()); + if (flags & Qt::IgnoredGesturesPropagateToParent) + parentPropagatedGestures.insert(gesture); + } else { + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "no target for" << gesture << "at" + << gesture->hotSpot() << gesture->d_func()->sceneHotSpot; + } + } + qSort(cachedTargetItems.begin(), cachedTargetItems.end(), qt_closestItemFirst); + for (int i = 0; i < cachedTargetItems.size(); ++i) { + QWeakPointer receiver = cachedTargetItems.at(i); + QSet gestures = + undeliveredGestures & cachedItemGestures.value(receiver.data()); + gestures -= cachedAlreadyDeliveredGestures.value(receiver.data()); + + if (gestures.isEmpty()) + continue; + + cachedAlreadyDeliveredGestures[receiver.data()] += gestures; + const bool isPanel = receiver.data()->isPanel(); + + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "delivering to" + << receiver.data() << gestures; + QGestureEvent ev(gestures.toList()); + ev.setWidget(event->widget()); + sendEvent(receiver.data(), &ev); + QSet ignoredGestures; + foreach (QGesture *g, gestures) { + if (!ev.isAccepted() && !ev.isAccepted(g)) { + // if the gesture was ignored by its target, we will update the + // targetItems list with a possible target items (items that + // want to receive partial gestures). + // ### wont' work if the target was destroyed in the event + // we will just stop delivering it. + if (receiver && receiver.data() == gestureTargets.value(g, 0)) + ignoredGestures.insert(g); + } else { + if (receiver && g->state() == Qt::GestureStarted) { + // someone accepted the propagated initial GestureStarted + // event, let it be the new target for all following events. + gestureTargets[g] = receiver.data(); + } + undeliveredGestures.remove(g); + } + } + if (undeliveredGestures.isEmpty()) + break; + + // ignoredGestures list is only filled when delivering to the gesture + // target item, so it is safe to assume item == target. + if (!ignoredGestures.isEmpty() && !isPanel) { + // look for new potential targets for gestures that were ignored + // and should be propagated. + + QSet targetsSet = cachedTargetItems.toSet(); + + if (receiver) { + // first if the gesture should be propagated to parents only + for (QSet::iterator it = ignoredGestures.begin(); + it != ignoredGestures.end();) { + if (parentPropagatedGestures.contains(*it)) { + QGesture *gesture = *it; + const Qt::GestureType gestureType = gesture->gestureType(); + QGraphicsItem *item = receiver.data(); + while (item) { + if (QGraphicsObject *obj = item->toGraphicsObject()) { + if (item->d_func()->gestureContext.contains(gestureType)) { + targetsSet.insert(obj); + cachedItemGestures[obj].insert(gesture); + } + } + if (item->isPanel()) + break; + item = item->parentItem(); + } + + it = ignoredGestures.erase(it); + continue; + } + ++it; + } + } + + gestureTargetsAtHotSpots(ignoredGestures, Qt::ReceivePartialGestures, + &cachedItemGestures, &targetsSet, 0, 0); + + cachedTargetItems = targetsSet.toList(); + qSort(cachedTargetItems.begin(), cachedTargetItems.end(), qt_closestItemFirst); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "new targets:" << cachedTargetItems; + i = -1; // start delivery again + continue; + } + } + + foreach (QGesture *g, startedGestures) { + if (g->gestureCancelPolicy() == QGesture::CancelAllInContext) { + DEBUG() << "lets try to cancel some"; + // find gestures in context in Qt::GestureStarted or Qt::GestureUpdated state and cancel them + cancelGesturesForChildren(g); + } + } + + // forget about targets for gestures that have ended + foreach (QGesture *g, allGestures) { + switch (g->state()) { + case Qt::GestureFinished: + case Qt::GestureCanceled: + gestureTargets.remove(g); + break; + default: + break; + } + } + + cachedTargetItems.clear(); + cachedItemGestures.clear(); + cachedAlreadyDeliveredGestures.clear(); +} + +void QGraphicsScenePrivate::cancelGesturesForChildren(QGesture *original) +{ + Q_ASSERT(original); + QGraphicsItem *originalItem = gestureTargets.value(original); + if (originalItem == 0) // we only act on accepted gestures, which implies it has a target. + return; + + // iterate over all active gestures and for each find the owner + // if the owner is part of our sub-hierarchy, cancel it. + + QSet canceledGestures; + QHash::Iterator iter = gestureTargets.begin(); + while (iter != gestureTargets.end()) { + QGraphicsObject *item = iter.value(); + // note that we don't touch the gestures for our originalItem + if (item != originalItem && originalItem->isAncestorOf(item)) { + DEBUG() << " found a gesture to cancel" << iter.key(); + iter.key()->d_func()->state = Qt::GestureCanceled; + canceledGestures << iter.key(); + } + ++iter; + } + + // sort them per target item by cherry picking from almostCanceledGestures and delivering + QSet almostCanceledGestures = canceledGestures; + QSet::Iterator setIter; + while (!almostCanceledGestures.isEmpty()) { + QGraphicsObject *target = 0; + QSet gestures; + setIter = almostCanceledGestures.begin(); + // sort per target item + while (setIter != almostCanceledGestures.end()) { + QGraphicsObject *item = gestureTargets.value(*setIter); + if (target == 0) + target = item; + if (target == item) { + gestures << *setIter; + setIter = almostCanceledGestures.erase(setIter); + } else { + ++setIter; + } + } + Q_ASSERT(target); + + QList list = gestures.toList(); + QGestureEvent ev(list); + sendEvent(target, &ev); + + foreach (QGesture *g, list) { + if (ev.isAccepted() || ev.isAccepted(g)) + gestures.remove(g); + } + + foreach (QGesture *g, gestures) { + if (!g->hasHotSpot()) + continue; + + QList items = itemsAtPosition(QPoint(), g->d_func()->sceneHotSpot, 0); + for (int j = 0; j < items.size(); ++j) { + QGraphicsObject *item = items.at(j)->toGraphicsObject(); + if (!item) + continue; + QGraphicsItemPrivate *d = item->QGraphicsItem::d_func(); + if (d->gestureContext.contains(g->gestureType())) { + QList list; + list << g; + QGestureEvent ev(list); + sendEvent(item, &ev); + if (ev.isAccepted() || ev.isAccepted(g)) + break; // successfully delivered + } + } + } + } + + QGestureManager *gestureManager = QApplicationPrivate::instance()->gestureManager; + Q_ASSERT(gestureManager); // it would be very odd if we got called without a manager. + for (setIter = canceledGestures.begin(); setIter != canceledGestures.end(); ++setIter) { + gestureManager->recycle(*setIter); + gestureTargets.remove(*setIter); + } +} + +void QGraphicsScenePrivate::grabGesture(QGraphicsItem *, Qt::GestureType gesture) +{ + (void)QGestureManager::instance(); // create a gesture manager + if (!grabbedGestures[gesture]++) { + foreach (QGraphicsView *view, views) + view->viewport()->grabGesture(gesture); + } +} + +void QGraphicsScenePrivate::ungrabGesture(QGraphicsItem *item, Qt::GestureType gesture) +{ + // we know this can only be an object + Q_ASSERT(item->d_ptr->isObject); + QGraphicsObject *obj = static_cast(item); + QGestureManager::instance()->cleanupCachedGestures(obj, gesture); + if (!--grabbedGestures[gesture]) { + foreach (QGraphicsView *view, views) + view->viewport()->ungrabGesture(gesture); + } +} +#endif // QT_NO_GESTURES + +QT_END_NAMESPACE + +#include "moc_qgraphicsscene.cpp" + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicsscene.h b/src/widgets/graphicsview/qgraphicsscene.h new file mode 100644 index 0000000000..c17ab96074 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsscene.h @@ -0,0 +1,329 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSCENE_H +#define QGRAPHICSSCENE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +template class QList; +class QFocusEvent; +class QFont; +class QFontMetrics; +class QGraphicsEllipseItem; +class QGraphicsItem; +class QGraphicsItemGroup; +class QGraphicsLineItem; +class QGraphicsPathItem; +class QGraphicsPixmapItem; +class QGraphicsPolygonItem; +class QGraphicsProxyWidget; +class QGraphicsRectItem; +class QGraphicsSceneContextMenuEvent; +class QGraphicsSceneDragDropEvent; +class QGraphicsSceneEvent; +class QGraphicsSceneHelpEvent; +class QGraphicsSceneHoverEvent; +class QGraphicsSceneMouseEvent; +class QGraphicsSceneWheelEvent; +class QGraphicsSimpleTextItem; +class QGraphicsTextItem; +class QGraphicsView; +class QGraphicsWidget; +class QGraphicsSceneIndex; +class QHelpEvent; +class QInputMethodEvent; +class QKeyEvent; +class QLineF; +class QPainterPath; +class QPixmap; +class QPointF; +class QPolygonF; +class QRectF; +class QSizeF; +class QStyle; +class QStyleOptionGraphicsItem; + +class QGraphicsScenePrivate; +class Q_GUI_EXPORT QGraphicsScene : public QObject +{ + Q_OBJECT + Q_PROPERTY(QBrush backgroundBrush READ backgroundBrush WRITE setBackgroundBrush) + Q_PROPERTY(QBrush foregroundBrush READ foregroundBrush WRITE setForegroundBrush) + Q_PROPERTY(ItemIndexMethod itemIndexMethod READ itemIndexMethod WRITE setItemIndexMethod) + Q_PROPERTY(QRectF sceneRect READ sceneRect WRITE setSceneRect) + Q_PROPERTY(int bspTreeDepth READ bspTreeDepth WRITE setBspTreeDepth) + Q_PROPERTY(QPalette palette READ palette WRITE setPalette) + Q_PROPERTY(QFont font READ font WRITE setFont) + Q_PROPERTY(bool sortCacheEnabled READ isSortCacheEnabled WRITE setSortCacheEnabled) + Q_PROPERTY(bool stickyFocus READ stickyFocus WRITE setStickyFocus) + +public: + enum ItemIndexMethod { + BspTreeIndex, + NoIndex = -1 + }; + + enum SceneLayer { + ItemLayer = 0x1, + BackgroundLayer = 0x2, + ForegroundLayer = 0x4, + AllLayers = 0xffff + }; + Q_DECLARE_FLAGS(SceneLayers, SceneLayer) + + QGraphicsScene(QObject *parent = 0); + QGraphicsScene(const QRectF &sceneRect, QObject *parent = 0); + QGraphicsScene(qreal x, qreal y, qreal width, qreal height, QObject *parent = 0); + virtual ~QGraphicsScene(); + + QRectF sceneRect() const; + inline qreal width() const { return sceneRect().width(); } + inline qreal height() const { return sceneRect().height(); } + void setSceneRect(const QRectF &rect); + inline void setSceneRect(qreal x, qreal y, qreal w, qreal h) + { setSceneRect(QRectF(x, y, w, h)); } + + void render(QPainter *painter, + const QRectF &target = QRectF(), const QRectF &source = QRectF(), + Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio); + + ItemIndexMethod itemIndexMethod() const; + void setItemIndexMethod(ItemIndexMethod method); + + bool isSortCacheEnabled() const; + void setSortCacheEnabled(bool enabled); + + int bspTreeDepth() const; + void setBspTreeDepth(int depth); + + QRectF itemsBoundingRect() const; + + QList items() const; + QList items(Qt::SortOrder order) const; // ### Qt 5: unify + + QList items(const QPointF &pos, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + QList items(const QRectF &rect, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + QList items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + QList items(const QPainterPath &path, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + + QList items(const QPointF &pos) const; // ### obsolete + QList items(const QRectF &rect, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; // ### obsolete + QList items(const QPolygonF &polygon, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; // ### obsolete + QList items(const QPainterPath &path, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; // ### obsolete + + QList collidingItems(const QGraphicsItem *item, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + + QGraphicsItem *itemAt(const QPointF &pos) const; // ### obsolete + QGraphicsItem *itemAt(const QPointF &pos, const QTransform &deviceTransform) const; + + inline QList items(qreal x, qreal y, qreal w, qreal h, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const + { return items(QRectF(x, y, w, h), mode); } // ### obsolete + inline QList items(qreal x, qreal y, qreal w, qreal h, Qt::ItemSelectionMode mode, Qt::SortOrder order, + const QTransform &deviceTransform = QTransform()) const + { return items(QRectF(x, y, w, h), mode, order, deviceTransform); } + inline QGraphicsItem *itemAt(qreal x, qreal y) const // ### obsolete + { return itemAt(QPointF(x, y)); } + inline QGraphicsItem *itemAt(qreal x, qreal y, const QTransform &deviceTransform) const + { return itemAt(QPointF(x, y), deviceTransform); } + + QList selectedItems() const; + QPainterPath selectionArea() const; + void setSelectionArea(const QPainterPath &path); // ### obsolete + void setSelectionArea(const QPainterPath &path, const QTransform &deviceTransform); + void setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode); // ### obsolete + void setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode, const QTransform &deviceTransform); + + QGraphicsItemGroup *createItemGroup(const QList &items); + void destroyItemGroup(QGraphicsItemGroup *group); + + void addItem(QGraphicsItem *item); + QGraphicsEllipseItem *addEllipse(const QRectF &rect, const QPen &pen = QPen(), const QBrush &brush = QBrush()); + QGraphicsLineItem *addLine(const QLineF &line, const QPen &pen = QPen()); + QGraphicsPathItem *addPath(const QPainterPath &path, const QPen &pen = QPen(), const QBrush &brush = QBrush()); + QGraphicsPixmapItem *addPixmap(const QPixmap &pixmap); + QGraphicsPolygonItem *addPolygon(const QPolygonF &polygon, const QPen &pen = QPen(), const QBrush &brush = QBrush()); + QGraphicsRectItem *addRect(const QRectF &rect, const QPen &pen = QPen(), const QBrush &brush = QBrush()); + QGraphicsTextItem *addText(const QString &text, const QFont &font = QFont()); + QGraphicsSimpleTextItem *addSimpleText(const QString &text, const QFont &font = QFont()); + QGraphicsProxyWidget *addWidget(QWidget *widget, Qt::WindowFlags wFlags = 0); + inline QGraphicsEllipseItem *addEllipse(qreal x, qreal y, qreal w, qreal h, const QPen &pen = QPen(), const QBrush &brush = QBrush()) + { return addEllipse(QRectF(x, y, w, h), pen, brush); } + inline QGraphicsLineItem *addLine(qreal x1, qreal y1, qreal x2, qreal y2, const QPen &pen = QPen()) + { return addLine(QLineF(x1, y1, x2, y2), pen); } + inline QGraphicsRectItem *addRect(qreal x, qreal y, qreal w, qreal h, const QPen &pen = QPen(), const QBrush &brush = QBrush()) + { return addRect(QRectF(x, y, w, h), pen, brush); } + void removeItem(QGraphicsItem *item); + + QGraphicsItem *focusItem() const; + void setFocusItem(QGraphicsItem *item, Qt::FocusReason focusReason = Qt::OtherFocusReason); + bool hasFocus() const; + void setFocus(Qt::FocusReason focusReason = Qt::OtherFocusReason); + void clearFocus(); + + void setStickyFocus(bool enabled); + bool stickyFocus() const; + + QGraphicsItem *mouseGrabberItem() const; + + QBrush backgroundBrush() const; + void setBackgroundBrush(const QBrush &brush); + + QBrush foregroundBrush() const; + void setForegroundBrush(const QBrush &brush); + + virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + + QList views() const; + + inline void update(qreal x, qreal y, qreal w, qreal h) + { update(QRectF(x, y, w, h)); } + inline void invalidate(qreal x, qreal y, qreal w, qreal h, SceneLayers layers = AllLayers) + { invalidate(QRectF(x, y, w, h), layers); } + + QStyle *style() const; + void setStyle(QStyle *style); + + QFont font() const; + void setFont(const QFont &font); + + QPalette palette() const; + void setPalette(const QPalette &palette); + + bool isActive() const; + QGraphicsItem *activePanel() const; + void setActivePanel(QGraphicsItem *item); + QGraphicsWidget *activeWindow() const; + void setActiveWindow(QGraphicsWidget *widget); + + bool sendEvent(QGraphicsItem *item, QEvent *event); + +public Q_SLOTS: + void update(const QRectF &rect = QRectF()); + void invalidate(const QRectF &rect = QRectF(), SceneLayers layers = AllLayers); + void advance(); + void clearSelection(); + void clear(); + +protected: + bool event(QEvent *event); + bool eventFilter(QObject *watched, QEvent *event); + virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); + virtual void dragEnterEvent(QGraphicsSceneDragDropEvent *event); + virtual void dragMoveEvent(QGraphicsSceneDragDropEvent *event); + virtual void dragLeaveEvent(QGraphicsSceneDragDropEvent *event); + virtual void dropEvent(QGraphicsSceneDragDropEvent *event); + virtual void focusInEvent(QFocusEvent *event); + virtual void focusOutEvent(QFocusEvent *event); + virtual void helpEvent(QGraphicsSceneHelpEvent *event); + virtual void keyPressEvent(QKeyEvent *event); + virtual void keyReleaseEvent(QKeyEvent *event); + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + virtual void wheelEvent(QGraphicsSceneWheelEvent *event); + virtual void inputMethodEvent(QInputMethodEvent *event); + + virtual void drawBackground(QPainter *painter, const QRectF &rect); + virtual void drawForeground(QPainter *painter, const QRectF &rect); + virtual void drawItems(QPainter *painter, int numItems, + QGraphicsItem *items[], + const QStyleOptionGraphicsItem options[], + QWidget *widget = 0); + +protected Q_SLOTS: + bool focusNextPrevChild(bool next); + +Q_SIGNALS: + void changed(const QList ®ion); + void sceneRectChanged(const QRectF &rect); + void selectionChanged(); + +private: + Q_DECLARE_PRIVATE(QGraphicsScene) + Q_DISABLE_COPY(QGraphicsScene) + Q_PRIVATE_SLOT(d_func(), void _q_emitUpdated()) + Q_PRIVATE_SLOT(d_func(), void _q_polishItems()) + Q_PRIVATE_SLOT(d_func(), void _q_processDirtyItems()) + Q_PRIVATE_SLOT(d_func(), void _q_updateScenePosDescendants()) + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; + friend class QGraphicsObject; + friend class QGraphicsView; + friend class QGraphicsViewPrivate; + friend class QGraphicsWidget; + friend class QGraphicsWidgetPrivate; + friend class QGraphicsEffect; + friend class QGraphicsSceneIndex; + friend class QGraphicsSceneIndexPrivate; + friend class QGraphicsSceneBspTreeIndex; + friend class QGraphicsSceneBspTreeIndexPrivate; + friend class QGraphicsItemEffectSourcePrivate; +#ifndef QT_NO_GESTURES + friend class QGesture; +#endif +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGraphicsScene::SceneLayers) + +#endif // QT_NO_GRAPHICSVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/widgets/graphicsview/qgraphicsscene_bsp.cpp b/src/widgets/graphicsview/qgraphicsscene_bsp.cpp new file mode 100644 index 0000000000..3716debcc4 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsscene_bsp.cpp @@ -0,0 +1,296 @@ +/**************************************************************************** +** +** 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 QtGui module 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 "qgraphicsscene_bsp_p.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsSceneInsertItemBspTreeVisitor : public QGraphicsSceneBspTreeVisitor +{ +public: + QGraphicsItem *item; + + void visit(QList *items) + { items->prepend(item); } +}; + +class QGraphicsSceneRemoveItemBspTreeVisitor : public QGraphicsSceneBspTreeVisitor +{ +public: + QGraphicsItem *item; + + void visit(QList *items) + { items->removeAll(item); } +}; + +class QGraphicsSceneFindItemBspTreeVisitor : public QGraphicsSceneBspTreeVisitor +{ +public: + QList *foundItems; + bool onlyTopLevelItems; + + void visit(QList *items) + { + for (int i = 0; i < items->size(); ++i) { + QGraphicsItem *item = items->at(i); + if (onlyTopLevelItems && item->d_ptr->parent) + item = item->topLevelItem(); + if (!item->d_func()->itemDiscovered && item->d_ptr->visible) { + item->d_func()->itemDiscovered = 1; + foundItems->prepend(item); + } + } + } +}; + +QGraphicsSceneBspTree::QGraphicsSceneBspTree() + : leafCnt(0) +{ + insertVisitor = new QGraphicsSceneInsertItemBspTreeVisitor; + removeVisitor = new QGraphicsSceneRemoveItemBspTreeVisitor; + findVisitor = new QGraphicsSceneFindItemBspTreeVisitor; +} + +QGraphicsSceneBspTree::~QGraphicsSceneBspTree() +{ + delete insertVisitor; + delete removeVisitor; + delete findVisitor; +} + +void QGraphicsSceneBspTree::initialize(const QRectF &rect, int depth) +{ + this->rect = rect; + leafCnt = 0; + nodes.resize((1 << (depth + 1)) - 1); + nodes.fill(Node()); + leaves.resize(1 << depth); + leaves.fill(QList()); + + initialize(rect, depth, 0); +} + +void QGraphicsSceneBspTree::clear() +{ + leafCnt = 0; + nodes.clear(); + leaves.clear(); +} + +void QGraphicsSceneBspTree::insertItem(QGraphicsItem *item, const QRectF &rect) +{ + insertVisitor->item = item; + climbTree(insertVisitor, rect); +} + +void QGraphicsSceneBspTree::removeItem(QGraphicsItem *item, const QRectF &rect) +{ + removeVisitor->item = item; + climbTree(removeVisitor, rect); +} + +void QGraphicsSceneBspTree::removeItems(const QSet &items) +{ + for (int i = 0; i < leaves.size(); ++i) { + QList newItemList; + const QList &oldItemList = leaves[i]; + for (int j = 0; j < oldItemList.size(); ++j) { + QGraphicsItem *item = oldItemList.at(j); + if (!items.contains(item)) + newItemList << item; + } + leaves[i] = newItemList; + } +} + +QList QGraphicsSceneBspTree::items(const QRectF &rect, bool onlyTopLevelItems) const +{ + QList tmp; + findVisitor->foundItems = &tmp; + findVisitor->onlyTopLevelItems = onlyTopLevelItems; + climbTree(findVisitor, rect); + // Reset discovery bits. + for (int i = 0; i < tmp.size(); ++i) + tmp.at(i)->d_ptr->itemDiscovered = 0; + return tmp; +} + +int QGraphicsSceneBspTree::leafCount() const +{ + return leafCnt; +} + +QString QGraphicsSceneBspTree::debug(int index) const +{ + const Node *node = &nodes.at(index); + + QString tmp; + if (node->type == Node::Leaf) { + QRectF rect = rectForIndex(index); + if (!leaves[node->leafIndex].isEmpty()) { + tmp += QString::fromLatin1("[%1, %2, %3, %4] contains %5 items\n") + .arg(rect.left()).arg(rect.top()) + .arg(rect.width()).arg(rect.height()) + .arg(leaves[node->leafIndex].size()); + } + } else { + if (node->type == Node::Horizontal) { + tmp += debug(firstChildIndex(index)); + tmp += debug(firstChildIndex(index) + 1); + } else { + tmp += debug(firstChildIndex(index)); + tmp += debug(firstChildIndex(index) + 1); + } + } + + return tmp; +} + +void QGraphicsSceneBspTree::initialize(const QRectF &rect, int depth, int index) +{ + Node *node = &nodes[index]; + if (index == 0) { + node->type = Node::Horizontal; + node->offset = rect.center().x(); + } + + if (depth) { + Node::Type type; + QRectF rect1, rect2; + qreal offset1, offset2; + + if (node->type == Node::Horizontal) { + type = Node::Vertical; + rect1.setRect(rect.left(), rect.top(), rect.width(), rect.height() / 2); + rect2.setRect(rect1.left(), rect1.bottom(), rect1.width(), rect.height() - rect1.height()); + offset1 = rect1.center().x(); + offset2 = rect2.center().x(); + } else { + type = Node::Horizontal; + rect1.setRect(rect.left(), rect.top(), rect.width() / 2, rect.height()); + rect2.setRect(rect1.right(), rect1.top(), rect.width() - rect1.width(), rect1.height()); + offset1 = rect1.center().y(); + offset2 = rect2.center().y(); + } + + int childIndex = firstChildIndex(index); + + Node *child = &nodes[childIndex]; + child->offset = offset1; + child->type = type; + + child = &nodes[childIndex + 1]; + child->offset = offset2; + child->type = type; + + initialize(rect1, depth - 1, childIndex); + initialize(rect2, depth - 1, childIndex + 1); + } else { + node->type = Node::Leaf; + node->leafIndex = leafCnt++; + } +} + +void QGraphicsSceneBspTree::climbTree(QGraphicsSceneBspTreeVisitor *visitor, const QRectF &rect, int index) const +{ + if (nodes.isEmpty()) + return; + + const Node &node = nodes.at(index); + const int childIndex = firstChildIndex(index); + + switch (node.type) { + case Node::Leaf: { + visitor->visit(const_cast*>(&leaves[node.leafIndex])); + break; + } + case Node::Vertical: + if (rect.left() < node.offset) { + climbTree(visitor, rect, childIndex); + if (rect.right() >= node.offset) + climbTree(visitor, rect, childIndex + 1); + } else { + climbTree(visitor, rect, childIndex + 1); + } + break; + case Node::Horizontal: + if (rect.top() < node.offset) { + climbTree(visitor, rect, childIndex); + if (rect.bottom() >= node.offset) + climbTree(visitor, rect, childIndex + 1); + } else { + climbTree(visitor, rect, childIndex + 1); + } + } +} + +QRectF QGraphicsSceneBspTree::rectForIndex(int index) const +{ + if (index <= 0) + return rect; + + int parentIdx = parentIndex(index); + QRectF rect = rectForIndex(parentIdx); + const Node *parent = &nodes.at(parentIdx); + + if (parent->type == Node::Horizontal) { + if (index & 1) + rect.setRight(parent->offset); + else + rect.setLeft(parent->offset); + } else { + if (index & 1) + rect.setBottom(parent->offset); + else + rect.setTop(parent->offset); + } + + return rect; +} + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicsscene_bsp_p.h b/src/widgets/graphicsview/qgraphicsscene_bsp_p.h new file mode 100644 index 0000000000..4f230eff79 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsscene_bsp_p.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSCENEBSPTREE_P_H +#define QGRAPHICSSCENEBSPTREE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsItem; +class QGraphicsSceneBspTreeVisitor; +class QGraphicsSceneInsertItemBspTreeVisitor; +class QGraphicsSceneRemoveItemBspTreeVisitor; +class QGraphicsSceneFindItemBspTreeVisitor; + +class QGraphicsSceneBspTree +{ +public: + struct Node + { + enum Type { Horizontal, Vertical, Leaf }; + union { + qreal offset; + int leafIndex; + }; + Type type; + }; + + QGraphicsSceneBspTree(); + ~QGraphicsSceneBspTree(); + + void initialize(const QRectF &rect, int depth); + void clear(); + + void insertItem(QGraphicsItem *item, const QRectF &rect); + void removeItem(QGraphicsItem *item, const QRectF &rect); + void removeItems(const QSet &items); + + QList items(const QRectF &rect, bool onlyTopLevelItems = false) const; + int leafCount() const; + + inline int firstChildIndex(int index) const + { return index * 2 + 1; } + + inline int parentIndex(int index) const + { return index > 0 ? ((index & 1) ? ((index - 1) / 2) : ((index - 2) / 2)) : -1; } + + QString debug(int index) const; + +private: + void initialize(const QRectF &rect, int depth, int index); + void climbTree(QGraphicsSceneBspTreeVisitor *visitor, const QRectF &rect, int index = 0) const; + QRectF rectForIndex(int index) const; + + QVector nodes; + QVector > leaves; + int leafCnt; + QRectF rect; + + QGraphicsSceneInsertItemBspTreeVisitor *insertVisitor; + QGraphicsSceneRemoveItemBspTreeVisitor *removeVisitor; + QGraphicsSceneFindItemBspTreeVisitor *findVisitor; +}; + +class QGraphicsSceneBspTreeVisitor +{ +public: + virtual ~QGraphicsSceneBspTreeVisitor() { } + virtual void visit(QList *items) = 0; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW + +#endif // QGRAPHICSSCENEBSPTREE_P_H diff --git a/src/widgets/graphicsview/qgraphicsscene_p.h b/src/widgets/graphicsview/qgraphicsscene_p.h new file mode 100644 index 0000000000..9460a4dc51 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsscene_p.h @@ -0,0 +1,359 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSCENE_P_H +#define QGRAPHICSSCENE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicsscene.h" + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +#include "qgraphicssceneevent.h" +#include "qgraphicsview.h" +#include "qgraphicsview_p.h" +#include "qgraphicsitem_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsSceneIndex; +class QGraphicsView; +class QGraphicsWidget; + +class Q_AUTOTEST_EXPORT QGraphicsScenePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsScene) +public: + QGraphicsScenePrivate(); + void init(); + + static QGraphicsScenePrivate *get(QGraphicsScene *q); + + int changedSignalIndex; + int processDirtyItemsIndex; + int polishItemsIndex; + + QGraphicsScene::ItemIndexMethod indexMethod; + QGraphicsSceneIndex *index; + + int lastItemCount; + + QRectF sceneRect; + + quint32 hasSceneRect : 1; + quint32 dirtyGrowingItemsBoundingRect : 1; + quint32 updateAll : 1; + quint32 calledEmitUpdated : 1; + quint32 processDirtyItemsEmitted : 1; + quint32 needSortTopLevelItems : 1; + quint32 holesInTopLevelSiblingIndex : 1; + quint32 topLevelSequentialOrdering : 1; + quint32 scenePosDescendantsUpdatePending : 1; + quint32 stickyFocus : 1; + quint32 hasFocus : 1; + quint32 lastMouseGrabberItemHasImplicitMouseGrab : 1; + quint32 allItemsIgnoreHoverEvents : 1; + quint32 allItemsUseDefaultCursor : 1; + quint32 painterStateProtection : 1; + quint32 sortCacheEnabled : 1; // for compatibility + quint32 allItemsIgnoreTouchEvents : 1; + quint32 padding : 15; + + QRectF growingItemsBoundingRect; + + void _q_emitUpdated(); + QList updatedRects; + + QPainterPath selectionArea; + int selectionChanging; + QSet selectedItems; + QVector unpolishedItems; + QList topLevelItems; + + QMap movingItemsInitialPositions; + void registerTopLevelItem(QGraphicsItem *item); + void unregisterTopLevelItem(QGraphicsItem *item); + void _q_updateLater(); + void _q_polishItems(); + + void _q_processDirtyItems(); + + QSet scenePosItems; + void setScenePosItemEnabled(QGraphicsItem *item, bool enabled); + void registerScenePosItem(QGraphicsItem *item); + void unregisterScenePosItem(QGraphicsItem *item); + void _q_updateScenePosDescendants(); + + void removeItemHelper(QGraphicsItem *item); + + QBrush backgroundBrush; + QBrush foregroundBrush; + + quint32 rectAdjust; + QGraphicsItem *focusItem; + QGraphicsItem *lastFocusItem; + QGraphicsItem *passiveFocusItem; + QGraphicsWidget *tabFocusFirst; + QGraphicsItem *activePanel; + QGraphicsItem *lastActivePanel; + int activationRefCount; + int childExplicitActivation; + void setActivePanelHelper(QGraphicsItem *item, bool duringActivationEvent); + void setFocusItemHelper(QGraphicsItem *item, Qt::FocusReason focusReason); + + QList popupWidgets; + void addPopup(QGraphicsWidget *widget); + void removePopup(QGraphicsWidget *widget, bool itemIsDying = false); + + QGraphicsItem *lastMouseGrabberItem; + QList mouseGrabberItems; + void grabMouse(QGraphicsItem *item, bool implicit = false); + void ungrabMouse(QGraphicsItem *item, bool itemIsDying = false); + void clearMouseGrabber(); + + QList keyboardGrabberItems; + void grabKeyboard(QGraphicsItem *item); + void ungrabKeyboard(QGraphicsItem *item, bool itemIsDying = false); + void clearKeyboardGrabber(); + + QGraphicsItem *dragDropItem; + QGraphicsWidget *enterWidget; + Qt::DropAction lastDropAction; + QList cachedItemsUnderMouse; + QList hoverItems; + QPointF lastSceneMousePos; + void enableMouseTrackingOnViews(); + QMap mouseGrabberButtonDownPos; + QMap mouseGrabberButtonDownScenePos; + QMap mouseGrabberButtonDownScreenPos; + QList itemsAtPosition(const QPoint &screenPos, + const QPointF &scenePos, + QWidget *widget) const; + void storeMouseButtonsForMouseGrabber(QGraphicsSceneMouseEvent *event); + + QList views; + void addView(QGraphicsView *view); + void removeView(QGraphicsView *view); + + QMultiMap sceneEventFilters; + void installSceneEventFilter(QGraphicsItem *watched, QGraphicsItem *filter); + void removeSceneEventFilter(QGraphicsItem *watched, QGraphicsItem *filter); + bool filterDescendantEvent(QGraphicsItem *item, QEvent *event); + bool filterEvent(QGraphicsItem *item, QEvent *event); + bool sendEvent(QGraphicsItem *item, QEvent *event); + + bool dispatchHoverEvent(QGraphicsSceneHoverEvent *hoverEvent); + bool itemAcceptsHoverEvents_helper(const QGraphicsItem *item) const; + void leaveScene(QWidget *viewport); + + void cloneDragDropEvent(QGraphicsSceneDragDropEvent *dest, + QGraphicsSceneDragDropEvent *source); + void sendDragDropEvent(QGraphicsItem *item, + QGraphicsSceneDragDropEvent *dragDropEvent); + void sendHoverEvent(QEvent::Type type, QGraphicsItem *item, + QGraphicsSceneHoverEvent *hoverEvent); + void sendMouseEvent(QGraphicsSceneMouseEvent *mouseEvent); + void mousePressEventHandler(QGraphicsSceneMouseEvent *mouseEvent); + QGraphicsWidget *windowForItem(const QGraphicsItem *item) const; + + void drawItemHelper(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget, + bool painterStateProtection); + + void drawItems(QPainter *painter, const QTransform *const viewTransform, + QRegion *exposedRegion, QWidget *widget); + + void drawSubtreeRecursive(QGraphicsItem *item, QPainter *painter, const QTransform *const, + QRegion *exposedRegion, QWidget *widget, qreal parentOpacity = qreal(1.0), + const QTransform *const effectTransform = 0); + void draw(QGraphicsItem *, QPainter *, const QTransform *const, const QTransform *const, + QRegion *, QWidget *, qreal, const QTransform *const, bool, bool); + + void markDirty(QGraphicsItem *item, const QRectF &rect = QRectF(), bool invalidateChildren = false, + bool force = false, bool ignoreOpacity = false, bool removingItemFromScene = false, + bool updateBoundingRect = false); + void processDirtyItemsRecursive(QGraphicsItem *item, bool dirtyAncestorContainsChildren = false, + qreal parentOpacity = qreal(1.0)); + + inline void resetDirtyItem(QGraphicsItem *item, bool recursive = false) + { + Q_ASSERT(item); + item->d_ptr->dirty = 0; + item->d_ptr->paintedViewBoundingRectsNeedRepaint = 0; + item->d_ptr->geometryChanged = 0; + if (!item->d_ptr->dirtyChildren) + recursive = false; + item->d_ptr->dirtyChildren = 0; + item->d_ptr->needsRepaint = QRectF(); + item->d_ptr->allChildrenDirty = 0; + item->d_ptr->fullUpdatePending = 0; + item->d_ptr->ignoreVisible = 0; + item->d_ptr->ignoreOpacity = 0; +#ifndef QT_NO_GRAPHICSEFFECT + QGraphicsEffect::ChangeFlags flags; + if (item->d_ptr->notifyBoundingRectChanged) { + flags |= QGraphicsEffect::SourceBoundingRectChanged; + item->d_ptr->notifyBoundingRectChanged = 0; + } + if (item->d_ptr->notifyInvalidated) { + flags |= QGraphicsEffect::SourceInvalidated; + item->d_ptr->notifyInvalidated = 0; + } +#endif //QT_NO_GRAPHICSEFFECT + if (recursive) { + for (int i = 0; i < item->d_ptr->children.size(); ++i) + resetDirtyItem(item->d_ptr->children.at(i), recursive); + } +#ifndef QT_NO_GRAPHICSEFFECT + if (flags && item->d_ptr->graphicsEffect) + item->d_ptr->graphicsEffect->sourceChanged(flags); +#endif //QT_NO_GRAPHICSEFFECT + } + + inline void ensureSortedTopLevelItems() + { + if (needSortTopLevelItems) { + qSort(topLevelItems.begin(), topLevelItems.end(), qt_notclosestLeaf); + topLevelSequentialOrdering = false; + needSortTopLevelItems = false; + } + } + + void ensureSequentialTopLevelSiblingIndexes(); + + QStyle *style; + QFont font; + void setFont_helper(const QFont &font); + void resolveFont(); + void updateFont(const QFont &font); + QPalette palette; + void setPalette_helper(const QPalette &palette); + void resolvePalette(); + void updatePalette(const QPalette &palette); + + QStyleOptionGraphicsItem styleOptionTmp; + + QMap sceneCurrentTouchPoints; + QMap itemForTouchPointId; + static void updateTouchPointsForItem(QGraphicsItem *item, QTouchEvent *touchEvent); + int findClosestTouchPointId(const QPointF &scenePos); + void touchEventHandler(QTouchEvent *touchEvent); + bool sendTouchBeginEvent(QGraphicsItem *item, QTouchEvent *touchEvent); + void enableTouchEventsOnViews(); + + QList cachedTargetItems; +#ifndef QT_NO_GESTURES + QHash > cachedItemGestures; + QHash > cachedAlreadyDeliveredGestures; + QHash gestureTargets; + QHash grabbedGestures; + void gestureEventHandler(QGestureEvent *event); + void gestureTargetsAtHotSpots(const QSet &gestures, + Qt::GestureFlag flag, + QHash > *targets, + QSet *itemsSet = 0, + QSet *normal = 0, + QSet *conflicts = 0); + void cancelGesturesForChildren(QGesture *original); + void grabGesture(QGraphicsItem *, Qt::GestureType gesture); + void ungrabGesture(QGraphicsItem *, Qt::GestureType gesture); +#endif // QT_NO_GESTURES + + void updateInputMethodSensitivityInViews(); + + QList modalPanels; + void enterModal(QGraphicsItem *item, + QGraphicsItem::PanelModality panelModality = QGraphicsItem::NonModal); + void leaveModal(QGraphicsItem *item); +}; + +// QRectF::intersects() returns false always if either the source or target +// rectangle's width or height are 0. This works around that problem. +static inline void _q_adjustRect(QRectF *rect) +{ + Q_ASSERT(rect); + if (!rect->width()) + rect->adjust(qreal(-0.00001), 0, qreal(0.00001), 0); + if (!rect->height()) + rect->adjust(0, qreal(-0.00001), 0, qreal(0.00001)); +} + +static inline QRectF adjustedItemBoundingRect(const QGraphicsItem *item) +{ + Q_ASSERT(item); + QRectF boundingRect(item->boundingRect()); + _q_adjustRect(&boundingRect); + return boundingRect; +} + +static inline QRectF adjustedItemEffectiveBoundingRect(const QGraphicsItem *item) +{ + Q_ASSERT(item); + QRectF boundingRect(QGraphicsItemPrivate::get(item)->effectiveBoundingRect()); + _q_adjustRect(&boundingRect); + return boundingRect; +} + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW + +#endif diff --git a/src/widgets/graphicsview/qgraphicsscenebsptreeindex.cpp b/src/widgets/graphicsview/qgraphicsscenebsptreeindex.cpp new file mode 100644 index 0000000000..92e4a55952 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsscenebsptreeindex.cpp @@ -0,0 +1,719 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +/*! + \class QGraphicsSceneBspTreeIndex + \brief The QGraphicsSceneBspTreeIndex class provides an implementation of + a BSP indexing algorithm for discovering items in QGraphicsScene. + \since 4.6 + \ingroup graphicsview-api + + \internal + + QGraphicsSceneBspTreeIndex index use a BSP(Binary Space Partitioning) + implementation to discover items quickly. This implementation is + very efficient for static scene. It has a depth that you can set. + The depth directly affects performance and memory usage; the latter + growing exponentially with the depth of the tree. With an optimal tree + depth, the index can instantly determine the locality of items, even + for scenes with thousands or millions of items. This also greatly improves + rendering performance. + + By default, the value is 0, in which case Qt will guess a reasonable + default depth based on the size, location and number of items in the + scene. If these parameters change frequently, however, you may experience + slowdowns as the index retunes the depth internally. You can avoid + potential slowdowns by fixating the tree depth through setting this + property. + + The depth of the tree and the size of the scene rectangle decide the + granularity of the scene's partitioning. The size of each scene segment is + determined by the following algorithm: + + The BSP tree has an optimal size when each segment contains between 0 and + 10 items. + + \sa QGraphicsScene, QGraphicsView, QGraphicsSceneIndex +*/ + +#include + +#ifndef QT_NO_GRAPHICSVIEW + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +static inline int intmaxlog(int n) +{ + return (n > 0 ? qMax(qCeil(qLn(qreal(n)) / qLn(qreal(2))), 5) : 0); +} + +/*! + Constructs a private scene bsp index. +*/ +QGraphicsSceneBspTreeIndexPrivate::QGraphicsSceneBspTreeIndexPrivate(QGraphicsScene *scene) + : QGraphicsSceneIndexPrivate(scene), + bspTreeDepth(0), + indexTimerId(0), + restartIndexTimer(false), + regenerateIndex(true), + lastItemCount(0), + purgePending(false), + sortCacheEnabled(false), + updatingSortCache(false) +{ +} + + +/*! + This method will update the BSP index by removing the items from the temporary + unindexed list and add them in the indexedItems list. This will also + update the growingItemsBoundingRect if needed. This will update the BSP + implementation as well. + + \internal +*/ +void QGraphicsSceneBspTreeIndexPrivate::_q_updateIndex() +{ + Q_Q(QGraphicsSceneBspTreeIndex); + if (!indexTimerId) + return; + + q->killTimer(indexTimerId); + indexTimerId = 0; + + purgeRemovedItems(); + + // Add unindexedItems to indexedItems + for (int i = 0; i < unindexedItems.size(); ++i) { + if (QGraphicsItem *item = unindexedItems.at(i)) { + Q_ASSERT(!item->d_ptr->itemDiscovered); + if (!freeItemIndexes.isEmpty()) { + int freeIndex = freeItemIndexes.takeFirst(); + item->d_func()->index = freeIndex; + indexedItems[freeIndex] = item; + } else { + item->d_func()->index = indexedItems.size(); + indexedItems << item; + } + } + } + + // Determine whether we should regenerate the BSP tree. + if (bspTreeDepth == 0) { + int oldDepth = intmaxlog(lastItemCount); + bspTreeDepth = intmaxlog(indexedItems.size()); + static const int slack = 100; + if (bsp.leafCount() == 0 || (oldDepth != bspTreeDepth && qAbs(lastItemCount - indexedItems.size()) > slack)) { + // ### Crude algorithm. + regenerateIndex = true; + } + } + + // Regenerate the tree. + if (regenerateIndex) { + regenerateIndex = false; + bsp.initialize(sceneRect, bspTreeDepth); + unindexedItems = indexedItems; + lastItemCount = indexedItems.size(); + } + + // Insert all unindexed items into the tree. + for (int i = 0; i < unindexedItems.size(); ++i) { + if (QGraphicsItem *item = unindexedItems.at(i)) { + if (item->d_ptr->itemIsUntransformable()) { + untransformableItems << item; + continue; + } + if (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) + continue; + + bsp.insertItem(item, item->d_ptr->sceneEffectiveBoundingRect()); + } + } + unindexedItems.clear(); +} + + +/*! + \internal + + Removes stale pointers from all data structures. +*/ +void QGraphicsSceneBspTreeIndexPrivate::purgeRemovedItems() +{ + if (!purgePending && removedItems.isEmpty()) + return; + + // Remove stale items from the BSP tree. + bsp.removeItems(removedItems); + // Purge this list. + removedItems.clear(); + freeItemIndexes.clear(); + for (int i = 0; i < indexedItems.size(); ++i) { + if (!indexedItems.at(i)) + freeItemIndexes << i; + } + purgePending = false; +} + +/*! + \internal + + Starts or restarts the timer used for reindexing unindexed items. +*/ +void QGraphicsSceneBspTreeIndexPrivate::startIndexTimer(int interval) +{ + Q_Q(QGraphicsSceneBspTreeIndex); + if (indexTimerId) { + restartIndexTimer = true; + } else { + indexTimerId = q->startTimer(interval); + } +} + +/*! + \internal +*/ +void QGraphicsSceneBspTreeIndexPrivate::resetIndex() +{ + purgeRemovedItems(); + for (int i = 0; i < indexedItems.size(); ++i) { + if (QGraphicsItem *item = indexedItems.at(i)) { + item->d_ptr->index = -1; + Q_ASSERT(!item->d_ptr->itemDiscovered); + unindexedItems << item; + } + } + indexedItems.clear(); + freeItemIndexes.clear(); + untransformableItems.clear(); + regenerateIndex = true; + startIndexTimer(); +} + +/*! + \internal +*/ +void QGraphicsSceneBspTreeIndexPrivate::climbTree(QGraphicsItem *item, int *stackingOrder) +{ + if (!item->d_ptr->children.isEmpty()) { + QList childList = item->d_ptr->children; + qSort(childList.begin(), childList.end(), qt_closestLeaf); + for (int i = 0; i < childList.size(); ++i) { + QGraphicsItem *item = childList.at(i); + if (!(item->flags() & QGraphicsItem::ItemStacksBehindParent)) + climbTree(childList.at(i), stackingOrder); + } + item->d_ptr->globalStackingOrder = (*stackingOrder)++; + for (int i = 0; i < childList.size(); ++i) { + QGraphicsItem *item = childList.at(i); + if (item->flags() & QGraphicsItem::ItemStacksBehindParent) + climbTree(childList.at(i), stackingOrder); + } + } else { + item->d_ptr->globalStackingOrder = (*stackingOrder)++; + } +} + +/*! + \internal +*/ +void QGraphicsSceneBspTreeIndexPrivate::_q_updateSortCache() +{ + Q_Q(QGraphicsSceneBspTreeIndex); + _q_updateIndex(); + + if (!sortCacheEnabled || !updatingSortCache) + return; + + updatingSortCache = false; + int stackingOrder = 0; + + QList topLevels; + const QList items = q->items(); + for (int i = 0; i < items.size(); ++i) { + QGraphicsItem *item = items.at(i); + if (item && !item->d_ptr->parent) + topLevels << item; + } + + qSort(topLevels.begin(), topLevels.end(), qt_closestLeaf); + for (int i = 0; i < topLevels.size(); ++i) + climbTree(topLevels.at(i), &stackingOrder); +} + +/*! + \internal +*/ +void QGraphicsSceneBspTreeIndexPrivate::invalidateSortCache() +{ + Q_Q(QGraphicsSceneBspTreeIndex); + if (!sortCacheEnabled || updatingSortCache) + return; + + updatingSortCache = true; + QMetaObject::invokeMethod(q, "_q_updateSortCache", Qt::QueuedConnection); +} + +void QGraphicsSceneBspTreeIndexPrivate::addItem(QGraphicsItem *item, bool recursive) +{ + if (!item) + return; + + // Prevent reusing a recently deleted pointer: purge all removed item from our lists. + purgeRemovedItems(); + + // Invalidate any sort caching; arrival of a new item means we need to resort. + // Update the scene's sort cache settings. + item->d_ptr->globalStackingOrder = -1; + invalidateSortCache(); + + // Indexing requires sceneBoundingRect(), but because \a item might + // not be completely constructed at this point, we need to store it in + // a temporary list and schedule an indexing for later. + if (item->d_ptr->index == -1) { + Q_ASSERT(!unindexedItems.contains(item)); + unindexedItems << item; + startIndexTimer(0); + } else { + Q_ASSERT(indexedItems.contains(item)); + qWarning("QGraphicsSceneBspTreeIndex::addItem: item has already been added to this BSP"); + } + + if (recursive) { + for (int i = 0; i < item->d_ptr->children.size(); ++i) + addItem(item->d_ptr->children.at(i), recursive); + } +} + +void QGraphicsSceneBspTreeIndexPrivate::removeItem(QGraphicsItem *item, bool recursive, + bool moveToUnindexedItems) +{ + if (!item) + return; + + if (item->d_ptr->index != -1) { + Q_ASSERT(item->d_ptr->index < indexedItems.size()); + Q_ASSERT(indexedItems.at(item->d_ptr->index) == item); + Q_ASSERT(!item->d_ptr->itemDiscovered); + freeItemIndexes << item->d_ptr->index; + indexedItems[item->d_ptr->index] = 0; + item->d_ptr->index = -1; + + if (item->d_ptr->itemIsUntransformable()) { + untransformableItems.removeOne(item); + } else if (item->d_ptr->inDestructor) { + // Avoid virtual function calls from the destructor. + purgePending = true; + removedItems << item; + } else if (!(item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)) { + bsp.removeItem(item, item->d_ptr->sceneEffectiveBoundingRect()); + } + } else { + unindexedItems.removeOne(item); + } + invalidateSortCache(); // ### Only do this when removing from BSP? + + Q_ASSERT(item->d_ptr->index == -1); + Q_ASSERT(!indexedItems.contains(item)); + Q_ASSERT(!unindexedItems.contains(item)); + Q_ASSERT(!untransformableItems.contains(item)); + + if (moveToUnindexedItems) + addItem(item); + + if (recursive) { + for (int i = 0; i < item->d_ptr->children.size(); ++i) + removeItem(item->d_ptr->children.at(i), recursive, moveToUnindexedItems); + } +} + +QList QGraphicsSceneBspTreeIndexPrivate::estimateItems(const QRectF &rect, Qt::SortOrder order, + bool onlyTopLevelItems) +{ + Q_Q(QGraphicsSceneBspTreeIndex); + if (onlyTopLevelItems && rect.isNull()) + return q->QGraphicsSceneIndex::estimateTopLevelItems(rect, order); + + purgeRemovedItems(); + _q_updateSortCache(); + Q_ASSERT(unindexedItems.isEmpty()); + + QList rectItems = bsp.items(rect, onlyTopLevelItems); + if (onlyTopLevelItems) { + for (int i = 0; i < untransformableItems.size(); ++i) { + QGraphicsItem *item = untransformableItems.at(i); + if (!item->d_ptr->parent) { + rectItems << item; + } else { + item = item->topLevelItem(); + if (!rectItems.contains(item)) + rectItems << item; + } + } + } else { + rectItems += untransformableItems; + } + + sortItems(&rectItems, order, sortCacheEnabled, onlyTopLevelItems); + return rectItems; +} + +/*! + Sort a list of \a itemList in a specific \a order and use the cache if requested. + + \internal +*/ +void QGraphicsSceneBspTreeIndexPrivate::sortItems(QList *itemList, Qt::SortOrder order, + bool sortCacheEnabled, bool onlyTopLevelItems) +{ + if (order == Qt::SortOrder(-1)) + return; + + if (onlyTopLevelItems) { + if (order == Qt::DescendingOrder) + qSort(itemList->begin(), itemList->end(), qt_closestLeaf); + else if (order == Qt::AscendingOrder) + qSort(itemList->begin(), itemList->end(), qt_notclosestLeaf); + return; + } + + if (sortCacheEnabled) { + if (order == Qt::DescendingOrder) { + qSort(itemList->begin(), itemList->end(), closestItemFirst_withCache); + } else if (order == Qt::AscendingOrder) { + qSort(itemList->begin(), itemList->end(), closestItemLast_withCache); + } + } else { + if (order == Qt::DescendingOrder) { + qSort(itemList->begin(), itemList->end(), qt_closestItemFirst); + } else if (order == Qt::AscendingOrder) { + qSort(itemList->begin(), itemList->end(), qt_closestItemLast); + } + } +} + +/*! + Constructs a BSP scene index for the given \a scene. +*/ +QGraphicsSceneBspTreeIndex::QGraphicsSceneBspTreeIndex(QGraphicsScene *scene) + : QGraphicsSceneIndex(*new QGraphicsSceneBspTreeIndexPrivate(scene), scene) +{ + +} + +QGraphicsSceneBspTreeIndex::~QGraphicsSceneBspTreeIndex() +{ + Q_D(QGraphicsSceneBspTreeIndex); + for (int i = 0; i < d->indexedItems.size(); ++i) { + // Ensure item bits are reset properly. + if (QGraphicsItem *item = d->indexedItems.at(i)) { + Q_ASSERT(!item->d_ptr->itemDiscovered); + item->d_ptr->index = -1; + } + } +} + +/*! + \internal + Clear the all the BSP index. +*/ +void QGraphicsSceneBspTreeIndex::clear() +{ + Q_D(QGraphicsSceneBspTreeIndex); + d->bsp.clear(); + d->lastItemCount = 0; + d->freeItemIndexes.clear(); + for (int i = 0; i < d->indexedItems.size(); ++i) { + // Ensure item bits are reset properly. + if (QGraphicsItem *item = d->indexedItems.at(i)) { + Q_ASSERT(!item->d_ptr->itemDiscovered); + item->d_ptr->index = -1; + } + } + d->indexedItems.clear(); + d->unindexedItems.clear(); + d->untransformableItems.clear(); + d->regenerateIndex = true; +} + +/*! + Add the \a item into the BSP index. +*/ +void QGraphicsSceneBspTreeIndex::addItem(QGraphicsItem *item) +{ + Q_D(QGraphicsSceneBspTreeIndex); + d->addItem(item); +} + +/*! + Remove the \a item from the BSP index. +*/ +void QGraphicsSceneBspTreeIndex::removeItem(QGraphicsItem *item) +{ + Q_D(QGraphicsSceneBspTreeIndex); + d->removeItem(item); +} + +/*! + \internal + Update the BSP when the \a item 's bounding rect has changed. +*/ +void QGraphicsSceneBspTreeIndex::prepareBoundingRectChange(const QGraphicsItem *item) +{ + if (!item) + return; + + if (item->d_ptr->index == -1 || item->d_ptr->itemIsUntransformable() + || (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)) { + return; // Item is not in BSP tree; nothing to do. + } + + Q_D(QGraphicsSceneBspTreeIndex); + QGraphicsItem *thatItem = const_cast(item); + d->removeItem(thatItem, /*recursive=*/false, /*moveToUnindexedItems=*/true); + for (int i = 0; i < item->d_ptr->children.size(); ++i) // ### Do we really need this? + prepareBoundingRectChange(item->d_ptr->children.at(i)); +} + +/*! + Returns an estimation visible items that are either inside or + intersect with the specified \a rect and return a list sorted using \a order. + + \a deviceTransform is the transformation apply to the view. + +*/ +QList QGraphicsSceneBspTreeIndex::estimateItems(const QRectF &rect, Qt::SortOrder order) const +{ + Q_D(const QGraphicsSceneBspTreeIndex); + return const_cast(d)->estimateItems(rect, order); +} + +QList QGraphicsSceneBspTreeIndex::estimateTopLevelItems(const QRectF &rect, Qt::SortOrder order) const +{ + Q_D(const QGraphicsSceneBspTreeIndex); + return const_cast(d)->estimateItems(rect, order, /*onlyTopLevels=*/true); +} + +/*! + \fn QList QGraphicsSceneBspTreeIndex::items(Qt::SortOrder order = Qt::DescendingOrder) const; + + Return all items in the BSP index and sort them using \a order. +*/ +QList QGraphicsSceneBspTreeIndex::items(Qt::SortOrder order) const +{ + Q_D(const QGraphicsSceneBspTreeIndex); + const_cast(d)->purgeRemovedItems(); + QList itemList; + + // If freeItemIndexes is empty, we know there are no holes in indexedItems and + // unindexedItems. + if (d->freeItemIndexes.isEmpty()) { + if (d->unindexedItems.isEmpty()) { + itemList = d->indexedItems; + } else { + itemList = d->indexedItems + d->unindexedItems; + } + } else { + // Rebuild the list of items to avoid holes. ### We could also just + // compress the item lists at this point. + foreach (QGraphicsItem *item, d->indexedItems + d->unindexedItems) { + if (item) + itemList << item; + } + } + if (order != -1) { + //We sort descending order + d->sortItems(&itemList, order, d->sortCacheEnabled); + } + return itemList; +} + +/*! + \property QGraphicsSceneBspTreeIndex::bspTreeDepth + \brief the depth of the BSP index tree + \since 4.6 + + This value determines the depth of BSP tree. The depth + directly affects performance and memory usage; the latter + growing exponentially with the depth of the tree. With an optimal tree + depth, the index can instantly determine the locality of items, even + for scenes with thousands or millions of items. This also greatly improves + rendering performance. + + By default, the value is 0, in which case Qt will guess a reasonable + default depth based on the size, location and number of items in the + scene. If these parameters change frequently, however, you may experience + slowdowns as the index retunes the depth internally. You can avoid + potential slowdowns by fixating the tree depth through setting this + property. + + The depth of the tree and the size of the scene rectangle decide the + granularity of the scene's partitioning. The size of each scene segment is + determined by the following algorithm: + + The BSP tree has an optimal size when each segment contains between 0 and + 10 items. + +*/ +int QGraphicsSceneBspTreeIndex::bspTreeDepth() +{ + Q_D(const QGraphicsSceneBspTreeIndex); + return d->bspTreeDepth; +} + +void QGraphicsSceneBspTreeIndex::setBspTreeDepth(int depth) +{ + Q_D(QGraphicsSceneBspTreeIndex); + if (d->bspTreeDepth == depth) + return; + d->bspTreeDepth = depth; + d->resetIndex(); +} + +/*! + \internal + + This method react to the \a rect change of the scene and + reset the BSP tree index. +*/ +void QGraphicsSceneBspTreeIndex::updateSceneRect(const QRectF &rect) +{ + Q_D(QGraphicsSceneBspTreeIndex); + d->sceneRect = rect; + d->resetIndex(); +} + +/*! + \internal + + This method react to the \a change of the \a item and use the \a value to + update the BSP tree if necessary. +*/ +void QGraphicsSceneBspTreeIndex::itemChange(const QGraphicsItem *item, QGraphicsItem::GraphicsItemChange change, const void *const value) +{ + Q_D(QGraphicsSceneBspTreeIndex); + switch (change) { + case QGraphicsItem::ItemFlagsChange: { + // Handle ItemIgnoresTransformations + QGraphicsItem::GraphicsItemFlags newFlags = *static_cast(value); + bool ignoredTransform = item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations; + bool willIgnoreTransform = newFlags & QGraphicsItem::ItemIgnoresTransformations; + bool clipsChildren = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape; + bool willClipChildren = newFlags & QGraphicsItem::ItemClipsChildrenToShape; + if ((ignoredTransform != willIgnoreTransform) || (clipsChildren != willClipChildren)) { + QGraphicsItem *thatItem = const_cast(item); + // Remove item and its descendants from the index and append + // them to the list of unindexed items. Then, when the index + // is updated, they will be put into the bsp-tree or the list + // of untransformable items. + d->removeItem(thatItem, /*recursive=*/true, /*moveToUnidexedItems=*/true); + } + break; + } + case QGraphicsItem::ItemZValueChange: + d->invalidateSortCache(); + break; + case QGraphicsItem::ItemParentChange: { + d->invalidateSortCache(); + // Handle ItemIgnoresTransformations + const QGraphicsItem *newParent = static_cast(value); + bool ignoredTransform = item->d_ptr->itemIsUntransformable(); + bool willIgnoreTransform = (item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations) + || (newParent && newParent->d_ptr->itemIsUntransformable()); + bool ancestorClippedChildren = item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren; + bool ancestorWillClipChildren = newParent + && ((newParent->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape) + || (newParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)); + if ((ignoredTransform != willIgnoreTransform) || (ancestorClippedChildren != ancestorWillClipChildren)) { + QGraphicsItem *thatItem = const_cast(item); + // Remove item and its descendants from the index and append + // them to the list of unindexed items. Then, when the index + // is updated, they will be put into the bsp-tree or the list + // of untransformable items. + d->removeItem(thatItem, /*recursive=*/true, /*moveToUnidexedItems=*/true); + } + break; + } + default: + break; + } +} +/*! + \reimp + + Used to catch the timer event. + + \internal +*/ +bool QGraphicsSceneBspTreeIndex::event(QEvent *event) +{ + Q_D(QGraphicsSceneBspTreeIndex); + switch (event->type()) { + case QEvent::Timer: + if (d->indexTimerId && static_cast(event)->timerId() == d->indexTimerId) { + if (d->restartIndexTimer) { + d->restartIndexTimer = false; + } else { + // this call will kill the timer + d->_q_updateIndex(); + } + } + // Fallthrough intended - support timers in subclasses. + default: + return QObject::event(event); + } + return true; +} + +QT_END_NAMESPACE + +#include "moc_qgraphicsscenebsptreeindex_p.cpp" + +#endif // QT_NO_GRAPHICSVIEW + diff --git a/src/widgets/graphicsview/qgraphicsscenebsptreeindex_p.h b/src/widgets/graphicsview/qgraphicsscenebsptreeindex_p.h new file mode 100644 index 0000000000..fadf9d8a74 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsscenebsptreeindex_p.h @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** 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 QtCore module 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$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QGRAPHICSBSPTREEINDEX_H +#define QGRAPHICSBSPTREEINDEX_H + +#include + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +#include "qgraphicssceneindex_p.h" +#include "qgraphicsitem_p.h" +#include "qgraphicsscene_bsp_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +static const int QGRAPHICSSCENE_INDEXTIMER_TIMEOUT = 2000; + +class QGraphicsScene; +class QGraphicsSceneBspTreeIndexPrivate; + +class Q_AUTOTEST_EXPORT QGraphicsSceneBspTreeIndex : public QGraphicsSceneIndex +{ + Q_OBJECT + Q_PROPERTY(int bspTreeDepth READ bspTreeDepth WRITE setBspTreeDepth) +public: + QGraphicsSceneBspTreeIndex(QGraphicsScene *scene = 0); + ~QGraphicsSceneBspTreeIndex(); + + QList estimateItems(const QRectF &rect, Qt::SortOrder order) const; + QList estimateTopLevelItems(const QRectF &rect, Qt::SortOrder order) const; + QList items(Qt::SortOrder order = Qt::DescendingOrder) const; + + int bspTreeDepth(); + void setBspTreeDepth(int depth); + +protected Q_SLOTS: + void updateSceneRect(const QRectF &rect); + +protected: + bool event(QEvent *event); + void clear(); + + void addItem(QGraphicsItem *item); + void removeItem(QGraphicsItem *item); + void prepareBoundingRectChange(const QGraphicsItem *item); + + void itemChange(const QGraphicsItem *item, QGraphicsItem::GraphicsItemChange change, const void *const value); + +private : + Q_DECLARE_PRIVATE(QGraphicsSceneBspTreeIndex) + Q_DISABLE_COPY(QGraphicsSceneBspTreeIndex) + Q_PRIVATE_SLOT(d_func(), void _q_updateSortCache()) + Q_PRIVATE_SLOT(d_func(), void _q_updateIndex()) + + friend class QGraphicsScene; + friend class QGraphicsScenePrivate; +}; + +class QGraphicsSceneBspTreeIndexPrivate : public QGraphicsSceneIndexPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneBspTreeIndex) +public: + QGraphicsSceneBspTreeIndexPrivate(QGraphicsScene *scene); + + QGraphicsSceneBspTree bsp; + QRectF sceneRect; + int bspTreeDepth; + int indexTimerId; + bool restartIndexTimer; + bool regenerateIndex; + int lastItemCount; + + QList indexedItems; + QList unindexedItems; + QList untransformableItems; + QList freeItemIndexes; + + bool purgePending; + QSet removedItems; + void purgeRemovedItems(); + + void _q_updateIndex(); + void startIndexTimer(int interval = QGRAPHICSSCENE_INDEXTIMER_TIMEOUT); + void resetIndex(); + + void _q_updateSortCache(); + bool sortCacheEnabled; + bool updatingSortCache; + void invalidateSortCache(); + void addItem(QGraphicsItem *item, bool recursive = false); + void removeItem(QGraphicsItem *item, bool recursive = false, bool moveToUnindexedItems = false); + QList estimateItems(const QRectF &, Qt::SortOrder, bool b = false); + + static void climbTree(QGraphicsItem *item, int *stackingOrder); + + static inline bool closestItemFirst_withCache(const QGraphicsItem *item1, const QGraphicsItem *item2) + { + return item1->d_ptr->globalStackingOrder < item2->d_ptr->globalStackingOrder; + } + static inline bool closestItemLast_withCache(const QGraphicsItem *item1, const QGraphicsItem *item2) + { + return item1->d_ptr->globalStackingOrder >= item2->d_ptr->globalStackingOrder; + } + + static void sortItems(QList *itemList, Qt::SortOrder order, + bool cached, bool onlyTopLevelItems = false); +}; + +static inline bool QRectF_intersects(const QRectF &s, const QRectF &r) +{ + qreal xp = s.left(); + qreal yp = s.top(); + qreal w = s.width(); + qreal h = s.height(); + qreal l1 = xp; + qreal r1 = xp; + if (w < 0) + l1 += w; + else + r1 += w; + + qreal l2 = r.left(); + qreal r2 = r.left(); + if (w < 0) + l2 += r.width(); + else + r2 += r.width(); + + if (l1 >= r2 || l2 >= r1) + return false; + + qreal t1 = yp; + qreal b1 = yp; + if (h < 0) + t1 += h; + else + b1 += h; + + qreal t2 = r.top(); + qreal b2 = r.top(); + if (r.height() < 0) + t2 += r.height(); + else + b2 += r.height(); + + return !(t1 >= b2 || t2 >= b1); +} + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW + +#endif // QGRAPHICSBSPTREEINDEX_H diff --git a/src/widgets/graphicsview/qgraphicssceneevent.cpp b/src/widgets/graphicsview/qgraphicssceneevent.cpp new file mode 100644 index 0000000000..1b1f3db0aa --- /dev/null +++ b/src/widgets/graphicsview/qgraphicssceneevent.cpp @@ -0,0 +1,1674 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +/*! + \class QGraphicsSceneEvent + \brief The QGraphicsSceneEvent class provides a base class for all + graphics view related events. + \since 4.2 + \ingroup graphicsview-api + + When a QGraphicsView receives Qt mouse, keyboard, and drag and + drop events (QMouseEvent, QKeyEvent, QDragEvent, etc.), it + translates them into instances of QGraphicsSceneEvent subclasses + and forwards them to the QGraphicsScene it displays. The scene + then forwards the events to the relevant items. + + For example, when a QGraphicsView receives a QMouseEvent of type + MousePress as a response to a user click, the view sends a + QGraphicsSceneMouseEvent of type GraphicsSceneMousePress to the + underlying QGraphicsScene through its + \l{QGraphicsScene::}{mousePressEvent()} function. The default + QGraphicsScene::mousePressEvent() implementation determines which + item was clicked and forwards the event to + QGraphicsItem::mousePressEvent(). + + \omit ### Beskrive widget() \endomit + + Subclasses such as QGraphicsSceneMouseEvent and + QGraphicsSceneContextMenuEvent provide the coordinates from the + original QEvent in screen, scene, and item coordinates (see + \l{QGraphicsSceneMouseEvent::}{screenPos()}, + \l{QGraphicsSceneMouseEvent::}{scenePos()}, and + \l{QGraphicsSceneMouseEvent::}{pos()}). The item coordinates are + set by the QGraphicsScene before it forwards the event to the + event to a QGraphicsItem. The mouse events also add the + possibility to retrieve the coordinates from the last event + received by the view (see + \l{QGraphicsSceneMouseEvent::}{lastScreenPos()}, + \l{QGraphicsSceneMouseEvent::}{lastScenePos()}, and + \l{QGraphicsSceneMouseEvent::}{lastPos()}). + + \sa QEvent +*/ + +/*! + \class QGraphicsSceneMouseEvent + \brief The QGraphicsSceneMouseEvent class provides mouse events + in the graphics view framework. + \since 4.2 + \ingroup graphicsview-api + + When a QGraphicsView receives a QMouseEvent, it translates it to a + QGraphicsSceneMouseEvent. The event is then forwarded to the + QGraphicsScene associated with the view. If the event is not + handled by the scene, the view may use it, e.g., for the + \l{QGraphicsView::}{DragMode}. + + In addition to containing the item, scene, and screen coordinates + of the event (as pos(), scenePos(), and screenPos()), mouse + events also contain the coordinates of the previous mouse + event received by the view. These can be retrieved with + lastPos(), lastScreenPos(), and lastScenePos(). + + \sa QGraphicsSceneContextMenuEvent, + QGraphicsSceneHoverEvent, QGraphicsSceneWheelEvent, + QMouseEvent +*/ + +/*! + \class QGraphicsSceneWheelEvent + \brief The QGraphicsSceneWheelEvent class provides wheel events + in the graphics view framework. + \brief The QGraphicsSceneWheelEvent class provides wheel events in the + graphics view framework. + \since 4.2 + \ingroup graphicsview-api + + \l{QWheelEvent}{QWheelEvent}s received by a QGraphicsView are translated + into QGraphicsSceneWheelEvents; it translates the QWheelEvent::globalPos() + into item, scene, and screen coordinates (pos(), scenePos(), and + screenPos()). + + \sa QGraphicsSceneMouseEvent, QGraphicsSceneContextMenuEvent, + QGraphicsSceneHoverEvent, QWheelEvent +*/ + +/*! + \class QGraphicsSceneContextMenuEvent + \brief The QGraphicsSceneContextMenuEvent class provides context + menu events in the graphics view framework. + \since 4.2 + \ingroup graphicsview-api + + A QContextMenuEvent received by a QGraphicsView is translated + into a QGraphicsSceneContextMenuEvent. The + QContextMenuEvent::globalPos() is translated into item, scene, and + screen coordinates (pos(), scenePos(), and screenPos()). + + \sa QGraphicsSceneMouseEvent, QGraphicsSceneWheelEvent, + QContextMenuEvent +*/ + +/*! + \enum QGraphicsSceneContextMenuEvent::Reason + + This enum describes the reason why the context event was sent. + + \value Mouse The mouse caused the event to be sent. On most + platforms, this means the right mouse button was clicked. + + \value Keyboard The keyboard caused this event to be sent. On + Windows and Mac OS X, this means the menu button was pressed. + + \value Other The event was sent by some other means (i.e. not + by the mouse or keyboard). +*/ + +/*! + \class QGraphicsSceneHoverEvent + \brief The QGraphicsSceneHoverEvent class provides hover events + in the graphics view framework. + \since 4.2 + \ingroup graphicsview-api + + When a QGraphicsView receives a QHoverEvent event, it translates + it into QGraphicsSceneHoverEvent. The event is then forwarded to + the QGraphicsScene associated with the view. + + \sa QGraphicsSceneMouseEvent, QGraphicsSceneContextMenuEvent, + QGraphicsSceneWheelEvent, QHoverEvent +*/ + +/*! + \class QGraphicsSceneHelpEvent + \brief The QGraphicsSceneHelpEvent class provides events when a + tooltip is requested. + \since 4.2 + \ingroup graphicsview-api + + When a QGraphicsView receives a QEvent of type + QEvent::ToolTip, it creates a QGraphicsSceneHelpEvent, which is + forwarded to the scene. You can set a tooltip on a QGraphicsItem + with \l{QGraphicsItem::}{setToolTip()}; by default QGraphicsScene + displays the tooltip of the QGraphicsItem with the highest + z-value (i.e, the top-most item) under the mouse position. + + QGraphicsView does not forward events when + \l{QWhatsThis}{"What's This"} and \l{QStatusTipEvent}{status tip} + help is requested. If you need this, you can reimplement + QGraphicsView::viewportEvent() and forward QStatusTipEvent + events and \l{QEvent}{QEvents} of type QEvent::WhatsThis to the + scene. + + \sa QEvent +*/ + +/*! + \class QGraphicsSceneDragDropEvent + \brief The QGraphicsSceneDragDropEvent class provides events for + drag and drop in the graphics view framework. + \since 4.2 + \ingroup graphicsview-api + + QGraphicsView inherits the drag and drop functionality provided + by QWidget. When it receives a drag and drop event, it translates + it to a QGraphicsSceneDragDropEvent. + + QGraphicsSceneDragDropEvent stores events of type + GraphicsSceneDragEnter, GraphicsSceneDragLeave, + GraphicsSceneDragMove, or GraphicsSceneDrop. + + QGraphicsSceneDragDropEvent contains the position of the mouse + cursor in both item, scene, and screen coordinates; this can be + retrieved with pos(), scenePos(), and screenPos(). + + The scene sends the event to the first QGraphicsItem under the + mouse cursor that accepts drops; a graphics item is set to accept + drops with \l{QGraphicsItem::}{setAcceptDrops()}. +*/ + +/*! + \class QGraphicsSceneResizeEvent + \brief The QGraphicsSceneResizeEvent class provides events for widget + resizing in the graphics view framework. + \since 4.4 + \ingroup graphicsview-api + + A QGraphicsWidget sends itself a QGraphicsSceneResizeEvent immediately + when its geometry changes. + + It's similar to QResizeEvent, but its sizes, oldSize() and newSize(), use + QSizeF instead of QSize. + + \sa QGraphicsWidget::setGeometry(), QGraphicsWidget::resize() +*/ + +/*! + \class QGraphicsSceneMoveEvent + \brief The QGraphicsSceneMoveEvent class provides events for widget + moving in the graphics view framework. + \since 4.4 + \ingroup graphicsview-api + + A QGraphicsWidget sends itself a QGraphicsSceneMoveEvent immediately when + its local position changes. The delivery is implemented as part of + QGraphicsItem::itemChange(). + + It's similar to QMoveEvent, but its positions, oldPos() and newPos(), use + QPointF instead of QPoint. + + \sa QGraphicsItem::setPos(), QGraphicsItem::ItemPositionChange, + QGraphicsItem::ItemPositionHasChanged +*/ + +#include "qgraphicssceneevent.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#ifndef QT_NO_DEBUG +#include +#endif +#include +#include +#include +#include +#include "qgraphicsview.h" +#include "qgraphicsitem.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsSceneEventPrivate +{ +public: + inline QGraphicsSceneEventPrivate() + : widget(0), + q_ptr(0) + { } + + inline virtual ~QGraphicsSceneEventPrivate() + { } + + QWidget *widget; + QGraphicsSceneEvent *q_ptr; +}; + +/*! + \internal + + Constructs a generic graphics scene event of the specified \a type. +*/ +QGraphicsSceneEvent::QGraphicsSceneEvent(Type type) + : QEvent(type), d_ptr(new QGraphicsSceneEventPrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + \internal + + Constructs a generic graphics scene event. +*/ +QGraphicsSceneEvent::QGraphicsSceneEvent(QGraphicsSceneEventPrivate &dd, Type type) + : QEvent(type), d_ptr(&dd) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys the event. +*/ +QGraphicsSceneEvent::~QGraphicsSceneEvent() +{ +} + +/*! + Returns the widget where the event originated, or 0 if the event + originates from another application. +*/ +QWidget *QGraphicsSceneEvent::widget() const +{ + return d_ptr->widget; +} + +/*! + \internal + + Sets the \a widget related to this event. + + \sa widget() +*/ +void QGraphicsSceneEvent::setWidget(QWidget *widget) +{ + d_ptr->widget = widget; +} + +class QGraphicsSceneMouseEventPrivate : public QGraphicsSceneEventPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneMouseEvent) +public: + inline QGraphicsSceneMouseEventPrivate() + : button(Qt::NoButton), + buttons(0), modifiers(0) + { } + + QPointF pos; + QPointF scenePos; + QPoint screenPos; + QPointF lastPos; + QPointF lastScenePos; + QPoint lastScreenPos; + QMap buttonDownPos; + QMap buttonDownScenePos; + QMap buttonDownScreenPos; + Qt::MouseButton button; + Qt::MouseButtons buttons; + Qt::KeyboardModifiers modifiers; +}; + +/*! + \internal + + Constructs a generic graphics scene mouse event of the specified \a type. +*/ +QGraphicsSceneMouseEvent::QGraphicsSceneMouseEvent(Type type) + : QGraphicsSceneEvent(*new QGraphicsSceneMouseEventPrivate, type) +{ +} + +/*! + Destroys the event. +*/ +QGraphicsSceneMouseEvent::~QGraphicsSceneMouseEvent() +{ +} + +/*! + Returns the mouse cursor position in item coordinates. + + \sa scenePos(), screenPos(), lastPos() +*/ +QPointF QGraphicsSceneMouseEvent::pos() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->pos; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->pos = pos; +} + +/*! + Returns the mouse cursor position in scene coordinates. + + \sa pos(), screenPos(), lastScenePos() +*/ +QPointF QGraphicsSceneMouseEvent::scenePos() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->scenePos; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->scenePos = pos; +} + +/*! + Returns the mouse cursor position in screen coordinates. + + \sa pos(), scenePos(), lastScreenPos() +*/ +QPoint QGraphicsSceneMouseEvent::screenPos() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->screenPos; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->screenPos = pos; +} + +/*! + Returns the mouse cursor position in item coordinates where the specified + \a button was clicked. + + \sa buttonDownScenePos(), buttonDownScreenPos(), pos() +*/ +QPointF QGraphicsSceneMouseEvent::buttonDownPos(Qt::MouseButton button) const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->buttonDownPos.value(button); +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setButtonDownPos(Qt::MouseButton button, const QPointF &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->buttonDownPos.insert(button, pos); +} + +/*! + Returns the mouse cursor position in scene coordinates where the + specified \a button was clicked. + + \sa buttonDownPos(), buttonDownScreenPos(), scenePos() +*/ +QPointF QGraphicsSceneMouseEvent::buttonDownScenePos(Qt::MouseButton button) const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->buttonDownScenePos.value(button); +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setButtonDownScenePos(Qt::MouseButton button, const QPointF &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->buttonDownScenePos.insert(button, pos); +} + +/*! + Returns the mouse cursor position in screen coordinates where the + specified \a button was clicked. + + \sa screenPos(), buttonDownPos(), buttonDownScenePos() +*/ +QPoint QGraphicsSceneMouseEvent::buttonDownScreenPos(Qt::MouseButton button) const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->buttonDownScreenPos.value(button); +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setButtonDownScreenPos(Qt::MouseButton button, const QPoint &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->buttonDownScreenPos.insert(button, pos); +} + +/*! + Returns the last recorded mouse cursor position in item + coordinates. + + \sa lastScenePos(), lastScreenPos(), pos() +*/ +QPointF QGraphicsSceneMouseEvent::lastPos() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->lastPos; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setLastPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->lastPos = pos; +} + +/*! + Returns the last recorded mouse cursor position in scene + coordinates. The last recorded position is the position of + the previous mouse event received by the view that created + the event. + + \sa lastPos(), lastScreenPos(), scenePos() +*/ +QPointF QGraphicsSceneMouseEvent::lastScenePos() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->lastScenePos; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setLastScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->lastScenePos = pos; +} + +/*! + Returns the last recorded mouse cursor position in screen + coordinates. The last recorded position is the position of + the previous mouse event received by the view that created + the event. + + \sa lastPos(), lastScenePos(), screenPos() +*/ +QPoint QGraphicsSceneMouseEvent::lastScreenPos() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->lastScreenPos; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setLastScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->lastScreenPos = pos; +} + +/*! + Returns the combination of mouse buttons that were pressed at the + time the event was sent. + + \sa button(), modifiers() +*/ +Qt::MouseButtons QGraphicsSceneMouseEvent::buttons() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->buttons; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setButtons(Qt::MouseButtons buttons) +{ + Q_D(QGraphicsSceneMouseEvent); + d->buttons = buttons; +} + +/*! + Returns the mouse button (if any) that caused the event. + + \sa buttons(), modifiers() +*/ +Qt::MouseButton QGraphicsSceneMouseEvent::button() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->button; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setButton(Qt::MouseButton button) +{ + Q_D(QGraphicsSceneMouseEvent); + d->button = button; +} + +/*! + Returns the keyboard modifiers in use at the time the event was + sent. + + \sa buttons(), button() +*/ +Qt::KeyboardModifiers QGraphicsSceneMouseEvent::modifiers() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->modifiers; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setModifiers(Qt::KeyboardModifiers modifiers) +{ + Q_D(QGraphicsSceneMouseEvent); + d->modifiers = modifiers; +} + +class QGraphicsSceneWheelEventPrivate : public QGraphicsSceneEventPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneWheelEvent) +public: + inline QGraphicsSceneWheelEventPrivate() + : buttons(0), modifiers(0), delta(0), orientation(Qt::Horizontal) + { } + + QPointF pos; + QPointF scenePos; + QPoint screenPos; + Qt::MouseButtons buttons; + Qt::KeyboardModifiers modifiers; + int delta; + Qt::Orientation orientation; +}; + +/*! + \internal + + Constructs a QGraphicsSceneWheelEvent of type \a type, which + is always QEvent::GraphicsSceneWheel. +*/ +QGraphicsSceneWheelEvent::QGraphicsSceneWheelEvent(Type type) + : QGraphicsSceneEvent(*new QGraphicsSceneWheelEventPrivate, type) +{ +} + +/*! + Destroys the QGraphicsSceneWheelEvent. +*/ +QGraphicsSceneWheelEvent::~QGraphicsSceneWheelEvent() +{ +} + +/*! + Returns the position of the cursor in item coordinates when the + wheel event occurred. + + \sa scenePos(), screenPos() +*/ +QPointF QGraphicsSceneWheelEvent::pos() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->pos; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneWheelEvent); + d->pos = pos; +} + +/*! + Returns the position of the cursor in scene coordinates when the wheel + event occurred. + + \sa pos(), screenPos() +*/ +QPointF QGraphicsSceneWheelEvent::scenePos() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->scenePos; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneWheelEvent); + d->scenePos = pos; +} + +/*! + Returns the position of the cursor in screen coordinates when the wheel + event occurred. + + \sa pos(), scenePos() +*/ +QPoint QGraphicsSceneWheelEvent::screenPos() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->screenPos; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneWheelEvent); + d->screenPos = pos; +} + +/*! + Returns the mouse buttons that were pressed when the wheel event occurred. + + \sa modifiers() +*/ +Qt::MouseButtons QGraphicsSceneWheelEvent::buttons() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->buttons; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setButtons(Qt::MouseButtons buttons) +{ + Q_D(QGraphicsSceneWheelEvent); + d->buttons = buttons; +} + +/*! + Returns the keyboard modifiers that were active when the wheel event + occurred. + + \sa buttons() +*/ +Qt::KeyboardModifiers QGraphicsSceneWheelEvent::modifiers() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->modifiers; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setModifiers(Qt::KeyboardModifiers modifiers) +{ + Q_D(QGraphicsSceneWheelEvent); + d->modifiers = modifiers; +} + +/*! + Returns the distance that the wheel is rotated, in eighths (1/8s) + of a degree. A positive value indicates that the wheel was + rotated forwards away from the user; a negative value indicates + that the wheel was rotated backwards toward the user. + + Most mouse types work in steps of 15 degrees, in which case the delta + value is a multiple of 120 (== 15 * 8). +*/ +int QGraphicsSceneWheelEvent::delta() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->delta; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setDelta(int delta) +{ + Q_D(QGraphicsSceneWheelEvent); + d->delta = delta; +} + +/*! + Returns the wheel orientation. +*/ +Qt::Orientation QGraphicsSceneWheelEvent::orientation() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->orientation; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setOrientation(Qt::Orientation orientation) +{ + Q_D(QGraphicsSceneWheelEvent); + d->orientation = orientation; +} + +class QGraphicsSceneContextMenuEventPrivate : public QGraphicsSceneEventPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneContextMenuEvent) + public: + inline QGraphicsSceneContextMenuEventPrivate() + : modifiers(0), reason(QGraphicsSceneContextMenuEvent::Other) + { } + + QPointF pos; + QPointF scenePos; + QPoint screenPos; + Qt::KeyboardModifiers modifiers; + QGraphicsSceneContextMenuEvent::Reason reason; +}; + +/*! + \internal + + Constructs a graphics scene context menu event of the specified \a type. +*/ +QGraphicsSceneContextMenuEvent::QGraphicsSceneContextMenuEvent(Type type) + : QGraphicsSceneEvent(*new QGraphicsSceneContextMenuEventPrivate, type) +{ +} + +/*! + Destroys the event. +*/ +QGraphicsSceneContextMenuEvent::~QGraphicsSceneContextMenuEvent() +{ +} + +/*! + Returns the position of the mouse cursor in item coordinates at the moment + the context menu was requested. + + \sa scenePos(), screenPos() +*/ +QPointF QGraphicsSceneContextMenuEvent::pos() const +{ + Q_D(const QGraphicsSceneContextMenuEvent); + return d->pos; +} + +/*! + \fn void QGraphicsSceneContextMenuEvent::setPos(const QPointF &point) + \internal + + Sets the position associated with the context menu to the given \a point + in item coordinates. +*/ +void QGraphicsSceneContextMenuEvent::setPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneContextMenuEvent); + d->pos = pos; +} + +/*! + Returns the position of the mouse cursor in scene coordinates at the moment the + the context menu was requested. + + \sa pos(), screenPos() +*/ +QPointF QGraphicsSceneContextMenuEvent::scenePos() const +{ + Q_D(const QGraphicsSceneContextMenuEvent); + return d->scenePos; +} + +/*! + \fn void QGraphicsSceneContextMenuEvent::setScenePos(const QPointF &point) + \internal + + Sets the position associated with the context menu to the given \a point + in scene coordinates. +*/ +void QGraphicsSceneContextMenuEvent::setScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneContextMenuEvent); + d->scenePos = pos; +} + +/*! + Returns the position of the mouse cursor in screen coordinates at the moment the + the context menu was requested. + + \sa pos(), scenePos() +*/ +QPoint QGraphicsSceneContextMenuEvent::screenPos() const +{ + Q_D(const QGraphicsSceneContextMenuEvent); + return d->screenPos; +} + +/*! + \fn void QGraphicsSceneContextMenuEvent::setScreenPos(const QPoint &point) + \internal + + Sets the position associated with the context menu to the given \a point + in screen coordinates. +*/ +void QGraphicsSceneContextMenuEvent::setScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneContextMenuEvent); + d->screenPos = pos; +} + +/*! + Returns the keyboard modifiers in use when the context menu was requested. +*/ +Qt::KeyboardModifiers QGraphicsSceneContextMenuEvent::modifiers() const +{ + Q_D(const QGraphicsSceneContextMenuEvent); + return d->modifiers; +} + +/*! + \internal + + Sets the keyboard modifiers associated with the context menu to the \a + modifiers specified. +*/ +void QGraphicsSceneContextMenuEvent::setModifiers(Qt::KeyboardModifiers modifiers) +{ + Q_D(QGraphicsSceneContextMenuEvent); + d->modifiers = modifiers; +} + +/*! + Returns the reason for the context menu event. + + \sa QGraphicsSceneContextMenuEvent::Reason +*/ +QGraphicsSceneContextMenuEvent::Reason QGraphicsSceneContextMenuEvent::reason() const +{ + Q_D(const QGraphicsSceneContextMenuEvent); + return d->reason; +} + +/*! + \internal + Sets the reason for the context menu event to \a reason. + + \sa reason() +*/ +void QGraphicsSceneContextMenuEvent::setReason(Reason reason) +{ + Q_D(QGraphicsSceneContextMenuEvent); + d->reason = reason; +} + +class QGraphicsSceneHoverEventPrivate : public QGraphicsSceneEventPrivate +{ +public: + QPointF pos; + QPointF scenePos; + QPoint screenPos; + QPointF lastPos; + QPointF lastScenePos; + QPoint lastScreenPos; + Qt::KeyboardModifiers modifiers; +}; + +/*! + \internal + + Constructs a graphics scene hover event of the specified \a type. +*/ +QGraphicsSceneHoverEvent::QGraphicsSceneHoverEvent(Type type) + : QGraphicsSceneEvent(*new QGraphicsSceneHoverEventPrivate, type) +{ +} + +/*! + Destroys the event. +*/ +QGraphicsSceneHoverEvent::~QGraphicsSceneHoverEvent() +{ +} + +/*! + Returns the position of the mouse cursor in item coordinates at the moment + the hover event was sent. + + \sa scenePos(), screenPos() +*/ +QPointF QGraphicsSceneHoverEvent::pos() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->pos; +} + +/*! + \fn void QGraphicsSceneHoverEvent::setPos(const QPointF &point) + \internal + + Sets the position associated with the hover event to the given \a point in + item coordinates. +*/ +void QGraphicsSceneHoverEvent::setPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneHoverEvent); + d->pos = pos; +} + +/*! + Returns the position of the mouse cursor in scene coordinates at the + moment the hover event was sent. + + \sa pos(), screenPos() +*/ +QPointF QGraphicsSceneHoverEvent::scenePos() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->scenePos; +} + +/*! + \fn void QGraphicsSceneHoverEvent::setScenePos(const QPointF &point) + \internal + + Sets the position associated with the hover event to the given \a point in + scene coordinates. +*/ +void QGraphicsSceneHoverEvent::setScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneHoverEvent); + d->scenePos = pos; +} + +/*! + Returns the position of the mouse cursor in screen coordinates at the + moment the hover event was sent. + + \sa pos(), scenePos() +*/ +QPoint QGraphicsSceneHoverEvent::screenPos() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->screenPos; +} + +/*! + \fn void QGraphicsSceneHoverEvent::setScreenPos(const QPoint &point) + \internal + + Sets the position associated with the hover event to the given \a point in + screen coordinates. +*/ +void QGraphicsSceneHoverEvent::setScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneHoverEvent); + d->screenPos = pos; +} + +/*! + \since 4.4 + + Returns the last recorded mouse cursor position in item coordinates. + + \sa lastScenePos(), lastScreenPos(), pos() +*/ +QPointF QGraphicsSceneHoverEvent::lastPos() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->lastPos; +} + +/*! + \internal +*/ +void QGraphicsSceneHoverEvent::setLastPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneHoverEvent); + d->lastPos = pos; +} + +/*! + \since 4.4 + + Returns the last recorded, the scene coordinates of the previous mouse or + hover event received by the view, that created the event mouse cursor + position in scene coordinates. + + \sa lastPos(), lastScreenPos(), scenePos() +*/ +QPointF QGraphicsSceneHoverEvent::lastScenePos() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->lastScenePos; +} + +/*! + \internal +*/ +void QGraphicsSceneHoverEvent::setLastScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneHoverEvent); + d->lastScenePos = pos; +} + +/*! + \since 4.4 + + Returns the last recorded mouse cursor position in screen coordinates. The + last recorded position is the position of the previous mouse or hover + event received by the view that created the event. + + \sa lastPos(), lastScenePos(), screenPos() +*/ +QPoint QGraphicsSceneHoverEvent::lastScreenPos() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->lastScreenPos; +} + +/*! + \internal +*/ +void QGraphicsSceneHoverEvent::setLastScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneHoverEvent); + d->lastScreenPos = pos; +} + +/*! + \since 4.4 + + Returns the keyboard modifiers at the moment the hover event was sent. +*/ +Qt::KeyboardModifiers QGraphicsSceneHoverEvent::modifiers() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->modifiers; +} + +/*! + \fn void QGraphicsSceneHoverEvent::setModifiers(Qt::KeyboardModifiers modifiers) + \internal + + Sets the modifiers for the current hover event to \a modifiers. +*/ +void QGraphicsSceneHoverEvent::setModifiers(Qt::KeyboardModifiers modifiers) +{ + Q_D(QGraphicsSceneHoverEvent); + d->modifiers = modifiers; +} + +class QGraphicsSceneHelpEventPrivate : public QGraphicsSceneEventPrivate +{ +public: + QPointF scenePos; + QPoint screenPos; +}; + +/*! + \internal + + Constructs a graphics scene help event of the specified \a type. +*/ +QGraphicsSceneHelpEvent::QGraphicsSceneHelpEvent(Type type) + : QGraphicsSceneEvent(*new QGraphicsSceneHelpEventPrivate, type) +{ +} + +/*! + Destroys the event. +*/ +QGraphicsSceneHelpEvent::~QGraphicsSceneHelpEvent() +{ +} + +/*! + Returns the position of the mouse cursor in scene coordinates at the + moment the help event was sent. + + \sa screenPos() +*/ +QPointF QGraphicsSceneHelpEvent::scenePos() const +{ + Q_D(const QGraphicsSceneHelpEvent); + return d->scenePos; +} + +/*! + \fn void QGraphicsSceneHelpEvent::setScenePos(const QPointF &point) + \internal + + Sets the position associated with the context menu to the given \a point + in scene coordinates. +*/ +void QGraphicsSceneHelpEvent::setScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneHelpEvent); + d->scenePos = pos; +} + +/*! + Returns the position of the mouse cursor in screen coordinates at the + moment the help event was sent. + + \sa scenePos() +*/ +QPoint QGraphicsSceneHelpEvent::screenPos() const +{ + Q_D(const QGraphicsSceneHelpEvent); + return d->screenPos; +} + +/*! + \fn void QGraphicsSceneHelpEvent::setScreenPos(const QPoint &point) + \internal + + Sets the position associated with the context menu to the given \a point + in screen coordinates. +*/ +void QGraphicsSceneHelpEvent::setScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneHelpEvent); + d->screenPos = pos; +} + +class QGraphicsSceneDragDropEventPrivate : public QGraphicsSceneEventPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneDragDropEvent) +public: + inline QGraphicsSceneDragDropEventPrivate() + : source(0), mimeData(0) + { } + + QPointF pos; + QPointF scenePos; + QPoint screenPos; + Qt::MouseButtons buttons; + Qt::KeyboardModifiers modifiers; + Qt::DropActions possibleActions; + Qt::DropAction proposedAction; + Qt::DropAction dropAction; + QWidget *source; + const QMimeData *mimeData; +}; + +/*! + \internal + + Constructs a new QGraphicsSceneDragDropEvent of the + specified \a type. The type can be either + QEvent::GraphicsSceneDragEnter, QEvent::GraphicsSceneDragLeave, + QEvent::GraphicsSceneDragMove, or QEvent::GraphicsSceneDrop. +*/ +QGraphicsSceneDragDropEvent::QGraphicsSceneDragDropEvent(Type type) + : QGraphicsSceneEvent(*new QGraphicsSceneDragDropEventPrivate, type) +{ +} + +/*! + Destroys the object. +*/ +QGraphicsSceneDragDropEvent::~QGraphicsSceneDragDropEvent() +{ +} + +/*! + Returns the mouse position of the event relative to the + view that sent the event. + + \sa QGraphicsView, screenPos(), scenePos() +*/ +QPointF QGraphicsSceneDragDropEvent::pos() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->pos; +} + +/*! + \internal + Sets the position of the mouse to \a pos; this should be + relative to the widget that generated the event, which normally + is a QGraphicsView. + + \sa pos(), setScenePos(), setScreenPos() +*/ + +void QGraphicsSceneDragDropEvent::setPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->pos = pos; +} + +/*! + Returns the position of the mouse in scene coordinates. + + \sa pos(), screenPos() +*/ +QPointF QGraphicsSceneDragDropEvent::scenePos() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->scenePos; +} + +/*! + \internal + Sets the scene position of the mouse to \a pos. + + \sa scenePos(), setScreenPos(), setPos() +*/ +void QGraphicsSceneDragDropEvent::setScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->scenePos = pos; +} + +/*! + Returns the position of the mouse relative to the screen. + + \sa pos(), scenePos() +*/ +QPoint QGraphicsSceneDragDropEvent::screenPos() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->screenPos; +} + +/*! + \internal + Sets the mouse position relative to the screen to \a pos. + + \sa screenPos(), setScenePos(), setPos() +*/ +void QGraphicsSceneDragDropEvent::setScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->screenPos = pos; +} + +/*! + Returns a Qt::MouseButtons value indicating which buttons + were pressed on the mouse when this mouse event was + generated. + + \sa Qt::MouseButtons +*/ +Qt::MouseButtons QGraphicsSceneDragDropEvent::buttons() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->buttons; +} + +/*! + \internal + Sets the mouse buttons that were pressed when the event was + created to \a buttons. + + \sa Qt::MouseButtons, buttons() +*/ +void QGraphicsSceneDragDropEvent::setButtons(Qt::MouseButtons buttons) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->buttons = buttons; +} + +/*! + Returns the keyboard modifiers that were pressed when the drag + and drop event was created. + + \sa Qt::KeyboardModifiers +*/ +Qt::KeyboardModifiers QGraphicsSceneDragDropEvent::modifiers() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->modifiers; +} + +/*! + \internal + Sets the keyboard modifiers that were pressed when the event + was created to \a modifiers. + + \sa Qt::KeyboardModifiers, modifiers() +*/ + +void QGraphicsSceneDragDropEvent::setModifiers(Qt::KeyboardModifiers modifiers) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->modifiers = modifiers; +} + +/*! + Returns the possible drop actions that the drag and + drop can result in. + + \sa Qt::DropActions +*/ + +Qt::DropActions QGraphicsSceneDragDropEvent::possibleActions() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->possibleActions; +} + +/*! + \internal + Sets the possible drop actions that the drag can + result in to \a actions. + + \sa Qt::DropActions, possibleActions() +*/ +void QGraphicsSceneDragDropEvent::setPossibleActions(Qt::DropActions actions) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->possibleActions = actions; +} + +/*! + Returns the drop action that is proposed, i.e., preferred. + The action must be one of the possible actions as defined by + \c possibleActions(). + + \sa Qt::DropAction, possibleActions() +*/ + +Qt::DropAction QGraphicsSceneDragDropEvent::proposedAction() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->proposedAction; +} + +/*! + \internal + Sets the proposed action to \a action. The proposed action + is a Qt::DropAction that is one of the possible actions as + given by \c possibleActions(). + + \sa proposedAction(), Qt::DropAction, possibleActions() +*/ + +void QGraphicsSceneDragDropEvent::setProposedAction(Qt::DropAction action) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->proposedAction = action; +} + +/*! + Sets the proposed action as accepted, i.e, the drop action + is set to the proposed action. This is equal to: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicssceneevent.cpp 0 + + When using this function, one should not call \c accept(). + + \sa dropAction(), setDropAction(), proposedAction() +*/ + +void QGraphicsSceneDragDropEvent::acceptProposedAction() +{ + Q_D(QGraphicsSceneDragDropEvent); + d->dropAction = d->proposedAction; +} + +/*! + Returns the action that was performed in this drag and drop. + This should be set by the receiver of the drop and is + returned by QDrag::exec(). + + \sa setDropAction(), acceptProposedAction() +*/ + +Qt::DropAction QGraphicsSceneDragDropEvent::dropAction() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->dropAction; +} + +/*! + This function lets the receiver of the drop set the drop + action that was performed to \a action, which should be one + of the + \l{QGraphicsSceneDragDropEvent::possibleActions()}{possible + actions}. Call \c accept() in stead of \c + acceptProposedAction() if you use this function. + + \sa dropAction(), accept(), possibleActions() +*/ +void QGraphicsSceneDragDropEvent::setDropAction(Qt::DropAction action) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->dropAction = action; +} + +/*! + This function returns the QGraphicsView that created the + QGraphicsSceneDragDropEvent. +*/ +QWidget *QGraphicsSceneDragDropEvent::source() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->source; +} + +/*! + \internal + This function set the source widget, i.e., the widget that + created the drop event, to \a source. +*/ +void QGraphicsSceneDragDropEvent::setSource(QWidget *source) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->source = source; +} + +/*! + This function returns the MIME data of the event. +*/ +const QMimeData *QGraphicsSceneDragDropEvent::mimeData() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->mimeData; +} + +/*! + \internal + This function sets the MIME data for the event. +*/ +void QGraphicsSceneDragDropEvent::setMimeData(const QMimeData *data) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->mimeData = data; +} + +class QGraphicsSceneResizeEventPrivate : public QGraphicsSceneEventPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneResizeEvent) +public: + inline QGraphicsSceneResizeEventPrivate() + { } + + QSizeF oldSize; + QSizeF newSize; +}; + +/*! + Constructs a QGraphicsSceneResizeEvent. +*/ +QGraphicsSceneResizeEvent::QGraphicsSceneResizeEvent() + : QGraphicsSceneEvent(*new QGraphicsSceneResizeEventPrivate, QEvent::GraphicsSceneResize) +{ +} + +/*! + Destroys the QGraphicsSceneResizeEvent. +*/ +QGraphicsSceneResizeEvent::~QGraphicsSceneResizeEvent() +{ +} + +/*! + Returns the old size (i.e., the size immediately before the widget was + resized). + + \sa newSize(), QGraphicsWidget::resize() +*/ +QSizeF QGraphicsSceneResizeEvent::oldSize() const +{ + Q_D(const QGraphicsSceneResizeEvent); + return d->oldSize; +} + +/*! + \internal +*/ +void QGraphicsSceneResizeEvent::setOldSize(const QSizeF &size) +{ + Q_D(QGraphicsSceneResizeEvent); + d->oldSize = size; +} + +/*! + Returns the new size (i.e., the current size). + + \sa oldSize(), QGraphicsWidget::resize() +*/ +QSizeF QGraphicsSceneResizeEvent::newSize() const +{ + Q_D(const QGraphicsSceneResizeEvent); + return d->newSize; +} + +/*! + \internal +*/ +void QGraphicsSceneResizeEvent::setNewSize(const QSizeF &size) +{ + Q_D(QGraphicsSceneResizeEvent); + d->newSize = size; +} + +class QGraphicsSceneMoveEventPrivate : public QGraphicsSceneEventPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneMoveEvent) +public: + inline QGraphicsSceneMoveEventPrivate() + { } + + QPointF oldPos; + QPointF newPos; +}; + +/*! + Constructs a QGraphicsSceneMoveEvent. +*/ +QGraphicsSceneMoveEvent::QGraphicsSceneMoveEvent() + : QGraphicsSceneEvent(*new QGraphicsSceneMoveEventPrivate, QEvent::GraphicsSceneMove) +{ +} + +/*! + Destroys the QGraphicsSceneMoveEvent. +*/ +QGraphicsSceneMoveEvent::~QGraphicsSceneMoveEvent() +{ +} + +/*! + Returns the old position (i.e., the position immediately before the widget + was moved). + + \sa newPos(), QGraphicsItem::setPos() +*/ +QPointF QGraphicsSceneMoveEvent::oldPos() const +{ + Q_D(const QGraphicsSceneMoveEvent); + return d->oldPos; +} + +/*! + \internal +*/ +void QGraphicsSceneMoveEvent::setOldPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneMoveEvent); + d->oldPos = pos; +} + +/*! + Returns the new position (i.e., the current position). + + \sa oldPos(), QGraphicsItem::setPos() +*/ +QPointF QGraphicsSceneMoveEvent::newPos() const +{ + Q_D(const QGraphicsSceneMoveEvent); + return d->newPos; +} + +/*! + \internal +*/ +void QGraphicsSceneMoveEvent::setNewPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneMoveEvent); + d->newPos = pos; +} + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicssceneevent.h b/src/widgets/graphicsview/qgraphicssceneevent.h new file mode 100644 index 0000000000..afaa33c9ce --- /dev/null +++ b/src/widgets/graphicsview/qgraphicssceneevent.h @@ -0,0 +1,326 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSCENEEVENT_H +#define QGRAPHICSSCENEEVENT_H + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QMimeData; +class QPointF; +class QSizeF; +class QWidget; + +class QGraphicsSceneEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneEvent : public QEvent +{ +public: + QGraphicsSceneEvent(Type type); + ~QGraphicsSceneEvent(); + + QWidget *widget() const; + void setWidget(QWidget *widget); + +protected: + QGraphicsSceneEvent(QGraphicsSceneEventPrivate &dd, Type type = None); + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QGraphicsSceneEvent) +private: + Q_DISABLE_COPY(QGraphicsSceneEvent) +}; + +class QGraphicsSceneMouseEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneMouseEvent : public QGraphicsSceneEvent +{ +public: + QGraphicsSceneMouseEvent(Type type = None); + ~QGraphicsSceneMouseEvent(); + + QPointF pos() const; + void setPos(const QPointF &pos); + + QPointF scenePos() const; + void setScenePos(const QPointF &pos); + + QPoint screenPos() const; + void setScreenPos(const QPoint &pos); + + QPointF buttonDownPos(Qt::MouseButton button) const; + void setButtonDownPos(Qt::MouseButton button, const QPointF &pos); + + QPointF buttonDownScenePos(Qt::MouseButton button) const; + void setButtonDownScenePos(Qt::MouseButton button, const QPointF &pos); + + QPoint buttonDownScreenPos(Qt::MouseButton button) const; + void setButtonDownScreenPos(Qt::MouseButton button, const QPoint &pos); + + QPointF lastPos() const; + void setLastPos(const QPointF &pos); + + QPointF lastScenePos() const; + void setLastScenePos(const QPointF &pos); + + QPoint lastScreenPos() const; + void setLastScreenPos(const QPoint &pos); + + Qt::MouseButtons buttons() const; + void setButtons(Qt::MouseButtons buttons); + + Qt::MouseButton button() const; + void setButton(Qt::MouseButton button); + + Qt::KeyboardModifiers modifiers() const; + void setModifiers(Qt::KeyboardModifiers modifiers); + +private: + Q_DECLARE_PRIVATE(QGraphicsSceneMouseEvent) + Q_DISABLE_COPY(QGraphicsSceneMouseEvent) +}; + +class QGraphicsSceneWheelEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneWheelEvent : public QGraphicsSceneEvent +{ +public: + QGraphicsSceneWheelEvent(Type type = None); + ~QGraphicsSceneWheelEvent(); + + QPointF pos() const; + void setPos(const QPointF &pos); + + QPointF scenePos() const; + void setScenePos(const QPointF &pos); + + QPoint screenPos() const; + void setScreenPos(const QPoint &pos); + + Qt::MouseButtons buttons() const; + void setButtons(Qt::MouseButtons buttons); + + Qt::KeyboardModifiers modifiers() const; + void setModifiers(Qt::KeyboardModifiers modifiers); + + int delta() const; + void setDelta(int delta); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + +private: + Q_DECLARE_PRIVATE(QGraphicsSceneWheelEvent) + Q_DISABLE_COPY(QGraphicsSceneWheelEvent) +}; + +class QGraphicsSceneContextMenuEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneContextMenuEvent : public QGraphicsSceneEvent +{ +public: + enum Reason { Mouse, Keyboard, Other }; + + QGraphicsSceneContextMenuEvent(Type type = None); + ~QGraphicsSceneContextMenuEvent(); + + QPointF pos() const; + void setPos(const QPointF &pos); + + QPointF scenePos() const; + void setScenePos(const QPointF &pos); + + QPoint screenPos() const; + void setScreenPos(const QPoint &pos); + + Qt::KeyboardModifiers modifiers() const; + void setModifiers(Qt::KeyboardModifiers modifiers); + + Reason reason() const; + void setReason(Reason reason); + +private: + Q_DECLARE_PRIVATE(QGraphicsSceneContextMenuEvent) + Q_DISABLE_COPY(QGraphicsSceneContextMenuEvent) +}; + +class QGraphicsSceneHoverEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneHoverEvent : public QGraphicsSceneEvent +{ +public: + QGraphicsSceneHoverEvent(Type type = None); + ~QGraphicsSceneHoverEvent(); + + QPointF pos() const; + void setPos(const QPointF &pos); + + QPointF scenePos() const; + void setScenePos(const QPointF &pos); + + QPoint screenPos() const; + void setScreenPos(const QPoint &pos); + + QPointF lastPos() const; + void setLastPos(const QPointF &pos); + + QPointF lastScenePos() const; + void setLastScenePos(const QPointF &pos); + + QPoint lastScreenPos() const; + void setLastScreenPos(const QPoint &pos); + + Qt::KeyboardModifiers modifiers() const; + void setModifiers(Qt::KeyboardModifiers modifiers); + +private: + Q_DECLARE_PRIVATE(QGraphicsSceneHoverEvent) + Q_DISABLE_COPY(QGraphicsSceneHoverEvent) +}; + +class QGraphicsSceneHelpEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneHelpEvent : public QGraphicsSceneEvent +{ +public: + QGraphicsSceneHelpEvent(Type type = None); + ~QGraphicsSceneHelpEvent(); + + QPointF scenePos() const; + void setScenePos(const QPointF &pos); + + QPoint screenPos() const; + void setScreenPos(const QPoint &pos); + +private: + Q_DECLARE_PRIVATE(QGraphicsSceneHelpEvent) + Q_DISABLE_COPY(QGraphicsSceneHelpEvent) +}; + +class QGraphicsSceneDragDropEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneDragDropEvent : public QGraphicsSceneEvent +{ +public: + QGraphicsSceneDragDropEvent(Type type = None); + ~QGraphicsSceneDragDropEvent(); + + QPointF pos() const; + void setPos(const QPointF &pos); + + QPointF scenePos() const; + void setScenePos(const QPointF &pos); + + QPoint screenPos() const; + void setScreenPos(const QPoint &pos); + + Qt::MouseButtons buttons() const; + void setButtons(Qt::MouseButtons buttons); + + Qt::KeyboardModifiers modifiers() const; + void setModifiers(Qt::KeyboardModifiers modifiers); + + Qt::DropActions possibleActions() const; + void setPossibleActions(Qt::DropActions actions); + + Qt::DropAction proposedAction() const; + void setProposedAction(Qt::DropAction action); + void acceptProposedAction(); + + Qt::DropAction dropAction() const; + void setDropAction(Qt::DropAction action); + + QWidget *source() const; + void setSource(QWidget *source); + + const QMimeData *mimeData() const; + void setMimeData(const QMimeData *data); + +private: + Q_DECLARE_PRIVATE(QGraphicsSceneDragDropEvent) + Q_DISABLE_COPY(QGraphicsSceneDragDropEvent) +}; + +class QGraphicsSceneResizeEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneResizeEvent : public QGraphicsSceneEvent +{ + Q_DECLARE_PRIVATE(QGraphicsSceneResizeEvent) + Q_DISABLE_COPY(QGraphicsSceneResizeEvent) +public: + QGraphicsSceneResizeEvent(); + ~QGraphicsSceneResizeEvent(); + + QSizeF oldSize() const; + void setOldSize(const QSizeF &size); + + QSizeF newSize() const; + void setNewSize(const QSizeF &size); +}; + +class QGraphicsSceneMoveEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneMoveEvent : public QGraphicsSceneEvent +{ + Q_DECLARE_PRIVATE(QGraphicsSceneMoveEvent) + Q_DISABLE_COPY(QGraphicsSceneMoveEvent) +public: + QGraphicsSceneMoveEvent(); + ~QGraphicsSceneMoveEvent(); + + QPointF oldPos() const; + void setOldPos(const QPointF &pos); + + QPointF newPos() const; + void setNewPos(const QPointF &pos); +}; + +#endif // QT_NO_GRAPHICSVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/widgets/graphicsview/qgraphicssceneindex.cpp b/src/widgets/graphicsview/qgraphicssceneindex.cpp new file mode 100644 index 0000000000..964e9cb0ef --- /dev/null +++ b/src/widgets/graphicsview/qgraphicssceneindex.cpp @@ -0,0 +1,648 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ +/*! + \class QGraphicsSceneIndex + \brief The QGraphicsSceneIndex class provides a base class to implement + a custom indexing algorithm for discovering items in QGraphicsScene. + \since 4.6 + \ingroup graphicsview-api + + \internal + + The QGraphicsSceneIndex class provides a base class to implement + a custom indexing algorithm for discovering items in QGraphicsScene. You + need to subclass it and reimplement addItem, removeItem, estimateItems + and items in order to have an functional indexing. + + \sa QGraphicsScene, QGraphicsView +*/ + +#include "qdebug.h" +#include "qgraphicsscene.h" +#include "qgraphicsitem_p.h" +#include "qgraphicsscene_p.h" +#include "qgraphicswidget.h" +#include "qgraphicssceneindex_p.h" +#include "qgraphicsscenebsptreeindex_p.h" + +#ifndef QT_NO_GRAPHICSVIEW + +QT_BEGIN_NAMESPACE + +class QGraphicsSceneIndexRectIntersector : public QGraphicsSceneIndexIntersector +{ +public: + bool intersect(const QGraphicsItem *item, const QRectF &exposeRect, Qt::ItemSelectionMode mode, + const QTransform &deviceTransform) const + { + QRectF brect = item->boundingRect(); + _q_adjustRect(&brect); + + // ### Add test for this (without making things slower?) + Q_UNUSED(exposeRect); + + bool keep = true; + const QGraphicsItemPrivate *itemd = QGraphicsItemPrivate::get(item); + if (itemd->itemIsUntransformable()) { + // Untransformable items; map the scene rect to item coordinates. + const QTransform transform = item->deviceTransform(deviceTransform); + QRectF itemRect = (deviceTransform * transform.inverted()).mapRect(sceneRect); + if (mode == Qt::ContainsItemShape || mode == Qt::ContainsItemBoundingRect) + keep = itemRect.contains(brect) && itemRect != brect; + else + keep = itemRect.intersects(brect); + if (keep && (mode == Qt::ContainsItemShape || mode == Qt::IntersectsItemShape)) { + QPainterPath itemPath; + itemPath.addRect(itemRect); + keep = QGraphicsSceneIndexPrivate::itemCollidesWithPath(item, itemPath, mode); + } + } else { + Q_ASSERT(!itemd->dirtySceneTransform); + const QRectF itemSceneBoundingRect = itemd->sceneTransformTranslateOnly + ? brect.translated(itemd->sceneTransform.dx(), + itemd->sceneTransform.dy()) + : itemd->sceneTransform.mapRect(brect); + if (mode == Qt::ContainsItemShape || mode == Qt::ContainsItemBoundingRect) + keep = sceneRect != brect && sceneRect.contains(itemSceneBoundingRect); + else + keep = sceneRect.intersects(itemSceneBoundingRect); + if (keep && (mode == Qt::ContainsItemShape || mode == Qt::IntersectsItemShape)) { + QPainterPath rectPath; + rectPath.addRect(sceneRect); + if (itemd->sceneTransformTranslateOnly) + rectPath.translate(-itemd->sceneTransform.dx(), -itemd->sceneTransform.dy()); + else + rectPath = itemd->sceneTransform.inverted().map(rectPath); + keep = QGraphicsSceneIndexPrivate::itemCollidesWithPath(item, rectPath, mode); + } + } + return keep; + } + + QRectF sceneRect; +}; + +class QGraphicsSceneIndexPointIntersector : public QGraphicsSceneIndexIntersector +{ +public: + bool intersect(const QGraphicsItem *item, const QRectF &exposeRect, Qt::ItemSelectionMode mode, + const QTransform &deviceTransform) const + { + QRectF brect = item->boundingRect(); + _q_adjustRect(&brect); + + // ### Add test for this (without making things slower?) + Q_UNUSED(exposeRect); + + bool keep = false; + const QGraphicsItemPrivate *itemd = QGraphicsItemPrivate::get(item); + if (itemd->itemIsUntransformable()) { + // Untransformable items; map the scene point to item coordinates. + const QTransform transform = item->deviceTransform(deviceTransform); + QPointF itemPoint = (deviceTransform * transform.inverted()).map(scenePoint); + keep = brect.contains(itemPoint); + if (keep && (mode == Qt::ContainsItemShape || mode == Qt::IntersectsItemShape)) { + QPainterPath pointPath; + pointPath.addRect(QRectF(itemPoint, QSizeF(1, 1))); + keep = QGraphicsSceneIndexPrivate::itemCollidesWithPath(item, pointPath, mode); + } + } else { + Q_ASSERT(!itemd->dirtySceneTransform); + QRectF sceneBoundingRect = itemd->sceneTransformTranslateOnly + ? brect.translated(itemd->sceneTransform.dx(), + itemd->sceneTransform.dy()) + : itemd->sceneTransform.mapRect(brect); + keep = sceneBoundingRect.intersects(QRectF(scenePoint, QSizeF(1, 1))); + if (keep) { + QPointF p = itemd->sceneTransformTranslateOnly + ? QPointF(scenePoint.x() - itemd->sceneTransform.dx(), + scenePoint.y() - itemd->sceneTransform.dy()) + : itemd->sceneTransform.inverted().map(scenePoint); + keep = item->contains(p); + } + } + + return keep; + } + + QPointF scenePoint; +}; + +class QGraphicsSceneIndexPathIntersector : public QGraphicsSceneIndexIntersector +{ +public: + bool intersect(const QGraphicsItem *item, const QRectF &exposeRect, Qt::ItemSelectionMode mode, + const QTransform &deviceTransform) const + { + QRectF brect = item->boundingRect(); + _q_adjustRect(&brect); + + // ### Add test for this (without making things slower?) + Q_UNUSED(exposeRect); + + bool keep = true; + const QGraphicsItemPrivate *itemd = QGraphicsItemPrivate::get(item); + if (itemd->itemIsUntransformable()) { + // Untransformable items; map the scene rect to item coordinates. + const QTransform transform = item->deviceTransform(deviceTransform); + QPainterPath itemPath = (deviceTransform * transform.inverted()).map(scenePath); + if (mode == Qt::ContainsItemShape || mode == Qt::ContainsItemBoundingRect) + keep = itemPath.contains(brect); + else + keep = itemPath.intersects(brect); + if (keep && (mode == Qt::ContainsItemShape || mode == Qt::IntersectsItemShape)) + keep = QGraphicsSceneIndexPrivate::itemCollidesWithPath(item, itemPath, mode); + } else { + Q_ASSERT(!itemd->dirtySceneTransform); + const QRectF itemSceneBoundingRect = itemd->sceneTransformTranslateOnly + ? brect.translated(itemd->sceneTransform.dx(), + itemd->sceneTransform.dy()) + : itemd->sceneTransform.mapRect(brect); + if (mode == Qt::ContainsItemShape || mode == Qt::ContainsItemBoundingRect) + keep = scenePath.contains(itemSceneBoundingRect); + else + keep = scenePath.intersects(itemSceneBoundingRect); + if (keep && (mode == Qt::ContainsItemShape || mode == Qt::IntersectsItemShape)) { + QPainterPath itemPath = itemd->sceneTransformTranslateOnly + ? scenePath.translated(-itemd->sceneTransform.dx(), + -itemd->sceneTransform.dy()) + : itemd->sceneTransform.inverted().map(scenePath); + keep = QGraphicsSceneIndexPrivate::itemCollidesWithPath(item, itemPath, mode); + } + } + return keep; + } + + QPainterPath scenePath; +}; + +/*! + Constructs a private scene index. +*/ +QGraphicsSceneIndexPrivate::QGraphicsSceneIndexPrivate(QGraphicsScene *scene) : scene(scene) +{ + pointIntersector = new QGraphicsSceneIndexPointIntersector; + rectIntersector = new QGraphicsSceneIndexRectIntersector; + pathIntersector = new QGraphicsSceneIndexPathIntersector; +} + +/*! + Destructor of private scene index. +*/ +QGraphicsSceneIndexPrivate::~QGraphicsSceneIndexPrivate() +{ + delete pointIntersector; + delete rectIntersector; + delete pathIntersector; +} + +/*! + \internal + + Checks if item collides with the path and mode, but also checks that if it + doesn't collide, maybe its frame rect will. +*/ +bool QGraphicsSceneIndexPrivate::itemCollidesWithPath(const QGraphicsItem *item, + const QPainterPath &path, + Qt::ItemSelectionMode mode) +{ + if (item->collidesWithPath(path, mode)) + return true; + if (item->isWidget()) { + // Check if this is a window, and if its frame rect collides. + const QGraphicsWidget *widget = static_cast(item); + if (widget->isWindow()) { + QRectF frameRect = widget->windowFrameRect(); + QPainterPath framePath; + framePath.addRect(frameRect); + bool intersects = path.intersects(frameRect); + if (mode == Qt::IntersectsItemShape || mode == Qt::IntersectsItemBoundingRect) + return intersects || path.contains(frameRect.topLeft()) + || framePath.contains(path.elementAt(0)); + return !intersects && path.contains(frameRect.topLeft()); + } + } + return false; +} + +/*! + \internal + This function returns the items in ascending order. +*/ +void QGraphicsSceneIndexPrivate::recursive_items_helper(QGraphicsItem *item, QRectF exposeRect, + QGraphicsSceneIndexIntersector *intersector, + QList *items, + const QTransform &viewTransform, + Qt::ItemSelectionMode mode, + qreal parentOpacity) const +{ + Q_ASSERT(item); + if (!item->d_ptr->visible) + return; + + const qreal opacity = item->d_ptr->combineOpacityFromParent(parentOpacity); + const bool itemIsFullyTransparent = QGraphicsItemPrivate::isOpacityNull(opacity); + const bool itemHasChildren = !item->d_ptr->children.isEmpty(); + if (itemIsFullyTransparent && (!itemHasChildren || item->d_ptr->childrenCombineOpacity())) + return; + + // Update the item's scene transform if dirty. + const bool itemIsUntransformable = item->d_ptr->itemIsUntransformable(); + const bool wasDirtyParentSceneTransform = item->d_ptr->dirtySceneTransform && !itemIsUntransformable; + if (wasDirtyParentSceneTransform) { + item->d_ptr->updateSceneTransformFromParent(); + Q_ASSERT(!item->d_ptr->dirtySceneTransform); + } + + const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape); + bool processItem = !itemIsFullyTransparent; + if (processItem) { + processItem = intersector->intersect(item, exposeRect, mode, viewTransform); + if (!processItem && (!itemHasChildren || itemClipsChildrenToShape)) { + if (wasDirtyParentSceneTransform) + item->d_ptr->invalidateChildrenSceneTransform(); + return; + } + } // else we know for sure this item has children we must process. + + int i = 0; + if (itemHasChildren) { + // Sort children. + item->d_ptr->ensureSortedChildren(); + + // Clip to shape. + if (itemClipsChildrenToShape && !itemIsUntransformable) { + QPainterPath mappedShape = item->d_ptr->sceneTransformTranslateOnly + ? item->shape().translated(item->d_ptr->sceneTransform.dx(), + item->d_ptr->sceneTransform.dy()) + : item->d_ptr->sceneTransform.map(item->shape()); + exposeRect &= mappedShape.controlPointRect(); + } + + // Process children behind + for (i = 0; i < item->d_ptr->children.size(); ++i) { + QGraphicsItem *child = item->d_ptr->children.at(i); + if (wasDirtyParentSceneTransform) + child->d_ptr->dirtySceneTransform = 1; + if (!(child->d_ptr->flags & QGraphicsItem::ItemStacksBehindParent)) + break; + if (itemIsFullyTransparent && !(child->d_ptr->flags & QGraphicsItem::ItemIgnoresParentOpacity)) + continue; + recursive_items_helper(child, exposeRect, intersector, items, viewTransform, + mode, opacity); + } + } + + // Process item + if (processItem) + items->append(item); + + // Process children in front + if (itemHasChildren) { + for (; i < item->d_ptr->children.size(); ++i) { + QGraphicsItem *child = item->d_ptr->children.at(i); + if (wasDirtyParentSceneTransform) + child->d_ptr->dirtySceneTransform = 1; + if (itemIsFullyTransparent && !(child->d_ptr->flags & QGraphicsItem::ItemIgnoresParentOpacity)) + continue; + recursive_items_helper(child, exposeRect, intersector, items, viewTransform, + mode, opacity); + } + } +} + +void QGraphicsSceneIndexPrivate::init() +{ + if (!scene) + return; + + QObject::connect(scene, SIGNAL(sceneRectChanged(QRectF)), + q_func(), SLOT(updateSceneRect(QRectF))); +} + +/*! + Constructs an abstract scene index for a given \a scene. +*/ +QGraphicsSceneIndex::QGraphicsSceneIndex(QGraphicsScene *scene) +: QObject(*new QGraphicsSceneIndexPrivate(scene), scene) +{ + d_func()->init(); +} + +/*! + \internal +*/ +QGraphicsSceneIndex::QGraphicsSceneIndex(QGraphicsSceneIndexPrivate &dd, QGraphicsScene *scene) + : QObject(dd, scene) +{ + d_func()->init(); +} + +/*! + Destroys the scene index. +*/ +QGraphicsSceneIndex::~QGraphicsSceneIndex() +{ + +} + +/*! + Returns the scene of this index. +*/ +QGraphicsScene* QGraphicsSceneIndex::scene() const +{ + Q_D(const QGraphicsSceneIndex); + return d->scene; +} + +/*! + \fn QList QGraphicsSceneIndex::items(const QPointF &pos, + Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform + &deviceTransform) const + + Returns all visible items that, depending on \a mode, are at the specified + \a pos and return a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with \a pos are returned. + + \a deviceTransform is the transformation apply to the view. + + This method use the estimation of the index (estimateItems) and refine the + list to get an exact result. If you want to implement your own refinement + algorithm you can reimplement this method. + + \sa estimateItems() + +*/ +QList QGraphicsSceneIndex::items(const QPointF &pos, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + + Q_D(const QGraphicsSceneIndex); + QList itemList; + d->pointIntersector->scenePoint = pos; + d->items_helper(QRectF(pos, QSizeF(1, 1)), d->pointIntersector, &itemList, deviceTransform, mode, order); + return itemList; +} + +/*! + \fn QList QGraphicsSceneIndex::items(const QRectF &rect, + Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform + &deviceTransform) const + + \overload + + Returns all visible items that, depending on \a mode, are either inside or + intersect with the specified \a rect and return a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a rect are returned. + + \a deviceTransform is the transformation apply to the view. + + This method use the estimation of the index (estimateItems) and refine + the list to get an exact result. If you want to implement your own + refinement algorithm you can reimplement this method. + + \sa estimateItems() + +*/ +QList QGraphicsSceneIndex::items(const QRectF &rect, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsSceneIndex); + QRectF exposeRect = rect; + _q_adjustRect(&exposeRect); + QList itemList; + d->rectIntersector->sceneRect = rect; + d->items_helper(exposeRect, d->rectIntersector, &itemList, deviceTransform, mode, order); + return itemList; +} + +/*! + \fn QList QGraphicsSceneIndex::items(const QPolygonF + &polygon, Qt::ItemSelectionMode mode, Qt::SortOrder order, const + QTransform &deviceTransform) const + + \overload + + Returns all visible items that, depending on \a mode, are either inside or + intersect with the specified \a polygon and return a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a polygon are returned. + + \a deviceTransform is the transformation apply to the view. + + This method use the estimation of the index (estimateItems) and refine + the list to get an exact result. If you want to implement your own + refinement algorithm you can reimplement this method. + + \sa estimateItems() + +*/ +QList QGraphicsSceneIndex::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsSceneIndex); + QList itemList; + QRectF exposeRect = polygon.boundingRect(); + _q_adjustRect(&exposeRect); + QPainterPath path; + path.addPolygon(polygon); + d->pathIntersector->scenePath = path; + d->items_helper(exposeRect, d->pathIntersector, &itemList, deviceTransform, mode, order); + return itemList; +} + +/*! + \fn QList QGraphicsSceneIndex::items(const QPainterPath + &path, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform + &deviceTransform) const + + \overload + + Returns all visible items that, depending on \a mode, are either inside or + intersect with the specified \a path and return a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a path are returned. + + \a deviceTransform is the transformation apply to the view. + + This method use the estimation of the index (estimateItems) and refine + the list to get an exact result. If you want to implement your own + refinement algorithm you can reimplement this method. + + \sa estimateItems() + +*/ +QList QGraphicsSceneIndex::items(const QPainterPath &path, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsSceneIndex); + QList itemList; + QRectF exposeRect = path.controlPointRect(); + _q_adjustRect(&exposeRect); + d->pathIntersector->scenePath = path; + d->items_helper(exposeRect, d->pathIntersector, &itemList, deviceTransform, mode, order); + return itemList; +} + +/*! + This virtual function return an estimation of items at position \a point. + This method return a list sorted using \a order. +*/ +QList QGraphicsSceneIndex::estimateItems(const QPointF &point, Qt::SortOrder order) const +{ + return estimateItems(QRectF(point, QSize(1, 1)), order); +} + +QList QGraphicsSceneIndex::estimateTopLevelItems(const QRectF &rect, Qt::SortOrder order) const +{ + Q_D(const QGraphicsSceneIndex); + Q_UNUSED(rect); + QGraphicsScenePrivate *scened = d->scene->d_func(); + scened->ensureSortedTopLevelItems(); + if (order == Qt::DescendingOrder) { + QList sorted; + for (int i = scened->topLevelItems.size() - 1; i >= 0; --i) + sorted << scened->topLevelItems.at(i); + return sorted; + } + return scened->topLevelItems; +} + +/*! + \fn QList QGraphicsSceneIndex::items(Qt::SortOrder order = Qt::DescendingOrder) const + + This pure virtual function all items in the index and sort them using + \a order. +*/ + + +/*! + Notifies the index that the scene's scene rect has changed. \a rect + is thew new scene rect. + + \sa QGraphicsScene::sceneRect() +*/ +void QGraphicsSceneIndex::updateSceneRect(const QRectF &rect) +{ + Q_UNUSED(rect); +} + +/*! + This virtual function removes all items in the scene index. +*/ +void QGraphicsSceneIndex::clear() +{ + const QList allItems = items(); + for (int i = 0 ; i < allItems.size(); ++i) + removeItem(allItems.at(i)); +} + +/*! + \fn virtual void QGraphicsSceneIndex::addItem(QGraphicsItem *item) = 0 + + This pure virtual function inserts an \a item to the scene index. + + \sa removeItem(), deleteItem() +*/ + +/*! + \fn virtual void QGraphicsSceneIndex::removeItem(QGraphicsItem *item) = 0 + + This pure virtual function removes an \a item to the scene index. + + \sa addItem(), deleteItem() +*/ + +/*! + This method is called when an \a item has been deleted. + The default implementation call removeItem. Be carefull, + if your implementation of removeItem use pure virtual method + of QGraphicsItem like boundingRect(), then you should reimplement + this method. + + \sa addItem(), removeItem() +*/ +void QGraphicsSceneIndex::deleteItem(QGraphicsItem *item) +{ + removeItem(item); +} + +/*! + This virtual function is called by QGraphicsItem to notify the index + that some part of the \a item 's state changes. By reimplementing this + function, your can react to a change, and in some cases, (depending on \a + change,) adjustments in the index can be made. + + \a change is the parameter of the item that is changing. \a value is the + value that changed; the type of the value depends on \a change. + + The default implementation does nothing. + + \sa QGraphicsItem::GraphicsItemChange +*/ +void QGraphicsSceneIndex::itemChange(const QGraphicsItem *item, QGraphicsItem::GraphicsItemChange change, const void *const value) +{ + Q_UNUSED(item); + Q_UNUSED(change); + Q_UNUSED(value); +} + +/*! + Notify the index for a geometry change of an \a item. + + \sa QGraphicsItem::prepareGeometryChange() +*/ +void QGraphicsSceneIndex::prepareBoundingRectChange(const QGraphicsItem *item) +{ + Q_UNUSED(item); +} + +QT_END_NAMESPACE + +#include "moc_qgraphicssceneindex_p.cpp" + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicssceneindex_p.h b/src/widgets/graphicsview/qgraphicssceneindex_p.h new file mode 100644 index 0000000000..e498e822fa --- /dev/null +++ b/src/widgets/graphicsview/qgraphicssceneindex_p.h @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** 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 QtCore module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSCENEINDEX_H +#define QGRAPHICSSCENEINDEX_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicsscene_p.h" +#include "qgraphicsscene.h" +#include + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsSceneIndexIntersector; +class QGraphicsSceneIndexPointIntersector; +class QGraphicsSceneIndexRectIntersector; +class QGraphicsSceneIndexPathIntersector; +class QGraphicsSceneIndexPrivate; +class QPointF; +class QRectF; +template class QList; + +class Q_AUTOTEST_EXPORT QGraphicsSceneIndex : public QObject +{ + Q_OBJECT + +public: + QGraphicsSceneIndex(QGraphicsScene *scene = 0); + virtual ~QGraphicsSceneIndex(); + + QGraphicsScene *scene() const; + + virtual QList items(Qt::SortOrder order = Qt::DescendingOrder) const = 0; + virtual QList items(const QPointF &pos, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + virtual QList items(const QRectF &rect, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + virtual QList items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + virtual QList items(const QPainterPath &path, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + virtual QList estimateItems(const QPointF &point, Qt::SortOrder order) const; + virtual QList estimateItems(const QRectF &rect, Qt::SortOrder order) const = 0; + virtual QList estimateTopLevelItems(const QRectF &, Qt::SortOrder order) const; + +protected Q_SLOTS: + virtual void updateSceneRect(const QRectF &rect); + +protected: + virtual void clear(); + virtual void addItem(QGraphicsItem *item) = 0; + virtual void removeItem(QGraphicsItem *item) = 0; + virtual void deleteItem(QGraphicsItem *item); + + virtual void itemChange(const QGraphicsItem *item, QGraphicsItem::GraphicsItemChange, const void *const value); + virtual void prepareBoundingRectChange(const QGraphicsItem *item); + + QGraphicsSceneIndex(QGraphicsSceneIndexPrivate &dd, QGraphicsScene *scene); + + friend class QGraphicsScene; + friend class QGraphicsScenePrivate; + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; + friend class QGraphicsSceneBspTreeIndex; +private: + Q_DISABLE_COPY(QGraphicsSceneIndex) + Q_DECLARE_PRIVATE(QGraphicsSceneIndex) +}; + +class QGraphicsSceneIndexPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneIndex) +public: + QGraphicsSceneIndexPrivate(QGraphicsScene *scene); + ~QGraphicsSceneIndexPrivate(); + + void init(); + static bool itemCollidesWithPath(const QGraphicsItem *item, const QPainterPath &path, Qt::ItemSelectionMode mode); + + void recursive_items_helper(QGraphicsItem *item, QRectF exposeRect, + QGraphicsSceneIndexIntersector *intersector, QList *items, + const QTransform &viewTransform, + Qt::ItemSelectionMode mode, qreal parentOpacity = 1.0) const; + inline void items_helper(const QRectF &rect, QGraphicsSceneIndexIntersector *intersector, + QList *items, const QTransform &viewTransform, + Qt::ItemSelectionMode mode, Qt::SortOrder order) const; + + QGraphicsScene *scene; + QGraphicsSceneIndexPointIntersector *pointIntersector; + QGraphicsSceneIndexRectIntersector *rectIntersector; + QGraphicsSceneIndexPathIntersector *pathIntersector; +}; + +inline void QGraphicsSceneIndexPrivate::items_helper(const QRectF &rect, QGraphicsSceneIndexIntersector *intersector, + QList *items, const QTransform &viewTransform, + Qt::ItemSelectionMode mode, Qt::SortOrder order) const +{ + Q_Q(const QGraphicsSceneIndex); + const QList tli = q->estimateTopLevelItems(rect, Qt::AscendingOrder); + for (int i = 0; i < tli.size(); ++i) + recursive_items_helper(tli.at(i), rect, intersector, items, viewTransform, mode); + if (order == Qt::DescendingOrder) { + const int n = items->size(); + for (int i = 0; i < n / 2; ++i) + items->swap(i, n - i - 1); + } +} + +class QGraphicsSceneIndexIntersector +{ +public: + QGraphicsSceneIndexIntersector() { } + virtual ~QGraphicsSceneIndexIntersector() { } + virtual bool intersect(const QGraphicsItem *item, const QRectF &exposeRect, Qt::ItemSelectionMode mode, + const QTransform &deviceTransform) const = 0; +}; + +#endif // QT_NO_GRAPHICSVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGRAPHICSSCENEINDEX_H diff --git a/src/widgets/graphicsview/qgraphicsscenelinearindex.cpp b/src/widgets/graphicsview/qgraphicsscenelinearindex.cpp new file mode 100644 index 0000000000..074d43961a --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsscenelinearindex.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +/*! + \class QGraphicsSceneLinearIndex + \brief The QGraphicsSceneLinearIndex class provides an implementation of + a linear indexing algorithm for discovering items in QGraphicsScene. + \since 4.6 + \ingroup graphicsview-api + \internal + + QGraphicsSceneLinearIndex index is default linear implementation to discover items. + It basically store all items in a list and return them to the scene. + + \sa QGraphicsScene, QGraphicsView, QGraphicsSceneIndex, QGraphicsSceneBspTreeIndex +*/ + +#include + +/*! + \fn QGraphicsSceneLinearIndex::QGraphicsSceneLinearIndex(QGraphicsScene *scene = 0): + + Construct a linear index for the given \a scene. +*/ + +/*! + \fn QList QGraphicsSceneLinearIndex::items(Qt::SortOrder order = Qt::DescendingOrder) const; + + Return all items in the index and sort them using \a order. +*/ + + +/*! + \fn virtual QList QGraphicsSceneLinearIndex::estimateItems(const QRectF &rect, Qt::SortOrder order) const + + Returns an estimation visible items that are either inside or + intersect with the specified \a rect and return a list sorted using \a order. +*/ + +/*! + \fn void QGraphicsSceneLinearIndex::clear() + \internal + Clear the all the BSP index. +*/ + +/*! + \fn virtual void QGraphicsSceneLinearIndex::addItem(QGraphicsItem *item) + + Add the \a item into the index. +*/ + +/*! + \fn virtual void QGraphicsSceneLinearIndex::removeItem(QGraphicsItem *item) + + Add the \a item from the index. +*/ + diff --git a/src/widgets/graphicsview/qgraphicsscenelinearindex_p.h b/src/widgets/graphicsview/qgraphicsscenelinearindex_p.h new file mode 100644 index 0000000000..ef72f57faf --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsscenelinearindex_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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 QtCore module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSCENELINEARINDEX_H +#define QGRAPHICSSCENELINEARINDEX_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_AUTOTEST_EXPORT QGraphicsSceneLinearIndex : public QGraphicsSceneIndex +{ + Q_OBJECT + +public: + QGraphicsSceneLinearIndex(QGraphicsScene *scene = 0) : QGraphicsSceneIndex(scene) + { } + + QList items(Qt::SortOrder order = Qt::DescendingOrder) const + { Q_UNUSED(order); return m_items; } + + virtual QList estimateItems(const QRectF &rect, Qt::SortOrder order) const + { + Q_UNUSED(rect); + Q_UNUSED(order); + return m_items; + } + +protected : + virtual void clear() + { m_items.clear(); } + + virtual void addItem(QGraphicsItem *item) + { m_items << item; } + + virtual void removeItem(QGraphicsItem *item) + { m_items.removeOne(item); } + +private: + QList m_items; +}; + +#endif // QT_NO_GRAPHICSVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGRAPHICSSCENELINEARINDEX_H diff --git a/src/widgets/graphicsview/qgraphicstransform.cpp b/src/widgets/graphicsview/qgraphicstransform.cpp new file mode 100644 index 0000000000..513c41f46f --- /dev/null +++ b/src/widgets/graphicsview/qgraphicstransform.cpp @@ -0,0 +1,594 @@ +/**************************************************************************** +** +** 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 QtDeclarative module 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$ +** +****************************************************************************/ + +/*! + \class QGraphicsTransform + \brief The QGraphicsTransform class is an abstract base class for building + advanced transformations on QGraphicsItems. + \since 4.6 + \ingroup graphicsview-api + + As an alternative to QGraphicsItem::transform, QGraphicsTransform lets you + create and control advanced transformations that can be configured + independently using specialized properties. + + QGraphicsItem allows you to assign any number of QGraphicsTransform + instances to one QGraphicsItem. Each QGraphicsTransform is applied in + order, one at a time, to the QGraphicsItem it's assigned to. + + QGraphicsTransform is particularly useful for animations. Whereas + QGraphicsItem::setTransform() lets you assign any transform directly to an + item, there is no direct way to interpolate between two different + transformations (e.g., when transitioning between two states, each for + which the item has a different arbitrary transform assigned). Using + QGraphicsTransform you can interpolate the property values of each + independent transformation. The resulting operation is then combined into a + single transform which is applied to QGraphicsItem. + + Transformations are computed in true 3D space using QMatrix4x4. + When the transformation is applied to a QGraphicsItem, it will be + projected back to a 2D QTransform. When multiple QGraphicsTransform + objects are applied to a QGraphicsItem, all of the transformations + are computed in true 3D space, with the projection back to 2D + only occurring after the last QGraphicsTransform is applied. + The exception to this is QGraphicsRotation, which projects back to + 2D after each rotation to preserve the perspective effect around + the X and Y axes. + + If you want to create your own configurable transformation, you can create + a subclass of QGraphicsTransform (or any or the existing subclasses), and + reimplement the pure virtual applyTo() function, which takes a pointer to a + QMatrix4x4. Each operation you would like to apply should be exposed as + properties (e.g., customTransform->setVerticalShear(2.5)). Inside you + reimplementation of applyTo(), you can modify the provided transform + respectively. + + QGraphicsTransform can be used together with QGraphicsItem::setTransform(), + QGraphicsItem::setRotation(), and QGraphicsItem::setScale(). + + \sa QGraphicsItem::transform(), QGraphicsScale, QGraphicsRotation +*/ + +#include "qgraphicstransform.h" +#include "qgraphicsitem_p.h" +#include "qgraphicstransform_p.h" +#include +#include +#include + +#ifndef QT_NO_GRAPHICSVIEW +QT_BEGIN_NAMESPACE +void QGraphicsTransformPrivate::setItem(QGraphicsItem *i) +{ + if (item == i) + return; + + if (item) { + Q_Q(QGraphicsTransform); + QGraphicsItemPrivate *d_ptr = item->d_ptr.data(); + + item->prepareGeometryChange(); + Q_ASSERT(d_ptr->transformData); + d_ptr->transformData->graphicsTransforms.removeAll(q); + d_ptr->dirtySceneTransform = 1; + item = 0; + } + + item = i; +} + +void QGraphicsTransformPrivate::updateItem(QGraphicsItem *item) +{ + item->prepareGeometryChange(); + item->d_ptr->dirtySceneTransform = 1; +} + +/*! + Constructs a new QGraphicsTransform with the given \a parent. +*/ +QGraphicsTransform::QGraphicsTransform(QObject *parent) + : QObject(*new QGraphicsTransformPrivate, parent) +{ +} + +/*! + Destroys the graphics transform. +*/ +QGraphicsTransform::~QGraphicsTransform() +{ + Q_D(QGraphicsTransform); + d->setItem(0); +} + +/*! + \internal +*/ +QGraphicsTransform::QGraphicsTransform(QGraphicsTransformPrivate &p, QObject *parent) + : QObject(p, parent) +{ +} + +/*! + \fn void QGraphicsTransform::applyTo(QMatrix4x4 *matrix) const + + This pure virtual method has to be reimplemented in derived classes. + + It applies this transformation to \a matrix. + + \sa QGraphicsItem::transform(), QMatrix4x4::toTransform() +*/ + +/*! + Notifies that this transform operation has changed its parameters in such a + way that applyTo() will return a different result than before. + + When implementing you own custom graphics transform, you must call this + function every time you change a parameter, to let QGraphicsItem know that + its transformation needs to be updated. + + \sa applyTo() +*/ +void QGraphicsTransform::update() +{ + Q_D(QGraphicsTransform); + if (d->item) + d->updateItem(d->item); +} + +/*! + \class QGraphicsScale + \brief The QGraphicsScale class provides a scale transformation. + \since 4.6 + + QGraphicsScene provides certain parameters to help control how the scale + should be applied. + + The origin is the point that the item is scaled from (i.e., it stays fixed + relative to the parent as the rest of the item grows). By default the + origin is QPointF(0, 0). + + The parameters xScale, yScale, and zScale describe the scale factors to + apply in horizontal, vertical, and depth directions. They can take on any + value, including 0 (to collapse the item to a point) or negative value. + A negative xScale value will mirror the item horizontally. A negative yScale + value will flip the item vertically. A negative zScale will flip the + item end for end. + + \sa QGraphicsTransform, QGraphicsItem::setScale(), QTransform::scale() +*/ + +class QGraphicsScalePrivate : public QGraphicsTransformPrivate +{ +public: + QGraphicsScalePrivate() + : xScale(1), yScale(1), zScale(1) {} + QVector3D origin; + qreal xScale; + qreal yScale; + qreal zScale; +}; + +/*! + Constructs an empty QGraphicsScale object with the given \a parent. +*/ +QGraphicsScale::QGraphicsScale(QObject *parent) + : QGraphicsTransform(*new QGraphicsScalePrivate, parent) +{ +} + +/*! + Destroys the graphics scale. +*/ +QGraphicsScale::~QGraphicsScale() +{ +} + +/*! + \property QGraphicsScale::origin + \brief the origin of the scale in 3D space. + + All scaling will be done relative to this point (i.e., this point + will stay fixed, relative to the parent, when the item is scaled). + + \sa xScale, yScale, zScale +*/ +QVector3D QGraphicsScale::origin() const +{ + Q_D(const QGraphicsScale); + return d->origin; +} +void QGraphicsScale::setOrigin(const QVector3D &point) +{ + Q_D(QGraphicsScale); + if (d->origin == point) + return; + d->origin = point; + update(); + emit originChanged(); +} + +/*! + \property QGraphicsScale::xScale + \brief the horizontal scale factor. + + The scale factor can be any real number; the default value is 1.0. If you + set the factor to 0.0, the item will be collapsed to a single point. If you + provide a negative value, the item will be mirrored horizontally around its + origin. + + \sa yScale, zScale, origin +*/ +qreal QGraphicsScale::xScale() const +{ + Q_D(const QGraphicsScale); + return d->xScale; +} +void QGraphicsScale::setXScale(qreal scale) +{ + Q_D(QGraphicsScale); + if (d->xScale == scale) + return; + d->xScale = scale; + update(); + emit xScaleChanged(); + emit scaleChanged(); +} + +/*! + \property QGraphicsScale::yScale + \brief the vertical scale factor. + + The scale factor can be any real number; the default value is 1.0. If you + set the factor to 0.0, the item will be collapsed to a single point. If you + provide a negative value, the item will be flipped vertically around its + origin. + + \sa xScale, zScale, origin +*/ +qreal QGraphicsScale::yScale() const +{ + Q_D(const QGraphicsScale); + return d->yScale; +} +void QGraphicsScale::setYScale(qreal scale) +{ + Q_D(QGraphicsScale); + if (d->yScale == scale) + return; + d->yScale = scale; + update(); + emit yScaleChanged(); + emit scaleChanged(); +} + +/*! + \property QGraphicsScale::zScale + \brief the depth scale factor. + + The scale factor can be any real number; the default value is 1.0. If you + set the factor to 0.0, the item will be collapsed to a single point. If you + provide a negative value, the item will be flipped end for end around its + origin. + + \sa xScale, yScale, origin +*/ +qreal QGraphicsScale::zScale() const +{ + Q_D(const QGraphicsScale); + return d->zScale; +} +void QGraphicsScale::setZScale(qreal scale) +{ + Q_D(QGraphicsScale); + if (d->zScale == scale) + return; + d->zScale = scale; + update(); + emit zScaleChanged(); + emit scaleChanged(); +} + +/*! + \reimp +*/ +void QGraphicsScale::applyTo(QMatrix4x4 *matrix) const +{ + Q_D(const QGraphicsScale); + matrix->translate(d->origin); + matrix->scale(d->xScale, d->yScale, d->zScale); + matrix->translate(-d->origin); +} + +/*! + \fn QGraphicsScale::originChanged() + + QGraphicsScale emits this signal when its origin changes. + + \sa QGraphicsScale::origin +*/ + +/*! + \fn QGraphicsScale::xScaleChanged() + \since 4.7 + + This signal is emitted whenever the \l xScale property changes. +*/ + +/*! + \fn QGraphicsScale::yScaleChanged() + \since 4.7 + + This signal is emitted whenever the \l yScale property changes. +*/ + +/*! + \fn QGraphicsScale::zScaleChanged() + \since 4.7 + + This signal is emitted whenever the \l zScale property changes. +*/ + +/*! + \fn QGraphicsScale::scaleChanged() + + This signal is emitted whenever the xScale, yScale, or zScale + of the object changes. + + \sa QGraphicsScale::xScale, QGraphicsScale::yScale + \sa QGraphicsScale::zScale +*/ + +/*! + \class QGraphicsRotation + \brief The QGraphicsRotation class provides a rotation transformation around + a given axis. + \since 4.6 + + You can provide the desired axis by assigning a QVector3D to the axis property + or by passing a member if Qt::Axis to the setAxis convenience function. + By default the axis is (0, 0, 1) i.e., rotation around the Z axis. + + The angle property, which is provided by QGraphicsRotation, now + describes the number of degrees to rotate around this axis. + + QGraphicsRotation provides certain parameters to help control how the + rotation should be applied. + + The origin is the point that the item is rotated around (i.e., it stays + fixed relative to the parent as the rest of the item is rotated). By + default the origin is QPointF(0, 0). + + The angle property provides the number of degrees to rotate the item + clockwise around the origin. This value also be negative, indicating a + counter-clockwise rotation. For animation purposes it may also be useful to + provide rotation angles exceeding (-360, 360) degrees, for instance to + animate how an item rotates several times. + + Note: the final rotation is the combined effect of a rotation in + 3D space followed by a projection back to 2D. If several rotations + are performed in succession, they will not behave as expected unless + they were all around the Z axis. + + \sa QGraphicsTransform, QGraphicsItem::setRotation(), QTransform::rotate() +*/ + +class QGraphicsRotationPrivate : public QGraphicsTransformPrivate +{ +public: + QGraphicsRotationPrivate() + : angle(0), axis(0, 0, 1) {} + QVector3D origin; + qreal angle; + QVector3D axis; +}; + +/*! + Constructs a new QGraphicsRotation with the given \a parent. +*/ +QGraphicsRotation::QGraphicsRotation(QObject *parent) + : QGraphicsTransform(*new QGraphicsRotationPrivate, parent) +{ +} + +/*! + Destroys the graphics rotation. +*/ +QGraphicsRotation::~QGraphicsRotation() +{ +} + +/*! + \property QGraphicsRotation::origin + \brief the origin of the rotation in 3D space. + + All rotations will be done relative to this point (i.e., this point + will stay fixed, relative to the parent, when the item is rotated). + + \sa angle +*/ +QVector3D QGraphicsRotation::origin() const +{ + Q_D(const QGraphicsRotation); + return d->origin; +} +void QGraphicsRotation::setOrigin(const QVector3D &point) +{ + Q_D(QGraphicsRotation); + if (d->origin == point) + return; + d->origin = point; + update(); + emit originChanged(); +} + +/*! + \property QGraphicsRotation::angle + \brief the angle for clockwise rotation, in degrees. + + The angle can be any real number; the default value is 0.0. A value of 180 + will rotate 180 degrees, clockwise. If you provide a negative number, the + item will be rotated counter-clockwise. Normally the rotation angle will be + in the range (-360, 360), but you can also provide numbers outside of this + range (e.g., a angle of 370 degrees gives the same result as 10 degrees). + Setting the angle to NaN results in no rotation. + + \sa origin +*/ +qreal QGraphicsRotation::angle() const +{ + Q_D(const QGraphicsRotation); + return d->angle; +} +void QGraphicsRotation::setAngle(qreal angle) +{ + Q_D(QGraphicsRotation); + if (d->angle == angle) + return; + d->angle = angle; + update(); + emit angleChanged(); +} + +/*! + \fn QGraphicsRotation::originChanged() + + This signal is emitted whenever the origin has changed. + + \sa QGraphicsRotation::origin +*/ + +/*! + \fn void QGraphicsRotation::angleChanged() + + This signal is emitted whenever the angle has changed. + + \sa QGraphicsRotation::angle +*/ + +/*! + \property QGraphicsRotation::axis + \brief a rotation axis, specified by a vector in 3D space. + + This can be any axis in 3D space. By default the axis is (0, 0, 1), + which is aligned with the Z axis. If you provide another axis, + QGraphicsRotation will provide a transformation that rotates + around this axis. For example, if you would like to rotate an item + around its X axis, you could pass (1, 0, 0) as the axis. + + \sa QTransform, QGraphicsRotation::angle +*/ +QVector3D QGraphicsRotation::axis() const +{ + Q_D(const QGraphicsRotation); + return d->axis; +} +void QGraphicsRotation::setAxis(const QVector3D &axis) +{ + Q_D(QGraphicsRotation); + if (d->axis == axis) + return; + d->axis = axis; + update(); + emit axisChanged(); +} + +/*! + \fn void QGraphicsRotation::setAxis(Qt::Axis axis) + + Convenience function to set the axis to \a axis. + + Note: the Qt::YAxis rotation for QTransform is inverted from the + correct mathematical rotation in 3D space. The QGraphicsRotation + class implements a correct mathematical rotation. The following + two sequences of code will perform the same transformation: + + \code + QTransform t; + t.rotate(45, Qt::YAxis); + + QGraphicsRotation r; + r.setAxis(Qt::YAxis); + r.setAngle(-45); + \endcode +*/ +void QGraphicsRotation::setAxis(Qt::Axis axis) +{ + switch (axis) + { + case Qt::XAxis: + setAxis(QVector3D(1, 0, 0)); + break; + case Qt::YAxis: + setAxis(QVector3D(0, 1, 0)); + break; + case Qt::ZAxis: + setAxis(QVector3D(0, 0, 1)); + break; + } +} + +/*! + \reimp +*/ +void QGraphicsRotation::applyTo(QMatrix4x4 *matrix) const +{ + Q_D(const QGraphicsRotation); + + if (d->angle == 0. || d->axis.isNull() || qIsNaN(d->angle)) + return; + + matrix->translate(d->origin); + matrix->projectedRotate(d->angle, d->axis.x(), d->axis.y(), d->axis.z()); + matrix->translate(-d->origin); +} + +/*! + \fn void QGraphicsRotation::axisChanged() + + This signal is emitted whenever the axis of the object changes. + + \sa QGraphicsRotation::axis +*/ + +#include "moc_qgraphicstransform.cpp" + +QT_END_NAMESPACE +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicstransform.h b/src/widgets/graphicsview/qgraphicstransform.h new file mode 100644 index 0000000000..e2dd05b5be --- /dev/null +++ b/src/widgets/graphicsview/qgraphicstransform.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSTRANSFORM_H +#define QGRAPHICSTRANSFORM_H + +#include +#include +#include +#include + +#ifndef QT_NO_GRAPHICSVIEW +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QGraphicsItem; +class QGraphicsTransformPrivate; + +class Q_GUI_EXPORT QGraphicsTransform : public QObject +{ + Q_OBJECT +public: + QGraphicsTransform(QObject *parent = 0); + ~QGraphicsTransform(); + + virtual void applyTo(QMatrix4x4 *matrix) const = 0; + +protected Q_SLOTS: + void update(); + +protected: + QGraphicsTransform(QGraphicsTransformPrivate &p, QObject *parent); + +private: + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; + Q_DECLARE_PRIVATE(QGraphicsTransform) +}; + +class QGraphicsScalePrivate; + +class Q_GUI_EXPORT QGraphicsScale : public QGraphicsTransform +{ + Q_OBJECT + + Q_PROPERTY(QVector3D origin READ origin WRITE setOrigin NOTIFY originChanged) + Q_PROPERTY(qreal xScale READ xScale WRITE setXScale NOTIFY xScaleChanged) + Q_PROPERTY(qreal yScale READ yScale WRITE setYScale NOTIFY yScaleChanged) + Q_PROPERTY(qreal zScale READ zScale WRITE setZScale NOTIFY zScaleChanged) +public: + QGraphicsScale(QObject *parent = 0); + ~QGraphicsScale(); + + QVector3D origin() const; + void setOrigin(const QVector3D &point); + + qreal xScale() const; + void setXScale(qreal); + + qreal yScale() const; + void setYScale(qreal); + + qreal zScale() const; + void setZScale(qreal); + + void applyTo(QMatrix4x4 *matrix) const; + +Q_SIGNALS: + void originChanged(); + void xScaleChanged(); + void yScaleChanged(); + void zScaleChanged(); + void scaleChanged(); + +private: + Q_DECLARE_PRIVATE(QGraphicsScale) +}; + +class QGraphicsRotationPrivate; + +class Q_GUI_EXPORT QGraphicsRotation : public QGraphicsTransform +{ + Q_OBJECT + + Q_PROPERTY(QVector3D origin READ origin WRITE setOrigin NOTIFY originChanged) + Q_PROPERTY(qreal angle READ angle WRITE setAngle NOTIFY angleChanged) + Q_PROPERTY(QVector3D axis READ axis WRITE setAxis NOTIFY axisChanged) +public: + QGraphicsRotation(QObject *parent = 0); + ~QGraphicsRotation(); + + QVector3D origin() const; + void setOrigin(const QVector3D &point); + + qreal angle() const; + void setAngle(qreal); + + QVector3D axis() const; + void setAxis(const QVector3D &axis); + void setAxis(Qt::Axis axis); + + void applyTo(QMatrix4x4 *matrix) const; + +Q_SIGNALS: + void originChanged(); + void angleChanged(); + void axisChanged(); + +private: + Q_DECLARE_PRIVATE(QGraphicsRotation) +}; + +QT_END_NAMESPACE + +QT_END_HEADER +#endif //QT_NO_GRAPHICSVIEW + +#endif // QFXTRANSFORM_H diff --git a/src/widgets/graphicsview/qgraphicstransform_p.h b/src/widgets/graphicsview/qgraphicstransform_p.h new file mode 100644 index 0000000000..2a0bf859e3 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicstransform_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSTRANSFORM_P_H +#define QGRAPHICSTRANSFORM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qobject_p.h" +#ifndef QT_NO_GRAPHICSVIEW +QT_BEGIN_NAMESPACE + +class QGraphicsItem; + +class QGraphicsTransformPrivate : public QObjectPrivate { +public: + Q_DECLARE_PUBLIC(QGraphicsTransform) + + QGraphicsTransformPrivate() + : QObjectPrivate(), item(0) {} + + QGraphicsItem *item; + + void setItem(QGraphicsItem *item); + static void updateItem(QGraphicsItem *item); +}; + +QT_END_NAMESPACE +#endif //QT_NO_GRAPHCISVIEW + +#endif // QGRAPHICSTRANSFORM_P_H diff --git a/src/widgets/graphicsview/qgraphicsview.cpp b/src/widgets/graphicsview/qgraphicsview.cpp new file mode 100644 index 0000000000..488a36af49 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsview.cpp @@ -0,0 +1,3880 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +static const int QGRAPHICSVIEW_REGION_RECT_THRESHOLD = 50; + +static const int QGRAPHICSVIEW_PREALLOC_STYLE_OPTIONS = 503; // largest prime < 2^9 + +/*! + \class QGraphicsView + \brief The QGraphicsView class provides a widget for displaying the + contents of a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + + QGraphicsView visualizes the contents of a QGraphicsScene in a scrollable + viewport. To create a scene with geometrical items, see QGraphicsScene's + documentation. QGraphicsView is part of the \l{Graphics View Framework}. + + To visualize a scene, you start by constructing a QGraphicsView object, + passing the address of the scene you want to visualize to QGraphicsView's + constructor. Alternatively, you can call setScene() to set the scene at a + later point. After you call show(), the view will by default scroll to the + center of the scene and display any items that are visible at this + point. For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 0 + + You can explicitly scroll to any position on the scene by using the + scroll bars, or by calling centerOn(). By passing a point to centerOn(), + QGraphicsView will scroll its viewport to ensure that the point is + centered in the view. An overload is provided for scrolling to a + QGraphicsItem, in which case QGraphicsView will see to that the center of + the item is centered in the view. If all you want is to ensure that a + certain area is visible, (but not necessarily centered,) you can call + ensureVisible() instead. + + QGraphicsView can be used to visualize a whole scene, or only parts of it. + The visualized area is by default detected automatically when the view is + displayed for the first time (by calling + QGraphicsScene::itemsBoundingRect()). To set the visualized area rectangle + yourself, you can call setSceneRect(). This will adjust the scroll bars' + ranges appropriately. Note that although the scene supports a virtually + unlimited size, the range of the scroll bars will never exceed the range of + an integer (INT_MIN, INT_MAX). + + QGraphicsView visualizes the scene by calling render(). By default, the + items are drawn onto the viewport by using a regular QPainter, and using + default render hints. To change the default render hints that + QGraphicsView passes to QPainter when painting items, you can call + setRenderHints(). + + By default, QGraphicsView provides a regular QWidget for the viewport + widget. You can access this widget by calling viewport(), or you can + replace it by calling setViewport(). To render using OpenGL, simply call + setViewport(new QGLWidget). QGraphicsView takes ownership of the viewport + widget. + + QGraphicsView supports affine transformations, using QTransform. You can + either pass a matrix to setTransform(), or you can call one of the + convenience functions rotate(), scale(), translate() or shear(). The most + two common transformations are scaling, which is used to implement + zooming, and rotation. QGraphicsView keeps the center of the view fixed + during a transformation. Because of the scene alignment (setAligment()), + translating the view will have no visual impact. + + You can interact with the items on the scene by using the mouse and + keyboard. QGraphicsView translates the mouse and key events into \e scene + events, (events that inherit QGraphicsSceneEvent,), and forward them to + the visualized scene. In the end, it's the individual item that handles + the events and reacts to them. For example, if you click on a selectable + item, the item will typically let the scene know that it has been + selected, and it will also redraw itself to display a selection + rectangle. Similiary, if you click and drag the mouse to move a movable + item, it's the item that handles the mouse moves and moves itself. Item + interaction is enabled by default, and you can toggle it by calling + setInteractive(). + + You can also provide your own custom scene interaction, by creating a + subclass of QGraphicsView, and reimplementing the mouse and key event + handlers. To simplify how you programmatically interact with items in the + view, QGraphicsView provides the mapping functions mapToScene() and + mapFromScene(), and the item accessors items() and itemAt(). These + functions allow you to map points, rectangles, polygons and paths between + view coordinates and scene coordinates, and to find items on the scene + using view coordinates. + + \img graphicsview-view.png + + \sa QGraphicsScene, QGraphicsItem, QGraphicsSceneEvent +*/ + +/*! + \enum QGraphicsView::ViewportAnchor + + This enums describe the possible anchors that QGraphicsView can + use when the user resizes the view or when the view is + transformed. + + \value NoAnchor No anchor, i.e. the view leaves the scene's + position unchanged. + \value AnchorViewCenter The scene point at the center of the view + is used as the anchor. + \value AnchorUnderMouse The point under the mouse is used as the anchor. + + \sa resizeAnchor, transformationAnchor +*/ + +/*! + \enum QGraphicsView::ViewportUpdateMode + + \since 4.3 + + This enum describes how QGraphicsView updates its viewport when the scene + contents change or are exposed. + + \value FullViewportUpdate When any visible part of the scene changes or is + reexposed, QGraphicsView will update the entire viewport. This approach is + fastest when QGraphicsView spends more time figuring out what to draw than + it would spend drawing (e.g., when very many small items are repeatedly + updated). This is the preferred update mode for viewports that do not + support partial updates, such as QGLWidget, and for viewports that need to + disable scroll optimization. + + \value MinimalViewportUpdate QGraphicsView will determine the minimal + viewport region that requires a redraw, minimizing the time spent drawing + by avoiding a redraw of areas that have not changed. This is + QGraphicsView's default mode. Although this approach provides the best + performance in general, if there are many small visible changes on the + scene, QGraphicsView might end up spending more time finding the minimal + approach than it will spend drawing. + + \value SmartViewportUpdate QGraphicsView will attempt to find an optimal + update mode by analyzing the areas that require a redraw. + + \value BoundingRectViewportUpdate The bounding rectangle of all changes in + the viewport will be redrawn. This mode has the advantage that + QGraphicsView searches only one region for changes, minimizing time spent + determining what needs redrawing. The disadvantage is that areas that have + not changed also need to be redrawn. + + \value NoViewportUpdate QGraphicsView will never update its viewport when + the scene changes; the user is expected to control all updates. This mode + disables all (potentially slow) item visibility testing in QGraphicsView, + and is suitable for scenes that either require a fixed frame rate, or where + the viewport is otherwise updated externally. + + \sa viewportUpdateMode +*/ + +/*! + \enum QGraphicsView::OptimizationFlag + + \since 4.3 + + This enum describes flags that you can enable to improve rendering + performance in QGraphicsView. By default, none of these flags are set. + Note that setting a flag usually imposes a side effect, and this effect + can vary between paint devices and platforms. + + \value DontClipPainter This value is obsolete and has no effect. + + \value DontSavePainterState When rendering, QGraphicsView protects the + painter state (see QPainter::save()) when rendering the background or + foreground, and when rendering each item. This allows you to leave the + painter in an altered state (i.e., you can call QPainter::setPen() or + QPainter::setBrush() without restoring the state after painting). However, + if the items consistently do restore the state, you should enable this + flag to prevent QGraphicsView from doing the same. + + \value DontAdjustForAntialiasing Disables QGraphicsView's antialiasing + auto-adjustment of exposed areas. Items that render antialiased lines on + the boundaries of their QGraphicsItem::boundingRect() can end up rendering + parts of the line outside. To prevent rendering artifacts, QGraphicsView + expands all exposed regions by 2 pixels in all directions. If you enable + this flag, QGraphicsView will no longer perform these adjustments, + minimizing the areas that require redrawing, which improves performance. A + common side effect is that items that do draw with antialiasing can leave + painting traces behind on the scene as they are moved. + + \value IndirectPainting Since Qt 4.6, restore the old painting algorithm + that calls QGraphicsView::drawItems() and QGraphicsScene::drawItems(). + To be used only for compatibility with old code. +*/ + +/*! + \enum QGraphicsView::CacheModeFlag + + This enum describes the flags that you can set for a QGraphicsView's cache + mode. + + \value CacheNone All painting is done directly onto the viewport. + + \value CacheBackground The background is cached. This affects both custom + backgrounds, and backgrounds based on the backgroundBrush property. When + this flag is enabled, QGraphicsView will allocate one pixmap with the full + size of the viewport. + + \sa cacheMode +*/ + +/*! + \enum QGraphicsView::DragMode + + This enum describes the default action for the view when pressing and + dragging the mouse over the viewport. + + \value NoDrag Nothing happens; the mouse event is ignored. + + \value ScrollHandDrag The cursor changes into a pointing hand, and + dragging the mouse around will scroll the scrolbars. This mode works both + in \l{QGraphicsView::interactive}{interactive} and non-interactive mode. + + \value RubberBandDrag A rubber band will appear. Dragging the mouse will + set the rubber band geometry, and all items covered by the rubber band are + selected. This mode is disabled for non-interactive views. + + \sa dragMode, QGraphicsScene::setSelectionArea() +*/ + +#include "qgraphicsview.h" +#include "qgraphicsview_p.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicsitem.h" +#include "qgraphicsitem_p.h" +#include "qgraphicsscene.h" +#include "qgraphicsscene_p.h" +#include "qgraphicssceneevent.h" +#include "qgraphicswidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef Q_WS_X11 +#include +#include +#endif + +#include + +QT_BEGIN_NAMESPACE + +bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); + +inline int q_round_bound(qreal d) //### (int)(qreal) INT_MAX != INT_MAX for single precision +{ + if (d <= (qreal) INT_MIN) + return INT_MIN; + else if (d >= (qreal) INT_MAX) + return INT_MAX; + return d >= 0.0 ? int(d + 0.5) : int(d - int(d-1) + 0.5) + int(d-1); +} + +void QGraphicsViewPrivate::translateTouchEvent(QGraphicsViewPrivate *d, QTouchEvent *touchEvent) +{ + QList touchPoints = touchEvent->touchPoints(); + for (int i = 0; i < touchPoints.count(); ++i) { + QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; + // the scene will set the item local pos, startPos, lastPos, and rect before delivering to + // an item, but for now those functions are returning the view's local coordinates + touchPoint.setSceneRect(d->mapToScene(touchPoint.rect())); + touchPoint.setStartScenePos(d->mapToScene(touchPoint.startPos())); + touchPoint.setLastScenePos(d->mapToScene(touchPoint.lastPos())); + + // screenPos, startScreenPos, lastScreenPos, and screenRect are already set + } + + touchEvent->setTouchPoints(touchPoints); +} + +/*! + \internal +*/ +QGraphicsViewPrivate::QGraphicsViewPrivate() + : renderHints(QPainter::TextAntialiasing), + dragMode(QGraphicsView::NoDrag), + sceneInteractionAllowed(true), hasSceneRect(false), + connectedToScene(false), + useLastMouseEvent(false), + identityMatrix(true), + dirtyScroll(true), + accelerateScrolling(true), + keepLastCenterPoint(true), + transforming(false), + handScrolling(false), + mustAllocateStyleOptions(false), + mustResizeBackgroundPixmap(true), + fullUpdatePending(true), + hasUpdateClip(false), + mousePressButton(Qt::NoButton), + leftIndent(0), topIndent(0), + lastMouseEvent(QEvent::None, QPoint(), Qt::NoButton, 0, 0), + alignment(Qt::AlignCenter), + transformationAnchor(QGraphicsView::AnchorViewCenter), resizeAnchor(QGraphicsView::NoAnchor), + viewportUpdateMode(QGraphicsView::MinimalViewportUpdate), + optimizationFlags(0), + scene(0), +#ifndef QT_NO_RUBBERBAND + rubberBanding(false), + rubberBandSelectionMode(Qt::IntersectsItemShape), +#endif + handScrollMotions(0), cacheMode(0), +#ifndef QT_NO_CURSOR + hasStoredOriginalCursor(false), +#endif + lastDragDropEvent(0), + updateSceneSlotReimplementedChecked(false) +{ + styleOptions.reserve(QGRAPHICSVIEW_PREALLOC_STYLE_OPTIONS); +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::recalculateContentSize() +{ + Q_Q(QGraphicsView); + + QSize maxSize = q->maximumViewportSize(); + int width = maxSize.width(); + int height = maxSize.height(); + QRectF viewRect = matrix.mapRect(q->sceneRect()); + + bool frameOnlyAround = (q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, 0, q)); + if (frameOnlyAround) { + if (hbarpolicy == Qt::ScrollBarAlwaysOn) + height -= frameWidth * 2; + if (vbarpolicy == Qt::ScrollBarAlwaysOn) + width -= frameWidth * 2; + } + + // Adjust the maximum width and height of the viewport based on the width + // of visible scroll bars. + int scrollBarExtent = q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, q); + if (frameOnlyAround) + scrollBarExtent += frameWidth * 2; + + bool useHorizontalScrollBar = (viewRect.width() > width) && hbarpolicy != Qt::ScrollBarAlwaysOff; + bool useVerticalScrollBar = (viewRect.height() > height) && vbarpolicy != Qt::ScrollBarAlwaysOff; + if (useHorizontalScrollBar && !useVerticalScrollBar) { + if (viewRect.height() > height - scrollBarExtent) + useVerticalScrollBar = true; + } + if (useVerticalScrollBar && !useHorizontalScrollBar) { + if (viewRect.width() > width - scrollBarExtent) + useHorizontalScrollBar = true; + } + if (useHorizontalScrollBar && hbarpolicy != Qt::ScrollBarAlwaysOn) + height -= scrollBarExtent; + if (useVerticalScrollBar && vbarpolicy != Qt::ScrollBarAlwaysOn) + width -= scrollBarExtent; + + // Setting the ranges of these scroll bars can/will cause the values to + // change, and scrollContentsBy() will be called correspondingly. This + // will reset the last center point. + QPointF savedLastCenterPoint = lastCenterPoint; + + // Remember the former indent settings + qreal oldLeftIndent = leftIndent; + qreal oldTopIndent = topIndent; + + // If the whole scene fits horizontally, we center the scene horizontally, + // and ignore the horizontal scroll bars. + int left = q_round_bound(viewRect.left()); + int right = q_round_bound(viewRect.right() - width); + if (left >= right) { + hbar->setRange(0, 0); + + switch (alignment & Qt::AlignHorizontal_Mask) { + case Qt::AlignLeft: + leftIndent = -viewRect.left(); + break; + case Qt::AlignRight: + leftIndent = width - viewRect.width() - viewRect.left() - 1; + break; + case Qt::AlignHCenter: + default: + leftIndent = width / 2 - (viewRect.left() + viewRect.right()) / 2; + break; + } + } else { + hbar->setRange(left, right); + hbar->setPageStep(width); + hbar->setSingleStep(width / 20); + leftIndent = 0; + } + + // If the whole scene fits vertically, we center the scene vertically, and + // ignore the vertical scroll bars. + int top = q_round_bound(viewRect.top()); + int bottom = q_round_bound(viewRect.bottom() - height); + if (top >= bottom) { + vbar->setRange(0, 0); + + switch (alignment & Qt::AlignVertical_Mask) { + case Qt::AlignTop: + topIndent = -viewRect.top(); + break; + case Qt::AlignBottom: + topIndent = height - viewRect.height() - viewRect.top() - 1; + break; + case Qt::AlignVCenter: + default: + topIndent = height / 2 - (viewRect.top() + viewRect.bottom()) / 2; + break; + } + } else { + vbar->setRange(top, bottom); + vbar->setPageStep(height); + vbar->setSingleStep(height / 20); + topIndent = 0; + } + + // Restorethe center point from before the ranges changed. + lastCenterPoint = savedLastCenterPoint; + + // Issue a full update if the indents change. + // ### If the transform is still the same, we can get away with just a + // scroll instead. + if (oldLeftIndent != leftIndent || oldTopIndent != topIndent) { + dirtyScroll = true; + updateAll(); + } else if (q->isRightToLeft() && !leftIndent) { + // In reverse mode, the horizontal scroll always changes after the content + // size has changed, as the scroll is calculated by summing the min and + // max values of the range and subtracting the current value. In normal + // mode the scroll remains unchanged unless the indent has changed. + dirtyScroll = true; + } + + if (cacheMode & QGraphicsView::CacheBackground) { + // Invalidate the background pixmap + mustResizeBackgroundPixmap = true; + } +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::centerView(QGraphicsView::ViewportAnchor anchor) +{ + Q_Q(QGraphicsView); + switch (anchor) { + case QGraphicsView::AnchorUnderMouse: { + if (q->underMouse()) { + // Last scene pos: lastMouseMoveScenePoint + // Current mouse pos: + QPointF transformationDiff = q->mapToScene(viewport->rect().center()) + - q->mapToScene(viewport->mapFromGlobal(QCursor::pos())); + q->centerOn(lastMouseMoveScenePoint + transformationDiff); + } else { + q->centerOn(lastCenterPoint); + } + break; + } + case QGraphicsView::AnchorViewCenter: + q->centerOn(lastCenterPoint); + break; + case QGraphicsView::NoAnchor: + break; + } +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::updateLastCenterPoint() +{ + Q_Q(QGraphicsView); + lastCenterPoint = q->mapToScene(viewport->rect().center()); +} + +/*! + \internal + + Returns the horizontal scroll value (the X value of the left edge of the + viewport). +*/ +qint64 QGraphicsViewPrivate::horizontalScroll() const +{ + if (dirtyScroll) + const_cast(this)->updateScroll(); + return scrollX; +} + +/*! + \internal + + Returns the vertical scroll value (the X value of the top edge of the + viewport). +*/ +qint64 QGraphicsViewPrivate::verticalScroll() const +{ + if (dirtyScroll) + const_cast(this)->updateScroll(); + return scrollY; +} + +/*! + \internal + + Maps the given rectangle to the scene using QTransform::mapRect() +*/ +QRectF QGraphicsViewPrivate::mapRectToScene(const QRect &rect) const +{ + if (dirtyScroll) + const_cast(this)->updateScroll(); + QRectF scrolled = QRectF(rect.translated(scrollX, scrollY)); + return identityMatrix ? scrolled : matrix.inverted().mapRect(scrolled); +} + + +/*! + \internal + + Maps the given rectangle from the scene using QTransform::mapRect() +*/ +QRectF QGraphicsViewPrivate::mapRectFromScene(const QRectF &rect) const +{ + if (dirtyScroll) + const_cast(this)->updateScroll(); + return (identityMatrix ? rect : matrix.mapRect(rect)).translated(-scrollX, -scrollY); +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::updateScroll() +{ + Q_Q(QGraphicsView); + scrollX = qint64(-leftIndent); + if (q->isRightToLeft()) { + if (!leftIndent) { + scrollX += hbar->minimum(); + scrollX += hbar->maximum(); + scrollX -= hbar->value(); + } + } else { + scrollX += hbar->value(); + } + + scrollY = qint64(vbar->value() - topIndent); + + dirtyScroll = false; +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::replayLastMouseEvent() +{ + if (!useLastMouseEvent || !scene) + return; + mouseMoveEventHandler(&lastMouseEvent); +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::storeMouseEvent(QMouseEvent *event) +{ + useLastMouseEvent = true; + lastMouseEvent = QMouseEvent(QEvent::MouseMove, event->pos(), event->globalPos(), + event->button(), event->buttons(), event->modifiers()); +} + +void QGraphicsViewPrivate::mouseMoveEventHandler(QMouseEvent *event) +{ + Q_Q(QGraphicsView); + + storeMouseEvent(event); + lastMouseEvent.setAccepted(false); + + if (!sceneInteractionAllowed) + return; + if (handScrolling) + return; + if (!scene) + return; + + QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseMove); + mouseEvent.setWidget(viewport); + mouseEvent.setButtonDownScenePos(mousePressButton, mousePressScenePoint); + mouseEvent.setButtonDownScreenPos(mousePressButton, mousePressScreenPoint); + mouseEvent.setScenePos(q->mapToScene(event->pos())); + mouseEvent.setScreenPos(event->globalPos()); + mouseEvent.setLastScenePos(lastMouseMoveScenePoint); + mouseEvent.setLastScreenPos(lastMouseMoveScreenPoint); + mouseEvent.setButtons(event->buttons()); + mouseEvent.setButton(event->button()); + mouseEvent.setModifiers(event->modifiers()); + lastMouseMoveScenePoint = mouseEvent.scenePos(); + lastMouseMoveScreenPoint = mouseEvent.screenPos(); + mouseEvent.setAccepted(false); + if (event->spontaneous()) + qt_sendSpontaneousEvent(scene, &mouseEvent); + else + QApplication::sendEvent(scene, &mouseEvent); + + // Remember whether the last event was accepted or not. + lastMouseEvent.setAccepted(mouseEvent.isAccepted()); + + if (mouseEvent.isAccepted() && mouseEvent.buttons() != 0) { + // The event was delivered to a mouse grabber; the press is likely to + // have set a cursor, and we must not change it. + return; + } + +#ifndef QT_NO_CURSOR + // If all the items ignore hover events, we don't look-up any items + // in QGraphicsScenePrivate::dispatchHoverEvent, hence the + // cachedItemsUnderMouse list will be empty. We therefore do the look-up + // for cursor items here if not all items use the default cursor. + if (scene->d_func()->allItemsIgnoreHoverEvents && !scene->d_func()->allItemsUseDefaultCursor + && scene->d_func()->cachedItemsUnderMouse.isEmpty()) { + scene->d_func()->cachedItemsUnderMouse = scene->d_func()->itemsAtPosition(mouseEvent.screenPos(), + mouseEvent.scenePos(), + mouseEvent.widget()); + } + // Find the topmost item under the mouse with a cursor. + foreach (QGraphicsItem *item, scene->d_func()->cachedItemsUnderMouse) { + if (item->hasCursor()) { + _q_setViewportCursor(item->cursor()); + return; + } + } + + // No items with cursors found; revert to the view cursor. + if (hasStoredOriginalCursor) { + // Restore the original viewport cursor. + hasStoredOriginalCursor = false; + viewport->setCursor(originalCursor); + } +#endif +} + +/*! + \internal +*/ +#ifndef QT_NO_RUBBERBAND +QRegion QGraphicsViewPrivate::rubberBandRegion(const QWidget *widget, const QRect &rect) const +{ + QStyleHintReturnMask mask; + QStyleOptionRubberBand option; + option.initFrom(widget); + option.rect = rect; + option.opaque = false; + option.shape = QRubberBand::Rectangle; + + QRegion tmp; + tmp += rect; + if (widget->style()->styleHint(QStyle::SH_RubberBand_Mask, &option, widget, &mask)) + tmp &= mask.region; + return tmp; +} +#endif + +/*! + \internal +*/ +#ifndef QT_NO_CURSOR +void QGraphicsViewPrivate::_q_setViewportCursor(const QCursor &cursor) +{ + if (!hasStoredOriginalCursor) { + hasStoredOriginalCursor = true; + originalCursor = viewport->cursor(); + } + viewport->setCursor(cursor); +} +#endif + +/*! + \internal +*/ +#ifndef QT_NO_CURSOR +void QGraphicsViewPrivate::_q_unsetViewportCursor() +{ + Q_Q(QGraphicsView); + foreach (QGraphicsItem *item, q->items(lastMouseEvent.pos())) { + if (item->hasCursor()) { + _q_setViewportCursor(item->cursor()); + return; + } + } + + // Restore the original viewport cursor. + if (hasStoredOriginalCursor) { + hasStoredOriginalCursor = false; + if (dragMode == QGraphicsView::ScrollHandDrag) + viewport->setCursor(Qt::OpenHandCursor); + else + viewport->setCursor(originalCursor); + } +} +#endif + +/*! + \internal +*/ +void QGraphicsViewPrivate::storeDragDropEvent(const QGraphicsSceneDragDropEvent *event) +{ + delete lastDragDropEvent; + lastDragDropEvent = new QGraphicsSceneDragDropEvent(event->type()); + lastDragDropEvent->setScenePos(event->scenePos()); + lastDragDropEvent->setScreenPos(event->screenPos()); + lastDragDropEvent->setButtons(event->buttons()); + lastDragDropEvent->setModifiers(event->modifiers()); + lastDragDropEvent->setPossibleActions(event->possibleActions()); + lastDragDropEvent->setProposedAction(event->proposedAction()); + lastDragDropEvent->setDropAction(event->dropAction()); + lastDragDropEvent->setMimeData(event->mimeData()); + lastDragDropEvent->setWidget(event->widget()); + lastDragDropEvent->setSource(event->source()); +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::populateSceneDragDropEvent(QGraphicsSceneDragDropEvent *dest, + QDropEvent *source) +{ +#ifndef QT_NO_DRAGANDDROP + Q_Q(QGraphicsView); + dest->setScenePos(q->mapToScene(source->pos())); + dest->setScreenPos(q->mapToGlobal(source->pos())); + dest->setButtons(source->mouseButtons()); + dest->setModifiers(source->keyboardModifiers()); + dest->setPossibleActions(source->possibleActions()); + dest->setProposedAction(source->proposedAction()); + dest->setDropAction(source->dropAction()); + dest->setMimeData(source->mimeData()); + dest->setWidget(viewport); + dest->setSource(source->source()); +#else + Q_UNUSED(dest) + Q_UNUSED(source) +#endif +} + +/*! + \internal +*/ +QRect QGraphicsViewPrivate::mapToViewRect(const QGraphicsItem *item, const QRectF &rect) const +{ + Q_Q(const QGraphicsView); + if (dirtyScroll) + const_cast(this)->updateScroll(); + + if (item->d_ptr->itemIsUntransformable()) { + QTransform itv = item->deviceTransform(q->viewportTransform()); + return itv.mapRect(rect).toAlignedRect(); + } + + // Translate-only + // COMBINE + QPointF offset; + const QGraphicsItem *parentItem = item; + const QGraphicsItemPrivate *itemd; + do { + itemd = parentItem->d_ptr.data(); + if (itemd->transformData) + break; + offset += itemd->pos; + } while ((parentItem = itemd->parent)); + + QRectF baseRect = rect.translated(offset.x(), offset.y()); + if (!parentItem) { + if (identityMatrix) { + baseRect.translate(-scrollX, -scrollY); + return baseRect.toAlignedRect(); + } + return matrix.mapRect(baseRect).translated(-scrollX, -scrollY).toAlignedRect(); + } + + QTransform tr = parentItem->sceneTransform(); + if (!identityMatrix) + tr *= matrix; + QRectF r = tr.mapRect(baseRect); + r.translate(-scrollX, -scrollY); + return r.toAlignedRect(); +} + +/*! + \internal +*/ +QRegion QGraphicsViewPrivate::mapToViewRegion(const QGraphicsItem *item, const QRectF &rect) const +{ + Q_Q(const QGraphicsView); + if (dirtyScroll) + const_cast(this)->updateScroll(); + + // Accurate bounding region + QTransform itv = item->deviceTransform(q->viewportTransform()); + return item->boundingRegion(itv) & itv.mapRect(rect).toAlignedRect(); +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::processPendingUpdates() +{ + if (!scene) + return; + + if (fullUpdatePending) { + viewport->update(); + } else if (viewportUpdateMode == QGraphicsView::BoundingRectViewportUpdate) { + viewport->update(dirtyBoundingRect); + } else { + viewport->update(dirtyRegion); // Already adjusted in updateRect/Region. + } + + dirtyBoundingRect = QRect(); + dirtyRegion = QRegion(); +} + +static inline bool intersectsViewport(const QRect &r, int width, int height) +{ return !(r.left() > width) && !(r.right() < 0) && !(r.top() >= height) && !(r.bottom() < 0); } + +static inline bool containsViewport(const QRect &r, int width, int height) +{ return r.left() <= 0 && r.top() <= 0 && r.right() >= width - 1 && r.bottom() >= height - 1; } + +static inline void QRect_unite(QRect *rect, const QRect &other) +{ + if (rect->isEmpty()) { + *rect = other; + } else { + rect->setCoords(qMin(rect->left(), other.left()), qMin(rect->top(), other.top()), + qMax(rect->right(), other.right()), qMax(rect->bottom(), other.bottom())); + } +} + +/* + Calling this function results in update rects being clipped to the item's + bounding rect. Note that updates prior to this function call is not clipped. + The clip is removed by passing 0. +*/ +void QGraphicsViewPrivate::setUpdateClip(QGraphicsItem *item) +{ + Q_Q(QGraphicsView); + // We simply ignore the request if the update mode is either FullViewportUpdate + // or NoViewportUpdate; in that case there's no point in clipping anything. + if (!item || viewportUpdateMode == QGraphicsView::NoViewportUpdate + || viewportUpdateMode == QGraphicsView::FullViewportUpdate) { + hasUpdateClip = false; + return; + } + + // Calculate the clip (item's bounding rect in view coordinates). + // Optimized version of: + // QRect clip = item->deviceTransform(q->viewportTransform()) + // .mapRect(item->boundingRect()).toAlignedRect(); + QRect clip; + if (item->d_ptr->itemIsUntransformable()) { + QTransform xform = item->deviceTransform(q->viewportTransform()); + clip = xform.mapRect(item->boundingRect()).toAlignedRect(); + } else if (item->d_ptr->sceneTransformTranslateOnly && identityMatrix) { + QRectF r(item->boundingRect()); + r.translate(item->d_ptr->sceneTransform.dx() - horizontalScroll(), + item->d_ptr->sceneTransform.dy() - verticalScroll()); + clip = r.toAlignedRect(); + } else if (!q->isTransformed()) { + clip = item->d_ptr->sceneTransform.mapRect(item->boundingRect()).toAlignedRect(); + } else { + QTransform xform = item->d_ptr->sceneTransform; + xform *= q->viewportTransform(); + clip = xform.mapRect(item->boundingRect()).toAlignedRect(); + } + + if (hasUpdateClip) { + // Intersect with old clip. + updateClip &= clip; + } else { + updateClip = clip; + hasUpdateClip = true; + } +} + +bool QGraphicsViewPrivate::updateRegion(const QRectF &rect, const QTransform &xform) +{ + if (rect.isEmpty()) + return false; + + if (viewportUpdateMode != QGraphicsView::MinimalViewportUpdate + && viewportUpdateMode != QGraphicsView::SmartViewportUpdate) { + // No point in updating with QRegion granularity; use the rect instead. + return updateRectF(xform.mapRect(rect)); + } + + // Update mode is either Minimal or Smart, so we have to do a potentially slow operation, + // which is clearly documented here: QGraphicsItem::setBoundingRegionGranularity. + const QRegion region = xform.map(QRegion(rect.toAlignedRect())); + QRect viewRect = region.boundingRect(); + const bool dontAdjustForAntialiasing = optimizationFlags & QGraphicsView::DontAdjustForAntialiasing; + if (dontAdjustForAntialiasing) + viewRect.adjust(-1, -1, 1, 1); + else + viewRect.adjust(-2, -2, 2, 2); + if (!intersectsViewport(viewRect, viewport->width(), viewport->height())) + return false; // Update region for sure outside viewport. + + const QVector &rects = region.rects(); + for (int i = 0; i < rects.size(); ++i) { + viewRect = rects.at(i); + if (dontAdjustForAntialiasing) + viewRect.adjust(-1, -1, 1, 1); + else + viewRect.adjust(-2, -2, 2, 2); + if (hasUpdateClip) + viewRect &= updateClip; + dirtyRegion += viewRect; + } + + return true; +} + +// NB! Assumes the rect 'r' is already aligned and adjusted for antialiasing. +// For QRectF use updateRectF(const QRectF &) to ensure proper adjustments. +bool QGraphicsViewPrivate::updateRect(const QRect &r) +{ + if (fullUpdatePending || viewportUpdateMode == QGraphicsView::NoViewportUpdate + || !intersectsViewport(r, viewport->width(), viewport->height())) { + return false; + } + + switch (viewportUpdateMode) { + case QGraphicsView::FullViewportUpdate: + fullUpdatePending = true; + viewport->update(); + break; + case QGraphicsView::BoundingRectViewportUpdate: + if (hasUpdateClip) + QRect_unite(&dirtyBoundingRect, r & updateClip); + else + QRect_unite(&dirtyBoundingRect, r); + if (containsViewport(dirtyBoundingRect, viewport->width(), viewport->height())) { + fullUpdatePending = true; + viewport->update(); + } + break; + case QGraphicsView::SmartViewportUpdate: // ### DEPRECATE + case QGraphicsView::MinimalViewportUpdate: + if (hasUpdateClip) + dirtyRegion += r & updateClip; + else + dirtyRegion += r; + break; + default: + break; + } + + return true; +} + +QStyleOptionGraphicsItem *QGraphicsViewPrivate::allocStyleOptionsArray(int numItems) +{ + if (mustAllocateStyleOptions || (numItems > styleOptions.capacity())) + // too many items, let's allocate on-the-fly + return new QStyleOptionGraphicsItem[numItems]; + + // expand only whenever necessary + if (numItems > styleOptions.size()) + styleOptions.resize(numItems); + + mustAllocateStyleOptions = true; + return styleOptions.data(); +} + +void QGraphicsViewPrivate::freeStyleOptionsArray(QStyleOptionGraphicsItem *array) +{ + mustAllocateStyleOptions = false; + if (array != styleOptions.data()) + delete [] array; +} + +extern QPainterPath qt_regionToPath(const QRegion ®ion); + +/*! + ### Adjustments in findItems: mapToScene(QRect) forces us to adjust the + input rectangle by (0, 0, 1, 1), because it uses QRect::bottomRight() + (etc) when mapping the rectangle to a polygon (which is _wrong_). In + addition, as QGraphicsItem::boundingRect() is defined in logical space, + but the default pen for QPainter is cosmetic with a width of 0, QPainter + is at risk of painting 1 pixel outside the bounding rect. Therefore we + must search for items with an adjustment of (-1, -1, 1, 1). +*/ +QList QGraphicsViewPrivate::findItems(const QRegion &exposedRegion, bool *allItems, + const QTransform &viewTransform) const +{ + Q_Q(const QGraphicsView); + + // Step 1) If all items are contained within the expose region, then + // return a list of all visible items. ### the scene's growing bounding + // rect does not take into account untransformable items. + const QRectF exposedRegionSceneBounds = q->mapToScene(exposedRegion.boundingRect().adjusted(-1, -1, 1, 1)) + .boundingRect(); + if (exposedRegionSceneBounds.contains(scene->sceneRect())) { + Q_ASSERT(allItems); + *allItems = true; + + // All items are guaranteed within the exposed region. + return scene->items(Qt::AscendingOrder); + } + + // Step 2) If the expose region is a simple rect and the view is only + // translated or scaled, search for items using + // QGraphicsScene::items(QRectF). + bool simpleRectLookup = exposedRegion.rectCount() == 1 && matrix.type() <= QTransform::TxScale; + if (simpleRectLookup) { + return scene->items(exposedRegionSceneBounds, + Qt::IntersectsItemBoundingRect, + Qt::AscendingOrder, viewTransform); + } + + // If the region is complex or the view has a complex transform, adjust + // the expose region, convert it to a path, and then search for items + // using QGraphicsScene::items(QPainterPath); + QRegion adjustedRegion; + foreach (const QRect &r, exposedRegion.rects()) + adjustedRegion += r.adjusted(-1, -1, 1, 1); + + const QPainterPath exposedScenePath(q->mapToScene(qt_regionToPath(adjustedRegion))); + return scene->items(exposedScenePath, Qt::IntersectsItemBoundingRect, + Qt::AscendingOrder, viewTransform); +} + +/*! + \internal + + Enables input methods for the view if and only if the current focus item of + the scene accepts input methods. Call function whenever that condition has + potentially changed. +*/ +void QGraphicsViewPrivate::updateInputMethodSensitivity() +{ + Q_Q(QGraphicsView); + QGraphicsItem *focusItem = 0; + bool enabled = scene && (focusItem = scene->focusItem()) + && (focusItem->d_ptr->flags & QGraphicsItem::ItemAcceptsInputMethod); + q->setAttribute(Qt::WA_InputMethodEnabled, enabled); + q->viewport()->setAttribute(Qt::WA_InputMethodEnabled, enabled); + + if (!enabled) { + q->setInputMethodHints(0); + return; + } + + QGraphicsProxyWidget *proxy = focusItem->d_ptr->isWidget && focusItem->d_ptr->isProxyWidget() + ? static_cast(focusItem) : 0; + if (!proxy) { + q->setInputMethodHints(focusItem->inputMethodHints()); + } else if (QWidget *widget = proxy->widget()) { + if (QWidget *fw = widget->focusWidget()) + widget = fw; + q->setInputMethodHints(widget->inputMethodHints()); + } else { + q->setInputMethodHints(0); + } +} + +/*! + Constructs a QGraphicsView. \a parent is passed to QWidget's constructor. +*/ +QGraphicsView::QGraphicsView(QWidget *parent) + : QAbstractScrollArea(*new QGraphicsViewPrivate, parent) +{ + setViewport(0); + setAcceptDrops(true); + setBackgroundRole(QPalette::Base); + // Investigate leaving these disabled by default. + setAttribute(Qt::WA_InputMethodEnabled); + viewport()->setAttribute(Qt::WA_InputMethodEnabled); +} + +/*! + Constructs a QGraphicsView and sets the visualized scene to \a + scene. \a parent is passed to QWidget's constructor. +*/ +QGraphicsView::QGraphicsView(QGraphicsScene *scene, QWidget *parent) + : QAbstractScrollArea(*new QGraphicsViewPrivate, parent) +{ + setScene(scene); + setViewport(0); + setAcceptDrops(true); + setBackgroundRole(QPalette::Base); + // Investigate leaving these disabled by default. + setAttribute(Qt::WA_InputMethodEnabled); + viewport()->setAttribute(Qt::WA_InputMethodEnabled); +} + +/*! + \internal + */ +QGraphicsView::QGraphicsView(QGraphicsViewPrivate &dd, QWidget *parent) + : QAbstractScrollArea(dd, parent) +{ + setViewport(0); + setAcceptDrops(true); + setBackgroundRole(QPalette::Base); + // Investigate leaving these disabled by default. + setAttribute(Qt::WA_InputMethodEnabled); + viewport()->setAttribute(Qt::WA_InputMethodEnabled); +} + +/*! + Destructs the QGraphicsView object. +*/ +QGraphicsView::~QGraphicsView() +{ + Q_D(QGraphicsView); + if (d->scene) + d->scene->d_func()->views.removeAll(this); + delete d->lastDragDropEvent; +} + +/*! + \reimp +*/ +QSize QGraphicsView::sizeHint() const +{ + Q_D(const QGraphicsView); + if (d->scene) { + QSizeF baseSize = d->matrix.mapRect(sceneRect()).size(); + baseSize += QSizeF(d->frameWidth * 2, d->frameWidth * 2); + return baseSize.boundedTo((3 * QApplication::desktop()->size()) / 4).toSize(); + } + return QAbstractScrollArea::sizeHint(); +} + +/*! + \property QGraphicsView::renderHints + \brief the default render hints for the view + + These hints are + used to initialize QPainter before each visible item is drawn. QPainter + uses render hints to toggle rendering features such as antialiasing and + smooth pixmap transformation. + + QPainter::TextAntialiasing is enabled by default. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 1 +*/ +QPainter::RenderHints QGraphicsView::renderHints() const +{ + Q_D(const QGraphicsView); + return d->renderHints; +} +void QGraphicsView::setRenderHints(QPainter::RenderHints hints) +{ + Q_D(QGraphicsView); + if (hints == d->renderHints) + return; + d->renderHints = hints; + d->updateAll(); +} + +/*! + If \a enabled is true, the render hint \a hint is enabled; otherwise it + is disabled. + + \sa renderHints +*/ +void QGraphicsView::setRenderHint(QPainter::RenderHint hint, bool enabled) +{ + Q_D(QGraphicsView); + QPainter::RenderHints oldHints = d->renderHints; + if (enabled) + d->renderHints |= hint; + else + d->renderHints &= ~hint; + if (oldHints != d->renderHints) + d->updateAll(); +} + +/*! + \property QGraphicsView::alignment + \brief the alignment of the scene in the view when the whole + scene is visible. + + If the whole scene is visible in the view, (i.e., there are no visible + scroll bars,) the view's alignment will decide where the scene will be + rendered in the view. For example, if the alignment is Qt::AlignCenter, + which is default, the scene will be centered in the view, and if the + alignment is (Qt::AlignLeft | Qt::AlignTop), the scene will be rendered in + the top-left corner of the view. +*/ +Qt::Alignment QGraphicsView::alignment() const +{ + Q_D(const QGraphicsView); + return d->alignment; +} +void QGraphicsView::setAlignment(Qt::Alignment alignment) +{ + Q_D(QGraphicsView); + if (d->alignment != alignment) { + d->alignment = alignment; + d->recalculateContentSize(); + } +} + +/*! + \property QGraphicsView::transformationAnchor + \brief how the view should position the scene during transformations. + + QGraphicsView uses this property to decide how to position the scene in + the viewport when the transformation matrix changes, and the coordinate + system of the view is transformed. The default behavior, AnchorViewCenter, + ensures that the scene point at the center of the view remains unchanged + during transformations (e.g., when rotating, the scene will appear to + rotate around the center of the view). + + Note that the effect of this property is noticeable when only a part of the + scene is visible (i.e., when there are scroll bars). Otherwise, if the + whole scene fits in the view, QGraphicsScene uses the view \l alignment to + position the scene in the view. + + \sa alignment, resizeAnchor +*/ +QGraphicsView::ViewportAnchor QGraphicsView::transformationAnchor() const +{ + Q_D(const QGraphicsView); + return d->transformationAnchor; +} +void QGraphicsView::setTransformationAnchor(ViewportAnchor anchor) +{ + Q_D(QGraphicsView); + d->transformationAnchor = anchor; + + // Ensure mouse tracking is enabled in the case we are using AnchorUnderMouse + // in order to have up-to-date information for centering the view. + if (d->transformationAnchor == AnchorUnderMouse) + d->viewport->setMouseTracking(true); +} + +/*! + \property QGraphicsView::resizeAnchor + \brief how the view should position the scene when the view is resized. + + QGraphicsView uses this property to decide how to position the scene in + the viewport when the viewport widget's size changes. The default + behavior, NoAnchor, leaves the scene's position unchanged during a resize; + the top-left corner of the view will appear to be anchored while resizing. + + Note that the effect of this property is noticeable when only a part of the + scene is visible (i.e., when there are scroll bars). Otherwise, if the + whole scene fits in the view, QGraphicsScene uses the view \l alignment to + position the scene in the view. + + \sa alignment, transformationAnchor, Qt::WNorthWestGravity +*/ +QGraphicsView::ViewportAnchor QGraphicsView::resizeAnchor() const +{ + Q_D(const QGraphicsView); + return d->resizeAnchor; +} +void QGraphicsView::setResizeAnchor(ViewportAnchor anchor) +{ + Q_D(QGraphicsView); + d->resizeAnchor = anchor; + + // Ensure mouse tracking is enabled in the case we are using AnchorUnderMouse + // in order to have up-to-date information for centering the view. + if (d->resizeAnchor == AnchorUnderMouse) + d->viewport->setMouseTracking(true); +} + +/*! + \property QGraphicsView::viewportUpdateMode + \brief how the viewport should update its contents. + + \since 4.3 + + QGraphicsView uses this property to decide how to update areas of the + scene that have been reexposed or changed. Usually you do not need to + modify this property, but there are some cases where doing so can improve + rendering performance. See the ViewportUpdateMode documentation for + specific details. + + The default value is MinimalViewportUpdate, where QGraphicsView will + update as small an area of the viewport as possible when the contents + change. + + \sa ViewportUpdateMode, cacheMode +*/ +QGraphicsView::ViewportUpdateMode QGraphicsView::viewportUpdateMode() const +{ + Q_D(const QGraphicsView); + return d->viewportUpdateMode; +} +void QGraphicsView::setViewportUpdateMode(ViewportUpdateMode mode) +{ + Q_D(QGraphicsView); + d->viewportUpdateMode = mode; +} + +/*! + \property QGraphicsView::optimizationFlags + \brief flags that can be used to tune QGraphicsView's performance. + + \since 4.3 + + QGraphicsView uses clipping, extra bounding rect adjustments, and certain + other aids to improve rendering quality and performance for the common + case graphics scene. However, depending on the target platform, the scene, + and the viewport in use, some of these operations can degrade performance. + + The effect varies from flag to flag; see the OptimizationFlags + documentation for details. + + By default, no optimization flags are enabled. + + \sa setOptimizationFlag() +*/ +QGraphicsView::OptimizationFlags QGraphicsView::optimizationFlags() const +{ + Q_D(const QGraphicsView); + return d->optimizationFlags; +} +void QGraphicsView::setOptimizationFlags(OptimizationFlags flags) +{ + Q_D(QGraphicsView); + d->optimizationFlags = flags; +} + +/*! + Enables \a flag if \a enabled is true; otherwise disables \a flag. + + \sa optimizationFlags +*/ +void QGraphicsView::setOptimizationFlag(OptimizationFlag flag, bool enabled) +{ + Q_D(QGraphicsView); + if (enabled) + d->optimizationFlags |= flag; + else + d->optimizationFlags &= ~flag; +} + +/*! + \property QGraphicsView::dragMode + \brief the behavior for dragging the mouse over the scene while + the left mouse button is pressed. + + This property defines what should happen when the user clicks on the scene + background and drags the mouse (e.g., scrolling the viewport contents + using a pointing hand cursor, or selecting multiple items with a rubber + band). The default value, NoDrag, does nothing. + + This behavior only affects mouse clicks that are not handled by any item. + You can define a custom behavior by creating a subclass of QGraphicsView + and reimplementing mouseMoveEvent(). +*/ +QGraphicsView::DragMode QGraphicsView::dragMode() const +{ + Q_D(const QGraphicsView); + return d->dragMode; +} +void QGraphicsView::setDragMode(DragMode mode) +{ + Q_D(QGraphicsView); + if (d->dragMode == mode) + return; + +#ifndef QT_NO_CURSOR + if (d->dragMode == ScrollHandDrag) + viewport()->unsetCursor(); +#endif + + // If dragMode is unset while dragging, e.g. via a keyEvent, we + // don't unset the handScrolling state. When enabling scrolling + // again the mouseMoveEvent will automatically start scrolling, + // without a mousePress + if (d->dragMode == ScrollHandDrag && mode == NoDrag && d->handScrolling) + d->handScrolling = false; + + d->dragMode = mode; + +#ifndef QT_NO_CURSOR + if (d->dragMode == ScrollHandDrag) { + // Forget the stored viewport cursor when we enter scroll hand drag mode. + d->hasStoredOriginalCursor = false; + viewport()->setCursor(Qt::OpenHandCursor); + } +#endif +} + +#ifndef QT_NO_RUBBERBAND +/*! + \property QGraphicsView::rubberBandSelectionMode + \brief the behavior for selecting items with a rubber band selection rectangle. + \since 4.3 + + This property defines how items are selected when using the RubberBandDrag + drag mode. + + The default value is Qt::IntersectsItemShape; all items whose shape + intersects with or is contained by the rubber band are selected. + + \sa dragMode, items() +*/ +Qt::ItemSelectionMode QGraphicsView::rubberBandSelectionMode() const +{ + Q_D(const QGraphicsView); + return d->rubberBandSelectionMode; +} +void QGraphicsView::setRubberBandSelectionMode(Qt::ItemSelectionMode mode) +{ + Q_D(QGraphicsView); + d->rubberBandSelectionMode = mode; +} +#endif + +/*! + \property QGraphicsView::cacheMode + \brief which parts of the view are cached + + QGraphicsView can cache pre-rendered content in a QPixmap, which is then + drawn onto the viewport. The purpose of such caching is to speed up the + total rendering time for areas that are slow to render. Texture, gradient + and alpha blended backgrounds, for example, can be notibly slow to render; + especially with a transformed view. The CacheBackground flag enables + caching of the view's background. For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 2 + + The cache is invalidated every time the view is transformed. However, when + scrolling, only partial invalidation is required. + + By default, nothing is cached. + + \sa resetCachedContent(), QPixmapCache +*/ +QGraphicsView::CacheMode QGraphicsView::cacheMode() const +{ + Q_D(const QGraphicsView); + return d->cacheMode; +} +void QGraphicsView::setCacheMode(CacheMode mode) +{ + Q_D(QGraphicsView); + if (mode == d->cacheMode) + return; + d->cacheMode = mode; + resetCachedContent(); +} + +/*! + Resets any cached content. Calling this function will clear + QGraphicsView's cache. If the current cache mode is \l CacheNone, this + function does nothing. + + This function is called automatically for you when the backgroundBrush or + QGraphicsScene::backgroundBrush properties change; you only need to call + this function if you have reimplemented QGraphicsScene::drawBackground() + or QGraphicsView::drawBackground() to draw a custom background, and need + to trigger a full redraw. + + \sa cacheMode() +*/ +void QGraphicsView::resetCachedContent() +{ + Q_D(QGraphicsView); + if (d->cacheMode == CacheNone) + return; + + if (d->cacheMode & CacheBackground) { + // Background caching is enabled. + d->mustResizeBackgroundPixmap = true; + d->updateAll(); + } else if (d->mustResizeBackgroundPixmap) { + // Background caching is disabled. + // Cleanup, free some resources. + d->mustResizeBackgroundPixmap = false; + d->backgroundPixmap = QPixmap(); + d->backgroundPixmapExposed = QRegion(); + } +} + +/*! + Invalidates and schedules a redraw of \a layers inside \a rect. \a rect is + in scene coordinates. Any cached content for \a layers inside \a rect is + unconditionally invalidated and redrawn. + + You can call this function to notify QGraphicsView of changes to the + background or the foreground of the scene. It is commonly used for scenes + with tile-based backgrounds to notify changes when QGraphicsView has + enabled background caching. + + Note that QGraphicsView currently supports background caching only (see + QGraphicsView::CacheBackground). This function is equivalent to calling update() if any + layer but QGraphicsScene::BackgroundLayer is passed. + + \sa QGraphicsScene::invalidate(), update() +*/ +void QGraphicsView::invalidateScene(const QRectF &rect, QGraphicsScene::SceneLayers layers) +{ + Q_D(QGraphicsView); + if ((layers & QGraphicsScene::BackgroundLayer) && !d->mustResizeBackgroundPixmap) { + QRect viewRect = mapFromScene(rect).boundingRect(); + if (viewport()->rect().intersects(viewRect)) { + // The updated background area is exposed; schedule this area for + // redrawing. + d->backgroundPixmapExposed += viewRect; + if (d->scene) + d->scene->update(rect); + } + } +} + +/*! + \property QGraphicsView::interactive + \brief whether the view allowed scene interaction. + + If enabled, this view is set to allow scene interaction. Otherwise, this + view will not allow interaction, and any mouse or key events are ignored + (i.e., it will act as a read-only view). + + By default, this property is true. +*/ +bool QGraphicsView::isInteractive() const +{ + Q_D(const QGraphicsView); + return d->sceneInteractionAllowed; +} +void QGraphicsView::setInteractive(bool allowed) +{ + Q_D(QGraphicsView); + d->sceneInteractionAllowed = allowed; +} + +/*! + Returns a pointer to the scene that is currently visualized in the + view. If no scene is currently visualized, 0 is returned. + + \sa setScene() +*/ +QGraphicsScene *QGraphicsView::scene() const +{ + Q_D(const QGraphicsView); + return d->scene; +} + +/*! + Sets the current scene to \a scene. If \a scene is already being + viewed, this function does nothing. + + When a scene is set on a view, the QGraphicsScene::changed() signal + is automatically connected to this view's updateScene() slot, and the + view's scroll bars are adjusted to fit the size of the scene. +*/ +void QGraphicsView::setScene(QGraphicsScene *scene) +{ + Q_D(QGraphicsView); + if (d->scene == scene) + return; + + // Always update the viewport when the scene changes. + d->updateAll(); + + // Remove the previously assigned scene. + if (d->scene) { + disconnect(d->scene, SIGNAL(changed(QList)), + this, SLOT(updateScene(QList))); + disconnect(d->scene, SIGNAL(sceneRectChanged(QRectF)), + this, SLOT(updateSceneRect(QRectF))); + d->scene->d_func()->removeView(this); + d->connectedToScene = false; + + if (isActiveWindow() && isVisible()) { + QEvent windowDeactivate(QEvent::WindowDeactivate); + QApplication::sendEvent(d->scene, &windowDeactivate); + } + if(hasFocus()) + d->scene->clearFocus(); + } + + // Assign the new scene and update the contents (scrollbars, etc.)). + if ((d->scene = scene)) { + connect(d->scene, SIGNAL(sceneRectChanged(QRectF)), + this, SLOT(updateSceneRect(QRectF))); + d->updateSceneSlotReimplementedChecked = false; + d->scene->d_func()->addView(this); + d->recalculateContentSize(); + d->lastCenterPoint = sceneRect().center(); + d->keepLastCenterPoint = true; + // We are only interested in mouse tracking if items accept + // hover events or use non-default cursors. + if (!d->scene->d_func()->allItemsIgnoreHoverEvents + || !d->scene->d_func()->allItemsUseDefaultCursor) { + d->viewport->setMouseTracking(true); + } + + // enable touch events if any items is interested in them + if (!d->scene->d_func()->allItemsIgnoreTouchEvents) + d->viewport->setAttribute(Qt::WA_AcceptTouchEvents); + + if (isActiveWindow() && isVisible()) { + QEvent windowActivate(QEvent::WindowActivate); + QApplication::sendEvent(d->scene, &windowActivate); + } + } else { + d->recalculateContentSize(); + } + + d->updateInputMethodSensitivity(); + + if (d->scene && hasFocus()) + d->scene->setFocus(); +} + +/*! + \property QGraphicsView::sceneRect + \brief the area of the scene visualized by this view. + + The scene rectangle defines the extent of the scene, and in the view's case, + this means the area of the scene that you can navigate using the scroll + bars. + + If unset, or if a null QRectF is set, this property has the same value as + QGraphicsScene::sceneRect, and it changes with + QGraphicsScene::sceneRect. Otherwise, the view's scene rect is unaffected + by the scene. + + Note that, although the scene supports a virtually unlimited size, the + range of the scroll bars will never exceed the range of an integer + (INT_MIN, INT_MAX). When the scene is larger than the scroll bars' values, + you can choose to use translate() to navigate the scene instead. + + By default, this property contains a rectangle at the origin with zero + width and height. + + \sa QGraphicsScene::sceneRect +*/ +QRectF QGraphicsView::sceneRect() const +{ + Q_D(const QGraphicsView); + if (d->hasSceneRect) + return d->sceneRect; + if (d->scene) + return d->scene->sceneRect(); + return QRectF(); +} +void QGraphicsView::setSceneRect(const QRectF &rect) +{ + Q_D(QGraphicsView); + d->hasSceneRect = !rect.isNull(); + d->sceneRect = rect; + d->recalculateContentSize(); +} + +/*! + Returns the current transformation matrix for the view. If no current + transformation is set, the identity matrix is returned. + + \sa setMatrix(), transform(), rotate(), scale(), shear(), translate() +*/ +QMatrix QGraphicsView::matrix() const +{ + Q_D(const QGraphicsView); + return d->matrix.toAffine(); +} + +/*! + Sets the view's current transformation matrix to \a matrix. + + If \a combine is true, then \a matrix is combined with the current matrix; + otherwise, \a matrix \e replaces the current matrix. \a combine is false + by default. + + The transformation matrix tranforms the scene into view coordinates. Using + the default transformation, provided by the identity matrix, one pixel in + the view represents one unit in the scene (e.g., a 10x10 rectangular item + is drawn using 10x10 pixels in the view). If a 2x2 scaling matrix is + applied, the scene will be drawn in 1:2 (e.g., a 10x10 rectangular item is + then drawn using 20x20 pixels in the view). + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 3 + + To simplify interation with items using a transformed view, QGraphicsView + provides mapTo... and mapFrom... functions that can translate between + scene and view coordinates. For example, you can call mapToScene() to map + a view coordinate to a floating point scene coordinate, or mapFromScene() + to map from floating point scene coordinates to view coordinates. + + \sa matrix(), setTransform(), rotate(), scale(), shear(), translate() +*/ +void QGraphicsView::setMatrix(const QMatrix &matrix, bool combine) +{ + setTransform(QTransform(matrix), combine); +} + +/*! + Resets the view transformation matrix to the identity matrix. + + \sa resetTransform() +*/ +void QGraphicsView::resetMatrix() +{ + resetTransform(); +} + +/*! + Rotates the current view transformation \a angle degrees clockwise. + + \sa setTransform(), transform(), scale(), shear(), translate() +*/ +void QGraphicsView::rotate(qreal angle) +{ + Q_D(QGraphicsView); + QTransform matrix = d->matrix; + matrix.rotate(angle); + setTransform(matrix); +} + +/*! + Scales the current view transformation by (\a sx, \a sy). + + \sa setTransform(), transform(), rotate(), shear(), translate() +*/ +void QGraphicsView::scale(qreal sx, qreal sy) +{ + Q_D(QGraphicsView); + QTransform matrix = d->matrix; + matrix.scale(sx, sy); + setTransform(matrix); +} + +/*! + Shears the current view transformation by (\a sh, \a sv). + + \sa setTransform(), transform(), rotate(), scale(), translate() +*/ +void QGraphicsView::shear(qreal sh, qreal sv) +{ + Q_D(QGraphicsView); + QTransform matrix = d->matrix; + matrix.shear(sh, sv); + setTransform(matrix); +} + +/*! + Translates the current view transformation by (\a dx, \a dy). + + \sa setTransform(), transform(), rotate(), shear() +*/ +void QGraphicsView::translate(qreal dx, qreal dy) +{ + Q_D(QGraphicsView); + QTransform matrix = d->matrix; + matrix.translate(dx, dy); + setTransform(matrix); +} + +/*! + Scrolls the contents of the viewport to ensure that the scene + coordinate \a pos, is centered in the view. + + Because \a pos is a floating point coordinate, and the scroll bars operate + on integer coordinates, the centering is only an approximation. + + \note If the item is close to or outside the border, it will be visible + in the view, but not centered. + + \sa ensureVisible() +*/ +void QGraphicsView::centerOn(const QPointF &pos) +{ + Q_D(QGraphicsView); + qreal width = viewport()->width(); + qreal height = viewport()->height(); + QPointF viewPoint = d->matrix.map(pos); + QPointF oldCenterPoint = pos; + + if (!d->leftIndent) { + if (isRightToLeft()) { + qint64 horizontal = 0; + horizontal += horizontalScrollBar()->minimum(); + horizontal += horizontalScrollBar()->maximum(); + horizontal -= int(viewPoint.x() - width / 2.0); + horizontalScrollBar()->setValue(horizontal); + } else { + horizontalScrollBar()->setValue(int(viewPoint.x() - width / 2.0)); + } + } + if (!d->topIndent) + verticalScrollBar()->setValue(int(viewPoint.y() - height / 2.0)); + d->lastCenterPoint = oldCenterPoint; +} + +/*! + \fn QGraphicsView::centerOn(qreal x, qreal y) + \overload + + This function is provided for convenience. It's equivalent to calling + centerOn(QPointF(\a x, \a y)). +*/ + +/*! + \overload + + Scrolls the contents of the viewport to ensure that \a item + is centered in the view. + + \sa ensureVisible() +*/ +void QGraphicsView::centerOn(const QGraphicsItem *item) +{ + centerOn(item->sceneBoundingRect().center()); +} + +/*! + Scrolls the contents of the viewport so that the scene rectangle \a rect + is visible, with margins specified in pixels by \a xmargin and \a + ymargin. If the specified rect cannot be reached, the contents are + scrolled to the nearest valid position. The default value for both margins + is 50 pixels. + + \sa centerOn() +*/ +void QGraphicsView::ensureVisible(const QRectF &rect, int xmargin, int ymargin) +{ + Q_D(QGraphicsView); + qreal width = viewport()->width(); + qreal height = viewport()->height(); + QRectF viewRect = d->matrix.mapRect(rect); + + qreal left = d->horizontalScroll(); + qreal right = left + width; + qreal top = d->verticalScroll(); + qreal bottom = top + height; + + if (viewRect.left() <= left + xmargin) { + // need to scroll from the left + if (!d->leftIndent) + horizontalScrollBar()->setValue(int(viewRect.left() - xmargin - 0.5)); + } + if (viewRect.right() >= right - xmargin) { + // need to scroll from the right + if (!d->leftIndent) + horizontalScrollBar()->setValue(int(viewRect.right() - width + xmargin + 0.5)); + } + if (viewRect.top() <= top + ymargin) { + // need to scroll from the top + if (!d->topIndent) + verticalScrollBar()->setValue(int(viewRect.top() - ymargin - 0.5)); + } + if (viewRect.bottom() >= bottom - ymargin) { + // need to scroll from the bottom + if (!d->topIndent) + verticalScrollBar()->setValue(int(viewRect.bottom() - height + ymargin + 0.5)); + } +} + +/*! + \fn QGraphicsView::ensureVisible(qreal x, qreal y, qreal w, qreal h, + int xmargin, int ymargin) + \overload + + This function is provided for convenience. It's equivalent to calling + ensureVisible(QRectF(\a x, \a y, \a w, \a h), \a xmargin, \a ymargin). +*/ + +/*! + \overload + + Scrolls the contents of the viewport so that the center of item \a item is + visible, with margins specified in pixels by \a xmargin and \a ymargin. If + the specified point cannot be reached, the contents are scrolled to the + nearest valid position. The default value for both margins is 50 pixels. + + \sa centerOn() +*/ +void QGraphicsView::ensureVisible(const QGraphicsItem *item, int xmargin, int ymargin) +{ + ensureVisible(item->sceneBoundingRect(), xmargin, ymargin); +} + +/*! + Scales the view matrix and scrolls the scroll bars to ensure that the + scene rectangle \a rect fits inside the viewport. \a rect must be inside + the scene rect; otherwise, fitInView() cannot guarantee that the whole + rect is visible. + + This function keeps the view's rotation, translation, or shear. The view + is scaled according to \a aspectRatioMode. \a rect will be centered in the + view if it does not fit tightly. + + It's common to call fitInView() from inside a reimplementation of + resizeEvent(), to ensure that the whole scene, or parts of the scene, + scales automatically to fit the new size of the viewport as the view is + resized. Note though, that calling fitInView() from inside resizeEvent() + can lead to unwanted resize recursion, if the new transformation toggles + the automatic state of the scrollbars. You can toggle the scrollbar + policies to always on or always off to prevent this (see + horizontalScrollBarPolicy() and verticalScrollBarPolicy()). + + If \a rect is empty, or if the viewport is too small, this + function will do nothing. + + \sa setTransform(), ensureVisible(), centerOn() +*/ +void QGraphicsView::fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRatioMode) +{ + Q_D(QGraphicsView); + if (!d->scene || rect.isNull()) + return; + + // Reset the view scale to 1:1. + QRectF unity = d->matrix.mapRect(QRectF(0, 0, 1, 1)); + if (unity.isEmpty()) + return; + scale(1 / unity.width(), 1 / unity.height()); + + // Find the ideal x / y scaling ratio to fit \a rect in the view. + int margin = 2; + QRectF viewRect = viewport()->rect().adjusted(margin, margin, -margin, -margin); + if (viewRect.isEmpty()) + return; + QRectF sceneRect = d->matrix.mapRect(rect); + if (sceneRect.isEmpty()) + return; + qreal xratio = viewRect.width() / sceneRect.width(); + qreal yratio = viewRect.height() / sceneRect.height(); + + // Respect the aspect ratio mode. + switch (aspectRatioMode) { + case Qt::KeepAspectRatio: + xratio = yratio = qMin(xratio, yratio); + break; + case Qt::KeepAspectRatioByExpanding: + xratio = yratio = qMax(xratio, yratio); + break; + case Qt::IgnoreAspectRatio: + break; + } + + // Scale and center on the center of \a rect. + scale(xratio, yratio); + centerOn(rect.center()); +} + +/*! + \fn void QGraphicsView::fitInView(qreal x, qreal y, qreal w, qreal h, + Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) + + \overload + + This convenience function is equivalent to calling + fitInView(QRectF(\a x, \a y, \a w, \a h), \a aspectRatioMode). + + \sa ensureVisible(), centerOn() +*/ + +/*! + \overload + + Ensures that \a item fits tightly inside the view, scaling the view + according to \a aspectRatioMode. + + \sa ensureVisible(), centerOn() +*/ +void QGraphicsView::fitInView(const QGraphicsItem *item, Qt::AspectRatioMode aspectRatioMode) +{ + QPainterPath path = item->isClipped() ? item->clipPath() : item->shape(); + if (item->d_ptr->hasTranslateOnlySceneTransform()) { + path.translate(item->d_ptr->sceneTransform.dx(), item->d_ptr->sceneTransform.dy()); + fitInView(path.boundingRect(), aspectRatioMode); + } else { + fitInView(item->d_ptr->sceneTransform.map(path).boundingRect(), aspectRatioMode); + } +} + +/*! + Renders the \a source rect, which is in view coordinates, from the scene + into \a target, which is in paint device coordinates, using \a + painter. This function is useful for capturing the contents of the view + onto a paint device, such as a QImage (e.g., to take a screenshot), or for + printing to QPrinter. For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 4 + + If \a source is a null rect, this function will use viewport()->rect() to + determine what to draw. If \a target is a null rect, the full dimensions + of \a painter's paint device (e.g., for a QPrinter, the page size) will be + used. + + The source rect contents will be transformed according to \a + aspectRatioMode to fit into the target rect. By default, the aspect ratio + is kept, and \a source is scaled to fit in \a target. + + \sa QGraphicsScene::render() +*/ +void QGraphicsView::render(QPainter *painter, const QRectF &target, const QRect &source, + Qt::AspectRatioMode aspectRatioMode) +{ + // ### Switch to using the recursive rendering algorithm instead. + + Q_D(QGraphicsView); + if (!d->scene || !(painter && painter->isActive())) + return; + + // Default source rect = viewport rect + QRect sourceRect = source; + if (source.isNull()) + sourceRect = viewport()->rect(); + + // Default target rect = device rect + QRectF targetRect = target; + if (target.isNull()) { + if (painter->device()->devType() == QInternal::Picture) + targetRect = sourceRect; + else + targetRect.setRect(0, 0, painter->device()->width(), painter->device()->height()); + } + + // Find the ideal x / y scaling ratio to fit \a source into \a target. + qreal xratio = targetRect.width() / sourceRect.width(); + qreal yratio = targetRect.height() / sourceRect.height(); + + // Scale according to the aspect ratio mode. + switch (aspectRatioMode) { + case Qt::KeepAspectRatio: + xratio = yratio = qMin(xratio, yratio); + break; + case Qt::KeepAspectRatioByExpanding: + xratio = yratio = qMax(xratio, yratio); + break; + case Qt::IgnoreAspectRatio: + break; + } + + // Find all items to draw, and reverse the list (we want to draw + // in reverse order). + QPolygonF sourceScenePoly = mapToScene(sourceRect.adjusted(-1, -1, 1, 1)); + QList itemList = d->scene->items(sourceScenePoly, + Qt::IntersectsItemBoundingRect); + QGraphicsItem **itemArray = new QGraphicsItem *[itemList.size()]; + int numItems = itemList.size(); + for (int i = 0; i < numItems; ++i) + itemArray[numItems - i - 1] = itemList.at(i); + itemList.clear(); + + // Setup painter matrix. + QTransform moveMatrix = QTransform::fromTranslate(-d->horizontalScroll(), -d->verticalScroll()); + QTransform painterMatrix = d->matrix * moveMatrix; + painterMatrix *= QTransform() + .translate(targetRect.left(), targetRect.top()) + .scale(xratio, yratio) + .translate(-sourceRect.left(), -sourceRect.top()); + + // Generate the style options + QStyleOptionGraphicsItem *styleOptionArray = d->allocStyleOptionsArray(numItems); + for (int i = 0; i < numItems; ++i) + itemArray[i]->d_ptr->initStyleOption(&styleOptionArray[i], painterMatrix, targetRect.toRect()); + + painter->save(); + + // Clip in device coordinates to avoid QRegion transformations. + painter->setClipRect(targetRect); + QPainterPath path; + path.addPolygon(sourceScenePoly); + path.closeSubpath(); + painter->setClipPath(painterMatrix.map(path), Qt::IntersectClip); + + // Transform the painter. + painter->setTransform(painterMatrix, true); + + // Render the scene. + QRectF sourceSceneRect = sourceScenePoly.boundingRect(); + drawBackground(painter, sourceSceneRect); + drawItems(painter, numItems, itemArray, styleOptionArray); + drawForeground(painter, sourceSceneRect); + + delete [] itemArray; + d->freeStyleOptionsArray(styleOptionArray); + + painter->restore(); +} + +/*! + Returns a list of all the items in the associated scene, in descending + stacking order (i.e., the first item in the returned list is the uppermost + item). + + \sa QGraphicsScene::items(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsView::items() const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return QList(); + return d->scene->items(); +} + +/*! + Returns a list of all the items at the position \a pos in the view. The + items are listed in descending stacking order (i.e., the first item in the + list is the uppermost item, and the last item is the lowermost item). \a + pos is in viewport coordinates. + + This function is most commonly called from within mouse event handlers in + a subclass in QGraphicsView. \a pos is in untransformed viewport + coordinates, just like QMouseEvent::pos(). + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 5 + + \sa QGraphicsScene::items(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsView::items(const QPoint &pos) const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return QList(); + // ### Unify these two, and use the items(QPointF) version in + // QGraphicsScene instead. The scene items function could use the viewport + // transform to map the point to a rect/polygon. + if ((d->identityMatrix || d->matrix.type() <= QTransform::TxScale)) { + // Use the rect version + QTransform xinv = viewportTransform().inverted(); + return d->scene->items(xinv.mapRect(QRectF(pos.x(), pos.y(), 1, 1)), + Qt::IntersectsItemShape, + Qt::DescendingOrder, + viewportTransform()); + } + // Use the polygon version + return d->scene->items(mapToScene(pos.x(), pos.y(), 1, 1), + Qt::IntersectsItemShape, + Qt::DescendingOrder, + viewportTransform()); +} + +/*! + \fn QGraphicsView::items(int x, int y) const + + This function is provided for convenience. It's equivalent to calling + items(QPoint(\a x, \a y)). +*/ + +/*! + \overload + + Returns a list of all the items that, depending on \a mode, are either + contained by or intersect with \a rect. \a rect is in viewport + coordinates. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a rect are returned. + + The items are sorted in descending stacking order (i.e., the first item in + the returned list is the uppermost item). + + \sa itemAt(), items(), mapToScene(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsView::items(const QRect &rect, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return QList(); + return d->scene->items(mapToScene(rect), mode, Qt::DescendingOrder, viewportTransform()); +} + +/*! + \fn QList QGraphicsView::items(int x, int y, int w, int h, Qt::ItemSelectionMode mode) const + \since 4.3 + + This convenience function is equivalent to calling items(QRectF(\a x, \a + y, \a w, \a h), \a mode). +*/ + +/*! + \overload + + Returns a list of all the items that, depending on \a mode, are either + contained by or intersect with \a polygon. \a polygon is in viewport + coordinates. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a polygon are returned. + + The items are sorted by descending stacking order (i.e., the first item in + the returned list is the uppermost item). + + \sa itemAt(), items(), mapToScene(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsView::items(const QPolygon &polygon, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return QList(); + return d->scene->items(mapToScene(polygon), mode, Qt::DescendingOrder, viewportTransform()); +} + +/*! + \overload + + Returns a list of all the items that, depending on \a mode, are either + contained by or intersect with \a path. \a path is in viewport + coordinates. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a path are returned. + + \sa itemAt(), items(), mapToScene(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsView::items(const QPainterPath &path, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return QList(); + return d->scene->items(mapToScene(path), mode, Qt::DescendingOrder, viewportTransform()); +} + +/*! + Returns the item at position \a pos, which is in viewport coordinates. + If there are several items at this position, this function returns + the topmost item. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 6 + + \sa items(), {QGraphicsItem#Sorting}{Sorting} +*/ +QGraphicsItem *QGraphicsView::itemAt(const QPoint &pos) const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return 0; + QList itemsAtPos = items(pos); + return itemsAtPos.isEmpty() ? 0 : itemsAtPos.first(); +} + +/*! + \overload + \fn QGraphicsItem *QGraphicsView::itemAt(int x, int y) const + + This function is provided for convenience. It's equivalent to + calling itemAt(QPoint(\a x, \a y)). +*/ + +/*! + Returns the viewport coordinate \a point mapped to scene coordinates. + + Note: It can be useful to map the whole rectangle covered by the pixel at + \a point instead of the point itself. To do this, you can call + mapToScene(QRect(\a point, QSize(2, 2))). + + \sa mapFromScene() +*/ +QPointF QGraphicsView::mapToScene(const QPoint &point) const +{ + Q_D(const QGraphicsView); + QPointF p = point; + p.rx() += d->horizontalScroll(); + p.ry() += d->verticalScroll(); + return d->identityMatrix ? p : d->matrix.inverted().map(p); +} + +/*! + \fn QGraphicsView::mapToScene(int x, int y) const + + This function is provided for convenience. It's equivalent to calling + mapToScene(QPoint(\a x, \a y)). +*/ + +/*! + Returns the viewport rectangle \a rect mapped to a scene coordinate + polygon. + + \sa mapFromScene() +*/ +QPolygonF QGraphicsView::mapToScene(const QRect &rect) const +{ + Q_D(const QGraphicsView); + if (!rect.isValid()) + return QPolygonF(); + + QPointF scrollOffset(d->horizontalScroll(), d->verticalScroll()); + QRect r = rect.adjusted(0, 0, 1, 1); + QPointF tl = scrollOffset + r.topLeft(); + QPointF tr = scrollOffset + r.topRight(); + QPointF br = scrollOffset + r.bottomRight(); + QPointF bl = scrollOffset + r.bottomLeft(); + + QPolygonF poly(4); + if (!d->identityMatrix) { + QTransform x = d->matrix.inverted(); + poly[0] = x.map(tl); + poly[1] = x.map(tr); + poly[2] = x.map(br); + poly[3] = x.map(bl); + } else { + poly[0] = tl; + poly[1] = tr; + poly[2] = br; + poly[3] = bl; + } + return poly; +} + +/*! + \fn QGraphicsView::mapToScene(int x, int y, int w, int h) const + + This function is provided for convenience. It's equivalent to calling + mapToScene(QRect(\a x, \a y, \a w, \a h)). +*/ + +/*! + Returns the viewport polygon \a polygon mapped to a scene coordinate + polygon. + + \sa mapFromScene() +*/ +QPolygonF QGraphicsView::mapToScene(const QPolygon &polygon) const +{ + QPolygonF poly; + foreach (const QPoint &point, polygon) + poly << mapToScene(point); + return poly; +} + +/*! + Returns the viewport painter path \a path mapped to a scene coordinate + painter path. + + \sa mapFromScene() +*/ +QPainterPath QGraphicsView::mapToScene(const QPainterPath &path) const +{ + Q_D(const QGraphicsView); + QTransform matrix = QTransform::fromTranslate(d->horizontalScroll(), d->verticalScroll()); + matrix *= d->matrix.inverted(); + return matrix.map(path); +} + +/*! + Returns the scene coordinate \a point to viewport coordinates. + + \sa mapToScene() +*/ +QPoint QGraphicsView::mapFromScene(const QPointF &point) const +{ + Q_D(const QGraphicsView); + QPointF p = d->identityMatrix ? point : d->matrix.map(point); + p.rx() -= d->horizontalScroll(); + p.ry() -= d->verticalScroll(); + return p.toPoint(); +} + +/*! + \fn QGraphicsView::mapFromScene(qreal x, qreal y) const + + This function is provided for convenience. It's equivalent to + calling mapFromScene(QPointF(\a x, \a y)). +*/ + +/*! + Returns the scene rectangle \a rect to a viewport coordinate + polygon. + + \sa mapToScene() +*/ +QPolygon QGraphicsView::mapFromScene(const QRectF &rect) const +{ + Q_D(const QGraphicsView); + QPointF tl; + QPointF tr; + QPointF br; + QPointF bl; + if (!d->identityMatrix) { + const QTransform &x = d->matrix; + tl = x.map(rect.topLeft()); + tr = x.map(rect.topRight()); + br = x.map(rect.bottomRight()); + bl = x.map(rect.bottomLeft()); + } else { + tl = rect.topLeft(); + tr = rect.topRight(); + br = rect.bottomRight(); + bl = rect.bottomLeft(); + } + QPointF scrollOffset(d->horizontalScroll(), d->verticalScroll()); + tl -= scrollOffset; + tr -= scrollOffset; + br -= scrollOffset; + bl -= scrollOffset; + + QPolygon poly(4); + poly[0] = tl.toPoint(); + poly[1] = tr.toPoint(); + poly[2] = br.toPoint(); + poly[3] = bl.toPoint(); + return poly; +} + +/*! + \fn QGraphicsView::mapFromScene(qreal x, qreal y, qreal w, qreal h) const + + This function is provided for convenience. It's equivalent to + calling mapFromScene(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Returns the scene coordinate polygon \a polygon to a viewport coordinate + polygon. + + \sa mapToScene() +*/ +QPolygon QGraphicsView::mapFromScene(const QPolygonF &polygon) const +{ + QPolygon poly; + foreach (const QPointF &point, polygon) + poly << mapFromScene(point); + return poly; +} + +/*! + Returns the scene coordinate painter path \a path to a viewport coordinate + painter path. + + \sa mapToScene() +*/ +QPainterPath QGraphicsView::mapFromScene(const QPainterPath &path) const +{ + Q_D(const QGraphicsView); + QTransform matrix = d->matrix; + matrix *= QTransform::fromTranslate(-d->horizontalScroll(), -d->verticalScroll()); + return matrix.map(path); +} + +/*! + \reimp +*/ +QVariant QGraphicsView::inputMethodQuery(Qt::InputMethodQuery query) const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return QVariant(); + + QVariant value = d->scene->inputMethodQuery(query); + if (value.type() == QVariant::RectF) + value = d->mapRectFromScene(value.toRectF()); + else if (value.type() == QVariant::PointF) + value = mapFromScene(value.toPointF()); + else if (value.type() == QVariant::Rect) + value = d->mapRectFromScene(value.toRect()).toRect(); + else if (value.type() == QVariant::Point) + value = mapFromScene(value.toPoint()); + return value; +} + +/*! + \property QGraphicsView::backgroundBrush + \brief the background brush of the scene. + + This property sets the background brush for the scene in this view. It is + used to override the scene's own background, and defines the behavior of + drawBackground(). To provide custom background drawing for this view, you + can reimplement drawBackground() instead. + + By default, this property contains a brush with the Qt::NoBrush pattern. + + \sa QGraphicsScene::backgroundBrush, foregroundBrush +*/ +QBrush QGraphicsView::backgroundBrush() const +{ + Q_D(const QGraphicsView); + return d->backgroundBrush; +} +void QGraphicsView::setBackgroundBrush(const QBrush &brush) +{ + Q_D(QGraphicsView); + d->backgroundBrush = brush; + d->updateAll(); + + if (d->cacheMode & CacheBackground) { + // Invalidate the background pixmap + d->mustResizeBackgroundPixmap = true; + } +} + +/*! + \property QGraphicsView::foregroundBrush + \brief the foreground brush of the scene. + + This property sets the foreground brush for the scene in this view. It is + used to override the scene's own foreground, and defines the behavior of + drawForeground(). To provide custom foreground drawing for this view, you + can reimplement drawForeground() instead. + + By default, this property contains a brush with the Qt::NoBrush pattern. + + \sa QGraphicsScene::foregroundBrush, backgroundBrush +*/ +QBrush QGraphicsView::foregroundBrush() const +{ + Q_D(const QGraphicsView); + return d->foregroundBrush; +} +void QGraphicsView::setForegroundBrush(const QBrush &brush) +{ + Q_D(QGraphicsView); + d->foregroundBrush = brush; + d->updateAll(); +} + +/*! + Schedules an update of the scene rectangles \a rects. + + \sa QGraphicsScene::changed() +*/ +void QGraphicsView::updateScene(const QList &rects) +{ + // ### Note: Since 4.5, this slot is only called if the user explicitly + // establishes a connection between the scene and the view, as the scene + // and view are no longer connected. We need to keep it working (basically + // leave it as it is), but the new delivery path is through + // QGraphicsScenePrivate::itemUpdate(). + Q_D(QGraphicsView); + if (d->fullUpdatePending || d->viewportUpdateMode == QGraphicsView::NoViewportUpdate) + return; + + // Extract and reset dirty scene rect info. + QVector dirtyViewportRects; + const QVector &dirtyRects = d->dirtyRegion.rects(); + for (int i = 0; i < dirtyRects.size(); ++i) + dirtyViewportRects += dirtyRects.at(i); + d->dirtyRegion = QRegion(); + d->dirtyBoundingRect = QRect(); + + bool fullUpdate = !d->accelerateScrolling || d->viewportUpdateMode == QGraphicsView::FullViewportUpdate; + bool boundingRectUpdate = (d->viewportUpdateMode == QGraphicsView::BoundingRectViewportUpdate) + || (d->viewportUpdateMode == QGraphicsView::SmartViewportUpdate + && ((dirtyViewportRects.size() + rects.size()) >= QGRAPHICSVIEW_REGION_RECT_THRESHOLD)); + + QRegion updateRegion; + QRect boundingRect; + QRect viewportRect = viewport()->rect(); + bool redraw = false; + QTransform transform = viewportTransform(); + + // Convert scene rects to viewport rects. + foreach (const QRectF &rect, rects) { + QRect xrect = transform.mapRect(rect).toAlignedRect(); + if (!(d->optimizationFlags & DontAdjustForAntialiasing)) + xrect.adjust(-2, -2, 2, 2); + else + xrect.adjust(-1, -1, 1, 1); + if (!viewportRect.intersects(xrect)) + continue; + dirtyViewportRects << xrect; + } + + foreach (const QRect &rect, dirtyViewportRects) { + // Add the exposed rect to the update region. In rect update + // mode, we only count the bounding rect of items. + if (!boundingRectUpdate) { + updateRegion += rect; + } else { + boundingRect |= rect; + } + redraw = true; + if (fullUpdate) { + // If fullUpdate is true and we found a visible dirty rect, + // we're done. + break; + } + } + + if (!redraw) + return; + + if (fullUpdate) + viewport()->update(); + else if (boundingRectUpdate) + viewport()->update(boundingRect); + else + viewport()->update(updateRegion); +} + +/*! + Notifies QGraphicsView that the scene's scene rect has changed. \a rect + is the new scene rect. If the view already has an explicitly set scene + rect, this function does nothing. + + \sa sceneRect, QGraphicsScene::sceneRectChanged() +*/ +void QGraphicsView::updateSceneRect(const QRectF &rect) +{ + Q_D(QGraphicsView); + if (!d->hasSceneRect) { + d->sceneRect = rect; + d->recalculateContentSize(); + } +} + +/*! + This slot is called by QAbstractScrollArea after setViewport() has been + called. Reimplement this function in a subclass of QGraphicsView to + initialize the new viewport \a widget before it is used. + + \sa setViewport() +*/ +void QGraphicsView::setupViewport(QWidget *widget) +{ + Q_D(QGraphicsView); + + if (!widget) { + qWarning("QGraphicsView::setupViewport: cannot initialize null widget"); + return; + } + + const bool isGLWidget = widget->inherits("QGLWidget"); + + d->accelerateScrolling = !(isGLWidget); + + widget->setFocusPolicy(Qt::StrongFocus); + + if (!isGLWidget) { + // autoFillBackground enables scroll acceleration. + widget->setAutoFillBackground(true); + } + + // We are only interested in mouse tracking if items + // accept hover events or use non-default cursors or if + // AnchorUnderMouse is used as transformation or resize anchor. + if ((d->scene && (!d->scene->d_func()->allItemsIgnoreHoverEvents + || !d->scene->d_func()->allItemsUseDefaultCursor)) + || d->transformationAnchor == AnchorUnderMouse + || d->resizeAnchor == AnchorUnderMouse) { + widget->setMouseTracking(true); + } + + // enable touch events if any items is interested in them + if (d->scene && !d->scene->d_func()->allItemsIgnoreTouchEvents) + widget->setAttribute(Qt::WA_AcceptTouchEvents); + +#ifndef QT_NO_GESTURES + if (d->scene) { + foreach (Qt::GestureType gesture, d->scene->d_func()->grabbedGestures.keys()) + widget->grabGesture(gesture); + } +#endif + + widget->setAcceptDrops(acceptDrops()); +} + +/*! + \reimp +*/ +bool QGraphicsView::event(QEvent *event) +{ + Q_D(QGraphicsView); + + if (d->sceneInteractionAllowed) { + switch (event->type()) { + case QEvent::ShortcutOverride: + if (d->scene) + return QApplication::sendEvent(d->scene, event); + break; + case QEvent::KeyPress: + if (d->scene) { + QKeyEvent *k = static_cast(event); + if (k->key() == Qt::Key_Tab || k->key() == Qt::Key_Backtab) { + // Send the key events to the scene. This will invoke the + // scene's tab focus handling, and if the event is + // accepted, we return (prevent further event delivery), + // and the base implementation will call QGraphicsView's + // focusNextPrevChild() function. If the event is ignored, + // we fall back to standard tab focus handling. + QApplication::sendEvent(d->scene, event); + if (event->isAccepted()) + return true; + // Ensure the event doesn't propagate just because the + // scene ignored it. If the event propagates, then tab + // handling will be called twice (this and parent). + event->accept(); + } + } + break; + default: + break; + } + } + + return QAbstractScrollArea::event(event); +} + +/*! + \reimp +*/ +bool QGraphicsView::viewportEvent(QEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene) + return QAbstractScrollArea::viewportEvent(event); + + switch (event->type()) { + case QEvent::Enter: + QApplication::sendEvent(d->scene, event); + break; + case QEvent::WindowActivate: + QApplication::sendEvent(d->scene, event); + break; + case QEvent::WindowDeactivate: + // ### This is a temporary fix for until we get proper mouse + // grab events. mouseGrabberItem should be set to 0 if we lose + // the mouse grab. + // Remove all popups when the scene loses focus. + if (!d->scene->d_func()->popupWidgets.isEmpty()) + d->scene->d_func()->removePopup(d->scene->d_func()->popupWidgets.first()); + QApplication::sendEvent(d->scene, event); + break; + case QEvent::Show: + if (d->scene && isActiveWindow()) { + QEvent windowActivate(QEvent::WindowActivate); + QApplication::sendEvent(d->scene, &windowActivate); + } + break; + case QEvent::Hide: + // spontaneous event will generate a WindowDeactivate. + if (!event->spontaneous() && d->scene && isActiveWindow()) { + QEvent windowDeactivate(QEvent::WindowDeactivate); + QApplication::sendEvent(d->scene, &windowDeactivate); + } + break; + case QEvent::Leave: + // ### This is a temporary fix for until we get proper mouse grab + // events. activeMouseGrabberItem should be set to 0 if we lose the + // mouse grab. + if ((QApplication::activePopupWidget() && QApplication::activePopupWidget() != window()) + || (QApplication::activeModalWidget() && QApplication::activeModalWidget() != window()) + || (QApplication::activeWindow() != window())) { + if (!d->scene->d_func()->popupWidgets.isEmpty()) + d->scene->d_func()->removePopup(d->scene->d_func()->popupWidgets.first()); + } + d->useLastMouseEvent = false; + // a hack to pass a viewport pointer to the scene inside the leave event + Q_ASSERT(event->d == 0); + event->d = reinterpret_cast(viewport()); + QApplication::sendEvent(d->scene, event); + break; +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTip: { + QHelpEvent *toolTip = static_cast(event); + QGraphicsSceneHelpEvent helpEvent(QEvent::GraphicsSceneHelp); + helpEvent.setWidget(viewport()); + helpEvent.setScreenPos(toolTip->globalPos()); + helpEvent.setScenePos(mapToScene(toolTip->pos())); + QApplication::sendEvent(d->scene, &helpEvent); + toolTip->setAccepted(helpEvent.isAccepted()); + return true; + } +#endif + case QEvent::Paint: + // Reset full update + d->fullUpdatePending = false; + d->dirtyScrollOffset = QPoint(); + if (d->scene) { + // Check if this view reimplements the updateScene slot; if it + // does, we can't do direct update delivery and have to fall back + // to connecting the changed signal. + if (!d->updateSceneSlotReimplementedChecked) { + d->updateSceneSlotReimplementedChecked = true; + const QMetaObject *mo = metaObject(); + if (mo != &QGraphicsView::staticMetaObject) { + if (mo->indexOfSlot("updateScene(QList)") + != QGraphicsView::staticMetaObject.indexOfSlot("updateScene(QList)")) { + connect(d->scene, SIGNAL(changed(QList)), + this, SLOT(updateScene(QList))); + } + } + } + } + break; + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + { + if (!isEnabled()) + return false; + + if (d->scene && d->sceneInteractionAllowed) { + // Convert and deliver the touch event to the scene. + QTouchEvent *touchEvent = static_cast(event); + touchEvent->setWidget(viewport()); + QGraphicsViewPrivate::translateTouchEvent(d, touchEvent); + (void) QApplication::sendEvent(d->scene, touchEvent); + } + + return true; + } +#ifndef QT_NO_GESTURES + case QEvent::Gesture: + case QEvent::GestureOverride: + { + if (!isEnabled()) + return false; + + if (d->scene && d->sceneInteractionAllowed) { + QGestureEvent *gestureEvent = static_cast(event); + gestureEvent->setWidget(viewport()); + (void) QApplication::sendEvent(d->scene, gestureEvent); + } + return true; + } +#endif // QT_NO_GESTURES + default: + break; + } + + return QAbstractScrollArea::viewportEvent(event); +} + +#ifndef QT_NO_CONTEXTMENU +/*! + \reimp +*/ +void QGraphicsView::contextMenuEvent(QContextMenuEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + + d->mousePressViewPoint = event->pos(); + d->mousePressScenePoint = mapToScene(d->mousePressViewPoint); + d->mousePressScreenPoint = event->globalPos(); + d->lastMouseMoveScenePoint = d->mousePressScenePoint; + d->lastMouseMoveScreenPoint = d->mousePressScreenPoint; + + QGraphicsSceneContextMenuEvent contextEvent(QEvent::GraphicsSceneContextMenu); + contextEvent.setWidget(viewport()); + contextEvent.setScenePos(d->mousePressScenePoint); + contextEvent.setScreenPos(d->mousePressScreenPoint); + contextEvent.setModifiers(event->modifiers()); + contextEvent.setReason((QGraphicsSceneContextMenuEvent::Reason)(event->reason())); + contextEvent.setAccepted(event->isAccepted()); + QApplication::sendEvent(d->scene, &contextEvent); + event->setAccepted(contextEvent.isAccepted()); +} +#endif // QT_NO_CONTEXTMENU + +/*! + \reimp +*/ +void QGraphicsView::dropEvent(QDropEvent *event) +{ +#ifndef QT_NO_DRAGANDDROP + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + + // Generate a scene event. + QGraphicsSceneDragDropEvent sceneEvent(QEvent::GraphicsSceneDrop); + d->populateSceneDragDropEvent(&sceneEvent, event); + + // Send it to the scene. + QApplication::sendEvent(d->scene, &sceneEvent); + + // Accept the originating event if the scene accepted the scene event. + event->setAccepted(sceneEvent.isAccepted()); + if (sceneEvent.isAccepted()) + event->setDropAction(sceneEvent.dropAction()); + + delete d->lastDragDropEvent; + d->lastDragDropEvent = 0; + +#else + Q_UNUSED(event) +#endif +} + +/*! + \reimp +*/ +void QGraphicsView::dragEnterEvent(QDragEnterEvent *event) +{ +#ifndef QT_NO_DRAGANDDROP + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + + // Disable replaying of mouse move events. + d->useLastMouseEvent = false; + + // Generate a scene event. + QGraphicsSceneDragDropEvent sceneEvent(QEvent::GraphicsSceneDragEnter); + d->populateSceneDragDropEvent(&sceneEvent, event); + + // Store it for later use. + d->storeDragDropEvent(&sceneEvent); + + // Send it to the scene. + QApplication::sendEvent(d->scene, &sceneEvent); + + // Accept the originating event if the scene accepted the scene event. + if (sceneEvent.isAccepted()) { + event->setAccepted(true); + event->setDropAction(sceneEvent.dropAction()); + } +#else + Q_UNUSED(event) +#endif +} + +/*! + \reimp +*/ +void QGraphicsView::dragLeaveEvent(QDragLeaveEvent *event) +{ +#ifndef QT_NO_DRAGANDDROP + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + if (!d->lastDragDropEvent) { + qWarning("QGraphicsView::dragLeaveEvent: drag leave received before drag enter"); + return; + } + + // Generate a scene event. + QGraphicsSceneDragDropEvent sceneEvent(QEvent::GraphicsSceneDragLeave); + sceneEvent.setScenePos(d->lastDragDropEvent->scenePos()); + sceneEvent.setScreenPos(d->lastDragDropEvent->screenPos()); + sceneEvent.setButtons(d->lastDragDropEvent->buttons()); + sceneEvent.setModifiers(d->lastDragDropEvent->modifiers()); + sceneEvent.setPossibleActions(d->lastDragDropEvent->possibleActions()); + sceneEvent.setProposedAction(d->lastDragDropEvent->proposedAction()); + sceneEvent.setDropAction(d->lastDragDropEvent->dropAction()); + sceneEvent.setMimeData(d->lastDragDropEvent->mimeData()); + sceneEvent.setWidget(d->lastDragDropEvent->widget()); + sceneEvent.setSource(d->lastDragDropEvent->source()); + delete d->lastDragDropEvent; + d->lastDragDropEvent = 0; + + // Send it to the scene. + QApplication::sendEvent(d->scene, &sceneEvent); + + // Accept the originating event if the scene accepted the scene event. + if (sceneEvent.isAccepted()) + event->setAccepted(true); +#else + Q_UNUSED(event) +#endif +} + +/*! + \reimp +*/ +void QGraphicsView::dragMoveEvent(QDragMoveEvent *event) +{ +#ifndef QT_NO_DRAGANDDROP + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + + // Generate a scene event. + QGraphicsSceneDragDropEvent sceneEvent(QEvent::GraphicsSceneDragMove); + d->populateSceneDragDropEvent(&sceneEvent, event); + + // Store it for later use. + d->storeDragDropEvent(&sceneEvent); + + // Send it to the scene. + QApplication::sendEvent(d->scene, &sceneEvent); + + // Ignore the originating event if the scene ignored the scene event. + event->setAccepted(sceneEvent.isAccepted()); + if (sceneEvent.isAccepted()) + event->setDropAction(sceneEvent.dropAction()); +#else + Q_UNUSED(event) +#endif +} + +/*! + \reimp +*/ +void QGraphicsView::focusInEvent(QFocusEvent *event) +{ + Q_D(QGraphicsView); + d->updateInputMethodSensitivity(); + QAbstractScrollArea::focusInEvent(event); + if (d->scene) + QApplication::sendEvent(d->scene, event); + // Pass focus on if the scene cannot accept focus. + if (!d->scene || !event->isAccepted()) + QAbstractScrollArea::focusInEvent(event); +} + +/*! + \reimp +*/ +bool QGraphicsView::focusNextPrevChild(bool next) +{ + return QAbstractScrollArea::focusNextPrevChild(next); +} + +/*! + \reimp +*/ +void QGraphicsView::focusOutEvent(QFocusEvent *event) +{ + Q_D(QGraphicsView); + QAbstractScrollArea::focusOutEvent(event); + if (d->scene) + QApplication::sendEvent(d->scene, event); +} + +/*! + \reimp +*/ +void QGraphicsView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) { + QAbstractScrollArea::keyPressEvent(event); + return; + } + QApplication::sendEvent(d->scene, event); + if (!event->isAccepted()) + QAbstractScrollArea::keyPressEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsView::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + QApplication::sendEvent(d->scene, event); + if (!event->isAccepted()) + QAbstractScrollArea::keyReleaseEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsView::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + + d->storeMouseEvent(event); + d->mousePressViewPoint = event->pos(); + d->mousePressScenePoint = mapToScene(d->mousePressViewPoint); + d->mousePressScreenPoint = event->globalPos(); + d->lastMouseMoveScenePoint = d->mousePressScenePoint; + d->lastMouseMoveScreenPoint = d->mousePressScreenPoint; + d->mousePressButton = event->button(); + + QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseDoubleClick); + mouseEvent.setWidget(viewport()); + mouseEvent.setButtonDownScenePos(d->mousePressButton, d->mousePressScenePoint); + mouseEvent.setButtonDownScreenPos(d->mousePressButton, d->mousePressScreenPoint); + mouseEvent.setScenePos(mapToScene(d->mousePressViewPoint)); + mouseEvent.setScreenPos(d->mousePressScreenPoint); + mouseEvent.setLastScenePos(d->lastMouseMoveScenePoint); + mouseEvent.setLastScreenPos(d->lastMouseMoveScreenPoint); + mouseEvent.setButtons(event->buttons()); + mouseEvent.setButtons(event->buttons()); + mouseEvent.setAccepted(false); + mouseEvent.setButton(event->button()); + mouseEvent.setModifiers(event->modifiers()); + if (event->spontaneous()) + qt_sendSpontaneousEvent(d->scene, &mouseEvent); + else + QApplication::sendEvent(d->scene, &mouseEvent); +} + +/*! + \reimp +*/ +void QGraphicsView::mousePressEvent(QMouseEvent *event) +{ + Q_D(QGraphicsView); + + // Store this event for replaying, finding deltas, and for + // scroll-dragging; even in non-interactive mode, scroll hand dragging is + // allowed, so we store the event at the very top of this function. + d->storeMouseEvent(event); + d->lastMouseEvent.setAccepted(false); + + if (d->sceneInteractionAllowed) { + // Store some of the event's button-down data. + d->mousePressViewPoint = event->pos(); + d->mousePressScenePoint = mapToScene(d->mousePressViewPoint); + d->mousePressScreenPoint = event->globalPos(); + d->lastMouseMoveScenePoint = d->mousePressScenePoint; + d->lastMouseMoveScreenPoint = d->mousePressScreenPoint; + d->mousePressButton = event->button(); + + if (d->scene) { + // Convert and deliver the mouse event to the scene. + QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMousePress); + mouseEvent.setWidget(viewport()); + mouseEvent.setButtonDownScenePos(d->mousePressButton, d->mousePressScenePoint); + mouseEvent.setButtonDownScreenPos(d->mousePressButton, d->mousePressScreenPoint); + mouseEvent.setScenePos(d->mousePressScenePoint); + mouseEvent.setScreenPos(d->mousePressScreenPoint); + mouseEvent.setLastScenePos(d->lastMouseMoveScenePoint); + mouseEvent.setLastScreenPos(d->lastMouseMoveScreenPoint); + mouseEvent.setButtons(event->buttons()); + mouseEvent.setButton(event->button()); + mouseEvent.setModifiers(event->modifiers()); + mouseEvent.setAccepted(false); + if (event->spontaneous()) + qt_sendSpontaneousEvent(d->scene, &mouseEvent); + else + QApplication::sendEvent(d->scene, &mouseEvent); + + // Update the original mouse event accepted state. + bool isAccepted = mouseEvent.isAccepted(); + event->setAccepted(isAccepted); + + // Update the last mouse event accepted state. + d->lastMouseEvent.setAccepted(isAccepted); + + if (isAccepted) + return; + } + } + +#ifndef QT_NO_RUBBERBAND + if (d->dragMode == QGraphicsView::RubberBandDrag && !d->rubberBanding) { + if (d->sceneInteractionAllowed) { + // Rubberbanding is only allowed in interactive mode. + event->accept(); + d->rubberBanding = true; + d->rubberBandRect = QRect(); + if (d->scene) { + // Initiating a rubber band always clears the selection. + d->scene->clearSelection(); + } + } + } else +#endif + if (d->dragMode == QGraphicsView::ScrollHandDrag && event->button() == Qt::LeftButton) { + // Left-button press in scroll hand mode initiates hand scrolling. + event->accept(); + d->handScrolling = true; + d->handScrollMotions = 0; +#ifndef QT_NO_CURSOR + viewport()->setCursor(Qt::ClosedHandCursor); +#endif + } +} + +/*! + \reimp +*/ +void QGraphicsView::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QGraphicsView); + +#ifndef QT_NO_RUBBERBAND + if (d->dragMode == QGraphicsView::RubberBandDrag && d->sceneInteractionAllowed) { + d->storeMouseEvent(event); + if (d->rubberBanding) { + // Check for enough drag distance + if ((d->mousePressViewPoint - event->pos()).manhattanLength() + < QApplication::startDragDistance()) { + return; + } + + // Update old rubberband + if (d->viewportUpdateMode != QGraphicsView::NoViewportUpdate && !d->rubberBandRect.isEmpty()) { + if (d->viewportUpdateMode != FullViewportUpdate) + viewport()->update(d->rubberBandRegion(viewport(), d->rubberBandRect)); + else + d->updateAll(); + } + + // Stop rubber banding if the user has let go of all buttons (even + // if we didn't get the release events). + if (!event->buttons()) { + d->rubberBanding = false; + d->rubberBandRect = QRect(); + return; + } + + // Update rubberband position + const QPoint &mp = d->mousePressViewPoint; + QPoint ep = event->pos(); + d->rubberBandRect = QRect(qMin(mp.x(), ep.x()), qMin(mp.y(), ep.y()), + qAbs(mp.x() - ep.x()) + 1, qAbs(mp.y() - ep.y()) + 1); + + // Update new rubberband + if (d->viewportUpdateMode != QGraphicsView::NoViewportUpdate){ + if (d->viewportUpdateMode != FullViewportUpdate) + viewport()->update(d->rubberBandRegion(viewport(), d->rubberBandRect)); + else + d->updateAll(); + } + // Set the new selection area + QPainterPath selectionArea; + selectionArea.addPolygon(mapToScene(d->rubberBandRect)); + selectionArea.closeSubpath(); + if (d->scene) + d->scene->setSelectionArea(selectionArea, d->rubberBandSelectionMode, + viewportTransform()); + return; + } + } else +#endif // QT_NO_RUBBERBAND + if (d->dragMode == QGraphicsView::ScrollHandDrag) { + if (d->handScrolling) { + QScrollBar *hBar = horizontalScrollBar(); + QScrollBar *vBar = verticalScrollBar(); + QPoint delta = event->pos() - d->lastMouseEvent.pos(); + hBar->setValue(hBar->value() + (isRightToLeft() ? delta.x() : -delta.x())); + vBar->setValue(vBar->value() - delta.y()); + + // Detect how much we've scrolled to disambiguate scrolling from + // clicking. + ++d->handScrollMotions; + } + } + + d->mouseMoveEventHandler(event); +} + +/*! + \reimp +*/ +void QGraphicsView::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QGraphicsView); + +#ifndef QT_NO_RUBBERBAND + if (d->dragMode == QGraphicsView::RubberBandDrag && d->sceneInteractionAllowed && !event->buttons()) { + if (d->rubberBanding) { + if (d->viewportUpdateMode != QGraphicsView::NoViewportUpdate){ + if (d->viewportUpdateMode != FullViewportUpdate) + viewport()->update(d->rubberBandRegion(viewport(), d->rubberBandRect)); + else + d->updateAll(); + } + d->rubberBanding = false; + d->rubberBandRect = QRect(); + } + } else +#endif + if (d->dragMode == QGraphicsView::ScrollHandDrag && event->button() == Qt::LeftButton) { +#ifndef QT_NO_CURSOR + // Restore the open hand cursor. ### There might be items + // under the mouse that have a valid cursor at this time, so + // we could repeat the steps from mouseMoveEvent(). + viewport()->setCursor(Qt::OpenHandCursor); +#endif + d->handScrolling = false; + + if (d->scene && d->sceneInteractionAllowed && !d->lastMouseEvent.isAccepted() && d->handScrollMotions <= 6) { + // If we've detected very little motion during the hand drag, and + // no item accepted the last event, we'll interpret that as a + // click to the scene, and reset the selection. + d->scene->clearSelection(); + } + } + + d->storeMouseEvent(event); + + if (!d->sceneInteractionAllowed) + return; + + if (!d->scene) + return; + + QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseRelease); + mouseEvent.setWidget(viewport()); + mouseEvent.setButtonDownScenePos(d->mousePressButton, d->mousePressScenePoint); + mouseEvent.setButtonDownScreenPos(d->mousePressButton, d->mousePressScreenPoint); + mouseEvent.setScenePos(mapToScene(event->pos())); + mouseEvent.setScreenPos(event->globalPos()); + mouseEvent.setLastScenePos(d->lastMouseMoveScenePoint); + mouseEvent.setLastScreenPos(d->lastMouseMoveScreenPoint); + mouseEvent.setButtons(event->buttons()); + mouseEvent.setButton(event->button()); + mouseEvent.setModifiers(event->modifiers()); + mouseEvent.setAccepted(false); + if (event->spontaneous()) + qt_sendSpontaneousEvent(d->scene, &mouseEvent); + else + QApplication::sendEvent(d->scene, &mouseEvent); + + // Update the last mouse event selected state. + d->lastMouseEvent.setAccepted(mouseEvent.isAccepted()); + +#ifndef QT_NO_CURSOR + if (mouseEvent.isAccepted() && mouseEvent.buttons() == 0 && viewport()->testAttribute(Qt::WA_SetCursor)) { + // The last mouse release on the viewport will trigger clearing the cursor. + d->_q_unsetViewportCursor(); + } +#endif +} + +#ifndef QT_NO_WHEELEVENT +/*! + \reimp +*/ +void QGraphicsView::wheelEvent(QWheelEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) { + QAbstractScrollArea::wheelEvent(event); + return; + } + + event->ignore(); + + QGraphicsSceneWheelEvent wheelEvent(QEvent::GraphicsSceneWheel); + wheelEvent.setWidget(viewport()); + wheelEvent.setScenePos(mapToScene(event->pos())); + wheelEvent.setScreenPos(event->globalPos()); + wheelEvent.setButtons(event->buttons()); + wheelEvent.setModifiers(event->modifiers()); + wheelEvent.setDelta(event->delta()); + wheelEvent.setOrientation(event->orientation()); + wheelEvent.setAccepted(false); + QApplication::sendEvent(d->scene, &wheelEvent); + event->setAccepted(wheelEvent.isAccepted()); + if (!event->isAccepted()) + QAbstractScrollArea::wheelEvent(event); +} +#endif // QT_NO_WHEELEVENT + +/*! + \reimp +*/ +void QGraphicsView::paintEvent(QPaintEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene) { + QAbstractScrollArea::paintEvent(event); + return; + } + + // Set up painter state protection. + d->scene->d_func()->painterStateProtection = !(d->optimizationFlags & DontSavePainterState); + + // Determine the exposed region + d->exposedRegion = event->region(); + QRectF exposedSceneRect = mapToScene(d->exposedRegion.boundingRect()).boundingRect(); + + // Set up the painter + QPainter painter(viewport()); +#ifndef QT_NO_RUBBERBAND + if (d->rubberBanding && !d->rubberBandRect.isEmpty()) + painter.save(); +#endif + // Set up render hints + painter.setRenderHints(painter.renderHints(), false); + painter.setRenderHints(d->renderHints, true); + + // Set up viewport transform + const bool viewTransformed = isTransformed(); + if (viewTransformed) + painter.setWorldTransform(viewportTransform()); + const QTransform viewTransform = painter.worldTransform(); + + // Draw background + if ((d->cacheMode & CacheBackground) +#ifdef Q_WS_X11 + && X11->use_xrender +#endif + ) { + // Recreate the background pixmap, and flag the whole background as + // exposed. + if (d->mustResizeBackgroundPixmap) { + d->backgroundPixmap = QPixmap(viewport()->size()); + QBrush bgBrush = viewport()->palette().brush(viewport()->backgroundRole()); + if (!bgBrush.isOpaque()) + d->backgroundPixmap.fill(Qt::transparent); + QPainter p(&d->backgroundPixmap); + p.fillRect(0, 0, d->backgroundPixmap.width(), d->backgroundPixmap.height(), bgBrush); + d->backgroundPixmapExposed = QRegion(viewport()->rect()); + d->mustResizeBackgroundPixmap = false; + } + + // Redraw exposed areas + if (!d->backgroundPixmapExposed.isEmpty()) { + QPainter backgroundPainter(&d->backgroundPixmap); + backgroundPainter.setClipRegion(d->backgroundPixmapExposed, Qt::ReplaceClip); + if (viewTransformed) + backgroundPainter.setTransform(viewTransform); + QRectF backgroundExposedSceneRect = mapToScene(d->backgroundPixmapExposed.boundingRect()).boundingRect(); + drawBackground(&backgroundPainter, backgroundExposedSceneRect); + d->backgroundPixmapExposed = QRegion(); + } + + // Blit the background from the background pixmap + if (viewTransformed) { + painter.setWorldTransform(QTransform()); + painter.drawPixmap(QPoint(), d->backgroundPixmap); + painter.setWorldTransform(viewTransform); + } else { + painter.drawPixmap(QPoint(), d->backgroundPixmap); + } + } else { + if (!(d->optimizationFlags & DontSavePainterState)) + painter.save(); + drawBackground(&painter, exposedSceneRect); + if (!(d->optimizationFlags & DontSavePainterState)) + painter.restore(); + } + + // Items + if (!(d->optimizationFlags & IndirectPainting)) { + const quint32 oldRectAdjust = d->scene->d_func()->rectAdjust; + if (d->optimizationFlags & QGraphicsView::DontAdjustForAntialiasing) + d->scene->d_func()->rectAdjust = 1; + else + d->scene->d_func()->rectAdjust = 2; + d->scene->d_func()->drawItems(&painter, viewTransformed ? &viewTransform : 0, + &d->exposedRegion, viewport()); + d->scene->d_func()->rectAdjust = oldRectAdjust; + // Make sure the painter's world transform is restored correctly when + // drawing without painter state protection (DontSavePainterState). + // We only change the worldTransform() so there's no need to do a full-blown + // save() and restore(). Also note that we don't have to do this in case of + // IndirectPainting (the else branch), because in that case we always save() + // and restore() in QGraphicsScene::drawItems(). + if (!d->scene->d_func()->painterStateProtection) + painter.setOpacity(1.0); + painter.setWorldTransform(viewTransform); + } else { + // Make sure we don't have unpolished items before we draw + if (!d->scene->d_func()->unpolishedItems.isEmpty()) + d->scene->d_func()->_q_polishItems(); + // We reset updateAll here (after we've issued polish events) + // so that we can discard update requests coming from polishEvent(). + d->scene->d_func()->updateAll = false; + + // Find all exposed items + bool allItems = false; + QList itemList = d->findItems(d->exposedRegion, &allItems, viewTransform); + if (!itemList.isEmpty()) { + // Generate the style options. + const int numItems = itemList.size(); + QGraphicsItem **itemArray = &itemList[0]; // Relies on QList internals, but is perfectly valid. + QStyleOptionGraphicsItem *styleOptionArray = d->allocStyleOptionsArray(numItems); + QTransform transform(Qt::Uninitialized); + for (int i = 0; i < numItems; ++i) { + QGraphicsItem *item = itemArray[i]; + QGraphicsItemPrivate *itemd = item->d_ptr.data(); + itemd->initStyleOption(&styleOptionArray[i], viewTransform, d->exposedRegion, allItems); + // Cache the item's area in view coordinates. + // Note that we have to do this here in case the base class implementation + // (QGraphicsScene::drawItems) is not called. If it is, we'll do this + // operation twice, but that's the price one has to pay for using indirect + // painting :-/. + const QRectF brect = adjustedItemEffectiveBoundingRect(item); + if (!itemd->itemIsUntransformable()) { + transform = item->sceneTransform(); + if (viewTransformed) + transform *= viewTransform; + } else { + transform = item->deviceTransform(viewTransform); + } + itemd->paintedViewBoundingRects.insert(d->viewport, transform.mapRect(brect).toRect()); + } + // Draw the items. + drawItems(&painter, numItems, itemArray, styleOptionArray); + d->freeStyleOptionsArray(styleOptionArray); + } + } + + // Foreground + drawForeground(&painter, exposedSceneRect); + +#ifndef QT_NO_RUBBERBAND + // Rubberband + if (d->rubberBanding && !d->rubberBandRect.isEmpty()) { + painter.restore(); + QStyleOptionRubberBand option; + option.initFrom(viewport()); + option.rect = d->rubberBandRect; + option.shape = QRubberBand::Rectangle; + + QStyleHintReturnMask mask; + if (viewport()->style()->styleHint(QStyle::SH_RubberBand_Mask, &option, viewport(), &mask)) { + // painter clipping for masked rubberbands + painter.setClipRegion(mask.region, Qt::IntersectClip); + } + + viewport()->style()->drawControl(QStyle::CE_RubberBand, &option, &painter, viewport()); + } +#endif + + painter.end(); + + // Restore painter state protection. + d->scene->d_func()->painterStateProtection = true; +} + +/*! + \reimp +*/ +void QGraphicsView::resizeEvent(QResizeEvent *event) +{ + Q_D(QGraphicsView); + // Save the last center point - the resize may scroll the view, which + // changes the center point. + QPointF oldLastCenterPoint = d->lastCenterPoint; + + QAbstractScrollArea::resizeEvent(event); + d->recalculateContentSize(); + + // Restore the center point again. + if (d->resizeAnchor == NoAnchor && !d->keepLastCenterPoint) { + d->updateLastCenterPoint(); + } else { + d->lastCenterPoint = oldLastCenterPoint; + } + d->centerView(d->resizeAnchor); + d->keepLastCenterPoint = false; + + if (d->cacheMode & CacheBackground) { + // Invalidate the background pixmap + d->mustResizeBackgroundPixmap = true; + } +} + +/*! + \reimp +*/ +void QGraphicsView::scrollContentsBy(int dx, int dy) +{ + Q_D(QGraphicsView); + d->dirtyScroll = true; + if (d->transforming) + return; + if (isRightToLeft()) + dx = -dx; + + if (d->viewportUpdateMode != QGraphicsView::NoViewportUpdate) { + if (d->viewportUpdateMode != QGraphicsView::FullViewportUpdate) { + if (d->accelerateScrolling) { +#ifndef QT_NO_RUBBERBAND + // Update new and old rubberband regions + if (!d->rubberBandRect.isEmpty()) { + QRegion rubberBandRegion(d->rubberBandRegion(viewport(), d->rubberBandRect)); + rubberBandRegion += rubberBandRegion.translated(-dx, -dy); + viewport()->update(rubberBandRegion); + } +#endif + d->dirtyScrollOffset.rx() += dx; + d->dirtyScrollOffset.ry() += dy; + d->dirtyRegion.translate(dx, dy); + viewport()->scroll(dx, dy); + } else { + d->updateAll(); + } + } else { + d->updateAll(); + } + } + + d->updateLastCenterPoint(); + + if ((d->cacheMode & CacheBackground) +#ifdef Q_WS_X11 + && X11->use_xrender +#endif + ) { + // Scroll the background pixmap + QRegion exposed; + if (!d->backgroundPixmap.isNull()) + d->backgroundPixmap.scroll(dx, dy, d->backgroundPixmap.rect(), &exposed); + + // Invalidate the background pixmap + d->backgroundPixmapExposed.translate(dx, dy); + d->backgroundPixmapExposed += exposed; + } + + // Always replay on scroll. + if (d->sceneInteractionAllowed) + d->replayLastMouseEvent(); +} + +/*! + \reimp +*/ +void QGraphicsView::showEvent(QShowEvent *event) +{ + Q_D(QGraphicsView); + d->recalculateContentSize(); + d->centerView(d->transformationAnchor); + QAbstractScrollArea::showEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsView::inputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QGraphicsView); + if (d->scene) + QApplication::sendEvent(d->scene, event); +} + +/*! + Draws the background of the scene using \a painter, before any items and + the foreground are drawn. Reimplement this function to provide a custom + background for this view. + + If all you want is to define a color, texture or gradient for the + background, you can call setBackgroundBrush() instead. + + All painting is done in \e scene coordinates. \a rect is the exposed + rectangle. + + The default implementation fills \a rect using the view's backgroundBrush. + If no such brush is defined (the default), the scene's drawBackground() + function is called instead. + + \sa drawForeground(), QGraphicsScene::drawBackground() +*/ +void QGraphicsView::drawBackground(QPainter *painter, const QRectF &rect) +{ + Q_D(QGraphicsView); + if (d->scene && d->backgroundBrush.style() == Qt::NoBrush) { + d->scene->drawBackground(painter, rect); + return; + } + + painter->fillRect(rect, d->backgroundBrush); +} + +/*! + Draws the foreground of the scene using \a painter, after the background + and all items are drawn. Reimplement this function to provide a custom + foreground for this view. + + If all you want is to define a color, texture or gradient for the + foreground, you can call setForegroundBrush() instead. + + All painting is done in \e scene coordinates. \a rect is the exposed + rectangle. + + The default implementation fills \a rect using the view's foregroundBrush. + If no such brush is defined (the default), the scene's drawForeground() + function is called instead. + + \sa drawBackground(), QGraphicsScene::drawForeground() +*/ +void QGraphicsView::drawForeground(QPainter *painter, const QRectF &rect) +{ + Q_D(QGraphicsView); + if (d->scene && d->foregroundBrush.style() == Qt::NoBrush) { + d->scene->drawForeground(painter, rect); + return; + } + + painter->fillRect(rect, d->foregroundBrush); +} + +/*! + \obsolete + + Draws the items \a items in the scene using \a painter, after the + background and before the foreground are drawn. \a numItems is the number + of items in \a items and options in \a options. \a options is a list of + styleoptions; one for each item. Reimplement this function to provide + custom item drawing for this view. + + The default implementation calls the scene's drawItems() function. + + Since Qt 4.6, this function is not called anymore unless + the QGraphicsView::IndirectPainting flag is given as an Optimization + flag. + + \sa drawForeground(), drawBackground(), QGraphicsScene::drawItems() +*/ +void QGraphicsView::drawItems(QPainter *painter, int numItems, + QGraphicsItem *items[], + const QStyleOptionGraphicsItem options[]) +{ + Q_D(QGraphicsView); + if (d->scene) { + QWidget *widget = painter->device() == viewport() ? viewport() : 0; + d->scene->drawItems(painter, numItems, items, options, widget); + } +} + +/*! + Returns the current transformation matrix for the view. If no current + transformation is set, the identity matrix is returned. + + \sa setTransform(), rotate(), scale(), shear(), translate() +*/ +QTransform QGraphicsView::transform() const +{ + Q_D(const QGraphicsView); + return d->matrix; +} + +/*! + Returns a matrix that maps viewport coordinates to scene coordinates. + + \sa mapToScene(), mapFromScene() +*/ +QTransform QGraphicsView::viewportTransform() const +{ + Q_D(const QGraphicsView); + QTransform moveMatrix = QTransform::fromTranslate(-d->horizontalScroll(), -d->verticalScroll()); + return d->identityMatrix ? moveMatrix : d->matrix * moveMatrix; +} + +/*! + \since 4.6 + + Returns true if the view is transformed (i.e., a non-identity transform + has been assigned, or the scrollbars are adjusted). + + \sa setTransform(), horizontalScrollBar(), verticalScrollBar() +*/ +bool QGraphicsView::isTransformed() const +{ + Q_D(const QGraphicsView); + return !d->identityMatrix || d->horizontalScroll() || d->verticalScroll(); +} + +/*! + Sets the view's current transformation matrix to \a matrix. + + If \a combine is true, then \a matrix is combined with the current matrix; + otherwise, \a matrix \e replaces the current matrix. \a combine is false + by default. + + The transformation matrix tranforms the scene into view coordinates. Using + the default transformation, provided by the identity matrix, one pixel in + the view represents one unit in the scene (e.g., a 10x10 rectangular item + is drawn using 10x10 pixels in the view). If a 2x2 scaling matrix is + applied, the scene will be drawn in 1:2 (e.g., a 10x10 rectangular item is + then drawn using 20x20 pixels in the view). + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 7 + + To simplify interation with items using a transformed view, QGraphicsView + provides mapTo... and mapFrom... functions that can translate between + scene and view coordinates. For example, you can call mapToScene() to map + a view coordiate to a floating point scene coordinate, or mapFromScene() + to map from floating point scene coordinates to view coordinates. + + \sa transform(), rotate(), scale(), shear(), translate() +*/ +void QGraphicsView::setTransform(const QTransform &matrix, bool combine ) +{ + Q_D(QGraphicsView); + QTransform oldMatrix = d->matrix; + if (!combine) + d->matrix = matrix; + else + d->matrix = matrix * d->matrix; + if (oldMatrix == d->matrix) + return; + + d->identityMatrix = d->matrix.isIdentity(); + d->transforming = true; + if (d->scene) { + d->recalculateContentSize(); + d->centerView(d->transformationAnchor); + } else { + d->updateLastCenterPoint(); + } + + if (d->sceneInteractionAllowed) + d->replayLastMouseEvent(); + d->transforming = false; + + // Any matrix operation requires a full update. + d->updateAll(); +} + +/*! + Resets the view transformation to the identity matrix. + + \sa transform(), setTransform() +*/ +void QGraphicsView::resetTransform() +{ + setTransform(QTransform()); +} + +QPointF QGraphicsViewPrivate::mapToScene(const QPointF &point) const +{ + QPointF p = point; + p.rx() += horizontalScroll(); + p.ry() += verticalScroll(); + return identityMatrix ? p : matrix.inverted().map(p); +} + +QRectF QGraphicsViewPrivate::mapToScene(const QRectF &rect) const +{ + QPointF scrollOffset(horizontalScroll(), verticalScroll()); + QPointF tl = scrollOffset + rect.topLeft(); + QPointF tr = scrollOffset + rect.topRight(); + QPointF br = scrollOffset + rect.bottomRight(); + QPointF bl = scrollOffset + rect.bottomLeft(); + + QPolygonF poly(4); + if (!identityMatrix) { + QTransform x = matrix.inverted(); + poly[0] = x.map(tl); + poly[1] = x.map(tr); + poly[2] = x.map(br); + poly[3] = x.map(bl); + } else { + poly[0] = tl; + poly[1] = tr; + poly[2] = br; + poly[3] = bl; + } + return poly.boundingRect(); +} + +QT_END_NAMESPACE + +#include "moc_qgraphicsview.cpp" + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicsview.h b/src/widgets/graphicsview/qgraphicsview.h new file mode 100644 index 0000000000..b2a7bdb226 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsview.h @@ -0,0 +1,316 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSVIEW_H +#define QGRAPHICSVIEW_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsItem; +class QPainterPath; +class QPolygonF; +class QStyleOptionGraphicsItem; + +class QGraphicsViewPrivate; +class Q_GUI_EXPORT QGraphicsView : public QAbstractScrollArea +{ + Q_OBJECT + Q_FLAGS(QPainter::RenderHints CacheMode OptimizationFlags) + Q_ENUMS(ViewportAnchor DragMode ViewportUpdateMode) + Q_PROPERTY(QBrush backgroundBrush READ backgroundBrush WRITE setBackgroundBrush) + Q_PROPERTY(QBrush foregroundBrush READ foregroundBrush WRITE setForegroundBrush) + Q_PROPERTY(bool interactive READ isInteractive WRITE setInteractive) + Q_PROPERTY(QRectF sceneRect READ sceneRect WRITE setSceneRect) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment) + Q_PROPERTY(QPainter::RenderHints renderHints READ renderHints WRITE setRenderHints) + Q_PROPERTY(DragMode dragMode READ dragMode WRITE setDragMode) + Q_PROPERTY(CacheMode cacheMode READ cacheMode WRITE setCacheMode) + Q_PROPERTY(ViewportAnchor transformationAnchor READ transformationAnchor WRITE setTransformationAnchor) + Q_PROPERTY(ViewportAnchor resizeAnchor READ resizeAnchor WRITE setResizeAnchor) + Q_PROPERTY(ViewportUpdateMode viewportUpdateMode READ viewportUpdateMode WRITE setViewportUpdateMode) +#ifndef QT_NO_RUBBERBAND + Q_PROPERTY(Qt::ItemSelectionMode rubberBandSelectionMode READ rubberBandSelectionMode WRITE setRubberBandSelectionMode) +#endif + Q_PROPERTY(OptimizationFlags optimizationFlags READ optimizationFlags WRITE setOptimizationFlags) + +public: + enum ViewportAnchor { + NoAnchor, + AnchorViewCenter, + AnchorUnderMouse + }; + + enum CacheModeFlag { + CacheNone = 0x0, + CacheBackground = 0x1 + }; + Q_DECLARE_FLAGS(CacheMode, CacheModeFlag) + + enum DragMode { + NoDrag, + ScrollHandDrag, + RubberBandDrag + }; + + enum ViewportUpdateMode { + FullViewportUpdate, + MinimalViewportUpdate, + SmartViewportUpdate, + NoViewportUpdate, + BoundingRectViewportUpdate + }; + + enum OptimizationFlag { + DontClipPainter = 0x1, // obsolete + DontSavePainterState = 0x2, + DontAdjustForAntialiasing = 0x4, + IndirectPainting = 0x8 + }; + Q_DECLARE_FLAGS(OptimizationFlags, OptimizationFlag) + + QGraphicsView(QWidget *parent = 0); + QGraphicsView(QGraphicsScene *scene, QWidget *parent = 0); + ~QGraphicsView(); + + QSize sizeHint() const; + + QPainter::RenderHints renderHints() const; + void setRenderHint(QPainter::RenderHint hint, bool enabled = true); + void setRenderHints(QPainter::RenderHints hints); + + Qt::Alignment alignment() const; + void setAlignment(Qt::Alignment alignment); + + ViewportAnchor transformationAnchor() const; + void setTransformationAnchor(ViewportAnchor anchor); + + ViewportAnchor resizeAnchor() const; + void setResizeAnchor(ViewportAnchor anchor); + + ViewportUpdateMode viewportUpdateMode() const; + void setViewportUpdateMode(ViewportUpdateMode mode); + + OptimizationFlags optimizationFlags() const; + void setOptimizationFlag(OptimizationFlag flag, bool enabled = true); + void setOptimizationFlags(OptimizationFlags flags); + + DragMode dragMode() const; + void setDragMode(DragMode mode); + +#ifndef QT_NO_RUBBERBAND + Qt::ItemSelectionMode rubberBandSelectionMode() const; + void setRubberBandSelectionMode(Qt::ItemSelectionMode mode); +#endif + + CacheMode cacheMode() const; + void setCacheMode(CacheMode mode); + void resetCachedContent(); + + bool isInteractive() const; + void setInteractive(bool allowed); + + QGraphicsScene *scene() const; + void setScene(QGraphicsScene *scene); + + QRectF sceneRect() const; + void setSceneRect(const QRectF &rect); + inline void setSceneRect(qreal x, qreal y, qreal w, qreal h); + + QMatrix matrix() const; + void setMatrix(const QMatrix &matrix, bool combine = false); + void resetMatrix(); + QTransform transform() const; + QTransform viewportTransform() const; + bool isTransformed() const; + void setTransform(const QTransform &matrix, bool combine = false); + void resetTransform(); + void rotate(qreal angle); + void scale(qreal sx, qreal sy); + void shear(qreal sh, qreal sv); + void translate(qreal dx, qreal dy); + + void centerOn(const QPointF &pos); + inline void centerOn(qreal x, qreal y); + void centerOn(const QGraphicsItem *item); + void ensureVisible(const QRectF &rect, int xmargin = 50, int ymargin = 50); + inline void ensureVisible(qreal x, qreal y, qreal w, qreal h, int xmargin = 50, int ymargin = 50); + void ensureVisible(const QGraphicsItem *item, int xmargin = 50, int ymargin = 50); + void fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRadioMode = Qt::IgnoreAspectRatio); + inline void fitInView(qreal x, qreal y, qreal w, qreal h, + Qt::AspectRatioMode aspectRadioMode = Qt::IgnoreAspectRatio); + void fitInView(const QGraphicsItem *item, + Qt::AspectRatioMode aspectRadioMode = Qt::IgnoreAspectRatio); + + void render(QPainter *painter, const QRectF &target = QRectF(), const QRect &source = QRect(), + Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio); + + QList items() const; + QList items(const QPoint &pos) const; + inline QList items(int x, int y) const; + QList items(const QRect &rect, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + inline QList items(int x, int y, int w, int h, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + QList items(const QPolygon &polygon, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + QList items(const QPainterPath &path, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + QGraphicsItem *itemAt(const QPoint &pos) const; + inline QGraphicsItem *itemAt(int x, int y) const; + + QPointF mapToScene(const QPoint &point) const; + QPolygonF mapToScene(const QRect &rect) const; + QPolygonF mapToScene(const QPolygon &polygon) const; + QPainterPath mapToScene(const QPainterPath &path) const; + QPoint mapFromScene(const QPointF &point) const; + QPolygon mapFromScene(const QRectF &rect) const; + QPolygon mapFromScene(const QPolygonF &polygon) const; + QPainterPath mapFromScene(const QPainterPath &path) const; + inline QPointF mapToScene(int x, int y) const; + inline QPolygonF mapToScene(int x, int y, int w, int h) const; + inline QPoint mapFromScene(qreal x, qreal y) const; + inline QPolygon mapFromScene(qreal x, qreal y, qreal w, qreal h) const; + + QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + + QBrush backgroundBrush() const; + void setBackgroundBrush(const QBrush &brush); + + QBrush foregroundBrush() const; + void setForegroundBrush(const QBrush &brush); + +public Q_SLOTS: + void updateScene(const QList &rects); + void invalidateScene(const QRectF &rect = QRectF(), QGraphicsScene::SceneLayers layers = QGraphicsScene::AllLayers); + void updateSceneRect(const QRectF &rect); + +protected Q_SLOTS: + void setupViewport(QWidget *widget); + +protected: + QGraphicsView(QGraphicsViewPrivate &, QWidget *parent = 0); + bool event(QEvent *event); + bool viewportEvent(QEvent *event); + +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *event); +#endif + void dragEnterEvent(QDragEnterEvent *event); + void dragLeaveEvent(QDragLeaveEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dropEvent(QDropEvent *event); + void focusInEvent(QFocusEvent *event); + bool focusNextPrevChild(bool next); + void focusOutEvent(QFocusEvent *event); + void keyPressEvent(QKeyEvent *event); + void keyReleaseEvent(QKeyEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); +#ifndef QT_NO_WHEELEVENT + void wheelEvent(QWheelEvent *event); +#endif + void paintEvent(QPaintEvent *event); + void resizeEvent(QResizeEvent *event); + void scrollContentsBy(int dx, int dy); + void showEvent(QShowEvent *event); + void inputMethodEvent(QInputMethodEvent *event); + + virtual void drawBackground(QPainter *painter, const QRectF &rect); + virtual void drawForeground(QPainter *painter, const QRectF &rect); + virtual void drawItems(QPainter *painter, int numItems, + QGraphicsItem *items[], + const QStyleOptionGraphicsItem options[]); + +private: + Q_DECLARE_PRIVATE(QGraphicsView) + Q_DISABLE_COPY(QGraphicsView) +#ifndef QT_NO_CURSOR + Q_PRIVATE_SLOT(d_func(), void _q_setViewportCursor(const QCursor &)) + Q_PRIVATE_SLOT(d_func(), void _q_unsetViewportCursor()) +#endif + friend class QGraphicsSceneWidget; + friend class QGraphicsScene; + friend class QGraphicsScenePrivate; + friend class QGraphicsItemPrivate; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGraphicsView::CacheMode) +Q_DECLARE_OPERATORS_FOR_FLAGS(QGraphicsView::OptimizationFlags) + +inline void QGraphicsView::setSceneRect(qreal ax, qreal ay, qreal aw, qreal ah) +{ setSceneRect(QRectF(ax, ay, aw, ah)); } +inline void QGraphicsView::centerOn(qreal ax, qreal ay) +{ centerOn(QPointF(ax, ay)); } +inline void QGraphicsView::ensureVisible(qreal ax, qreal ay, qreal aw, qreal ah, int xmargin, int ymargin) +{ ensureVisible(QRectF(ax, ay, aw, ah), xmargin, ymargin); } +inline void QGraphicsView::fitInView(qreal ax, qreal ay, qreal w, qreal h, Qt::AspectRatioMode mode) +{ fitInView(QRectF(ax, ay, w, h), mode); } +inline QList QGraphicsView::items(int ax, int ay) const +{ return items(QPoint(ax, ay)); } +inline QList QGraphicsView::items(int ax, int ay, int w, int h, Qt::ItemSelectionMode mode) const +{ return items(QRect(ax, ay, w, h), mode); } +inline QGraphicsItem *QGraphicsView::itemAt(int ax, int ay) const +{ return itemAt(QPoint(ax, ay)); } +inline QPointF QGraphicsView::mapToScene(int ax, int ay) const +{ return mapToScene(QPoint(ax, ay)); } +inline QPolygonF QGraphicsView::mapToScene(int ax, int ay, int w, int h) const +{ return mapToScene(QRect(ax, ay, w, h)); } +inline QPoint QGraphicsView::mapFromScene(qreal ax, qreal ay) const +{ return mapFromScene(QPointF(ax, ay)); } +inline QPolygon QGraphicsView::mapFromScene(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapFromScene(QRectF(ax, ay, w, h)); } + +#endif // QT_NO_GRAPHICSVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGRAPHICSVIEW_H diff --git a/src/widgets/graphicsview/qgraphicsview_p.h b/src/widgets/graphicsview/qgraphicsview_p.h new file mode 100644 index 0000000000..5572b7e770 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicsview_p.h @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSVIEW_P_H +#define QGRAPHICSVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicsview.h" + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +#include +#include +#include "qgraphicssceneevent.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QGraphicsViewPrivate : public QAbstractScrollAreaPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsView) +public: + QGraphicsViewPrivate(); + + void recalculateContentSize(); + void centerView(QGraphicsView::ViewportAnchor anchor); + + QPainter::RenderHints renderHints; + + QGraphicsView::DragMode dragMode; + + quint32 sceneInteractionAllowed : 1; + quint32 hasSceneRect : 1; + quint32 connectedToScene : 1; + quint32 useLastMouseEvent : 1; + quint32 identityMatrix : 1; + quint32 dirtyScroll : 1; + quint32 accelerateScrolling : 1; + quint32 keepLastCenterPoint : 1; + quint32 transforming : 1; + quint32 handScrolling : 1; + quint32 mustAllocateStyleOptions : 1; + quint32 mustResizeBackgroundPixmap : 1; + quint32 fullUpdatePending : 1; + quint32 hasUpdateClip : 1; + quint32 padding : 18; + + QRectF sceneRect; + void updateLastCenterPoint(); + + qint64 horizontalScroll() const; + qint64 verticalScroll() const; + + QRectF mapRectToScene(const QRect &rect) const; + QRectF mapRectFromScene(const QRectF &rect) const; + + QRect updateClip; + QPointF mousePressItemPoint; + QPointF mousePressScenePoint; + QPoint mousePressViewPoint; + QPoint mousePressScreenPoint; + QPointF lastMouseMoveScenePoint; + QPoint lastMouseMoveScreenPoint; + QPoint dirtyScrollOffset; + Qt::MouseButton mousePressButton; + QTransform matrix; + qint64 scrollX, scrollY; + void updateScroll(); + + qreal leftIndent; + qreal topIndent; + + // Replaying mouse events + QMouseEvent lastMouseEvent; + void replayLastMouseEvent(); + void storeMouseEvent(QMouseEvent *event); + void mouseMoveEventHandler(QMouseEvent *event); + + QPointF lastCenterPoint; + Qt::Alignment alignment; + + QGraphicsView::ViewportAnchor transformationAnchor; + QGraphicsView::ViewportAnchor resizeAnchor; + QGraphicsView::ViewportUpdateMode viewportUpdateMode; + QGraphicsView::OptimizationFlags optimizationFlags; + + QPointer scene; +#ifndef QT_NO_RUBBERBAND + QRect rubberBandRect; + QRegion rubberBandRegion(const QWidget *widget, const QRect &rect) const; + bool rubberBanding; + Qt::ItemSelectionMode rubberBandSelectionMode; +#endif + int handScrollMotions; + + QGraphicsView::CacheMode cacheMode; + + QVector styleOptions; + QStyleOptionGraphicsItem *allocStyleOptionsArray(int numItems); + void freeStyleOptionsArray(QStyleOptionGraphicsItem *array); + + QBrush backgroundBrush; + QBrush foregroundBrush; + QPixmap backgroundPixmap; + QRegion backgroundPixmapExposed; + +#ifndef QT_NO_CURSOR + QCursor originalCursor; + bool hasStoredOriginalCursor; + void _q_setViewportCursor(const QCursor &cursor); + void _q_unsetViewportCursor(); +#endif + + QGraphicsSceneDragDropEvent *lastDragDropEvent; + void storeDragDropEvent(const QGraphicsSceneDragDropEvent *event); + void populateSceneDragDropEvent(QGraphicsSceneDragDropEvent *dest, + QDropEvent *source); + + QRect mapToViewRect(const QGraphicsItem *item, const QRectF &rect) const; + QRegion mapToViewRegion(const QGraphicsItem *item, const QRectF &rect) const; + QRegion dirtyRegion; + QRect dirtyBoundingRect; + void processPendingUpdates(); + inline void updateAll() + { + viewport->update(); + fullUpdatePending = true; + dirtyBoundingRect = QRect(); + dirtyRegion = QRegion(); + } + + inline void dispatchPendingUpdateRequests() + { +#ifdef Q_WS_MAC + // QWidget::update() works slightly different on the Mac without the raster engine; + // it's not part of our backing store so it needs special threatment. + if (QApplicationPrivate::graphics_system_name != QLatin1String("raster")) { + // At this point either HIViewSetNeedsDisplay (Carbon) or setNeedsDisplay: YES (Cocoa) + // is called, which means there's a pending update request. We want to dispatch it + // now because otherwise graphics view updates would require two + // round-trips in the event loop before the item is painted. + extern void qt_mac_dispatchPendingUpdateRequests(QWidget *); + qt_mac_dispatchPendingUpdateRequests(viewport->window()); + } else +#endif // !Q_WS_MAC + { + if (qt_widget_private(viewport)->paintOnScreen()) + QCoreApplication::sendPostedEvents(viewport, QEvent::UpdateRequest); + else + QCoreApplication::sendPostedEvents(viewport->window(), QEvent::UpdateRequest); + } + } + + void setUpdateClip(QGraphicsItem *); + + inline bool updateRectF(const QRectF &rect) + { + if (rect.isEmpty()) + return false; + if (optimizationFlags & QGraphicsView::DontAdjustForAntialiasing) + return updateRect(rect.toAlignedRect().adjusted(-1, -1, 1, 1)); + return updateRect(rect.toAlignedRect().adjusted(-2, -2, 2, 2)); + } + + bool updateRect(const QRect &rect); + bool updateRegion(const QRectF &rect, const QTransform &xform); + bool updateSceneSlotReimplementedChecked; + QRegion exposedRegion; + + QList findItems(const QRegion &exposedRegion, bool *allItems, + const QTransform &viewTransform) const; + + QPointF mapToScene(const QPointF &point) const; + QRectF mapToScene(const QRectF &rect) const; + static void translateTouchEvent(QGraphicsViewPrivate *d, QTouchEvent *touchEvent); + void updateInputMethodSensitivity(); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW + +#endif diff --git a/src/widgets/graphicsview/qgraphicswidget.cpp b/src/widgets/graphicsview/qgraphicswidget.cpp new file mode 100644 index 0000000000..675a5c5c77 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicswidget.cpp @@ -0,0 +1,2388 @@ +/**************************************************************************** +** +** 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 QtGui module 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 "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicswidget.h" +#include "qgraphicswidget_p.h" +#include "qgraphicslayout.h" +#include "qgraphicslayout_p.h" +#include "qgraphicsscene.h" +#include "qgraphicssceneevent.h" + +#ifndef QT_NO_ACTION +#include +#endif +#include +#include +#ifndef QT_NO_SHORTCUT +#include +#endif +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QGraphicsWidget + \brief The QGraphicsWidget class is the base class for all widget + items in a QGraphicsScene. + \since 4.4 + \ingroup graphicsview-api + + QGraphicsWidget is an extended base item that provides extra functionality + over QGraphicsItem. It is similar to QWidget in many ways: + + \list + \o Provides a \l palette, a \l font and a \l style(). + \o Has a defined geometry(). + \o Supports layouts with setLayout() and layout(). + \o Supports shortcuts and actions with grabShortcut() and insertAction() + \endlist + + Unlike QGraphicsItem, QGraphicsWidget is not an abstract class; you can + create instances of a QGraphicsWidget without having to subclass it. + This approach is useful for widgets that only serve the purpose of + organizing child widgets into a layout. + + QGraphicsWidget can be used as a base item for your own custom item if + you require advanced input focus handling, e.g., tab focus and activation, or + layouts. + + Since QGraphicsWidget resembles QWidget and has similar API, it is + easier to port a widget from QWidget to QGraphicsWidget, instead of + QGraphicsItem. + + \note QWidget-based widgets can be directly embedded into a + QGraphicsScene using QGraphicsProxyWidget. + + Noticeable differences between QGraphicsWidget and QWidget are: + + \table + \header \o QGraphicsWidget + \o QWidget + \row \o Coordinates and geometry are defined with qreals (doubles or + floats, depending on the platform). + \o QWidget uses integer geometry (QPoint, QRect). + \row \o The widget is already visible by default; you do not have to + call show() to display the widget. + \o QWidget is hidden by default until you call show(). + \row \o A subset of widget attributes are supported. + \o All widget attributes are supported. + \row \o A top-level item's style defaults to QGraphicsScene::style + \o A top-level widget's style defaults to QApplication::style + \row \o Graphics View provides a custom drag and drop framework, different + from QWidget. + \o Standard drag and drop framework. + \row \o Widget items do not support modality. + \o Full modality support. + \endtable + + QGraphicsWidget supports a subset of Qt's widget attributes, + (Qt::WidgetAttribute), as shown in the table below. Any attributes not + listed in this table are unsupported, or otherwise unused. + + \table + \header \o Widget Attribute \o Usage + \row \o Qt::WA_SetLayoutDirection + \o Set by setLayoutDirection(), cleared by + unsetLayoutDirection(). You can test this attribute to + check if the widget has been explicitly assigned a + \l{QGraphicsWidget::layoutDirection()} + {layoutDirection}. If the attribute is not set, the + \l{QGraphicsWidget::layoutDirection()} + {layoutDirection()} is inherited. + \row \o Qt::WA_RightToLeft + \o Toggled by setLayoutDirection(). Inherited from the + parent/scene. If set, the widget's layout will order + horizontally arranged widgets from right to left. + \row \o Qt::WA_SetStyle + \o Set and cleared by setStyle(). If this attribute is + set, the widget has been explicitly assigned a style. + If it is unset, the widget will use the scene's or the + application's style. + \row \o Qt::WA_Resized + \o Set by setGeometry() and resize(). + \row \o Qt::WA_SetPalette + \o Set by setPalette(). + \row \o Qt::WA_SetFont + \o Set by setPalette(). + \row \o Qt::WA_WindowPropagation + \o Enables propagation to window widgets. + \endtable + + Although QGraphicsWidget inherits from both QObject and QGraphicsItem, + you should use the functions provided by QGraphicsItem, \e not QObject, to + manage the relationships between parent and child items. These functions + control the stacking order of items as well as their ownership. + + \note The QObject::parent() should always return 0 for QGraphicsWidgets, + but this policy is not strictly defined. + + \sa QGraphicsProxyWidget, QGraphicsItem, {Widgets and Layouts} +*/ + +/*! + Constructs a QGraphicsWidget instance. The optional \a parent argument is + passed to QGraphicsItem's constructor. The optional \a wFlags argument + specifies the widget's window flags (e.g., whether the widget should be a + window, a tool, a popup, etc). +*/ +QGraphicsWidget::QGraphicsWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) + : QGraphicsObject(*new QGraphicsWidgetPrivate, 0, 0), QGraphicsLayoutItem(0, false) +{ + Q_D(QGraphicsWidget); + d->init(parent, wFlags); +} + +/*! + \internal + + Constructs a new QGraphicsWidget, using \a dd as parent. +*/ +QGraphicsWidget::QGraphicsWidget(QGraphicsWidgetPrivate &dd, QGraphicsItem *parent, QGraphicsScene *scene, Qt::WindowFlags wFlags) + : QGraphicsObject(dd, 0, scene), QGraphicsLayoutItem(0, false) +{ + Q_D(QGraphicsWidget); + d->init(parent, wFlags); +} + +/* + \internal + \class QGraphicsWidgetStyles + + We use this thread-safe class to maintain a hash of styles for widgets + styles. Note that QApplication::style() itself isn't thread-safe, QStyle + isn't thread-safe, and we don't have a thread-safe factory for creating + the default style, nor cloning a style. +*/ +class QGraphicsWidgetStyles +{ +public: + QStyle *styleForWidget(const QGraphicsWidget *widget) const + { + QMutexLocker locker(&mutex); + return styles.value(widget, 0); + } + + void setStyleForWidget(QGraphicsWidget *widget, QStyle *style) + { + QMutexLocker locker(&mutex); + if (style) + styles[widget] = style; + else + styles.remove(widget); + } + +private: + QMap styles; + mutable QMutex mutex; +}; +Q_GLOBAL_STATIC(QGraphicsWidgetStyles, widgetStyles) + +/*! + Destroys the QGraphicsWidget instance. +*/ +QGraphicsWidget::~QGraphicsWidget() +{ + Q_D(QGraphicsWidget); +#ifndef QT_NO_ACTION + // Remove all actions from this widget + for (int i = 0; i < d->actions.size(); ++i) { + QActionPrivate *apriv = d->actions.at(i)->d_func(); + apriv->graphicsWidgets.removeAll(this); + } + d->actions.clear(); +#endif + + if (QGraphicsScene *scn = scene()) { + QGraphicsScenePrivate *sceneD = scn->d_func(); + if (sceneD->tabFocusFirst == this) + sceneD->tabFocusFirst = (d->focusNext == this ? 0 : d->focusNext); + } + d->focusPrev->d_func()->focusNext = d->focusNext; + d->focusNext->d_func()->focusPrev = d->focusPrev; + + // Play it really safe + d->focusNext = this; + d->focusPrev = this; + + clearFocus(); + + //we check if we have a layout previously + if (d->layout) { + QGraphicsLayout *temp = d->layout; + foreach (QGraphicsItem * item, childItems()) { + // In case of a custom layout which doesn't remove and delete items, we ensure that + // the parent layout item does not point to the deleted layout. This code is here to + // avoid regression from 4.4 to 4.5, because according to 4.5 docs it is not really needed. + if (item->isWidget()) { + QGraphicsWidget *widget = static_cast(item); + if (widget->parentLayoutItem() == d->layout) + widget->setParentLayoutItem(0); + } + } + d->layout = 0; + delete temp; + } + + // Remove this graphics widget from widgetStyles + widgetStyles()->setStyleForWidget(this, 0); +} + +/*! + \property QGraphicsWidget::size + \brief the size of the widget + + Calling resize() resizes the widget to a \a size bounded by minimumSize() + and maximumSize(). This property only affects the widget's width and + height (e.g., its right and bottom edges); the widget's position and + top-left corner remains unaffected. + + Resizing a widget triggers the widget to immediately receive a + \l{QEvent::GraphicsSceneResize}{GraphicsSceneResize} event with the + widget's old and new size. If the widget has a layout assigned when this + event arrives, the layout will be activated and it will automatically + update any child widgets's geometry. + + This property does not affect any layout of the parent widget. If the + widget itself is managed by a parent layout; e.g., it has a parent widget + with a layout assigned, that layout will not activate. + + By default, this property contains a size with zero width and height. + + \sa setGeometry(), QGraphicsSceneResizeEvent, QGraphicsLayout +*/ +QSizeF QGraphicsWidget::size() const +{ + return QGraphicsLayoutItem::geometry().size(); +} + +void QGraphicsWidget::resize(const QSizeF &size) +{ + setGeometry(QRectF(pos(), size)); +} + +/*! + \fn void QGraphicsWidget::resize(qreal w, qreal h) + + This convenience function is equivalent to calling resize(QSizeF(w, h)). + + \sa setGeometry(), setTransform() +*/ + +/*! + \property QGraphicsWidget::sizePolicy + \brief the size policy for the widget + \sa sizePolicy(), setSizePolicy(), QWidget::sizePolicy() +*/ + +/*! + \fn QGraphicsWidget::geometryChanged() + + This signal gets emitted whenever the geometry is changed in setGeometry(). +*/ + +/*! + \property QGraphicsWidget::geometry + \brief the geometry of the widget + + Sets the item's geometry to \a rect. The item's position and size are + modified as a result of calling this function. The item is first moved, + then resized. + + A side effect of calling this function is that the widget will receive + a move event and a resize event. Also, if the widget has a layout + assigned, the layout will activate. + + \sa geometry(), resize() +*/ +void QGraphicsWidget::setGeometry(const QRectF &rect) +{ + QGraphicsWidgetPrivate *wd = QGraphicsWidget::d_func(); + QGraphicsLayoutItemPrivate *d = QGraphicsLayoutItem::d_ptr.data(); + QRectF newGeom; + QPointF oldPos = d->geom.topLeft(); + if (!wd->inSetPos) { + setAttribute(Qt::WA_Resized); + newGeom = rect; + newGeom.setSize(rect.size().expandedTo(effectiveSizeHint(Qt::MinimumSize)) + .boundedTo(effectiveSizeHint(Qt::MaximumSize))); + if (newGeom == d->geom) + return; + + // setPos triggers ItemPositionChange, which can adjust position + wd->inSetGeometry = 1; + setPos(newGeom.topLeft()); + wd->inSetGeometry = 0; + newGeom.moveTopLeft(pos()); + + if (newGeom == d->geom) + return; + + // Update and prepare to change the geometry (remove from index) if the size has changed. + if (wd->scene) { + if (rect.topLeft() == d->geom.topLeft()) { + prepareGeometryChange(); + } + } + } + + // Update the layout item geometry + bool moved = oldPos != pos(); + if (moved) { + // Send move event. + QGraphicsSceneMoveEvent event; + event.setOldPos(oldPos); + event.setNewPos(pos()); + QApplication::sendEvent(this, &event); + if (wd->inSetPos) { + //set the new pos + d->geom.moveTopLeft(pos()); + emit geometryChanged(); + return; + } + } + QSizeF oldSize = size(); + QGraphicsLayoutItem::setGeometry(newGeom); + // Send resize event + bool resized = newGeom.size() != oldSize; + if (resized) { + QGraphicsSceneResizeEvent re; + re.setOldSize(oldSize); + re.setNewSize(newGeom.size()); + if (oldSize.width() != newGeom.size().width()) + emit widthChanged(); + if (oldSize.height() != newGeom.size().height()) + emit heightChanged(); + QApplication::sendEvent(this, &re); + } + emit geometryChanged(); +} + +/*! + \fn QRectF QGraphicsWidget::rect() const + + Returns the item's local rect as a QRectF. This function is equivalent + to QRectF(QPointF(), size()). + + \sa setGeometry(), resize() +*/ + +/*! + \fn void QGraphicsWidget::setGeometry(qreal x, qreal y, qreal w, qreal h) + + This convenience function is equivalent to calling setGeometry(QRectF( + \a x, \a y, \a w, \a h)). + + \sa geometry(), resize() +*/ + +/*! + \property QGraphicsWidget::minimumSize + \brief the minimum size of the widget + + \sa setMinimumSize(), minimumSize(), preferredSize, maximumSize +*/ + +/*! + \property QGraphicsWidget::preferredSize + \brief the preferred size of the widget + + \sa setPreferredSize(), preferredSize(), minimumSize, maximumSize +*/ + +/*! + \property QGraphicsWidget::maximumSize + \brief the maximum size of the widget + + \sa setMaximumSize(), maximumSize(), minimumSize, preferredSize +*/ + +/*! + Sets the widget's contents margins to \a left, \a top, \a right and \a + bottom. + + Contents margins are used by the assigned layout to define the placement + of subwidgets and layouts. Margins are particularly useful for widgets + that constrain subwidgets to only a section of its own geometry. For + example, a group box with a layout will place subwidgets inside its frame, + but below the title. + + Changing a widget's contents margins will always trigger an update(), and + any assigned layout will be activated automatically. The widget will then + receive a \l{QEvent::ContentsRectChange}{ContentsRectChange} event. + + \sa getContentsMargins(), setGeometry() +*/ +void QGraphicsWidget::setContentsMargins(qreal left, qreal top, qreal right, qreal bottom) +{ + Q_D(QGraphicsWidget); + + if (!d->margins && left == 0 && top == 0 && right == 0 && bottom == 0) + return; + d->ensureMargins(); + if (left == d->margins[d->Left] + && top == d->margins[d->Top] + && right == d->margins[d->Right] + && bottom == d->margins[d->Bottom]) + return; + + d->margins[d->Left] = left; + d->margins[d->Top] = top; + d->margins[d->Right] = right; + d->margins[d->Bottom] = bottom; + + if (QGraphicsLayout *l = d->layout) + l->invalidate(); + else + updateGeometry(); + + QEvent e(QEvent::ContentsRectChange); + QApplication::sendEvent(this, &e); +} + +/*! + Gets the widget's contents margins. The margins are stored in \a left, \a + top, \a right and \a bottom, as pointers to qreals. Each argument can + be \e {omitted} by passing 0. + + \sa setContentsMargins() +*/ +void QGraphicsWidget::getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const +{ + Q_D(const QGraphicsWidget); + if (left || top || right || bottom) + d->ensureMargins(); + if (left) + *left = d->margins[d->Left]; + if (top) + *top = d->margins[d->Top]; + if (right) + *right = d->margins[d->Right]; + if (bottom) + *bottom = d->margins[d->Bottom]; +} + +/*! + Sets the widget's window frame margins to \a left, \a top, \a right and + \a bottom. The default frame margins are provided by the style, and they + depend on the current window flags. + + If you would like to draw your own window decoration, you can set your + own frame margins to override the default margins. + + \sa unsetWindowFrameMargins(), getWindowFrameMargins(), windowFrameRect() +*/ +void QGraphicsWidget::setWindowFrameMargins(qreal left, qreal top, qreal right, qreal bottom) +{ + Q_D(QGraphicsWidget); + + if (!d->windowFrameMargins && left == 0 && top == 0 && right == 0 && bottom == 0) + return; + d->ensureWindowFrameMargins(); + bool unchanged = + d->windowFrameMargins[d->Left] == left + && d->windowFrameMargins[d->Top] == top + && d->windowFrameMargins[d->Right] == right + && d->windowFrameMargins[d->Bottom] == bottom; + if (d->setWindowFrameMargins && unchanged) + return; + if (!unchanged) + prepareGeometryChange(); + d->windowFrameMargins[d->Left] = left; + d->windowFrameMargins[d->Top] = top; + d->windowFrameMargins[d->Right] = right; + d->windowFrameMargins[d->Bottom] = bottom; + d->setWindowFrameMargins = true; +} + +/*! + Gets the widget's window frame margins. The margins are stored in \a left, + \a top, \a right and \a bottom as pointers to qreals. Each argument can + be \e {omitted} by passing 0. + + \sa setWindowFrameMargins(), windowFrameRect() +*/ +void QGraphicsWidget::getWindowFrameMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const +{ + Q_D(const QGraphicsWidget); + if (left || top || right || bottom) + d->ensureWindowFrameMargins(); + if (left) + *left = d->windowFrameMargins[d->Left]; + if (top) + *top = d->windowFrameMargins[d->Top]; + if (right) + *right = d->windowFrameMargins[d->Right]; + if (bottom) + *bottom = d->windowFrameMargins[d->Bottom]; +} + +/*! + Resets the window frame margins to the default value, provided by the style. + + \sa setWindowFrameMargins(), getWindowFrameMargins(), windowFrameRect() +*/ +void QGraphicsWidget::unsetWindowFrameMargins() +{ + Q_D(QGraphicsWidget); + if ((d->windowFlags & Qt::Window) && (d->windowFlags & Qt::WindowType_Mask) != Qt::Popup && + (d->windowFlags & Qt::WindowType_Mask) != Qt::ToolTip && !(d->windowFlags & Qt::FramelessWindowHint)) { + QStyleOptionTitleBar bar; + d->initStyleOptionTitleBar(&bar); + QStyle *style = this->style(); + qreal margin = style->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth); + qreal titleBarHeight = d->titleBarHeight(bar); + setWindowFrameMargins(margin, titleBarHeight, margin, margin); + } else { + setWindowFrameMargins(0, 0, 0, 0); + } + d->setWindowFrameMargins = false; +} + +/*! + Returns the widget's geometry in parent coordinates including any window + frame. + + \sa windowFrameRect(), getWindowFrameMargins(), setWindowFrameMargins() +*/ +QRectF QGraphicsWidget::windowFrameGeometry() const +{ + Q_D(const QGraphicsWidget); + return d->windowFrameMargins + ? geometry().adjusted(-d->windowFrameMargins[d->Left], -d->windowFrameMargins[d->Top], + d->windowFrameMargins[d->Right], d->windowFrameMargins[d->Bottom]) + : geometry(); +} + +/*! + Returns the widget's local rect including any window frame. + + \sa windowFrameGeometry(), getWindowFrameMargins(), setWindowFrameMargins() +*/ +QRectF QGraphicsWidget::windowFrameRect() const +{ + Q_D(const QGraphicsWidget); + return d->windowFrameMargins + ? rect().adjusted(-d->windowFrameMargins[d->Left], -d->windowFrameMargins[d->Top], + d->windowFrameMargins[d->Right], d->windowFrameMargins[d->Bottom]) + : rect(); +} + +/*! + Populates a style option object for this widget based on its current + state, and stores the output in \a option. The default implementation + populates \a option with the following properties. + + \table + \header + \o Style Option Property + \o Value + \row + \o state & QStyle::State_Enabled + \o Corresponds to QGraphicsItem::isEnabled(). + \row + \o state & QStyle::State_HasFocus + \o Corresponds to QGraphicsItem::hasFocus(). + \row + \o state & QStyle::State_MouseOver + \o Corresponds to QGraphicsItem::isUnderMouse(). + \row + \o direction + \o Corresponds to QGraphicsWidget::layoutDirection(). + \row + \o rect + \o Corresponds to QGraphicsWidget::rect().toRect(). + \row + \o palette + \o Corresponds to QGraphicsWidget::palette(). + \row + \o fontMetrics + \o Corresponds to QFontMetrics(QGraphicsWidget::font()). + \endtable + + Subclasses of QGraphicsWidget should call the base implementation, and + then test the type of \a option using qstyleoption_cast<>() or test + QStyleOption::Type before storing widget-specific options. + + For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicswidget.cpp 0 + + \sa QStyleOption::initFrom() +*/ +void QGraphicsWidget::initStyleOption(QStyleOption *option) const +{ + Q_ASSERT(option); + + option->state = QStyle::State_None; + if (isEnabled()) + option->state |= QStyle::State_Enabled; + if (hasFocus()) + option->state |= QStyle::State_HasFocus; + // if (window->testAttribute(Qt::WA_KeyboardFocusChange)) // ### Window + // option->state |= QStyle::State_KeyboardFocusChange; + if (isUnderMouse()) + option->state |= QStyle::State_MouseOver; + if (QGraphicsWidget *w = window()) { + if (w->isActiveWindow()) + option->state |= QStyle::State_Active; + } + if (isWindow()) + option->state |= QStyle::State_Window; + /* + ### +#ifdef Q_WS_MAC + extern bool qt_mac_can_clickThrough(const QGraphicsWidget *w); //qwidget_mac.cpp + if (!(option->state & QStyle::State_Active) && !qt_mac_can_clickThrough(widget)) + option->state &= ~QStyle::State_Enabled; + + switch (QMacStyle::widgetSizePolicy(widget)) { + case QMacStyle::SizeSmall: + option->state |= QStyle::State_Small; + break; + case QMacStyle::SizeMini: + option->state |= QStyle::State_Mini; + break; + default: + ; + } +#endif +#ifdef QT_KEYPAD_NAVIGATION + if (widget->hasEditFocus()) + state |= QStyle::State_HasEditFocus; +#endif + */ + option->direction = layoutDirection(); + option->rect = rect().toRect(); // ### truncation! + option->palette = palette(); + if (!isEnabled()) { + option->palette.setCurrentColorGroup(QPalette::Disabled); + } else if (isActiveWindow()) { + option->palette.setCurrentColorGroup(QPalette::Active); + } else { + option->palette.setCurrentColorGroup(QPalette::Inactive); + } + option->fontMetrics = QFontMetrics(font()); +} + +/*! + \reimp +*/ +QSizeF QGraphicsWidget::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_D(const QGraphicsWidget); + QSizeF sh; + if (d->layout) { + QSizeF marginSize(0,0); + if (d->margins) { + marginSize = QSizeF(d->margins[d->Left] + d->margins[d->Right], + d->margins[d->Top] + d->margins[d->Bottom]); + } + sh = d->layout->effectiveSizeHint(which, constraint - marginSize); + sh += marginSize; + } else { + switch (which) { + case Qt::MinimumSize: + sh = QSizeF(0, 0); + break; + case Qt::PreferredSize: + sh = QSizeF(50, 50); //rather arbitrary + break; + case Qt::MaximumSize: + sh = QSizeF(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + break; + default: + qWarning("QGraphicsWidget::sizeHint(): Don't know how to handle the value of 'which'"); + break; + } + } + return sh; +} + +/*! + \property QGraphicsWidget::layout + \brief The layout of the widget + + Any existing layout manager is deleted before the new layout is assigned. If + \a layout is 0, the widget is left without a layout. Existing subwidgets' + geometries will remain unaffected. + + QGraphicsWidget takes ownership of \a layout. + + All widgets that are currently managed by \a layout or all of its + sublayouts, are automatically reparented to this item. The layout is then + invalidated, and the child widget geometries are adjusted according to + this item's geometry() and contentsMargins(). Children who are not + explicitly managed by \a layout remain unaffected by the layout after + it has been assigned to this widget. + + If no layout is currently managing this widget, layout() will return 0. + +*/ + +/*! + \fn void QGraphicsWidget::layoutChanged() + This signal gets emitted whenever the layout of the item changes + \internal +*/ + +/*! + Returns this widget's layout, or 0 if no layout is currently managing this + widget. + + \sa setLayout() +*/ +QGraphicsLayout *QGraphicsWidget::layout() const +{ + Q_D(const QGraphicsWidget); + return d->layout; +} + +/*! + \fn void QGraphicsWidget::setLayout(QGraphicsLayout *layout) + + Sets the layout for this widget to \a layout. Any existing layout manager + is deleted before the new layout is assigned. If \a layout is 0, the + widget is left without a layout. Existing subwidgets' geometries will + remain unaffected. + + All widgets that are currently managed by \a layout or all of its + sublayouts, are automatically reparented to this item. The layout is then + invalidated, and the child widget geometries are adjusted according to + this item's geometry() and contentsMargins(). Children who are not + explicitly managed by \a layout remain unaffected by the layout after + it has been assigned to this widget. + + QGraphicsWidget takes ownership of \a layout. + + \sa layout(), QGraphicsLinearLayout::addItem(), QGraphicsLayout::invalidate() +*/ +void QGraphicsWidget::setLayout(QGraphicsLayout *l) +{ + Q_D(QGraphicsWidget); + if (d->layout == l) + return; + d->setLayout_helper(l); + if (!l) + return; + + // Prevent assigning a layout that is already assigned to another widget. + QGraphicsLayoutItem *oldParent = l->parentLayoutItem(); + if (oldParent && oldParent != this) { + qWarning("QGraphicsWidget::setLayout: Attempting to set a layout on %s" + " \"%s\", when the layout already has a parent", + metaObject()->className(), qPrintable(objectName())); + return; + } + + // Install and activate the layout. + l->setParentLayoutItem(this); + l->d_func()->reparentChildItems(this); + l->invalidate(); + emit layoutChanged(); +} + +/*! + Adjusts the size of the widget to its effective preferred size hint. + + This function is called implicitly when the item is shown for the first + time. + + \sa effectiveSizeHint(), Qt::MinimumSize +*/ +void QGraphicsWidget::adjustSize() +{ + QSizeF sz = effectiveSizeHint(Qt::PreferredSize); + // What if sz is not valid?! + if (sz.isValid()) + resize(sz); +} + +/*! + \property QGraphicsWidget::layoutDirection + \brief the layout direction for this widget. + + This property modifies this widget's and all of its descendants' + Qt::WA_RightToLeft attribute. It also sets this widget's + Qt::WA_SetLayoutDirection attribute. + + The widget's layout direction determines the order in which the layout + manager horizontally arranges subwidgets of this widget. The default + value depends on the language and locale of the application, and is + typically in the same direction as words are read and written. With + Qt::LeftToRight, the layout starts placing subwidgets from the left + side of this widget towards the right. Qt::RightToLeft does the opposite - + the layout will place widgets starting from the right edge moving towards + the left. + + Subwidgets inherit their layout direction from the parent. Top-level + widget items inherit their layout direction from + QGraphicsScene::layoutDirection. If you change a widget's layout direction + by calling setLayoutDirection(), the widget will send itself a + \l{QEvent::LayoutDirectionChange}{LayoutDirectionChange} event, and then + propagate the new layout direction to all its descendants. + + \sa QWidget::layoutDirection, QApplication::layoutDirection +*/ +Qt::LayoutDirection QGraphicsWidget::layoutDirection() const +{ + return testAttribute(Qt::WA_RightToLeft) ? Qt::RightToLeft : Qt::LeftToRight; +} +void QGraphicsWidget::setLayoutDirection(Qt::LayoutDirection direction) +{ + Q_D(QGraphicsWidget); + setAttribute(Qt::WA_SetLayoutDirection, true); + d->setLayoutDirection_helper(direction); +} +void QGraphicsWidget::unsetLayoutDirection() +{ + Q_D(QGraphicsWidget); + setAttribute(Qt::WA_SetLayoutDirection, false); + d->resolveLayoutDirection(); +} + +/*! + Returns a pointer to the widget's style. If this widget does not have any + explicitly assigned style, the scene's style is returned instead. In turn, + if the scene does not have any assigned style, this function returns + QApplication::style(). + + \sa setStyle() +*/ +QStyle *QGraphicsWidget::style() const +{ + if (QStyle *style = widgetStyles()->styleForWidget(this)) + return style; + // ### This is not thread-safe. QApplication::style() is not thread-safe. + return scene() ? scene()->style() : QApplication::style(); +} + +/*! + Sets the widget's style to \a style. QGraphicsWidget does \e not take + ownership of \a style. + + If no style is assigned, or \a style is 0, the widget will use + QGraphicsScene::style() (if this has been set). Otherwise the widget will + use QApplication::style(). + + This function sets the Qt::WA_SetStyle attribute if \a style is not 0; + otherwise it clears the attribute. + + \sa style() +*/ +void QGraphicsWidget::setStyle(QStyle *style) +{ + setAttribute(Qt::WA_SetStyle, style != 0); + widgetStyles()->setStyleForWidget(this, style); + + // Deliver StyleChange to the widget itself (doesn't propagate). + QEvent event(QEvent::StyleChange); + QApplication::sendEvent(this, &event); +} + +/*! + \property QGraphicsWidget::font + \brief the widgets' font + + This property provides the widget's font. + + QFont consists of font properties that have been explicitly defined and + properties implicitly inherited from the widget's parent. Hence, font() + can return a different font compared to the one set with setFont(). + This scheme allows you to define single entries in a font without + affecting the font's inherited entries. + + When a widget's font changes, it resolves its entries against its + parent widget. If the widget does not have a parent widget, it resolves + its entries against the scene. The widget then sends itself a + \l{QEvent::FontChange}{FontChange} event and notifies all its + descendants so that they can resolve their fonts as well. + + By default, this property contains the application's default font. + + \sa QApplication::font(), QGraphicsScene::font, QFont::resolve() +*/ +QFont QGraphicsWidget::font() const +{ + Q_D(const QGraphicsWidget); + QFont fnt = d->font; + fnt.resolve(fnt.resolve() | d->inheritedFontResolveMask); + return fnt; +} +void QGraphicsWidget::setFont(const QFont &font) +{ + Q_D(QGraphicsWidget); + setAttribute(Qt::WA_SetFont, font.resolve() != 0); + + QFont naturalFont = d->naturalWidgetFont(); + QFont resolvedFont = font.resolve(naturalFont); + d->setFont_helper(resolvedFont); +} + +/*! + \property QGraphicsWidget::palette + \brief the widget's palette + + This property provides the widget's palette. The palette provides colors + and brushes for color groups (e.g., QPalette::Button) and states (e.g., + QPalette::Inactive), loosely defining the general look of the widget and + its children. + + QPalette consists of color groups that have been explicitly defined, and + groups that are implicitly inherited from the widget's parent. Because of + this, palette() can return a different palette than what has been set with + setPalette(). This scheme allows you to define single entries in a palette + without affecting the palette's inherited entries. + + When a widget's palette changes, it resolves its entries against its + parent widget, or if it doesn't have a parent widget, it resolves against + the scene. It then sends itself a \l{QEvent::PaletteChange}{PaletteChange} + event, and notifies all its descendants so they can resolve their palettes + as well. + + By default, this property contains the application's default palette. + + \sa QApplication::palette(), QGraphicsScene::palette, QPalette::resolve() +*/ +QPalette QGraphicsWidget::palette() const +{ + Q_D(const QGraphicsWidget); + return d->palette; +} +void QGraphicsWidget::setPalette(const QPalette &palette) +{ + Q_D(QGraphicsWidget); + setAttribute(Qt::WA_SetPalette, palette.resolve() != 0); + + QPalette naturalPalette = d->naturalWidgetPalette(); + QPalette resolvedPalette = palette.resolve(naturalPalette); + d->setPalette_helper(resolvedPalette); +} + +/*! + \property QGraphicsWidget::autoFillBackground + \brief whether the widget background is filled automatically + \since 4.7 + + If enabled, this property will cause Qt to fill the background of the + widget before invoking the paint() method. The color used is defined by the + QPalette::Window color role from the widget's \l{QPalette}{palette}. + + In addition, Windows are always filled with QPalette::Window, unless the + WA_OpaquePaintEvent or WA_NoSystemBackground attributes are set. + + By default, this property is false. + + \sa Qt::WA_OpaquePaintEvent, Qt::WA_NoSystemBackground, +*/ +bool QGraphicsWidget::autoFillBackground() const +{ + Q_D(const QGraphicsWidget); + return d->autoFillBackground; +} +void QGraphicsWidget::setAutoFillBackground(bool enabled) +{ + Q_D(QGraphicsWidget); + if (d->autoFillBackground != enabled) { + d->autoFillBackground = enabled; + update(); + } +} + +/*! + If this widget is currently managed by a layout, this function notifies + the layout that the widget's size hints have changed and the layout + may need to resize and reposition the widget accordingly. + + Call this function if the widget's sizeHint() has changed. + + \sa QGraphicsLayout::invalidate() +*/ +void QGraphicsWidget::updateGeometry() +{ + QGraphicsLayoutItem::updateGeometry(); + QGraphicsLayoutItem *parentItem = parentLayoutItem(); + + if (parentItem && parentItem->isLayout()) { + parentItem->updateGeometry(); + } else { + if (parentItem) { + QGraphicsWidget *parentWid = parentWidget(); //### + if (parentWid->isVisible()) + QApplication::postEvent(parentWid, new QEvent(QEvent::LayoutRequest)); + } + bool wasResized = testAttribute(Qt::WA_Resized); + resize(size()); // this will restrict the size + setAttribute(Qt::WA_Resized, wasResized); + } +} + +/*! + \reimp + + QGraphicsWidget uses the base implementation of this function to catch and + deliver events related to state changes in the item. Because of this, it is + very important that subclasses call the base implementation. + + \a change specifies the type of change, and \a value is the new value. + + For example, QGraphicsWidget uses ItemVisibleChange to deliver + \l{QEvent::Show} {Show} and \l{QEvent::Hide}{Hide} events, + ItemPositionHasChanged to deliver \l{QEvent::Move}{Move} events, + and ItemParentChange both to deliver \l{QEvent::ParentChange} + {ParentChange} events, and for managing the focus chain. + + QGraphicsWidget enables the ItemSendsGeometryChanges flag by default in + order to track position changes. + + \sa QGraphicsItem::itemChange() +*/ +QVariant QGraphicsWidget::itemChange(GraphicsItemChange change, const QVariant &value) +{ + Q_D(QGraphicsWidget); + switch (change) { + case ItemEnabledHasChanged: { + // Send EnabledChange after the enabled state has changed. + QEvent event(QEvent::EnabledChange); + QApplication::sendEvent(this, &event); + break; + } + case ItemVisibleChange: + if (value.toBool()) { + // Send Show event before the item has been shown. + QShowEvent event; + QApplication::sendEvent(this, &event); + bool resized = testAttribute(Qt::WA_Resized); + if (!resized) { + adjustSize(); + setAttribute(Qt::WA_Resized, false); + } + } + break; + case ItemVisibleHasChanged: + if (!value.toBool()) { + // Send Hide event after the item has been hidden. + QHideEvent event; + QApplication::sendEvent(this, &event); + } + break; + case ItemPositionHasChanged: + d->setGeometryFromSetPos(); + break; + case ItemParentChange: { + // Deliver ParentAboutToChange. + QEvent event(QEvent::ParentAboutToChange); + QApplication::sendEvent(this, &event); + break; + } + case ItemParentHasChanged: { + // Deliver ParentChange. + QEvent event(QEvent::ParentChange); + QApplication::sendEvent(this, &event); + break; + } + case ItemCursorHasChanged: { + // Deliver CursorChange. + QEvent event(QEvent::CursorChange); + QApplication::sendEvent(this, &event); + break; + } + case ItemToolTipHasChanged: { + // Deliver ToolTipChange. + QEvent event(QEvent::ToolTipChange); + QApplication::sendEvent(this, &event); + break; + } + default: + break; + } + return QGraphicsItem::itemChange(change, value); +} + +/*! + \internal + + This virtual function is used to notify changes to any property (both + dynamic properties, and registered with Q_PROPERTY) in the + widget. Depending on the property itself, the notification can be + delivered before or after the value has changed. + + \a propertyName is the name of the property (e.g., "size" or "font"), and + \a value is the (proposed) new value of the property. The function returns + the new value, which may be different from \a value if the notification + supports adjusting the property value. The base implementation simply + returns \a value for any \a propertyName. + + QGraphicsWidget delivers notifications for the following properties: + + \table \o propertyName \o Property + \row \o layoutDirection \o QGraphicsWidget::layoutDirection + \row \o size \o QGraphicsWidget::size + \row \o font \o QGraphicsWidget::font + \row \o palette \o QGraphicsWidget::palette + \endtable + + \sa itemChange() +*/ +QVariant QGraphicsWidget::propertyChange(const QString &propertyName, const QVariant &value) +{ + Q_UNUSED(propertyName); + return value; +} + +/*! + QGraphicsWidget's implementation of sceneEvent() simply passes \a event to + QGraphicsWidget::event(). You can handle all events for your widget in + event() or in any of the convenience functions; you should not have to + reimplement this function in a subclass of QGraphicsWidget. + + \sa QGraphicsItem::sceneEvent() +*/ +bool QGraphicsWidget::sceneEvent(QEvent *event) +{ + return QGraphicsItem::sceneEvent(event); +} + +/*! + This event handler, for \a event, receives events for the window frame if + this widget is a window. Its base implementation provides support for + default window frame interaction such as moving, resizing, etc. + + You can reimplement this handler in a subclass of QGraphicsWidget to + provide your own custom window frame interaction support. + + Returns true if \a event has been recognized and processed; otherwise, + returns false. + + \sa event() +*/ +bool QGraphicsWidget::windowFrameEvent(QEvent *event) +{ + Q_D(QGraphicsWidget); + switch (event->type()) { + case QEvent::GraphicsSceneMousePress: + d->windowFrameMousePressEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseMove: + d->ensureWindowData(); + if (d->windowData->grabbedSection != Qt::NoSection) { + d->windowFrameMouseMoveEvent(static_cast(event)); + event->accept(); + } + break; + case QEvent::GraphicsSceneMouseRelease: + d->windowFrameMouseReleaseEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverMove: + d->windowFrameHoverMoveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverLeave: + d->windowFrameHoverLeaveEvent(static_cast(event)); + break; + default: + break; + } + return event->isAccepted(); +} + +/*! + \since 4.4 + + Returns the window frame section at position \a pos, or + Qt::NoSection if there is no window frame section at this + position. + + This function is used in QGraphicsWidget's base implementation for window + frame interaction. + + You can reimplement this function if you want to customize how a window + can be interactively moved or resized. For instance, if you only want to + allow a window to be resized by the bottom right corner, you can + reimplement this function to return Qt::NoSection for all sections except + Qt::BottomRightSection. + + \sa windowFrameEvent(), paintWindowFrame(), windowFrameGeometry() +*/ +Qt::WindowFrameSection QGraphicsWidget::windowFrameSectionAt(const QPointF &pos) const +{ + Q_D(const QGraphicsWidget); + + const QRectF r = windowFrameRect(); + if (!r.contains(pos)) + return Qt::NoSection; + + const qreal left = r.left(); + const qreal top = r.top(); + const qreal right = r.right(); + const qreal bottom = r.bottom(); + const qreal x = pos.x(); + const qreal y = pos.y(); + + const qreal cornerMargin = 20; + //### Not sure of this one, it should be the same value for all edges. + const qreal windowFrameWidth = d->windowFrameMargins + ? d->windowFrameMargins[d->Left] : 0; + + Qt::WindowFrameSection s = Qt::NoSection; + if (x <= left + cornerMargin) { + if (y <= top + windowFrameWidth || (x <= left + windowFrameWidth && y <= top + cornerMargin)) { + s = Qt::TopLeftSection; + } else if (y >= bottom - windowFrameWidth || (x <= left + windowFrameWidth && y >= bottom - windowFrameWidth)) { + s = Qt::BottomLeftSection; + } else if (x <= left + windowFrameWidth) { + s = Qt::LeftSection; + } + } else if (x >= right - cornerMargin) { + if (y <= top + windowFrameWidth || (x >= right - windowFrameWidth && y <= top + cornerMargin)) { + s = Qt::TopRightSection; + } else if (y >= bottom - windowFrameWidth || (x >= right - windowFrameWidth && y >= bottom - windowFrameWidth)) { + s = Qt::BottomRightSection; + } else if (x >= right - windowFrameWidth) { + s = Qt::RightSection; + } + } else if (y <= top + windowFrameWidth) { + s = Qt::TopSection; + } else if (y >= bottom - windowFrameWidth) { + s = Qt::BottomSection; + } + if (s == Qt::NoSection) { + QRectF r1 = r; + r1.setHeight(d->windowFrameMargins + ? d->windowFrameMargins[d->Top] : 0); + if (r1.contains(pos)) + s = Qt::TitleBarArea; + } + return s; +} + +/*! + \reimp + + Handles the \a event. QGraphicsWidget handles the following + events: + + \table \o Event \o Usage + \row \o Polish + \o Delivered to the widget some time after it has been + shown. + \row \o GraphicsSceneMove + \o Delivered to the widget after its local position has + changed. + \row \o GraphicsSceneResize + \o Delivered to the widget after its size has changed. + \row \o Show + \o Delivered to the widget before it has been shown. + \row \o Hide + \o Delivered to the widget after it has been hidden. + \row \o PaletteChange + \o Delivered to the widget after its palette has changed. + \row \o FontChange + \o Delivered to the widget after its font has changed. + \row \o EnabledChange + \o Delivered to the widget after its enabled state has + changed. + \row \o StyleChange + \o Delivered to the widget after its style has changed. + \row \o LayoutDirectionChange + \o Delivered to the widget after its layout direction has + changed. + \row \o ContentsRectChange + \o Delivered to the widget after its contents margins/ + contents rect has changed. + \endtable +*/ +bool QGraphicsWidget::event(QEvent *event) +{ + Q_D(QGraphicsWidget); + // Forward the event to the layout first. + if (d->layout) + d->layout->widgetEvent(event); + + // Handle the event itself. + switch (event->type()) { + case QEvent::GraphicsSceneMove: + moveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneResize: + resizeEvent(static_cast(event)); + break; + case QEvent::Show: + showEvent(static_cast(event)); + break; + case QEvent::Hide: + hideEvent(static_cast(event)); + break; + case QEvent::Polish: + polishEvent(); + d->polished = true; + if (!d->font.isCopyOf(QApplication::font())) + d->updateFont(d->font); + break; + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + update(); + break; + // Taken from QWidget::event + case QEvent::ActivationChange: + case QEvent::EnabledChange: + case QEvent::FontChange: + case QEvent::StyleChange: + case QEvent::PaletteChange: + case QEvent::ParentChange: + case QEvent::ContentsRectChange: + case QEvent::LayoutDirectionChange: + changeEvent(event); + break; + case QEvent::Close: + closeEvent((QCloseEvent *)event); + break; + case QEvent::GrabMouse: + grabMouseEvent(event); + break; + case QEvent::UngrabMouse: + ungrabMouseEvent(event); + break; + case QEvent::GrabKeyboard: + grabKeyboardEvent(event); + break; + case QEvent::UngrabKeyboard: + ungrabKeyboardEvent(event); + break; + case QEvent::GraphicsSceneMousePress: + if (d->hasDecoration() && windowFrameEvent(event)) + return true; + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: + d->ensureWindowData(); + if (d->hasDecoration() && d->windowData->grabbedSection != Qt::NoSection) + return windowFrameEvent(event); + break; + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverMove: + case QEvent::GraphicsSceneHoverLeave: + if (d->hasDecoration()) { + windowFrameEvent(event); + // Filter out hover events if they were sent to us only because of the + // decoration (special case in QGraphicsScenePrivate::dispatchHoverEvent). + if (!acceptsHoverEvents()) + return true; + } + break; + default: + break; + } + return QObject::event(event); +} + +/*! + This event handler can be reimplemented to handle state changes. + + The state being changed in this event can be retrieved through \a event. + + Change events include: QEvent::ActivationChange, QEvent::EnabledChange, + QEvent::FontChange, QEvent::StyleChange, QEvent::PaletteChange, + QEvent::ParentChange, QEvent::LayoutDirectionChange, and + QEvent::ContentsRectChange. +*/ +void QGraphicsWidget::changeEvent(QEvent *event) +{ + Q_D(QGraphicsWidget); + switch (event->type()) { + case QEvent::StyleChange: + // ### Don't unset if the margins are explicitly set. + unsetWindowFrameMargins(); + if (d->layout) + d->layout->invalidate(); + case QEvent::FontChange: + update(); + updateGeometry(); + break; + case QEvent::PaletteChange: + update(); + break; + case QEvent::ParentChange: + d->resolveFont(d->inheritedFontResolveMask); + d->resolvePalette(d->inheritedPaletteResolveMask); + break; + default: + break; + } +} + +/*! + This event handler, for \a event, can be reimplemented in a subclass to + receive widget close events. The default implementation accepts the + event. + + \sa close(), QCloseEvent +*/ +void QGraphicsWidget::closeEvent(QCloseEvent *event) +{ + event->accept(); +} + +/*! + \reimp +*/ +void QGraphicsWidget::focusInEvent(QFocusEvent *event) +{ + Q_UNUSED(event); + if (focusPolicy() != Qt::NoFocus) + update(); +} + +/*! + Finds a new widget to give the keyboard focus to, as appropriate for Tab + and Shift+Tab, and returns true if it can find a new widget; returns false + otherwise. If \a next is true, this function searches forward; if \a next + is false, it searches backward. + + Sometimes, you will want to reimplement this function to provide special + focus handling for your widget and its subwidgets. For example, a web + browser might reimplement it to move its current active link forward or + backward, and call the base implementation only when it reaches the last + or first link on the page. + + Child widgets call focusNextPrevChild() on their parent widgets, but only + the window that contains the child widgets decides where to redirect + focus. By reimplementing this function for an object, you gain control of + focus traversal for all child widgets. + + \sa focusPolicy() +*/ +bool QGraphicsWidget::focusNextPrevChild(bool next) +{ + Q_D(QGraphicsWidget); + // Let the parent's focusNextPrevChild implementation decide what to do. + QGraphicsWidget *parent = 0; + if (!isWindow() && (parent = parentWidget())) + return parent->focusNextPrevChild(next); + if (!d->scene) + return false; + if (d->scene->focusNextPrevChild(next)) + return true; + if (isWindow()) { + setFocus(next ? Qt::TabFocusReason : Qt::BacktabFocusReason); + if (hasFocus()) + return true; + } + return false; +} + +/*! + \reimp +*/ +void QGraphicsWidget::focusOutEvent(QFocusEvent *event) +{ + Q_UNUSED(event); + if (focusPolicy() != Qt::NoFocus) + update(); +} + +/*! + This event handler, for \l{QEvent::Hide}{Hide} events, is delivered after + the widget has been hidden, for example, setVisible(false) has been called + for the widget or one of its ancestors when the widget was previously + shown. + + You can reimplement this event handler to detect when your widget is + hidden. Calling QEvent::accept() or QEvent::ignore() on \a event has no + effect. + + \sa showEvent(), QWidget::hideEvent(), ItemVisibleChange +*/ +void QGraphicsWidget::hideEvent(QHideEvent *event) +{ + ///### focusNextPrevChild(true), don't lose focus when the focus widget + // is hidden. + Q_UNUSED(event); +} + +/*! + This event handler, for \l{QEvent::GraphicsSceneMove}{GraphicsSceneMove} + events, is delivered after the widget has moved (e.g., its local position + has changed). + + This event is only delivered when the item is moved locally. Calling + setTransform() or moving any of the item's ancestors does not affect the + item's local position. + + You can reimplement this event handler to detect when your widget has + moved. Calling QEvent::accept() or QEvent::ignore() on \a event has no + effect. + + \sa ItemPositionChange, ItemPositionHasChanged +*/ +void QGraphicsWidget::moveEvent(QGraphicsSceneMoveEvent *event) +{ + // ### Last position is always == current position + Q_UNUSED(event); +} + +/*! + This event is delivered to the item by the scene at some point after it + has been constructed, but before it is shown or otherwise accessed through + the scene. You can use this event handler to do last-minute initializations + of the widget which require the item to be fully constructed. + + The base implementation does nothing. +*/ +void QGraphicsWidget::polishEvent() +{ +} + +/*! + This event handler, for + \l{QEvent::GraphicsSceneResize}{GraphicsSceneResize} events, is + delivered after the widget has been resized (i.e., its local size has + changed). \a event contains both the old and the new size. + + This event is only delivered when the widget is resized locally; calling + setTransform() on the widget or any of its ancestors or view, does not + affect the widget's local size. + + You can reimplement this event handler to detect when your widget has been + resized. Calling QEvent::accept() or QEvent::ignore() on \a event has no + effect. + + \sa geometry(), setGeometry() +*/ +void QGraphicsWidget::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + Q_UNUSED(event); +} + +/*! + This event handler, for \l{QEvent::Show}{Show} events, is delivered before + the widget has been shown, for example, setVisible(true) has been called + for the widget or one of its ancestors when the widget was previously + hidden. + + You can reimplement this event handler to detect when your widget is + shown. Calling QEvent::accept() or QEvent::ignore() on \a event has no + effect. + + \sa hideEvent(), QWidget::showEvent(), ItemVisibleChange +*/ +void QGraphicsWidget::showEvent(QShowEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QGraphicsWidget::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QGraphicsWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); +} + +/*! + This event handler, for \a event, can be reimplemented in a subclass to + receive notifications for Qt::GrabMouse events. + + \sa grabMouse(), grabKeyboard() +*/ +void QGraphicsWidget::grabMouseEvent(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + This event handler, for \a event, can be reimplemented in a subclass to + receive notifications for Qt::UngrabMouse events. + + \sa ungrabMouse(), ungrabKeyboard() +*/ +void QGraphicsWidget::ungrabMouseEvent(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + This event handler, for \a event, can be reimplemented in a subclass to + receive notifications for Qt::GrabKeyboard events. + + \sa grabKeyboard(), grabMouse() +*/ +void QGraphicsWidget::grabKeyboardEvent(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + This event handler, for \a event, can be reimplemented in a subclass to + receive notifications for Qt::UngrabKeyboard events. + + \sa ungrabKeyboard(), ungrabMouse() +*/ +void QGraphicsWidget::ungrabKeyboardEvent(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + Returns the widgets window type. + + \sa windowFlags(), isWindow(), isPanel() +*/ +Qt::WindowType QGraphicsWidget::windowType() const +{ + return Qt::WindowType(int(windowFlags()) & Qt::WindowType_Mask); +} + +/*! + \property QGraphicsWidget::windowFlags + \brief the widget's window flags + + Window flags are a combination of a window type (e.g., Qt::Dialog) and + several flags giving hints on the behavior of the window. The behavior + is platform-dependent. + + By default, this property contains no window flags. + + Windows are panels. If you set the Qt::Window flag, the ItemIsPanel flag + will be set automatically. If you clear the Qt::Window flag, the + ItemIsPanel flag is also cleared. Note that the ItemIsPanel flag can be + set independently of Qt::Window. + + \sa isWindow(), isPanel() +*/ +Qt::WindowFlags QGraphicsWidget::windowFlags() const +{ + Q_D(const QGraphicsWidget); + return d->windowFlags; +} +void QGraphicsWidget::setWindowFlags(Qt::WindowFlags wFlags) +{ + Q_D(QGraphicsWidget); + if (d->windowFlags == wFlags) + return; + bool wasPopup = (d->windowFlags & Qt::WindowType_Mask) == Qt::Popup; + + d->adjustWindowFlags(&wFlags); + d->windowFlags = wFlags; + if (!d->setWindowFrameMargins) + unsetWindowFrameMargins(); + + setFlag(ItemIsPanel, d->windowFlags & Qt::Window); + + bool isPopup = (d->windowFlags & Qt::WindowType_Mask) == Qt::Popup; + if (d->scene && isVisible() && wasPopup != isPopup) { + // Popup state changed; update implicit mouse grab. + if (!isPopup) + d->scene->d_func()->removePopup(this); + else + d->scene->d_func()->addPopup(this); + } + + if (d->scene && d->scene->d_func()->allItemsIgnoreHoverEvents && d->hasDecoration()) { + d->scene->d_func()->allItemsIgnoreHoverEvents = false; + d->scene->d_func()->enableMouseTrackingOnViews(); + } +} + +/*! + Returns true if this widget's window is in the active window, or if the + widget does not have a window but is in an active scene (i.e., a scene + that currently has focus). + + The active window is the window that either contains a child widget that + currently has input focus, or that itself has input focus. + + \sa QGraphicsScene::activeWindow(), QGraphicsScene::setActiveWindow(), isActive() +*/ +bool QGraphicsWidget::isActiveWindow() const +{ + return isActive(); +} + +/*! + \property QGraphicsWidget::windowTitle + \brief This property holds the window title (caption). + + This property is only used for windows. + + By default, if no title has been set, this property contains an + empty string. +*/ +void QGraphicsWidget::setWindowTitle(const QString &title) +{ + Q_D(QGraphicsWidget); + d->ensureWindowData(); + d->windowData->windowTitle = title; +} +QString QGraphicsWidget::windowTitle() const +{ + Q_D(const QGraphicsWidget); + return d->windowData ? d->windowData->windowTitle : QString(); +} + +/*! + \property QGraphicsWidget::focusPolicy + \brief the way the widget accepts keyboard focus + + The focus policy is Qt::TabFocus if the widget accepts keyboard focus by + tabbing, Qt::ClickFocus if the widget accepts focus by clicking, + Qt::StrongFocus if it accepts both, and Qt::NoFocus (the default) if it + does not accept focus at all. + + You must enable keyboard focus for a widget if it processes keyboard + events. This is normally done from the widget's constructor. For instance, + the QLineEdit constructor calls setFocusPolicy(Qt::StrongFocus). + + If you enable a focus policy (i.e., not Qt::NoFocus), QGraphicsWidget will + automatically enable the ItemIsFocusable flag. Setting Qt::NoFocus on a + widget will clear the ItemIsFocusable flag. If the widget currently has + keyboard focus, the widget will automatically lose focus. + + \sa focusInEvent(), focusOutEvent(), keyPressEvent(), keyReleaseEvent(), enabled +*/ +Qt::FocusPolicy QGraphicsWidget::focusPolicy() const +{ + Q_D(const QGraphicsWidget); + return d->focusPolicy; +} +void QGraphicsWidget::setFocusPolicy(Qt::FocusPolicy policy) +{ + Q_D(QGraphicsWidget); + if (d->focusPolicy == policy) + return; + d->focusPolicy = policy; + if (hasFocus() && policy == Qt::NoFocus) + clearFocus(); + setFlag(ItemIsFocusable, policy != Qt::NoFocus); +} + +/*! + If this widget, a child or descendant of this widget currently has input + focus, this function will return a pointer to that widget. If + no descendant widget has input focus, 0 is returned. + + \sa QGraphicsItem::focusItem(), QWidget::focusWidget() +*/ +QGraphicsWidget *QGraphicsWidget::focusWidget() const +{ + Q_D(const QGraphicsWidget); + if (d->subFocusItem && d->subFocusItem->d_ptr->isWidget) + return static_cast(d->subFocusItem); + return 0; +} + +#ifndef QT_NO_SHORTCUT +/*! + \since 4.5 + + Adds a shortcut to Qt's shortcut system that watches for the given key \a + sequence in the given \a context. If the \a context is + Qt::ApplicationShortcut, the shortcut applies to the application as a + whole. Otherwise, it is either local to this widget, Qt::WidgetShortcut, + or to the window itself, Qt::WindowShortcut. For widgets that are not part + of a window (i.e., top-level widgets and their children), + Qt::WindowShortcut shortcuts apply to the scene. + + If the same key \a sequence has been grabbed by several widgets, + when the key \a sequence occurs a QEvent::Shortcut event is sent + to all the widgets to which it applies in a non-deterministic + order, but with the ``ambiguous'' flag set to true. + + \warning You should not normally need to use this function; + instead create \l{QAction}s with the shortcut key sequences you + require (if you also want equivalent menu options and toolbar + buttons), or create \l{QShortcut}s if you just need key sequences. + Both QAction and QShortcut handle all the event filtering for you, + and provide signals which are triggered when the user triggers the + key sequence, so are much easier to use than this low-level + function. + + \sa releaseShortcut() setShortcutEnabled() QWidget::grabShortcut() +*/ +int QGraphicsWidget::grabShortcut(const QKeySequence &sequence, Qt::ShortcutContext context) +{ + Q_ASSERT(qApp); + if (sequence.isEmpty()) + return 0; + // ### setAttribute(Qt::WA_GrabbedShortcut); + return qApp->d_func()->shortcutMap.addShortcut(this, sequence, context); +} + +/*! + \since 4.5 + + Removes the shortcut with the given \a id from Qt's shortcut + system. The widget will no longer receive QEvent::Shortcut events + for the shortcut's key sequence (unless it has other shortcuts + with the same key sequence). + + \warning You should not normally need to use this function since + Qt's shortcut system removes shortcuts automatically when their + parent widget is destroyed. It is best to use QAction or + QShortcut to handle shortcuts, since they are easier to use than + this low-level function. Note also that this is an expensive + operation. + + \sa grabShortcut() setShortcutEnabled() , QWidget::releaseShortcut() +*/ +void QGraphicsWidget::releaseShortcut(int id) +{ + Q_ASSERT(qApp); + if (id) + qApp->d_func()->shortcutMap.removeShortcut(id, this, 0); +} + +/*! + \since 4.5 + + If \a enabled is true, the shortcut with the given \a id is + enabled; otherwise the shortcut is disabled. + + \warning You should not normally need to use this function since + Qt's shortcut system enables/disables shortcuts automatically as + widgets become hidden/visible and gain or lose focus. It is best + to use QAction or QShortcut to handle shortcuts, since they are + easier to use than this low-level function. + + \sa grabShortcut() releaseShortcut(), QWidget::setShortcutEnabled() +*/ +void QGraphicsWidget::setShortcutEnabled(int id, bool enabled) +{ + Q_ASSERT(qApp); + if (id) + qApp->d_func()->shortcutMap.setShortcutEnabled(enabled, id, this, 0); +} + +/*! + \since 4.5 + + If \a enabled is true, auto repeat of the shortcut with the + given \a id is enabled; otherwise it is disabled. + + \sa grabShortcut() releaseShortcut() QWidget::setShortcutAutoRepeat() +*/ +void QGraphicsWidget::setShortcutAutoRepeat(int id, bool enabled) +{ + Q_ASSERT(qApp); + if (id) + qApp->d_func()->shortcutMap.setShortcutAutoRepeat(enabled, id, this, 0); +} +#endif + +#ifndef QT_NO_ACTION +/*! + \since 4.5 + + Appends the action \a action to this widget's list of actions. + + All QGraphicsWidgets have a list of \l{QAction}s, however they can be + represented graphically in many different ways. The default use of the + QAction list (as returned by actions()) is to create a context QMenu. + + A QGraphicsWidget should only have one of each action and adding an action + it already has will not cause the same action to be in the widget twice. + + \sa removeAction(), insertAction(), actions(), QWidget::addAction() +*/ +void QGraphicsWidget::addAction(QAction *action) +{ + insertAction(0, action); +} + +/*! + \since 4.5 + + Appends the actions \a actions to this widget's list of actions. + + \sa removeAction(), QMenu, addAction(), QWidget::addActions() +*/ +void QGraphicsWidget::addActions(QList actions) +{ + for (int i = 0; i < actions.count(); ++i) + insertAction(0, actions.at(i)); +} + +/*! + \since 4.5 + + Inserts the action \a action to this widget's list of actions, + before the action \a before. It appends the action if \a before is 0 or + \a before is not a valid action for this widget. + + A QGraphicsWidget should only have one of each action. + + \sa removeAction(), addAction(), QMenu, actions(), + QWidget::insertActions() +*/ +void QGraphicsWidget::insertAction(QAction *before, QAction *action) +{ + if (!action) { + qWarning("QWidget::insertAction: Attempt to insert null action"); + return; + } + + Q_D(QGraphicsWidget); + int index = d->actions.indexOf(action); + if (index != -1) + d->actions.removeAt(index); + + int pos = d->actions.indexOf(before); + if (pos < 0) { + before = 0; + pos = d->actions.size(); + } + d->actions.insert(pos, action); + + if (index == -1) { + QActionPrivate *apriv = action->d_func(); + apriv->graphicsWidgets.append(this); + } + + QActionEvent e(QEvent::ActionAdded, action, before); + QApplication::sendEvent(this, &e); +} + +/*! + \since 4.5 + + Inserts the actions \a actions to this widget's list of actions, + before the action \a before. It appends the action if \a before is 0 or + \a before is not a valid action for this widget. + + A QGraphicsWidget can have at most one of each action. + + \sa removeAction(), QMenu, insertAction(), QWidget::insertActions() +*/ +void QGraphicsWidget::insertActions(QAction *before, QList actions) +{ + for (int i = 0; i < actions.count(); ++i) + insertAction(before, actions.at(i)); +} + +/*! + \since 4.5 + + Removes the action \a action from this widget's list of actions. + + \sa insertAction(), actions(), insertAction(), QWidget::removeAction() +*/ +void QGraphicsWidget::removeAction(QAction *action) +{ + if (!action) + return; + + Q_D(QGraphicsWidget); + + QActionPrivate *apriv = action->d_func(); + apriv->graphicsWidgets.removeAll(this); + + if (d->actions.removeAll(action)) { + QActionEvent e(QEvent::ActionRemoved, action); + QApplication::sendEvent(this, &e); + } +} + +/*! + \since 4.5 + + Returns the (possibly empty) list of this widget's actions. + + \sa insertAction(), removeAction(), QWidget::actions(), + QAction::associatedWidgets(), QAction::associatedGraphicsWidgets() +*/ +QList QGraphicsWidget::actions() const +{ + Q_D(const QGraphicsWidget); + return d->actions; +} +#endif + +/*! + Moves the \a second widget around the ring of focus widgets so that + keyboard focus moves from the \a first widget to the \a second widget when + the Tab key is pressed. + + Note that since the tab order of the \a second widget is changed, you + should order a chain like this: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicswidget.cpp 1 + + \e not like this: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicswidget.cpp 2 + + If \a first is 0, this indicates that \a second should be the first widget + to receive input focus should the scene gain Tab focus (i.e., the user + hits Tab so that focus passes into the scene). If \a second is 0, this + indicates that \a first should be the first widget to gain focus if the + scene gained BackTab focus. + + By default, tab order is defined implicitly using widget creation order. + + \sa focusPolicy, {Keyboard Focus} +*/ +void QGraphicsWidget::setTabOrder(QGraphicsWidget *first, QGraphicsWidget *second) +{ + if (!first && !second) { + qWarning("QGraphicsWidget::setTabOrder(0, 0) is undefined"); + return; + } + if ((first && second) && first->scene() != second->scene()) { + qWarning("QGraphicsWidget::setTabOrder: scenes %p and %p are different", + first->scene(), second->scene()); + return; + } + QGraphicsScene *scene = first ? first->scene() : second->scene(); + if (!scene && (!first || !second)) { + qWarning("QGraphicsWidget::setTabOrder: assigning tab order from/to the" + " scene requires the item to be in a scene."); + return; + } + + // If either first or second are 0, the scene's tabFocusFirst is updated + // to point to the first item in the scene's focus chain. Then first or + // second are set to point to tabFocusFirst. + QGraphicsScenePrivate *sceneD = scene->d_func(); + if (!first) { + sceneD->tabFocusFirst = second; + return; + } + if (!second) { + sceneD->tabFocusFirst = first->d_func()->focusNext; + return; + } + + // Both first and second are != 0. + QGraphicsWidget *firstFocusNext = first->d_func()->focusNext; + if (firstFocusNext == second) { + // Nothing to do. + return; + } + + // Update the focus chain. + QGraphicsWidget *secondFocusPrev = second->d_func()->focusPrev; + QGraphicsWidget *secondFocusNext = second->d_func()->focusNext; + firstFocusNext->d_func()->focusPrev = second; + first->d_func()->focusNext = second; + second->d_func()->focusNext = firstFocusNext; + second->d_func()->focusPrev = first; + secondFocusPrev->d_func()->focusNext = secondFocusNext; + secondFocusNext->d_func()->focusPrev = secondFocusPrev; + + Q_ASSERT(first->d_func()->focusNext->d_func()->focusPrev == first); + Q_ASSERT(first->d_func()->focusPrev->d_func()->focusNext == first); + + Q_ASSERT(second->d_func()->focusNext->d_func()->focusPrev == second); + Q_ASSERT(second->d_func()->focusPrev->d_func()->focusNext == second); + +} + +/*! + If \a on is true, this function enables \a attribute; otherwise + \a attribute is disabled. + + See the class documentation for QGraphicsWidget for a complete list of + which attributes are supported, and what they are for. + + \sa testAttribute(), QWidget::setAttribute() +*/ +void QGraphicsWidget::setAttribute(Qt::WidgetAttribute attribute, bool on) +{ + Q_D(QGraphicsWidget); + // ### most flags require some immediate action + // ### we might want to qWarn use of unsupported attributes + // ### we might want to not use Qt::WidgetAttribute, but roll our own instead + d->setAttribute(attribute, on); +} + +/*! + Returns true if \a attribute is enabled for this widget; otherwise, + returns false. + + \sa setAttribute() +*/ +bool QGraphicsWidget::testAttribute(Qt::WidgetAttribute attribute) const +{ + Q_D(const QGraphicsWidget); + return d->testAttribute(attribute); +} + +/*! + \reimp +*/ +int QGraphicsWidget::type() const +{ + return Type; +} + +/*! + \reimp +*/ +void QGraphicsWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(painter); + Q_UNUSED(option); + Q_UNUSED(widget); +} + +/*! + This virtual function is called by QGraphicsScene to draw the window frame + for windows using \a painter, \a option, and \a widget, in local + coordinates. The base implementation uses the current style to render the + frame and title bar. + + You can reimplement this function in a subclass of QGraphicsWidget to + provide custom rendering of the widget's window frame. + + \sa QGraphicsItem::paint() +*/ +void QGraphicsWidget::paintWindowFrame(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + const bool fillBackground = !testAttribute(Qt::WA_OpaquePaintEvent) + && !testAttribute(Qt::WA_NoSystemBackground); + QGraphicsProxyWidget *proxy = qobject_cast(this); + const bool embeddedWidgetFillsOwnBackground = proxy && proxy->widget(); + + if (rect().contains(option->exposedRect)) { + if (fillBackground && !embeddedWidgetFillsOwnBackground) + painter->fillRect(option->exposedRect, palette().window()); + return; + } + + Q_D(QGraphicsWidget); + + QRect windowFrameRect = QRect(QPoint(), windowFrameGeometry().size().toSize()); + QStyleOptionTitleBar bar; + bar.QStyleOption::operator=(*option); + d->initStyleOptionTitleBar(&bar); // this clear flags in bar.state + d->ensureWindowData(); + if (d->windowData->buttonMouseOver) + bar.state |= QStyle::State_MouseOver; + else + bar.state &= ~QStyle::State_MouseOver; + if (d->windowData->buttonSunken) + bar.state |= QStyle::State_Sunken; + else + bar.state &= ~QStyle::State_Sunken; + + bar.rect = windowFrameRect; + + // translate painter to make the style happy + const QPointF styleOrigin = this->windowFrameRect().topLeft(); + painter->translate(styleOrigin); + +#ifdef Q_WS_MAC + const QSize pixmapSize = windowFrameRect.size(); + if (pixmapSize.width() <= 0 || pixmapSize.height() <= 0) + return; + QPainter *realPainter = painter; + QPixmap pm(pixmapSize); + painter = new QPainter(&pm); +#endif + + // Fill background + QStyleHintReturnMask mask; + bool setMask = style()->styleHint(QStyle::SH_WindowFrame_Mask, &bar, widget, &mask) && !mask.region.isEmpty(); + bool hasBorder = !style()->styleHint(QStyle::SH_TitleBar_NoBorder, &bar, widget); + int frameWidth = style()->pixelMetric(QStyle::PM_MDIFrameWidth, &bar, widget); + if (setMask) { + painter->save(); + painter->setClipRegion(mask.region, Qt::IntersectClip); + } + if (fillBackground) { + if (embeddedWidgetFillsOwnBackground) { + // Don't fill the background twice. + QPainterPath windowFrameBackground; + windowFrameBackground.addRect(windowFrameRect); + // Adjust with 0.5 to avoid border artifacts between + // widget background and frame background. + windowFrameBackground.addRect(rect().translated(-styleOrigin).adjusted(0.5, 0.5, -0.5, -0.5)); + painter->fillPath(windowFrameBackground, palette().window()); + } else { + painter->fillRect(windowFrameRect, palette().window()); + } + } + painter->setRenderHint(QPainter::NonCosmeticDefaultPen); + + // Draw title + int height = (int)d->titleBarHeight(bar); + bar.rect.setHeight(height); + if (hasBorder) // Frame is painted by PE_FrameWindow + bar.rect.adjust(frameWidth, frameWidth, -frameWidth, 0); + + painter->save(); + painter->setFont(QApplication::font("QWorkspaceTitleBar")); + style()->drawComplexControl(QStyle::CC_TitleBar, &bar, painter, widget); + painter->restore(); + if (setMask) + painter->restore(); + // Draw window frame + QStyleOptionFrame frameOptions; + frameOptions.QStyleOption::operator=(*option); + initStyleOption(&frameOptions); + if (!hasBorder) + painter->setClipRect(windowFrameRect.adjusted(0, +height, 0, 0), Qt::IntersectClip); + if (hasFocus()) { + frameOptions.state |= QStyle::State_HasFocus; + } else { + frameOptions.state &= ~QStyle::State_HasFocus; + } + bool isActive = isActiveWindow(); + if (isActive) { + frameOptions.state |= QStyle::State_Active; + } else { + frameOptions.state &= ~QStyle::State_Active; + } + + frameOptions.palette.setCurrentColorGroup(isActive ? QPalette::Active : QPalette::Normal); + frameOptions.rect = windowFrameRect; + frameOptions.lineWidth = style()->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth, 0, widget); + frameOptions.midLineWidth = 1; + style()->drawPrimitive(QStyle::PE_FrameWindow, &frameOptions, painter, widget); + +#ifdef Q_WS_MAC + realPainter->drawPixmap(QPoint(), pm); + delete painter; +#endif +} + +/*! + \reimp +*/ +QRectF QGraphicsWidget::boundingRect() const +{ + return windowFrameRect(); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsWidget::shape() const +{ + QPainterPath path; + path.addRect(rect()); + return path; +} + +/*! + Call this function to close the widget. + + Returns true if the widget was closed; otherwise returns false. + This slot will first send a QCloseEvent to the widget, which may or may + not accept the event. If the event was ignored, nothing happens. If the + event was accepted, it will hide() the widget. + + If the widget has the Qt::WA_DeleteOnClose attribute set it will be + deleted. +*/ +bool QGraphicsWidget::close() +{ + QCloseEvent closeEvent; + QApplication::sendEvent(this, &closeEvent); + if (!closeEvent.isAccepted()) { + return false; + } + // hide + if (isVisible()) { + hide(); + } + if (testAttribute(Qt::WA_DeleteOnClose)) { + deleteLater(); + } + return true; +} + +#ifdef Q_NO_USING_KEYWORD +/*! + \fn const QObjectList &QGraphicsWidget::children() const + \internal + + This function returns the same value as QObject::children(). It's + provided to differentiate between the obsolete member + QGraphicsItem::children() and QObject::children(). QGraphicsItem now + provides childItems() instead. +*/ +#endif + +#if 0 +void QGraphicsWidget::dumpFocusChain() +{ + qDebug() << "=========== Dumping focus chain =============="; + int i = 0; + QGraphicsWidget *next = this; + QSet visited; + do { + if (!next) { + qWarning("Found a focus chain that is not circular, (next == 0)"); + break; + } + qDebug() << i++ << QString::number(uint(next), 16) << next->className() << next->data(0) << QString::fromAscii("focusItem:%1").arg(next->hasFocus() ? '1' : '0') << QLatin1String("next:") << next->d_func()->focusNext->data(0) << QLatin1String("prev:") << next->d_func()->focusPrev->data(0); + if (visited.contains(next)) { + qWarning("Already visited this node. However, I expected to dump until I found myself."); + break; + } + visited << next; + next = next->d_func()->focusNext; + } while (next != this); +} +#endif + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicswidget.h b/src/widgets/graphicsview/qgraphicswidget.h new file mode 100644 index 0000000000..6cbe579608 --- /dev/null +++ b/src/widgets/graphicsview/qgraphicswidget.h @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSWIDGET_H +#define QGRAPHICSWIDGET_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QFont; +class QFontMetrics; +class QGraphicsLayout; +class QGraphicsSceneMoveEvent; +class QGraphicsWidgetPrivate; +class QGraphicsSceneResizeEvent; +class QStyle; +class QStyleOption; + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsWidgetPrivate; + +class Q_GUI_EXPORT QGraphicsWidget : public QGraphicsObject, public QGraphicsLayoutItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem QGraphicsLayoutItem) + Q_PROPERTY(QPalette palette READ palette WRITE setPalette) + Q_PROPERTY(QFont font READ font WRITE setFont) + Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection RESET unsetLayoutDirection) + Q_PROPERTY(QSizeF size READ size WRITE resize NOTIFY geometryChanged) + Q_PROPERTY(QSizeF minimumSize READ minimumSize WRITE setMinimumSize) + Q_PROPERTY(QSizeF preferredSize READ preferredSize WRITE setPreferredSize) + Q_PROPERTY(QSizeF maximumSize READ maximumSize WRITE setMaximumSize) + Q_PROPERTY(QSizePolicy sizePolicy READ sizePolicy WRITE setSizePolicy) + Q_PROPERTY(Qt::FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy) + Q_PROPERTY(Qt::WindowFlags windowFlags READ windowFlags WRITE setWindowFlags) + Q_PROPERTY(QString windowTitle READ windowTitle WRITE setWindowTitle) + Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry NOTIFY geometryChanged) + Q_PROPERTY(bool autoFillBackground READ autoFillBackground WRITE setAutoFillBackground) + Q_PROPERTY(QGraphicsLayout* layout READ layout WRITE setLayout NOTIFY layoutChanged) +public: + QGraphicsWidget(QGraphicsItem *parent = 0, Qt::WindowFlags wFlags = 0); + ~QGraphicsWidget(); + QGraphicsLayout *layout() const; + void setLayout(QGraphicsLayout *layout); + void adjustSize(); + + Qt::LayoutDirection layoutDirection() const; + void setLayoutDirection(Qt::LayoutDirection direction); + void unsetLayoutDirection(); + + QStyle *style() const; + void setStyle(QStyle *style); + + QFont font() const; + void setFont(const QFont &font); + + QPalette palette() const; + void setPalette(const QPalette &palette); + + bool autoFillBackground() const; + void setAutoFillBackground(bool enabled); + + void resize(const QSizeF &size); + inline void resize(qreal w, qreal h) { resize(QSizeF(w, h)); } + QSizeF size() const; + + void setGeometry(const QRectF &rect); + inline void setGeometry(qreal x, qreal y, qreal w, qreal h); + inline QRectF rect() const { return QRectF(QPointF(), size()); } + + void setContentsMargins(qreal left, qreal top, qreal right, qreal bottom); + void getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const; + + void setWindowFrameMargins(qreal left, qreal top, qreal right, qreal bottom); + void getWindowFrameMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const; + void unsetWindowFrameMargins(); + QRectF windowFrameGeometry() const; + QRectF windowFrameRect() const; + + // Window handling + Qt::WindowFlags windowFlags() const; + Qt::WindowType windowType() const; + void setWindowFlags(Qt::WindowFlags wFlags); + bool isActiveWindow() const; + void setWindowTitle(const QString &title); + QString windowTitle() const; + + // Focus handling + Qt::FocusPolicy focusPolicy() const; + void setFocusPolicy(Qt::FocusPolicy policy); + static void setTabOrder(QGraphicsWidget *first, QGraphicsWidget *second); + QGraphicsWidget *focusWidget() const; + +#ifndef QT_NO_SHORTCUT + int grabShortcut(const QKeySequence &sequence, Qt::ShortcutContext context = Qt::WindowShortcut); + void releaseShortcut(int id); + void setShortcutEnabled(int id, bool enabled = true); + void setShortcutAutoRepeat(int id, bool enabled = true); +#endif + +#ifndef QT_NO_ACTION + //actions + void addAction(QAction *action); + void addActions(QList actions); + void insertAction(QAction *before, QAction *action); + void insertActions(QAction *before, QList actions); + void removeAction(QAction *action); + QList actions() const; +#endif + + void setAttribute(Qt::WidgetAttribute attribute, bool on = true); + bool testAttribute(Qt::WidgetAttribute attribute) const; + + enum { + Type = 11 + }; + int type() const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + virtual void paintWindowFrame(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + QRectF boundingRect() const; + QPainterPath shape() const; + +#if 0 + void dumpFocusChain(); +#endif + + // ### Qt 5: Disambiguate +#ifdef Q_NO_USING_KEYWORD + const QObjectList &children() const { return QObject::children(); } +#else + using QObject::children; +#endif + +Q_SIGNALS: + void geometryChanged(); + void layoutChanged(); + +public Q_SLOTS: + bool close(); + +protected: + virtual void initStyleOption(QStyleOption *option) const; + + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + void updateGeometry(); + + // Notification + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + virtual QVariant propertyChange(const QString &propertyName, const QVariant &value); + + // Scene events + bool sceneEvent(QEvent *event); + virtual bool windowFrameEvent(QEvent *e); + virtual Qt::WindowFrameSection windowFrameSectionAt(const QPointF& pos) const; + + // Base event handlers + bool event(QEvent *event); + //virtual void actionEvent(QActionEvent *event); + virtual void changeEvent(QEvent *event); + virtual void closeEvent(QCloseEvent *event); + //void create(WId window = 0, bool initializeWindow = true, bool destroyOldWindow = true); + //void destroy(bool destroyWindow = true, bool destroySubWindows = true); + void focusInEvent(QFocusEvent *event); + virtual bool focusNextPrevChild(bool next); + void focusOutEvent(QFocusEvent *event); + virtual void hideEvent(QHideEvent *event); + //virtual bool macEvent(EventHandlerCallRef caller, EventRef event); + //virtual int metric(PaintDeviceMetric m ) const; + virtual void moveEvent(QGraphicsSceneMoveEvent *event); + virtual void polishEvent(); + //virtual bool qwsEvent(QWSEvent *event); + //void resetInputContext (); + virtual void resizeEvent(QGraphicsSceneResizeEvent *event); + virtual void showEvent(QShowEvent *event); + //virtual void tabletEvent(QTabletEvent *event); + //virtual bool winEvent(MSG *message, long *result); + //virtual bool x11Event(XEvent *event); + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + virtual void grabMouseEvent(QEvent *event); + virtual void ungrabMouseEvent(QEvent *event); + virtual void grabKeyboardEvent(QEvent *event); + virtual void ungrabKeyboardEvent(QEvent *event); + QGraphicsWidget(QGraphicsWidgetPrivate &, QGraphicsItem *parent, QGraphicsScene *, Qt::WindowFlags wFlags = 0); + +private: + Q_DISABLE_COPY(QGraphicsWidget) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QGraphicsWidget) + friend class QGraphicsScene; + friend class QGraphicsScenePrivate; + friend class QGraphicsView; + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; + friend class QGraphicsLayout; + friend class QWidget; + friend class QApplication; +}; + +inline void QGraphicsWidget::setGeometry(qreal ax, qreal ay, qreal aw, qreal ah) +{ setGeometry(QRectF(ax, ay, aw, ah)); } + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif + diff --git a/src/widgets/graphicsview/qgraphicswidget_p.cpp b/src/widgets/graphicsview/qgraphicswidget_p.cpp new file mode 100644 index 0000000000..45800551cb --- /dev/null +++ b/src/widgets/graphicsview/qgraphicswidget_p.cpp @@ -0,0 +1,910 @@ +/**************************************************************************** +** +** 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 QtGui module 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 "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include +#include +#include "qgraphicswidget_p.h" +#include "qgraphicslayoutitem_p.h" +#include "qgraphicslayout.h" +#include "qgraphicsscene_p.h" +#include +#include +#include +#include +#include +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) +# include +#endif + +QT_BEGIN_NAMESPACE + +void QGraphicsWidgetPrivate::init(QGraphicsItem *parentItem, Qt::WindowFlags wFlags) +{ + Q_Q(QGraphicsWidget); + + attributes = 0; + isWidget = 1; // QGraphicsItem::isWidget() returns true. + focusNext = focusPrev = q; + focusPolicy = Qt::NoFocus; + + adjustWindowFlags(&wFlags); + windowFlags = wFlags; + + if (parentItem) + setParentItemHelper(parentItem, 0, 0); + + q->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred, QSizePolicy::DefaultType)); + q->setGraphicsItem(q); + + resolveLayoutDirection(); + q->unsetWindowFrameMargins(); + flags |= QGraphicsItem::ItemUsesExtendedStyleOption; + flags |= QGraphicsItem::ItemSendsGeometryChanges; + if (windowFlags & Qt::Window) + flags |= QGraphicsItem::ItemIsPanel; +} + +qreal QGraphicsWidgetPrivate::titleBarHeight(const QStyleOptionTitleBar &options) const +{ + Q_Q(const QGraphicsWidget); + int height = q->style()->pixelMetric(QStyle::PM_TitleBarHeight, &options); +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + if (qobject_cast(q->style())) { + height -=4; + } +#endif + return (qreal)height; +} + +/*! + \internal +*/ +QGraphicsWidgetPrivate::~QGraphicsWidgetPrivate() +{ + // Remove any lazily allocated data + delete[] margins; + delete[] windowFrameMargins; + delete windowData; +} + +/*! + \internal + + Ensures that margins is allocated. + This function must be called before any dereferencing. +*/ +void QGraphicsWidgetPrivate::ensureMargins() const +{ + if (!margins) { + margins = new qreal[4]; + for (int i = 0; i < 4; ++i) + margins[i] = 0; + } +} + +/*! + \internal + + Ensures that windowFrameMargins is allocated. + This function must be called before any dereferencing. +*/ +void QGraphicsWidgetPrivate::ensureWindowFrameMargins() const +{ + if (!windowFrameMargins) { + windowFrameMargins = new qreal[4]; + for (int i = 0; i < 4; ++i) + windowFrameMargins[i] = 0; + } +} + +/*! + \internal + + Ensures that windowData is allocated. + This function must be called before any dereferencing. +*/ +void QGraphicsWidgetPrivate::ensureWindowData() +{ + if (!windowData) + windowData = new WindowData; +} + +void QGraphicsWidgetPrivate::setPalette_helper(const QPalette &palette) +{ + if (this->palette == palette && this->palette.resolve() == palette.resolve()) + return; + updatePalette(palette); +} + +void QGraphicsWidgetPrivate::resolvePalette(uint inheritedMask) +{ + inheritedPaletteResolveMask = inheritedMask; + QPalette naturalPalette = naturalWidgetPalette(); + QPalette resolvedPalette = palette.resolve(naturalPalette); + updatePalette(resolvedPalette); +} + +void QGraphicsWidgetPrivate::updatePalette(const QPalette &palette) +{ + Q_Q(QGraphicsWidget); + // Update local palette setting. + this->palette = palette; + + // Calculate new mask. + if (q->isWindow() && !q->testAttribute(Qt::WA_WindowPropagation)) + inheritedPaletteResolveMask = 0; + int mask = palette.resolve() | inheritedPaletteResolveMask; + + // Propagate to children. + for (int i = 0; i < children.size(); ++i) { + QGraphicsItem *item = children.at(i); + if (item->isWidget()) { + QGraphicsWidget *w = static_cast(item); + if (!w->isWindow() || w->testAttribute(Qt::WA_WindowPropagation)) + w->d_func()->resolvePalette(mask); + } else { + item->d_ptr->resolvePalette(mask); + } + } + + // Notify change. + QEvent event(QEvent::PaletteChange); + QApplication::sendEvent(q, &event); +} + +void QGraphicsWidgetPrivate::setLayoutDirection_helper(Qt::LayoutDirection direction) +{ + Q_Q(QGraphicsWidget); + if ((direction == Qt::RightToLeft) == (testAttribute(Qt::WA_RightToLeft))) + return; + q->setAttribute(Qt::WA_RightToLeft, (direction == Qt::RightToLeft)); + + // Propagate this change to all children. + for (int i = 0; i < children.size(); ++i) { + QGraphicsItem *item = children.at(i); + if (item->isWidget()) { + QGraphicsWidget *widget = static_cast(item); + if (widget->parentWidget() && !widget->testAttribute(Qt::WA_SetLayoutDirection)) + widget->d_func()->setLayoutDirection_helper(direction); + } + } + + // Send the notification event to this widget item. + QEvent e(QEvent::LayoutDirectionChange); + QApplication::sendEvent(q, &e); +} + +void QGraphicsWidgetPrivate::resolveLayoutDirection() +{ + Q_Q(QGraphicsWidget); + if (q->testAttribute(Qt::WA_SetLayoutDirection)) { + return; + } + if (QGraphicsWidget *parentWidget = q->parentWidget()) { + setLayoutDirection_helper(parentWidget->layoutDirection()); + } else if (scene) { + // ### shouldn't the scene have a layoutdirection really? how does + // ### QGraphicsWidget get changes from QApplication::layoutDirection? + setLayoutDirection_helper(QApplication::layoutDirection()); + } else { + setLayoutDirection_helper(QApplication::layoutDirection()); + } +} + +QPalette QGraphicsWidgetPrivate::naturalWidgetPalette() const +{ + Q_Q(const QGraphicsWidget); + QPalette palette; + if (QGraphicsWidget *parent = q->parentWidget()) { + palette = parent->palette(); + } else if (scene) { + palette = scene->palette(); + } + palette.resolve(0); + return palette; +} + +void QGraphicsWidgetPrivate::setFont_helper(const QFont &font) +{ + if (this->font == font && this->font.resolve() == font.resolve()) + return; + updateFont(font); +} + +void QGraphicsWidgetPrivate::resolveFont(uint inheritedMask) +{ + Q_Q(QGraphicsWidget); + inheritedFontResolveMask = inheritedMask; + if (QGraphicsWidget *p = q->parentWidget()) + inheritedFontResolveMask |= p->d_func()->inheritedFontResolveMask; + QFont naturalFont = naturalWidgetFont(); + QFont resolvedFont = font.resolve(naturalFont); + updateFont(resolvedFont); +} + +void QGraphicsWidgetPrivate::updateFont(const QFont &font) +{ + Q_Q(QGraphicsWidget); + // Update the local font setting. + this->font = font; + + // Calculate new mask. + if (q->isWindow() && !q->testAttribute(Qt::WA_WindowPropagation)) + inheritedFontResolveMask = 0; + int mask = font.resolve() | inheritedFontResolveMask; + + // Propagate to children. + for (int i = 0; i < children.size(); ++i) { + QGraphicsItem *item = children.at(i); + if (item->isWidget()) { + QGraphicsWidget *w = static_cast(item); + if (!w->isWindow() || w->testAttribute(Qt::WA_WindowPropagation)) + w->d_func()->resolveFont(mask); + } else { + item->d_ptr->resolveFont(mask); + } + } + + if (!polished) + return; + // Notify change. + QEvent event(QEvent::FontChange); + QApplication::sendEvent(q, &event); +} + +QFont QGraphicsWidgetPrivate::naturalWidgetFont() const +{ + Q_Q(const QGraphicsWidget); + QFont naturalFont; // ### no application font support + if (QGraphicsWidget *parent = q->parentWidget()) { + naturalFont = parent->font(); + } else if (scene) { + naturalFont = scene->font(); + } + naturalFont.resolve(0); + return naturalFont; +} + +void QGraphicsWidgetPrivate::initStyleOptionTitleBar(QStyleOptionTitleBar *option) +{ + Q_Q(QGraphicsWidget); + ensureWindowData(); + q->initStyleOption(option); + option->rect.setHeight(titleBarHeight(*option)); + option->titleBarFlags = windowFlags; + option->subControls = QStyle::SC_TitleBarCloseButton | QStyle::SC_TitleBarLabel | QStyle::SC_TitleBarSysMenu; + option->activeSubControls = windowData->hoveredSubControl; + bool isActive = q->isActiveWindow(); + if (isActive) { + option->state |= QStyle::State_Active; + option->titleBarState = Qt::WindowActive; + option->titleBarState |= QStyle::State_Active; + } else { + option->state &= ~QStyle::State_Active; + option->titleBarState = Qt::WindowNoState; + } + QFont windowTitleFont = QApplication::font("QWorkspaceTitleBar"); + QRect textRect = q->style()->subControlRect(QStyle::CC_TitleBar, option, QStyle::SC_TitleBarLabel, 0); + option->text = QFontMetrics(windowTitleFont).elidedText( + windowData->windowTitle, Qt::ElideRight, textRect.width()); +} + +void QGraphicsWidgetPrivate::adjustWindowFlags(Qt::WindowFlags *flags) +{ + bool customize = (*flags & (Qt::CustomizeWindowHint + | Qt::FramelessWindowHint + | Qt::WindowTitleHint + | Qt::WindowSystemMenuHint + | Qt::WindowMinimizeButtonHint + | Qt::WindowMaximizeButtonHint + | Qt::WindowContextHelpButtonHint)); + + uint type = (*flags & Qt::WindowType_Mask); + if (customize) + ; + else if (type == Qt::Dialog || type == Qt::Sheet) + *flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowContextHelpButtonHint; + else if (type == Qt::Tool) + *flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint; + else if (type == Qt::Window || type == Qt::SubWindow) + *flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint + | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint; +} + +void QGraphicsWidgetPrivate::windowFrameMouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QGraphicsWidget); + ensureWindowData(); + if (windowData->grabbedSection != Qt::NoSection) { + if (windowData->grabbedSection == Qt::TitleBarArea) { + windowData->buttonSunken = false; + QStyleOptionTitleBar bar; + initStyleOptionTitleBar(&bar); + // make sure that the coordinates (rect and pos) we send to the style are positive. + bar.rect = q->windowFrameRect().toRect(); + bar.rect.moveTo(0,0); + bar.rect.setHeight(q->style()->pixelMetric(QStyle::PM_TitleBarHeight, &bar)); + QPointF pos = event->pos(); + if (windowFrameMargins) { + pos.rx() += windowFrameMargins[Left]; + pos.ry() += windowFrameMargins[Top]; + } + bar.subControls = QStyle::SC_TitleBarCloseButton; + if (q->style()->subControlRect(QStyle::CC_TitleBar, &bar, + QStyle::SC_TitleBarCloseButton, + event->widget()).contains(pos.toPoint())) { + q->close(); + } + } + if (!(static_cast(event)->buttons())) + windowData->grabbedSection = Qt::NoSection; + event->accept(); + } +} + +void QGraphicsWidgetPrivate::windowFrameMousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QGraphicsWidget); + if (event->button() != Qt::LeftButton) + return; + + ensureWindowData(); + windowData->startGeometry = q->geometry(); + windowData->grabbedSection = q->windowFrameSectionAt(event->pos()); + ensureWindowData(); + if (windowData->grabbedSection == Qt::TitleBarArea + && windowData->hoveredSubControl == QStyle::SC_TitleBarCloseButton) { + windowData->buttonSunken = true; + q->update(); + } + event->setAccepted(windowData->grabbedSection != Qt::NoSection); +} + +/*! + Used to calculate the + Precondition: + \a widget should support either hfw or wfh + + If \a heightForWidth is set to false, this function will query the width for height + instead. \a width will then be interpreted as height, \a minh and \a maxh will be interpreted + as minimum width and maximum width. + */ +static qreal minimumHeightForWidth(qreal width, qreal minh, qreal maxh, + const QGraphicsWidget *widget, + bool heightForWidth = true) +{ + qreal minimumHeightForWidth = -1; + const bool hasHFW = QGraphicsLayoutItemPrivate::get(widget)->hasHeightForWidth(); + if (hasHFW == heightForWidth) { + minimumHeightForWidth = hasHFW + ? widget->effectiveSizeHint(Qt::MinimumSize, QSizeF(width, -1)).height() + : widget->effectiveSizeHint(Qt::MinimumSize, QSizeF(-1, width)).width(); //"width" is here height! + } else { + // widthForHeight + const qreal constraint = width; + while (maxh - minh > 0.1) { + qreal middle = minh + (maxh - minh)/2; + // ### really bad, if we are a widget with a layout it will call + // layout->effectiveSizeHint(Qt::MiniumumSize), which again will call + // sizeHint three times because of how the cache works + qreal hfw = hasHFW + ? widget->effectiveSizeHint(Qt::MinimumSize, QSizeF(middle, -1)).height() + : widget->effectiveSizeHint(Qt::MinimumSize, QSizeF(-1, middle)).width(); + if (hfw > constraint) { + minh = middle; + } else if (hfw <= constraint) { + maxh = middle; + } + } + minimumHeightForWidth = maxh; + } + return minimumHeightForWidth; +} + +static qreal minimumWidthForHeight(qreal height, qreal minw, qreal maxw, + const QGraphicsWidget *widget) +{ + return minimumHeightForWidth(height, minw, maxw, widget, false); +} + +static QSizeF closestAcceptableSize(const QSizeF &proposed, + const QGraphicsWidget *widget) +{ + const QSizeF current = widget->size(); + + qreal minw = proposed.width(); + qreal maxw = current.width(); + qreal minh = proposed.height(); + qreal maxh = current.height(); + + qreal middlew = maxw; + qreal middleh = maxh; + qreal min_hfw; + min_hfw = minimumHeightForWidth(maxw, minh, maxh, widget); + + do { + if (maxw - minw < 0.1) { + // we still havent found anything, cut off binary search + minw = maxw; + minh = maxh; + } + middlew = minw + (maxw - minw)/2.0; + middleh = minh + (maxh - minh)/2.0; + + min_hfw = minimumHeightForWidth(middlew, minh, maxh, widget); + + if (min_hfw > middleh) { + minw = middlew; + minh = middleh; + } else if (min_hfw <= middleh) { + maxw = middlew; + maxh = middleh; + } + } while (maxw != minw); + + min_hfw = minimumHeightForWidth(middlew, minh, maxh, widget); + + QSizeF result; + if (min_hfw < maxh) { + result = QSizeF(middlew, min_hfw); + } else { + // Needed because of the cut-off we do above. + result = QSizeF(minimumWidthForHeight(maxh, proposed.width(), current.width(), widget), maxh); + } + return result; +} + +static void _q_boundGeometryToSizeConstraints(const QRectF &startGeometry, + QRectF *rect, Qt::WindowFrameSection section, + const QSizeF &min, const QSizeF &max, + const QGraphicsWidget *widget) +{ + const QRectF proposedRect = *rect; + qreal width = qBound(min.width(), proposedRect.width(), max.width()); + qreal height = qBound(min.height(), proposedRect.height(), max.height()); + + const bool hasHFW = QGraphicsLayoutItemPrivate::get(widget)->hasHeightForWidth(); + const bool hasWFH = QGraphicsLayoutItemPrivate::get(widget)->hasWidthForHeight(); + + const bool widthChanged = proposedRect.width() != widget->size().width(); + const bool heightChanged = proposedRect.height() != widget->size().height(); + + if (hasHFW || hasWFH) { + if (widthChanged || heightChanged) { + qreal minExtent; + qreal maxExtent; + qreal constraint; + qreal proposed; + if (hasHFW) { + minExtent = min.height(); + maxExtent = max.height(); + constraint = width; + proposed = proposedRect.height(); + } else { + // width for height + minExtent = min.width(); + maxExtent = max.width(); + constraint = height; + proposed = proposedRect.width(); + } + if (minimumHeightForWidth(constraint, minExtent, maxExtent, widget, hasHFW) > proposed) { + QSizeF effectiveSize = closestAcceptableSize(QSizeF(width, height), widget); + width = effectiveSize.width(); + height = effectiveSize.height(); + } + } + } + + switch (section) { + case Qt::LeftSection: + rect->setRect(startGeometry.right() - qRound(width), startGeometry.top(), + qRound(width), startGeometry.height()); + break; + case Qt::TopLeftSection: + rect->setRect(startGeometry.right() - qRound(width), startGeometry.bottom() - qRound(height), + qRound(width), qRound(height)); + break; + case Qt::TopSection: + rect->setRect(startGeometry.left(), startGeometry.bottom() - qRound(height), + startGeometry.width(), qRound(height)); + break; + case Qt::TopRightSection: + rect->setTop(rect->bottom() - qRound(height)); + rect->setWidth(qRound(width)); + break; + case Qt::RightSection: + rect->setWidth(qRound(width)); + break; + case Qt::BottomRightSection: + rect->setWidth(qRound(width)); + rect->setHeight(qRound(height)); + break; + case Qt::BottomSection: + rect->setHeight(qRound(height)); + break; + case Qt::BottomLeftSection: + rect->setRect(startGeometry.right() - qRound(width), startGeometry.top(), + qRound(width), qRound(height)); + break; + default: + break; + } +} + +void QGraphicsWidgetPrivate::windowFrameMouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QGraphicsWidget); + ensureWindowData(); + if (!(event->buttons() & Qt::LeftButton) || windowData->hoveredSubControl != QStyle::SC_TitleBarLabel) + return; + + QLineF delta(q->mapFromScene(event->buttonDownScenePos(Qt::LeftButton)), event->pos()); + QLineF parentDelta(q->mapToParent(delta.p1()), q->mapToParent(delta.p2())); + QLineF parentXDelta(q->mapToParent(QPointF(delta.p1().x(), 0)), q->mapToParent(QPointF(delta.p2().x(), 0))); + QLineF parentYDelta(q->mapToParent(QPointF(0, delta.p1().y())), q->mapToParent(QPointF(0, delta.p2().y()))); + + QRectF newGeometry; + switch (windowData->grabbedSection) { + case Qt::LeftSection: + newGeometry = QRectF(windowData->startGeometry.topLeft() + + QPointF(parentXDelta.dx(), parentXDelta.dy()), + windowData->startGeometry.size() - QSizeF(delta.dx(), delta.dy())); + break; + case Qt::TopLeftSection: + newGeometry = QRectF(windowData->startGeometry.topLeft() + + QPointF(parentDelta.dx(), parentDelta.dy()), + windowData->startGeometry.size() - QSizeF(delta.dx(), delta.dy())); + break; + case Qt::TopSection: + newGeometry = QRectF(windowData->startGeometry.topLeft() + + QPointF(parentYDelta.dx(), parentYDelta.dy()), + windowData->startGeometry.size() - QSizeF(0, delta.dy())); + break; + case Qt::TopRightSection: + newGeometry = QRectF(windowData->startGeometry.topLeft() + + QPointF(parentYDelta.dx(), parentYDelta.dy()), + windowData->startGeometry.size() - QSizeF(-delta.dx(), delta.dy())); + break; + case Qt::RightSection: + newGeometry = QRectF(windowData->startGeometry.topLeft(), + windowData->startGeometry.size() + QSizeF(delta.dx(), 0)); + break; + case Qt::BottomRightSection: + newGeometry = QRectF(windowData->startGeometry.topLeft(), + windowData->startGeometry.size() + QSizeF(delta.dx(), delta.dy())); + break; + case Qt::BottomSection: + newGeometry = QRectF(windowData->startGeometry.topLeft(), + windowData->startGeometry.size() + QSizeF(0, delta.dy())); + break; + case Qt::BottomLeftSection: + newGeometry = QRectF(windowData->startGeometry.topLeft() + + QPointF(parentXDelta.dx(), parentXDelta.dy()), + windowData->startGeometry.size() - QSizeF(delta.dx(), -delta.dy())); + break; + case Qt::TitleBarArea: + newGeometry = QRectF(windowData->startGeometry.topLeft() + + QPointF(parentDelta.dx(), parentDelta.dy()), + windowData->startGeometry.size()); + break; + case Qt::NoSection: + break; + } + + if (windowData->grabbedSection != Qt::NoSection) { + _q_boundGeometryToSizeConstraints(windowData->startGeometry, &newGeometry, + windowData->grabbedSection, + q->effectiveSizeHint(Qt::MinimumSize), + q->effectiveSizeHint(Qt::MaximumSize), + q); + q->setGeometry(newGeometry); + } +} + +void QGraphicsWidgetPrivate::windowFrameHoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_Q(QGraphicsWidget); + if (!hasDecoration()) + return; + + ensureWindowData(); + + if (q->rect().contains(event->pos())) { + if (windowData->buttonMouseOver || windowData->hoveredSubControl != QStyle::SC_None) + windowFrameHoverLeaveEvent(event); + return; + } + + bool wasMouseOver = windowData->buttonMouseOver; + QRect oldButtonRect = windowData->buttonRect; + windowData->buttonRect = QRect(); + windowData->buttonMouseOver = false; + QPointF pos = event->pos(); + QStyleOptionTitleBar bar; + // make sure that the coordinates (rect and pos) we send to the style are positive. + if (windowFrameMargins) { + pos.rx() += windowFrameMargins[Left]; + pos.ry() += windowFrameMargins[Top]; + } + initStyleOptionTitleBar(&bar); + bar.rect = q->windowFrameRect().toRect(); + bar.rect.moveTo(0,0); + bar.rect.setHeight(int(titleBarHeight(bar))); + + Qt::CursorShape cursorShape = Qt::ArrowCursor; + bool needsSetCursorCall = true; + switch (q->windowFrameSectionAt(event->pos())) { + case Qt::TopLeftSection: + case Qt::BottomRightSection: + cursorShape = Qt::SizeFDiagCursor; + break; + case Qt::TopRightSection: + case Qt::BottomLeftSection: + cursorShape = Qt::SizeBDiagCursor; + break; + case Qt::LeftSection: + case Qt::RightSection: + cursorShape = Qt::SizeHorCursor; + break; + case Qt::TopSection: + case Qt::BottomSection: + cursorShape = Qt::SizeVerCursor; + break; + case Qt::TitleBarArea: + windowData->buttonRect = q->style()->subControlRect( + QStyle::CC_TitleBar, &bar, QStyle::SC_TitleBarCloseButton, 0); +#ifdef Q_WS_MAC + // On mac we should hover if we are in the 'area' of the buttons + windowData->buttonRect |= q->style()->subControlRect( + QStyle::CC_TitleBar, &bar, QStyle::SC_TitleBarMinButton, 0); + windowData->buttonRect |= q->style()->subControlRect( + QStyle::CC_TitleBar, &bar, QStyle::SC_TitleBarMaxButton, 0); +#endif + if (windowData->buttonRect.contains(pos.toPoint())) + windowData->buttonMouseOver = true; + event->ignore(); + break; + default: + needsSetCursorCall = false; + event->ignore(); + } +#ifndef QT_NO_CURSOR + if (needsSetCursorCall) + q->setCursor(cursorShape); +#endif + // update buttons if we hover over them + windowData->hoveredSubControl = q->style()->hitTestComplexControl(QStyle::CC_TitleBar, &bar, pos.toPoint(), 0); + if (windowData->hoveredSubControl != QStyle::SC_TitleBarCloseButton) + windowData->hoveredSubControl = QStyle::SC_TitleBarLabel; + + if (windowData->buttonMouseOver != wasMouseOver) { + if (!oldButtonRect.isNull()) + q->update(QRectF(oldButtonRect).translated(q->windowFrameRect().topLeft())); + if (!windowData->buttonRect.isNull()) + q->update(QRectF(windowData->buttonRect).translated(q->windowFrameRect().topLeft())); + } +} + +void QGraphicsWidgetPrivate::windowFrameHoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + Q_Q(QGraphicsWidget); + if (hasDecoration()) { + // ### restore the cursor, don't override it +#ifndef QT_NO_CURSOR + q->unsetCursor(); +#endif + + ensureWindowData(); + + bool needsUpdate = false; + if (windowData->hoveredSubControl == QStyle::SC_TitleBarCloseButton + || windowData->buttonMouseOver) + needsUpdate = true; + + // update the hover state (of buttons etc...) + windowData->hoveredSubControl = QStyle::SC_None; + windowData->buttonMouseOver = false; + windowData->buttonRect = QRect(); + if (needsUpdate) + q->update(windowData->buttonRect); + } +} + +bool QGraphicsWidgetPrivate::hasDecoration() const +{ + return (windowFlags & Qt::Window) && (windowFlags & Qt::WindowTitleHint); +} + +/** + * is called after a reparent has taken place to fix up the focus chain(s) + */ +void QGraphicsWidgetPrivate::fixFocusChainBeforeReparenting(QGraphicsWidget *newParent, QGraphicsScene *oldScene, QGraphicsScene *newScene) +{ + Q_Q(QGraphicsWidget); + + Q_ASSERT(focusNext && focusPrev); + + QGraphicsWidget *n = q; //last one in 'new' list + QGraphicsWidget *o = 0; //last one in 'old' list + + QGraphicsWidget *w = focusNext; + + QGraphicsWidget *firstOld = 0; + bool wasPreviousNew = true; + + while (w != q) { + bool isCurrentNew = q->isAncestorOf(w); + if (isCurrentNew) { + if (!wasPreviousNew) { + n->d_func()->focusNext = w; + w->d_func()->focusPrev = n; + } + n = w; + } else /*if (!isCurrentNew)*/ { + if (wasPreviousNew) { + if (o) { + o->d_func()->focusNext = w; + w->d_func()->focusPrev = o; + } else { + firstOld = w; + } + } + o = w; + } + w = w->d_func()->focusNext; + wasPreviousNew = isCurrentNew; + } + + // repair the 'old' chain + if (firstOld) { + o->d_func()->focusNext = firstOld; + firstOld->d_func()->focusPrev = o; + } + + // update tabFocusFirst for oldScene if the item is going to be removed from oldScene + if (newParent) + newScene = newParent->scene(); + + if (oldScene && newScene != oldScene) + oldScene->d_func()->tabFocusFirst = (firstOld && firstOld->scene() == oldScene) ? firstOld : 0; + + QGraphicsItem *topLevelItem = newParent ? newParent->topLevelItem() : 0; + QGraphicsWidget *topLevel = 0; + if (topLevelItem && topLevelItem->isWidget()) + topLevel = static_cast(topLevelItem); + + if (topLevel && newParent) { + QGraphicsWidget *last = topLevel->d_func()->focusPrev; + // link last with new chain + last->d_func()->focusNext = q; + focusPrev = last; + + // link last in chain with + topLevel->d_func()->focusPrev = n; + n->d_func()->focusNext = topLevel; + } else { + // q is the start of the focus chain + n->d_func()->focusNext = q; + focusPrev = n; + } + +} + +void QGraphicsWidgetPrivate::setLayout_helper(QGraphicsLayout *l) +{ + delete (this->layout); + layout = l; + if (!l) { + Q_Q(QGraphicsWidget); + q->updateGeometry(); + } +} + +qreal QGraphicsWidgetPrivate::width() const +{ + Q_Q(const QGraphicsWidget); + return q->geometry().width(); +} + +void QGraphicsWidgetPrivate::setWidth(qreal w) +{ + if (qIsNaN(w)) + return; + Q_Q(QGraphicsWidget); + if (q->geometry().width() == w) + return; + + QRectF oldGeom = q->geometry(); + + q->setGeometry(QRectF(q->x(), q->y(), w, height())); +} + +void QGraphicsWidgetPrivate::resetWidth() +{ + Q_Q(QGraphicsWidget); + q->setGeometry(QRectF(q->x(), q->y(), 0, height())); +} + +qreal QGraphicsWidgetPrivate::height() const +{ + Q_Q(const QGraphicsWidget); + return q->geometry().height(); +} + +void QGraphicsWidgetPrivate::setHeight(qreal h) +{ + if (qIsNaN(h)) + return; + Q_Q(QGraphicsWidget); + if (q->geometry().height() == h) + return; + + QRectF oldGeom = q->geometry(); + + q->setGeometry(QRectF(q->x(), q->y(), width(), h)); +} + +void QGraphicsWidgetPrivate::resetHeight() +{ + Q_Q(QGraphicsWidget); + q->setGeometry(QRectF(q->x(), q->y(), width(), 0)); +} + +void QGraphicsWidgetPrivate::setGeometryFromSetPos() +{ + if (inSetGeometry) + return; + Q_Q(QGraphicsWidget); + inSetPos = 1; + // Ensure setGeometry is called (avoid recursion when setPos is + // called from within setGeometry). + q->setGeometry(QRectF(pos, q->size())); + inSetPos = 0 ; +} + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgraphicswidget_p.h b/src/widgets/graphicsview/qgraphicswidget_p.h new file mode 100644 index 0000000000..4379eafbde --- /dev/null +++ b/src/widgets/graphicsview/qgraphicswidget_p.h @@ -0,0 +1,226 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRAPHICSWIDGET_P_H +#define QGRAPHICSWIDGET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include "qgraphicsitem_p.h" +#include "qgraphicswidget.h" +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsLayout; +class QStyleOptionTitleBar; + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsWidgetPrivate : public QGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsWidget) +public: + QGraphicsWidgetPrivate() + : margins(0), + layout(0), + inheritedPaletteResolveMask(0), + inheritedFontResolveMask(0), + inSetGeometry(0), + polished(0), + inSetPos(0), + autoFillBackground(0), + focusPolicy(Qt::NoFocus), + focusNext(0), + focusPrev(0), + windowFlags(0), + windowData(0), + setWindowFrameMargins(false), + windowFrameMargins(0) + { } + virtual ~QGraphicsWidgetPrivate(); + + void init(QGraphicsItem *parentItem, Qt::WindowFlags wFlags); + qreal titleBarHeight(const QStyleOptionTitleBar &options) const; + + // Margins + enum {Left, Top, Right, Bottom}; + mutable qreal *margins; + void ensureMargins() const; + + void fixFocusChainBeforeReparenting(QGraphicsWidget *newParent, QGraphicsScene *oldScene, QGraphicsScene *newScene = 0); + void setLayout_helper(QGraphicsLayout *l); + + // Layouts + QGraphicsLayout *layout; + void setLayoutDirection_helper(Qt::LayoutDirection direction); + void resolveLayoutDirection(); + + // Style + QPalette palette; + uint inheritedPaletteResolveMask; + void setPalette_helper(const QPalette &palette); + void resolvePalette(uint inheritedMask); + void updatePalette(const QPalette &palette); + QPalette naturalWidgetPalette() const; + QFont font; + uint inheritedFontResolveMask; + void setFont_helper(const QFont &font); + void resolveFont(uint inheritedMask); + void updateFont(const QFont &font); + QFont naturalWidgetFont() const; + + // Window specific + void initStyleOptionTitleBar(QStyleOptionTitleBar *option); + void adjustWindowFlags(Qt::WindowFlags *wFlags); + void windowFrameMouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void windowFrameMousePressEvent(QGraphicsSceneMouseEvent *event); + void windowFrameMouseMoveEvent(QGraphicsSceneMouseEvent *event); + void windowFrameHoverMoveEvent(QGraphicsSceneHoverEvent *event); + void windowFrameHoverLeaveEvent(QGraphicsSceneHoverEvent *event); + bool hasDecoration() const; + + // Private Properties + qreal width() const; + void setWidth(qreal); + void resetWidth(); + + qreal height() const; + void setHeight(qreal); + void resetHeight(); + void setGeometryFromSetPos(); + + // State + inline int attributeToBitIndex(Qt::WidgetAttribute att) const + { + int bit = -1; + switch (att) { + case Qt::WA_SetLayoutDirection: bit = 0; break; + case Qt::WA_RightToLeft: bit = 1; break; + case Qt::WA_SetStyle: bit = 2; break; + case Qt::WA_Resized: bit = 3; break; + case Qt::WA_DeleteOnClose: bit = 4; break; + case Qt::WA_NoSystemBackground: bit = 5; break; + case Qt::WA_OpaquePaintEvent: bit = 6; break; + case Qt::WA_SetPalette: bit = 7; break; + case Qt::WA_SetFont: bit = 8; break; + case Qt::WA_WindowPropagation: bit = 9; break; + default: break; + } + return bit; + } + inline void setAttribute(Qt::WidgetAttribute att, bool value) + { + int bit = attributeToBitIndex(att); + if (bit == -1) { + qWarning("QGraphicsWidget::setAttribute: unsupported attribute %d", int(att)); + return; + } + if (value) + attributes |= (1 << bit); + else + attributes &= ~(1 << bit); + } + inline bool testAttribute(Qt::WidgetAttribute att) const + { + int bit = attributeToBitIndex(att); + if (bit == -1) + return false; + return (attributes & (1 << bit)) != 0; + } + quint32 attributes : 10; + quint32 inSetGeometry : 1; + quint32 polished: 1; + quint32 inSetPos : 1; + quint32 autoFillBackground : 1; + + // Focus + Qt::FocusPolicy focusPolicy; + QGraphicsWidget *focusNext; + QGraphicsWidget *focusPrev; + + // Windows + Qt::WindowFlags windowFlags; + struct WindowData { + QString windowTitle; + QStyle::SubControl hoveredSubControl; + Qt::WindowFrameSection grabbedSection; + uint buttonMouseOver : 1; + uint buttonSunken : 1; + QRectF startGeometry; + QRect buttonRect; + WindowData() + : hoveredSubControl(QStyle::SC_None) + , grabbedSection(Qt::NoSection) + , buttonMouseOver(false) + , buttonSunken(false) + {} + } *windowData; + void ensureWindowData(); + + bool setWindowFrameMargins; + mutable qreal *windowFrameMargins; + void ensureWindowFrameMargins() const; + +#ifndef QT_NO_ACTION + QList actions; +#endif +}; + +#endif //!defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +QT_END_NAMESPACE + +#endif //QGRAPHICSWIDGET_P_H + diff --git a/src/widgets/graphicsview/qgridlayoutengine.cpp b/src/widgets/graphicsview/qgridlayoutengine.cpp new file mode 100644 index 0000000000..b8586cef21 --- /dev/null +++ b/src/widgets/graphicsview/qgridlayoutengine.cpp @@ -0,0 +1,1742 @@ +/**************************************************************************** +** +** 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 QtGui module 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 "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include + +#include "qgraphicslayoutitem.h" +#include "qgridlayoutengine_p.h" +#include "qstyleoption.h" +#include "qvarlengtharray.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +template +static void insertOrRemoveItems(QVector &items, int index, int delta) +{ + int count = items.count(); + if (index < count) { + if (delta > 0) { + items.insert(index, delta, T()); + } else if (delta < 0) { + items.remove(index, qMin(-delta, count - index)); + } + } +} + +static qreal growthFactorBelowPreferredSize(qreal desired, qreal sumAvailable, qreal sumDesired) +{ + Q_ASSERT(sumDesired != 0.0); + return desired * qPow(sumAvailable / sumDesired, desired / sumDesired); +} + +static qreal fixedDescent(qreal descent, qreal ascent, qreal targetSize) +{ + if (descent < 0.0) + return -1.0; + + Q_ASSERT(descent >= 0.0); + Q_ASSERT(ascent >= 0.0); + Q_ASSERT(targetSize >= ascent + descent); + + qreal extra = targetSize - (ascent + descent); + return descent + (extra / 2.0); +} + +static qreal compare(const QGridLayoutBox &box1, const QGridLayoutBox &box2, int which) +{ + qreal size1 = box1.q_sizes(which); + qreal size2 = box2.q_sizes(which); + + if (which == MaximumSize) { + return size2 - size1; + } else { + return size1 - size2; + } +} + +void QGridLayoutBox::add(const QGridLayoutBox &other, int stretch, qreal spacing) +{ + Q_ASSERT(q_minimumDescent < 0.0); + + q_minimumSize += other.q_minimumSize + spacing; + q_preferredSize += other.q_preferredSize + spacing; + q_maximumSize += ((stretch == 0) ? other.q_preferredSize : other.q_maximumSize) + spacing; +} + +void QGridLayoutBox::combine(const QGridLayoutBox &other) +{ + q_minimumDescent = qMax(q_minimumDescent, other.q_minimumDescent); + q_minimumAscent = qMax(q_minimumAscent, other.q_minimumAscent); + + q_minimumSize = qMax(q_minimumAscent + q_minimumDescent, + qMax(q_minimumSize, other.q_minimumSize)); + qreal maxMax; + if (q_maximumSize == FLT_MAX && other.q_maximumSize != FLT_MAX) + maxMax = other.q_maximumSize; + else if (other.q_maximumSize == FLT_MAX && q_maximumSize != FLT_MAX) + maxMax = q_maximumSize; + else + maxMax = qMax(q_maximumSize, other.q_maximumSize); + + q_maximumSize = qMax(q_minimumSize, maxMax); + q_preferredSize = qBound(q_minimumSize, qMax(q_preferredSize, other.q_preferredSize), + q_maximumSize); +} + +void QGridLayoutBox::normalize() +{ + q_maximumSize = qMax(qreal(0.0), q_maximumSize); + q_minimumSize = qBound(qreal(0.0), q_minimumSize, q_maximumSize); + q_preferredSize = qBound(q_minimumSize, q_preferredSize, q_maximumSize); + q_minimumDescent = qMin(q_minimumDescent, q_minimumSize); + + Q_ASSERT((q_minimumDescent < 0.0) == (q_minimumAscent < 0.0)); +} + +#ifdef QT_DEBUG +void QGridLayoutBox::dump(int indent) const +{ + qDebug("%*sBox (%g <= %g <= %g [%g/%g])", indent, "", q_minimumSize, q_preferredSize, + q_maximumSize, q_minimumAscent, q_minimumDescent); +} +#endif + +bool operator==(const QGridLayoutBox &box1, const QGridLayoutBox &box2) +{ + for (int i = 0; i < NSizes; ++i) { + if (box1.q_sizes(i) != box2.q_sizes(i)) + return false; + } + return box1.q_minimumDescent == box2.q_minimumDescent + && box1.q_minimumAscent == box2.q_minimumAscent; +} + +void QGridLayoutRowData::reset(int count) +{ + ignore.fill(false, count); + boxes.fill(QGridLayoutBox(), count); + multiCellMap.clear(); + stretches.fill(0, count); + spacings.fill(0.0, count); + hasIgnoreFlag = false; +} + +void QGridLayoutRowData::distributeMultiCells(const QGridLayoutRowInfo &rowInfo) +{ + MultiCellMap::const_iterator i = multiCellMap.constBegin(); + for (; i != multiCellMap.constEnd(); ++i) { + int start = i.key().first; + int span = i.key().second; + int end = start + span; + const QGridLayoutBox &box = i.value().q_box; + int stretch = i.value().q_stretch; + + QGridLayoutBox totalBox = this->totalBox(start, end); + QVarLengthArray extras(span); + QVarLengthArray dummy(span); + QVarLengthArray newSizes(span); + + for (int j = 0; j < NSizes; ++j) { + qreal extra = compare(box, totalBox, j); + if (extra > 0.0) { + calculateGeometries(start, end, box.q_sizes(j), dummy.data(), newSizes.data(), + 0, totalBox, rowInfo); + + for (int k = 0; k < span; ++k) + extras[k].q_sizes(j) = newSizes[k]; + } + } + + for (int k = 0; k < span; ++k) { + boxes[start + k].combine(extras[k]); + if (stretch != 0) + stretches[start + k] = qMax(stretches[start + k], stretch); + } + } + multiCellMap.clear(); +} + +void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSize, qreal *positions, + qreal *sizes, qreal *descents, + const QGridLayoutBox &totalBox, + const QGridLayoutRowInfo &rowInfo) +{ + Q_ASSERT(end > start); + + targetSize = qMax(totalBox.q_minimumSize, targetSize); + + int n = end - start; + QVarLengthArray newSizes(n); + QVarLengthArray factors(n); + qreal sumFactors = 0.0; + int sumStretches = 0; + qreal sumAvailable; + + for (int i = 0; i < n; ++i) { + if (stretches[start + i] > 0) + sumStretches += stretches[start + i]; + } + + if (targetSize < totalBox.q_preferredSize) { + stealBox(start, end, MinimumSize, positions, sizes); + + sumAvailable = targetSize - totalBox.q_minimumSize; + if (sumAvailable > 0.0) { + qreal sumDesired = totalBox.q_preferredSize - totalBox.q_minimumSize; + + for (int i = 0; i < n; ++i) { + if (ignore.testBit(start + i)) { + factors[i] = 0.0; + continue; + } + + const QGridLayoutBox &box = boxes.at(start + i); + qreal desired = box.q_preferredSize - box.q_minimumSize; + factors[i] = growthFactorBelowPreferredSize(desired, sumAvailable, sumDesired); + sumFactors += factors[i]; + } + + for (int i = 0; i < n; ++i) { + Q_ASSERT(sumFactors > 0.0); + qreal delta = sumAvailable * factors[i] / sumFactors; + newSizes[i] = sizes[i] + delta; + } + } + } else { + bool isLargerThanMaximum = (targetSize > totalBox.q_maximumSize); + if (isLargerThanMaximum) { + stealBox(start, end, MaximumSize, positions, sizes); + sumAvailable = targetSize - totalBox.q_maximumSize; + } else { + stealBox(start, end, PreferredSize, positions, sizes); + sumAvailable = targetSize - totalBox.q_preferredSize; + } + + if (sumAvailable > 0.0) { + qreal sumCurrentAvailable = sumAvailable; + bool somethingHasAMaximumSize = false; + + qreal sumSizes = 0.0; + for (int i = 0; i < n; ++i) + sumSizes += sizes[i]; + + for (int i = 0; i < n; ++i) { + if (ignore.testBit(start + i)) { + newSizes[i] = 0.0; + factors[i] = 0.0; + continue; + } + + const QGridLayoutBox &box = boxes.at(start + i); + qreal boxSize; + + qreal desired; + if (isLargerThanMaximum) { + boxSize = box.q_maximumSize; + desired = rowInfo.boxes.value(start + i).q_maximumSize - boxSize; + } else { + boxSize = box.q_preferredSize; + desired = box.q_maximumSize - boxSize; + } + if (desired == 0.0) { + newSizes[i] = sizes[i]; + factors[i] = 0.0; + } else { + Q_ASSERT(desired > 0.0); + + int stretch = stretches[start + i]; + if (sumStretches == 0) { + if (hasIgnoreFlag) { + factors[i] = (stretch < 0) ? 1.0 : 0.0; + } else { + factors[i] = (stretch < 0) ? sizes[i] : 0.0; + } + } else if (stretch == sumStretches) { + factors[i] = 1.0; + } else if (stretch <= 0) { + factors[i] = 0.0; + } else { + qreal ultimateSize; + qreal ultimateSumSizes; + qreal x = ((stretch * sumSizes) + - (sumStretches * boxSize)) + / (sumStretches - stretch); + if (x >= 0.0) { + ultimateSize = boxSize + x; + ultimateSumSizes = sumSizes + x; + } else { + ultimateSize = boxSize; + ultimateSumSizes = (sumStretches * boxSize) + / stretch; + } + + /* + We multiply these by 1.5 to give some space for a smooth transition + (at the expense of the stretch factors, which are not fully respected + during the transition). + */ + ultimateSize = ultimateSize * 3 / 2; + ultimateSumSizes = ultimateSumSizes * 3 / 2; + + qreal beta = ultimateSumSizes - sumSizes; + if (!beta) { + factors[i] = 1; + } else { + qreal alpha = qMin(sumCurrentAvailable, beta); + qreal ultimateFactor = (stretch * ultimateSumSizes / sumStretches) + - (boxSize); + qreal transitionalFactor = sumCurrentAvailable * (ultimateSize - boxSize) / beta; + + factors[i] = ((alpha * ultimateFactor) + + ((beta - alpha) * transitionalFactor)) / beta; + } + + } + sumFactors += factors[i]; + if (desired < sumCurrentAvailable) + somethingHasAMaximumSize = true; + + newSizes[i] = -1.0; + } + } + + bool keepGoing = somethingHasAMaximumSize; + while (keepGoing) { + keepGoing = false; + + for (int i = 0; i < n; ++i) { + if (newSizes[i] >= 0.0) + continue; + + qreal maxBoxSize; + if (isLargerThanMaximum) + maxBoxSize = rowInfo.boxes.value(start + i).q_maximumSize; + else + maxBoxSize = boxes.at(start + i).q_maximumSize; + + qreal avail = sumCurrentAvailable * factors[i] / sumFactors; + if (sizes[i] + avail >= maxBoxSize) { + newSizes[i] = maxBoxSize; + sumCurrentAvailable -= maxBoxSize - sizes[i]; + sumFactors -= factors[i]; + keepGoing = (sumCurrentAvailable > 0.0); + if (!keepGoing) + break; + } + } + } + + for (int i = 0; i < n; ++i) { + if (newSizes[i] < 0.0) { + qreal delta = (sumFactors == 0.0) ? 0.0 + : sumCurrentAvailable * factors[i] / sumFactors; + newSizes[i] = sizes[i] + delta; + } + } + } + } + + if (sumAvailable > 0) { + qreal offset = 0; + for (int i = 0; i < n; ++i) { + qreal delta = newSizes[i] - sizes[i]; + positions[i] += offset; + sizes[i] += delta; + offset += delta; + } + +#if 0 // some "pixel allocation" + int surplus = targetSize - (positions[n - 1] + sizes[n - 1]); + Q_ASSERT(surplus >= 0 && surplus <= n); + + int prevSurplus = -1; + while (surplus > 0 && surplus != prevSurplus) { + prevSurplus = surplus; + + int offset = 0; + for (int i = 0; i < n; ++i) { + const QGridLayoutBox &box = boxes.at(start + i); + int delta = (!ignore.testBit(start + i) && surplus > 0 + && factors[i] > 0 && sizes[i] < box.q_maximumSize) + ? 1 : 0; + + positions[i] += offset; + sizes[i] += delta; + offset += delta; + surplus -= delta; + } + } + Q_ASSERT(surplus == 0); +#endif + } + + if (descents) { + for (int i = 0; i < n; ++i) { + if (ignore.testBit(start + i)) + continue; + const QGridLayoutBox &box = boxes.at(start + i); + descents[i] = fixedDescent(box.q_minimumDescent, box.q_minimumAscent, sizes[i]); + } + } +} + +QGridLayoutBox QGridLayoutRowData::totalBox(int start, int end) const +{ + QGridLayoutBox result; + if (start < end) { + result.q_maximumSize = 0.0; + qreal nextSpacing = 0.0; + for (int i = start; i < end; ++i) { + result.add(boxes.at(i), stretches.at(i), nextSpacing); + nextSpacing = spacings.at(i); + } + } + return result; +} + +void QGridLayoutRowData::stealBox(int start, int end, int which, qreal *positions, qreal *sizes) +{ + qreal offset = 0.0; + qreal nextSpacing = 0.0; + + for (int i = start; i < end; ++i) { + qreal avail = 0.0; + + if (!ignore.testBit(i)) { + const QGridLayoutBox &box = boxes.at(i); + avail = box.q_sizes(which); + offset += nextSpacing; + nextSpacing = spacings.at(i); + } + + *positions++ = offset; + *sizes++ = avail; + offset += avail; + } +} + +#ifdef QT_DEBUG +void QGridLayoutRowData::dump(int indent) const +{ + qDebug("%*sData", indent, ""); + + for (int i = 0; i < ignore.count(); ++i) { + qDebug("%*s Row %d (stretch %d, spacing %g)", indent, "", i, stretches.at(i), + spacings.at(i)); + if (ignore.testBit(i)) + qDebug("%*s Ignored", indent, ""); + boxes.at(i).dump(indent + 2); + } + + MultiCellMap::const_iterator it = multiCellMap.constBegin(); + while (it != multiCellMap.constEnd()) { + qDebug("%*s Multi-cell entry <%d, %d> (stretch %d)", indent, "", it.key().first, + it.key().second, it.value().q_stretch); + it.value().q_box.dump(indent + 2); + } +} +#endif + +QGridLayoutItem::QGridLayoutItem(QGridLayoutEngine *engine, QGraphicsLayoutItem *layoutItem, + int row, int column, int rowSpan, int columnSpan, + Qt::Alignment alignment, int itemAtIndex) + : q_engine(engine), q_layoutItem(layoutItem), q_alignment(alignment) +{ + q_firstRows[Hor] = column; + q_firstRows[Ver] = row; + q_rowSpans[Hor] = columnSpan; + q_rowSpans[Ver] = rowSpan; + q_stretches[Hor] = -1; + q_stretches[Ver] = -1; + + q_engine->insertItem(this, itemAtIndex); +} + +int QGridLayoutItem::firstRow(Qt::Orientation orientation) const +{ + return q_firstRows[orientation == Qt::Vertical]; +} + +int QGridLayoutItem::firstColumn(Qt::Orientation orientation) const +{ + return q_firstRows[orientation == Qt::Horizontal]; +} + +int QGridLayoutItem::lastRow(Qt::Orientation orientation) const +{ + return firstRow(orientation) + rowSpan(orientation) - 1; +} + +int QGridLayoutItem::lastColumn(Qt::Orientation orientation) const +{ + return firstColumn(orientation) + columnSpan(orientation) - 1; +} + +int QGridLayoutItem::rowSpan(Qt::Orientation orientation) const +{ + return q_rowSpans[orientation == Qt::Vertical]; +} + +int QGridLayoutItem::columnSpan(Qt::Orientation orientation) const +{ + return q_rowSpans[orientation == Qt::Horizontal]; +} + +void QGridLayoutItem::setFirstRow(int row, Qt::Orientation orientation) +{ + q_firstRows[orientation == Qt::Vertical] = row; +} + +void QGridLayoutItem::setRowSpan(int rowSpan, Qt::Orientation orientation) +{ + q_rowSpans[orientation == Qt::Vertical] = rowSpan; +} + +int QGridLayoutItem::stretchFactor(Qt::Orientation orientation) const +{ + int stretch = q_stretches[orientation == Qt::Vertical]; + if (stretch >= 0) + return stretch; + + QSizePolicy::Policy policy = sizePolicy(orientation); + + if (policy & QSizePolicy::ExpandFlag) { + return 1; + } else if (policy & QSizePolicy::GrowFlag) { + return -1; // because we max it up + } else { + return 0; + } +} + +void QGridLayoutItem::setStretchFactor(int stretch, Qt::Orientation orientation) +{ + Q_ASSERT(stretch >= 0); // ### deal with too big stretches + q_stretches[orientation == Qt::Vertical] = stretch; +} + +QSizePolicy::Policy QGridLayoutItem::sizePolicy(Qt::Orientation orientation) const +{ + QSizePolicy sizePolicy(q_layoutItem->sizePolicy()); + return (orientation == Qt::Horizontal) ? sizePolicy.horizontalPolicy() + : sizePolicy.verticalPolicy(); +} + +/* + returns true if the size policy returns true for either hasHeightForWidth() + or hasWidthForHeight() + */ +bool QGridLayoutItem::hasDynamicConstraint() const +{ + return QGraphicsLayoutItemPrivate::get(q_layoutItem)->hasHeightForWidth() + || QGraphicsLayoutItemPrivate::get(q_layoutItem)->hasWidthForHeight(); +} + +Qt::Orientation QGridLayoutItem::dynamicConstraintOrientation() const +{ + if (QGraphicsLayoutItemPrivate::get(q_layoutItem)->hasHeightForWidth()) + return Qt::Vertical; + else //if (QGraphicsLayoutItemPrivate::get(q_layoutItem)->hasWidthForHeight()) + return Qt::Horizontal; +} + +QSizePolicy::ControlTypes QGridLayoutItem::controlTypes(LayoutSide /* side */) const +{ + return q_layoutItem->sizePolicy().controlType(); +} + +QSizeF QGridLayoutItem::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + return q_layoutItem->effectiveSizeHint(which, constraint); +} + +QGridLayoutBox QGridLayoutItem::box(Qt::Orientation orientation, qreal constraint) const +{ + QGridLayoutBox result; + QSizePolicy::Policy policy = sizePolicy(orientation); + + if (orientation == Qt::Horizontal) { + QSizeF constraintSize(-1.0, constraint); + + result.q_preferredSize = sizeHint(Qt::PreferredSize, constraintSize).width(); + + if (policy & QSizePolicy::ShrinkFlag) { + result.q_minimumSize = sizeHint(Qt::MinimumSize, constraintSize).width(); + } else { + result.q_minimumSize = result.q_preferredSize; + } + + if (policy & (QSizePolicy::GrowFlag | QSizePolicy::ExpandFlag)) { + result.q_maximumSize = sizeHint(Qt::MaximumSize, constraintSize).width(); + } else { + result.q_maximumSize = result.q_preferredSize; + } + } else { + QSizeF constraintSize(constraint, -1.0); + + result.q_preferredSize = sizeHint(Qt::PreferredSize, constraintSize).height(); + + if (policy & QSizePolicy::ShrinkFlag) { + result.q_minimumSize = sizeHint(Qt::MinimumSize, constraintSize).height(); + } else { + result.q_minimumSize = result.q_preferredSize; + } + + if (policy & (QSizePolicy::GrowFlag | QSizePolicy::ExpandFlag)) { + result.q_maximumSize = sizeHint(Qt::MaximumSize, constraintSize).height(); + } else { + result.q_maximumSize = result.q_preferredSize; + } + + result.q_minimumDescent = sizeHint(Qt::MinimumDescent, constraintSize).height(); + if (result.q_minimumDescent >= 0.0) + result.q_minimumAscent = result.q_minimumSize - result.q_minimumDescent; + } + if (policy & QSizePolicy::IgnoreFlag) + result.q_preferredSize = result.q_minimumSize; + + return result; +} + +QRectF QGridLayoutItem::geometryWithin(qreal x, qreal y, qreal width, qreal height, + qreal rowDescent) const +{ + rowDescent = -1.0; // ### This disables the descent + + QGridLayoutBox vBox = box(Qt::Vertical); + if (vBox.q_minimumDescent < 0.0 || rowDescent < 0.0) { + qreal cellWidth = width; + qreal cellHeight = height; + + + QSizeF size = effectiveMaxSize(QSizeF(-1,-1)); + if (hasDynamicConstraint()) { + if (dynamicConstraintOrientation() == Qt::Vertical) { + if (size.width() > cellWidth) + size = effectiveMaxSize(QSizeF(cellWidth, -1)); + } else if (size.height() > cellHeight) { + size = effectiveMaxSize(QSizeF(-1, cellHeight)); + } + } + size = size.boundedTo(QSizeF(cellWidth, cellHeight)); + width = size.width(); + height = size.height(); + + Qt::Alignment align = q_engine->effectiveAlignment(this); + switch (align & Qt::AlignHorizontal_Mask) { + case Qt::AlignHCenter: + x += (cellWidth - width)/2; + break; + case Qt::AlignRight: + x += cellWidth - width; + break; + default: + break; + } + switch (align & Qt::AlignVertical_Mask) { + case Qt::AlignVCenter: + y += (cellHeight - height)/2; + break; + case Qt::AlignBottom: + y += cellHeight - height; + break; + default: + break; + } + return QRectF(x, y, width, height); + } else { + qreal descent = vBox.q_minimumDescent; + qreal ascent = vBox.q_minimumSize - descent; + return QRectF(x, y + height - rowDescent - ascent, width, ascent + descent); + } +} + +void QGridLayoutItem::setGeometry(const QRectF &rect) +{ + q_layoutItem->setGeometry(rect); +} + +void QGridLayoutItem::transpose() +{ + qSwap(q_firstRows[Hor], q_firstRows[Ver]); + qSwap(q_rowSpans[Hor], q_rowSpans[Ver]); + qSwap(q_stretches[Hor], q_stretches[Ver]); +} + +void QGridLayoutItem::insertOrRemoveRows(int row, int delta, Qt::Orientation orientation) +{ + int oldFirstRow = firstRow(orientation); + if (oldFirstRow >= row) { + setFirstRow(oldFirstRow + delta, orientation); + } else if (lastRow(orientation) >= row) { + setRowSpan(rowSpan(orientation) + delta, orientation); + } +} +/*! + \internal + returns the effective maximumSize, will take the sizepolicy into + consideration. (i.e. if sizepolicy does not have QSizePolicy::Grow, then + maxSizeHint will be the preferredSize) + Note that effectiveSizeHint does not take sizePolicy into consideration, + (since it only evaluates the hints, as the name implies) +*/ +QSizeF QGridLayoutItem::effectiveMaxSize(const QSizeF &constraint) const +{ + QSizeF size = constraint; + bool vGrow = (sizePolicy(Qt::Vertical) & QSizePolicy::GrowFlag) == QSizePolicy::GrowFlag; + bool hGrow = (sizePolicy(Qt::Horizontal) & QSizePolicy::GrowFlag) == QSizePolicy::GrowFlag; + if (!vGrow || !hGrow) { + QSizeF pref = layoutItem()->effectiveSizeHint(Qt::PreferredSize, constraint); + if (!vGrow) + size.setHeight(pref.height()); + if (!hGrow) + size.setWidth(pref.width()); + } + + if (!size.isValid()) { + QSizeF maxSize = layoutItem()->effectiveSizeHint(Qt::MaximumSize, size); + if (size.width() == -1) + size.setWidth(maxSize.width()); + if (size.height() == -1) + size.setHeight(maxSize.height()); + } + return size; +} + +#ifdef QT_DEBUG +void QGridLayoutItem::dump(int indent) const +{ + qDebug("%*s%p (%d, %d) %d x %d", indent, "", q_layoutItem, firstRow(), firstColumn(), + rowSpan(), columnSpan()); + + if (q_stretches[Hor] >= 0) + qDebug("%*s Horizontal stretch: %d", indent, "", q_stretches[Hor]); + if (q_stretches[Ver] >= 0) + qDebug("%*s Vertical stretch: %d", indent, "", q_stretches[Ver]); + if (q_alignment != 0) + qDebug("%*s Alignment: %x", indent, "", uint(q_alignment)); + qDebug("%*s Horizontal size policy: %x Vertical size policy: %x", + indent, "", sizePolicy(Qt::Horizontal), sizePolicy(Qt::Vertical)); +} +#endif + +void QGridLayoutRowInfo::insertOrRemoveRows(int row, int delta) +{ + count += delta; + + insertOrRemoveItems(stretches, row, delta); + insertOrRemoveItems(spacings, row, delta); + insertOrRemoveItems(alignments, row, delta); + insertOrRemoveItems(boxes, row, delta); +} + +#ifdef QT_DEBUG +void QGridLayoutRowInfo::dump(int indent) const +{ + qDebug("%*sInfo (count: %d)", indent, "", count); + for (int i = 0; i < count; ++i) { + QString message; + + if (stretches.value(i).value() >= 0) + message += QString::fromAscii(" stretch %1").arg(stretches.value(i).value()); + if (spacings.value(i).value() >= 0.0) + message += QString::fromAscii(" spacing %1").arg(spacings.value(i).value()); + if (alignments.value(i) != 0) + message += QString::fromAscii(" alignment %1").arg(int(alignments.value(i)), 16); + + if (!message.isEmpty() || boxes.value(i) != QGridLayoutBox()) { + qDebug("%*s Row %d:%s", indent, "", i, qPrintable(message)); + if (boxes.value(i) != QGridLayoutBox()) + boxes.value(i).dump(indent + 1); + } + } +} +#endif + +QGridLayoutEngine::QGridLayoutEngine() +{ + m_visualDirection = Qt::LeftToRight; + invalidate(); +} + +int QGridLayoutEngine::rowCount(Qt::Orientation orientation) const +{ + return q_infos[orientation == Qt::Vertical].count; +} + +int QGridLayoutEngine::columnCount(Qt::Orientation orientation) const +{ + return q_infos[orientation == Qt::Horizontal].count; +} + +int QGridLayoutEngine::itemCount() const +{ + return q_items.count(); +} + +QGridLayoutItem *QGridLayoutEngine::itemAt(int index) const +{ + Q_ASSERT(index >= 0 && index < itemCount()); + return q_items.at(index); +} + +int QGridLayoutEngine::indexOf(QGraphicsLayoutItem *item) const +{ + for (int i = 0; i < q_items.size(); ++i) { + if (item == q_items.at(i)->layoutItem()) + return i; + } + return -1; +} + +int QGridLayoutEngine::effectiveFirstRow(Qt::Orientation orientation) const +{ + ensureEffectiveFirstAndLastRows(); + return q_cachedEffectiveFirstRows[orientation == Qt::Vertical]; +} + +int QGridLayoutEngine::effectiveLastRow(Qt::Orientation orientation) const +{ + ensureEffectiveFirstAndLastRows(); + return q_cachedEffectiveLastRows[orientation == Qt::Vertical]; +} + +void QGridLayoutEngine::setSpacing(qreal spacing, Qt::Orientations orientations) +{ + Q_ASSERT(spacing >= 0.0); + if (orientations & Qt::Horizontal) + q_defaultSpacings[Hor].setUserValue(spacing); + if (orientations & Qt::Vertical) + q_defaultSpacings[Ver].setUserValue(spacing); + + invalidate(); +} + +qreal QGridLayoutEngine::spacing(const QLayoutStyleInfo &styleInfo, Qt::Orientation orientation) const +{ + if (q_defaultSpacings[orientation == Qt::Vertical].isDefault()) { + QStyle *style = styleInfo.style(); + QStyleOption option; + option.initFrom(styleInfo.widget()); + qreal defaultSpacing = (qreal)style->pixelMetric(orientation == Qt::Vertical ? QStyle::PM_LayoutVerticalSpacing + : QStyle::PM_LayoutHorizontalSpacing, &option, styleInfo.widget()); + q_defaultSpacings[orientation == Qt::Vertical].setCachedValue(defaultSpacing); + } + return q_defaultSpacings[orientation == Qt::Vertical].value(); +} + +void QGridLayoutEngine::setRowSpacing(int row, qreal spacing, Qt::Orientation orientation) +{ + Q_ASSERT(row >= 0); + + QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; + if (row >= rowInfo.spacings.count()) + rowInfo.spacings.resize(row + 1); + if (spacing >= 0) + rowInfo.spacings[row].setUserValue(spacing); + else + rowInfo.spacings[row] = QLayoutParameter(); + invalidate(); +} + +qreal QGridLayoutEngine::rowSpacing(int row, Qt::Orientation orientation) const +{ + QLayoutParameter spacing = q_infos[orientation == Qt::Vertical].spacings.value(row); + if (!spacing.isDefault()) + return spacing.value(); + return q_defaultSpacings[orientation == Qt::Vertical].value(); +} + +void QGridLayoutEngine::setRowStretchFactor(int row, int stretch, Qt::Orientation orientation) +{ + Q_ASSERT(row >= 0); + Q_ASSERT(stretch >= 0); + + maybeExpandGrid(row, -1, orientation); + + QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; + if (row >= rowInfo.stretches.count()) + rowInfo.stretches.resize(row + 1); + rowInfo.stretches[row].setUserValue(stretch); +} + +int QGridLayoutEngine::rowStretchFactor(int row, Qt::Orientation orientation) const +{ + QStretchParameter stretch = q_infos[orientation == Qt::Vertical].stretches.value(row); + if (!stretch.isDefault()) + return stretch.value(); + return 0; +} + +void QGridLayoutEngine::setStretchFactor(QGraphicsLayoutItem *layoutItem, int stretch, + Qt::Orientation orientation) +{ + Q_ASSERT(stretch >= 0); + + if (QGridLayoutItem *item = findLayoutItem(layoutItem)) + item->setStretchFactor(stretch, orientation); +} + +int QGridLayoutEngine::stretchFactor(QGraphicsLayoutItem *layoutItem, Qt::Orientation orientation) const +{ + if (QGridLayoutItem *item = findLayoutItem(layoutItem)) + return item->stretchFactor(orientation); + return 0; +} + +void QGridLayoutEngine::setRowSizeHint(Qt::SizeHint which, int row, qreal size, + Qt::Orientation orientation) +{ + Q_ASSERT(row >= 0); + Q_ASSERT(size >= 0.0); + + maybeExpandGrid(row, -1, orientation); + + QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; + if (row >= rowInfo.boxes.count()) + rowInfo.boxes.resize(row + 1); + rowInfo.boxes[row].q_sizes(which) = size; +} + +qreal QGridLayoutEngine::rowSizeHint(Qt::SizeHint which, int row, Qt::Orientation orientation) const +{ + return q_infos[orientation == Qt::Vertical].boxes.value(row).q_sizes(which); +} + +void QGridLayoutEngine::setRowAlignment(int row, Qt::Alignment alignment, + Qt::Orientation orientation) +{ + Q_ASSERT(row >= 0); + + maybeExpandGrid(row, -1, orientation); + + QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; + if (row >= rowInfo.alignments.count()) + rowInfo.alignments.resize(row + 1); + rowInfo.alignments[row] = alignment; +} + +Qt::Alignment QGridLayoutEngine::rowAlignment(int row, Qt::Orientation orientation) const +{ + Q_ASSERT(row >= 0); + return q_infos[orientation == Qt::Vertical].alignments.value(row); +} + +void QGridLayoutEngine::setAlignment(QGraphicsLayoutItem *layoutItem, Qt::Alignment alignment) +{ + if (QGridLayoutItem *item = findLayoutItem(layoutItem)) + item->setAlignment(alignment); + invalidate(); +} + +Qt::Alignment QGridLayoutEngine::alignment(QGraphicsLayoutItem *layoutItem) const +{ + if (QGridLayoutItem *item = findLayoutItem(layoutItem)) + return item->alignment(); + return 0; +} + +Qt::Alignment QGridLayoutEngine::effectiveAlignment(const QGridLayoutItem *layoutItem) const +{ + Qt::Alignment align = layoutItem->alignment(); + if (!(align & Qt::AlignVertical_Mask)) { + // no vertical alignment, respect the row alignment + int y = layoutItem->firstRow(); + align |= (rowAlignment(y, Qt::Vertical) & Qt::AlignVertical_Mask); + } + if (!(align & Qt::AlignHorizontal_Mask)) { + // no horizontal alignment, respect the column alignment + int x = layoutItem->firstColumn(); + align |= (rowAlignment(x, Qt::Horizontal) & Qt::AlignHorizontal_Mask); + } + return align; +} + +/*! + \internal + The \a index is only used by QGraphicsLinearLayout to ensure that itemAt() reflects the order + of visual arrangement. Strictly speaking it does not have to, but most people expect it to. + (And if it didn't we would have to add itemArrangedAt(int index) or something..) + */ +void QGridLayoutEngine::insertItem(QGridLayoutItem *item, int index) +{ + maybeExpandGrid(item->lastRow(), item->lastColumn()); + + if (index == -1) + q_items.append(item); + else + q_items.insert(index, item); + + for (int i = item->firstRow(); i <= item->lastRow(); ++i) { + for (int j = item->firstColumn(); j <= item->lastColumn(); ++j) { + if (itemAt(i, j)) + qWarning("QGridLayoutEngine::addItem: Cell (%d, %d) already taken", i, j); + setItemAt(i, j, item); + } + } +} + +void QGridLayoutEngine::addItem(QGridLayoutItem *item) +{ + insertItem(item, -1); +} + +void QGridLayoutEngine::removeItem(QGridLayoutItem *item) +{ + Q_ASSERT(q_items.contains(item)); + + invalidate(); + + for (int i = item->firstRow(); i <= item->lastRow(); ++i) { + for (int j = item->firstColumn(); j <= item->lastColumn(); ++j) { + if (itemAt(i, j) == item) + setItemAt(i, j, 0); + } + } + + q_items.removeAll(item); +} + +QGridLayoutItem *QGridLayoutEngine::findLayoutItem(QGraphicsLayoutItem *layoutItem) const +{ + for (int i = q_items.count() - 1; i >= 0; --i) { + QGridLayoutItem *item = q_items.at(i); + if (item->layoutItem() == layoutItem) + return item; + } + return 0; +} + +QGridLayoutItem *QGridLayoutEngine::itemAt(int row, int column, Qt::Orientation orientation) const +{ + if (orientation == Qt::Horizontal) + qSwap(row, column); + if (uint(row) >= uint(rowCount()) || uint(column) >= uint(columnCount())) + return 0; + return q_grid.at((row * internalGridColumnCount()) + column); +} + +void QGridLayoutEngine::invalidate() +{ + q_cachedEffectiveFirstRows[Hor] = -1; + q_cachedEffectiveFirstRows[Ver] = -1; + q_cachedEffectiveLastRows[Hor] = -1; + q_cachedEffectiveLastRows[Ver] = -1; + q_cachedDataForStyleInfo.invalidate(); + q_cachedSize = QSizeF(); + q_cachedConstraintOrientation = UnknownConstraint; +} + +static void visualRect(QRectF *geom, Qt::LayoutDirection dir, const QRectF &contentsRect) +{ + if (dir == Qt::RightToLeft) + geom->moveRight(contentsRect.right() - (geom->left() - contentsRect.left())); +} + +void QGridLayoutEngine::setGeometries(const QLayoutStyleInfo &styleInfo, + const QRectF &contentsGeometry) +{ + if (rowCount() < 1 || columnCount() < 1) + return; + + ensureGeometries(styleInfo, contentsGeometry.size()); + + for (int i = q_items.count() - 1; i >= 0; --i) { + QGridLayoutItem *item = q_items.at(i); + + qreal x = q_xx[item->firstColumn()]; + qreal y = q_yy[item->firstRow()]; + qreal width = q_widths[item->lastColumn()]; + qreal height = q_heights[item->lastRow()]; + + if (item->columnSpan() != 1) + width += q_xx[item->lastColumn()] - x; + if (item->rowSpan() != 1) + height += q_yy[item->lastRow()] - y; + + QRectF geom = item->geometryWithin(contentsGeometry.x() + x, contentsGeometry.y() + y, + width, height, q_descents[item->lastRow()]); + visualRect(&geom, visualDirection(), contentsGeometry); + item->setGeometry(geom); + } +} + +// ### candidate for deletion +QRectF QGridLayoutEngine::cellRect(const QLayoutStyleInfo &styleInfo, + const QRectF &contentsGeometry, int row, int column, int rowSpan, + int columnSpan) const +{ + if (uint(row) >= uint(rowCount()) || uint(column) >= uint(columnCount()) + || rowSpan < 1 || columnSpan < 1) + return QRectF(); + + ensureGeometries(styleInfo, contentsGeometry.size()); + + int lastColumn = qMax(column + columnSpan, columnCount()) - 1; + int lastRow = qMax(row + rowSpan, rowCount()) - 1; + + qreal x = q_xx[column]; + qreal y = q_yy[row]; + qreal width = q_widths[lastColumn]; + qreal height = q_heights[lastRow]; + + if (columnSpan != 1) + width += q_xx[lastColumn] - x; + if (rowSpan != 1) + height += q_yy[lastRow] - y; + + return QRectF(contentsGeometry.x() + x, contentsGeometry.y() + y, width, height); +} + +QSizeF QGridLayoutEngine::sizeHint(const QLayoutStyleInfo &styleInfo, Qt::SizeHint which, + const QSizeF &constraint) const +{ + QGridLayoutBox sizehint_totalBoxes[NOrientations]; + + bool sizeHintCalculated = false; + + if (hasDynamicConstraint() && rowCount() > 0 && columnCount() > 0) { + if (constraintOrientation() == Qt::Vertical) { + //We have items whose height depends on their width + if (constraint.width() >= 0) { + if (q_cachedDataForStyleInfo != styleInfo) + ensureColumnAndRowData(&q_columnData, &sizehint_totalBoxes[Hor], styleInfo, NULL, NULL, Qt::Horizontal); + else + sizehint_totalBoxes[Hor] = q_totalBoxes[Hor]; + QVector sizehint_xx; + QVector sizehint_widths; + + sizehint_xx.resize(columnCount()); + sizehint_widths.resize(columnCount()); + qreal width = constraint.width(); + //Calculate column widths and positions, and put results in q_xx.data() and q_widths.data() so that we can use this information as + //constraints to find the row heights + q_columnData.calculateGeometries(0, columnCount(), width, sizehint_xx.data(), sizehint_widths.data(), + 0, sizehint_totalBoxes[Hor], q_infos[Hor]); + ensureColumnAndRowData(&q_rowData, &sizehint_totalBoxes[Ver], styleInfo, sizehint_xx.data(), sizehint_widths.data(), Qt::Vertical); + sizeHintCalculated = true; + } + } else { + if (constraint.height() >= 0) { + //We have items whose width depends on their height + ensureColumnAndRowData(&q_rowData, &sizehint_totalBoxes[Ver], styleInfo, NULL, NULL, Qt::Vertical); + QVector sizehint_yy; + QVector sizehint_heights; + + sizehint_yy.resize(rowCount()); + sizehint_heights.resize(rowCount()); + qreal height = constraint.height(); + //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() so that we can use this information as + //constraints to find the column widths + q_rowData.calculateGeometries(0, rowCount(), height, sizehint_yy.data(), sizehint_heights.data(), + 0, sizehint_totalBoxes[Ver], q_infos[Ver]); + ensureColumnAndRowData(&q_columnData, &sizehint_totalBoxes[Hor], styleInfo, sizehint_yy.data(), sizehint_heights.data(), Qt::Horizontal); + sizeHintCalculated = true; + } + } + } + + if (!sizeHintCalculated) { + //No items with height for width, so it doesn't matter which order we do these in + if (q_cachedDataForStyleInfo != styleInfo) { + ensureColumnAndRowData(&q_columnData, &sizehint_totalBoxes[Hor], styleInfo, NULL, NULL, Qt::Horizontal); + ensureColumnAndRowData(&q_rowData, &sizehint_totalBoxes[Ver], styleInfo, NULL, NULL, Qt::Vertical); + } else { + sizehint_totalBoxes[Hor] = q_totalBoxes[Hor]; + sizehint_totalBoxes[Ver] = q_totalBoxes[Ver]; + } + } + + switch (which) { + case Qt::MinimumSize: + return QSizeF(sizehint_totalBoxes[Hor].q_minimumSize, sizehint_totalBoxes[Ver].q_minimumSize); + case Qt::PreferredSize: + return QSizeF(sizehint_totalBoxes[Hor].q_preferredSize, sizehint_totalBoxes[Ver].q_preferredSize); + case Qt::MaximumSize: + return QSizeF(sizehint_totalBoxes[Hor].q_maximumSize, sizehint_totalBoxes[Ver].q_maximumSize); + case Qt::MinimumDescent: + return QSizeF(-1.0, sizehint_totalBoxes[Hor].q_minimumDescent); // ### doesn't work + default: + break; + } + return QSizeF(); +} + +QSizePolicy::ControlTypes QGridLayoutEngine::controlTypes(LayoutSide side) const +{ + Qt::Orientation orientation = (side == Top || side == Bottom) ? Qt::Vertical : Qt::Horizontal; + int row = (side == Top || side == Left) ? effectiveFirstRow(orientation) + : effectiveLastRow(orientation); + QSizePolicy::ControlTypes result = 0; + + for (int column = columnCount(orientation) - 1; column >= 0; --column) { + if (QGridLayoutItem *item = itemAt(row, column, orientation)) + result |= item->controlTypes(side); + } + return result; +} + +void QGridLayoutEngine::transpose() +{ + invalidate(); + + for (int i = q_items.count() - 1; i >= 0; --i) + q_items.at(i)->transpose(); + + qSwap(q_defaultSpacings[Hor], q_defaultSpacings[Ver]); + qSwap(q_infos[Hor], q_infos[Ver]); + + regenerateGrid(); +} + +void QGridLayoutEngine::setVisualDirection(Qt::LayoutDirection direction) +{ + m_visualDirection = direction; +} + +Qt::LayoutDirection QGridLayoutEngine::visualDirection() const +{ + return m_visualDirection; +} + +#ifdef QT_DEBUG +void QGridLayoutEngine::dump(int indent) const +{ + qDebug("%*sEngine", indent, ""); + + qDebug("%*s Items (%d)", indent, "", q_items.count()); + int i; + for (i = 0; i < q_items.count(); ++i) + q_items.at(i)->dump(indent + 2); + + qDebug("%*s Grid (%d x %d)", indent, "", internalGridRowCount(), + internalGridColumnCount()); + for (int row = 0; row < internalGridRowCount(); ++row) { + QString message = QLatin1String("[ "); + for (int column = 0; column < internalGridColumnCount(); ++column) { + message += QString::number(q_items.indexOf(itemAt(row, column))).rightJustified(3); + message += QLatin1Char(' '); + } + message += QLatin1Char(']'); + qDebug("%*s %s", indent, "", qPrintable(message)); + } + + if (q_defaultSpacings[Hor].value() >= 0.0 || q_defaultSpacings[Ver].value() >= 0.0) + qDebug("%*s Default spacings: %g %g", indent, "", q_defaultSpacings[Hor].value(), + q_defaultSpacings[Ver].value()); + + qDebug("%*s Column and row info", indent, ""); + q_infos[Hor].dump(indent + 2); + q_infos[Ver].dump(indent + 2); + + qDebug("%*s Column and row data", indent, ""); + q_columnData.dump(indent + 2); + q_rowData.dump(indent + 2); + + qDebug("%*s Geometries output", indent, ""); + QVector *cellPos = &q_yy; + for (int pass = 0; pass < 2; ++pass) { + QString message; + for (i = 0; i < cellPos->count(); ++i) { + message += QLatin1String((message.isEmpty() ? "[" : ", ")); + message += QString::number(cellPos->at(i)); + } + message += QLatin1Char(']'); + qDebug("%*s %s %s", indent, "", (pass == 0 ? "rows:" : "columns:"), qPrintable(message)); + cellPos = &q_xx; + } +} +#endif + +void QGridLayoutEngine::maybeExpandGrid(int row, int column, Qt::Orientation orientation) +{ + invalidate(); // ### move out of here? + + if (orientation == Qt::Horizontal) + qSwap(row, column); + + if (row < rowCount() && column < columnCount()) + return; + + int oldGridRowCount = internalGridRowCount(); + int oldGridColumnCount = internalGridColumnCount(); + + q_infos[Ver].count = qMax(row + 1, rowCount()); + q_infos[Hor].count = qMax(column + 1, columnCount()); + + int newGridRowCount = internalGridRowCount(); + int newGridColumnCount = internalGridColumnCount(); + + int newGridSize = newGridRowCount * newGridColumnCount; + if (newGridSize != q_grid.count()) { + q_grid.resize(newGridSize); + + if (newGridColumnCount != oldGridColumnCount) { + for (int i = oldGridRowCount - 1; i >= 1; --i) { + for (int j = oldGridColumnCount - 1; j >= 0; --j) { + int oldIndex = (i * oldGridColumnCount) + j; + int newIndex = (i * newGridColumnCount) + j; + + Q_ASSERT(newIndex > oldIndex); + q_grid[newIndex] = q_grid[oldIndex]; + q_grid[oldIndex] = 0; + } + } + } + } +} + +void QGridLayoutEngine::regenerateGrid() +{ + q_grid.fill(0); + + for (int i = q_items.count() - 1; i >= 0; --i) { + QGridLayoutItem *item = q_items.at(i); + + for (int j = item->firstRow(); j <= item->lastRow(); ++j) { + for (int k = item->firstColumn(); k <= item->lastColumn(); ++k) { + setItemAt(j, k, item); + } + } + } +} + +void QGridLayoutEngine::setItemAt(int row, int column, QGridLayoutItem *item) +{ + Q_ASSERT(row >= 0 && row < rowCount()); + Q_ASSERT(column >= 0 && column < columnCount()); + q_grid[(row * internalGridColumnCount()) + column] = item; +} + +void QGridLayoutEngine::insertOrRemoveRows(int row, int delta, Qt::Orientation orientation) +{ + int oldRowCount = rowCount(orientation); + Q_ASSERT(uint(row) <= uint(oldRowCount)); + + invalidate(); + + // appending rows (or columns) is easy + if (row == oldRowCount && delta > 0) { + maybeExpandGrid(oldRowCount + delta - 1, -1, orientation); + return; + } + + q_infos[orientation == Qt::Vertical].insertOrRemoveRows(row, delta); + + for (int i = q_items.count() - 1; i >= 0; --i) + q_items.at(i)->insertOrRemoveRows(row, delta, orientation); + + q_grid.resize(internalGridRowCount() * internalGridColumnCount()); + regenerateGrid(); +} + +void QGridLayoutEngine::fillRowData(QGridLayoutRowData *rowData, const QLayoutStyleInfo &styleInfo, + qreal *colPositions, qreal *colSizes, + Qt::Orientation orientation) const +{ + const int ButtonMask = QSizePolicy::ButtonBox | QSizePolicy::PushButton; + const QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; + const QGridLayoutRowInfo &columnInfo = q_infos[orientation == Qt::Horizontal]; + LayoutSide top = (orientation == Qt::Vertical) ? Top : Left; + LayoutSide bottom = (orientation == Qt::Vertical) ? Bottom : Right; + + QStyle *style = styleInfo.style(); + QStyleOption option; + option.initFrom(styleInfo.widget()); + + const QLayoutParameter &defaultSpacing = q_defaultSpacings[orientation == Qt::Vertical]; + qreal innerSpacing = 0.0; + if (style) + innerSpacing = (qreal)style->pixelMetric(orientation == Qt::Vertical ? QStyle::PM_LayoutVerticalSpacing + : QStyle::PM_LayoutHorizontalSpacing, + &option, styleInfo.widget()); + if (innerSpacing >= 0.0) + defaultSpacing.setCachedValue(innerSpacing); + + for (int row = 0; row < rowInfo.count; ++row) { + bool rowIsEmpty = true; + bool rowIsIdenticalToPrevious = (row > 0); + + for (int column = 0; column < columnInfo.count; ++column) { + QGridLayoutItem *item = itemAt(row, column, orientation); + + if (rowIsIdenticalToPrevious && item != itemAt(row - 1, column, orientation)) + rowIsIdenticalToPrevious = false; + + if (item) + rowIsEmpty = false; + } + + if ((rowIsEmpty || rowIsIdenticalToPrevious) + && rowInfo.spacings.value(row).isDefault() + && rowInfo.stretches.value(row).isDefault() + && rowInfo.boxes.value(row) == QGridLayoutBox()) + rowData->ignore.setBit(row, true); + + if (rowInfo.spacings.value(row).isUser()) { + rowData->spacings[row] = rowInfo.spacings.at(row).value(); + } else if (!defaultSpacing.isDefault()) { + rowData->spacings[row] = defaultSpacing.value(); + } + + rowData->stretches[row] = rowInfo.stretches.value(row).value(); + } + + struct RowAdHocData { + int q_row; + unsigned int q_hasButtons : 8; + unsigned int q_hasNonButtons : 8; + + inline RowAdHocData() : q_row(-1), q_hasButtons(false), q_hasNonButtons(false) {} + inline void init(int row) { + this->q_row = row; + q_hasButtons = false; + q_hasNonButtons = false; + } + inline bool hasOnlyButtons() const { return q_hasButtons && !q_hasNonButtons; } + inline bool hasOnlyNonButtons() const { return q_hasNonButtons && !q_hasButtons; } + }; + RowAdHocData lastRowAdHocData; + RowAdHocData nextToLastRowAdHocData; + RowAdHocData nextToNextToLastRowAdHocData; + + rowData->hasIgnoreFlag = false; + for (int row = 0; row < rowInfo.count; ++row) { + if (rowData->ignore.testBit(row)) + continue; + + QGridLayoutBox &rowBox = rowData->boxes[row]; + if (option.state & QStyle::State_Window) { + nextToNextToLastRowAdHocData = nextToLastRowAdHocData; + nextToLastRowAdHocData = lastRowAdHocData; + lastRowAdHocData.init(row); + } + + bool userRowStretch = rowInfo.stretches.value(row).isUser(); + int &rowStretch = rowData->stretches[row]; + + bool hasIgnoreFlag = true; + for (int column = 0; column < columnInfo.count; ++column) { + QGridLayoutItem *item = itemAt(row, column, orientation); + if (item) { + int itemRow = item->firstRow(orientation); + int itemColumn = item->firstColumn(orientation); + + if (itemRow == row && itemColumn == column) { + int itemStretch = item->stretchFactor(orientation); + if (!(item->sizePolicy(orientation) & QSizePolicy::IgnoreFlag)) + hasIgnoreFlag = false; + int itemRowSpan = item->rowSpan(orientation); + + int effectiveRowSpan = 1; + for (int i = 1; i < itemRowSpan; ++i) { + if (!rowData->ignore.testBit(i)) + ++effectiveRowSpan; + } + + QGridLayoutBox *box; + if (effectiveRowSpan == 1) { + box = &rowBox; + if (!userRowStretch && itemStretch != 0) + rowStretch = qMax(rowStretch, itemStretch); + } else { + QGridLayoutMultiCellData &multiCell = + rowData->multiCellMap[qMakePair(row, effectiveRowSpan)]; + box = &multiCell.q_box; + multiCell.q_stretch = itemStretch; + } + // Items with constraints need to be passed the constraint + if (colSizes && colPositions && item->hasDynamicConstraint() && orientation == item->dynamicConstraintOrientation()) { + /* Get the width of the item by summing up the widths of the columns that it spans. + * We need to have already calculated the widths of the columns by calling + * q_columns->calculateGeometries() before hand and passing the value in the colSizes + * and colPositions parameters. + * The variable name is still colSizes even when it actually has the row sizes + */ + qreal length = colSizes[item->lastColumn(orientation)]; + if (item->columnSpan(orientation) != 1) + length += colPositions[item->lastColumn(orientation)] - colPositions[item->firstColumn(orientation)]; + box->combine(item->box(orientation, length)); + } else { + box->combine(item->box(orientation)); + } + + if (effectiveRowSpan == 1) { + QSizePolicy::ControlTypes controls = item->controlTypes(top); + if (controls & ButtonMask) + lastRowAdHocData.q_hasButtons = true; + if (controls & ~ButtonMask) + lastRowAdHocData.q_hasNonButtons = true; + } + } + } + } + if (row < rowInfo.boxes.count()) { + QGridLayoutBox rowBoxInfo = rowInfo.boxes.at(row); + rowBoxInfo.normalize(); + rowBox.q_minimumSize = qMax(rowBox.q_minimumSize, rowBoxInfo.q_minimumSize); + rowBox.q_maximumSize = qMax(rowBox.q_minimumSize, + (rowBoxInfo.q_maximumSize != FLT_MAX ? + rowBoxInfo.q_maximumSize : rowBox.q_maximumSize)); + rowBox.q_preferredSize = qBound(rowBox.q_minimumSize, + qMax(rowBox.q_preferredSize, rowBoxInfo.q_preferredSize), + rowBox.q_maximumSize); + } + if (hasIgnoreFlag) + rowData->hasIgnoreFlag = true; + } + + /* + Heuristic: Detect button boxes that don't use QSizePolicy::ButtonBox. + This is somewhat ad hoc but it usually does the trick. + */ + bool lastRowIsButtonBox = (lastRowAdHocData.hasOnlyButtons() + && nextToLastRowAdHocData.hasOnlyNonButtons()); + bool lastTwoRowsIsButtonBox = (lastRowAdHocData.hasOnlyButtons() + && nextToLastRowAdHocData.hasOnlyButtons() + && nextToNextToLastRowAdHocData.hasOnlyNonButtons() + && orientation == Qt::Vertical); + + if (defaultSpacing.isDefault()) { + int prevRow = -1; + for (int row = 0; row < rowInfo.count; ++row) { + if (rowData->ignore.testBit(row)) + continue; + + if (prevRow != -1 && !rowInfo.spacings.value(prevRow).isUser()) { + qreal &rowSpacing = rowData->spacings[prevRow]; + for (int column = 0; column < columnInfo.count; ++column) { + QGridLayoutItem *item1 = itemAt(prevRow, column, orientation); + QGridLayoutItem *item2 = itemAt(row, column, orientation); + + if (item1 && item2 && item1 != item2) { + QSizePolicy::ControlTypes controls1 = item1->controlTypes(bottom); + QSizePolicy::ControlTypes controls2 = item2->controlTypes(top); + + if (controls2 & QSizePolicy::PushButton) { + if ((row == nextToLastRowAdHocData.q_row && lastTwoRowsIsButtonBox) + || (row == lastRowAdHocData.q_row && lastRowIsButtonBox)) { + controls2 &= ~QSizePolicy::PushButton; + controls2 |= QSizePolicy::ButtonBox; + } + } + + qreal spacing = style->combinedLayoutSpacing(controls1, controls2, + orientation, &option, + styleInfo.widget()); + if (orientation == Qt::Horizontal) { + qreal width1 = rowData->boxes.at(prevRow).q_minimumSize; + qreal width2 = rowData->boxes.at(row).q_minimumSize; + QRectF rect1 = item1->geometryWithin(0.0, 0.0, width1, FLT_MAX, -1.0); + QRectF rect2 = item2->geometryWithin(0.0, 0.0, width2, FLT_MAX, -1.0); + spacing -= (width1 - (rect1.x() + rect1.width())) + rect2.x(); + } else { + const QGridLayoutBox &box1 = rowData->boxes.at(prevRow); + const QGridLayoutBox &box2 = rowData->boxes.at(row); + qreal height1 = box1.q_minimumSize; + qreal height2 = box2.q_minimumSize; + qreal rowDescent1 = fixedDescent(box1.q_minimumDescent, + box1.q_minimumAscent, height1); + qreal rowDescent2 = fixedDescent(box2.q_minimumDescent, + box2.q_minimumAscent, height2); + QRectF rect1 = item1->geometryWithin(0.0, 0.0, FLT_MAX, height1, + rowDescent1); + QRectF rect2 = item2->geometryWithin(0.0, 0.0, FLT_MAX, height2, + rowDescent2); + spacing -= (height1 - (rect1.y() + rect1.height())) + rect2.y(); + } + rowSpacing = qMax(spacing, rowSpacing); + } + } + } + prevRow = row; + } + } else if (lastRowIsButtonBox || lastTwoRowsIsButtonBox) { + /* + Even for styles that define a uniform spacing, we cheat a + bit and use the window margin as the spacing. This + significantly improves the look of dialogs. + */ + int prevRow = lastRowIsButtonBox ? nextToLastRowAdHocData.q_row + : nextToNextToLastRowAdHocData.q_row; + if (!defaultSpacing.isUser() && !rowInfo.spacings.value(prevRow).isUser()) { + qreal windowMargin = style->pixelMetric(orientation == Qt::Vertical + ? QStyle::PM_LayoutBottomMargin + : QStyle::PM_LayoutRightMargin, + &option, styleInfo.widget()); + + qreal &rowSpacing = rowData->spacings[prevRow]; + rowSpacing = qMax(windowMargin, rowSpacing); + } + } +} + +void QGridLayoutEngine::ensureEffectiveFirstAndLastRows() const +{ + if (q_cachedEffectiveFirstRows[Hor] == -1 && !q_items.isEmpty()) { + int rowCount = this->rowCount(); + int columnCount = this->columnCount(); + + q_cachedEffectiveFirstRows[Ver] = rowCount; + q_cachedEffectiveFirstRows[Hor] = columnCount; + q_cachedEffectiveLastRows[Ver] = -1; + q_cachedEffectiveLastRows[Hor] = -1; + + for (int i = q_items.count() - 1; i >= 0; --i) { + const QGridLayoutItem *item = q_items.at(i); + + for (int j = 0; j < NOrientations; ++j) { + Qt::Orientation orientation = (j == Hor) ? Qt::Horizontal : Qt::Vertical; + if (item->firstRow(orientation) < q_cachedEffectiveFirstRows[j]) + q_cachedEffectiveFirstRows[j] = item->firstRow(orientation); + if (item->lastRow(orientation) > q_cachedEffectiveLastRows[j]) + q_cachedEffectiveLastRows[j] = item->lastRow(orientation); + } + } + } +} + +void QGridLayoutEngine::ensureColumnAndRowData(QGridLayoutRowData *rowData, QGridLayoutBox *totalBox, + const QLayoutStyleInfo &styleInfo, + qreal *colPositions, qreal *colSizes, + Qt::Orientation orientation) const +{ + rowData->reset(rowCount(orientation)); + fillRowData(rowData, styleInfo, colPositions, colSizes, orientation); + const QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; + rowData->distributeMultiCells(rowInfo); + *totalBox = rowData->totalBox(0, rowCount(orientation)); + //We have items whose width depends on their height +} + +/** + returns false if the layout has contradicting constraints (i.e. some items with a horizontal + constraint and other items with a vertical constraint) + */ +bool QGridLayoutEngine::ensureDynamicConstraint() const +{ + if (q_cachedConstraintOrientation == UnknownConstraint) { + for (int i = q_items.count() - 1; i >= 0; --i) { + QGridLayoutItem *item = q_items.at(i); + if (item->hasDynamicConstraint()) { + Qt::Orientation itemConstraintOrientation = item->dynamicConstraintOrientation(); + if (q_cachedConstraintOrientation == UnknownConstraint) { + q_cachedConstraintOrientation = itemConstraintOrientation; + } else if (q_cachedConstraintOrientation != itemConstraintOrientation) { + q_cachedConstraintOrientation = UnfeasibleConstraint; + qWarning("QGridLayoutEngine: Unfeasible, cannot mix horizontal and" + " vertical constraint in the same layout"); + return false; + } + } + } + if (q_cachedConstraintOrientation == UnknownConstraint) + q_cachedConstraintOrientation = NoConstraint; + } + return true; +} + +bool QGridLayoutEngine::hasDynamicConstraint() const +{ + if (!ensureDynamicConstraint()) + return false; + return q_cachedConstraintOrientation != NoConstraint; +} + +/* + * return value is only valid if hasConstraint() returns true + */ +Qt::Orientation QGridLayoutEngine::constraintOrientation() const +{ + (void)ensureDynamicConstraint(); + return (Qt::Orientation)q_cachedConstraintOrientation; +} + +void QGridLayoutEngine::ensureGeometries(const QLayoutStyleInfo &styleInfo, + const QSizeF &size) const +{ + if (q_cachedDataForStyleInfo == styleInfo && q_cachedSize == size) + return; + + q_cachedDataForStyleInfo = styleInfo; + q_cachedSize = size; + + q_xx.resize(columnCount()); + q_widths.resize(columnCount()); + q_yy.resize(rowCount()); + q_heights.resize(rowCount()); + q_descents.resize(rowCount()); + + if (constraintOrientation() != Qt::Horizontal) { + //We might have items whose width depends on their height + ensureColumnAndRowData(&q_columnData, &q_totalBoxes[Hor], styleInfo, NULL, NULL, Qt::Horizontal); + //Calculate column widths and positions, and put results in q_xx.data() and q_widths.data() so that we can use this information as + //constraints to find the row heights + q_columnData.calculateGeometries(0, columnCount(), size.width(), q_xx.data(), q_widths.data(), + 0, q_totalBoxes[Hor], q_infos[Hor] ); + ensureColumnAndRowData(&q_rowData, &q_totalBoxes[Ver], styleInfo, q_xx.data(), q_widths.data(), Qt::Vertical); + //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() + q_rowData.calculateGeometries(0, rowCount(), size.height(), q_yy.data(), q_heights.data(), + q_descents.data(), q_totalBoxes[Ver], q_infos[Ver]); + } else { + //We have items whose height depends on their width + ensureColumnAndRowData(&q_rowData, &q_totalBoxes[Ver], styleInfo, NULL, NULL, Qt::Vertical); + //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() so that we can use this information as + //constraints to find the column widths + q_rowData.calculateGeometries(0, rowCount(), size.height(), q_yy.data(), q_heights.data(), + q_descents.data(), q_totalBoxes[Ver], q_infos[Ver]); + ensureColumnAndRowData(&q_columnData, &q_totalBoxes[Hor], styleInfo, q_yy.data(), q_heights.data(), Qt::Horizontal); + //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() + q_columnData.calculateGeometries(0, columnCount(), size.width(), q_xx.data(), q_widths.data(), + 0, q_totalBoxes[Hor], q_infos[Hor]); + } +} + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/widgets/graphicsview/qgridlayoutengine_p.h b/src/widgets/graphicsview/qgridlayoutengine_p.h new file mode 100644 index 0000000000..c5b35f59e8 --- /dev/null +++ b/src/widgets/graphicsview/qgridlayoutengine_p.h @@ -0,0 +1,458 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QGRIDLAYOUTENGINE_P_H +#define QGRIDLAYOUTENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the graphics view layout classes. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qalgorithms.h" +#include "qbitarray.h" +#include "qlist.h" +#include "qmap.h" +#include "qpair.h" +#include "qvector.h" +#include "qgraphicslayout_p.h" +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsLayoutItem; +class QStyle; +class QWidget; + +// ### deal with Descent in a similar way +enum { + MinimumSize = Qt::MinimumSize, + PreferredSize = Qt::PreferredSize, + MaximumSize = Qt::MaximumSize, + NSizes +}; + +// do not reorder +enum { + Hor, + Ver, + NOrientations +}; + +// do not reorder +enum LayoutSide { + Left, + Top, + Right, + Bottom +}; + +enum { + NoConstraint, + HorizontalConstraint, // Width depends on the height + VerticalConstraint, // Height depends on the width + UnknownConstraint, // need to update cache + UnfeasibleConstraint // not feasible, it be has some items with Vertical and others with Horizontal constraints +}; + +template +class QLayoutParameter +{ +public: + enum State { Default, User, Cached }; + + inline QLayoutParameter() : q_value(T()), q_state(Default) {} + inline QLayoutParameter(T value, State state = Default) : q_value(value), q_state(state) {} + + inline void setUserValue(T value) { + q_value = value; + q_state = User; + } + inline void setCachedValue(T value) const { + if (q_state != User) { + q_value = value; + q_state = Cached; + } + } + inline T value() const { return q_value; } + inline T value(T defaultValue) const { return isUser() ? q_value : defaultValue; } + inline bool isDefault() const { return q_state == Default; } + inline bool isUser() const { return q_state == User; } + inline bool isCached() const { return q_state == Cached; } + +private: + mutable T q_value; + mutable State q_state; +}; + +class QStretchParameter : public QLayoutParameter +{ +public: + QStretchParameter() : QLayoutParameter(-1) {} + +}; + +class QGridLayoutBox +{ +public: + inline QGridLayoutBox() + : q_minimumSize(0), q_preferredSize(0), q_maximumSize(FLT_MAX), + q_minimumDescent(-1), q_minimumAscent(-1) {} + + void add(const QGridLayoutBox &other, int stretch, qreal spacing); + void combine(const QGridLayoutBox &other); + void normalize(); + +#ifdef QT_DEBUG + void dump(int indent = 0) const; +#endif + // This code could use the union-struct-array trick, but a compiler + // bug prevents this from working. + qreal q_minimumSize; + qreal q_preferredSize; + qreal q_maximumSize; + qreal q_minimumDescent; + qreal q_minimumAscent; + inline qreal &q_sizes(int which) + { + qreal *t; + switch (which) { + case Qt::MinimumSize: + t = &q_minimumSize; + break; + case Qt::PreferredSize: + t = &q_preferredSize; + break; + case Qt::MaximumSize: + t = &q_maximumSize; + break; + case Qt::MinimumDescent: + t = &q_minimumDescent; + break; + case (Qt::MinimumDescent + 1): + t = &q_minimumAscent; + break; + default: + t = 0; + break; + } + return *t; + } + inline const qreal &q_sizes(int which) const + { + const qreal *t; + switch (which) { + case Qt::MinimumSize: + t = &q_minimumSize; + break; + case Qt::PreferredSize: + t = &q_preferredSize; + break; + case Qt::MaximumSize: + t = &q_maximumSize; + break; + case Qt::MinimumDescent: + t = &q_minimumDescent; + break; + case (Qt::MinimumDescent + 1): + t = &q_minimumAscent; + break; + default: + t = 0; + break; + } + return *t; + } +}; + +bool operator==(const QGridLayoutBox &box1, const QGridLayoutBox &box2); +inline bool operator!=(const QGridLayoutBox &box1, const QGridLayoutBox &box2) + { return !operator==(box1, box2); } + +class QGridLayoutMultiCellData +{ +public: + inline QGridLayoutMultiCellData() : q_stretch(-1) {} + + QGridLayoutBox q_box; + int q_stretch; +}; + +typedef QMap, QGridLayoutMultiCellData> MultiCellMap; + +class QGridLayoutRowInfo; + +class QGridLayoutRowData +{ +public: + void reset(int count); + void distributeMultiCells(const QGridLayoutRowInfo &rowInfo); + void calculateGeometries(int start, int end, qreal targetSize, qreal *positions, qreal *sizes, + qreal *descents, const QGridLayoutBox &totalBox, + const QGridLayoutRowInfo &rowInfo); + QGridLayoutBox totalBox(int start, int end) const; + void stealBox(int start, int end, int which, qreal *positions, qreal *sizes); + +#ifdef QT_DEBUG + void dump(int indent = 0) const; +#endif + + QBitArray ignore; // ### rename q_ + QVector boxes; + MultiCellMap multiCellMap; + QVector stretches; + QVector spacings; + bool hasIgnoreFlag; +}; + +class QGridLayoutEngine; + +class QGridLayoutItem +{ +public: + QGridLayoutItem(QGridLayoutEngine *engine, QGraphicsLayoutItem *layoutItem, int row, int column, + int rowSpan = 1, int columnSpan = 1, Qt::Alignment alignment = 0, + int itemAtIndex = -1); + + inline int firstRow() const { return q_firstRows[Ver]; } + inline int firstColumn() const { return q_firstRows[Hor]; } + inline int rowSpan() const { return q_rowSpans[Ver]; } + inline int columnSpan() const { return q_rowSpans[Hor]; } + inline int lastRow() const { return firstRow() + rowSpan() - 1; } + inline int lastColumn() const { return firstColumn() + columnSpan() - 1; } + + int firstRow(Qt::Orientation orientation) const; + int firstColumn(Qt::Orientation orientation) const; + int lastRow(Qt::Orientation orientation) const; + int lastColumn(Qt::Orientation orientation) const; + int rowSpan(Qt::Orientation orientation) const; + int columnSpan(Qt::Orientation orientation) const; + void setFirstRow(int row, Qt::Orientation orientation = Qt::Vertical); + void setRowSpan(int rowSpan, Qt::Orientation orientation = Qt::Vertical); + + int stretchFactor(Qt::Orientation orientation) const; + void setStretchFactor(int stretch, Qt::Orientation orientation); + + inline Qt::Alignment alignment() const { return q_alignment; } + inline void setAlignment(Qt::Alignment alignment) { q_alignment = alignment; } + + QSizePolicy::Policy sizePolicy(Qt::Orientation orientation) const; + + bool hasDynamicConstraint() const; + Qt::Orientation dynamicConstraintOrientation() const; + + QSizePolicy::ControlTypes controlTypes(LayoutSide side) const; + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + QGridLayoutBox box(Qt::Orientation orientation, qreal constraint = -1.0) const; + QRectF geometryWithin(qreal x, qreal y, qreal width, qreal height, qreal rowDescent) const; + + QGraphicsLayoutItem *layoutItem() const { return q_layoutItem; } + + void setGeometry(const QRectF &rect); + void transpose(); + void insertOrRemoveRows(int row, int delta, Qt::Orientation orientation = Qt::Vertical); + QSizeF effectiveMaxSize(const QSizeF &constraint) const; + +#ifdef QT_DEBUG + void dump(int indent = 0) const; +#endif + +private: + QGridLayoutEngine *q_engine; // ### needed? + QGraphicsLayoutItem *q_layoutItem; + int q_firstRows[NOrientations]; + int q_rowSpans[NOrientations]; + int q_stretches[NOrientations]; + Qt::Alignment q_alignment; +}; + +class QGridLayoutRowInfo +{ +public: + inline QGridLayoutRowInfo() : count(0) {} + + void insertOrRemoveRows(int row, int delta); + +#ifdef QT_DEBUG + void dump(int indent = 0) const; +#endif + + int count; + QVector stretches; + QVector > spacings; + QVector alignments; + QVector boxes; +}; + +class QGridLayoutEngine +{ +public: + QGridLayoutEngine(); + inline ~QGridLayoutEngine() { qDeleteAll(q_items); } + + int rowCount(Qt::Orientation orientation) const; + int columnCount(Qt::Orientation orientation) const; + inline int rowCount() const { return q_infos[Ver].count; } + inline int columnCount() const { return q_infos[Hor].count; } + // returns the number of items inserted, which may be less than (rowCount * columnCount) + int itemCount() const; + QGridLayoutItem *itemAt(int index) const; + int indexOf(QGraphicsLayoutItem *item) const; + + int effectiveFirstRow(Qt::Orientation orientation = Qt::Vertical) const; + int effectiveLastRow(Qt::Orientation orientation = Qt::Vertical) const; + + void setSpacing(qreal spacing, Qt::Orientations orientations); + qreal spacing(const QLayoutStyleInfo &styleInfo, Qt::Orientation orientation) const; + // ### setSpacingAfterRow(), spacingAfterRow() + void setRowSpacing(int row, qreal spacing, Qt::Orientation orientation = Qt::Vertical); + qreal rowSpacing(int row, Qt::Orientation orientation = Qt::Vertical) const; + + void setRowStretchFactor(int row, int stretch, Qt::Orientation orientation = Qt::Vertical); + int rowStretchFactor(int row, Qt::Orientation orientation = Qt::Vertical) const; + + void setStretchFactor(QGraphicsLayoutItem *layoutItem, int stretch, + Qt::Orientation orientation); + int stretchFactor(QGraphicsLayoutItem *layoutItem, Qt::Orientation orientation) const; + + void setRowSizeHint(Qt::SizeHint which, int row, qreal size, + Qt::Orientation orientation = Qt::Vertical); + qreal rowSizeHint(Qt::SizeHint which, int row, + Qt::Orientation orientation = Qt::Vertical) const; + + void setRowAlignment(int row, Qt::Alignment alignment, Qt::Orientation orientation); + Qt::Alignment rowAlignment(int row, Qt::Orientation orientation) const; + + void setAlignment(QGraphicsLayoutItem *layoutItem, Qt::Alignment alignment); + Qt::Alignment alignment(QGraphicsLayoutItem *layoutItem) const; + Qt::Alignment effectiveAlignment(const QGridLayoutItem *layoutItem) const; + + + void insertItem(QGridLayoutItem *item, int index); + void addItem(QGridLayoutItem *item); + void removeItem(QGridLayoutItem *item); + QGridLayoutItem *findLayoutItem(QGraphicsLayoutItem *layoutItem) const; + QGridLayoutItem *itemAt(int row, int column, Qt::Orientation orientation = Qt::Vertical) const; + inline void insertRow(int row, Qt::Orientation orientation = Qt::Vertical) + { insertOrRemoveRows(row, +1, orientation); } + inline void removeRows(int row, int count, Qt::Orientation orientation) + { insertOrRemoveRows(row, -count, orientation); } + + void invalidate(); + void setGeometries(const QLayoutStyleInfo &styleInfo, const QRectF &contentsGeometry); + QRectF cellRect(const QLayoutStyleInfo &styleInfo, const QRectF &contentsGeometry, int row, + int column, int rowSpan, int columnSpan) const; + QSizeF sizeHint(const QLayoutStyleInfo &styleInfo, Qt::SizeHint which, + const QSizeF &constraint) const; + + // heightForWidth / widthForHeight support + QSizeF dynamicallyConstrainedSizeHint(Qt::SizeHint which, const QSizeF &constraint) const; + bool ensureDynamicConstraint() const; + bool hasDynamicConstraint() const; + Qt::Orientation constraintOrientation() const; + + + QSizePolicy::ControlTypes controlTypes(LayoutSide side) const; + void transpose(); + void setVisualDirection(Qt::LayoutDirection direction); + Qt::LayoutDirection visualDirection() const; +#ifdef QT_DEBUG + void dump(int indent = 0) const; +#endif + +private: + static int grossRoundUp(int n) { return ((n + 2) | 0x3) - 2; } + + void maybeExpandGrid(int row, int column, Qt::Orientation orientation = Qt::Vertical); + void regenerateGrid(); + inline int internalGridRowCount() const { return grossRoundUp(rowCount()); } + inline int internalGridColumnCount() const { return grossRoundUp(columnCount()); } + void setItemAt(int row, int column, QGridLayoutItem *item); + void insertOrRemoveRows(int row, int delta, Qt::Orientation orientation = Qt::Vertical); + void fillRowData(QGridLayoutRowData *rowData, const QLayoutStyleInfo &styleInfo, + qreal *colPositions, qreal *colSizes, + Qt::Orientation orientation = Qt::Vertical) const; + void ensureEffectiveFirstAndLastRows() const; + void ensureColumnAndRowData(QGridLayoutRowData *rowData, QGridLayoutBox *totalBox, + const QLayoutStyleInfo &styleInfo, + qreal *colPositions, qreal *colSizes, + Qt::Orientation orientation) const; + + void ensureGeometries(const QLayoutStyleInfo &styleInfo, const QSizeF &size) const; + + // User input + QVector q_grid; + QList q_items; + QLayoutParameter q_defaultSpacings[NOrientations]; + QGridLayoutRowInfo q_infos[NOrientations]; + Qt::LayoutDirection m_visualDirection; + + // Lazily computed from the above user input + mutable int q_cachedEffectiveFirstRows[NOrientations]; + mutable int q_cachedEffectiveLastRows[NOrientations]; + mutable quint8 q_cachedConstraintOrientation : 3; + + // Layout item input + mutable QLayoutStyleInfo q_cachedDataForStyleInfo; + mutable QGridLayoutRowData q_columnData; + mutable QGridLayoutRowData q_rowData; + mutable QGridLayoutBox q_totalBoxes[NOrientations]; + + // Output + mutable QSizeF q_cachedSize; + mutable QVector q_xx; + mutable QVector q_yy; + mutable QVector q_widths; + mutable QVector q_heights; + mutable QVector q_descents; + + friend class QGridLayoutItem; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/widgets/graphicsview/qsimplex_p.cpp b/src/widgets/graphicsview/qsimplex_p.cpp new file mode 100644 index 0000000000..d2d9646b90 --- /dev/null +++ b/src/widgets/graphicsview/qsimplex_p.cpp @@ -0,0 +1,673 @@ +/**************************************************************************** +** +** 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 QtGui module 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 "qsimplex_p.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QSimplex + + The QSimplex class is a Linear Programming problem solver based on the two-phase + simplex method. + + It takes a set of QSimplexConstraints as its restrictive constraints and an + additional QSimplexConstraint as its objective function. Then methods to maximize + and minimize the problem solution are provided. + + The two-phase simplex method is based on the following steps: + First phase: + 1.a) Modify the original, complex, and possibly not feasible problem, into a new, + easy to solve problem. + 1.b) Set as the objective of the new problem, a feasible solution for the original + complex problem. + 1.c) Run simplex to optimize the modified problem and check whether a solution for + the original problem exists. + + Second phase: + 2.a) Go back to the original problem with the feasibl (but not optimal) solution + found in the first phase. + 2.b) Set the original objective. + 3.c) Run simplex to optimize the original problem towards its optimal solution. +*/ + +/*! + \internal +*/ +QSimplex::QSimplex() : objective(0), rows(0), columns(0), firstArtificial(0), matrix(0) +{ +} + +/*! + \internal +*/ +QSimplex::~QSimplex() +{ + clearDataStructures(); +} + +/*! + \internal +*/ +void QSimplex::clearDataStructures() +{ + if (matrix == 0) + return; + + // Matrix + rows = 0; + columns = 0; + firstArtificial = 0; + free(matrix); + matrix = 0; + + // Constraints + for (int i = 0; i < constraints.size(); ++i) { + delete constraints[i]->helper.first; + delete constraints[i]->artificial; + delete constraints[i]; + } + constraints.clear(); + + // Other + variables.clear(); + objective = 0; +} + +/*! + \internal + Sets the new constraints in the simplex solver and returns whether the problem + is feasible. + + This method sets the new constraints, normalizes them, creates the simplex matrix + and runs the first simplex phase. +*/ +bool QSimplex::setConstraints(const QList newConstraints) +{ + //////////////////////////// + // Reset to initial state // + //////////////////////////// + clearDataStructures(); + + if (newConstraints.isEmpty()) + return true; // we are ok with no constraints + + // Make deep copy of constraints. We need this copy because we may change + // them in the simplification method. + for (int i = 0; i < newConstraints.size(); ++i) { + QSimplexConstraint *c = new QSimplexConstraint; + c->constant = newConstraints[i]->constant; + c->ratio = newConstraints[i]->ratio; + c->variables = newConstraints[i]->variables; + constraints << c; + } + + // Remove constraints of type Var == K and replace them for their value. + if (!simplifyConstraints(&constraints)) { + qWarning() << "QSimplex: No feasible solution!"; + clearDataStructures(); + return false; + } + + /////////////////////////////////////// + // Prepare variables and constraints // + /////////////////////////////////////// + + // Set Variables direct mapping. + // "variables" is a list that provides a stable, indexed list of all variables + // used in this problem. + QSet variablesSet; + for (int i = 0; i < constraints.size(); ++i) + variablesSet += \ + QSet::fromList(constraints[i]->variables.keys()); + variables = variablesSet.toList(); + + // Set Variables reverse mapping + // We also need to be able to find the index for a given variable, to do that + // we store in each variable its index. + for (int i = 0; i < variables.size(); ++i) { + // The variable "0" goes at the column "1", etc... + variables[i]->index = i + 1; + } + + // Normalize Constraints + // In this step, we prepare the constraints in two ways: + // Firstly, we modify all constraints of type "LessOrEqual" or "MoreOrEqual" + // by the adding slack or surplus variables and making them "Equal" constraints. + // Secondly, we need every single constraint to have a direct, easy feasible + // solution. Constraints that have slack variables are already easy to solve, + // to all the others we add artificial variables. + // + // At the end we modify the constraints as follows: + // - LessOrEqual: SLACK variable is added. + // - Equal: ARTIFICIAL variable is added. + // - More or Equal: ARTIFICIAL and SURPLUS variables are added. + int variableIndex = variables.size(); + QList artificialList; + + for (int i = 0; i < constraints.size(); ++i) { + QSimplexVariable *slack; + QSimplexVariable *surplus; + QSimplexVariable *artificial; + + Q_ASSERT(constraints[i]->helper.first == 0); + Q_ASSERT(constraints[i]->artificial == 0); + + switch(constraints[i]->ratio) { + case QSimplexConstraint::LessOrEqual: + slack = new QSimplexVariable; + slack->index = ++variableIndex; + constraints[i]->helper.first = slack; + constraints[i]->helper.second = 1.0; + break; + case QSimplexConstraint::MoreOrEqual: + surplus = new QSimplexVariable; + surplus->index = ++variableIndex; + constraints[i]->helper.first = surplus; + constraints[i]->helper.second = -1.0; + // fall through + case QSimplexConstraint::Equal: + artificial = new QSimplexVariable; + constraints[i]->artificial = artificial; + artificialList += constraints[i]->artificial; + break; + } + } + + // All original, slack and surplus have already had its index set + // at this point. We now set the index of the artificial variables + // as to ensure they are at the end of the variable list and therefore + // can be easily removed at the end of this method. + firstArtificial = variableIndex + 1; + for (int i = 0; i < artificialList.size(); ++i) + artificialList[i]->index = ++variableIndex; + artificialList.clear(); + + ///////////////////////////// + // Fill the Simplex matrix // + ///////////////////////////// + + // One for each variable plus the Basic and BFS columns (first and last) + columns = variableIndex + 2; + // One for each constraint plus the objective function + rows = constraints.size() + 1; + + matrix = (qreal *)malloc(sizeof(qreal) * columns * rows); + if (!matrix) { + qWarning() << "QSimplex: Unable to allocate memory!"; + return false; + } + for (int i = columns * rows - 1; i >= 0; --i) + matrix[i] = 0.0; + + // Fill Matrix + for (int i = 1; i <= constraints.size(); ++i) { + QSimplexConstraint *c = constraints[i - 1]; + + if (c->artificial) { + // Will use artificial basic variable + setValueAt(i, 0, c->artificial->index); + setValueAt(i, c->artificial->index, 1.0); + + if (c->helper.second != 0.0) { + // Surplus variable + setValueAt(i, c->helper.first->index, c->helper.second); + } + } else { + // Slack is used as the basic variable + Q_ASSERT(c->helper.second == 1.0); + setValueAt(i, 0, c->helper.first->index); + setValueAt(i, c->helper.first->index, 1.0); + } + + QHash::const_iterator iter; + for (iter = c->variables.constBegin(); + iter != c->variables.constEnd(); + ++iter) { + setValueAt(i, iter.key()->index, iter.value()); + } + + setValueAt(i, columns - 1, c->constant); + } + + // Set objective for the first-phase Simplex. + // Z = -1 * sum_of_artificial_vars + for (int j = firstArtificial; j < columns - 1; ++j) + setValueAt(0, j, 1.0); + + // Maximize our objective (artificial vars go to zero) + solveMaxHelper(); + + // If there is a solution where the sum of all artificial + // variables is zero, then all of them can be removed and yet + // we will have a feasible (but not optimal) solution for the + // original problem. + // Otherwise, we clean up our structures and report there is + // no feasible solution. + if ((valueAt(0, columns - 1) != 0.0) && (qAbs(valueAt(0, columns - 1)) > 0.00001)) { + qWarning() << "QSimplex: No feasible solution!"; + clearDataStructures(); + return false; + } + + // Remove artificial variables. We already have a feasible + // solution for the first problem, thus we don't need them + // anymore. + clearColumns(firstArtificial, columns - 2); + + return true; +} + +/*! + \internal + + Run simplex on the current matrix with the current objective. + + This is the iterative method. The matrix lines are combined + as to modify the variable values towards the best solution possible. + The method returns when the matrix is in the optimal state. +*/ +void QSimplex::solveMaxHelper() +{ + reducedRowEchelon(); + while (iterate()) ; +} + +/*! + \internal +*/ +void QSimplex::setObjective(QSimplexConstraint *newObjective) +{ + objective = newObjective; +} + +/*! + \internal +*/ +void QSimplex::clearRow(int rowIndex) +{ + qreal *item = matrix + rowIndex * columns; + for (int i = 0; i < columns; ++i) + item[i] = 0.0; +} + +/*! + \internal +*/ +void QSimplex::clearColumns(int first, int last) +{ + for (int i = 0; i < rows; ++i) { + qreal *row = matrix + i * columns; + for (int j = first; j <= last; ++j) + row[j] = 0.0; + } +} + +/*! + \internal +*/ +void QSimplex::dumpMatrix() +{ + qDebug("---- Simplex Matrix ----\n"); + + QString str(QLatin1String(" ")); + for (int j = 0; j < columns; ++j) + str += QString::fromAscii(" <%1 >").arg(j, 2); + qDebug("%s", qPrintable(str)); + for (int i = 0; i < rows; ++i) { + str = QString::fromAscii("Row %1:").arg(i, 2); + + qreal *row = matrix + i * columns; + for (int j = 0; j < columns; ++j) + str += QString::fromAscii("%1").arg(row[j], 7, 'f', 2); + qDebug("%s", qPrintable(str)); + } + qDebug("------------------------\n"); +} + +/*! + \internal +*/ +void QSimplex::combineRows(int toIndex, int fromIndex, qreal factor) +{ + if (!factor) + return; + + qreal *from = matrix + fromIndex * columns; + qreal *to = matrix + toIndex * columns; + + for (int j = 1; j < columns; ++j) { + qreal value = from[j]; + + // skip to[j] = to[j] + factor*0.0 + if (value == 0.0) + continue; + + to[j] += factor * value; + + // ### Avoid Numerical errors + if (qAbs(to[j]) < 0.0000000001) + to[j] = 0.0; + } +} + +/*! + \internal +*/ +int QSimplex::findPivotColumn() +{ + qreal min = 0; + int minIndex = -1; + + for (int j = 0; j < columns-1; ++j) { + if (valueAt(0, j) < min) { + min = valueAt(0, j); + minIndex = j; + } + } + + return minIndex; +} + +/*! + \internal + + For a given pivot column, find the pivot row. That is, the row with the + minimum associated "quotient" where: + + - quotient is the division of the value in the last column by the value + in the pivot column. + - rows with value less or equal to zero are ignored + - if two rows have the same quotient, lines are chosen based on the + highest variable index (value in the first column) + + The last condition avoids a bug where artificial variables would be + left behind for the second-phase simplex, and with 'good' + constraints would be removed before it, what would lead to incorrect + results. +*/ +int QSimplex::pivotRowForColumn(int column) +{ + qreal min = qreal(999999999999.0); // ### + int minIndex = -1; + + for (int i = 1; i < rows; ++i) { + qreal divisor = valueAt(i, column); + if (divisor <= 0) + continue; + + qreal quotient = valueAt(i, columns - 1) / divisor; + if (quotient < min) { + min = quotient; + minIndex = i; + } else if ((quotient == min) && (valueAt(i, 0) > valueAt(minIndex, 0))) { + minIndex = i; + } + } + + return minIndex; +} + +/*! + \internal +*/ +void QSimplex::reducedRowEchelon() +{ + for (int i = 1; i < rows; ++i) { + int factorInObjectiveRow = valueAt(i, 0); + combineRows(0, i, -1 * valueAt(0, factorInObjectiveRow)); + } +} + +/*! + \internal + + Does one iteration towards a better solution for the problem. + See 'solveMaxHelper'. +*/ +bool QSimplex::iterate() +{ + // Find Pivot column + int pivotColumn = findPivotColumn(); + if (pivotColumn == -1) + return false; + + // Find Pivot row for column + int pivotRow = pivotRowForColumn(pivotColumn); + if (pivotRow == -1) { + qWarning() << "QSimplex: Unbounded problem!"; + return false; + } + + // Normalize Pivot Row + qreal pivot = valueAt(pivotRow, pivotColumn); + if (pivot != 1.0) + combineRows(pivotRow, pivotRow, (1.0 - pivot) / pivot); + + // Update other rows + for (int row=0; row < rows; ++row) { + if (row == pivotRow) + continue; + + combineRows(row, pivotRow, -1 * valueAt(row, pivotColumn)); + } + + // Update first column + setValueAt(pivotRow, 0, pivotColumn); + + // dumpMatrix(); + // qDebug("------------ end of iteration --------------\n"); + return true; +} + +/*! + \internal + + Both solveMin and solveMax are interfaces to this method. + + The enum solverFactor admits 2 values: Minimum (-1) and Maximum (+1). + + This method sets the original objective and runs the second phase + Simplex to obtain the optimal solution for the problem. As the internal + simplex solver is only able to _maximize_ objectives, we handle the + minimization case by inverting the original objective and then + maximizing it. +*/ +qreal QSimplex::solver(solverFactor factor) +{ + // Remove old objective + clearRow(0); + + // Set new objective in the first row of the simplex matrix + qreal resultOffset = 0; + QHash::const_iterator iter; + for (iter = objective->variables.constBegin(); + iter != objective->variables.constEnd(); + ++iter) { + + // Check if the variable was removed in the simplification process. + // If so, we save its offset to the objective function and skip adding + // it to the matrix. + if (iter.key()->index == -1) { + resultOffset += iter.value() * iter.key()->result; + continue; + } + + setValueAt(0, iter.key()->index, -1 * factor * iter.value()); + } + + solveMaxHelper(); + collectResults(); + +#ifdef QT_DEBUG + for (int i = 0; i < constraints.size(); ++i) { + Q_ASSERT(constraints[i]->isSatisfied()); + } +#endif + + // Return the value calculated by the simplex plus the value of the + // fixed variables. + return (factor * valueAt(0, columns - 1)) + resultOffset; +} + +/*! + \internal + Minimize the original objective. +*/ +qreal QSimplex::solveMin() +{ + return solver(Minimum); +} + +/*! + \internal + Maximize the original objective. +*/ +qreal QSimplex::solveMax() +{ + return solver(Maximum); +} + +/*! + \internal + + Reads results from the simplified matrix and saves them in the + "result" member of each QSimplexVariable. +*/ +void QSimplex::collectResults() +{ + // All variables are zero unless overridden below. + + // ### Is this really needed? Is there any chance that an + // important variable remains as non-basic at the end of simplex? + for (int i = 0; i < variables.size(); ++i) + variables[i]->result = 0; + + // Basic variables + // Update the variable indicated in the first column with the value + // in the last column. + for (int i = 1; i < rows; ++i) { + int index = valueAt(i, 0) - 1; + if (index < variables.size()) + variables[index]->result = valueAt(i, columns - 1); + } +} + +/*! + \internal + + Looks for single-valued variables and remove them from the constraints list. +*/ +bool QSimplex::simplifyConstraints(QList *constraints) +{ + QHash results; // List of single-valued variables + bool modified = true; // Any chance more optimization exists? + + while (modified) { + modified = false; + + // For all constraints + QList::iterator iter = constraints->begin(); + while (iter != constraints->end()) { + QSimplexConstraint *c = *iter; + if ((c->ratio == QSimplexConstraint::Equal) && (c->variables.count() == 1)) { + // Check whether this is a constraint of type Var == K + // If so, save its value to "results". + QSimplexVariable *variable = c->variables.constBegin().key(); + qreal result = c->constant / c->variables.value(variable); + + results.insert(variable, result); + variable->result = result; + variable->index = -1; + modified = true; + + } + + // Replace known values among their variables + QHash::const_iterator r; + for (r = results.constBegin(); r != results.constEnd(); ++r) { + if (c->variables.contains(r.key())) { + c->constant -= r.value() * c->variables.take(r.key()); + modified = true; + } + } + + // Keep it normalized + if (c->constant < 0) + c->invert(); + + if (c->variables.isEmpty()) { + // If constraint became empty due to substitution, delete it. + if (c->isSatisfied() == false) + // We must ensure that the constraint soon to be deleted would not + // make the problem unfeasible if left behind. If that's the case, + // we return false so the simplex solver can properly report that. + return false; + + delete c; + iter = constraints->erase(iter); + } else { + ++iter; + } + } + } + + return true; +} + +void QSimplexConstraint::invert() +{ + constant = -constant; + ratio = Ratio(2 - ratio); + + QHash::iterator iter; + for (iter = variables.begin(); iter != variables.end(); ++iter) { + iter.value() = -iter.value(); + } +} + +QT_END_NAMESPACE diff --git a/src/widgets/graphicsview/qsimplex_p.h b/src/widgets/graphicsview/qsimplex_p.h new file mode 100644 index 0000000000..e6eced311e --- /dev/null +++ b/src/widgets/graphicsview/qsimplex_p.h @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** 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 QtGui module 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$ +** +****************************************************************************/ + +#ifndef QSIMPLEX_P_H +#define QSIMPLEX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +struct QSimplexVariable +{ + QSimplexVariable() : result(0), index(0) {} + + qreal result; + int index; +}; + + +/*! + \internal + + Representation of a LP constraint like: + + (c1 * X1) + (c2 * X2) + ... = K + or <= K + or >= K + + Where (ci, Xi) are the pairs in "variables" and K the real "constant". +*/ +struct QSimplexConstraint +{ + QSimplexConstraint() : constant(0), ratio(Equal), artificial(0) {} + + enum Ratio { + LessOrEqual = 0, + Equal, + MoreOrEqual + }; + + QHash variables; + qreal constant; + Ratio ratio; + + QPair helper; + QSimplexVariable * artificial; + + void invert(); + + bool isSatisfied() { + qreal leftHandSide(0); + + QHash::const_iterator iter; + for (iter = variables.constBegin(); iter != variables.constEnd(); ++iter) { + leftHandSide += iter.value() * iter.key()->result; + } + + Q_ASSERT(constant > 0 || qFuzzyCompare(1, 1 + constant)); + + if ((leftHandSide == constant) || qAbs(leftHandSide - constant) < 0.0000001) + return true; + + switch (ratio) { + case LessOrEqual: + return leftHandSide < constant; + case MoreOrEqual: + return leftHandSide > constant; + default: + return false; + } + } + +#ifdef QT_DEBUG + QString toString() { + QString result; + result += QString::fromAscii("-- QSimplexConstraint %1 --").arg(quintptr(this), 0, 16); + + QHash::const_iterator iter; + for (iter = variables.constBegin(); iter != variables.constEnd(); ++iter) { + result += QString::fromAscii(" %1 x %2").arg(iter.value()).arg(quintptr(iter.key()), 0, 16); + } + + switch (ratio) { + case LessOrEqual: + result += QString::fromAscii(" (less) <= %1").arg(constant); + break; + case MoreOrEqual: + result += QString::fromAscii(" (more) >= %1").arg(constant); + break; + default: + result += QString::fromAscii(" (eqal) == %1").arg(constant); + } + + return result; + } +#endif +}; + +class QSimplex +{ +public: + QSimplex(); + virtual ~QSimplex(); + + qreal solveMin(); + qreal solveMax(); + + bool setConstraints(const QList constraints); + void setObjective(QSimplexConstraint *objective); + + void dumpMatrix(); + +private: + // Matrix handling + qreal valueAt(int row, int column); + void setValueAt(int row, int column, qreal value); + void clearRow(int rowIndex); + void clearColumns(int first, int last); + void combineRows(int toIndex, int fromIndex, qreal factor); + + // Simplex + bool simplifyConstraints(QList *constraints); + int findPivotColumn(); + int pivotRowForColumn(int column); + void reducedRowEchelon(); + bool iterate(); + + // Helpers + void clearDataStructures(); + void solveMaxHelper(); + enum solverFactor { Minimum = -1, Maximum = 1 }; + qreal solver(solverFactor factor); + void collectResults(); + + QList constraints; + QList variables; + QSimplexConstraint *objective; + + int rows; + int columns; + int firstArtificial; + + qreal *matrix; +}; + +inline qreal QSimplex::valueAt(int rowIndex, int columnIndex) +{ + return matrix[rowIndex * columns + columnIndex]; +} + +inline void QSimplex::setValueAt(int rowIndex, int columnIndex, qreal value) +{ + matrix[rowIndex * columns + columnIndex] = value; +} + +QT_END_NAMESPACE + +#endif // QSIMPLEX_P_H -- cgit v1.2.3