/*************************************************************************** * Copyright (c) 2013 Luke Parry * * Copyright (c) 2019 Franck Jullien * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ #include #include # include # include # include # include # include # include # include # include # include # include # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Rez.h" #include "ZVALUE.h" #include "QGIArrow.h" #include "QGIDimLines.h" #include "QGIViewBalloon.h" #include "ViewProviderBalloon.h" #include "DrawGuiUtil.h" #include "QGIViewPart.h" #include "QGIViewDimension.h" #include "QGVPage.h" #include "MDIViewPage.h" #include "TaskBalloon.h" //TODO: hide the Qt coord system (+y down). using namespace TechDraw; using namespace TechDrawGui; QGIBalloonLabel::QGIBalloonLabel() { posX = 0; posY = 0; setCacheMode(QGraphicsItem::NoCache); setFlag(ItemSendsGeometryChanges, true); setFlag(ItemIsMovable, true); setFlag(ItemIsSelectable, true); setAcceptHoverEvents(true); m_labelText = new QGCustomText(); m_labelText->setParentItem(this); m_ctrl = false; hasHover = false; } QVariant QGIBalloonLabel::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == ItemSelectedHasChanged && scene()) { if(isSelected()) { Q_EMIT selected(true); setPrettySel(); } else { Q_EMIT selected(false); setPrettyNormal(); } update(); } else if(change == ItemPositionHasChanged && scene()) { setLabelCenter(); Q_EMIT dragging(m_ctrl); } return QGraphicsItem::itemChange(change, value); } void QGIBalloonLabel::mousePressEvent(QGraphicsSceneMouseEvent * event) { if(event->modifiers() & Qt::ControlModifier) { m_ctrl = true; } if(scene() && this == scene()->mouseGrabberItem()) { Q_EMIT dragFinished(); } QGraphicsItem::mousePressEvent(event); } void QGIBalloonLabel::mouseMoveEvent(QGraphicsSceneMouseEvent * event) { QGraphicsItem::mouseMoveEvent(event); } void QGIBalloonLabel::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) { // Base::Console().Message("QGIVB::mouseRelease - pos: %s\n", // DrawUtil::formatVector(pos()).c_str()); m_ctrl = false; if(scene() && this == scene()->mouseGrabberItem()) { Q_EMIT dragFinished(); } QGraphicsItem::mouseReleaseEvent(event); } void QGIBalloonLabel::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event) { Gui::Control().showDialog(new TaskDlgBalloon(parent)); QGraphicsItem::mouseDoubleClickEvent(event); } void QGIBalloonLabel::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { Q_EMIT hover(true); hasHover = true; if (!isSelected()) { setPrettyPre(); } else { setPrettySel(); } QGraphicsItem::hoverEnterEvent(event); } void QGIBalloonLabel::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { QGIView *view = dynamic_cast (parentItem()); assert(view != 0); Q_UNUSED(view); Q_EMIT hover(false); hasHover = false; if (!isSelected()) { setPrettyNormal(); } else { setPrettySel(); } QGraphicsItem::hoverLeaveEvent(event); } QRectF QGIBalloonLabel::boundingRect() const { return childrenBoundingRect(); } void QGIBalloonLabel::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(widget); Q_UNUSED(painter); QStyleOptionGraphicsItem myOption(*option); myOption.state &= ~QStyle::State_Selected; //QGraphicsObject/QGraphicsItem::paint gives link error. } void QGIBalloonLabel::setPosFromCenter(const double &xCenter, const double &yCenter) { //set label's Qt position(top,left) given boundingRect center point setPos(xCenter - m_labelText->boundingRect().width() / 2., yCenter - m_labelText->boundingRect().height() / 2.); } void QGIBalloonLabel::setLabelCenter() { //save label's bRect center (posX,posY) given Qt position (top,left) posX = x() + m_labelText->boundingRect().width() / 2.; posY = y() + m_labelText->boundingRect().height() / 2.; } void QGIBalloonLabel::setFont(QFont f) { m_labelText->setFont(f); } void QGIBalloonLabel::setDimString(QString t) { prepareGeometryChange(); m_labelText->setPlainText(t); } void QGIBalloonLabel::setDimString(QString t, qreal maxWidth) { prepareGeometryChange(); m_labelText->setPlainText(t); m_labelText->setTextWidth(maxWidth); } void QGIBalloonLabel::setPrettySel(void) { m_labelText->setPrettySel(); } void QGIBalloonLabel::setPrettyPre(void) { m_labelText->setPrettyPre(); } void QGIBalloonLabel::setPrettyNormal(void) { m_labelText->setPrettyNormal(); } void QGIBalloonLabel::setColor(QColor c) { m_colNormal = c; m_labelText->setColor(m_colNormal); } //************************************************************** QGIViewBalloon::QGIViewBalloon() : hasHover(false), m_lineWidth(0.0) { setHandlesChildEvents(false); setFlag(QGraphicsItem::ItemIsMovable, false); setCacheMode(QGraphicsItem::NoCache); balloonLabel = new QGIBalloonLabel(); balloonLabel->parent = this; addToGroup(balloonLabel); balloonLabel->setColor(getNormalColor()); balloonLabel->setPrettyNormal(); balloonLines = new QGIDimLines(); addToGroup(balloonLines); balloonLines->setNormalColor(getNormalColor()); balloonLines->setPrettyNormal(); balloonShape = new QGIDimLines(); addToGroup(balloonShape); balloonShape->setNormalColor(getNormalColor()); balloonShape->setPrettyNormal(); arrow = new QGIArrow(); addToGroup(arrow); arrow->setNormalColor(getNormalColor()); arrow->setFillColor(getNormalColor()); arrow->setPrettyNormal(); balloonLabel->setZValue(ZVALUE::LABEL); arrow->setZValue(ZVALUE::DIMENSION); balloonLines->setZValue(ZVALUE::DIMENSION); balloonLines->setStyle(Qt::SolidLine); balloonShape->setZValue(ZVALUE::DIMENSION); balloonShape->setStyle(Qt::SolidLine); oldLabelCenter = new QPointF; oldLabelCenter->setX(0.0); oldLabelCenter->setY(0.0); balloonLabel->setPosFromCenter(0, 0); // connecting the needed slots and signals QObject::connect( balloonLabel, SIGNAL(dragging(bool)), this , SLOT (balloonLabelDragged(bool))); QObject::connect( balloonLabel, SIGNAL(dragFinished()), this , SLOT (balloonLabelDragFinished())); QObject::connect( balloonLabel, SIGNAL(selected(bool)), this , SLOT (select(bool))); QObject::connect( balloonLabel, SIGNAL(hover(bool)), this , SLOT (hover(bool))); // toggleBorder(false); setZValue(ZVALUE::DIMENSION); //note: this won't paint dimensions over another View if it stacks //above this Dimension's parent view. need Layers? } //from QGVP::mouseReleaseEvent - pos = eventPos void QGIViewBalloon::placeBalloon(QPointF pos) { // Base::Console().Message("QGIVB::placeBalloon(%s)\n", // DrawUtil::formatVector(pos).c_str()); auto balloon( dynamic_cast(getViewObject()) ); if( balloon == nullptr ) { return; } DrawView* balloonParent = nullptr; App::DocumentObject* docObj = balloon->SourceView.getValue(); if (docObj == nullptr) { return; } else { balloonParent = dynamic_cast(docObj); } auto featPage = balloonParent->findParentPage(); if (featPage == nullptr) { return; } auto vp = static_cast(getViewProvider(getViewObject())); if ( vp == nullptr ) { return; } balloon->OriginX.setValue(Rez::appX(mapFromScene(pos).x())); balloon->OriginY.setValue(Rez::appX(mapFromScene(pos).y())); balloon->X.setValue(Rez::appX(mapFromScene(pos).x() + 200)); balloon->Y.setValue(- Rez::appX(mapFromScene(pos).y() - 200)); int idx = featPage->getNextBalloonIndex(); QString labelText = QString::number(idx); balloon->Text.setValue(std::to_string(idx).c_str()); QFont font = balloonLabel->getFont(); font.setPixelSize(calculateFontPixelSize(vp->Fontsize.getValue())); font.setFamily(QString::fromUtf8(vp->Font.getValue())); balloonLabel->setFont(font); prepareGeometryChange(); // Default label position balloonLabel->setPosFromCenter(mapFromScene(pos).x() + 200, mapFromScene(pos).y() -200); balloonLabel->setDimString(labelText, Rez::guiX(balloon->TextWrapLen.getValue())); draw(); } void QGIViewBalloon::setViewPartFeature(TechDraw::DrawViewBalloon *balloon) { if(balloon == 0) return; setViewFeature(static_cast(balloon)); float x = Rez::guiX(balloon->X.getValue()); float y = Rez::guiX(-balloon->Y.getValue()); balloonLabel->setColor(getNormalColor()); balloonLabel->setPosFromCenter(x, y); QString labelText = QString::fromUtf8(balloon->Text.getStrValue().data()); balloonLabel->setDimString(labelText, Rez::guiX(balloon->TextWrapLen.getValue())); updateBalloon(); draw(); } void QGIViewBalloon::select(bool state) { setSelected(state); draw(); } void QGIViewBalloon::hover(bool state) { hasHover = state; draw(); } void QGIViewBalloon::updateView(bool update) { Q_UNUSED(update); auto balloon( dynamic_cast(getViewObject()) ); if( balloon == nullptr ) return; auto vp = static_cast(getViewProvider(getViewObject())); if ( vp == nullptr ) { return; } if (update) { QString labelText = QString::fromUtf8(balloon->Text.getStrValue().data()); balloonLabel->setDimString(labelText, Rez::guiX(balloon->TextWrapLen.getValue())); balloonLabel->setColor(getNormalColor()); } if ( balloon->X.isTouched() || balloon->Y.isTouched()){ float x = Rez::guiX(balloon->X.getValue()); float y = Rez::guiX(balloon->Y.getValue()); balloonLabel->setPosFromCenter(x,-y); } updateBalloon(); draw(); } void QGIViewBalloon::updateBalloon(bool obtuse) { (void) obtuse; const auto balloon( dynamic_cast(getViewObject()) ); if( balloon == nullptr ) { return; } auto vp = static_cast(getViewProvider(getViewObject())); if ( vp == nullptr ) { return; } QFont font = balloonLabel->getFont(); font.setPixelSize(calculateFontPixelSize(vp->Fontsize.getValue())); font.setFamily(QString::fromUtf8(vp->Font.getValue())); balloonLabel->setFont(font); QString labelText = QString::fromUtf8(balloon->Text.getStrValue().data()); balloonLabel->verticalSep = false; balloonLabel->seps.clear(); if (strcmp(balloon->Symbol.getValueAsString(), "Rectangle") == 0) { while (labelText.contains(QString::fromUtf8("|"))) { int pos = labelText.indexOf(QString::fromUtf8("|")); labelText.replace(pos, 1, QString::fromUtf8(" ")); QFontMetrics fm(balloonLabel->getFont()); balloonLabel->seps.push_back(fm.width((labelText.left(pos + 2)))); balloonLabel->verticalSep = true; } } balloonLabel->setDimString(labelText, Rez::guiX(balloon->TextWrapLen.getValue())); balloonLabel->setPosFromCenter(balloonLabel->X(),balloonLabel->Y()); } void QGIViewBalloon::balloonLabelDragged(bool ctrl) { draw_modifier(ctrl); } void QGIViewBalloon::balloonLabelDragFinished() { auto dvb( dynamic_cast(getViewObject()) ); if( dvb == nullptr ) { return; } //this needs to be scaled? double x = Rez::appX(balloonLabel->X()), y = Rez::appX(balloonLabel->Y()); Gui::Command::openCommand("Drag Balloon"); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.X = %f", dvb->getNameInDocument(), x); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.Y = %f", dvb->getNameInDocument(), -y); Gui::Command::commitCommand(); } void QGIViewBalloon::draw() { draw_modifier(false); } void QGIViewBalloon::draw_modifier(bool modifier) { if (!isVisible()) { //should this be controlled by parent ViewPart? return; } TechDraw::DrawViewBalloon *balloon = dynamic_cast(getViewObject()); if((!balloon) || //nothing to draw, don't try (!balloon->isDerivedFrom(TechDraw::DrawViewBalloon::getClassTypeId()))) { balloonLabel->hide(); hide(); return; } balloonLabel->show(); show(); const TechDraw::DrawViewPart *refObj = balloon->getViewPart(); if (refObj == nullptr) { return; } if(!refObj->hasGeometry()) { //nothing to draw yet (restoring) balloonLabel->hide(); hide(); return; } auto vp = static_cast(getViewProvider(getViewObject())); if ( vp == nullptr ) { return; } m_colNormal = getNormalColor(); balloonLabel->setColor(m_colNormal); m_lineWidth = Rez::guiX(vp->LineWidth.getValue()); double textWidth = balloonLabel->getDimText()->boundingRect().width(); double textHeight = balloonLabel->getDimText()->boundingRect().height(); QRectF mappedRect = mapRectFromItem(balloonLabel, balloonLabel->boundingRect()); Base::Vector3d lblCenter = Base::Vector3d(mappedRect.center().x(), mappedRect.center().y(), 0.0); if (balloon->isLocked()) { balloonLabel->setFlag(QGraphicsItem::ItemIsMovable, false); } else balloonLabel->setFlag(QGraphicsItem::ItemIsMovable, true); Base::Vector3d dLineStart; Base::Vector3d kinkPoint; double kinkLength = Rez::guiX(5.0); float orginX = Rez::guiX(balloon->OriginX.getValue()); float orginY = Rez::guiX(balloon->OriginY.getValue()); const char *balloonType = balloon->Symbol.getValueAsString(); float scale = balloon->SymbolScale.getValue(); double offset = 0; QPainterPath balloonPath; if (strcmp(balloonType, "Circular") == 0) { double balloonRadius = sqrt(pow((textHeight / 2.0), 2) + pow((textWidth / 2.0), 2)); balloonRadius = balloonRadius * scale; balloonPath.moveTo(lblCenter.x, lblCenter.y); balloonPath.addEllipse(lblCenter.x - balloonRadius,lblCenter.y - balloonRadius, balloonRadius * 2, balloonRadius * 2); offset = balloonRadius; } else if (strcmp(balloonType, "None") == 0) { balloonPath = QPainterPath(); offset = (textWidth / 2.0) + Rez::guiX(2.0); } else if (strcmp(balloonType, "Rectangle") == 0) { //Add some room textHeight = (textHeight * scale) + Rez::guiX(1.0); if (balloonLabel->verticalSep) { for (std::vector::iterator it = balloonLabel->seps.begin() ; it != balloonLabel->seps.end(); ++it) { balloonPath.moveTo(lblCenter.x - (textWidth / 2.0) + *it, lblCenter.y - (textHeight / 2.0)); balloonPath.lineTo(lblCenter.x - (textWidth / 2.0) + *it, lblCenter.y + (textHeight / 2.0)); } } textWidth = (textWidth * scale) + Rez::guiX(2.0); balloonPath.addRect(lblCenter.x -(textWidth / 2.0), lblCenter.y - (textHeight / 2.0), textWidth, textHeight); offset = (textWidth / 2.0); } else if (strcmp(balloonType, "Triangle") == 0) { double radius = sqrt(pow((textHeight / 2.0), 2) + pow((textWidth / 2.0), 2)); radius = radius * scale; radius += Rez::guiX(3.0); offset = (tan(30 * M_PI / 180) * radius); QPolygonF triangle; double startAngle = -M_PI / 2; double angle = startAngle; for (int i = 0; i < 4; i++) { triangle += QPointF(lblCenter.x + (radius * cos(angle)), lblCenter.y + (radius * sin(angle))); angle += (2 * M_PI / 3); } balloonPath.moveTo(lblCenter.x + (radius * cos(startAngle)), lblCenter.y + (radius * sin(startAngle))); balloonPath.addPolygon(triangle); } else if (strcmp(balloonType, "Inspection") == 0) { //Add some room textWidth = (textWidth * scale) + Rez::guiX(2.0); textHeight = (textHeight * scale) + Rez::guiX(1.0); QPointF textBoxCorner(lblCenter.x - (textWidth / 2.0), lblCenter.y - (textHeight / 2.0)); balloonPath.moveTo(textBoxCorner); balloonPath.lineTo(textBoxCorner.x() + textWidth, textBoxCorner.y()); balloonPath.arcTo(textBoxCorner.x() + textWidth - (textHeight / 2.0), textBoxCorner.y(), textHeight, textHeight, 90, -180); balloonPath.lineTo(textBoxCorner.x(), textBoxCorner.y() + textHeight); balloonPath.arcTo(textBoxCorner.x() - (textHeight / 2), textBoxCorner.y(), textHeight, textHeight, -90, -180); offset = (textWidth / 2.0) + (textHeight / 2.0); } else if (strcmp(balloonType, "Square") == 0) { //Add some room textWidth = (textWidth * scale) + Rez::guiX(2.0); textHeight = (textHeight * scale) + Rez::guiX(1.0); double max = std::max(textWidth, textHeight); balloonPath.addRect(lblCenter.x -(max / 2.0), lblCenter.y - (max / 2.0), max, max); offset = (max / 2.0); } else if (strcmp(balloonType, "Hexagon") == 0) { double radius = sqrt(pow((textHeight / 2.0), 2) + pow((textWidth / 2.0), 2)); radius = radius * scale; radius += Rez::guiX(1.0); offset = radius; QPolygonF triangle; double startAngle = -2 * M_PI / 3; double angle = startAngle; for (int i = 0; i < 7; i++) { triangle += QPointF(lblCenter.x + (radius * cos(angle)), lblCenter.y + (radius * sin(angle))); angle += (2 * M_PI / 6); } balloonPath.moveTo(lblCenter.x + (radius * cos(startAngle)), lblCenter.y + (radius * sin(startAngle))); balloonPath.addPolygon(triangle); } offset = (lblCenter.x < orginX) ? offset : -offset; dLineStart.y = lblCenter.y; dLineStart.x = lblCenter.x + offset; kinkLength = (lblCenter.x < orginX) ? kinkLength : -kinkLength; kinkPoint.y = dLineStart.y; kinkPoint.x = dLineStart.x + kinkLength; QPainterPath dLinePath; dLinePath.moveTo(dLineStart.x, dLineStart.y); dLinePath.lineTo(kinkPoint.x, kinkPoint.y); if (modifier) { balloon->OriginX.setValue(Rez::appX(orginX + lblCenter.x - oldLabelCenter->x())); balloon->OriginY.setValue(Rez::appX(orginY + lblCenter.y - oldLabelCenter->y())); } orginX = Rez::guiX(balloon->OriginX.getValue()); orginY = Rez::guiX(balloon->OriginY.getValue()); dLinePath.lineTo(orginX, orginY); oldLabelCenter->setX(lblCenter.x); oldLabelCenter->setY(lblCenter.y); balloonLines->setPath(dLinePath); balloonShape->setPath(balloonPath); const char *endType = balloon->EndType.getValueAsString(); if (strcmp(endType, "Arrow") == 0) { arrow->setStyle(QGIArrow::getPrefArrowStyle()); } else if (strcmp(endType, "Dot") == 0) { arrow->setStyle(3); } arrow->setSize(QGIArrow::getPrefArrowSize()); arrow->draw(); Base::Vector3d orign(orginX, orginY, 0.0); Base::Vector3d dirballoonLinesLine = (orign - kinkPoint).Normalize(); float arAngle = atan2(dirballoonLinesLine.y, dirballoonLinesLine.x) * 180 / M_PI; arrow->setPos(orginX, orginY); arrow->setRotation(arAngle); arrow->show(); // redraw the Dimension and the parent View if (hasHover && !isSelected()) { arrow->setPrettyPre(); balloonLines->setPrettyPre(); balloonShape->setPrettyPre(); } else if (isSelected()) { arrow->setPrettySel(); balloonLines->setPrettySel(); balloonShape->setPrettySel(); } else { arrow->setPrettyNormal(); balloonLines->setPrettyNormal(); balloonShape->setPrettyNormal(); } update(); if (parentItem()) { //TODO: parent redraw still required with new frame/label?? parentItem()->update(); } else { Base::Console().Log("INFO - QGIVD::draw - no parent to update\n"); } } void QGIViewBalloon::drawBorder(void) { //Dimensions have no border! // Base::Console().Message("TRACE - QGIViewDimension::drawBorder - doing nothing!\n"); } QVariant QGIViewBalloon::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == ItemSelectedHasChanged && scene()) { if(isSelected()) { balloonLabel->setSelected(true); } else { balloonLabel->setSelected(false); } draw(); } return QGIView::itemChange(change, value); } void QGIViewBalloon::paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) { QStyleOptionGraphicsItem myOption(*option); myOption.state &= ~QStyle::State_Selected; QPaintDevice* hw = painter->device(); QSvgGenerator* svg = dynamic_cast(hw); setPens(); //double balloonLinesSaveWidth = arrow->getWidth(); if (svg) { setSvgPens(); } else { setPens(); } QGIView::paint (painter, &myOption, widget); setPens(); } void QGIViewBalloon::setSvgPens(void) { double svgLineFactor = 3.0; //magic number. should be a setting somewhere. balloonLines->setWidth(m_lineWidth/svgLineFactor); balloonShape->setWidth(m_lineWidth/svgLineFactor); arrow->setWidth(arrow->getWidth()/svgLineFactor); } void QGIViewBalloon::setPens(void) { balloonLines->setWidth(m_lineWidth); balloonShape->setWidth(m_lineWidth); arrow->setWidth(m_lineWidth); } QColor QGIViewBalloon::getNormalColor() { Base::Reference hGrp = App::GetApplication().GetUserParameter() .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Dimensions"); App::Color fcColor; fcColor.setPackedValue(hGrp->GetUnsigned("Color", 0x00000000)); m_colNormal = fcColor.asValue(); auto balloon( dynamic_cast(getViewObject()) ); if( balloon == nullptr ) return m_colNormal; auto vp = static_cast(getViewProvider(getViewObject())); if ( vp == nullptr ) { return m_colNormal; } m_colNormal = vp->Color.getValue().asValue(); return m_colNormal; } #include