QT(16):Graphics View事件传播机制和qdraw项目框架

一、Graphics View框架事件传播机制

QGraphicsView从键盘和鼠标接收输入事件,并将这些事件转换为场景事件(在适当的情况下将使用的坐标转换为场景坐标),然后再将事件发送到可视化场景。

QGraphicsScene的事件传播体系结构将场景事件传递到项目,并管理项目之间的传播。如果场景在特定位置收到鼠标按下事件,则场景会将事件传递给位于该位置的任何项目。

以鼠标点击事件的传播为例:
1、在qgraphicsview.cpp中的鼠标点击事件中,将鼠标事件的数据赋值给新建的场景鼠标事件,并将该事件传递给场景。

void QGraphicsView::mousePressEvent(QMouseEvent *event)
{
    Q_D(QGraphicsView);
// 存储此事件,用于重播、计算增量和滚动拖动;
// 即使在非交互模式下,也允许滚动拖动,因此在函数的最开始存储事件。
    // Store this event for replaying, finding deltas, and for
    // scroll-dragging; even in non-interactive mode, scroll hand dragging is
    // allowed, so we store the event at the very top of this function.
    d->storeMouseEvent(event);
    d->lastMouseEvent.setAccepted(false);

    if (d->sceneInteractionAllowed) {
        // Store some of the event's button-down data.// 存储一些按钮按下的数据
        d->mousePressViewPoint = event->pos();
        d->mousePressScenePoint = mapToScene(d->mousePressViewPoint);
        d->mousePressScreenPoint = event->globalPos();
        d->lastMouseMoveScenePoint = d->mousePressScenePoint;
        d->lastMouseMoveScreenPoint = d->mousePressScreenPoint;
        d->mousePressButton = event->button();

        if (d->scene) {
            // Convert and deliver the mouse event to the scene.
            // 将鼠标事件转换并传递给场景。
            QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMousePress);
            mouseEvent.setWidget(viewport());
            mouseEvent.setButtonDownScenePos(d->mousePressButton, d->mousePressScenePoint);
            mouseEvent.setButtonDownScreenPos(d->mousePressButton, d->mousePressScreenPoint);
            mouseEvent.setScenePos(d->mousePressScenePoint);
            mouseEvent.setScreenPos(d->mousePressScreenPoint);
            mouseEvent.setLastScenePos(d->lastMouseMoveScenePoint);
            mouseEvent.setLastScreenPos(d->lastMouseMoveScreenPoint);
            mouseEvent.setButtons(event->buttons());
            mouseEvent.setButton(event->button());
            mouseEvent.setModifiers(event->modifiers());
            mouseEvent.setSource(event->source());
            mouseEvent.setFlags(event->flags());
            mouseEvent.setAccepted(false);
            if (event->spontaneous())
                qt_sendSpontaneousEvent(d->scene, &mouseEvent);
            else
                QCoreApplication::sendEvent(d->scene, &mouseEvent);
 			// 更新原始鼠标事件的接受状态。
            // Update the original mouse event accepted state.
            bool isAccepted = mouseEvent.isAccepted();
            event->setAccepted(isAccepted);

            // Update the last mouse event accepted state.
            d->lastMouseEvent.setAccepted(isAccepted);

            if (isAccepted)
                return;
        }
    }

#if QT_CONFIG(rubberband)
    if (d->dragMode == QGraphicsView::RubberBandDrag && !d->rubberBanding) {
        if (d->sceneInteractionAllowed) {
            // Rubberbanding is only allowed in interactive mode.
            event->accept();
            d->rubberBanding = true;
            d->rubberBandRect = QRect();
            if (d->scene) {
                bool extendSelection = (event->modifiers() & Qt::ControlModifier) != 0;

                if (extendSelection) {
                    d->rubberBandSelectionOperation = Qt::AddToSelection;
                } else {
                    d->rubberBandSelectionOperation = Qt::ReplaceSelection;
                    d->scene->clearSelection();
                }
            }
        }
    } else
#endif
        if (d->dragMode == QGraphicsView::ScrollHandDrag && event->button() == Qt::LeftButton) {
        	//在滚动拖动模式下,左键按下开始手动滚动。
            // Left-button press in scroll hand mode initiates hand scrolling.
            event->accept();
            d->handScrolling = true;
            d->handScrollMotions = 0;
#ifndef QT_NO_CURSOR
            viewport()->setCursor(Qt::ClosedHandCursor);
#endif
        }
}

