diff -r 000000000000 -r 1918ee327afb src/gui/graphicsview/qgraphicsscene.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/graphicsview/qgraphicsscene.cpp Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,5970 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QGraphicsScene + \brief The QGraphicsScene class provides a surface for managing a large + number of 2D graphical items. + \since 4.2 + \ingroup graphicsview-api + + + The class serves as a container for QGraphicsItems. It is used together + with QGraphicsView for visualizing graphical items, such as lines, + rectangles, text, or even custom items, on a 2D surface. QGraphicsScene is + part of \l{The Graphics View Framework}. + + QGraphicsScene also provides functionality that lets you efficiently + determine both the location of items, and for determining what items are + visible within an arbitrary area on the scene. With the QGraphicsView + widget, you can either visualize the whole scene, or zoom in and view only + parts of the scene. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 0 + + Note that QGraphicsScene has no visual appearance of its own; it only + manages the items. You need to create a QGraphicsView widget to visualize + the scene. + + To add items to a scene, you start off by constructing a QGraphicsScene + object. Then, you have two options: either add your existing QGraphicsItem + objects by calling addItem(), or you can call one of the convenience + functions addEllipse(), addLine(), addPath(), addPixmap(), addPolygon(), + addRect(), or addText(), which all return a pointer to the newly added item. + The dimensions of the items added with these functions are relative to the + item's coordinate system, and the items position is initialized to (0, + 0) in the scene. + + You can then visualize the scene using QGraphicsView. When the scene + changes, (e.g., when an item moves or is transformed) QGraphicsScene + emits the changed() signal. To remove an item, call removeItem(). + + QGraphicsScene uses an indexing algorithm to manage the location of items + efficiently. By default, a BSP (Binary Space Partitioning) tree is used; an + algorithm suitable for large scenes where most items remain static (i.e., + do not move around). You can choose to disable this index by calling + setItemIndexMethod(). For more information about the available indexing + algorithms, see the itemIndexMethod property. + + The scene's bounding rect is set by calling setSceneRect(). Items can be + placed at any position on the scene, and the size of the scene is by + default unlimited. The scene rect is used only for internal bookkeeping, + maintaining the scene's item index. If the scene rect is unset, + QGraphicsScene will use the bounding area of all items, as returned by + itemsBoundingRect(), as the scene rect. However, itemsBoundingRect() is a + relatively time consuming function, as it operates by collecting + positional information for every item on the scene. Because of this, you + should always set the scene rect when operating on large scenes. + + One of QGraphicsScene's greatest strengths is its ability to efficiently + determine the location of items. Even with millions of items on the scene, + the items() functions can determine the location of an item within few + milliseconds. There are several overloads to items(): one that finds items + at a certain position, one that finds items inside or intersecting with a + polygon or a rectangle, and more. The list of returned items is sorted by + stacking order, with the topmost item being the first item in the list. + For convenience, there is also an itemAt() function that returns the + topmost item at a given position. + + QGraphicsScene maintains selection information for the scene. To select + items, call setSelectionArea(), and to clear the current selection, call + clearSelection(). Call selectedItems() to get the list of all selected + items. + + \section1 Event Handling and Propagation + + Another responsibility that QGraphicsScene has, is to propagate events + from QGraphicsView. To send an event to a scene, you construct an event + that inherits QEvent, and then send it using, for example, + QApplication::sendEvent(). event() is responsible for dispatching + the event to the individual items. Some common events are handled by + convenience event handlers. For example, key press events are handled by + keyPressEvent(), and mouse press events are handled by mousePressEvent(). + + Key events are delivered to the \e {focus item}. To set the focus item, + you can either call setFocusItem(), passing an item that accepts focus, or + the item itself can call QGraphicsItem::setFocus(). Call focusItem() to + get the current focus item. For compatibility with widgets, the scene also + maintains its own focus information. By default, the scene does not have + focus, and all key events are discarded. If setFocus() is called, or if an + item on the scene gains focus, the scene automatically gains focus. If the + scene has focus, hasFocus() will return true, and key events will be + forwarded to the focus item, if any. If the scene loses focus, (i.e., + someone calls clearFocus(),) while an item has focus, the scene will + maintain its item focus information, and once the scene regains focus, it + will make sure the last focus item regains focus. + + For mouse-over effects, QGraphicsScene dispatches \e {hover + events}. If an item accepts hover events (see + QGraphicsItem::acceptHoverEvents()), it will receive a \l + {QEvent::}{GraphicsSceneHoverEnter} event when the mouse enters + its area. As the mouse continues moving inside the item's area, + QGraphicsScene will send it \l {QEvent::}{GraphicsSceneHoverMove} + events. When the mouse leaves the item's area, the item will + receive a \l {QEvent::}{GraphicsSceneHoverLeave} event. + + All mouse events are delivered to the current \e {mouse grabber} + item. An item becomes the scene's mouse grabber if it accepts + mouse events (see QGraphicsItem::acceptedMouseButtons()) and it + receives a mouse press. It stays the mouse grabber until it + receives a mouse release when no other mouse buttons are + pressed. You can call mouseGrabberItem() to determine what item is + currently grabbing the mouse. + + \sa QGraphicsItem, QGraphicsView +*/ + +/*! + \enum QGraphicsScene::SceneLayer + \since 4.3 + + This enum describes the rendering layers in a QGraphicsScene. When + QGraphicsScene draws the scene contents, it renders each of these layers + separately, in order. + + Each layer represents a flag that can be OR'ed together when calling + functions such as invalidate() or QGraphicsView::invalidateScene(). + + \value ItemLayer The item layer. QGraphicsScene renders all items are in + this layer by calling the virtual function drawItems(). The item layer is + drawn after the background layer, but before the foreground layer. + + \value BackgroundLayer The background layer. QGraphicsScene renders the + scene's background in this layer by calling the virtual function + drawBackground(). The background layer is drawn first of all layers. + + \value ForegroundLayer The foreground layer. QGraphicsScene renders the + scene's foreground in this layer by calling the virtual function + drawForeground(). The foreground layer is drawn last of all layers. + + \value AllLayers All layers; this value represents a combination of all + three layers. + + \sa invalidate(), QGraphicsView::invalidateScene() +*/ + +/*! + \enum QGraphicsScene::ItemIndexMethod + + This enum describes the indexing algorithms QGraphicsScene provides for + managing positional information about items on the scene. + + \value BspTreeIndex A Binary Space Partitioning tree is applied. All + QGraphicsScene's item location algorithms are of an order close to + logarithmic complexity, by making use of binary search. Adding, moving and + removing items is logarithmic. This approach is best for static scenes + (i.e., scenes where most items do not move). + + \value NoIndex No index is applied. Item location is of linear complexity, + as all items on the scene are searched. Adding, moving and removing items, + however, is done in constant time. This approach is ideal for dynamic + scenes, where many items are added, moved or removed continuously. + + \sa setItemIndexMethod(), bspTreeDepth +*/ + +#include "qgraphicsscene.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicsitem.h" +#include "qgraphicsitem_p.h" +#include "qgraphicslayout.h" +#include "qgraphicsscene_p.h" +#include "qgraphicssceneevent.h" +#include "qgraphicsview.h" +#include "qgraphicsview_p.h" +#include "qgraphicswidget.h" +#include "qgraphicswidget_p.h" +#include "qgraphicssceneindex_p.h" +#include "qgraphicsscenebsptreeindex_p.h" +#include "qgraphicsscenelinearindex_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef Q_WS_X11 +#include +#endif +#include +#include + +// #define GESTURE_DEBUG +#ifndef GESTURE_DEBUG +# define DEBUG if (0) qDebug +#else +# define DEBUG qDebug +#endif + +QT_BEGIN_NAMESPACE + +bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); + +static void _q_hoverFromMouseEvent(QGraphicsSceneHoverEvent *hover, const QGraphicsSceneMouseEvent *mouseEvent) +{ + hover->setWidget(mouseEvent->widget()); + hover->setPos(mouseEvent->pos()); + hover->setScenePos(mouseEvent->scenePos()); + hover->setScreenPos(mouseEvent->screenPos()); + hover->setLastPos(mouseEvent->lastPos()); + hover->setLastScenePos(mouseEvent->lastScenePos()); + hover->setLastScreenPos(mouseEvent->lastScreenPos()); + hover->setModifiers(mouseEvent->modifiers()); + hover->setAccepted(mouseEvent->isAccepted()); +} + +int QGraphicsScenePrivate::changedSignalIndex; + +/*! + \internal +*/ +QGraphicsScenePrivate::QGraphicsScenePrivate() + : indexMethod(QGraphicsScene::BspTreeIndex), + index(0), + lastItemCount(0), + hasSceneRect(false), + dirtyGrowingItemsBoundingRect(true), + updateAll(false), + calledEmitUpdated(false), + processDirtyItemsEmitted(false), + selectionChanging(0), + needSortTopLevelItems(true), + holesInTopLevelSiblingIndex(false), + topLevelSequentialOrdering(true), + stickyFocus(false), + hasFocus(false), + focusItem(0), + lastFocusItem(0), + tabFocusFirst(0), + activePanel(0), + lastActivePanel(0), + activationRefCount(0), + childExplicitActivation(0), + lastMouseGrabberItem(0), + lastMouseGrabberItemHasImplicitMouseGrab(false), + dragDropItem(0), + enterWidget(0), + lastDropAction(Qt::IgnoreAction), + allItemsIgnoreHoverEvents(true), + allItemsUseDefaultCursor(true), + painterStateProtection(true), + sortCacheEnabled(false), + style(0), + allItemsIgnoreTouchEvents(true) +{ +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::init() +{ + Q_Q(QGraphicsScene); + + index = new QGraphicsSceneBspTreeIndex(q); + + // Keep this index so we can check for connected slots later on. + if (!changedSignalIndex) { + changedSignalIndex = signalIndex("changed(QList)"); + } + qApp->d_func()->scene_list.append(q); + q->update(); +} + +/*! + \internal +*/ +QGraphicsScenePrivate *QGraphicsScenePrivate::get(QGraphicsScene *q) +{ + return q->d_func(); +} + +void QGraphicsScenePrivate::_q_emitUpdated() +{ + Q_Q(QGraphicsScene); + calledEmitUpdated = false; + + if (dirtyGrowingItemsBoundingRect) { + if (!hasSceneRect) { + const QRectF oldGrowingItemsBoundingRect = growingItemsBoundingRect; + growingItemsBoundingRect |= q->itemsBoundingRect(); + if (oldGrowingItemsBoundingRect != growingItemsBoundingRect) + emit q->sceneRectChanged(growingItemsBoundingRect); + } + dirtyGrowingItemsBoundingRect = false; + } + + // Ensure all views are connected if anything is connected. This disables + // the optimization that items send updates directly to the views, but it + // needs to happen in order to keep compatibility with the behavior from + // Qt 4.4 and backward. + if (isSignalConnected(changedSignalIndex)) { + for (int i = 0; i < views.size(); ++i) { + QGraphicsView *view = views.at(i); + if (!view->d_func()->connectedToScene) { + view->d_func()->connectedToScene = true; + q->connect(q, SIGNAL(changed(QList)), + views.at(i), SLOT(updateScene(QList))); + } + } + } else { + updateAll = false; + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->processPendingUpdates(); + // It's important that we update all views before we dispatch, hence two for-loops. + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->dispatchPendingUpdateRequests(); + return; + } + + // Notify the changes to anybody interested. + QList oldUpdatedRects; + oldUpdatedRects = updateAll ? (QList() << q->sceneRect()) : updatedRects; + updateAll = false; + updatedRects.clear(); + emit q->changed(oldUpdatedRects); +} + +/*! + \internal + + ### This function is almost identical to QGraphicsItemPrivate::addChild(). +*/ +void QGraphicsScenePrivate::registerTopLevelItem(QGraphicsItem *item) +{ + item->d_ptr->ensureSequentialSiblingIndex(); + needSortTopLevelItems = true; // ### maybe false + item->d_ptr->siblingIndex = topLevelItems.size(); + topLevelItems.append(item); +} + +/*! + \internal + + ### This function is almost identical to QGraphicsItemPrivate::removeChild(). +*/ +void QGraphicsScenePrivate::unregisterTopLevelItem(QGraphicsItem *item) +{ + if (!holesInTopLevelSiblingIndex) + holesInTopLevelSiblingIndex = item->d_ptr->siblingIndex != topLevelItems.size() - 1; + if (topLevelSequentialOrdering && !holesInTopLevelSiblingIndex) + topLevelItems.removeAt(item->d_ptr->siblingIndex); + else + topLevelItems.removeOne(item); + // NB! Do not use topLevelItems.removeAt(item->d_ptr->siblingIndex) because + // the item is not guaranteed to be at the index after the list is sorted + // (see ensureSortedTopLevelItems()). + item->d_ptr->siblingIndex = -1; + if (topLevelSequentialOrdering) + topLevelSequentialOrdering = !holesInTopLevelSiblingIndex; +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::_q_polishItems() +{ + QSet::Iterator it; + const QVariant booleanTrueVariant(true); + while (!unpolishedItems.isEmpty()) { + it = unpolishedItems.begin(); + QGraphicsItem *item = *it; + unpolishedItems.erase(it); + if (!item->d_ptr->explicitlyHidden) { + item->itemChange(QGraphicsItem::ItemVisibleChange, booleanTrueVariant); + item->itemChange(QGraphicsItem::ItemVisibleHasChanged, booleanTrueVariant); + } + if (item->isWidget()) { + QEvent event(QEvent::Polish); + QApplication::sendEvent((QGraphicsWidget *)item, &event); + } + } +} + +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 + + 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(), false, false, false, 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->d_ptr->inDestructor && item == tabFocusFirst) { + QGraphicsWidget *widget = static_cast(item); + widget->d_func()->fixFocusChainBeforeReparenting(0, 0); + } + + item->d_func()->scene = 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->d_ptr->setParentItemHelper(0); + } + } else { + unregisterTopLevelItem(item); + } + + // Reset the mouse grabber and focus item data. + if (item == focusItem) + focusItem = 0; + if (item == lastFocusItem) + lastFocusItem = 0; + if (item == activePanel) { + // ### deactivate... + activePanel = 0; + } + if (item == lastActivePanel) + lastActivePanel = 0; + + // Disable selectionChanged() for individual items + ++selectionChanging; + int oldSelectedItemsSize = selectedItems.size(); + + // Update selected & hovered item bookkeeping + selectedItems.remove(item); + hoverItems.removeAll(item); + cachedItemsUnderMouse.removeAll(item); + unpolishedItems.remove(item); + resetDirtyItem(item); + + //We remove all references of item from the sceneEventFilter arrays + QMultiMap::iterator iterator = sceneEventFilters.begin(); + while (iterator != sceneEventFilters.end()) { + if (iterator.value() == item || iterator.key() == item) + iterator = sceneEventFilters.erase(iterator); + else + ++iterator; + } + + if (!item->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->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; + + // Reenable selectionChanged() for individual items + --selectionChanging; + if (!selectionChanging && selectedItems.size() != oldSelectedItemsSize) + emit q->selectionChanged(); +} + +/*! + \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) { + QFocusEvent event(QEvent::FocusOut, focusReason); + lastFocusItem = focusItem; + focusItem = 0; + sendEvent(lastFocusItem, &event); + + if (lastFocusItem + && (lastFocusItem->flags() & QGraphicsItem::ItemAcceptsInputMethod)) { + // Reset any visible preedit text + QInputMethodEvent imEvent; + sendEvent(lastFocusItem, &imEvent); + + // 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. + if (item) { + for (int i = 0; i < views.size(); ++i) + views.at(i)->inputContext()->reset(); + } + } + } + + if (item) { + focusItem = item; + QFocusEvent event(QEvent::FocusIn, focusReason); + sendEvent(item, &event); + } + + updateInputMethodSensitivityInViews(); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::addPopup(QGraphicsWidget *widget) +{ + Q_ASSERT(widget); + Q_ASSERT(!popupWidgets.contains(widget)); + popupWidgets << widget; + if (QGraphicsWidget *focusWidget = widget->focusWidget()) { + focusWidget->setFocus(Qt::PopupFocusReason); + } else { + grabKeyboard((QGraphicsItem *)widget); + if (focusItem && popupWidgets.size() == 1) { + QFocusEvent event(QEvent::FocusOut, Qt::PopupFocusReason); + sendEvent(focusItem, &event); + } + } + grabMouse((QGraphicsItem *)widget); +} + +/*! + \internal + + Remove \a widget from the popup list. Important notes: + + \a widget is guaranteed to be in the list of popups, but it might not be + the last entry; you can hide any item in the pop list before the others, + and this must cause all later mouse grabbers to lose the grab. +*/ +void QGraphicsScenePrivate::removePopup(QGraphicsWidget *widget, bool itemIsDying) +{ + Q_ASSERT(widget); + int index = popupWidgets.indexOf(widget); + Q_ASSERT(index != -1); + + for (int i = popupWidgets.size() - 1; i >= index; --i) { + QGraphicsWidget *widget = popupWidgets.takeLast(); + ungrabMouse(widget, itemIsDying); + if (focusItem && popupWidgets.isEmpty()) { + QFocusEvent event(QEvent::FocusIn, Qt::PopupFocusReason); + sendEvent(focusItem, &event); + } else if (keyboardGrabberItems.contains(static_cast(widget))) { + ungrabKeyboard(static_cast(widget), itemIsDying); + } + if (!itemIsDying && widget->isVisible()) { + widget->hide(); + widget->QGraphicsItem::d_ptr->explicitlyHidden = 0; + } + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::grabMouse(QGraphicsItem *item, bool implicit) +{ + // Append to list of mouse grabber items, and send a mouse grab event. + if (mouseGrabberItems.contains(item)) { + if (mouseGrabberItems.last() == item) { + Q_ASSERT(!implicit); + if (!lastMouseGrabberItemHasImplicitMouseGrab) { + qWarning("QGraphicsItem::grabMouse: already a mouse grabber"); + } else { + // Upgrade to an explicit mouse grab + lastMouseGrabberItemHasImplicitMouseGrab = false; + } + } else { + qWarning("QGraphicsItem::grabMouse: already blocked by mouse grabber: %p", + mouseGrabberItems.last()); + } + return; + } + + // Send ungrab event to the last grabber. + if (!mouseGrabberItems.isEmpty()) { + QGraphicsItem *last = mouseGrabberItems.last(); + if (lastMouseGrabberItemHasImplicitMouseGrab) { + // Implicit mouse grab is immediately lost. + last->ungrabMouse(); + } else { + // Just send ungrab event to current grabber. + QEvent ungrabEvent(QEvent::UngrabMouse); + sendEvent(last, &ungrabEvent); + } + } + + mouseGrabberItems << item; + lastMouseGrabberItemHasImplicitMouseGrab = implicit; + + // Send grab event to current grabber. + QEvent grabEvent(QEvent::GrabMouse); + sendEvent(item, &grabEvent); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::ungrabMouse(QGraphicsItem *item, bool itemIsDying) +{ + int index = mouseGrabberItems.indexOf(item); + if (index == -1) { + qWarning("QGraphicsItem::ungrabMouse: not a mouse grabber"); + return; + } + + if (item != mouseGrabberItems.last()) { + // Recursively ungrab the next mouse grabber until we reach this item + // to ensure state consistency. + ungrabMouse(mouseGrabberItems.at(index + 1), itemIsDying); + } + if (!popupWidgets.isEmpty() && item == popupWidgets.last()) { + // If the item is a popup, go via removePopup to ensure state + // consistency and that it gets hidden correctly - beware that + // removePopup() reenters this function to continue removing the grab. + removePopup((QGraphicsWidget *)item, itemIsDying); + return; + } + + // Send notification about mouse ungrab. + if (!itemIsDying) { + QEvent event(QEvent::UngrabMouse); + sendEvent(item, &event); + } + + // Remove the item from the list of grabbers. Whenever this happens, we + // reset the implicitGrab (there can be only ever be one implicit grabber + // in a scene, and it is always the latest grabber; if the implicit grab + // is lost, it is not automatically regained. + mouseGrabberItems.takeLast(); + lastMouseGrabberItemHasImplicitMouseGrab = false; + + // Send notification about mouse regrab. ### It's unfortunate that all the + // items get a GrabMouse event, but this is a rare case with a simple + // implementation and it does ensure a consistent state. + if (!itemIsDying && !mouseGrabberItems.isEmpty()) { + QGraphicsItem *last = mouseGrabberItems.last(); + QEvent event(QEvent::GrabMouse); + sendEvent(last, &event); + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::clearMouseGrabber() +{ + if (!mouseGrabberItems.isEmpty()) + mouseGrabberItems.first()->ungrabMouse(); + lastMouseGrabberItem = 0; +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::grabKeyboard(QGraphicsItem *item) +{ + if (keyboardGrabberItems.contains(item)) { + if (keyboardGrabberItems.last() == item) + qWarning("QGraphicsItem::grabKeyboard: already a keyboard grabber"); + else + qWarning("QGraphicsItem::grabKeyboard: already blocked by keyboard grabber: %p", + keyboardGrabberItems.last()); + return; + } + + // Send ungrab event to the last grabber. + if (!keyboardGrabberItems.isEmpty()) { + // Just send ungrab event to current grabber. + QEvent ungrabEvent(QEvent::UngrabKeyboard); + sendEvent(keyboardGrabberItems.last(), &ungrabEvent); + } + + keyboardGrabberItems << item; + + // Send grab event to current grabber. + QEvent grabEvent(QEvent::GrabKeyboard); + sendEvent(item, &grabEvent); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::ungrabKeyboard(QGraphicsItem *item, bool itemIsDying) +{ + int index = keyboardGrabberItems.lastIndexOf(item); + if (index == -1) { + qWarning("QGraphicsItem::ungrabKeyboard: not a keyboard grabber"); + return; + } + if (item != keyboardGrabberItems.last()) { + // Recursively ungrab the topmost keyboard grabber until we reach this + // item to ensure state consistency. + ungrabKeyboard(keyboardGrabberItems.at(index + 1), itemIsDying); + } + + // Send notification about keyboard ungrab. + if (!itemIsDying) { + QEvent event(QEvent::UngrabKeyboard); + sendEvent(item, &event); + } + + // Remove the item from the list of grabbers. + keyboardGrabberItems.takeLast(); + + // Send notification about mouse regrab. + if (!itemIsDying && !keyboardGrabberItems.isEmpty()) { + QGraphicsItem *last = keyboardGrabberItems.last(); + QEvent event(QEvent::GrabKeyboard); + sendEvent(last, &event); + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::clearKeyboardGrabber() +{ + if (!keyboardGrabberItems.isEmpty()) + ungrabKeyboard(keyboardGrabberItems.first()); +} + +void QGraphicsScenePrivate::enableMouseTrackingOnViews() +{ + foreach (QGraphicsView *view, views) + view->viewport()->setMouseTracking(true); +} + +/*! + Returns all items for the screen position in \a event. +*/ +QList QGraphicsScenePrivate::itemsAtPosition(const QPoint &screenPos, + const QPointF &scenePos, + QWidget *widget) const +{ + Q_Q(const QGraphicsScene); + QGraphicsView *view = widget ? qobject_cast(widget->parentWidget()) : 0; + if (!view) + return q->items(scenePos, Qt::IntersectsItemShape, Qt::DescendingOrder, QTransform()); + + const QRectF pointRect(QPointF(widget->mapFromGlobal(screenPos)), QSizeF(1, 1)); + if (!view->isTransformed()) + return q->items(pointRect, Qt::IntersectsItemShape, Qt::DescendingOrder); + + const QTransform viewTransform = view->viewportTransform(); + if (viewTransform.type() <= QTransform::TxScale) { + return q->items(viewTransform.inverted().mapRect(pointRect), Qt::IntersectsItemShape, + Qt::DescendingOrder, viewTransform); + } + return q->items(viewTransform.inverted().map(pointRect), Qt::IntersectsItemShape, + Qt::DescendingOrder, viewTransform); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::storeMouseButtonsForMouseGrabber(QGraphicsSceneMouseEvent *event) +{ + for (int i = 0x1; i <= 0x10; i <<= 1) { + if (event->buttons() & i) { + mouseGrabberButtonDownPos.insert(Qt::MouseButton(i), + mouseGrabberItems.last()->d_ptr->genericMapFromScene(event->scenePos(), + event->widget())); + mouseGrabberButtonDownScenePos.insert(Qt::MouseButton(i), event->scenePos()); + mouseGrabberButtonDownScreenPos.insert(Qt::MouseButton(i), event->screenPos()); + } + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::installSceneEventFilter(QGraphicsItem *watched, QGraphicsItem *filter) +{ + sceneEventFilters.insert(watched, filter); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::removeSceneEventFilter(QGraphicsItem *watched, QGraphicsItem *filter) +{ + if (!sceneEventFilters.contains(watched)) + return; + + QMultiMap::Iterator it = sceneEventFilters.lowerBound(watched); + QMultiMap::Iterator end = sceneEventFilters.upperBound(watched); + do { + if (it.value() == filter) + it = sceneEventFilters.erase(it); + else + ++it; + } while (it != end); +} + +/*! + \internal +*/ +bool QGraphicsScenePrivate::filterDescendantEvent(QGraphicsItem *item, QEvent *event) +{ + if (item && (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorFiltersChildEvents)) { + QGraphicsItem *parent = item->parentItem(); + while (parent) { + if (parent->d_ptr->filtersDescendantEvents && parent->sceneEventFilter(item, event)) + return true; + if (!(parent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorFiltersChildEvents)) + return false; + parent = parent->parentItem(); + } + } + return false; +} + +/*! + \internal +*/ +bool QGraphicsScenePrivate::filterEvent(QGraphicsItem *item, QEvent *event) +{ + if (item && !sceneEventFilters.contains(item)) + return false; + + QMultiMap::Iterator it = sceneEventFilters.lowerBound(item); + QMultiMap::Iterator end = sceneEventFilters.upperBound(item); + while (it != end) { + // ### The filterer and filteree might both be deleted. + if (it.value()->sceneEventFilter(it.key(), event)) + return true; + ++it; + } + return false; +} + +/*! + \internal + + This is the final dispatch point for any events from the scene to the + item. It filters the event first - if the filter returns true, the event + is considered to have been eaten by the filter, and is therefore stopped + (the default filter returns false). Then/otherwise, if the item is + enabled, the event is sent; otherwise it is stopped. +*/ +bool QGraphicsScenePrivate::sendEvent(QGraphicsItem *item, QEvent *event) +{ + if (QGraphicsObject *object = item->toGraphicsObject()) { + QApplicationPrivate *qAppPriv = QApplicationPrivate::instance(); + if (qAppPriv->gestureManager) { + if (qAppPriv->gestureManager->filterEvent(object, event)) + return true; + } + } + + 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()) { + // Make sure we don't clear focus. + setFocus = true; + break; + } + 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; + } + + // Check for scene modality. + bool sceneModality = false; + for (int i = 0; i < modalPanels.size(); ++i) { + if (modalPanels.at(i)->panelModality() == QGraphicsItem::SceneModal) { + sceneModality = true; + break; + } + } + + // If nobody could take focus, clear it. + if (!stickyFocus && !setFocus && !sceneModality) + q->setFocusItem(0, Qt::MouseFocusReason); + + // Any item will do. + if (sceneModality && cachedItemsUnderMouse.isEmpty()) + cachedItemsUnderMouse << modalPanels.first(); + + // Find a mouse grabber by sending mouse press events to all mouse grabber + // candidates one at a time, until the event is accepted. It's accepted by + // default, so the receiver has to explicitly ignore it for it to pass + // through. + foreach (QGraphicsItem *item, cachedItemsUnderMouse) { + if (!(item->acceptedMouseButtons() & mouseEvent->button())) { + // Skip items that don't accept the event's mouse button. + continue; + } + + // Check if this item is blocked by a modal panel and deliver the mouse event to the + // blocking panel instead of this item if blocked. + (void) item->isBlockedByModalPanel(&item); + + grabMouse(item, /* implicit = */ true); + mouseEvent->accept(); + + // check if the item we are sending to are disabled (before we send the event) + bool disabled = !item->isEnabled(); + bool isPanel = item->isPanel(); + if (mouseEvent->type() == QEvent::GraphicsSceneMouseDoubleClick + && item != lastMouseGrabberItem && lastMouseGrabberItem) { + // If this item is different from the item that received the last + // mouse event, and mouseEvent is a doubleclick event, then the + // event is converted to a press. Known limitation: + // Triple-clicking will not generate a doubleclick, though. + QGraphicsSceneMouseEvent mousePress(QEvent::GraphicsSceneMousePress); + mousePress.spont = mouseEvent->spont; + mousePress.accept(); + mousePress.setButton(mouseEvent->button()); + mousePress.setButtons(mouseEvent->buttons()); + mousePress.setScreenPos(mouseEvent->screenPos()); + mousePress.setScenePos(mouseEvent->scenePos()); + mousePress.setModifiers(mouseEvent->modifiers()); + mousePress.setWidget(mouseEvent->widget()); + mousePress.setButtonDownPos(mouseEvent->button(), + mouseEvent->buttonDownPos(mouseEvent->button())); + mousePress.setButtonDownScenePos(mouseEvent->button(), + mouseEvent->buttonDownScenePos(mouseEvent->button())); + mousePress.setButtonDownScreenPos(mouseEvent->button(), + mouseEvent->buttonDownScreenPos(mouseEvent->button())); + sendMouseEvent(&mousePress); + mouseEvent->setAccepted(mousePress.isAccepted()); + } else { + sendMouseEvent(mouseEvent); + } + + bool dontSendUngrabEvents = mouseGrabberItems.isEmpty() || mouseGrabberItems.last() != item; + if (disabled) { + ungrabMouse(item, /* itemIsDying = */ dontSendUngrabEvents); + break; + } + if (mouseEvent->isAccepted()) { + if (!mouseGrabberItems.isEmpty()) + storeMouseButtonsForMouseGrabber(mouseEvent); + lastMouseGrabberItem = item; + return; + } + ungrabMouse(item, /* itemIsDying = */ dontSendUngrabEvents); + + // Don't propagate through panels. + if (isPanel) + break; + } + + // Is the event still ignored? Then the mouse press goes to the scene. + // Reset the mouse grabber, clear the selection, clear focus, and leave + // the event ignored so that it can propagate through the originating + // view. + if (!mouseEvent->isAccepted()) { + clearMouseGrabber(); + + QGraphicsView *view = mouseEvent->widget() ? qobject_cast(mouseEvent->widget()->parentWidget()) : 0; + bool dontClearSelection = view && view->dragMode() == QGraphicsView::ScrollHandDrag; + if (!dontClearSelection) { + // Clear the selection if the originating view isn't in scroll + // hand drag mode. The view will clear the selection if no drag + // happened. + q->clearSelection(); + } + } +} + +/*! + \internal + + Ensures that the list of toplevels is sorted by insertion order, and that + the siblingIndexes are packed (no gaps), and start at 0. + + ### This function is almost identical to + QGraphicsItemPrivate::ensureSequentialSiblingIndex(). +*/ +void QGraphicsScenePrivate::ensureSequentialTopLevelSiblingIndexes() +{ + if (!topLevelSequentialOrdering) { + qSort(topLevelItems.begin(), topLevelItems.end(), QGraphicsItemPrivate::insertionOrder); + topLevelSequentialOrdering = true; + needSortTopLevelItems = 1; + } + if (holesInTopLevelSiblingIndex) { + holesInTopLevelSiblingIndex = 0; + for (int i = 0; i < topLevelItems.size(); ++i) + topLevelItems[i]->d_ptr->siblingIndex = i; + } +} + +/*! + \internal + + Set the font and propagate the changes if the font is different from the + current font. +*/ +void QGraphicsScenePrivate::setFont_helper(const QFont &font) +{ + if (this->font == font && this->font.resolve() == font.resolve()) + return; + updateFont(font); +} + +/*! + \internal + + Resolve the scene's font against the application font, and propagate the + changes too all items in the scene. +*/ +void QGraphicsScenePrivate::resolveFont() +{ + QFont naturalFont = QApplication::font(); + naturalFont.resolve(0); + QFont resolvedFont = font.resolve(naturalFont); + updateFont(resolvedFont); +} + +/*! + \internal + + Update the font, and whether or not it has changed, reresolve all fonts in + the scene. +*/ +void QGraphicsScenePrivate::updateFont(const QFont &font) +{ + Q_Q(QGraphicsScene); + + // Update local font setting. + this->font = font; + + // Resolve the fonts of all top-level widget items, or widget items + // whose parent is not a widget. + foreach (QGraphicsItem *item, q->items()) { + if (!item->parentItem()) { + // Resolvefont for an item is a noop operation, but + // every item can be a widget, or can have a widget + // childre. + item->d_ptr->resolveFont(font.resolve()); + } + } + + // Send the scene a FontChange event. + QEvent event(QEvent::FontChange); + QApplication::sendEvent(q, &event); +} + +/*! + \internal + + Set the palette and propagate the changes if the palette is different from + the current palette. +*/ +void QGraphicsScenePrivate::setPalette_helper(const QPalette &palette) +{ + if (this->palette == palette && this->palette.resolve() == palette.resolve()) + return; + updatePalette(palette); +} + +/*! + \internal + + Resolve the scene's palette against the application palette, and propagate + the changes too all items in the scene. +*/ +void QGraphicsScenePrivate::resolvePalette() +{ + QPalette naturalPalette = QApplication::palette(); + naturalPalette.resolve(0); + QPalette resolvedPalette = palette.resolve(naturalPalette); + updatePalette(resolvedPalette); +} + +/*! + \internal + + Update the palette, and whether or not it has changed, reresolve all + palettes in the scene. +*/ +void QGraphicsScenePrivate::updatePalette(const QPalette &palette) +{ + Q_Q(QGraphicsScene); + + // Update local palette setting. + this->palette = palette; + + // Resolve the palettes of all top-level widget items, or widget items + // whose parent is not a widget. + foreach (QGraphicsItem *item, q->items()) { + if (!item->parentItem()) { + // Resolvefont for an item is a noop operation, but + // every item can be a widget, or can have a widget + // childre. + item->d_ptr->resolvePalette(palette.resolve()); + } + } + + // Send the scene a PaletteChange event. + QEvent event(QEvent::PaletteChange); + QApplication::sendEvent(q, &event); +} + +/*! + Constructs a QGraphicsScene object. The \a parent parameter is + passed to QObject's constructor. +*/ +QGraphicsScene::QGraphicsScene(QObject *parent) + : QObject(*new QGraphicsScenePrivate, parent) +{ + d_func()->init(); +} + +/*! + Constructs a QGraphicsScene object, using \a sceneRect for its + scene rectangle. The \a parent parameter is passed to QObject's + constructor. + + \sa sceneRect +*/ +QGraphicsScene::QGraphicsScene(const QRectF &sceneRect, QObject *parent) + : QObject(*new QGraphicsScenePrivate, parent) +{ + d_func()->init(); + setSceneRect(sceneRect); +} + +/*! + Constructs a QGraphicsScene object, using the rectangle specified + by (\a x, \a y), and the given \a width and \a height for its + scene rectangle. The \a parent parameter is passed to QObject's + constructor. + + \sa sceneRect +*/ +QGraphicsScene::QGraphicsScene(qreal x, qreal y, qreal width, qreal height, QObject *parent) + : QObject(*new QGraphicsScenePrivate, parent) +{ + d_func()->init(); + setSceneRect(x, y, width, height); +} + +/*! + Destroys the QGraphicsScene object. +*/ +QGraphicsScene::~QGraphicsScene() +{ + Q_D(QGraphicsScene); + + // Remove this scene from qApp's global scene list. + qApp->d_func()->scene_list.removeAll(this); + + clear(); + + // Remove this scene from all associated views. + for (int j = 0; j < d->views.size(); ++j) + d->views.at(j)->setScene(0); +} + +/*! + \property QGraphicsScene::sceneRect + \brief the scene rectangle; the bounding rectangle of the scene + + The scene rectangle defines the extent of the scene. It is + primarily used by QGraphicsView to determine the view's default + scrollable area, and by QGraphicsScene to manage item indexing. + + If unset, or if set to a null QRectF, sceneRect() will return the largest + bounding rect of all items on the scene since the scene was created (i.e., + a rectangle that grows when items are added to or moved in the scene, but + never shrinks). + + \sa width(), height(), QGraphicsView::sceneRect +*/ +QRectF QGraphicsScene::sceneRect() const +{ + Q_D(const QGraphicsScene); + if (d->hasSceneRect) + return d->sceneRect; + + if (d->dirtyGrowingItemsBoundingRect) { + // Lazily update the growing items bounding rect + QGraphicsScenePrivate *thatd = const_cast(d); + QRectF oldGrowingBoundingRect = thatd->growingItemsBoundingRect; + thatd->growingItemsBoundingRect |= itemsBoundingRect(); + thatd->dirtyGrowingItemsBoundingRect = false; + if (oldGrowingBoundingRect != thatd->growingItemsBoundingRect) + emit const_cast(this)->sceneRectChanged(thatd->growingItemsBoundingRect); + } + return d->growingItemsBoundingRect; +} +void QGraphicsScene::setSceneRect(const QRectF &rect) +{ + Q_D(QGraphicsScene); + if (rect != d->sceneRect) { + d->hasSceneRect = !rect.isNull(); + d->sceneRect = rect; + emit sceneRectChanged(d->hasSceneRect ? rect : d->growingItemsBoundingRect); + } +} + +/*! + \fn qreal QGraphicsScene::width() const + + This convenience function is equivalent to calling sceneRect().width(). + + \sa height() +*/ + +/*! + \fn qreal QGraphicsScene::height() const + + This convenience function is equivalent to calling \c sceneRect().height(). + + \sa width() +*/ + +/*! + Renders the \a source rect from scene into \a target, using \a painter. This + function is useful for capturing the contents of the scene onto a paint + device, such as a QImage (e.g., to take a screenshot), or for printing + with QPrinter. For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 1 + + If \a source is a null rect, this function will use sceneRect() to + determine what to render. If \a target is a null rect, the dimensions of \a + painter's paint device will be used. + + The source rect contents will be transformed according to \a + aspectRatioMode to fit into the target rect. By default, the aspect ratio + is kept, and \a source is scaled to fit in \a target. + + \sa QGraphicsView::render() +*/ +void QGraphicsScene::render(QPainter *painter, const QRectF &target, const QRectF &source, + Qt::AspectRatioMode aspectRatioMode) +{ + // ### Switch to using the recursive rendering algorithm instead. + + // Default source rect = scene rect + QRectF sourceRect = source; + if (sourceRect.isNull()) + sourceRect = sceneRect(); + + // Default target rect = device rect + QRectF targetRect = target; + if (targetRect.isNull()) { + if (painter->device()->devType() == QInternal::Picture) + targetRect = sourceRect; + else + targetRect.setRect(0, 0, painter->device()->width(), painter->device()->height()); + } + + // Find the ideal x / y scaling ratio to fit \a source into \a target. + qreal xratio = targetRect.width() / sourceRect.width(); + qreal yratio = targetRect.height() / sourceRect.height(); + + // Scale according to the aspect ratio mode. + switch (aspectRatioMode) { + case Qt::KeepAspectRatio: + xratio = yratio = qMin(xratio, yratio); + break; + case Qt::KeepAspectRatioByExpanding: + xratio = yratio = qMax(xratio, yratio); + break; + case Qt::IgnoreAspectRatio: + break; + } + + // Find all items to draw, and reverse the list (we want to draw + // in reverse order). + QList itemList = items(sourceRect, Qt::IntersectsItemBoundingRect); + QGraphicsItem **itemArray = new QGraphicsItem *[itemList.size()]; + int numItems = itemList.size(); + for (int i = 0; i < numItems; ++i) + itemArray[numItems - i - 1] = itemList.at(i); + itemList.clear(); + + painter->save(); + + // Transform the painter. + painter->setClipRect(targetRect); + QTransform painterTransform; + painterTransform *= QTransform() + .translate(targetRect.left(), targetRect.top()) + .scale(xratio, yratio) + .translate(-sourceRect.left(), -sourceRect.top()); + painter->setWorldTransform(painterTransform, true); + + // Two unit vectors. + QLineF v1(0, 0, 1, 0); + QLineF v2(0, 0, 0, 1); + + // Generate the style options + QStyleOptionGraphicsItem *styleOptionArray = new QStyleOptionGraphicsItem[numItems]; + for (int i = 0; i < numItems; ++i) + itemArray[i]->d_ptr->initStyleOption(&styleOptionArray[i], painterTransform, targetRect.toRect()); + + // Render the scene. + drawBackground(painter, sourceRect); + drawItems(painter, numItems, itemArray, styleOptionArray); + drawForeground(painter, sourceRect); + + delete [] itemArray; + delete [] styleOptionArray; + + painter->restore(); +} + +/*! + \property QGraphicsScene::itemIndexMethod + \brief the item indexing method. + + QGraphicsScene applies an indexing algorithm to the scene, to speed up + item discovery functions like items() and itemAt(). Indexing is most + efficient for static scenes (i.e., where items don't move around). For + dynamic scenes, or scenes with many animated items, the index bookkeeping + can outweight the fast lookup speeds. + + For the common case, the default index method BspTreeIndex works fine. If + your scene uses many animations and you are experiencing slowness, you can + disable indexing by calling \c setItemIndexMethod(NoIndex). + + \sa bspTreeDepth +*/ +QGraphicsScene::ItemIndexMethod QGraphicsScene::itemIndexMethod() const +{ + Q_D(const QGraphicsScene); + return d->indexMethod; +} +void QGraphicsScene::setItemIndexMethod(ItemIndexMethod method) +{ + Q_D(QGraphicsScene); + if (d->indexMethod == method) + return; + + d->indexMethod = method; + + QList oldItems = d->index->items(Qt::DescendingOrder); + delete d->index; + if (method == BspTreeIndex) + d->index = new QGraphicsSceneBspTreeIndex(this); + else + d->index = new QGraphicsSceneLinearIndex(this); + for (int i = oldItems.size() - 1; i >= 0; --i) + d->index->addItem(oldItems.at(i)); +} + +/*! + \property QGraphicsScene::bspTreeDepth + \brief the depth of QGraphicsScene's BSP index tree + \since 4.3 + + This property has no effect when NoIndex is used. + + This value determines the depth of QGraphicsScene's BSP tree. The depth + directly affects QGraphicsScene's performance and memory usage; the latter + growing exponentially with the depth of the tree. With an optimal tree + depth, QGraphicsScene can instantly determine the locality of items, even + for scenes with thousands or millions of items. This also greatly improves + rendering performance. + + By default, the value is 0, in which case Qt will guess a reasonable + default depth based on the size, location and number of items in the + scene. If these parameters change frequently, however, you may experience + slowdowns as QGraphicsScene retunes the depth internally. You can avoid + potential slowdowns by fixating the tree depth through setting this + property. + + The depth of the tree and the size of the scene rectangle decide the + granularity of the scene's partitioning. The size of each scene segment is + determined by the following algorithm: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 2 + + The BSP tree has an optimal size when each segment contains between 0 and + 10 items. + + \sa itemIndexMethod +*/ +int QGraphicsScene::bspTreeDepth() const +{ + Q_D(const QGraphicsScene); + QGraphicsSceneBspTreeIndex *bspTree = qobject_cast(d->index); + return bspTree ? bspTree->bspTreeDepth() : 0; +} +void QGraphicsScene::setBspTreeDepth(int depth) +{ + Q_D(QGraphicsScene); + if (depth < 0) { + qWarning("QGraphicsScene::setBspTreeDepth: invalid depth %d ignored; must be >= 0", depth); + return; + } + + QGraphicsSceneBspTreeIndex *bspTree = qobject_cast(d->index); + if (!bspTree) { + qWarning("QGraphicsScene::setBspTreeDepth: can not apply if indexing method is not BSP"); + return; + } + bspTree->setBspTreeDepth(depth); +} + +/*! + \property QGraphicsScene::sortCacheEnabled + \brief whether sort caching is enabled + \since 4.5 + \obsolete + + Since Qt 4.6, this property has no effect. +*/ +bool QGraphicsScene::isSortCacheEnabled() const +{ + Q_D(const QGraphicsScene); + return d->sortCacheEnabled; +} +void QGraphicsScene::setSortCacheEnabled(bool enabled) +{ + Q_D(QGraphicsScene); + if (d->sortCacheEnabled == enabled) + return; + d->sortCacheEnabled = enabled; +} + +/*! + Calculates and returns the bounding rect of all items on the scene. This + function works by iterating over all items, and because if this, it can + be slow for large scenes. + + \sa sceneRect() +*/ +QRectF QGraphicsScene::itemsBoundingRect() const +{ + // Does not take untransformable items into account. + QRectF boundingRect; + foreach (QGraphicsItem *item, items()) + boundingRect |= item->sceneBoundingRect(); + return boundingRect; +} + +/*! + Returns a list of all items in the scene in descending stacking order. + + \sa addItem(), removeItem(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items() const +{ + Q_D(const QGraphicsScene); + return d->index->items(Qt::DescendingOrder); +} + +/*! + Returns an ordered list of all items on the scene. \a order decides the + stacking order. + + \sa addItem(), removeItem(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(Qt::SortOrder order) const +{ + Q_D(const QGraphicsScene); + return d->index->items(order); +} + +/*! + \obsolete + + Returns all visible items at position \a pos in the scene. The items are + listed in descending stacking order (i.e., the first item in the list is the + top-most item, and the last item is the bottom-most item). + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPointF &pos) const +{ + Q_D(const QGraphicsScene); + return d->index->items(pos, Qt::IntersectsItemShape, Qt::DescendingOrder); +} + +/*! + \overload + \obsolete + + Returns all visible items that, depending on \a mode, are either inside or + intersect with the specified \a rectangle. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a rectangle are returned. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QRectF &rectangle, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsScene); + return d->index->items(rectangle, mode, Qt::DescendingOrder); +} + +/*! + \fn QList QGraphicsScene::items(qreal x, qreal y, qreal w, qreal h, Qt::ItemSelectionMode mode) const + \obsolete + \since 4.3 + + This convenience function is equivalent to calling items(QRectF(\a x, \a y, \a w, \a h), \a mode). + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. +*/ + +/*! + \fn QList QGraphicsScene::items(qreal x, qreal y, qreal w, qreal h, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \overload + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are + either inside or intersect with the rectangle defined by \a x, \a y, + \a w and \a h, in a list sorted using \a order. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. +*/ + +/*! + \fn QList QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode) const + \overload + \obsolete + + Returns all visible items that, depending on \a mode, are either inside or + intersect with the polygon \a polygon. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a polygon are returned. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsScene); + return d->index->items(polygon, mode, Qt::DescendingOrder); +} + +/*! + \fn QList QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode) const + \overload + \obsolete + + Returns all visible items that, depending on \a path, are either inside or + intersect with the path \a path. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a path are returned. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsScene); + return d->index->items(path, mode, Qt::DescendingOrder); +} + +/*! + \fn QList QGraphicsScene::items(const QPointF &pos, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are at + the specified \a pos in a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with \a pos are returned. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPointF &pos, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsScene); + return d->index->items(pos, mode, order, deviceTransform); +} + +/*! + \fn QList QGraphicsScene::items(const QRectF &rect, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \overload + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are + either inside or intersect with the specified \a rect and return a + list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a rect are returned. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QRectF &rect, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsScene); + return d->index->items(rect, mode, order, deviceTransform); +} + +/*! + \fn QList QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \overload + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are + either inside or intersect with the specified \a polygon and return + a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a polygon are returned. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsScene); + return d->index->items(polygon, mode, order, deviceTransform); +} + +/*! + \fn QList QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \overload + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are + either inside or intersect with the specified \a path and return a + list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a path are returned. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsScene); + return d->index->items(path, mode, order, deviceTransform); +} + +/*! + Returns a list of all items that collide with \a item. Collisions are + determined by calling QGraphicsItem::collidesWithItem(); the collision + detection is determined by \a mode. By default, all items whose shape + intersects \a item or is contained inside \a item's shape are returned. + + The items are returned in descending stacking order (i.e., the first item + in the list is the uppermost item, and the last item is the lowermost + item). + + \sa items(), itemAt(), QGraphicsItem::collidesWithItem(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::collidingItems(const QGraphicsItem *item, + Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsScene); + if (!item) { + qWarning("QGraphicsScene::collidingItems: cannot find collisions for null item"); + return QList(); + } + + // Does not support ItemIgnoresTransformations. + QList tmp; + foreach (QGraphicsItem *itemInVicinity, d->index->estimateItems(item->sceneBoundingRect(), Qt::DescendingOrder)) { + if (item != itemInVicinity && item->collidesWithItem(itemInVicinity, mode)) + tmp << itemInVicinity; + } + return tmp; +} + +/*! + \overload + \obsolete + + Returns the topmost visible item at the specified \a position, or 0 if + there are no items at this position. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa items(), collidingItems(), {QGraphicsItem#Sorting}{Sorting} +*/ +QGraphicsItem *QGraphicsScene::itemAt(const QPointF &position) const +{ + QList itemsAtPoint = items(position); + return itemsAtPoint.isEmpty() ? 0 : itemsAtPoint.first(); +} + +/*! + \since 4.6 + + Returns the topmost visible item at the specified \a position, or 0 + if there are no items at this position. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa items(), collidingItems(), {QGraphicsItem#Sorting}{Sorting} +*/ +QGraphicsItem *QGraphicsScene::itemAt(const QPointF &position, const QTransform &deviceTransform) const +{ + QList itemsAtPoint = items(position, Qt::IntersectsItemShape, + Qt::DescendingOrder, deviceTransform); + return itemsAtPoint.isEmpty() ? 0 : itemsAtPoint.first(); +} + +/*! + \fn QGraphicsScene::itemAt(qreal x, qreal y, const QTransform &deviceTransform) const + \overload + \since 4.6 + + Returns the topmost item at the position specified by (\a x, \a + y), or 0 if there are no items at this position. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + This convenience function is equivalent to calling \c + {itemAt(QPointF(x, y), deviceTransform)}. +*/ + +/*! + \fn QGraphicsScene::itemAt(qreal x, qreal y) const + \overload + \obsolete + + Returns the topmost item at the position specified by (\a x, \a + y), or 0 if there are no items at this position. + + This convenience function is equivalent to calling \c + {itemAt(QPointF(x, y))}. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. +*/ + +/*! + Returns a list of all currently selected items. The items are + returned in no particular order. + + \sa setSelectionArea() +*/ +QList QGraphicsScene::selectedItems() const +{ + Q_D(const QGraphicsScene); + + // Optimization: Lazily removes items that are not selected. + QGraphicsScene *that = const_cast(this); + QSet actuallySelectedSet; + foreach (QGraphicsItem *item, that->d_func()->selectedItems) { + if (item->isSelected()) + actuallySelectedSet << item; + } + + that->d_func()->selectedItems = actuallySelectedSet; + + return d->selectedItems.values(); +} + +/*! + Returns the selection area that was previously set with + setSelectionArea(), or an empty QPainterPath if no selection area has been + set. + + \sa setSelectionArea() +*/ +QPainterPath QGraphicsScene::selectionArea() const +{ + Q_D(const QGraphicsScene); + return d->selectionArea; +} + +/*! + \since 4.6 + + Sets the selection area to \a path. All items within this area are + immediately selected, and all items outside are unselected. You can get + the list of all selected items by calling selectedItems(). + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + For an item to be selected, it must be marked as \e selectable + (QGraphicsItem::ItemIsSelectable). + + \sa clearSelection(), selectionArea() +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path, const QTransform &deviceTransform) +{ + setSelectionArea(path, Qt::IntersectsItemShape, deviceTransform); +} + +/*! + \obsolete + \overload + + Sets the selection area to \a path. + + This function is deprecated and leads to incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path) +{ + setSelectionArea(path, Qt::IntersectsItemShape, QTransform()); +} + +/*! + \obsolete + \overload + \since 4.3 + + Sets the selection area to \a path using \a mode to determine if items are + included in the selection area. + + \sa clearSelection(), selectionArea() +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode) +{ + setSelectionArea(path, mode, QTransform()); +} + +/*! + \overload + \since 4.6 + + Sets the selection area to \a path using \a mode to determine if items are + included in the selection area. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa clearSelection(), selectionArea() +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode, + const QTransform &deviceTransform) +{ + Q_D(QGraphicsScene); + + // Note: with boolean path operations, we can improve performance here + // quite a lot by "growing" the old path instead of replacing it. That + // allows us to only check the intersect area for changes, instead of + // reevaluating the whole path over again. + d->selectionArea = path; + + QSet unselectItems = d->selectedItems; + + // Disable emitting selectionChanged() for individual items. + ++d->selectionChanging; + bool changed = false; + + // Set all items in path to selected. + foreach (QGraphicsItem *item, items(path, mode, Qt::DescendingOrder, deviceTransform)) { + if (item->flags() & QGraphicsItem::ItemIsSelectable) { + if (!item->isSelected()) + changed = true; + unselectItems.remove(item); + item->setSelected(true); + } + } + + // Unselect all items outside path. + foreach (QGraphicsItem *item, unselectItems) { + item->setSelected(false); + changed = true; + } + + // Reenable emitting selectionChanged() for individual items. + --d->selectionChanging; + + if (!d->selectionChanging && changed) + emit selectionChanged(); +} + +/*! + Clears the current selection. + + \sa setSelectionArea(), selectedItems() +*/ +void QGraphicsScene::clearSelection() +{ + Q_D(QGraphicsScene); + + // Disable emitting selectionChanged + ++d->selectionChanging; + bool changed = !d->selectedItems.isEmpty(); + + foreach (QGraphicsItem *item, d->selectedItems) + item->setSelected(false); + d->selectedItems.clear(); + + // Reenable emitting selectionChanged() for individual items. + --d->selectionChanging; + + if (!d->selectionChanging && changed) + emit selectionChanged(); +} + +/*! + \since 4.4 + + Removes and deletes all items from the scene, but otherwise leaves the + state of the scene unchanged. + + \sa addItem() +*/ +void QGraphicsScene::clear() +{ + Q_D(QGraphicsScene); + // NB! We have to clear the index before deleting items; otherwise the + // index might try to access dangling item pointers. + d->index->clear(); + const QList items = d->topLevelItems; + qDeleteAll(items); + Q_ASSERT(d->topLevelItems.isEmpty()); + d->lastItemCount = 0; + d->allItemsIgnoreHoverEvents = true; + d->allItemsUseDefaultCursor = true; + d->allItemsIgnoreTouchEvents = true; +} + +/*! + Groups all items in \a items into a new QGraphicsItemGroup, and returns a + pointer to the group. The group is created with the common ancestor of \a + items as its parent, and with position (0, 0). The items are all + reparented to the group, and their positions and transformations are + mapped to the group. If \a items is empty, this function will return an + empty top-level QGraphicsItemGroup. + + QGraphicsScene has ownership of the group item; you do not need to delete + it. To dismantle (ungroup) a group, call destroyItemGroup(). + + \sa destroyItemGroup(), QGraphicsItemGroup::addToGroup() +*/ +QGraphicsItemGroup *QGraphicsScene::createItemGroup(const QList &items) +{ + // Build a list of the first item's ancestors + QList ancestors; + int n = 0; + if (!items.isEmpty()) { + QGraphicsItem *parent = items.at(n++); + while ((parent = parent->parentItem())) + ancestors.append(parent); + } + + // Find the common ancestor for all items + QGraphicsItem *commonAncestor = 0; + if (!ancestors.isEmpty()) { + while (n < items.size()) { + int commonIndex = -1; + QGraphicsItem *parent = items.at(n++); + do { + int index = ancestors.indexOf(parent, qMax(0, commonIndex)); + if (index != -1) { + commonIndex = index; + break; + } + } while ((parent = parent->parentItem())); + + if (commonIndex == -1) { + commonAncestor = 0; + break; + } + + commonAncestor = ancestors.at(commonIndex); + } + } + + // Create a new group at that level + QGraphicsItemGroup *group = new QGraphicsItemGroup(commonAncestor); + if (!commonAncestor) + addItem(group); + foreach (QGraphicsItem *item, items) + group->addToGroup(item); + return group; +} + +/*! + Reparents all items in \a group to \a group's parent item, then removes \a + group from the scene, and finally deletes it. The items' positions and + transformations are mapped from the group to the group's parent. + + \sa createItemGroup(), QGraphicsItemGroup::removeFromGroup() +*/ +void QGraphicsScene::destroyItemGroup(QGraphicsItemGroup *group) +{ + foreach (QGraphicsItem *item, group->children()) + group->removeFromGroup(item); + removeItem(group); + delete group; +} + +/*! + Adds or moves the item \a item and all its childen to the scene. + + 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->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->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(this))); + QGraphicsScene *targetScene = qVariantValue(newSceneVariant); + if (targetScene != this) { + if (targetScene && item->scene() != targetScene) + targetScene->addItem(item); + return; + } + + // Detach this item from its parent if the parent's scene is different + // from this scene. + if (QGraphicsItem *itemParent = item->parentItem()) { + if (itemParent->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->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->acceptTouchEvents()) { + d->allItemsIgnoreTouchEvents = false; + d->enableTouchEventsOnViews(); + } + + // Update selection lists + if (item->isSelected()) + d->selectedItems << item; + if (item->isWidget() && item->isVisible() && static_cast(item)->windowType() == Qt::Popup) + d->addPopup(static_cast(item)); + if (item->isPanel() && item->isVisible() && item->panelModality() != QGraphicsItem::NonModal) + d->enterModal(item); + + // Update creation order focus chain. Make sure to leave the widget's + // internal tab order intact. + if (item->isWidget()) { + QGraphicsWidget *widget = static_cast(item); + if (!d->tabFocusFirst) { + // No first tab focus widget - make this the first tab focus + // widget. + d->tabFocusFirst = widget; + } else if (!widget->parentWidget()) { + // Adding a widget that is not part of a tab focus chain. + QGraphicsWidget *last = d->tabFocusFirst->d_func()->focusPrev; + QGraphicsWidget *lastNew = widget->d_func()->focusPrev; + last->d_func()->focusNext = widget; + widget->d_func()->focusPrev = last; + d->tabFocusFirst->d_func()->focusPrev = lastNew; + lastNew->d_func()->focusNext = d->tabFocusFirst; + } + } + + // Add all children recursively + foreach (QGraphicsItem *child, item->children()) + addItem(child); + + // Resolve font and palette. + item->d_ptr->resolveFont(d->font.resolve()); + item->d_ptr->resolvePalette(d->palette.resolve()); + + if (!item->d_ptr->explicitlyHidden) { + if (d->unpolishedItems.isEmpty()) + QMetaObject::invokeMethod(this, "_q_polishItems", Qt::QueuedConnection); + d->unpolishedItems.insert(item); + } + + // 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; + } + } + + // 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(0))); + QGraphicsScene *targetScene = qVariantValue(newSceneVariant); + if (targetScene != 0 && targetScene != this) { + targetScene->addItem(item); + return; + } + + d->removeItemHelper(item); + + // Deliver post-change notification + item->itemChange(QGraphicsItem::ItemSceneHasChanged, newSceneVariant); + + d->updateInputMethodSensitivityInViews(); +} + +/*! + Returns the scene's current focus item, or 0 if no item currently has + focus. + + The focus item receives keyboard input when the scene receives a + key event. + + \sa setFocusItem(), QGraphicsItem::hasFocus() +*/ +QGraphicsItem *QGraphicsScene::focusItem() const +{ + Q_D(const QGraphicsScene); + return d->focusItem; +} + +/*! + 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; + 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); + view->d_func()->updateRegion(QRegion(view->mapFromScene(rect).boundingRect())); + } + } else { + d->updatedRects << rect; + } + } + + if (!d->calledEmitUpdated) { + d->calledEmitUpdated = true; + QMetaObject::invokeMethod(this, "_q_emitUpdated", Qt::QueuedConnection); + } +} + +/*! + \fn void QGraphicsScene::update(qreal x, qreal y, qreal w, qreal h) + \overload + \since 4.3 + + This function is equivalent to calling update(QRectF(\a x, \a y, \a w, + \a h)); +*/ + +/*! + Invalidates and schedules a redraw of the \a layers in \a rect on the + scene. Any cached content in \a layers is unconditionally invalidated and + redrawn. + + You can use this function overload to notify QGraphicsScene of changes to + the background or the foreground of the scene. This function is commonly + used for scenes with tile-based backgrounds to notify changes when + QGraphicsView has enabled + \l{QGraphicsView::CacheBackground}{CacheBackground}. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 5 + + Note that QGraphicsView currently supports background caching only (see + QGraphicsView::CacheBackground). This function is equivalent to calling + update() if any layer but BackgroundLayer is passed. + + \sa QGraphicsView::resetCachedContent() +*/ +void QGraphicsScene::invalidate(const QRectF &rect, SceneLayers layers) +{ + foreach (QGraphicsView *view, views()) + view->invalidateScene(rect, layers); + update(rect); +} + +/*! + \fn void QGraphicsScene::invalidate(qreal x, qreal y, qreal w, qreal h, SceneLayers layers) + \overload + \since 4.3 + + This convenience function is equivalent to calling invalidate(QRectF(\a x, \a + y, \a w, \a h), \a layers); +*/ + +/*! + Returns a list of all the views that display this scene. + + \sa QGraphicsView::scene() +*/ +QList QGraphicsScene::views() const +{ + Q_D(const QGraphicsScene); + return d->views; +} + +/*! + This slot \e advances the scene by one step, by calling + QGraphicsItem::advance() for all items on the scene. This is done in two + phases: in the first phase, all items are notified that the scene is about + to change, and in the second phase all items are notified that they can + move. In the first phase, QGraphicsItem::advance() is called passing a + value of 0 as an argument, and 1 is passed in the second phase. + + \sa QGraphicsItem::advance(), QGraphicsItemAnimation, QTimeLine +*/ +void QGraphicsScene::advance() +{ + for (int i = 0; i < 2; ++i) { + foreach (QGraphicsItem *item, items()) + item->advance(i); + } +} + +/*! + Processes the event \a event, and dispatches it to the respective + event handlers. + + In addition to calling the convenience event handlers, this + function is responsible for converting mouse move events to hover + events for when there is no mouse grabber item. Hover events are + delivered directly to items; there is no convenience function for + them. + + Unlike QWidget, QGraphicsScene does not have the convenience functions + \l{QWidget::}{enterEvent()} and \l{QWidget::}{leaveEvent()}. Use this + function to obtain those events instead. + + \sa contextMenuEvent(), keyPressEvent(), keyReleaseEvent(), + mousePressEvent(), mouseMoveEvent(), mouseReleaseEvent(), + mouseDoubleClickEvent(), focusInEvent(), focusOutEvent() +*/ +bool QGraphicsScene::event(QEvent *event) +{ + Q_D(QGraphicsScene); + + switch (event->type()) { + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverLeave: + case QEvent::GraphicsSceneHoverMove: + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + // Reset the under-mouse list to ensure that this event gets fresh + // item-under-mouse data. Be careful about this list; if people delete + // items from inside event handlers, this list can quickly end up + // having stale pointers in it. We need to clear it before dispatching + // events that use it. + // ### this should only be cleared if we received a new mouse move event, + // which relies on us fixing the replay mechanism in QGraphicsView. + d->cachedItemsUnderMouse.clear(); + default: + break; + } + + switch (event->type()) { + case QEvent::GraphicsSceneDragEnter: + dragEnterEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDragMove: + dragMoveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDragLeave: + dragLeaveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDrop: + dropEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneContextMenu: + contextMenuEvent(static_cast(event)); + break; + case QEvent::KeyPress: + if (!d->focusItem) { + QKeyEvent *k = static_cast(event); + if (k->key() == Qt::Key_Tab || k->key() == Qt::Key_Backtab) { + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + bool res = false; + if (k->key() == Qt::Key_Backtab + || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) { + res = focusNextPrevChild(false); + } else if (k->key() == Qt::Key_Tab) { + res = focusNextPrevChild(true); + } + if (!res) + event->ignore(); + return true; + } + } + } + keyPressEvent(static_cast(event)); + break; + case QEvent::KeyRelease: + keyReleaseEvent(static_cast(event)); + break; + case QEvent::ShortcutOverride: { + QGraphicsItem *parent = focusItem(); + while (parent) { + d->sendEvent(parent, event); + if (event->isAccepted()) + return true; + parent = parent->parentItem(); + } + } + return false; + case QEvent::GraphicsSceneMouseMove: + { + QGraphicsSceneMouseEvent *mouseEvent = static_cast(event); + d->lastSceneMousePos = mouseEvent->scenePos(); + mouseMoveEvent(mouseEvent); + break; + } + case QEvent::GraphicsSceneMousePress: + mousePressEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseRelease: + mouseReleaseEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseDoubleClick: + mouseDoubleClickEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneWheel: + wheelEvent(static_cast(event)); + break; + case QEvent::FocusIn: + focusInEvent(static_cast(event)); + break; + case QEvent::FocusOut: + focusOutEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverLeave: + case QEvent::GraphicsSceneHoverMove: + { + QGraphicsSceneHoverEvent *hoverEvent = static_cast(event); + d->lastSceneMousePos = hoverEvent->scenePos(); + d->dispatchHoverEvent(hoverEvent); + break; + } + case QEvent::Leave: + d->leaveScene(); + break; + case QEvent::GraphicsSceneHelp: + helpEvent(static_cast(event)); + break; + case QEvent::InputMethod: + inputMethodEvent(static_cast(event)); + break; + case QEvent::WindowActivate: + if (!d->activationRefCount++) { + if (d->lastActivePanel) { + // Activate the last panel. + d->setActivePanelHelper(d->lastActivePanel, true); + } else if (d->tabFocusFirst && d->tabFocusFirst->isPanel()) { + // Activate the panel of the first item in the tab focus + // chain. + d->setActivePanelHelper(d->tabFocusFirst, true); + } else { + // Activate all toplevel items. + QEvent event(QEvent::WindowActivate); + foreach (QGraphicsItem *item, items()) { + if (item->isVisible() && !item->isPanel() && !item->parentItem()) + sendEvent(item, &event); + } + } + } + break; + case QEvent::WindowDeactivate: + if (!--d->activationRefCount) { + if (d->activePanel) { + // Deactivate the active panel (but keep it so we can + // reactivate it later). + QGraphicsItem *lastActivePanel = d->activePanel; + d->setActivePanelHelper(0, true); + d->lastActivePanel = lastActivePanel; + } else { + // Activate all toplevel items. + QEvent event(QEvent::WindowDeactivate); + foreach (QGraphicsItem *item, items()) { + if (item->isVisible() && !item->isPanel() && !item->parentItem()) + sendEvent(item, &event); + } + } + } + break; + case QEvent::ApplicationFontChange: { + // Resolve the existing scene font. + d->resolveFont(); + break; + } + case QEvent::FontChange: + // Update the entire scene when the font changes. + update(); + break; + case QEvent::ApplicationPaletteChange: { + // Resolve the existing scene palette. + d->resolvePalette(); + break; + } + case QEvent::PaletteChange: + // Update the entire scene when the palette changes. + update(); + break; + case QEvent::StyleChange: + // Reresolve all widgets' styles. Update all top-level widgets' + // geometries that do not have an explicit style set. + update(); + break; + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + d->touchEventHandler(static_cast(event)); + break; + case QEvent::Gesture: + case QEvent::GestureOverride: + d->gestureEventHandler(static_cast(event)); + break; + 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->lastFocusItem) { + // Set focus on the last focus item + setFocusItem(d->lastFocusItem, 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; + setFocusItem(0, focusEvent->reason()); + + // Remove all popups when the scene loses focus. + if (!d->popupWidgets.isEmpty()) + d->removePopup(d->popupWidgets.first()); +} + +/*! + This event handler, for event \a helpEvent, can be + reimplemented in a subclass to receive help events. The events + are of type QEvent::ToolTip, which are created when a tooltip is + requested. + + The default implementation shows the tooltip of the topmost + item, i.e., the item with the highest z-value, at the mouse + cursor position. If no item has a tooltip set, this function + does nothing. + + \sa QGraphicsItem::toolTip(), QGraphicsSceneHelpEvent +*/ +void QGraphicsScene::helpEvent(QGraphicsSceneHelpEvent *helpEvent) +{ +#ifdef QT_NO_TOOLTIP + Q_UNUSED(helpEvent); +#else + // Find the first item that does tooltips + Q_D(QGraphicsScene); + QList itemsAtPos = d->itemsAtPosition(helpEvent->screenPos(), + helpEvent->scenePos(), + helpEvent->widget()); + QGraphicsItem *toolTipItem = 0; + for (int i = 0; i < itemsAtPos.size(); ++i) { + QGraphicsItem *tmp = itemsAtPos.at(i); + if (!tmp->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->isBlockedByModalPanel() && + (item->acceptHoverEvents() + || (item->isWidget() + && static_cast(item)->d_func()->hasDecoration()))); +} + +/*! + This event handler, for event \a hoverEvent, can be reimplemented in a + subclass to receive hover enter events. The default implementation + forwards the event to the topmost item that accepts hover events at the + scene position from the event. + + \sa QGraphicsItem::hoverEvent(), QGraphicsItem::setAcceptHoverEvents() +*/ +bool QGraphicsScenePrivate::dispatchHoverEvent(QGraphicsSceneHoverEvent *hoverEvent) +{ + if (allItemsIgnoreHoverEvents) + return false; + + // Find the first item that accepts hover events, reusing earlier + // calculated data is possible. + if (cachedItemsUnderMouse.isEmpty()) { + cachedItemsUnderMouse = itemsAtPosition(hoverEvent->screenPos(), + hoverEvent->scenePos(), + hoverEvent->widget()); + } + + QGraphicsItem *item = 0; + for (int i = 0; i < cachedItemsUnderMouse.size(); ++i) { + QGraphicsItem *tmp = cachedItemsUnderMouse.at(i); + if (itemAcceptsHoverEvents_helper(tmp)) { + item = tmp; + break; + } + } + + // Find the common ancestor item for the new topmost hoverItem and the + // last item in the hoverItem list. + QGraphicsItem *commonAncestorItem = (item && !hoverItems.isEmpty()) ? item->commonAncestorItem(hoverItems.last()) : 0; + while (commonAncestorItem && !itemAcceptsHoverEvents_helper(commonAncestorItem)) + commonAncestorItem = commonAncestorItem->parentItem(); + if (commonAncestorItem && commonAncestorItem->panel() != item->panel()) { + // The common ancestor isn't in the same panel as the two hovered + // items. + commonAncestorItem = 0; + } + + // Check if the common ancestor item is known. + int index = commonAncestorItem ? hoverItems.indexOf(commonAncestorItem) : -1; + // Send hover leaves to any existing hovered children of the common + // ancestor item. + for (int i = hoverItems.size() - 1; i > index; --i) { + QGraphicsItem *lastItem = hoverItems.takeLast(); + if (itemAcceptsHoverEvents_helper(lastItem)) + sendHoverEvent(QEvent::GraphicsSceneHoverLeave, lastItem, hoverEvent); + } + + // Item is a child of a known item. Generate enter events for the + // missing links. + QList parents; + QGraphicsItem *parent = item; + while (parent && parent != commonAncestorItem) { + parents.prepend(parent); + if (parent->isPanel()) { + // Stop at the panel - we don't deliver beyond this point. + break; + } + parent = parent->parentItem(); + } + for (int i = 0; i < parents.size(); ++i) { + parent = parents.at(i); + hoverItems << parent; + if (itemAcceptsHoverEvents_helper(parent)) + sendHoverEvent(QEvent::GraphicsSceneHoverEnter, parent, hoverEvent); + } + + // Generate a move event for the item itself + if (item + && !hoverItems.isEmpty() + && item == hoverItems.last()) { + sendHoverEvent(QEvent::GraphicsSceneHoverMove, item, hoverEvent); + return true; + } + return false; +} + +/*! + \internal + + Handles all actions necessary to clean up the scene when the mouse leaves + the view. +*/ +void QGraphicsScenePrivate::leaveScene() +{ + Q_Q(QGraphicsScene); +#ifndef QT_NO_TOOLTIP + QToolTip::hideText(); +#endif + // Send HoverLeave events to all existing hover items, topmost first. + QGraphicsView *senderWidget = qobject_cast(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 wheelCandidates = d->itemsAtPosition(wheelEvent->screenPos(), + wheelEvent->scenePos(), + wheelEvent->widget()); + + bool hasSetFocus = false; + foreach (QGraphicsItem *item, wheelCandidates) { + if (!hasSetFocus && item->isEnabled() + && ((item->flags() & QGraphicsItem::ItemIsFocusable) && item->d_ptr->mouseSetsFocus)) { + if (item->isWidget() && static_cast(item)->focusPolicy() == Qt::WheelFocus) { + hasSetFocus = true; + if (item != focusItem()) + setFocusItem(item, Qt::MouseFocusReason); + } + } + + wheelEvent->setPos(item->d_ptr->genericMapFromScene(wheelEvent->scenePos(), + wheelEvent->widget())); + wheelEvent->accept(); + bool isPanel = item->isPanel(); + d->sendEvent(item, wheelEvent); + if (isPanel || wheelEvent->isAccepted()) + break; + } +} + +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive input method events for the scene. + + The default implementation forwards the event to the focusItem(). + If no item currently has focus or the current focus item does not + accept input methods, this function does nothing. + + \sa QGraphicsItem::inputMethodEvent() +*/ +void QGraphicsScene::inputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QGraphicsScene); + if (d->focusItem && (d->focusItem->flags() & QGraphicsItem::ItemAcceptsInputMethod)) + d->sendEvent(d->focusItem, event); +} + +/*! + Draws the background of the scene using \a painter, before any items and + the foreground are drawn. Reimplement this function to provide a custom + background for the scene. + + All painting is done in \e scene coordinates. The \a rect + parameter is the exposed rectangle. + + If all you want is to define a color, texture, or gradient for the + background, you can call setBackgroundBrush() instead. + + \sa drawForeground(), drawItems() +*/ +void QGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect) +{ + Q_D(QGraphicsScene); + + if (d->backgroundBrush.style() != Qt::NoBrush) { + if (d->painterStateProtection) + painter->save(); + painter->setBrushOrigin(0, 0); + painter->fillRect(rect, backgroundBrush()); + if (d->painterStateProtection) + painter->restore(); + } +} + +/*! + Draws the foreground of the scene using \a painter, after the background + and all items have been drawn. Reimplement this function to provide a + custom foreground for the scene. + + All painting is done in \e scene coordinates. The \a rect + parameter is the exposed rectangle. + + If all you want is to define a color, texture or gradient for the + foreground, you can call setForegroundBrush() instead. + + \sa drawBackground(), drawItems() +*/ +void QGraphicsScene::drawForeground(QPainter *painter, const QRectF &rect) +{ + Q_D(QGraphicsScene); + + if (d->foregroundBrush.style() != Qt::NoBrush) { + if (d->painterStateProtection) + painter->save(); + painter->setBrushOrigin(0, 0); + painter->fillRect(rect, foregroundBrush()); + if (d->painterStateProtection) + painter->restore(); + } +} + +static void _q_paintItem(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget, + bool useWindowOpacity, bool painterStateProtection) +{ + if (!item->isWidget()) { + item->paint(painter, option, widget); + return; + } + QGraphicsWidget *widgetItem = static_cast(item); + QGraphicsProxyWidget *proxy = qobject_cast(widgetItem); + const qreal windowOpacity = (proxy && proxy->widget() && useWindowOpacity) + ? proxy->widget()->windowOpacity() : 1.0; + const qreal oldPainterOpacity = painter->opacity(); + + if (qFuzzyIsNull(windowOpacity)) + return; + // Set new painter opacity. + if (windowOpacity < 1.0) + painter->setOpacity(oldPainterOpacity * windowOpacity); + + // set layoutdirection on the painter + Qt::LayoutDirection oldLayoutDirection = painter->layoutDirection(); + painter->setLayoutDirection(widgetItem->layoutDirection()); + + if (widgetItem->isWindow() && widgetItem->windowType() != Qt::Popup && widgetItem->windowType() != Qt::ToolTip + && !(widgetItem->windowFlags() & Qt::FramelessWindowHint)) { + if (painterStateProtection) + painter->save(); + widgetItem->paintWindowFrame(painter, option, widget); + if (painterStateProtection) + painter->restore(); + } + + 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.numRects() == 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(); + } +} + +/*! + \internal + + Draws items directly, or using cache. +*/ +void QGraphicsScenePrivate::drawItemHelper(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget, + bool painterStateProtection) +{ + QGraphicsItemPrivate *itemd = item->d_ptr.data(); + QGraphicsItem::CacheMode cacheMode = QGraphicsItem::CacheMode(itemd->cacheMode); + + // Render directly, using no cache. + if (cacheMode == QGraphicsItem::NoCache +#ifdef Q_WS_X11 + || !X11->use_xrender +#endif + ) { + _q_paintItem(static_cast(item), painter, option, widget, true, painterStateProtection); + return; + } + + const qreal oldPainterOpacity = painter->opacity(); + qreal newPainterOpacity = oldPainterOpacity; + QGraphicsProxyWidget *proxy = item->isWidget() ? qobject_cast(static_cast(item)) : 0; + if (proxy && proxy->widget()) { + const qreal windowOpacity = proxy->widget()->windowOpacity(); + if (windowOpacity < 1.0) + newPainterOpacity *= windowOpacity; + } + + // Item's (local) bounding rect + QRectF brect = item->boundingRect(); + QRectF adjustedBrect(brect); + _q_adjustRect(&adjustedBrect); + if (adjustedBrect.isEmpty()) + return; + + // Fetch the off-screen transparent buffer and exposed area info. + QPixmapCache::Key pixmapKey; + QPixmap pix; + bool pixmapFound; + QGraphicsItemCache *itemCache = itemd->extraItemCache(); + if (cacheMode == QGraphicsItem::ItemCoordinateCache) { + if (itemCache->boundingRect != brect.toRect()) { + itemCache->boundingRect = brect.toRect(); + itemCache->allExposed = true; + itemCache->exposed.clear(); + } + 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; + QRectF brectAligned = brect.toAlignedRect(); + if ((fixedCacheSize = itemCache->fixedSize.isValid())) { + pixmapSize = itemCache->fixedSize; + } else { + pixmapSize = brectAligned.size().toSize(); + } + + // Create or recreate the pixmap. + int adjust = itemCache->fixedSize.isValid() ? 0 : 2; + QSize adjustSize(adjust*2, adjust*2); + QRectF br = brectAligned.adjusted(-adjust, -adjust, adjust, adjust); + if (pix.isNull() || (!fixedCacheSize && (pixmapSize + adjustSize) != pix.size())) { + pix = QPixmap(pixmapSize + adjustSize); + 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, pix, QRectF(QPointF(), pix.size())); + painter->setOpacity(oldPainterOpacity); + } else { + painter->drawPixmap(br, pix, QRectF(QPointF(), pix.size())); + } + return; + } + + // Render using device coordinate cache mode. + if (cacheMode == QGraphicsItem::DeviceCoordinateCache) { + // Find the item's bounds in device coordinates. + QRectF deviceBounds = painter->worldTransform().mapRect(brect); + QRect deviceRect = deviceBounds.toRect().adjusted(-1, -1, 1, 1); + if (deviceRect.isEmpty()) + return; + QRect viewRect = widget ? widget->rect() : QRect(); + if (widget && !viewRect.intersects(deviceRect)) + return; + + // Resort to direct rendering if the device rect exceeds the + // (optional) maximum bounds. (QGraphicsSvgItem uses this). + QSize maximumCacheSize = + itemd->extra(QGraphicsItemPrivate::ExtraMaxDeviceCoordCacheSize).toSize(); + if (!maximumCacheSize.isEmpty() + && (deviceRect.width() > maximumCacheSize.width() + || deviceRect.height() > maximumCacheSize.height())) { + _q_paintItem(static_cast(item), painter, option, widget, + oldPainterOpacity != newPainterOpacity, painterStateProtection); + return; + } + + // Create or reuse offscreen pixmap, possibly scroll/blit from the old one. + 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(); + if (!invertable || diff.type() > QTransform::TxTranslate) { + pixModified = true; + itemCache->allExposed = true; + itemCache->exposed.clear(); + pix = QPixmap(); + } + + // ### This is a pretty bad way to determine when to start partial + // exposure for DeviceCoordinateCache but it's the least intrusive + // approach for now. +#if 0 + // Only if the device rect isn't fully contained. + bool allowPartialCacheExposure = !viewRect.contains(deviceRect); +#else + // Only if deviceRect is 20% taller or wider than the desktop. + QRect desktopRect = QApplication::desktop()->availableGeometry(widget); + bool allowPartialCacheExposure = (desktopRect.width() * 1.2 < deviceRect.width() + || desktopRect.height() * 1.2 < deviceRect.height()); +#endif + QRegion scrollExposure; + if (deviceData->cacheIndent != QPoint() || allowPartialCacheExposure) { + // Part of pixmap is drawn. Either device contains viewrect (big + // item covers whole screen) or parts of device are outside the + // viewport. In either case the device rect must be the intersect + // between the two. + int dx = deviceRect.left() < viewRect.left() ? viewRect.left() - deviceRect.left() : 0; + int dy = deviceRect.top() < viewRect.top() ? viewRect.top() - deviceRect.top() : 0; + QPoint newCacheIndent(dx, dy); + deviceRect &= viewRect; + + if (pix.isNull()) { + deviceData->cacheIndent = QPoint(); + itemCache->allExposed = true; + itemCache->exposed.clear(); + pixModified = true; + } + + // Copy / "scroll" the old pixmap onto the new ole and calculate + // scrolled exposure. + if (newCacheIndent != deviceData->cacheIndent || deviceRect.size() != pix.size()) { + QPoint diff = newCacheIndent - deviceData->cacheIndent; + QPixmap newPix(deviceRect.size()); + // ### Investigate removing this fill (test with Plasma and + // graphicssystem raster). + newPix.fill(Qt::transparent); + if (!pix.isNull()) { + QPainter newPixPainter(&newPix); + newPixPainter.drawPixmap(-diff, pix); + newPixPainter.end(); + } + QRegion exposed; + exposed += newPix.rect(); + if (!pix.isNull()) + exposed -= QRect(-diff, pix.size()); + scrollExposure = exposed; + + pix = newPix; + pixModified = true; + } + deviceData->cacheIndent = newCacheIndent; + } else { + // Full pixmap is drawn. + deviceData->cacheIndent = QPoint(); + + // Auto-adjust the pixmap size. + if (deviceRect.size() != pix.size()) { + // exposed needs to cover the whole pixmap + pix = QPixmap(deviceRect.size()); + pixModified = true; + itemCache->allExposed = true; + itemCache->exposed.clear(); + } + } + + // Check for newly invalidated areas. + if (itemCache->allExposed || !itemCache->exposed.isEmpty() || !scrollExposure.isEmpty()) { + //We know that we will modify the pixmap, removing it from the cache + //will detach the one we have and avoid a deep copy + if (pixmapFound) + QPixmapCache::remove(pixmapKey); + + // Construct an item-to-pixmap transform. + QPointF p = deviceRect.topLeft(); + QTransform itemToPixmap = painter->worldTransform(); + if (!p.isNull()) + itemToPixmap *= QTransform::fromTranslate(-p.x(), -p.y()); + + // Map the item's logical expose to pixmap coordinates. + QRegion pixmapExposed = scrollExposure; + if (!itemCache->allExposed) { + const QVector &exposed = itemCache->exposed; + for (int i = 0; i < exposed.size(); ++i) + pixmapExposed += itemToPixmap.mapRect(exposed.at(i)).toRect().adjusted(-1, -1, 1, 1); + } + + // Calculate the style option's exposedRect. + QRectF br; + if (itemCache->allExposed) { + br = item->boundingRect(); + } else { + const QVector &exposed = itemCache->exposed; + for (int i = 0; i < exposed.size(); ++i) + br |= exposed.at(i); + QTransform pixmapToItem = itemToPixmap.inverted(); + foreach (QRect r, scrollExposure.rects()) + br |= pixmapToItem.mapRect(r); + } + styleOptionTmp = *option; + styleOptionTmp.exposedRect = br.adjusted(-1, -1, 1, 1); + + // Render the exposed areas. + _q_paintIntoCache(&pix, item, pixmapExposed, itemToPixmap, painter->renderHints(), + &styleOptionTmp, painterStateProtection); + + // Reset expose data. + pixModified = true; + itemCache->allExposed = false; + itemCache->exposed.clear(); + } + + if (pixModified) { + // Insert this pixmap into the cache. + deviceData->key = QPixmapCache::insert(pix); + } + + // Redraw the exposed area using an untransformed painter. This + // effectively becomes a bitblit that does not transform the cache. + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + if (newPainterOpacity != oldPainterOpacity) { + painter->setOpacity(newPainterOpacity); + painter->drawPixmap(deviceRect.topLeft(), pix); + painter->setOpacity(oldPainterOpacity); + } else { + painter->drawPixmap(deviceRect.topLeft(), pix); + } + painter->setWorldTransform(restoreTransform); + return; + } +} + +void QGraphicsScenePrivate::drawItems(QPainter *painter, const QTransform *const viewTransform, + QRegion *exposedRegion, QWidget *widget) +{ + // Make sure we don't have unpolished items before we draw. + if (!unpolishedItems.isEmpty()) + _q_polishItems(); + + QRectF exposedSceneRect; + if (exposedRegion && indexMethod != QGraphicsScene::NoIndex) { + exposedSceneRect = exposedRegion->boundingRect().adjusted(-1, -1, 1, 1); + if (viewTransform) + exposedSceneRect = viewTransform->inverted().mapRect(exposedSceneRect); + } + const QList tli = index->estimateTopLevelItems(exposedSceneRect, Qt::AscendingOrder); + for (int i = 0; i < tli.size(); ++i) + drawSubtreeRecursive(tli.at(i), painter, viewTransform, exposedRegion, widget); +} + +void QGraphicsScenePrivate::drawSubtreeRecursive(QGraphicsItem *item, QPainter *painter, + const QTransform *const viewTransform, + QRegion *exposedRegion, QWidget *widget, + qreal parentOpacity, const QTransform *const effectTransform) +{ + Q_ASSERT(item); + + if (!item->d_ptr->visible) + return; + + const bool itemHasContents = !(item->d_ptr->flags & QGraphicsItem::ItemHasNoContents); + const bool itemHasChildren = !item->d_ptr->children.isEmpty(); + if (!itemHasContents && !itemHasChildren) + return; // Item has neither contents nor children!(?) + + const qreal opacity = item->d_ptr->combineOpacityFromParent(parentOpacity); + const bool itemIsFullyTransparent = (opacity < 0.0001); + 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()).toRect() + : transformPtr->mapRect(brect).toRect(); + if (widget) + item->d_ptr->paintedViewBoundingRects.insert(widget, viewBoundingRect); + viewBoundingRect.adjust(-1, -1, 1, 1); + drawItem = exposedRegion ? exposedRegion->intersects(viewBoundingRect) : !viewBoundingRect.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; + + if (item->d_ptr->graphicsEffect && item->d_ptr->graphicsEffect->isEnabled()) { + ENSURE_TRANSFORM_PTR; + QGraphicsItemPaintInfo info(viewTransform, transformPtr, effectTransform, exposedRegion, widget, &styleOptionTmp, + painter, opacity, wasDirtyParentSceneTransform, drawItem); + QGraphicsEffectSource *source = item->d_ptr->graphicsEffect->d_func()->source; + QGraphicsItemEffectSourcePrivate *sourced = static_cast + (source->d_func()); + sourced->info = &info; + const QTransform restoreTransform = painter->worldTransform(); + if (effectTransform) + painter->setWorldTransform(*transformPtr * *effectTransform); + else + painter->setWorldTransform(*transformPtr); + painter->setOpacity(opacity); + + if (sourced->lastEffectTransform != painter->worldTransform()) { + sourced->lastEffectTransform = painter->worldTransform(); + sourced->invalidateCache(); + } + item->d_ptr->graphicsEffect->draw(painter, source); + painter->setWorldTransform(restoreTransform); + sourced->info = 0; + } else { + draw(item, painter, viewTransform, transformPtr, exposedRegion, widget, opacity, + effectTransform, wasDirtyParentSceneTransform, drawItem); + } +} + +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 = (opacity < 0.0001); + const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape); + const bool itemHasChildren = !item->d_ptr->children.isEmpty(); + + int i = 0; + if (itemHasChildren) { + item->d_ptr->ensureSortedChildren(); + + if (itemClipsChildrenToShape) { + painter->save(); + Q_ASSERT(transformPtr); + if (effectTransform) + painter->setWorldTransform(*transformPtr * *effectTransform); + else + painter->setWorldTransform(*transformPtr); + painter->setClipPath(item->shape(), Qt::IntersectClip); + } + + // 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; + const bool savePainter = itemClipsToShape || painterStateProtection; + if (savePainter) + painter->save(); + + if (!itemHasChildren || !itemClipsChildrenToShape) { + if (effectTransform) + painter->setWorldTransform(*transformPtr * *effectTransform); + else + painter->setWorldTransform(*transformPtr); + } + + if (itemClipsToShape) + painter->setClipPath(item->shape(), Qt::IntersectClip); + 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 (savePainter) + painter->restore(); + } + + // Draw children in front + if (itemHasChildren) { + for (; i < item->d_ptr->children.size(); ++i) { + QGraphicsItem *child = item->d_ptr->children.at(i); + if (wasDirtyParentSceneTransform) + child->d_ptr->dirtySceneTransform = 1; + if (itemIsFullyTransparent && !(child->d_ptr->flags & QGraphicsItem::ItemIgnoresParentOpacity)) + continue; + drawSubtreeRecursive(child, painter, viewTransform, exposedRegion, widget, opacity, effectTransform); + } + } + + // Restore child clip + if (itemHasChildren && itemClipsChildrenToShape) + painter->restore(); +} + +void QGraphicsScenePrivate::markDirty(QGraphicsItem *item, const QRectF &rect, bool invalidateChildren, + bool maybeDirtyClipPath, bool force, bool ignoreOpacity, + bool removingItemFromScene) +{ + Q_ASSERT(item); + if (updateAll) + return; + + if (item->d_ptr->discardUpdateRequest(/*ignoreClipping=*/maybeDirtyClipPath, + /*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) { + 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 + && !item->d_ptr->graphicsEffect; + 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; + } + + 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; + + QGraphicsItem *p = item->d_ptr->parent; + while (p) { + p->d_ptr->dirtyChildren = 1; + if (p->d_ptr->graphicsEffect && p->d_ptr->graphicsEffect->isEnabled()) { + p->d_ptr->dirty = 1; + p->d_ptr->fullUpdatePending = 1; + } + p = p->d_ptr->parent; + } +} + +static inline bool updateHelper(QGraphicsViewPrivate *view, QGraphicsItemPrivate *item, + const QRectF &rect, bool itemIsUntransformable) +{ + Q_ASSERT(view); + Q_ASSERT(item); + + QGraphicsItem *itemq = static_cast(item->q_ptr); + QGraphicsView *viewq = static_cast(view->q_ptr); + + if (itemIsUntransformable) { + const QTransform xform = itemq->deviceTransform(viewq->viewportTransform()); + if (!item->hasBoundingRegionGranularity) + return view->updateRect(xform.mapRect(rect).toRect()); + return view->updateRegion(xform.map(QRegion(rect.toRect()))); + } + + if (item->sceneTransformTranslateOnly && view->identityMatrix) { + const qreal dx = item->sceneTransform.dx(); + const qreal dy = item->sceneTransform.dy(); + if (!item->hasBoundingRegionGranularity) { + QRectF r(rect); + r.translate(dx - view->horizontalScroll(), dy - view->verticalScroll()); + return view->updateRect(r.toRect()); + } + QRegion r(rect.toRect()); + r.translate(qRound(dx) - view->horizontalScroll(), qRound(dy) - view->verticalScroll()); + return view->updateRegion(r); + } + + if (!viewq->isTransformed()) { + if (!item->hasBoundingRegionGranularity) + return view->updateRect(item->sceneTransform.mapRect(rect).toRect()); + return view->updateRegion(item->sceneTransform.map(QRegion(rect.toRect()))); + } + + QTransform xform = item->sceneTransform; + xform *= viewq->viewportTransform(); + if (!item->hasBoundingRegionGranularity) + return view->updateRect(xform.mapRect(rect).toRect()); + return view->updateRegion(xform.map(QRegion(rect.toRect()))); +} + +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 && opacity < 0.0001; + 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 { + q->update(item->d_ptr->sceneTransform.mapRect(itemBoundingRect)); + } + } 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.isEmpty()) { + 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) { + if (!dirtyAncestorContainsChildren) { + dirtyAncestorContainsChildren = item->d_ptr->fullUpdatePending + && (item->d_ptr->flags & QGraphicsItem::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); + } + } else if (wasDirtyParentSceneTransform) { + item->d_ptr->invalidateChildrenSceneTransform(); + } + + resetDirtyItem(item); +} + +/*! + 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 + + \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(); + + QTransform viewTransform = painter->worldTransform(); + Q_UNUSED(options); + + // Determine view, expose and flags. + QGraphicsView *view = widget ? qobject_cast(widget->parentWidget()) : 0; + QRegion *expose = 0; + if (view) + expose = &view->d_func()->exposedRegion; + + // Find all toplevels, they are already sorted. + QList topLevelItems; + for (int i = 0; i < numItems; ++i) { + QGraphicsItem *item = items[i]->topLevelItem(); + if (!item->d_ptr->itemDiscovered) { + topLevelItems << item; + item->d_ptr->itemDiscovered = 1; + d->drawSubtreeRecursive(item, painter, &viewTransform, expose, widget); + } + } + + // Reset discovery bits. + for (int i = 0; i < topLevelItems.size(); ++i) + topLevelItems.at(i)->d_ptr->itemDiscovered = 0; + + painter->setWorldTransform(viewTransform); +} + +/*! + \since 4.4 + + Finds a new widget to give the keyboard focus to, as appropriate for Tab + and Shift+Tab, and returns true if it can find a new widget, or false if + it cannot. If \a next is true, this function searches forward; if \a next + is false, it searches backward. + + You can reimplement this function in a subclass of QGraphicsScene to + provide fine-grained control over how tab focus passes inside your + scene. The default implementation is based on the tab focus chain defined + by QGraphicsWidget::setTabOrder(). +*/ +bool QGraphicsScene::focusNextPrevChild(bool next) +{ + Q_D(QGraphicsScene); + + QGraphicsItem *item = focusItem(); + if (item && !item->isWidget()) { + // Tab out of the scene. + return false; + } + if (!item) { + if (d->lastFocusItem && !d->lastFocusItem->isWidget()) { + // Restore focus to the last focusable non-widget item that had + // focus. + setFocusItem(d->lastFocusItem, next ? Qt::TabFocusReason : Qt::BacktabFocusReason); + return true; + } + } + if (!d->tabFocusFirst) { + // No widgets... + return false; + } + + // The item must be a widget. + QGraphicsWidget *widget = 0; + if (!item) { + widget = next ? d->tabFocusFirst : d->tabFocusFirst->d_func()->focusPrev; + } else { + QGraphicsWidget *test = static_cast(item); + widget = next ? test->d_func()->focusNext : test->d_func()->focusPrev; + if ((next && widget == d->tabFocusFirst) || (!next && widget == d->tabFocusFirst->d_func()->focusPrev)) + return false; + } + QGraphicsWidget *widgetThatHadFocus = widget; + + // Run around the focus chain until we find a widget that can take tab focus. + do { + if (widget->flags() & QGraphicsItem::ItemIsFocusable + && widget->isEnabled() && widget->isVisibleTo(0) + && (widget->focusPolicy() & Qt::TabFocus) + && (!item || !item->isPanel() || item->isAncestorOf(widget)) + ) { + setFocusItem(widget, next ? Qt::TabFocusReason : Qt::BacktabFocusReason); + return true; + } + widget = next ? widget->d_func()->focusNext : widget->d_func()->focusPrev; + if ((next && widget == d->tabFocusFirst) || (!next && widget == d->tabFocusFirst->d_func()->focusPrev)) + return false; + } while (widget != widgetThatHadFocus); + + return false; +} + +/*! + \fn QGraphicsScene::changed(const QList ®ion) + + This signal is emitted by QGraphicsScene when control reaches the + event loop, if the scene content changes. The \a region parameter + contains a list of scene rectangles that indicate the area that + has been changed. + + \sa QGraphicsView::updateScene() +*/ + +/*! + \fn QGraphicsScene::sceneRectChanged(const QRectF &rect) + + This signal is emitted by QGraphicsScene whenever the scene rect changes. + The \a rect parameter is the new scene rectangle. + + \sa QGraphicsView::updateSceneRect() +*/ + +/*! + \fn QGraphicsScene::selectionChanged() + \since 4.3 + + This signal is emitted by QGraphicsScene whenever the selection + changes. You can call selectedItems() to get the new list of selected + items. + + The selection changes whenever an item is selected or unselected, a + selection area is set, cleared or otherwise changed, if a preselected item + is added to the scene, or if a selected item is removed from the scene. + + QGraphicsScene emits this signal only once for group selection operations. + For example, if you set a selection area, select or unselect a + QGraphicsItemGroup, or if you add or remove from the scene a parent item + that contains several selected items, selectionChanged() is emitted only + once after the operation has completed (instead of once for each item). + + \sa setSelectionArea(), selectedItems(), QGraphicsItem::setSelected() +*/ + +/*! + \since 4.4 + + Returns the scene's style, or the same as QApplication::style() if the + scene has not been explicitly assigned a style. + + \sa setStyle() +*/ +QStyle *QGraphicsScene::style() const +{ + Q_D(const QGraphicsScene); + // ### This function, and the use of styles in general, is non-reentrant. + return d->style ? d->style : QApplication::style(); +} + +/*! + \since 4.4 + + Sets or replaces the style of the scene to \a style, and reparents the + style to this scene. Any previously assigned style is deleted. The scene's + style defaults to QApplication::style(), and serves as the default for all + QGraphicsWidget items in the scene. + + Changing the style, either directly by calling this function, or + indirectly by calling QApplication::setStyle(), will automatically update + the style for all widgets in the scene that do not have a style explicitly + assigned to them. + + If \a style is 0, QGraphicsScene will revert to QApplication::style(). + + \sa style() +*/ +void QGraphicsScene::setStyle(QStyle *style) +{ + Q_D(QGraphicsScene); + // ### This function, and the use of styles in general, is non-reentrant. + if (style == d->style) + return; + + // Delete the old style, + delete d->style; + if ((d->style = style)) + d->style->setParent(this); + + // Notify the scene. + QEvent event(QEvent::StyleChange); + QApplication::sendEvent(this, &event); + + // Notify all widgets that don't have a style explicitly set. + foreach (QGraphicsItem *item, items()) { + if (item->isWidget()) { + QGraphicsWidget *widget = static_cast(item); + if (!widget->testAttribute(Qt::WA_SetStyle)) + QApplication::sendEvent(widget, &event); + } + } +} + +/*! + \property QGraphicsScene::font + \since 4.4 + \brief the scene's default font + + This property provides the scene's font. The scene font defaults to, + and resolves all its entries from, QApplication::font. + + If the scene's font changes, either directly through setFont() or + indirectly when the application font changes, QGraphicsScene first + sends itself a \l{QEvent::FontChange}{FontChange} event, and it then + sends \l{QEvent::FontChange}{FontChange} events to all top-level + widget items in the scene. These items respond by resolving their own + fonts to the scene, and they then notify their children, who again + notify their children, and so on, until all widget items have updated + their fonts. + + Changing the scene font, (directly or indirectly through + QApplication::setFont(),) automatically schedules a redraw the entire + scene. + + \sa QWidget::font, QApplication::setFont(), palette, style() +*/ +QFont QGraphicsScene::font() const +{ + Q_D(const QGraphicsScene); + return d->font; +} +void QGraphicsScene::setFont(const QFont &font) +{ + Q_D(QGraphicsScene); + QFont naturalFont = QApplication::font(); + naturalFont.resolve(0); + QFont resolvedFont = font.resolve(naturalFont); + d->setFont_helper(resolvedFont); +} + +/*! + \property QGraphicsScene::palette + \since 4.4 + \brief the scene's default palette + + This property provides the scene's palette. The scene palette defaults to, + and resolves all its entries from, QApplication::palette. + + If the scene's palette changes, either directly through setPalette() or + indirectly when the application palette changes, QGraphicsScene first + sends itself a \l{QEvent::PaletteChange}{PaletteChange} event, and it then + sends \l{QEvent::PaletteChange}{PaletteChange} events to all top-level + widget items in the scene. These items respond by resolving their own + palettes to the scene, and they then notify their children, who again + notify their children, and so on, until all widget items have updated + their palettes. + + Changing the scene palette, (directly or indirectly through + QApplication::setPalette(),) automatically schedules a redraw the entire + scene. + + \sa QWidget::palette, QApplication::setPalette(), font, style() +*/ +QPalette QGraphicsScene::palette() const +{ + Q_D(const QGraphicsScene); + return d->palette; +} +void QGraphicsScene::setPalette(const QPalette &palette) +{ + Q_D(QGraphicsScene); + QPalette naturalPalette = QApplication::palette(); + naturalPalette.resolve(0); + QPalette resolvedPalette = palette.resolve(naturalPalette); + d->setPalette_helper(resolvedPalette); +} + +/*! + \since 4.6 + + Returns true if the scene is active (e.g., it's viewed by + at least one QGraphicsView that is active); otherwise returns false. + + \sa QGraphicsItem::isActive(), QWidget::isActiveWindow() +*/ +bool QGraphicsScene::isActive() const +{ + Q_D(const QGraphicsScene); + return d->activationRefCount > 0; +} + +/*! + \since 4.6 + Returns the current active panel, or 0 if no panel is currently active. + + \sa QGraphicsScene::setActivePanel() +*/ +QGraphicsItem *QGraphicsScene::activePanel() const +{ + Q_D(const QGraphicsScene); + return d->activePanel; +} + +/*! + \since 4.6 + Activates \a item, which must be an item in this scene. You + can also pass 0 for \a item, in which case QGraphicsScene will + deactivate any currently active panel. + + If the scene is currently inactive, \a item remains inactive until the + scene becomes active (or, ir \a item is 0, no item will be activated). + + \sa activePanel(), isActive(), QGraphicsItem::isActive() +*/ +void QGraphicsScene::setActivePanel(QGraphicsItem *item) +{ + Q_D(QGraphicsScene); + d->setActivePanelHelper(item, false); +} + +/*! + \since 4.4 + + Returns the current active window, or 0 if no window is currently + active. + + \sa QGraphicsScene::setActiveWindow() +*/ +QGraphicsWidget *QGraphicsScene::activeWindow() const +{ + Q_D(const QGraphicsScene); + if (d->activePanel && d->activePanel->isWindow()) + return static_cast(d->activePanel); + return 0; +} + +/*! + \since 4.4 + Activates \a widget, which must be a widget in this scene. You can also + pass 0 for \a widget, in which case QGraphicsScene will deactivate any + currently active window. + + \sa activeWindow(), QGraphicsWidget::isActiveWindow() +*/ +void QGraphicsScene::setActiveWindow(QGraphicsWidget *widget) +{ + if (widget && widget->scene() != this) { + qWarning("QGraphicsScene::setActiveWindow: widget %p must be part of this scene", + widget); + return; + } + + // Activate the widget's panel (all windows are panels). + QGraphicsItem *panel = widget ? widget->panel() : 0; + setActivePanel(panel); + + // Raise + if (panel) { + QList siblingWindows; + QGraphicsItem *parent = panel->parentItem(); + // Raise ### inefficient for toplevels + foreach (QGraphicsItem *sibling, parent ? parent->children() : items()) { + if (sibling != panel && sibling->isWindow()) + siblingWindows << sibling; + } + + // Find the highest z value. + qreal z = panel->zValue(); + for (int i = 0; i < siblingWindows.size(); ++i) + z = qMax(z, siblingWindows.at(i)->zValue()); + + // This will probably never overflow. + const qreal litt = qreal(0.001); + panel->setZValue(z + litt); + } +} + +/*! + \since 4.6 + + Sends event \a event to item \a item through possible event filters. + + The event is sent only if the item is enabled. + + Returns \c false if the event was filtered or if the item is disabled. + Otherwise returns the value that was returned from the event handler. + + \sa QGraphicsItem::sceneEvent(), QGraphicsItem::sceneEventFilter() +*/ +bool QGraphicsScene::sendEvent(QGraphicsItem *item, QEvent *event) +{ + Q_D(QGraphicsScene); + if (!item) { + qWarning("QGraphicsScene::sendEvent: cannot send event to a null item"); + return false; + } + if (item->scene() != this) { + qWarning("QGraphicsScene::sendEvent: item %p's scene (%p)" + " is different from this scene (%p)", + item, item->scene(), this); + return false; + } + return d->sendEvent(item, event); +} + +void QGraphicsScenePrivate::addView(QGraphicsView *view) +{ + views << view; +} + +void QGraphicsScenePrivate::removeView(QGraphicsView *view) +{ + views.removeAll(view); +} + +void QGraphicsScenePrivate::updateTouchPointsForItem(QGraphicsItem *item, QTouchEvent *touchEvent) +{ + QList touchPoints = touchEvent->touchPoints(); + for (int i = 0; i < touchPoints.count(); ++i) { + QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; + touchPoint.setRect(item->mapFromScene(touchPoint.sceneRect()).boundingRect()); + touchPoint.setStartPos(item->d_ptr->genericMapFromScene(touchPoint.startScenePos(), touchEvent->widget())); + touchPoint.setLastPos(item->d_ptr->genericMapFromScene(touchPoint.lastScenePos(), touchEvent->widget())); + } + touchEvent->setTouchPoints(touchPoints); +} + +int QGraphicsScenePrivate::findClosestTouchPointId(const QPointF &scenePos) +{ + int closestTouchPointId = -1; + qreal closestDistance = qreal(0.); + foreach (const QTouchEvent::TouchPoint &touchPoint, sceneCurrentTouchPoints) { + qreal distance = QLineF(scenePos, touchPoint.scenePos()).length(); + if (closestTouchPointId == -1|| distance < closestDistance) { + closestTouchPointId = touchPoint.id(); + closestDistance = distance; + } + } + return closestTouchPointId; +} + +void QGraphicsScenePrivate::touchEventHandler(QTouchEvent *sceneTouchEvent) +{ + typedef QPair > StatesAndTouchPoints; + QHash itemsNeedingEvents; + + for (int i = 0; i < sceneTouchEvent->touchPoints().count(); ++i) { + const QTouchEvent::TouchPoint &touchPoint = sceneTouchEvent->touchPoints().at(i); + + // update state + QGraphicsItem *item = 0; + if (touchPoint.state() == Qt::TouchPointPressed) { + if (sceneTouchEvent->deviceType() == QTouchEvent::TouchPad) { + // on touch-pad devices, send all touch points to the same item + item = itemForTouchPointId.isEmpty() + ? 0 + : itemForTouchPointId.constBegin().value(); + } + + if (!item) { + // determine which item this touch point will go to + cachedItemsUnderMouse = itemsAtPosition(touchPoint.screenPos().toPoint(), + touchPoint.scenePos(), + sceneTouchEvent->widget()); + item = cachedItemsUnderMouse.isEmpty() ? 0 : cachedItemsUnderMouse.first(); + } + + if (sceneTouchEvent->deviceType() == QTouchEvent::TouchScreen) { + // on touch-screens, combine this touch point with the closest one we find if it + // is a a direct descendent or ancestor ( + int closestTouchPointId = findClosestTouchPointId(touchPoint.scenePos()); + QGraphicsItem *closestItem = itemForTouchPointId.value(closestTouchPointId); + if (!item + || (closestItem + && (item->isAncestorOf(closestItem) + || closestItem->isAncestorOf(item)))) { + item = closestItem; + } + } + if (!item) + continue; + + itemForTouchPointId.insert(touchPoint.id(), item); + sceneCurrentTouchPoints.insert(touchPoint.id(), touchPoint); + } else if (touchPoint.state() == Qt::TouchPointReleased) { + item = itemForTouchPointId.take(touchPoint.id()); + if (!item) + continue; + + sceneCurrentTouchPoints.remove(touchPoint.id()); + } else { + item = itemForTouchPointId.value(touchPoint.id()); + if (!item) + continue; + Q_ASSERT(sceneCurrentTouchPoints.contains(touchPoint.id())); + sceneCurrentTouchPoints[touchPoint.id()] = touchPoint; + } + + StatesAndTouchPoints &statesAndTouchPoints = itemsNeedingEvents[item]; + statesAndTouchPoints.first |= touchPoint.state(); + statesAndTouchPoints.second.append(touchPoint); + } + + if (itemsNeedingEvents.isEmpty()) { + sceneTouchEvent->accept(); + return; + } + + bool ignoreSceneTouchEvent = true; + QHash::ConstIterator it = itemsNeedingEvents.constBegin(); + const QHash::ConstIterator end = itemsNeedingEvents.constEnd(); + for (; it != end; ++it) { + QGraphicsItem *item = it.key(); + + (void) item->isBlockedByModalPanel(&item); + + // determine event type from the state mask + QEvent::Type eventType; + switch (it.value().first) { + case Qt::TouchPointPressed: + // all touch points have pressed state + eventType = QEvent::TouchBegin; + break; + case Qt::TouchPointReleased: + // all touch points have released state + eventType = QEvent::TouchEnd; + break; + case Qt::TouchPointStationary: + // don't send the event if nothing changed + continue; + default: + // all other combinations + eventType = QEvent::TouchUpdate; + break; + } + + QTouchEvent touchEvent(eventType); + touchEvent.setWidget(sceneTouchEvent->widget()); + touchEvent.setDeviceType(sceneTouchEvent->deviceType()); + touchEvent.setModifiers(sceneTouchEvent->modifiers()); + touchEvent.setTouchPointStates(it.value().first); + touchEvent.setTouchPoints(it.value().second); + + switch (touchEvent.type()) { + case QEvent::TouchBegin: + { + // if the TouchBegin handler recurses, we assume that means the event + // has been implicitly accepted and continue to send touch events + item->d_ptr->acceptedTouchBeginEvent = true; + bool res = sendTouchBeginEvent(item, &touchEvent) + && touchEvent.isAccepted(); + if (!res) + 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 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(); + 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; + } + break; + } + if (item->isPanel()) + break; + } + + touchEvent->setAccepted(eventAccepted); + return res; +} + +void QGraphicsScenePrivate::enableTouchEventsOnViews() +{ + foreach (QGraphicsView *view, views) + view->viewport()->setAttribute(Qt::WA_AcceptTouchEvents, true); +} + +void QGraphicsScenePrivate::updateInputMethodSensitivityInViews() +{ + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->updateInputMethodSensitivity(); +} + +void QGraphicsScenePrivate::enterModal(QGraphicsItem *panel, QGraphicsItem::PanelModality previousModality) +{ + Q_Q(QGraphicsScene); + Q_ASSERT(panel && panel->isPanel()); + + QGraphicsItem::PanelModality panelModality = panel->d_ptr->panelModality; + if (previousModality != QGraphicsItem::NonModal) { + // the panel is changing from one modality type to another... temporarily set it back so + // that blockedPanels is populated correctly + panel->d_ptr->panelModality = previousModality; + } + + QSet blockedPanels; + QList items = q->items(); // ### store panels separately + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel() && item->isBlockedByModalPanel()) + blockedPanels.insert(item); + } + // blockedPanels contains all currently blocked panels + + if (previousModality != QGraphicsItem::NonModal) { + // reset the modality to the proper value, since we changed it above + panel->d_ptr->panelModality = panelModality; + // remove this panel so that it will be reinserted at the front of the stack + modalPanels.removeAll(panel); + } + + modalPanels.prepend(panel); + + if (!hoverItems.isEmpty()) { + // send GraphicsSceneHoverLeave events to newly blocked hoverItems + QGraphicsSceneHoverEvent hoverEvent; + hoverEvent.setScenePos(lastSceneMousePos); + dispatchHoverEvent(&hoverEvent); + } + + if (!mouseGrabberItems.isEmpty() && lastMouseGrabberItemHasImplicitMouseGrab) { + QGraphicsItem *item = mouseGrabberItems.last(); + if (item->isBlockedByModalPanel()) + ungrabMouse(item, /*itemIsDying =*/ false); + } + + QEvent windowBlockedEvent(QEvent::WindowBlocked); + QEvent windowUnblockedEvent(QEvent::WindowUnblocked); + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel()) { + if (!blockedPanels.contains(item) && item->isBlockedByModalPanel()) { + // send QEvent::WindowBlocked to newly blocked panels + sendEvent(item, &windowBlockedEvent); + } else if (blockedPanels.contains(item) && !item->isBlockedByModalPanel()) { + // send QEvent::WindowUnblocked to unblocked panels when downgrading + // a panel from SceneModal to PanelModal + sendEvent(item, &windowUnblockedEvent); + } + } + } +} + +void QGraphicsScenePrivate::leaveModal(QGraphicsItem *panel) +{ + Q_Q(QGraphicsScene); + Q_ASSERT(panel && panel->isPanel()); + + QSet blockedPanels; + QList items = q->items(); // ### same as above + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel() && item->isBlockedByModalPanel()) + blockedPanels.insert(item); + } + + modalPanels.removeAll(panel); + + QEvent e(QEvent::WindowUnblocked); + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel() && blockedPanels.contains(item) && !item->isBlockedByModalPanel()) + sendEvent(item, &e); + } + + // send GraphicsSceneHoverEnter events to newly unblocked items + QGraphicsSceneHoverEvent hoverEvent; + hoverEvent.setScenePos(lastSceneMousePos); + dispatchHoverEvent(&hoverEvent); +} + +void QGraphicsScenePrivate::getGestureTargets(const QSet &gestures, + QWidget *viewport, + QMap *conflictedGestures, + QList > *conflictedItems, + QHash *normalGestures) +{ + foreach (QGesture *gesture, gestures) { + Qt::GestureType gestureType = gesture->gestureType(); + if (gesture->hasHotSpot()) { + QPoint screenPos = gesture->hotSpot().toPoint(); + QList items = itemsAtPosition(screenPos, QPointF(), viewport); + QList result; + 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(gestureType)) { + result.append(item); + } + } + DEBUG() << "QGraphicsScenePrivate::getGestureTargets:" + << gesture << result; + if (result.size() == 1) { + normalGestures->insert(gesture, result.first()); + } else if (!result.isEmpty()) { + conflictedGestures->insert(gestureType, gesture); + conflictedItems->append(result); + } + } + } +} + +void QGraphicsScenePrivate::gestureEventHandler(QGestureEvent *event) +{ + QWidget *viewport = event->widget(); + if (!viewport) + return; + QList allGestures = event->allGestures(); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "Delivering gestures:" << allGestures; + + typedef QHash > GesturesPerItem; + GesturesPerItem gesturesPerItem; + + QSet startedGestures; + foreach (QGesture *gesture, allGestures) { + 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); + } else { + gesturesPerItem[target].append(gesture); + } + } + + QMap conflictedGestures; + QList > conflictedItems; + QHash normalGestures; + getGestureTargets(startedGestures, viewport, &conflictedGestures, &conflictedItems, + &normalGestures); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "Conflicting gestures:" << conflictedGestures.values() << conflictedItems; + Q_ASSERT((conflictedGestures.isEmpty() && conflictedItems.isEmpty()) || + (!conflictedGestures.isEmpty() && !conflictedItems.isEmpty())); + + // gestures that were sent as override events, but no one accepted them + QHash ignoredConflictedGestures; + + // deliver conflicted gestures as override events first + while (!conflictedGestures.isEmpty() && !conflictedItems.isEmpty()) { + // get the topmost item to deliver the override event + Q_ASSERT(!conflictedItems.isEmpty()); + Q_ASSERT(!conflictedItems.first().isEmpty()); + QGraphicsObject *topmost = conflictedItems.first().first(); + for (int i = 1; i < conflictedItems.size(); ++i) { + QGraphicsObject *item = conflictedItems.at(i).first(); + if (qt_closestItemFirst(item, topmost)) { + topmost = item; + } + } + // get a list of gestures to send to the item + QList grabbedGestures = + topmost->QGraphicsItem::d_func()->gestureContext.keys(); + QList gestures; + for (int i = 0; i < grabbedGestures.size(); ++i) { + if (QGesture *g = conflictedGestures.value(grabbedGestures.at(i), 0)) { + gestures.append(g); + if (!ignoredConflictedGestures.contains(g)) + ignoredConflictedGestures.insert(g, topmost); + } + } + + // send gesture override to the topmost item + QGestureEvent ev(gestures); + 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); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "delivering override to" + << topmost << gestures; + sendEvent(topmost, &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->gestureType()); + gestureTargets.remove(g); + // add the gesture to the list of normal delivered gestures + normalGestures.insert(g, topmost); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "override was accepted:" + << g << topmost; + ignoredConflictedGestures.remove(g); + } + } + // remove the item that we've already delivered from the list + for (int i = 0; i < conflictedItems.size(); ) { + QList &items = conflictedItems[i]; + if (items.first() == topmost) { + items.removeFirst(); + if (items.isEmpty()) { + conflictedItems.removeAt(i); + continue; + } + } + ++i; + } + } + + // put back those started gestures that are not in the conflicted state + // and remember their targets + QHash::const_iterator it = normalGestures.begin(), + e = normalGestures.end(); + for (; it != e; ++it) { + QGesture *g = it.key(); + QGraphicsObject *receiver = it.value(); + Q_ASSERT(!gestureTargets.contains(g)); + gestureTargets.insert(g, receiver); + gesturesPerItem[receiver].append(g); + } + it = ignoredConflictedGestures.begin(); + e = ignoredConflictedGestures.end(); + for (; it != e; ++it) { + QGesture *g = it.key(); + QGraphicsObject *receiver = it.value(); + Q_ASSERT(!gestureTargets.contains(g)); + gestureTargets.insert(g, receiver); + gesturesPerItem[receiver].append(g); + } + + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "Started gestures:" << normalGestures.keys() + << "All gestures:" << gesturesPerItem.values(); + + // deliver all events + QList alreadyIgnoredGestures; + QHash > itemIgnoredGestures; + QList targetItems = gesturesPerItem.keys(); + qSort(targetItems.begin(), targetItems.end(), qt_closestItemFirst); + for (int i = 0; i < targetItems.size(); ++i) { + QGraphicsObject *item = targetItems.at(i); + QList gestures = gesturesPerItem.value(item); + // remove gestures that were already delivered once and were ignored + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "already ignored gestures for item" + << item << ":" << itemIgnoredGestures.value(item); + + if (itemIgnoredGestures.contains(item)) // don't deliver twice to the same item + continue; + + QGraphicsItemPrivate *gid = item->QGraphicsItem::d_func(); + foreach(QGesture *g, alreadyIgnoredGestures) { + if (gid->gestureContext.contains(g->gestureType())) + gestures += g; + } + if (gestures.isEmpty()) + continue; + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "delivering to" + << item << gestures; + QGestureEvent ev(gestures); + ev.setWidget(event->widget()); + sendEvent(item, &ev); + QSet ignoredGestures; + foreach (QGesture *g, gestures) { + if (!ev.isAccepted() && !ev.isAccepted(g)) + ignoredGestures.insert(g); + } + if (!ignoredGestures.isEmpty()) { + // get a list of items under the (current) hotspot of each ignored + // gesture and start delivery again from the beginning + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "item has ignored the event, will propagate." + << item << ignoredGestures; + itemIgnoredGestures[item] += ignoredGestures; + QMap conflictedGestures; + QList > itemsForConflictedGestures; + QHash normalGestures; + getGestureTargets(ignoredGestures, viewport, + &conflictedGestures, &itemsForConflictedGestures, + &normalGestures); + QSet itemsSet = targetItems.toSet(); + for (int k = 0; k < itemsForConflictedGestures.size(); ++k) + itemsSet += itemsForConflictedGestures.at(k).toSet(); + targetItems = itemsSet.toList(); + qSort(targetItems.begin(), targetItems.end(), qt_closestItemFirst); + alreadyIgnoredGestures = conflictedGestures.values(); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "new targets:" << targetItems; + i = -1; // start delivery again + continue; + } + } + + // 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; + } + } +} + +QT_END_NAMESPACE + +#include "moc_qgraphicsscene.cpp" + +#endif // QT_NO_GRAPHICSVIEW