// Copyright (C) 2016 Jochen Becher // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "annotationitem.h" #include "qmt/diagram_controller/diagramcontroller.h" #include "qmt/diagram/dannotation.h" #include "qmt/diagram_scene/diagramsceneconstants.h" #include "qmt/diagram_scene/diagramscenemodel.h" #include "qmt/diagram_scene/parts/rectangularselectionitem.h" #include "qmt/infrastructure/geometryutilities.h" #include "qmt/infrastructure/qmtassert.h" #include "qmt/style/stylecontroller.h" #include "qmt/style/style.h" #include #include #include #include #include #include #include #include namespace qmt { static const qreal MINIMUM_TEXT_WIDTH = 20.0; static const qreal CONTENTS_BORDER_VERTICAL = 4.0; static const qreal CONTENTS_BORDER_HORIZONTAL = 4.0; class AnnotationItem::AnnotationTextItem : public QGraphicsTextItem { public: explicit AnnotationTextItem(QGraphicsItem *parent) : QGraphicsTextItem(parent) { } void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) final { QMT_ASSERT(option, return); QStyleOptionGraphicsItem option2(*option); option2.state &= ~(QStyle::State_Selected | QStyle::State_HasFocus); QGraphicsTextItem::paint(painter, &option2, widget); } }; AnnotationItem::AnnotationItem(DAnnotation *annotation, DiagramSceneModel *diagramSceneModel, QGraphicsItem *parent) : QGraphicsItem(parent), m_annotation(annotation), m_diagramSceneModel(diagramSceneModel) { setFlags(QGraphicsItem::ItemIsSelectable); } AnnotationItem::~AnnotationItem() { } QRectF AnnotationItem::boundingRect() const { return childrenBoundingRect(); } void AnnotationItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(painter) Q_UNUSED(option) Q_UNUSED(widget) } void AnnotationItem::update() { QMT_CHECK(!m_isUpdating); m_isUpdating = true; prepareGeometryChange(); const Style *style = adaptedStyle(); // text if (!m_textItem) { m_textItem = new AnnotationTextItem(this); m_textItem->setTextInteractionFlags(Qt::TextEditorInteraction); m_textItem->installSceneEventFilter(this); QObject::connect(m_textItem->document(), &QTextDocument::contentsChanged, m_textItem, [=]() { this->onContentsChanged(); } ); } m_textItem->setFont(style->normalFont()); m_textItem->setDefaultTextColor(style->textBrush().color()); if (!m_isChanged) m_textItem->setPlainText(annotation()->text()); // item shown if annotation has no text and is not selected if (!m_noTextItem) m_noTextItem = new QGraphicsRectItem(this); m_noTextItem->setPen(QPen(QBrush(QColor(192, 192, 192)), 1, Qt::DashDotLine)); m_noTextItem->setVisible(!isSelected() && m_textItem->document()->isEmpty()); updateSelectionMarker(); updateGeometry(); setZValue(ANNOTATION_ITEMS_ZVALUE); m_isUpdating = false; } QPointF AnnotationItem::pos() const { return m_annotation->pos(); } QRectF AnnotationItem::rect() const { return m_annotation->rect(); } QSizeF AnnotationItem::minimumSize() const { return calcMinimumGeometry(); } void AnnotationItem::setPosAndRect(const QPointF &originalPos, const QRectF &originalRect, const QPointF &topLeftDelta, const QPointF &bottomRightDelta) { QPointF newPos = originalPos; QRectF newRect = originalRect; GeometryUtilities::adjustPosAndRect(&newPos, &newRect, topLeftDelta, bottomRightDelta, QPointF(0.0, 0.0)); if (newPos != m_annotation->pos() || newRect != m_annotation->rect()) { m_diagramSceneModel->diagramController()->startUpdateElement(m_annotation, m_diagramSceneModel->diagram(), DiagramController::UpdateGeometry); m_annotation->setPos(newPos); if (newRect.size() != m_annotation->rect().size()) m_annotation->setAutoSized(false); m_annotation->setRect(newRect); m_diagramSceneModel->diagramController()->finishUpdateElement(m_annotation, m_diagramSceneModel->diagram(), false); } } void AnnotationItem::alignItemSizeToRaster(Side adjustHorizontalSide, Side adjustVerticalSide, double rasterWidth, double rasterHeight) { Q_UNUSED(adjustHorizontalSide) Q_UNUSED(adjustVerticalSide) Q_UNUSED(rasterWidth) Q_UNUSED(rasterHeight) } void AnnotationItem::moveDelta(const QPointF &delta) { m_diagramSceneModel->diagramController()->startUpdateElement(m_annotation, m_diagramSceneModel->diagram(), DiagramController::UpdateGeometry); m_annotation->setPos(m_annotation->pos() + delta); m_diagramSceneModel->diagramController()->finishUpdateElement(m_annotation, m_diagramSceneModel->diagram(), false); } void AnnotationItem::alignItemPositionToRaster(double rasterWidth, double rasterHeight) { QPointF pos = m_annotation->pos(); QRectF rect = m_annotation->rect(); QPointF topLeft = pos + rect.topLeft(); double leftDelta = qRound(topLeft.x() / rasterWidth) * rasterWidth - topLeft.x(); double topDelta = qRound(topLeft.y() / rasterHeight) * rasterHeight - topLeft.y(); QPointF topLeftDelta(leftDelta, topDelta); setPosAndRect(pos, rect, topLeftDelta, topLeftDelta); } bool AnnotationItem::isSecondarySelected() const { return m_isSecondarySelected; } void AnnotationItem::setSecondarySelected(bool secondarySelected) { if (m_isSecondarySelected != secondarySelected) { m_isSecondarySelected = secondarySelected; update(); } } bool AnnotationItem::isFocusSelected() const { return m_isFocusSelected; } void AnnotationItem::setFocusSelected(bool focusSelected) { if (m_isFocusSelected != focusSelected) { m_isFocusSelected = focusSelected; update(); } } QRectF AnnotationItem::getSecondarySelectionBoundary() { return QRectF(); } void AnnotationItem::setBoundarySelected(const QRectF &boundary, bool secondary) { if (boundary.contains(mapRectToScene(boundingRect()))) { if (secondary) setSecondarySelected(true); else setSelected(true); } } bool AnnotationItem::isEditable() const { return true; } bool AnnotationItem::isEditing() const { return m_textItem && m_textItem->hasFocus(); } void AnnotationItem::edit() { if (m_textItem) m_textItem->setFocus(); } void AnnotationItem::finishEdit() { if (m_textItem) m_textItem->clearFocus(); } void AnnotationItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) m_diagramSceneModel->selectItem(this, event->modifiers() & Qt::ControlModifier); if (event->buttons() & Qt::LeftButton) m_diagramSceneModel->moveSelectedItems(this, QPointF(0.0, 0.0)); } void AnnotationItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (event->buttons() & Qt::LeftButton) m_diagramSceneModel->moveSelectedItems(this, event->scenePos() - event->lastScenePos()); } void AnnotationItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::LeftButton) { m_diagramSceneModel->moveSelectedItems(this, event->scenePos() - event->lastScenePos()); if (event->scenePos() != event->buttonDownScenePos(Qt::LeftButton)) m_diagramSceneModel->alignSelectedItemsPositionOnRaster(); } } void AnnotationItem::updateSelectionMarker() { if (isSelected() || m_isSecondarySelected) { if (!m_selectionMarker) { m_selectionMarker = new RectangularSelectionItem(this, this); m_selectionMarker->setShowBorder(true); m_selectionMarker->setFreedom(RectangularSelectionItem::FreedomHorizontalOnly); } m_selectionMarker->setSecondarySelected(isSelected() ? false : m_isSecondarySelected); } else if (m_selectionMarker) { if (m_selectionMarker->scene()) m_selectionMarker->scene()->removeItem(m_selectionMarker); delete m_selectionMarker; m_selectionMarker = nullptr; } } void AnnotationItem::updateSelectionMarkerGeometry(const QRectF &annotationRect) { if (m_selectionMarker) m_selectionMarker->setRect(annotationRect); } const Style *AnnotationItem::adaptedStyle() { return m_diagramSceneModel->styleController()->adaptAnnotationStyle(m_annotation); } bool AnnotationItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event) { if (watched == m_textItem) { if (event->type() == QEvent::FocusIn) { scene()->clearSelection(); setSelected(true); } } return false; } void AnnotationItem::onContentsChanged() { QMT_CHECK(!m_isChanged); m_isChanged = true; if (!m_isUpdating) { QString plainText = m_textItem->toPlainText(); if (m_annotation->text() != plainText) { m_diagramSceneModel->diagramController()->startUpdateElement(m_annotation, m_diagramSceneModel->diagram(), DiagramController::UpdateMinor); m_annotation->setText(plainText); m_diagramSceneModel->diagramController()->finishUpdateElement(m_annotation, m_diagramSceneModel->diagram(), false); } } m_isChanged = false; } QSizeF AnnotationItem::calcMinimumGeometry() const { qreal width = MINIMUM_TEXT_WIDTH + 2 * CONTENTS_BORDER_HORIZONTAL; qreal height = 0.0; // irrelevant; cannot be modified by user and will always be overwritten if (annotation()->isAutoSized()) { if (m_textItem) { m_textItem->setTextWidth(-1); QSizeF textSize = m_textItem->document()->size(); width = textSize.width() + 2 * CONTENTS_BORDER_HORIZONTAL; } } return QSizeF(width, height); } void AnnotationItem::updateGeometry() { prepareGeometryChange(); QSizeF geometry = calcMinimumGeometry(); qreal width = geometry.width(); qreal height = geometry.height(); if (annotation()->isAutoSized()) { if (m_textItem) { m_textItem->setTextWidth(-1); QSizeF textSize = m_textItem->document()->size(); width = textSize.width() + 2 * CONTENTS_BORDER_HORIZONTAL; height = textSize.height() + 2 * CONTENTS_BORDER_VERTICAL; } } else { QRectF rect = annotation()->rect(); width = rect.width(); if (m_textItem) { m_textItem->setTextWidth(width - 2 * CONTENTS_BORDER_HORIZONTAL); height = m_textItem->document()->size().height() + 2 * CONTENTS_BORDER_VERTICAL; } } // update sizes and positions double left = 0.0; double top = 0.0; setPos(annotation()->pos()); QRectF rect(left, top, width, height); // the object is updated without calling DiagramController intentionally. // attribute rect is not a real attribute stored on DObject but // a backup for the graphics item used for manual resized and persistency. annotation()->setRect(rect); if (m_noTextItem) m_noTextItem->setRect(rect); if (m_textItem) m_textItem->setPos(left + CONTENTS_BORDER_HORIZONTAL, top + CONTENTS_BORDER_VERTICAL); updateSelectionMarkerGeometry(rect); } } // namespace qmt