Skip to content
Snippets Groups Projects
PolygonView.cpp 5.81 KiB
//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Mask/PolygonView.cpp
//! @brief     Implements PolygonView class
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/View/Mask/PolygonView.h"
#include "GUI/Model/Device/MaskItems.h"
#include "GUI/View/Mask/PolygonPointView.h"
#include <QCursor>
#include <QPainter>

namespace {

const double bbox_margins = 5; // additional margins around points to form bounding box
}

PolygonView::PolygonView(PolygonItem* item)
    : IShape2DView(item)
    , m_item(item)
    , m_block_on_point_update(false)
    , m_close_polygon_request(false)
{
    setFlag(QGraphicsItem::ItemIsSelectable);
    setFlag(QGraphicsItem::ItemIsMovable);
    setFlag(QGraphicsItem::ItemSendsGeometryChanges);
}

MaskItemObject* PolygonView::parameterizedItem() const
{
    return m_item;
}

void PolygonView::addView(IShape2DView* childView)
{
    if (childItems().contains(childView))
        return;

    auto* pointView = dynamic_cast<PolygonPointView*>(childView);
    ASSERT(pointView);
    pointView->setParentItem(this);

    // polygon consisting from more than 2 points can be closed via hover event by clicking
    // on first polygon point
    if (!isClosedPolygon() && childItems().size() > 2)
        childItems()[0]->setAcceptHoverEvents(true);

    // during the drawing process the polygon is selected
    pointView->setVisible(isSelected());

    update_polygon();

    connect(pointView, &PolygonPointView::propertyChanged, this, &PolygonView::update_view);
    connect(pointView, &PolygonPointView::closePolygonRequest, this,
            &PolygonView::onClosePolygonRequest);
}

//! Returns last added poligon point in scene coordinates
QPointF PolygonView::lastAddedPoint() const
{
    return !childItems().empty() ? childItems().back()->scenePos() : QPointF();
}

QPainterPath PolygonView::shape() const
{
    QPainterPath path;
    path.addPolygon(m_polygon);
    path.closeSubpath();
    return path;
}

//! Returns true if there was a request to close polygon (emitted by its start point),
//! and then closes a polygon. Returns true if polygon was closed.
bool PolygonView::closePolygonIfNecessary()
{
    if (m_close_polygon_request) {
        for (QGraphicsItem* childItem : childItems()) {
            childItem->setFlag(QGraphicsItem::ItemIsMovable);
            childItem->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
            childItem->setAcceptHoverEvents(false);
            childItem->setCursor(Qt::SizeAllCursor);
        }
        m_item->setIsClosed(true);
        update();
    }
    return isClosedPolygon();
}

void PolygonView::onClosePolygonRequest(bool value)
{
    m_close_polygon_request = value;
}

bool PolygonView::isClosedPolygon()
{
    return m_item->isClosed();
}

void PolygonView::paint(QPainter* painter, const QStyleOptionGraphicsItem* o, QWidget* w)
{
    if (isClosedPolygon())
        IShape2DView::paint(painter, o, w);
    else {
        ASSERT(m_item);
        const bool mask_value = static_cast<PolygonItem*>(m_item)->maskValue();
        painter->setRenderHints(QPainter::Antialiasing);
        painter->setPen(MaskEditorHelper::getMaskPen(mask_value));
        painter->drawPolyline(m_polygon.toPolygon());
    }
}

QVariant PolygonView::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant& value)
{
    if (change == QGraphicsItem::ItemSelectedHasChanged)
        setChildrenVisible(this->isSelected());

    return value;
}

void PolygonView::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
    IShape2DView::mouseMoveEvent(event);
    update_points();
}

void PolygonView::update_view()
{
    update_polygon();
    update();
}

//! Runs through all PolygonPointItem and calculate bounding rectangle.
//! Determines position of the rectangle in scene.
//! Calculates position of PolygonPointView in local polygon coordinates
void PolygonView::update_polygon()
{
    if (m_block_on_point_update)
        return;

    m_block_on_point_update = true;

    if (!m_item->points().isEmpty()) {
        m_polygon.clear();

        for (auto* point : m_item->points())
            m_polygon << QPointF(toSceneX(point->posX()), toSceneY(point->posY()));

        const QRectF scene_rect = m_polygon.boundingRect().marginsAdded(
            QMarginsF(bbox_margins, bbox_margins, bbox_margins, bbox_margins));

        m_bounding_rect = QRectF(0.0, 0.0, scene_rect.width(), scene_rect.height());

        setPos(scene_rect.x(), scene_rect.y());
        update(); // to propagate changes to scene

        m_polygon = mapFromScene(m_polygon);

        int index(0);
        for (auto* childView : childItems())
            childView->setPos(m_polygon[index++]);

        setPos(scene_rect.x(), scene_rect.y());
    }
    m_block_on_point_update = false;
}

//! When polygon moves as a whole thing across the scene, given method updates coordinates
//! of PolygonPointItem's
void PolygonView::update_points()
{
    if (m_block_on_point_update)
        return;

    for (QGraphicsItem* childItem : childItems()) {
        auto* view = dynamic_cast<PolygonPointView*>(childItem);
        QPointF pos = view->scenePos();
        disconnect(view, &PolygonPointView::propertyChanged, this, &PolygonView::update_view);
        view->updateParameterizedItem(pos);
        connect(view, &PolygonPointView::propertyChanged, this, &PolygonView::update_view);
    }
}

void PolygonView::setChildrenVisible(bool value)
{
    for (QGraphicsItem* childItem : childItems())
        childItem->setVisible(value);
}