/*************************************************************************** * Copyright (c) 2019 WandererFan * * * * 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 #endif #include #include #include #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(getViewObject()) ); if (!annoFeat) { return; } auto vp = static_cast(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(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(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(getViewObject()) ); if (!sym) { return QPen(); } auto vp = static_cast(getViewProvider(getViewObject())); if (!vp) { return QPen(); } double rectWeight = Rez::guiX(vp->LineWidth.getValue()); Qt::PenStyle rectStyle = static_cast(vp->LineStyle.getValue()); App::Color temp = vp->LineColor.getValue(); QColor rectColor = temp.asValue(); 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(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