Files
create/src/Mod/TechDraw/Gui/QGIRichAnno.cpp
2023-11-30 14:58:06 +01:00

366 lines
13 KiB
C++

/***************************************************************************
* Copyright (c) 2019 WandererFan <wandererfan@gmail.com> *
* *
* 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 <cmath>
# include <QDialog>
# include <QGraphicsItem>
# include <QGraphicsSceneMouseEvent>
# include <QPainter>
# include <QRegularExpression>
# include <QRegularExpressionMatch>
# include <QTextBlock>
# include <QTextCursor>
# include <QTextDocumentFragment>
#endif
#include <App/Application.h>
#include <Mod/TechDraw/App/DrawRichAnno.h>
#include <Mod/TechDraw/App/DrawUtil.h>
#include "QGIRichAnno.h"
#include "mrichtextedit.h"
#include "PreferencesGui.h"
#include "QGCustomRect.h"
#include "QGCustomText.h"
#include "Rez.h"
#include "ViewProviderRichAnno.h"
#include "ZVALUE.h"
#include "DrawGuiUtil.h"
using namespace TechDraw;
using namespace TechDrawGui;
using DU = DrawUtil;
//**************************************************************
QGIRichAnno::QGIRichAnno() :
m_isExportingPdf(false), m_isExportingSvg(false), m_hasHover(false)
{
setHandlesChildEvents(false);
setAcceptHoverEvents(false);
setFlag(QGraphicsItem::ItemIsSelectable, true);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
m_text = new QGCustomText();
m_text->setTextInteractionFlags(Qt::NoTextInteraction);
addToGroup(m_text);
m_text->setZValue(ZVALUE::DIMENSION);
m_text->centerAt(0.0, 0.0);
m_rect = new QGCustomRect();
addToGroup(m_rect);
m_rect->setZValue(ZVALUE::DIMENSION - 1);
m_rect->centerAt(0.0, 0.0);
setZValue(ZVALUE::DIMENSION);
}
void QGIRichAnno::updateView(bool update)
{
// Base::Console().Message("QGIRA::updateView() - %s\n", getViewName());
Q_UNUSED(update);
auto annoFeat( dynamic_cast<TechDraw::DrawRichAnno*>(getViewObject()) );
if (!annoFeat) {
return;
}
auto vp = static_cast<ViewProviderRichAnno*>(getViewProvider(getViewObject()));
if (!vp) {
return;
}
//allow/prevent dragging
if (getViewObject()->isLocked()) {
setFlag(QGraphicsItem::ItemIsMovable, false);
} else {
setFlag(QGraphicsItem::ItemIsMovable, true);
}
if (annoFeat->X.isTouched() ||
annoFeat->Y.isTouched()) {
float x = Rez::guiX(annoFeat->X.getValue());
float y = Rez::guiX(annoFeat->Y.getValue());
m_text->centerAt(x, -y);
m_rect->centerAt(x, -y);
}
draw();
}
void QGIRichAnno::drawBorder()
{
////Leaders have no border!
// QGIView::drawBorder(); //good for debugging
}
void QGIRichAnno::draw()
{
// Base::Console().Log("QGIRA::draw() - %s - parent: %X\n", getFeature()->getNameInDocument(), parentItem());
if (!isVisible())
// Base::Console().Message("QGIRA::draw - not visible\n");
return;
TechDraw::DrawRichAnno* annoFeat = getFeature();
if (!annoFeat)
// Base::Console().Message("QGIRA::draw - no feature\n");
return;
auto vp = static_cast<ViewProviderRichAnno*>(getViewProvider(getFeature()));
if (!vp) {
// Base::Console().Message("QGIRA::draw - no viewprovider\n");
return;
}
setTextItem();
QGIView::draw();
}
void QGIRichAnno::setTextItem()
{
// Base::Console().Message("QGIRA::setTextItem() - %s - exportingSvg: %d\n", getViewName(), getExportingSvg());
TechDraw::DrawRichAnno* annoFeat = getFeature();
// convert the text size
QString inHtml = QString::fromUtf8(annoFeat->AnnoText.getValue());
QString outHtml = convertTextSizes(inHtml);
//position the text
prepareGeometryChange();
// control auto line break
if (annoFeat->MaxWidth.getValue() > 0.0) {
// we have set a maximum width, so convert it to scene units
m_text->setTextWidth(Rez::guiX(annoFeat->MaxWidth.getValue()));
} else {
// we don't want to break lines
m_text->setTextWidth(annoFeat->MaxWidth.getValue());
}
m_text->setHtml(outHtml);
if (getExportingSvg()) {
// lines are correctly spaced on screen or in pdf, but svg needs this
setLineSpacing(100);
}
if (!getExportingSvg()) {
// screen or pdf rendering
m_text->centerAt(0.0, 0.0);
}
// align the frame rectangle to the text
constexpr double frameMargin{10.0};
QRectF outRect = m_text->boundingRect().adjusted(-frameMargin, -frameMargin, frameMargin, frameMargin);
m_rect->setPen(rectPen());
m_rect->setBrush(Qt::NoBrush);
if (!getExportingSvg()) {
m_rect->setRect(outRect);
m_rect->setPos(m_text->pos().x() - frameMargin, m_text->pos().y() - frameMargin);
}
if (annoFeat->ShowFrame.getValue()) {
m_rect->show();
} else {
m_rect->hide();
}
}
// attempt to space the lines correctly after the font sizes are changed to match
// the Svg rendering of the QGraphicsTextItem.
void QGIRichAnno::setLineSpacing(int lineSpacing)
{
// left to itself, Qt appears to space the lines according to this formula
// DeltaY(in mm) = (1 + 2*pointSize + margin) which vastly under spaces the
// lines
m_text->document()->setUseDesignMetrics(true);
QTextBlock block = m_text->document()->begin();
for (; block.isValid(); block = block.next()) {
QTextCursor tc = QTextCursor(block);
QTextBlockFormat fmt = tc.blockFormat();
QTextCharFormat cFmt = tc.charFormat();
// this is already converted to pixels in setTextItem
//
double cssPixelSize = cFmt.font().pointSizeF();
// css Pixels are treated as if they were points in the conversion to Svg
double textHeightSU = Rez::guiX(cssPixelSize * 25.4 / 72.0); // actual height of text in scene units
double pointSize = cssPixelSize * 72.0 / 96.0;
double deltaYSU = 1.0 + 2.0 * pointSize; // how far Qt will space lines (based on samples)
double linegap = 0.4 * cssPixelSize; // 20% gaps above and below
// margins will be included in Qt's calculation of spacing
double margin = linegap * pointSize / 10.0;
QTextBlockFormat spacerFmt = QTextBlockFormat();
if (block.previous().isValid()) {
// there is a block before this one, so add a top margin
spacerFmt.setTopMargin(margin);
}
if (block.next().isValid()) {
// there is another block after this, so add a bottom margin
spacerFmt.setBottomMargin(margin);
}
double requiredSpacing = (textHeightSU / (deltaYSU - 1.0)) * lineSpacing;
spacerFmt.setLineHeight(requiredSpacing, QTextBlockFormat::ProportionalHeight);
tc.mergeBlockFormat(spacerFmt);
}
}
//! convert the word processing font size spec (in typographic points) to scene units for the screen or
//! pdf rendering or to CSS pixels for Svg rendering
QString QGIRichAnno::convertTextSizes(const QString& inHtml) const
{
constexpr double mmPerPoint{0.353}; // 25.4 mm/in / 72 points/inch
constexpr double cssPxPerPoint{1.333333}; // CSS says 12 pt text is 16 px high
double sceneUnitsPerPoint = Rez::getRezFactor() * mmPerPoint; // scene units per point: 3.53
QRegularExpression rxFontSize(QString::fromUtf8("font-size:([0-9]*)pt;"));
QRegularExpressionMatch match;
QStringList findList;
QStringList replList;
// find each occurrence of "font-size:..." and calculate the equivalent size in scene units
// or CSS pixels
int pos = 0;
while ((pos = inHtml.indexOf(rxFontSize, pos, &match)) != -1) {
QString found = match.captured(0);
findList << found;
QString qsOldSize = match.captured(1);
QString repl = found;
double newSize = qsOldSize.toDouble(); // in points
// The font size in the QGraphicsTextItem html is interpreted differently
// in QSvgRenderer rendering compared to painting the screen or pdf
if (getExportingSvg()) {
// scale point size to CSS pixels
newSize = newSize * cssPxPerPoint;
} else {
// scale point size to scene units
newSize = newSize * sceneUnitsPerPoint;
}
QString qsNewSize = QString::number(newSize, 'f', 2);
repl.replace(qsOldSize, qsNewSize);
replList << repl;
pos += match.capturedLength();
}
QString outHtml = inHtml;
int iRepl = 0;
//TODO: check list for duplicates?
for ( ; iRepl < findList.size(); iRepl++) {
outHtml = outHtml.replace(findList[iRepl], replList[iRepl]);
}
return outHtml;
}
TechDraw::DrawRichAnno* QGIRichAnno::getFeature()
{
TechDraw::DrawRichAnno* result =
static_cast<TechDraw::DrawRichAnno*>(getViewObject());
return result;
}
// TODO: this rect is the right size, but not in the right place
QRectF QGIRichAnno::boundingRect() const
{
QRectF roughRect = m_text->boundingRect() | m_rect->boundingRect();
double halfWidth = roughRect.width() / 2.0;
double halfHeight = roughRect.height() / 2.0;
return { -halfWidth, - halfHeight, halfWidth * 2.0, halfHeight * 2.0 };
}
void QGIRichAnno::paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) {
QStyleOptionGraphicsItem myOption(*option);
myOption.state &= ~QStyle::State_Selected;
// painter->setPen(Qt::blue);
// painter->drawRect(boundingRect()); //good for debugging
QGIView::paint (painter, &myOption, widget);
}
QPen QGIRichAnno::rectPen() const
{
const auto sym( dynamic_cast<TechDraw::DrawRichAnno*>(getViewObject()) );
if (!sym) {
return QPen();
}
auto vp = static_cast<ViewProviderRichAnno*>(getViewProvider(getViewObject()));
if (!vp) {
return QPen();
}
double rectWeight = Rez::guiX(vp->LineWidth.getValue());
Qt::PenStyle rectStyle = static_cast<Qt::PenStyle>(vp->LineStyle.getValue());
App::Color temp = vp->LineColor.getValue();
QColor rectColor = temp.asValue<QColor>();
QPen pen = QPen(rectStyle);
pen.setWidthF(rectWeight);
pen.setColor(rectColor);
return pen;
}
QFont QGIRichAnno::prefFont()
{
return PreferencesGui::labelFontQFont();
}
void QGIRichAnno::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) {
Q_UNUSED(event);
TechDraw::DrawRichAnno *annotation = dynamic_cast<TechDraw::DrawRichAnno *>(getViewObject());
if (!annotation)
return;
QString text = QString::fromUtf8(annotation->AnnoText.getValue());
QDialog dialog(nullptr);
dialog.setWindowTitle(QObject::tr("Rich text editor"));
dialog.setMinimumWidth(400);
dialog.setMinimumHeight(400);
MRichTextEdit richEdit(&dialog, text);
QGridLayout gridLayout(&dialog);
gridLayout.addWidget(&richEdit, 0, 0, 1, 1);
connect(&richEdit, &MRichTextEdit::saveText, &dialog, &QDialog::accept);
connect(&richEdit, &MRichTextEdit::editorFinished, &dialog, &QDialog::reject);
if (dialog.exec()) {
QString newText = richEdit.toHtml();
if (newText != text) {
App::GetApplication().setActiveTransaction("Set Rich Annotation Text");
annotation->AnnoText.setValue(newText.toStdString());
App::GetApplication().closeActiveTransaction();
}
}
}
#include <Mod/TechDraw/Gui/moc_QGIRichAnno.cpp>