| /**************************************************************************** |
| ** |
| ** 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$ |
| ** GNU Lesser General Public License Usage |
| ** This file may be used under the terms of the GNU Lesser General Public |
| ** License version 2.1 as published by the Free Software Foundation and |
| ** appearing in the file LICENSE.LGPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU Lesser |
| ** General Public License version 2.1 requirements will be met: |
| ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| ** |
| ** In addition, as a special exception, Nokia gives you certain additional |
| ** rights. These rights are described in the Nokia Qt LGPL Exception |
| ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| /*! |
| \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 <QtCore/qdebug.h> |
| #include <QtCore/qlist.h> |
| #include <QtCore/qmath.h> |
| #include <QtCore/qrect.h> |
| #include <QtCore/qset.h> |
| #include <QtCore/qstack.h> |
| #include <QtCore/qtimer.h> |
| #include <QtCore/qvarlengtharray.h> |
| #include <QtCore/QMetaMethod> |
| #include <QtGui/qapplication.h> |
| #include <QtGui/qdesktopwidget.h> |
| #include <QtGui/qevent.h> |
| #include <QtGui/qgraphicslayout.h> |
| #include <QtGui/qgraphicsproxywidget.h> |
| #include <QtGui/qgraphicswidget.h> |
| #include <QtGui/qmatrix.h> |
| #include <QtGui/qpaintengine.h> |
| #include <QtGui/qpainter.h> |
| #include <QtGui/qpixmapcache.h> |
| #include <QtGui/qpolygon.h> |
| #include <QtGui/qstyleoption.h> |
| #include <QtGui/qtooltip.h> |
| #include <QtGui/qtransform.h> |
| #include <QtGui/qinputcontext.h> |
| #include <QtGui/qgraphicseffect.h> |
| #include <private/qapplication_p.h> |
| #include <private/qobject_p.h> |
| #ifdef Q_WS_X11 |
| #include <private/qt_x11_p.h> |
| #endif |
| #include <private/qgraphicseffect_p.h> |
| #include <private/qgesturemanager_p.h> |
| #include <private/qpathclipper_p.h> |
| |
| // #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<QRectF>)"); |
| 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<QRectF>)), |
| views.at(i), SLOT(updateScene(QList<QRectF>))); |
| } |
| } |
| } 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<QRectF> oldUpdatedRects; |
| oldUpdatedRects = updateAll ? (QList<QRectF>() << 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<QGraphicsWidget *>(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<int, QGraphicsItem *>::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<QGraphicsItem*, QGraphicsItem*>::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<QGesture *, QGraphicsObject *>::iterator it; |
| for (it = gestureTargets.begin(); it != gestureTargets.end();) { |
| if (it.value() == item) |
| it = gestureTargets.erase(it); |
| else |
| ++it; |
| } |
| |
| QGraphicsObject *dummy = static_cast<QGraphicsObject *>(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<QGraphicsItem *>(widget))) { |
| ungrabKeyboard(static_cast<QGraphicsItem *>(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<QGraphicsItem *> QGraphicsScenePrivate::itemsAtPosition(const QPoint &/*screenPos*/, |
| const QPointF &scenePos, |
| QWidget *widget) const |
| { |
| Q_Q(const QGraphicsScene); |
| QGraphicsView *view = widget ? qobject_cast<QGraphicsView *>(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<QGraphicsItem *, QGraphicsItem *>::Iterator it = sceneEventFilters.lowerBound(watched); |
| QMultiMap<QGraphicsItem *, QGraphicsItem *>::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<QGraphicsItem *, QGraphicsItem *>::Iterator it = sceneEventFilters.lowerBound(item); |
| QMultiMap<QGraphicsItem *, QGraphicsItem *>::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<QGraphicsView *>(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<QGraphicsScenePrivate *>(d); |
| QRectF oldGrowingBoundingRect = thatd->growingItemsBoundingRect; |
| thatd->growingItemsBoundingRect |= itemsBoundingRect(); |
| thatd->dirtyGrowingItemsBoundingRect = false; |
| if (oldGrowingBoundingRect != thatd->growingItemsBoundingRect) |
| emit const_cast<QGraphicsScene *>(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<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsSceneBspTreeIndex *>(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<QGraphicsSceneBspTreeIndex *>(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<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> QGraphicsScene::items(const QRectF &rectangle, Qt::ItemSelectionMode mode) const |
| { |
| Q_D(const QGraphicsScene); |
| return d->index->items(rectangle, mode, Qt::DescendingOrder); |
| } |
| |
| /*! |
| \fn QList<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode) const |
| { |
| Q_D(const QGraphicsScene); |
| return d->index->items(polygon, mode, Qt::DescendingOrder); |
| } |
| |
| /*! |
| \fn QList<QGraphicsItem *> 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<QGraphicsItem *> QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode) const |
| { |
| Q_D(const QGraphicsScene); |
| return d->index->items(path, mode, Qt::DescendingOrder); |
| } |
| |
| /*! |
| \fn QList<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *>(); |
| } |
| |
| // Does not support ItemIgnoresTransformations. |
| QList<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> QGraphicsScene::selectedItems() const |
| { |
| Q_D(const QGraphicsScene); |
| |
| // Optimization: Lazily removes items that are not selected. |
| QGraphicsScene *that = const_cast<QGraphicsScene *>(this); |
| QSet<QGraphicsItem *> 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<QGraphicsItem *> 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<QGraphicsItem *> &items) |
| { |
| // Build a list of the first item's ancestors |
| QList<QGraphicsItem *> 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, |
| qVariantFromValue<QGraphicsScene *>(this))); |
| QGraphicsScene *targetScene = qVariantValue<QGraphicsScene *>(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<QGraphicsWidget *>(item)->windowType() == Qt::Popup) |
| d->addPopup(static_cast<QGraphicsWidget *>(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<QGraphicsWidget *>(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, |
| qVariantFromValue<QGraphicsScene *>(0))); |
| QGraphicsScene *targetScene = qVariantValue<QGraphicsScene *>(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 <QGraphicsView *> 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<QGraphicsSceneDragDropEvent *>(event)); |
| break; |
| case QEvent::GraphicsSceneDragMove: |
| dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent *>(event)); |
| break; |
| case QEvent::GraphicsSceneDragLeave: |
| dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent *>(event)); |
| break; |
| case QEvent::GraphicsSceneDrop: |
| dropEvent(static_cast<QGraphicsSceneDragDropEvent *>(event)); |
| break; |
| case QEvent::GraphicsSceneContextMenu: |
| contextMenuEvent(static_cast<QGraphicsSceneContextMenuEvent *>(event)); |
| break; |
| case QEvent::KeyPress: |
| if (!d->focusItem) { |
| QKeyEvent *k = static_cast<QKeyEvent *>(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<QKeyEvent *>(event)); |
| break; |
| case QEvent::KeyRelease: |
| keyReleaseEvent(static_cast<QKeyEvent *>(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<QGraphicsSceneMouseEvent *>(event); |
| d->lastSceneMousePos = mouseEvent->scenePos(); |
| mouseMoveEvent(mouseEvent); |
| break; |
| } |
| case QEvent::GraphicsSceneMousePress: |
| mousePressEvent(static_cast<QGraphicsSceneMouseEvent *>(event)); |
| break; |
| case QEvent::GraphicsSceneMouseRelease: |
| mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent *>(event)); |
| break; |
| case QEvent::GraphicsSceneMouseDoubleClick: |
| mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent *>(event)); |
| break; |
| case QEvent::GraphicsSceneWheel: |
| wheelEvent(static_cast<QGraphicsSceneWheelEvent *>(event)); |
| break; |
| case QEvent::FocusIn: |
| focusInEvent(static_cast<QFocusEvent *>(event)); |
| break; |
| case QEvent::FocusOut: |
| focusOutEvent(static_cast<QFocusEvent *>(event)); |
| break; |
| case QEvent::GraphicsSceneHoverEnter: |
| case QEvent::GraphicsSceneHoverLeave: |
| case QEvent::GraphicsSceneHoverMove: |
| { |
| QGraphicsSceneHoverEvent *hoverEvent = static_cast<QGraphicsSceneHoverEvent *>(event); |
| d->lastSceneMousePos = hoverEvent->scenePos(); |
| d->dispatchHoverEvent(hoverEvent); |
| break; |
| } |
| case QEvent::Leave: |
| d->leaveScene(); |
| break; |
| case QEvent::GraphicsSceneHelp: |
| helpEvent(static_cast<QGraphicsSceneHelpEvent *>(event)); |
| break; |
| case QEvent::InputMethod: |
| inputMethodEvent(static_cast<QInputMethodEvent *>(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<QTouchEvent *>(event)); |
| break; |
| #ifndef QT_NO_GESTURES |
| case QEvent::Gesture: |
| case QEvent::GestureOverride: |
| d->gestureEventHandler(static_cast<QGestureEvent *>(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<QGraphicsItem *> 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<const QGraphicsWidget *>(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<QGraphicsItem *> 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() |
| { |
| Q_Q(QGraphicsScene); |
| #ifndef QT_NO_TOOLTIP |
| QToolTip::hideText(); |
| #endif |
| // Send HoverLeave events to all existing hover items, topmost first. |
| QGraphicsView *senderWidget = qobject_cast<QGraphicsView *>(q->sender()); |
| QGraphicsSceneHoverEvent hoverEvent; |
| hoverEvent.setWidget(senderWidget); |
| |
| if (senderWidget) { |
| QPoint cursorPos = QCursor::pos(); |
| hoverEvent.setScenePos(senderWidget->mapToScene(senderWidget->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<QGraphicsItem *> 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<QGraphicsWidget *>::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<QGraphicsWidget *>(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<QGraphicsWidget *>(item); |
| QGraphicsProxyWidget *proxy = qobject_cast<QGraphicsProxyWidget *>(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::TxNone || type == QTransform::TxTranslate) { |
| return true; |
| } else if (type == QTransform::TxScale) { |
| // Check for 0 and 180 degree rotations. |
| // (0 might happen after 4 rotations of 90 degrees). |
| qreal m11 = transform.m11(); |
| qreal m12 = transform.m12(); |
| qreal m21 = transform.m21(); |
| qreal m22 = transform.m22(); |
| if (m12 == 0.0f && m21 == 0.0f) { |
| if (m11 == 1.0f && m22 == 1.0f) |
| return true; // 0 degrees |
| else if (m11 == -1.0f && m22 == -1.0f) |
| return true; // 180 degrees. |
| if(m11 == 1.0f && m22 == -1.0f) |
| return true; // 0 degrees inverted y. |
| else if(m11 == -1.0f && m22 == 1.0f) |
| return true; // 180 degrees inverted y. |
| } |
| } 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<QGraphicsWidget *>(item), painter, option, widget, true, painterStateProtection); |
| return; |
| } |
| |
| const qreal oldPainterOpacity = painter->opacity(); |
| qreal newPainterOpacity = oldPainterOpacity; |
| QGraphicsProxyWidget *proxy = item->isWidget() ? qobject_cast<QGraphicsProxyWidget *>(static_cast<QGraphicsWidget *>(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<QGraphicsWidget *>(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<QRectF> &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<QRectF> &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<QGraphicsItem *> 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<QGraphicsItemEffectSourcePrivate *> |
| (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(); |
| } |
| |
| // 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<QGraphicsItem *>(item->q_ptr); |
| QGraphicsView *viewq = static_cast<QGraphicsView *>(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<QGraphicsView *>(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<QGraphicsItem *> 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<QGraphicsWidget *>(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<QRectF> ®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<QGraphicsWidget *>(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<QGraphicsWidget *>(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<QGraphicsItem *> 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<QTouchEvent::TouchPoint> 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<Qt::TouchPointStates, QList<QTouchEvent::TouchPoint> > StatesAndTouchPoints; |
| QHash<QGraphicsItem *, StatesAndTouchPoints> 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<QGraphicsItem *, StatesAndTouchPoints>::ConstIterator it = itemsNeedingEvents.constBegin(); |
| const QHash<QGraphicsItem *, StatesAndTouchPoints>::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<QGraphicsItem *> blockedPanels; |
| QList<QGraphicsItem *> 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<QGraphicsItem *> blockedPanels; |
| QList<QGraphicsItem *> 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<QGesture *> &gestures, |
| Qt::GestureFlag flag, |
| QHash<QGraphicsObject *, QSet<QGesture *> > *targets, |
| QSet<QGraphicsObject *> *itemsSet, |
| QSet<QGesture *> *normal, |
| QSet<QGesture *> *conflicts) |
| { |
| QSet<QGesture *> normalGestures; // that are not in conflicted state. |
| foreach (QGesture *gesture, gestures) { |
| if (!gesture->hasHotSpot()) |
| continue; |
| const Qt::GestureType gestureType = gesture->gestureType(); |
| QList<QGraphicsItem *> 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<Qt::GestureType, Qt::GestureFlags>::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<QGraphicsView *>(viewport->parent()); |
| if (!graphicsView) |
| return; |
| |
| QList<QGesture *> allGestures = event->gestures(); |
| DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" |
| << "Gestures:" << allGestures; |
| |
| QSet<QGesture *> 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<QGesture *> normalGestures; // that have just one target |
| QSet<QGesture *> 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<QGraphicsObject> item = cachedTargetItems.at(i); |
| |
| // get gestures to deliver to the current item |
| QSet<QGesture *> 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<QGraphicsObject *, QSet<QGesture *> >::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<QGesture *> undeliveredGestures; |
| QSet<QGesture *> 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<QGraphicsObject> receiver = cachedTargetItems.at(i); |
| QSet<QGesture *> 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<QGesture *> 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<QGraphicsObject *> targetsSet = cachedTargetItems.toSet(); |
| |
| if (receiver) { |
| // first if the gesture should be propagated to parents only |
| for (QSet<QGesture *>::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<QGesture *> canceledGestures; |
| QHash<QGesture *, QGraphicsObject *>::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<QGesture *> almostCanceledGestures = canceledGestures; |
| QSet<QGesture *>::Iterator setIter; |
| while (!almostCanceledGestures.isEmpty()) { |
| QGraphicsObject *target = 0; |
| QSet<QGesture*> 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<QGesture *> 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<QGraphicsItem *> 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<QGesture *> 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<QGraphicsObject *>(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 |