Files
create/src/Gui/ViewProviderAnnotation.cpp
2025-11-11 13:49:01 +01:00

503 lines
17 KiB
C++

/***************************************************************************
* Copyright (c) 2008 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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 <QMenu>
#include <QFont>
#include <QFontMetrics>
#include <QImage>
#include <QPainter>
#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/nodes/SoAnnotation.h>
#include <Inventor/nodes/SoAsciiText.h>
#include <Inventor/nodes/SoBaseColor.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoFont.h>
#include <Inventor/nodes/SoImage.h>
#include <Inventor/nodes/SoLineSet.h>
#include <Inventor/nodes/SoPointSet.h>
#include <Inventor/nodes/SoRotationXYZ.h>
#include <Inventor/nodes/SoText2.h>
#include <Inventor/nodes/SoTranslation.h>
#include <Inventor/draggers/SoTranslate2Dragger.h>
#include <App/Annotation.h>
#include <App/Document.h>
#include <App/PropertyStandard.h>
#include <Base/Parameter.h>
#include "ViewProviderAnnotation.h"
#include "Application.h"
#include "BitmapFactory.h"
#include "Document.h"
#include "SoFCSelection.h"
#include "Tools.h"
#include "Window.h"
using namespace Gui;
const char* ViewProviderAnnotation::JustificationEnums[] = {"Left", "Right", "Center", nullptr};
const char* ViewProviderAnnotation::RotationAxisEnums[] = {"X", "Y", "Z", nullptr};
PROPERTY_SOURCE(Gui::ViewProviderAnnotation, Gui::ViewProviderDocumentObject)
ViewProviderAnnotation::ViewProviderAnnotation()
{
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/View"
);
unsigned long col = hGrp->GetUnsigned("AnnotationTextColor", 4294967295UL); // light grey
float r, g, b;
r = ((col >> 24) & 0xff) / 255.0;
g = ((col >> 16) & 0xff) / 255.0;
b = ((col >> 8) & 0xff) / 255.0;
ADD_PROPERTY(TextColor, (r, g, b));
ADD_PROPERTY(Justification, ((long)0));
Justification.setEnums(JustificationEnums);
ADD_PROPERTY(FontSize, (12));
ADD_PROPERTY(FontName, ("Arial"));
ADD_PROPERTY(LineSpacing, (1.0));
ADD_PROPERTY(Rotation, (0));
ADD_PROPERTY(RotationAxis, ((long)2));
RotationAxis.setEnums(RotationAxisEnums);
pFont = new SoFont();
pFont->ref();
pLabel = new SoText2();
pLabel->ref();
pLabel3d = new SoAsciiText();
pLabel3d->ref();
pColor = new SoBaseColor();
pColor->ref();
pTranslation = new SoTranslation();
pTranslation->ref();
pRotationXYZ = new SoRotationXYZ();
pRotationXYZ->ref();
RotationAxis.touch();
TextColor.touch();
FontSize.touch();
FontName.touch();
sPixmap = "Tree_Annotation";
}
ViewProviderAnnotation::~ViewProviderAnnotation()
{
pFont->unref();
pLabel->unref();
pLabel3d->unref();
pColor->unref();
pTranslation->unref();
pRotationXYZ->unref();
}
void ViewProviderAnnotation::onChanged(const App::Property* prop)
{
if (prop == &TextColor) {
const Base::Color& c = TextColor.getValue();
pColor->rgb.setValue(c.r, c.g, c.b);
}
else if (prop == &Justification) {
if (Justification.getValue() == 0) {
pLabel->justification = SoText2::LEFT;
pLabel3d->justification = SoAsciiText::LEFT;
}
else if (Justification.getValue() == 1) {
pLabel->justification = SoText2::RIGHT;
pLabel3d->justification = SoAsciiText::RIGHT;
}
else if (Justification.getValue() == 2) {
pLabel->justification = SoText2::CENTER;
pLabel3d->justification = SoAsciiText::CENTER;
}
}
else if (prop == &FontSize) {
pFont->size = FontSize.getValue();
}
else if (prop == &FontName) {
pFont->name = FontName.getValue();
}
else if (prop == &LineSpacing) {
pLabel->spacing = LineSpacing.getValue();
pLabel3d->spacing = LineSpacing.getValue();
}
else if (prop == &RotationAxis) {
if (RotationAxis.getValue() == 0) {
pRotationXYZ->axis = SoRotationXYZ::X;
}
else if (RotationAxis.getValue() == 1) {
pRotationXYZ->axis = SoRotationXYZ::Y;
}
else if (RotationAxis.getValue() == 2) {
pRotationXYZ->axis = SoRotationXYZ::Z;
}
}
else if (prop == &Rotation) {
pRotationXYZ->angle = (Rotation.getValue() / 360) * (2 * std::numbers::pi);
}
else {
ViewProviderDocumentObject::onChanged(prop);
}
}
std::vector<std::string> ViewProviderAnnotation::getDisplayModes() const
{
// add modes
std::vector<std::string> StrList;
StrList.emplace_back("Screen");
StrList.emplace_back("World");
return StrList;
}
void ViewProviderAnnotation::setDisplayMode(const char* ModeName)
{
if (strcmp(ModeName, "Screen") == 0) {
setDisplayMaskMode("Screen");
}
else if (strcmp(ModeName, "World") == 0) {
setDisplayMaskMode("World");
}
ViewProviderDocumentObject::setDisplayMode(ModeName);
}
void ViewProviderAnnotation::attach(App::DocumentObject* f)
{
ViewProviderDocumentObject::attach(f);
auto anno = new SoAnnotation();
auto anno3d = new SoAnnotation();
auto textsep = new SoFCSelection();
// set selection/highlight colors
float transparency;
ParameterGrp::handle hGrp = Gui::WindowParameter::getDefaultParameter()->GetGroup("View");
SbColor highlightColor = textsep->colorHighlight.getValue();
auto highlight = (unsigned long)(highlightColor.getPackedValue());
highlight = hGrp->GetUnsigned("HighlightColor", highlight);
highlightColor.setPackedValue((uint32_t)highlight, transparency);
textsep->colorHighlight.setValue(highlightColor);
// Do the same with the selection color
SbColor selectionColor = textsep->colorSelection.getValue();
auto selection = (unsigned long)(selectionColor.getPackedValue());
selection = hGrp->GetUnsigned("SelectionColor", selection);
selectionColor.setPackedValue((uint32_t)selection, transparency);
textsep->colorSelection.setValue(selectionColor);
textsep->objectName = pcObject->getNameInDocument();
textsep->documentName = pcObject->getDocument()->getName();
textsep->subElementName = "Main";
textsep->addChild(pTranslation);
textsep->addChild(pRotationXYZ);
textsep->addChild(pColor);
textsep->addChild(pFont); // causes problems
textsep->addChild(pLabel);
auto textsep3d = new SoFCSelection();
// set sel/highlight color here too
textsep3d->colorHighlight.setValue(highlightColor);
textsep3d->colorSelection.setValue(selectionColor);
textsep3d->objectName = pcObject->getNameInDocument();
textsep3d->documentName = pcObject->getDocument()->getName();
textsep3d->subElementName = "Main";
textsep3d->addChild(pTranslation);
textsep3d->addChild(pRotationXYZ);
textsep3d->addChild(pColor);
textsep3d->addChild(pFont);
textsep3d->addChild(pLabel3d);
anno->addChild(textsep);
anno3d->addChild(textsep3d);
addDisplayMaskMode(anno, "Screen");
addDisplayMaskMode(anno3d, "World");
}
void ViewProviderAnnotation::updateData(const App::Property* prop)
{
if (prop->is<App::PropertyStringList>() && strcmp(prop->getName(), "LabelText") == 0) {
const std::vector<std::string> lines
= static_cast<const App::PropertyStringList*>(prop)->getValues();
int index = 0;
pLabel->string.setNum((int)lines.size());
pLabel3d->string.setNum((int)lines.size());
for (const auto& line : lines) {
const char* cs = line.c_str();
if (line.empty()) {
cs = " "; // empty lines make coin crash, we use a space instead
}
pLabel->string.set1Value(index, SbString(cs));
pLabel3d->string.set1Value(index, SbString(cs));
index++;
}
}
else if (prop->is<App::PropertyVector>() && strcmp(prop->getName(), "Position") == 0) {
Base::Vector3d v = static_cast<const App::PropertyVector*>(prop)->getValue();
pTranslation->translation.setValue(v.x, v.y, v.z);
}
ViewProviderDocumentObject::updateData(prop);
}
// ----------------------------------------------------------------------------
const char* ViewProviderAnnotationLabel::JustificationEnums[] = {"Left", "Right", "Center", nullptr};
PROPERTY_SOURCE(Gui::ViewProviderAnnotationLabel, Gui::ViewProviderDocumentObject)
ViewProviderAnnotationLabel::ViewProviderAnnotationLabel()
{
ADD_PROPERTY(TextColor, (1.0f, 1.0f, 1.0f));
ADD_PROPERTY(BackgroundColor, (0.0f, 0.333f, 1.0f));
ADD_PROPERTY(Justification, ((long)0));
Justification.setEnums(JustificationEnums);
QFont fn;
ADD_PROPERTY(FontSize, (fn.pointSize()));
ADD_PROPERTY(FontName, ((const char*)fn.family().toLatin1()));
ADD_PROPERTY(Frame, (true));
pColor = new SoBaseColor();
pColor->ref();
pBaseTranslation = new SoTranslation();
pBaseTranslation->ref();
pTextTranslation = new TranslateManip();
pTextTranslation->ref();
pCoords = new SoCoordinate3();
pCoords->ref();
pImage = new SoImage();
pImage->ref();
BackgroundColor.touch();
sPixmap = "Tree_Annotation";
}
ViewProviderAnnotationLabel::~ViewProviderAnnotationLabel()
{
pColor->unref();
pBaseTranslation->unref();
pTextTranslation->unref();
pCoords->unref();
pImage->unref();
}
void ViewProviderAnnotationLabel::onChanged(const App::Property* prop)
{
if (prop == &BackgroundColor) {
const Base::Color& c = BackgroundColor.getValue();
pColor->rgb.setValue(c.r, c.g, c.b);
}
if (prop == &TextColor || prop == &BackgroundColor || prop == &Justification
|| prop == &FontSize || prop == &FontName || prop == &Frame) {
if (getObject()) {
App::Property* label = getObject()->getPropertyByName("LabelText");
if (label && label->is<App::PropertyStringList>()) {
drawImage(static_cast<App::PropertyStringList*>(label)->getValues());
}
}
}
else {
ViewProviderDocumentObject::onChanged(prop);
}
}
std::vector<std::string> ViewProviderAnnotationLabel::getDisplayModes() const
{
// add modes
std::vector<std::string> StrList;
StrList.emplace_back("Line");
StrList.emplace_back("Object");
return StrList;
}
void ViewProviderAnnotationLabel::setDisplayMode(const char* ModeName)
{
if (strcmp(ModeName, "Line") == 0) {
setDisplayMaskMode("Line");
}
else if (strcmp(ModeName, "Object") == 0) {
setDisplayMaskMode("Object");
}
ViewProviderDocumentObject::setDisplayMode(ModeName);
}
void ViewProviderAnnotationLabel::attach(App::DocumentObject* f)
{
ViewProviderDocumentObject::attach(f);
// plain image
SoSeparator* textsep = new SoAnnotation();
textsep->addChild(pBaseTranslation);
textsep->addChild(pImage);
// image with line
SoSeparator* linesep = new SoAnnotation();
linesep->addChild(pBaseTranslation);
linesep->addChild(pColor);
linesep->addChild(pCoords);
linesep->addChild(new SoLineSet());
auto ds = new SoDrawStyle();
ds->pointSize.setValue(3.0f);
linesep->addChild(ds);
linesep->addChild(new SoPointSet());
linesep->addChild(pTextTranslation);
linesep->addChild(pImage);
addDisplayMaskMode(linesep, "Line");
addDisplayMaskMode(textsep, "Object");
// Use the image node as the transform handle
SoSearchAction sa;
sa.setInterest(SoSearchAction::FIRST);
sa.setSearchingAll(true);
sa.setNode(this->pImage);
sa.apply(pcRoot);
SoPath* imagePath = sa.getPath();
if (imagePath) {
SoDragger* dragger = pTextTranslation->getDragger();
dragger->addStartCallback(dragStartCallback, this);
dragger->addFinishCallback(dragFinishCallback, this);
dragger->addMotionCallback(dragMotionCallback, this);
dragger->setPartAsPath("translator", imagePath);
// Hide the dragger feedback during translation
dragger->setPart("translatorActive", NULL);
dragger->setPart("xAxisFeedback", NULL);
dragger->setPart("yAxisFeedback", NULL);
}
}
void ViewProviderAnnotationLabel::updateData(const App::Property* prop)
{
if (prop->is<App::PropertyStringList>() && strcmp(prop->getName(), "LabelText") == 0) {
drawImage(static_cast<const App::PropertyStringList*>(prop)->getValues());
}
else if (prop->is<App::PropertyVector>() && strcmp(prop->getName(), "BasePosition") == 0) {
Base::Vector3d v = static_cast<const App::PropertyVector*>(prop)->getValue();
pBaseTranslation->translation.setValue(v.x, v.y, v.z);
}
else if (prop->is<App::PropertyVector>() && strcmp(prop->getName(), "TextPosition") == 0) {
Base::Vector3d v = static_cast<const App::PropertyVector*>(prop)->getValue();
pCoords->point.set1Value(1, SbVec3f(v.x, v.y, v.z));
pTextTranslation->translation.setValue(v.x, v.y, v.z);
}
ViewProviderDocumentObject::updateData(prop);
}
void ViewProviderAnnotationLabel::dragStartCallback(void*, SoDragger*)
{
// This is called when a manipulator is about to manipulating
Gui::Application::Instance->activeDocument()->openCommand(
QT_TRANSLATE_NOOP("Command", "Transform")
);
}
void ViewProviderAnnotationLabel::dragFinishCallback(void*, SoDragger*)
{
// This is called when a manipulator has done manipulating
Gui::Application::Instance->activeDocument()->commitCommand();
}
void ViewProviderAnnotationLabel::dragMotionCallback(void* data, SoDragger* drag)
{
auto that = static_cast<ViewProviderAnnotationLabel*>(data);
const SbMatrix& mat = drag->getMotionMatrix();
App::DocumentObject* obj = that->getObject();
if (obj && obj->is<App::AnnotationLabel>()) {
static_cast<App::AnnotationLabel*>(obj)->TextPosition.setValue(mat[3][0], mat[3][1], mat[3][2]);
}
}
void ViewProviderAnnotationLabel::drawImage(const std::vector<std::string>& s)
{
if (s.empty()) {
pImage->image = SoSFImage();
this->hide();
return;
}
QFont font(QString::fromLatin1(this->FontName.getValue()), (int)this->FontSize.getValue());
QFontMetrics fm(font);
int w = 0;
int h = fm.height() * s.size();
const Base::Color& b = this->BackgroundColor.getValue();
QColor brush;
brush.setRgbF(b.r, b.g, b.b);
const Base::Color& t = this->TextColor.getValue();
QColor front;
front.setRgbF(t.r, t.g, t.b);
QStringList lines;
for (const auto& it : s) {
QString line = QString::fromUtf8(it.c_str());
w = std::max<int>(w, QtTools::horizontalAdvance(fm, line));
lines << line;
}
QImage image(w + 10, h + 10, QImage::Format_ARGB32_Premultiplied);
image.fill(0x00000000);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing);
bool drawFrame = this->Frame.getValue();
if (drawFrame) {
painter.setPen(QPen(QColor(0, 0, 127), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
painter.setBrush(QBrush(brush, Qt::SolidPattern));
QRectF rectangle(0.0, 0.0, w + 10, h + 10);
painter.drawRoundedRect(rectangle, 5, 5);
}
painter.setPen(front);
Qt::Alignment align = Qt::AlignVCenter;
if (Justification.getValue() == 0) {
align = Qt::AlignVCenter | Qt::AlignLeft;
}
else if (Justification.getValue() == 1) {
align = Qt::AlignVCenter | Qt::AlignRight;
}
else {
align = Qt::AlignVCenter | Qt::AlignHCenter;
}
QString text = lines.join(QLatin1String("\n"));
painter.setFont(font);
painter.drawText(5, 5, w, h, align, text);
painter.end();
SoSFImage sfimage;
Gui::BitmapFactory().convert(image, sfimage);
pImage->image = sfimage;
}