2、在qgraphicsscene.cpp中的鼠标点击事件中,
如果鼠标抓取项为空,新建鼠标悬浮事件,分发该事件。
然后在mousePressEventHandler内处理该鼠标点击事件。

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);
}

3、场景私有类的鼠标事件处理函数中,
(1)、首先将鼠标事件的状态设置为忽略;
(2)、将事件发送给任何已存在的鼠标抓取项;
(3)、temsAtPosition查找在事件位置的所有项目,存储到cachedItemsUnderMouse;
(4)、更新窗口激活状态;
(5)、setFocusItem依次设置cachedItemsUnderMouse中项目为焦点项;
(6)、检查场景模态性;
(7)、sendMouseEvent将鼠标按下事件逐个发送给所有cachedItemsUnderMouse中项目;
(8)、事件还是被忽略吗?那么鼠标按下事件发送给场景。重置鼠标抓取项,清除选择,清除焦点,并保持事件被忽略以便能够传播到原始视图。

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.constLast()->isBlockedByModalPanel())
            return;
        //默认情况下忽略事件,但我们在传递后不考虑事件的接受状态;毕竟鼠标已经被抓取了。
        // The event is ignored by default, but we disregard the event's
        // accepted state after delivery; the mouse is grabbed, after all.
        sendMouseEvent(mouseEvent);
        return;
    }

    // Start by determining the number of items at the current position.首先确定当前位置处的项目数。
    // Reuse value from earlier calculations if possible.如果可能,重用之前的计算结果。
    if (cachedItemsUnderMouse.isEmpty()) {
        cachedItemsUnderMouse = itemsAtPosition(mouseEvent->screenPos(),
                                                mouseEvent->scenePos(),
                                                mouseEvent->widget());
    }

    // Update window activation.更新窗口激活状态。
    QGraphicsItem *topItem = cachedItemsUnderMouse.value(0);
    QGraphicsWidget *newActiveWindow = topItem ? topItem->window() : 0;
    if (newActiveWindow && newActiveWindow->isBlockedByModalPanel(&topItem)) {
        // pass activation to the blocking modal window将激活状态传递给阻塞的模态窗口
        newActiveWindow = topItem ? topItem->window() : 0;
    }

    if (newActiveWindow != q->activeWindow())
        q->setActiveWindow(newActiveWindow);

    // Set focus on the topmost enabled item that can take focus. 设置焦点到最上层的可接收焦点的项。
    bool setFocus = false;

    foreach (QGraphicsItem *item, cachedItemsUnderMouse) {
        if (item->isBlockedByModalPanel()
            || (item->d_ptr->flags & QGraphicsItem::ItemStopsFocusHandling)) {
            // Make sure we don't clear focus.确保不清除焦点。
            setFocus = true;
            break;
        }
        if (item->isEnabled() && ((item->flags() & QGraphicsItem::ItemIsFocusable))) {
            if (!item->isWidget() || ((QGraphicsWidget *)item)->focusPolicy() & Qt::ClickFocus) {
                setFocus = true;
                if (item != q->focusItem() && item->d_ptr->mouseSetsFocus)
                    q->setFocusItem(item, Qt::MouseFocusReason);
                break;
            }
        }
        if (item->isPanel())
            break;
        if (item->d_ptr->flags & QGraphicsItem::ItemStopsClickFocusPropagation)
            break;
    }

    // Check for scene modality. 检查场景模态性。
    bool sceneModality = false;
    for (int i = 0; i < modalPanels.size(); ++i) {
        if (modalPanels.at(i)->panelModality() == QGraphicsItem::SceneModal) {
            sceneModality = true;
            break;
        }
    }

    // If nobody could take focus, clear it.如果没有任何项能够接收焦点,清除焦点。
    if (!stickyFocus && !setFocus && !sceneModality)
        q->setFocusItem(0, Qt::MouseFocusReason);

    // Any item will do.随便找个项即可。
    if (sceneModality && cachedItemsUnderMouse.isEmpty())
        cachedItemsUnderMouse << modalPanels.constFirst();

    // 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.constLast() != item;
        if (disabled) {
            ungrabMouse(item, /* itemIsDying = */ dontSendUngrabEvents);
            break;
        }
        if (mouseEvent->isAccepted()) {
            if (!mouseGrabberItems.isEmpty())
                storeMouseButtonsForMouseGrabber(mouseEvent);
            lastMouseGrabberItem = item;
            return;
        }
        ungrabMouse(item, /* itemIsDying = */ dontSendUngrabEvents);

        // Don't propagate through panels.不要继续传播到面板
        if (isPanel)
            break;
    }
// 事件还是被忽略吗?那么鼠标按下事件发送给场景。
// 重置鼠标抓取项,清除选择,清除焦点,并保持事件被忽略以便能够传播到原始视图。
    // Is the event still ignored? Then the mouse press goes to the scene.
    // Reset the mouse grabber, clear the selection, clear focus, and leave
    // the event ignored so that it can propagate through the originating
    // view.
    if (!mouseEvent->isAccepted()) {
        clearMouseGrabber();

        QGraphicsView *view = mouseEvent->widget() ? qobject_cast<QGraphicsView *>(mouseEvent->widget()->parentWidget()) : 0;
        bool dontClearSelection = view && view->dragMode() == QGraphicsView::ScrollHandDrag;
        bool extendSelection = (mouseEvent->modifiers() & Qt::ControlModifier) != 0;
        dontClearSelection |= extendSelection;
        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();
        }
    }
}

4、在sendMouseEvent函数中,场景的私有类将事件发送给鼠标抓取项的最后一项

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.constLast();
    if (item->isBlockedByModalPanel())
        return;

    const QTransform mapFromScene = item->d_ptr->genericMapFromSceneTransform(mouseEvent->widget());
    const QPointF itemPos = mapFromScene.map(mouseEvent->scenePos());
    for (int i = 0x1; i <= 0x10; i <<= 1) {
        Qt::MouseButton button = Qt::MouseButton(i);
        mouseEvent->setButtonDownPos(button, mouseGrabberButtonDownPos.value(button, itemPos));
        mouseEvent->setButtonDownScenePos(button, mouseGrabberButtonDownScenePos.value(button, mouseEvent->scenePos()));
        mouseEvent->setButtonDownScreenPos(button, mouseGrabberButtonDownScreenPos.value(button, mouseEvent->screenPos()));
    }
    mouseEvent->setPos(itemPos);
    mouseEvent->setLastPos(mapFromScene.map(mouseEvent->lastScenePos()));
    sendEvent(item, mouseEvent);
}

二、qdraw:

QT(16):Graphics View事件传播机制和qdraw项目框架_第1张图片

1、DrawView继承于QGraphicsView,主要实现放缩、刻度尺(控件)、文件操作。

2、DrawScene继承于QGraphicsScene,主要实现项目的排列、组合等操作、键盘、鼠标等事件的处理、背景的绘制。
在事件处理函数中调用DrawTool的mouseMoveEvent事件,管理GraphicsItem类对象的新建和管理。

3、DrawTool管理基类,子类SelectTool(选择工具)、RotationTool(旋转工具)、RectTool(矩形工具)、PolygonTool(折线工具)工具实现具体功能。

RectTool类有rectTool、roundRectTool、ellipseTool三个静态对象。
PolygonTool类有lineTool、polygonTool、bezierTool、polylineTool四个静态对象。在静态变量定义时,七个对象添加到DrawTool指针的列表中。
在mainwindow.cpp中,点击不同的新建图形按钮,改变DrawTool的DrawShape类静态变量c_drawShape值。然后在画布上点击鼠标并移动完成绘制的操作。

enum DrawShape
{
    selection ,
    rotation  ,
    line ,
    rectangle ,
    roundrect ,
    ellipse ,
    bezier,
    polygon,
    polyline,
};

在场景类的鼠标事件中,通过查找DrawTool指针列表中当前操作对应的管理工具,调用该工具的同名函数进行处理,实现绘制功能。
选择和旋转功能同理。

在RectTool的鼠标事件中新建矩形、椭圆、圆角矩形。
在PolygonTool的鼠标事件中新建线段、多边形、贝塞尔曲线、折线。
这些图形的共同基类是GraphicsItem。

4、GraphicsItem继承于QObject和模板类AbstractShapeType,处理具体到项目内部的事件。

template < typename BaseType = QGraphicsItem >
class AbstractShapeType : public BaseType

GraphicsRectTool继承于GraphicsItem,GraphicsEllipseItem继承于GraphicsRectTool。
GraphicsPolygonItem继承于GraphicsItem,GraphicsLineItem继承于GraphicsPolygonItem,GraphicsBezier继承于GraphicsPolygonItem。

paint函数绘制item,duplicate函数复制item,control函数改变形状,stretch函数改变大小。

你可能感兴趣的:(qt,c++)