File indexing completed on 2024-05-12 03:48:21
0001 /* 0002 File : TextLabel.cpp 0003 Project : LabPlot 0004 Description : Text label supporting reach text and latex formatting 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2009 Tilman Benkert <thzs@gmx.net> 0007 SPDX-FileCopyrightText: 2012-2023 Alexander Semke <alexander.semke@web.de> 0008 SPDX-FileCopyrightText: 2019-2022 Stefan Gerlach <stefan.gerlach@uni.kn> 0009 0010 SPDX-License-Identifier: GPL-2.0-or-later 0011 */ 0012 0013 #include "TextLabel.h" 0014 #include "TextLabelPrivate.h" 0015 #include "Worksheet.h" 0016 #include "backend/core/Project.h" 0017 #include "backend/core/Settings.h" 0018 #include "backend/lib/XmlStreamReader.h" 0019 #include "backend/lib/commandtemplates.h" 0020 #include "backend/lib/macros.h" 0021 #include "backend/worksheet/plots/PlotArea.h" 0022 #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" 0023 #include "backend/worksheet/plots/cartesian/CartesianPlot.h" 0024 #include "kdefrontend/GuiTools.h" 0025 0026 #include <QApplication> 0027 #include <QBuffer> 0028 #include <QGraphicsScene> 0029 #include <QGraphicsSceneMouseEvent> 0030 #include <QKeyEvent> 0031 #include <QMenu> 0032 #include <QPainter> 0033 #include <QTextCharFormat> 0034 #include <QTextCursor> 0035 #include <QTextDocument> 0036 #include <QtConcurrent/QtConcurrentRun> 0037 0038 #include <KConfig> 0039 #include <KConfigGroup> 0040 #include <KLocalizedString> 0041 #include <QIcon> 0042 0043 #ifdef HAVE_DISCOUNT 0044 extern "C" { 0045 #include <mkdio.h> 0046 } 0047 #endif 0048 0049 class ScaledTextItem : public QGraphicsTextItem { 0050 public: 0051 explicit ScaledTextItem(QGraphicsItem* parent = nullptr) 0052 : QGraphicsTextItem(parent) { 0053 } 0054 0055 QRectF scaledBoundingRect() const { 0056 auto rect = QGraphicsTextItem::boundingRect(); 0057 rect.setHeight(rect.height() * scale()); 0058 rect.setWidth(rect.width() * scale()); 0059 // rect.moveTopLeft(QPointF(-rect.width() * scale() / 2, -rect.height() * scale() / 2)); 0060 return rect; 0061 } 0062 0063 protected: 0064 void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override { 0065 #if DEBUG_TEXTLABEL_BOUNDING_RECT 0066 painter->setPen(QColor(Qt::GlobalColor::green)); 0067 painter->drawRect(boundingRect()); 0068 0069 painter->setPen(QColor(Qt::GlobalColor::black)); 0070 painter->drawRect(QRectF(-5, -5, 10, 10)); 0071 #endif 0072 QGraphicsTextItem::paint(painter, option, widget); 0073 } 0074 0075 private: 0076 QRectF boundingRect() const override { 0077 return QGraphicsTextItem::boundingRect(); 0078 } 0079 0080 private: 0081 }; 0082 0083 /** 0084 * \class TextLabel 0085 * \brief A label supporting rendering of html- and tex-formatted texts. 0086 * 0087 * The label is aligned relative to the specified position. 0088 * The position can be either specified by providing the x- and y- coordinates 0089 * in parent's coordinate system, or by specifying one of the predefined position 0090 * flags (\c HorizontalPosition, \c VerticalPosition). 0091 */ 0092 0093 TextLabel::TextLabel(const QString& name, Type type) 0094 : WorksheetElement(name, new TextLabelPrivate(this), AspectType::TextLabel) 0095 , m_type(type) { 0096 init(); 0097 } 0098 0099 TextLabel::TextLabel(const QString& name, TextLabelPrivate* dd, Type type) 0100 : WorksheetElement(name, dd, AspectType::TextLabel) 0101 , m_type(type) { 0102 init(); 0103 } 0104 0105 TextLabel::TextLabel(const QString& name, CartesianPlot* plot, Type type) 0106 : WorksheetElement(name, new TextLabelPrivate(this), AspectType::TextLabel) 0107 , m_type(type) { 0108 m_plot = plot; 0109 cSystem = dynamic_cast<const CartesianCoordinateSystem*>(m_plot->coordinateSystem(m_cSystemIndex)); 0110 init(); 0111 } 0112 0113 void TextLabel::init() { 0114 QString groupName; 0115 switch (m_type) { 0116 case Type::General: 0117 groupName = QStringLiteral("TextLabel"); 0118 break; 0119 case Type::PlotTitle: 0120 groupName = QStringLiteral("PlotTitle"); 0121 break; 0122 case Type::AxisTitle: 0123 groupName = QStringLiteral("AxisTitle"); 0124 break; 0125 case Type::PlotLegendTitle: 0126 groupName = QStringLiteral("PlotLegendTitle"); 0127 break; 0128 case Type::InfoElementLabel: 0129 groupName = QStringLiteral("InfoElementLabel"); 0130 } 0131 0132 const KConfig config; 0133 DEBUG(" config has group \"" << STDSTRING(groupName) << "\": " << config.hasGroup(groupName)); 0134 // group is always valid if you call config.group(..; 0135 KConfigGroup group; 0136 if (config.hasGroup(groupName)) 0137 group = config.group(groupName); 0138 0139 Q_D(TextLabel); 0140 d->position.point = QPointF(0, 0); 0141 if (m_type == Type::PlotTitle || m_type == Type::PlotLegendTitle) { 0142 d->position.verticalPosition = WorksheetElement::VerticalPosition::Top; 0143 d->position.horizontalPosition = WorksheetElement::HorizontalPosition::Center; 0144 d->verticalAlignment = WorksheetElement::VerticalAlignment::Top; 0145 } else if (m_type == Type::AxisTitle) { 0146 d->position.horizontalPosition = WorksheetElement::HorizontalPosition::Center; 0147 d->position.verticalPosition = WorksheetElement::VerticalPosition::Center; 0148 } 0149 0150 KConfigGroup conf = Settings::group(QStringLiteral("Settings_Worksheet")); 0151 const auto& engine = conf.readEntry(QStringLiteral("LaTeXEngine"), ""); 0152 if (engine == QLatin1String("lualatex")) 0153 d->teXFont.setFamily(QStringLiteral("Latin Modern Roman")); 0154 0155 // read settings from config if group exists 0156 if (group.isValid()) { 0157 // properties common to all types 0158 d->textWrapper.mode = static_cast<TextLabel::Mode>(group.readEntry(QStringLiteral("Mode"), static_cast<int>(d->textWrapper.mode))); 0159 d->teXFont.setFamily(group.readEntry(QStringLiteral("TeXFontFamily"), d->teXFont.family())); 0160 d->teXFont.setPointSize(group.readEntry(QStringLiteral("TeXFontSize"), d->teXFont.pointSize())); 0161 d->fontColor = group.readEntry(QStringLiteral("FontColor"), d->fontColor); 0162 d->backgroundColor = group.readEntry(QStringLiteral("BackgroundColor"), d->backgroundColor); 0163 d->setRotation(group.readEntry(QStringLiteral("Rotation"), d->rotation())); 0164 0165 // border 0166 d->borderShape = (TextLabel::BorderShape)group.readEntry(QStringLiteral("BorderShape"), (int)d->borderShape); 0167 d->borderPen = QPen(group.readEntry(QStringLiteral("BorderColor"), d->borderPen.color()), 0168 group.readEntry(QStringLiteral("BorderWidth"), d->borderPen.width()), 0169 (Qt::PenStyle)group.readEntry(QStringLiteral("BorderStyle"), (int)(d->borderPen.style()))); 0170 d->borderOpacity = group.readEntry(QStringLiteral("BorderOpacity"), d->borderOpacity); 0171 0172 // position and alignment relevant properties 0173 d->position.point.setX(group.readEntry(QStringLiteral("PositionXValue"), 0.)); 0174 d->position.point.setY(group.readEntry(QStringLiteral("PositionYValue"), 0.)); 0175 d->position.horizontalPosition = (HorizontalPosition)group.readEntry(QStringLiteral("PositionX"), (int)d->position.horizontalPosition); 0176 d->position.verticalPosition = (VerticalPosition)group.readEntry(QStringLiteral("PositionY"), (int)d->position.verticalPosition); 0177 d->horizontalAlignment = 0178 (WorksheetElement::HorizontalAlignment)group.readEntry(QStringLiteral("HorizontalAlignment"), static_cast<int>(d->horizontalAlignment)); 0179 d->verticalAlignment = 0180 (WorksheetElement::VerticalAlignment)group.readEntry(QStringLiteral("VerticalAlignment"), static_cast<int>(d->verticalAlignment)); 0181 if (cSystem && cSystem->isValid()) 0182 d->positionLogical = cSystem->mapSceneToLogical(d->position.point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 0183 } 0184 d->updatePosition(); 0185 0186 DEBUG(Q_FUNC_INFO << ", default/run time image resolution: " << d->teXImageResolution << '/' << QApplication::primaryScreen()->physicalDotsPerInchX()); 0187 connect(&d->teXImageFutureWatcher, &QFutureWatcher<QByteArray>::finished, this, &TextLabel::updateTeXImage); 0188 } 0189 0190 // no need to delete the d-pointer here - it inherits from QGraphicsItem 0191 // and is deleted during the cleanup in QGraphicsScene 0192 TextLabel::~TextLabel() = default; 0193 0194 void TextLabel::setZoomFactor(double factor) { 0195 Q_D(TextLabel); 0196 d->setZoomFactor(factor); 0197 } 0198 0199 void TextLabel::retransform() { 0200 Q_D(TextLabel); 0201 d->retransform(); 0202 } 0203 0204 void TextLabel::handleResize(double horizontalRatio, double verticalRatio, bool /*pageResize*/) { 0205 DEBUG(Q_FUNC_INFO); 0206 0207 double ratio = 0; 0208 if (horizontalRatio > 1.0 || verticalRatio > 1.0) 0209 ratio = std::max(horizontalRatio, verticalRatio); 0210 else 0211 ratio = std::min(horizontalRatio, verticalRatio); 0212 0213 Q_D(TextLabel); 0214 d->teXFont.setPointSizeF(d->teXFont.pointSizeF() * ratio); 0215 d->updateText(); 0216 0217 // TODO: doesn't seem to work 0218 QTextDocument doc; 0219 doc.setHtml(d->textWrapper.text); 0220 QTextCursor cursor(&doc); 0221 cursor.select(QTextCursor::Document); 0222 QTextCharFormat fmt = cursor.charFormat(); 0223 QFont font = fmt.font(); 0224 font.setPointSizeF(font.pointSizeF() * ratio); 0225 fmt.setFont(font); 0226 cursor.setCharFormat(fmt); 0227 } 0228 0229 /*! 0230 Returns an icon to be used in the project explorer. 0231 */ 0232 QIcon TextLabel::icon() const { 0233 switch (text().mode) { 0234 case Mode::Markdown: 0235 return QIcon::fromTheme(QStringLiteral("text-x-markdown")); 0236 break; 0237 case Mode::LaTeX: 0238 return QIcon::fromTheme(QStringLiteral("text-x-tex")); 0239 break; 0240 case Mode::Text: 0241 default: 0242 return QIcon::fromTheme(QStringLiteral("draw-text")); 0243 // return QIcon::fromTheme(QLatin1String("text-x-plain")); 0244 } 0245 } 0246 0247 /* ============================ getter methods ================= */ 0248 BASIC_SHARED_D_READER_IMPL(TextLabel, TextLabel::TextWrapper, text, textWrapper) 0249 BASIC_SHARED_D_READER_IMPL(TextLabel, QColor, fontColor, fontColor) 0250 BASIC_SHARED_D_READER_IMPL(TextLabel, QColor, backgroundColor, backgroundColor) 0251 BASIC_SHARED_D_READER_IMPL(TextLabel, QFont, teXFont, teXFont) 0252 BASIC_SHARED_D_READER_IMPL(TextLabel, TextLabel::BorderShape, borderShape, borderShape) 0253 BASIC_SHARED_D_READER_IMPL(TextLabel, QPen, borderPen, borderPen) 0254 BASIC_SHARED_D_READER_IMPL(TextLabel, qreal, borderOpacity, borderOpacity) 0255 0256 /* ============================ setter methods and undo commands ================= */ 0257 STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXBackgroundColor, QColor, backgroundColor, updateText) 0258 void TextLabel::setBackgroundColor(const QColor color) { 0259 QDEBUG(Q_FUNC_INFO << ", color = " << color) 0260 Q_D(TextLabel); 0261 if (color != d->backgroundColor) 0262 exec(new TextLabelSetTeXBackgroundColorCmd(d, color, ki18n("%1: set background color"))); 0263 } 0264 0265 STD_SETTER_CMD_IMPL_F_S(TextLabel, SetText, TextLabel::TextWrapper, textWrapper, updateText) 0266 void TextLabel::setText(const TextWrapper& textWrapper) { 0267 Q_D(TextLabel); 0268 // DEBUG("********************\n" << Q_FUNC_INFO << ", old/new mode = " << (int)d->textWrapper.mode << " " << (int)textWrapper.mode) 0269 // DEBUG("\nOLD TEXT = " << STDSTRING(d->textWrapper.text) << '\n') 0270 // DEBUG("NEW TEXT = " << STDSTRING(textWrapper.text) << '\n') 0271 // QDEBUG("COLORS: color =" << d->fontColor << ", background color =" << d->backgroundColor) 0272 0273 if (d->textWrapper != textWrapper) { 0274 bool oldEmpty = d->textWrapper.text.isEmpty(); 0275 if (textWrapper.mode == TextLabel::Mode::Text && !textWrapper.text.isEmpty()) { 0276 QTextEdit pte(d->textWrapper.text); // te with previous text 0277 // restore formatting when text changes or switching back to text mode 0278 if (d->textWrapper.mode != TextLabel::Mode::Text || oldEmpty || pte.toPlainText().isEmpty()) { 0279 // DEBUG("RESTORE FORMATTING") 0280 QTextEdit te(d->textWrapper.text); 0281 te.selectAll(); 0282 te.setText(textWrapper.text); 0283 te.selectAll(); 0284 te.setTextColor(d->fontColor); 0285 te.setTextBackgroundColor(d->backgroundColor); 0286 0287 TextWrapper tw = textWrapper; 0288 tw.text = te.toHtml(); 0289 // DEBUG("\nTW TEXT = " << STDSTRING(tw.text) << std::endl) 0290 exec(new TextLabelSetTextCmd(d, tw, ki18n("%1: set label text"))); 0291 } else { // the existing text is being modified 0292 QUndoCommand* parent = nullptr; 0293 TextLabelSetTextCmd* command = nullptr; 0294 QTextEdit te; 0295 te.setHtml(textWrapper.text); 0296 te.selectAll(); 0297 if (textWrapper.text.indexOf(QStringLiteral("background-color:#")) != -1) { 0298 const auto& bgColor = te.textBackgroundColor(); 0299 if (bgColor != d->backgroundColor) { 0300 parent = new QUndoCommand(ki18n("%1: set label text").subs(name()).toString()); 0301 new TextLabelSetTeXBackgroundColorCmd(d, bgColor, ki18n("%1: set background color"), parent); 0302 } 0303 command = new TextLabelSetTextCmd(d, textWrapper, ki18n("%1: set label text"), parent); 0304 } else { 0305 // no color available yet, plain text is being provided -> set the color from member variable 0306 te.setTextBackgroundColor(d->backgroundColor); 0307 TextWrapper tw = textWrapper; 0308 tw.text = te.toHtml(); 0309 command = new TextLabelSetTextCmd(d, tw, ki18n("%1: set label text"), parent); 0310 } 0311 0312 if (!parent) 0313 exec(command); 0314 else 0315 exec(parent); 0316 } 0317 } else 0318 exec(new TextLabelSetTextCmd(d, textWrapper, ki18n("%1: set label text"))); 0319 0320 // If previously the text was empty, the bounding rect is zero 0321 // therefore the alignment did not work properly. 0322 // If text is added, the bounding rectangle is updated 0323 // and then the position must be changed to consider alignment 0324 if (oldEmpty) 0325 d->updatePosition(); 0326 } 0327 0328 // DEBUG(Q_FUNC_INFO << " DONE\n***********************") 0329 } 0330 0331 STD_SETTER_CMD_IMPL_F_S(TextLabel, SetPlaceholderText, TextLabel::TextWrapper, textWrapper, updateText) 0332 void TextLabel::setPlaceholderText(const TextWrapper& textWrapper) { 0333 Q_D(TextLabel); 0334 if ((textWrapper.textPlaceholder != d->textWrapper.textPlaceholder) || (textWrapper.mode != d->textWrapper.mode)) 0335 exec(new TextLabelSetPlaceholderTextCmd(d, textWrapper, ki18n("%1: set label placeholdertext"))); 0336 } 0337 0338 STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXFont, QFont, teXFont, updateText) 0339 void TextLabel::setTeXFont(const QFont& font) { 0340 Q_D(TextLabel); 0341 if (font != d->teXFont) 0342 exec(new TextLabelSetTeXFontCmd(d, font, ki18n("%1: set TeX main font"))); 0343 } 0344 0345 STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXFontColor, QColor, fontColor, updateText) 0346 void TextLabel::setFontColor(const QColor color) { 0347 Q_D(TextLabel); 0348 if (color != d->fontColor) 0349 exec(new TextLabelSetTeXFontColorCmd(d, color, ki18n("%1: set font color"))); 0350 } 0351 0352 // Border 0353 STD_SETTER_CMD_IMPL_F_S(TextLabel, SetBorderShape, TextLabel::BorderShape, borderShape, updateBorder) 0354 void TextLabel::setBorderShape(TextLabel::BorderShape shape) { 0355 Q_D(TextLabel); 0356 if (shape != d->borderShape) 0357 exec(new TextLabelSetBorderShapeCmd(d, shape, ki18n("%1: set border shape"))); 0358 } 0359 0360 STD_SETTER_CMD_IMPL_F_S(TextLabel, SetBorderPen, QPen, borderPen, update) 0361 void TextLabel::setBorderPen(const QPen& pen) { 0362 Q_D(TextLabel); 0363 if (pen != d->borderPen) 0364 exec(new TextLabelSetBorderPenCmd(d, pen, ki18n("%1: set border"))); 0365 } 0366 0367 STD_SETTER_CMD_IMPL_F_S(TextLabel, SetBorderOpacity, qreal, borderOpacity, update) 0368 void TextLabel::setBorderOpacity(qreal opacity) { 0369 Q_D(TextLabel); 0370 if (opacity != d->borderOpacity) 0371 exec(new TextLabelSetBorderOpacityCmd(d, opacity, ki18n("%1: set border opacity"))); 0372 } 0373 0374 // misc 0375 0376 QRectF TextLabel::size() { 0377 Q_D(TextLabel); 0378 return d->size(); 0379 } 0380 0381 QPointF TextLabel::findNearestGluePoint(QPointF scenePoint) { 0382 Q_D(TextLabel); 0383 return d->findNearestGluePoint(scenePoint); 0384 } 0385 0386 TextLabel::GluePoint TextLabel::gluePointAt(int index) { 0387 Q_D(TextLabel); 0388 return d->gluePointAt(index); 0389 } 0390 0391 int TextLabel::gluePointCount() { 0392 Q_D(const TextLabel); 0393 return d->m_gluePoints.length(); 0394 } 0395 0396 void TextLabel::updateTeXImage() { 0397 Q_D(TextLabel); 0398 d->updateTeXImage(); 0399 } 0400 0401 // ############################################################################## 0402 // ####################### Private implementation ############################### 0403 // ############################################################################## 0404 TextLabelPrivate::TextLabelPrivate(TextLabel* owner) 0405 : WorksheetElementPrivate(owner) 0406 , q(owner) { 0407 setFlag(QGraphicsItem::ItemIsSelectable); 0408 setFlag(QGraphicsItem::ItemIsMovable); 0409 setFlag(QGraphicsItem::ItemSendsGeometryChanges); 0410 setFlag(QGraphicsItem::ItemIsFocusable); 0411 setAcceptHoverEvents(true); 0412 0413 // scaling: 0414 // we need to scale from the font size specified in points to scene units. 0415 // furthermore, we create the tex-image in a higher resolution than usual desktop resolution 0416 // -> take this into account 0417 // m_textItem is only used for the normal text not for latex. So the scale is only needed for the 0418 // normal text, for latex the generated image will be shown directly 0419 m_textItem = new ScaledTextItem(this); 0420 m_textItem->setScale(Worksheet::convertToSceneUnits(1, Worksheet::Unit::Point)); 0421 m_textItem->setTextInteractionFlags(Qt::NoTextInteraction); 0422 } 0423 0424 /*! 0425 * \brief TextLabelPrivate::size 0426 * \return Size and position of the TextLabel in scene coords 0427 */ 0428 QRectF TextLabelPrivate::size() { 0429 double w, h; 0430 if (textWrapper.mode == TextLabel::Mode::LaTeX) { 0431 // image size is in pixel, convert to scene units 0432 w = teXImage.width() * teXImageScaleFactor; 0433 h = teXImage.height() * teXImageScaleFactor; 0434 } else { 0435 // size is in points, convert to scene units 0436 // TODO: the shift and scaling is just a workaround to avoid the big bounding box 0437 // s.a. TextLabelPrivate::updateBoundingRect() 0438 0439 // double xShift = 23., yScale = 0.8; 0440 // better scaling for multiline Markdown 0441 // if (textWrapper.mode == TextLabel::Mode::Markdown && textWrapper.text.contains(QLatin1Char('\n'))) 0442 // yScale = 0.95; 0443 // see updateBoundingRect() 0444 w = m_textItem->scaledBoundingRect().width(); 0445 h = m_textItem->scaledBoundingRect().height(); 0446 } 0447 qreal x = position.point.x(); 0448 qreal y = position.point.y(); 0449 return {x, y, w, h}; 0450 } 0451 0452 /*! 0453 * \brief TextLabelPrivate::findNearestGluePoint 0454 * Finds the glue point, which is nearest to @param point in scene coords. 0455 * \param point reference position 0456 * \return Nearest point to @param point 0457 */ 0458 QPointF TextLabelPrivate::findNearestGluePoint(QPointF scenePoint) { 0459 if (m_gluePoints.isEmpty()) 0460 return boundingRect().center(); 0461 0462 if (m_gluePoints.length() == 1) 0463 return mapParentToPlotArea(mapToParent(m_gluePoints.at(0).point)); 0464 0465 QPointF point = mapParentToPlotArea(mapToParent(m_gluePoints.at(0).point)); 0466 QPointF nearestPoint = point; 0467 double distance2 = pow(point.x() - scenePoint.x(), 2) + pow(point.y() - scenePoint.y(), 2); 0468 // assumption, more than one point available 0469 for (int i = 1; i < m_gluePoints.length(); i++) { 0470 point = mapParentToPlotArea(mapToParent(m_gluePoints.at(i).point)); 0471 double distance2_temp = pow(point.x() - scenePoint.x(), 2) + pow(point.y() - scenePoint.y(), 2); 0472 if (distance2_temp < distance2) { 0473 nearestPoint = point; 0474 distance2 = distance2_temp; 0475 } 0476 } 0477 0478 return nearestPoint; 0479 } 0480 0481 /*! 0482 * \brief TextLabelPrivate::gluePointAt 0483 * Returns the gluePoint at a specific index. 0484 * \param index 0485 * \return Returns gluepoint at index \param index 0486 */ 0487 TextLabel::GluePoint TextLabelPrivate::gluePointAt(int index) { 0488 QPointF pos; 0489 QString name; 0490 0491 if (m_gluePoints.isEmpty() || index > m_gluePoints.length()) { 0492 pos = boundingRect().center(); 0493 name = QLatin1String("center"); 0494 } else if (index < 0) { 0495 pos = m_gluePoints.at(0).point; 0496 name = m_gluePoints.at(0).name; 0497 } else { 0498 pos = m_gluePoints.at(index).point; 0499 name = m_gluePoints.at(index).name; 0500 } 0501 0502 return {mapParentToPlotArea(mapToParent(pos)), name}; 0503 } 0504 0505 /*! 0506 calculates the position and the bounding box of the label. Called on geometry or text changes. 0507 */ 0508 void TextLabelPrivate::retransform() { 0509 const bool suppress = suppressRetransform || q->isLoading(); 0510 trackRetransformCalled(suppress); 0511 if (suppress) 0512 return; 0513 0514 updatePosition(); // needed, because CartesianPlot calls retransform if some operations are done 0515 updateBorder(); 0516 } 0517 0518 void TextLabelPrivate::setZoomFactor(double factor) { 0519 zoomFactor = factor; 0520 0521 if (textWrapper.mode == TextLabel::Mode::LaTeX) { 0522 teXImage = GuiTools::imageFromPDFData(teXPdfData, zoomFactor); 0523 retransform(); 0524 } 0525 } 0526 0527 /*! 0528 calculates the position of the label, when the position relative to the parent was specified (left, right, etc.) 0529 */ 0530 void TextLabelPrivate::updatePosition() { 0531 if (q->isLoading()) 0532 return; 0533 0534 if (q->m_type == TextLabel::Type::AxisTitle) { 0535 // In an axis element, the label is part of the bounding rect of the axis 0536 // so it is not possible to align with the bounding rect 0537 QPointF p = position.point; 0538 suppressItemChangeEvent = true; 0539 setPos(p); 0540 suppressItemChangeEvent = false; 0541 0542 Q_EMIT q->positionChanged(position); 0543 0544 // the position in scene coordinates was changed, calculate the position in logical coordinates 0545 if (q->cSystem) { 0546 if (!coordinateBindingEnabled) { 0547 QPointF pos = q->align(p, m_boundingRectangle, horizontalAlignment, verticalAlignment, false); 0548 positionLogical = q->cSystem->mapSceneToLogical(pos, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 0549 } 0550 Q_EMIT q->positionLogicalChanged(positionLogical); 0551 } 0552 } else 0553 WorksheetElementPrivate::updatePosition(); 0554 } 0555 0556 /*! 0557 updates the static text. 0558 */ 0559 void TextLabelPrivate::updateText() { 0560 // DEBUG(Q_FUNC_INFO) 0561 if (suppressRetransform) 0562 return; 0563 0564 switch (textWrapper.mode) { 0565 case TextLabel::Mode::Text: { 0566 // DEBUG(Q_FUNC_INFO << ", TEXT = " << STDSTRING(textWrapper.text)) 0567 0568 // use colors when text not empty and not already defined in text 0569 if (!textWrapper.text.isEmpty() && !textWrapper.text.contains(QLatin1String(" color:"))) { 0570 QTextEdit te(textWrapper.text); 0571 te.selectAll(); 0572 te.setTextColor(fontColor); 0573 0574 // don't set background color (not used if not inside text) 0575 textWrapper.text = te.toHtml(); 0576 } 0577 0578 m_textItem->show(); 0579 m_textItem->setHtml(textWrapper.text); 0580 0581 // the size of the label was most probably changed, 0582 // recalculate the bounding box of the label 0583 updateBoundingRect(); 0584 break; 0585 } 0586 case TextLabel::Mode::LaTeX: { 0587 m_textItem->hide(); 0588 TeXRenderer::Formatting format; 0589 format.fontColor = fontColor; 0590 format.backgroundColor = backgroundColor; 0591 format.fontSize = teXFont.pointSize(); 0592 format.fontFamily = teXFont.family(); 0593 format.dpi = teXImageResolution; 0594 QFuture<QByteArray> future = QtConcurrent::run(TeXRenderer::renderImageLaTeX, textWrapper.text, &teXRenderResult, format); 0595 teXImageFutureWatcher.setFuture(future); 0596 0597 // don't need to call retransform() here since it is done in updateTeXImage 0598 // when the asynchronous rendering of the image is finished. 0599 break; 0600 } 0601 case TextLabel::Mode::Markdown: { 0602 #ifdef HAVE_DISCOUNT 0603 auto mdCharArray = textWrapper.text.toUtf8(); 0604 #ifdef HAVE_DISCOUNT3 0605 MMIOT* mdHandle = mkd_string(mdCharArray.data(), mdCharArray.size() + 1, nullptr); 0606 0607 mkd_flag_t* v3flags = mkd_flags(); 0608 mkd_set_flag_num(v3flags, MKD_LATEX); 0609 mkd_set_flag_num(v3flags, MKD_FENCEDCODE); 0610 mkd_set_flag_num(v3flags, MKD_GITHUBTAGS); 0611 0612 if (!mkd_compile(mdHandle, v3flags)) { 0613 #else 0614 MMIOT* mdHandle = mkd_string(mdCharArray.data(), mdCharArray.size() + 1, 0); 0615 0616 unsigned int flags = MKD_LATEX | MKD_FENCEDCODE | MKD_GITHUBTAGS; 0617 if (!mkd_compile(mdHandle, flags)) { 0618 #endif 0619 DEBUG(Q_FUNC_INFO << ", Failed to compile the markdown document"); 0620 mkd_cleanup(mdHandle); 0621 return; 0622 } 0623 char* htmlDocument; 0624 int htmlSize = mkd_document(mdHandle, &htmlDocument); 0625 QString html = QString::fromUtf8(htmlDocument, htmlSize); 0626 0627 mkd_cleanup(mdHandle); 0628 0629 // use QTextEdit to add other global properties (font size and colors) 0630 QTextEdit te; 0631 te.setHtml(html); 0632 te.selectAll(); 0633 te.setTextColor(fontColor); 0634 te.setFontPointSize(teXFont.pointSize()); 0635 te.setTextBackgroundColor(backgroundColor); 0636 0637 m_textItem->setHtml(te.toHtml()); 0638 m_textItem->show(); 0639 0640 updateBoundingRect(); 0641 #endif 0642 } 0643 } 0644 } 0645 0646 void TextLabelPrivate::updateBoundingRect() { 0647 // DEBUG(Q_FUNC_INFO) 0648 // determine the size of the label in scene units. 0649 double w, h; 0650 if (textWrapper.mode == TextLabel::Mode::LaTeX) { 0651 // image size is in pixel, convert to scene units. 0652 // the image is scaled so we have a good image quality when the worksheet was zoomed, 0653 // for the bounding rect we need to scale back since it's scaled again in paint() when drawing the rect 0654 w = teXImage.width() * teXImageScaleFactor / zoomFactor; 0655 h = teXImage.height() * teXImageScaleFactor / zoomFactor; 0656 } else { 0657 // size is in points, convert to scene units 0658 // QDEBUG(" BOUNDING RECT = " << m_textItem->scaledBoundingRect()) 0659 // TODO: the shift and scaling is just a workaround to avoid the big bounding box 0660 // s.a. TextLabelPrivate::size() 0661 0662 // double xShift = 23., yScale = 0.8; 0663 // better scaling for multiline Markdown 0664 // if (textWrapper.mode == TextLabel::Mode::Markdown && textWrapper.text.contains(QLatin1Char('\n'))) 0665 // yScale = 0.95; 0666 w = m_textItem->scaledBoundingRect().width(); // - xShift; 0667 h = m_textItem->scaledBoundingRect().height(); // * yScale; 0668 m_textItem->setPos(-w / 2, -h / 2); 0669 } 0670 0671 // DEBUG(Q_FUNC_INFO << ", scale factor = " << scaleFactor << ", w/h = " << w << " / " << h) 0672 m_boundingRectangle.setX(-w / 2); 0673 m_boundingRectangle.setY(-h / 2); 0674 m_boundingRectangle.setWidth(w); 0675 m_boundingRectangle.setHeight(h); 0676 0677 updatePosition(); 0678 updateBorder(); 0679 } 0680 0681 void TextLabelPrivate::updateTeXImage() { 0682 if (zoomFactor == -1.0) { 0683 // the view was not zoomed after the label was added so the zoom factor is not set yet. 0684 // determine the current zoom factor in the view and use it 0685 auto* worksheet = static_cast<const Worksheet*>(q->parent(AspectType::Worksheet)); 0686 if (!worksheet) 0687 return; 0688 zoomFactor = worksheet->zoomFactor(); 0689 } 0690 teXPdfData = teXImageFutureWatcher.result(); 0691 teXImage = GuiTools::imageFromPDFData(teXPdfData, zoomFactor); 0692 updateBoundingRect(); 0693 DEBUG(Q_FUNC_INFO << ", TeX renderer successful = " << teXRenderResult.successful); 0694 Q_EMIT q->teXImageUpdated(teXRenderResult); 0695 } 0696 0697 void TextLabelPrivate::updateBorder() { 0698 using GluePoint = TextLabel::GluePoint; 0699 0700 borderShapePath = QPainterPath(); 0701 const auto& br = m_boundingRectangle; 0702 switch (borderShape) { 0703 case (TextLabel::BorderShape::NoBorder): { 0704 m_gluePoints.clear(); 0705 m_gluePoints.append(GluePoint(QPointF(br.x() + br.width() / 2, br.y()), QStringLiteral("top"))); 0706 m_gluePoints.append(GluePoint(QPointF(br.x() + br.width(), br.y() + br.height() / 2), QStringLiteral("right"))); 0707 m_gluePoints.append(GluePoint(QPointF(br.x() + br.width() / 2, br.y() + br.height()), QStringLiteral("bottom"))); 0708 m_gluePoints.append(GluePoint(QPointF(br.x(), br.y() + br.height() / 2), QStringLiteral("left"))); 0709 break; 0710 } 0711 case (TextLabel::BorderShape::Rect): { 0712 borderShapePath.addRect(br); 0713 0714 m_gluePoints.clear(); 0715 m_gluePoints.append(GluePoint(QPointF(br.x() + br.width() / 2, br.y()), QStringLiteral("top"))); 0716 m_gluePoints.append(GluePoint(QPointF(br.x() + br.width(), br.y() + br.height() / 2), QStringLiteral("right"))); 0717 m_gluePoints.append(GluePoint(QPointF(br.x() + br.width() / 2, br.y() + br.height()), QStringLiteral("bottom"))); 0718 m_gluePoints.append(GluePoint(QPointF(br.x(), br.y() + br.height() / 2), QStringLiteral("left"))); 0719 break; 0720 } 0721 case (TextLabel::BorderShape::Ellipse): { 0722 const double xs = br.x(); 0723 const double ys = br.y(); 0724 const double w = br.width(); 0725 const double h = br.height(); 0726 const QRectF ellipseRect(xs - 0.1 * w, ys - 0.1 * h, 1.2 * w, 1.2 * h); 0727 borderShapePath.addEllipse(ellipseRect); 0728 0729 m_gluePoints.clear(); 0730 m_gluePoints.append(GluePoint(QPointF(ellipseRect.x() + ellipseRect.width() / 2, ellipseRect.y()), QStringLiteral("top"))); 0731 m_gluePoints.append(GluePoint(QPointF(ellipseRect.x() + ellipseRect.width(), ellipseRect.y() + ellipseRect.height() / 2), QStringLiteral("right"))); 0732 m_gluePoints.append(GluePoint(QPointF(ellipseRect.x() + ellipseRect.width() / 2, ellipseRect.y() + ellipseRect.height()), QStringLiteral("bottom"))); 0733 m_gluePoints.append(GluePoint(QPointF(ellipseRect.x(), ellipseRect.y() + ellipseRect.height() / 2), QStringLiteral("left"))); 0734 break; 0735 } 0736 case (TextLabel::BorderShape::RoundSideRect): { 0737 const double xs = br.x(); 0738 const double ys = br.y(); 0739 const double w = br.width(); 0740 const double h = br.height(); 0741 borderShapePath.moveTo(xs, ys); 0742 borderShapePath.lineTo(xs + w, ys); 0743 borderShapePath.quadTo(xs + w + h / 2, ys + h / 2, xs + w, ys + h); 0744 borderShapePath.lineTo(xs, ys + h); 0745 borderShapePath.quadTo(xs - h / 2, ys + h / 2, xs, ys); 0746 0747 m_gluePoints.clear(); 0748 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys), QStringLiteral("top"))); 0749 m_gluePoints.append(GluePoint(QPointF(xs + w + h / 2, ys + h / 2), QStringLiteral("right"))); 0750 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys + h), QStringLiteral("bottom"))); 0751 m_gluePoints.append(GluePoint(QPointF(xs - h / 2, ys + h / 2), QStringLiteral("left"))); 0752 break; 0753 } 0754 case (TextLabel::BorderShape::RoundCornerRect): { 0755 const double xs = br.x(); 0756 const double ys = br.y(); 0757 const double w = br.width(); 0758 const double h = br.height(); 0759 borderShapePath.moveTo(xs + h * 0.2, ys); 0760 borderShapePath.lineTo(xs + w - h * 0.2, ys); 0761 borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); 0762 borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); 0763 borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); 0764 borderShapePath.lineTo(xs + 0.2 * h, ys + h); 0765 borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); 0766 borderShapePath.lineTo(xs, ys + 0.2 * h); 0767 borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); 0768 0769 m_gluePoints.clear(); 0770 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys), QStringLiteral("top"))); 0771 m_gluePoints.append(GluePoint(QPointF(xs + w, ys + h / 2), QStringLiteral("right"))); 0772 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys + h), QStringLiteral("bottom"))); 0773 m_gluePoints.append(GluePoint(QPointF(xs, ys + h / 2), QStringLiteral("left"))); 0774 break; 0775 } 0776 case (TextLabel::BorderShape::InwardsRoundCornerRect): { 0777 const double xs = br.x(); 0778 const double ys = br.y(); 0779 const double w = br.width(); 0780 const double h = br.height(); 0781 borderShapePath.moveTo(xs, ys - 0.3 * h); 0782 borderShapePath.lineTo(xs + w, ys - 0.3 * h); 0783 borderShapePath.quadTo(xs + w, ys, xs + w + 0.3 * h, ys); 0784 borderShapePath.lineTo(xs + w + 0.3 * h, ys + h); 0785 borderShapePath.quadTo(xs + w, ys + h, xs + w, ys + h + 0.3 * h); 0786 borderShapePath.lineTo(xs, ys + h + 0.3 * h); 0787 borderShapePath.quadTo(xs, ys + h, xs - 0.3 * h, ys + h); 0788 borderShapePath.lineTo(xs - 0.3 * h, ys); 0789 borderShapePath.quadTo(xs, ys, xs, ys - 0.3 * h); 0790 0791 m_gluePoints.clear(); 0792 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys - 0.3 * h), QStringLiteral("top"))); 0793 m_gluePoints.append(GluePoint(QPointF(xs + w + 0.3 * h, ys + h / 2), QStringLiteral("right"))); 0794 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys + h + 0.3 * h), QStringLiteral("bottom"))); 0795 m_gluePoints.append(GluePoint(QPointF(xs - 0.3 * h, ys + h / 2), QStringLiteral("left"))); 0796 break; 0797 } 0798 case (TextLabel::BorderShape::DentedBorderRect): { 0799 const double xs = br.x(); 0800 const double ys = br.y(); 0801 const double w = br.width(); 0802 const double h = br.height(); 0803 borderShapePath.moveTo(xs - 0.2 * h, ys - 0.2 * h); 0804 borderShapePath.quadTo(xs + w / 2, ys, xs + w + 0.2 * h, ys - 0.2 * h); 0805 borderShapePath.quadTo(xs + w, ys + h / 2, xs + w + 0.2 * h, ys + h + 0.2 * h); 0806 borderShapePath.quadTo(xs + w / 2, ys + h, xs - 0.2 * h, ys + h + 0.2 * h); 0807 borderShapePath.quadTo(xs, ys + h / 2, xs - 0.2 * h, ys - 0.2 * h); 0808 0809 m_gluePoints.clear(); 0810 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys - 0.2 * h), QStringLiteral("top"))); 0811 m_gluePoints.append(GluePoint(QPointF(xs + w + 0.2 * h, ys + h / 2), QStringLiteral("right"))); 0812 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys + h + 0.2 * h), QStringLiteral("bottom"))); 0813 m_gluePoints.append(GluePoint(QPointF(xs - 0.2 * h, ys + h / 2), QStringLiteral("left"))); 0814 break; 0815 } 0816 case (TextLabel::BorderShape::Cuboid): { 0817 const double xs = br.x(); 0818 const double ys = br.y(); 0819 const double w = br.width(); 0820 const double h = br.height(); 0821 borderShapePath.moveTo(xs, ys); 0822 borderShapePath.lineTo(xs + w, ys); 0823 borderShapePath.lineTo(xs + w, ys + h); 0824 borderShapePath.lineTo(xs, ys + h); 0825 borderShapePath.lineTo(xs, ys); 0826 borderShapePath.lineTo(xs + 0.3 * h, ys - 0.2 * h); 0827 borderShapePath.lineTo(xs + w + 0.3 * h, ys - 0.2 * h); 0828 borderShapePath.lineTo(xs + w, ys); 0829 borderShapePath.moveTo(xs + w, ys + h); 0830 borderShapePath.lineTo(xs + w + 0.3 * h, ys + h - 0.2 * h); 0831 borderShapePath.lineTo(xs + w + 0.3 * h, ys - 0.2 * h); 0832 0833 m_gluePoints.clear(); 0834 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys - 0.1 * h), QStringLiteral("top"))); 0835 m_gluePoints.append(GluePoint(QPointF(xs + w + 0.15 * h, ys + h / 2), QStringLiteral("right"))); 0836 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys + h), QStringLiteral("bottom"))); 0837 m_gluePoints.append(GluePoint(QPointF(xs, ys + h / 2), QStringLiteral("left"))); 0838 break; 0839 } 0840 case (TextLabel::BorderShape::UpPointingRectangle): { 0841 const double xs = br.x(); 0842 const double ys = br.y(); 0843 const double w = br.width(); 0844 const double h = br.height(); 0845 borderShapePath.moveTo(xs + h * 0.2, ys); 0846 borderShapePath.lineTo(xs + w / 2 - 0.2 * h, ys); 0847 borderShapePath.lineTo(xs + w / 2, ys - 0.2 * h); 0848 borderShapePath.lineTo(xs + w / 2 + 0.2 * h, ys); 0849 borderShapePath.lineTo(xs + w - h * 0.2, ys); 0850 borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); 0851 borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); 0852 borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); 0853 borderShapePath.lineTo(xs + 0.2 * h, ys + h); 0854 borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); 0855 borderShapePath.lineTo(xs, ys + 0.2 * h); 0856 borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); 0857 0858 m_gluePoints.clear(); 0859 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys - h * 0.2), QStringLiteral("top"))); 0860 m_gluePoints.append(GluePoint(QPointF(xs + w, ys + h / 2), QStringLiteral("right"))); 0861 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys + h), QStringLiteral("bottom"))); 0862 m_gluePoints.append(GluePoint(QPointF(xs, ys + h / 2), QStringLiteral("left"))); 0863 break; 0864 } 0865 case (TextLabel::BorderShape::DownPointingRectangle): { 0866 const double xs = br.x(); 0867 const double ys = br.y(); 0868 const double w = br.width(); 0869 const double h = br.height(); 0870 borderShapePath.moveTo(xs + h * 0.2, ys); 0871 borderShapePath.lineTo(xs + w - h * 0.2, ys); 0872 borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); 0873 borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); 0874 borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); 0875 borderShapePath.lineTo(xs + w / 2 + 0.2 * h, ys + h); 0876 borderShapePath.lineTo(xs + w / 2, ys + h + 0.2 * h); 0877 borderShapePath.lineTo(xs + w / 2 - 0.2 * h, ys + h); 0878 borderShapePath.lineTo(xs + 0.2 * h, ys + h); 0879 borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); 0880 borderShapePath.lineTo(xs, ys + 0.2 * h); 0881 borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); 0882 0883 m_gluePoints.clear(); 0884 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys), QStringLiteral("top"))); 0885 m_gluePoints.append(GluePoint(QPointF(xs + w, ys + h / 2), QStringLiteral("right"))); 0886 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys + h + 0.2 * h), QStringLiteral("bottom"))); 0887 m_gluePoints.append(GluePoint(QPointF(xs, ys + h / 2), QStringLiteral("left"))); 0888 break; 0889 } 0890 case (TextLabel::BorderShape::LeftPointingRectangle): { 0891 const double xs = br.x(); 0892 const double ys = br.y(); 0893 const double w = br.width(); 0894 const double h = br.height(); 0895 borderShapePath.moveTo(xs + h * 0.2, ys); 0896 borderShapePath.lineTo(xs + w - h * 0.2, ys); 0897 borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); 0898 borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); 0899 borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); 0900 borderShapePath.lineTo(xs + 0.2 * h, ys + h); 0901 borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); 0902 borderShapePath.lineTo(xs, ys + h / 2 + 0.2 * h); 0903 borderShapePath.lineTo(xs - 0.2 * h, ys + h / 2); 0904 borderShapePath.lineTo(xs, ys + h / 2 - 0.2 * h); 0905 borderShapePath.lineTo(xs, ys + 0.2 * h); 0906 borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); 0907 0908 m_gluePoints.clear(); 0909 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys), QStringLiteral("top"))); 0910 m_gluePoints.append(GluePoint(QPointF(xs + w, ys + h / 2), QStringLiteral("right"))); 0911 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys + h), QStringLiteral("bottom"))); 0912 m_gluePoints.append(GluePoint(QPointF(xs - 0.2 * h, ys + h / 2), QStringLiteral("left"))); 0913 break; 0914 } 0915 case (TextLabel::BorderShape::RightPointingRectangle): { 0916 const double xs = br.x(); 0917 const double ys = br.y(); 0918 const double w = br.width(); 0919 const double h = br.height(); 0920 borderShapePath.moveTo(xs + h * 0.2, ys); 0921 borderShapePath.lineTo(xs + w - h * 0.2, ys); 0922 borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); 0923 borderShapePath.lineTo(xs + w, ys + h / 2 - 0.2 * h); 0924 borderShapePath.lineTo(xs + w + 0.2 * h, ys + h / 2); 0925 borderShapePath.lineTo(xs + w, ys + h / 2 + 0.2 * h); 0926 borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); 0927 borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); 0928 borderShapePath.lineTo(xs + 0.2 * h, ys + h); 0929 borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); 0930 borderShapePath.lineTo(xs, ys + 0.2 * h); 0931 borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); 0932 0933 m_gluePoints.clear(); 0934 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys), QStringLiteral("top"))); 0935 m_gluePoints.append(GluePoint(QPointF(xs + w + 0.2 * h, ys + h / 2), QStringLiteral("right"))); 0936 m_gluePoints.append(GluePoint(QPointF(xs + w / 2, ys + h), QStringLiteral("bottom"))); 0937 m_gluePoints.append(GluePoint(QPointF(xs, ys + h / 2), QStringLiteral("left"))); 0938 break; 0939 } 0940 } 0941 0942 recalcShapeAndBoundingRect(); 0943 } 0944 0945 /*! 0946 Returns the shape of this item as a QPainterPath in local coordinates. 0947 */ 0948 QPainterPath TextLabelPrivate::shape() const { 0949 return labelShape; 0950 } 0951 0952 /*! 0953 recalculates the outer bounds and the shape of the label. 0954 */ 0955 void TextLabelPrivate::recalcShapeAndBoundingRect() { 0956 prepareGeometryChange(); 0957 0958 labelShape = QPainterPath(); 0959 if (borderShape != TextLabel::BorderShape::NoBorder) { 0960 labelShape.addPath(WorksheetElement::shapeFromPath(borderShapePath, borderPen)); 0961 m_boundingRectangle = labelShape.boundingRect(); 0962 } else 0963 labelShape.addRect(m_boundingRectangle); 0964 0965 Q_EMIT q->changed(); 0966 } 0967 0968 void TextLabelPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget*) { 0969 if (positionInvalid || textWrapper.text.isEmpty()) 0970 return; 0971 0972 // draw the text 0973 painter->save(); 0974 switch (textWrapper.mode) { 0975 case TextLabel::Mode::LaTeX: { 0976 painter->setRenderHint(QPainter::SmoothPixmapTransform); 0977 if (m_boundingRectangle.width() != 0.0 && m_boundingRectangle.height() != 0.0) 0978 painter->drawImage(m_boundingRectangle, teXImage); 0979 break; 0980 } 0981 case TextLabel::Mode::Text: 0982 case TextLabel::Mode::Markdown: { 0983 // nothing to do here, the painting is done in the ScaledTextItem child 0984 break; 0985 } 0986 } 0987 painter->restore(); 0988 0989 // Fill the complete path with the background color, for text mode only since for latex and markdown 0990 // a rectangular image is drawn which is not cropped to the boundaries of the selected shape (eclipse, etc.) 0991 if (textWrapper.mode == TextLabel::Mode::Text) 0992 painter->fillPath(labelShape, QBrush(backgroundColor)); 0993 0994 // draw the border 0995 if (borderShape != TextLabel::BorderShape::NoBorder) { 0996 painter->save(); 0997 painter->setPen(borderPen); 0998 painter->setOpacity(borderOpacity); 0999 1000 painter->drawPath(borderShapePath); 1001 painter->restore(); 1002 } 1003 1004 // TODO: move the handling of m_hovered and the logic below 1005 // to draw the selectiong/hover box to WorksheetElementPrivate 1006 // so there is no need to duplicate this code in Plot, Label, Image, etc. 1007 const bool selected = isSelected(); 1008 const bool hovered = (m_hovered && !selected); 1009 if ((hovered || selected) && !q->isPrinting()) { 1010 static double penWidth = 2.; 1011 if (hovered) 1012 painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), penWidth)); 1013 else 1014 painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), penWidth)); 1015 1016 painter->drawPath(labelShape); 1017 } 1018 1019 #if DEBUG_TEXTLABEL_BOUNDING_RECT 1020 painter->setPen(QColor(Qt::GlobalColor::red)); 1021 painter->drawRect(boundingRect()); 1022 1023 painter->setPen(QColor(Qt::GlobalColor::darkGreen)); 1024 painter->drawRect(m_boundingRectangle.marginsAdded(QMarginsF(3, 3, 3, 3))); 1025 1026 painter->setPen(QColor(Qt::GlobalColor::blue)); 1027 painter->drawEllipse(QRectF(-5, -5, 10, 10)); 1028 #endif 1029 1030 #if DEBUG_TEXTLABEL_GLUEPOINTS 1031 // just for debugging 1032 painter->setPen(QColor(Qt::GlobalColor::red)); 1033 QRectF gluePointRect(0, 0, 10, 10); 1034 for (int i = 0; i < m_gluePoints.length(); i++) { 1035 gluePointRect.moveTo(m_gluePoints[i].point.x() - gluePointRect.width() / 2, m_gluePoints[i].point.y() - gluePointRect.height() / 2); 1036 painter->fillRect(gluePointRect, QColor(Qt::GlobalColor::red)); 1037 } 1038 #endif 1039 } 1040 1041 /* 1042 void TextLabelPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { 1043 if(coordinateBinding) 1044 positionLogical = q->cSystem->mapSceneToLogical(mapParentToPlotArea(pos()), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 1045 return QGraphicsItem::mouseMoveEvent(event); 1046 }*/ 1047 1048 // ############################################################################## 1049 // ################## Serialization/Deserialization ########################### 1050 // ############################################################################## 1051 //! Save as XML 1052 void TextLabel::save(QXmlStreamWriter* writer) const { 1053 Q_D(const TextLabel); 1054 1055 writer->writeStartElement(QStringLiteral("textLabel")); 1056 writeBasicAttributes(writer); 1057 writeCommentElement(writer); 1058 1059 writer->writeStartElement(QStringLiteral("geometry")); 1060 WorksheetElement::save(writer); 1061 writer->writeEndElement(); 1062 1063 writer->writeStartElement(QStringLiteral("text")); 1064 writer->writeCharacters(d->textWrapper.text); 1065 writer->writeEndElement(); 1066 1067 if (!d->textWrapper.textPlaceholder.isEmpty()) { 1068 writer->writeStartElement(QStringLiteral("textPlaceholder")); 1069 writer->writeCharacters(d->textWrapper.textPlaceholder); 1070 writer->writeEndElement(); 1071 } 1072 1073 writer->writeStartElement(QStringLiteral("format")); 1074 writer->writeAttribute(QStringLiteral("placeholder"), QString::number(d->textWrapper.allowPlaceholder)); 1075 writer->writeAttribute(QStringLiteral("mode"), QString::number(static_cast<int>(d->textWrapper.mode))); 1076 WRITE_QFONT(d->teXFont); 1077 WRITE_QCOLOR2(d->fontColor, "fontColor"); 1078 WRITE_QCOLOR2(d->backgroundColor, "backgroundColor"); 1079 writer->writeEndElement(); 1080 1081 // border 1082 writer->writeStartElement(QStringLiteral("border")); 1083 writer->writeAttribute(QStringLiteral("borderShape"), QString::number(static_cast<int>(d->borderShape))); 1084 WRITE_QPEN(d->borderPen); 1085 writer->writeAttribute(QStringLiteral("borderOpacity"), QString::number(d->borderOpacity)); 1086 writer->writeEndElement(); 1087 1088 if (d->textWrapper.mode == TextLabel::Mode::LaTeX) { 1089 writer->writeStartElement(QStringLiteral("teXPdfData")); 1090 writer->writeCharacters(QLatin1String(d->teXPdfData.toBase64())); 1091 writer->writeEndElement(); 1092 } 1093 1094 writer->writeEndElement(); // close "textLabel" section 1095 } 1096 1097 //! Load from XML 1098 bool TextLabel::load(XmlStreamReader* reader, bool preview) { 1099 if (!readBasicAttributes(reader)) 1100 return false; 1101 1102 Q_D(TextLabel); 1103 QXmlStreamAttributes attribs; 1104 QString str; 1105 1106 while (!reader->atEnd()) { 1107 reader->readNext(); 1108 if (reader->isEndElement() && reader->name() == QLatin1String("textLabel")) 1109 break; 1110 1111 if (!reader->isStartElement()) 1112 continue; 1113 1114 if (!preview && reader->name() == QLatin1String("comment")) { 1115 if (!readCommentElement(reader)) 1116 return false; 1117 } else if (!preview && reader->name() == QLatin1String("geometry")) { 1118 WorksheetElement::load(reader, preview); 1119 } else if (!preview && reader->name() == QLatin1String("text")) 1120 d->textWrapper.text = reader->readElementText(); 1121 else if (!preview && reader->name() == QLatin1String("textPlaceholder")) 1122 d->textWrapper.textPlaceholder = reader->readElementText(); 1123 else if (!preview && reader->name() == QLatin1String("format")) { 1124 attribs = reader->attributes(); 1125 1126 if (Project::xmlVersion() < 4) { 1127 str = attribs.value(QStringLiteral("teXUsed")).toString(); 1128 d->textWrapper.mode = static_cast<TextLabel::Mode>(str.toInt()); 1129 } else 1130 READ_INT_VALUE("mode", textWrapper.mode, TextLabel::Mode); 1131 1132 str = attribs.value(QStringLiteral("placeholder")).toString(); 1133 if (!str.isEmpty()) 1134 d->textWrapper.allowPlaceholder = str.toInt(); 1135 1136 READ_QFONT(d->teXFont); 1137 READ_QCOLOR2(d->fontColor, "fontColor"); 1138 READ_QCOLOR2(d->backgroundColor, "backgroundColor"); 1139 } else if (!preview && reader->name() == QLatin1String("border")) { 1140 attribs = reader->attributes(); 1141 READ_INT_VALUE("borderShape", borderShape, BorderShape); 1142 READ_QPEN(d->borderPen); 1143 READ_DOUBLE_VALUE("borderOpacity", borderOpacity); 1144 } else if (!preview && reader->name() == QLatin1String("teXPdfData")) { 1145 reader->readNext(); 1146 QString content = reader->text().toString().trimmed(); 1147 d->teXPdfData = QByteArray::fromBase64(content.toLatin1()); 1148 d->teXImage = GuiTools::imageFromPDFData(d->teXPdfData); 1149 } else { // unknown element 1150 reader->raiseUnknownElementWarning(); 1151 if (!reader->skipToEndElement()) 1152 return false; 1153 } 1154 } 1155 1156 if (preview) 1157 return true; 1158 1159 // starting with XML version 10, the background color used in the html text is extracted 1160 // to fill the complete shape of the label and is also saved. For older projects we need 1161 // to extract it from hmtl here if available. 1162 if (d->textWrapper.mode == TextLabel::Mode::Text && Project::xmlVersion() < 10) { 1163 if (d->textWrapper.text.indexOf(QStringLiteral("background-color:#")) != -1) { 1164 QTextEdit te; 1165 te.setHtml(d->textWrapper.text); 1166 te.selectAll(); 1167 d->backgroundColor = te.textBackgroundColor(); 1168 } else 1169 d->backgroundColor.setAlpha(0); 1170 } 1171 1172 // in case we use latex and the image was stored (older versions of LabPlot didn't save the image)and loaded, 1173 // we just need to call updateBoundingRect() to calculate the new rect. 1174 // otherwise, we set the static text and call updateBoundingRect() in updateText() 1175 if (!(d->textWrapper.mode == TextLabel::Mode::LaTeX && !d->teXPdfData.isEmpty())) 1176 d->updateText(); 1177 else { 1178 d->m_textItem->hide(); 1179 d->zoomFactor = 1.0; // on load the view is not zoomed yet 1180 d->updateBoundingRect(); 1181 } 1182 1183 return true; 1184 } 1185 1186 // ############################################################################## 1187 // ######################### Theme management ################################## 1188 // ############################################################################## 1189 void TextLabel::loadThemeConfig(const KConfig& config) { 1190 DEBUG(Q_FUNC_INFO << ", label = " << STDSTRING(name())) 1191 Q_D(TextLabel); 1192 1193 KConfigGroup group = config.group(QStringLiteral("Label")); 1194 // TODO: dark mode support? 1195 d->fontColor = group.readEntry(QStringLiteral("FontColor"), QColor(Qt::black)); // used when it's latex text 1196 d->backgroundColor = group.readEntry(QStringLiteral("BackgroundColor"), QColor(Qt::transparent)); // used when it's latex text 1197 if (d->textWrapper.mode == TextLabel::Mode::Text && !d->textWrapper.text.isEmpty()) { 1198 // To set the color in a html text, a QTextEdit must be used, QTextDocument is not enough 1199 QTextEdit te; 1200 te.setHtml(d->textWrapper.text); 1201 te.selectAll(); 1202 te.setTextColor(d->fontColor); 1203 te.setTextBackgroundColor(d->backgroundColor); // for plain text no background color supported, due to bug https://bugreports.qt.io/browse/QTBUG-25420 1204 1205 TextWrapper wrapper(te.toHtml(), TextLabel::Mode::Text, true); 1206 te.setHtml(d->textWrapper.textPlaceholder); 1207 te.selectAll(); 1208 te.setTextColor(d->fontColor); 1209 te.setTextBackgroundColor(d->backgroundColor); // for plain text no background color supported, due to bug https://bugreports.qt.io/browse/QTBUG-25420 1210 wrapper.textPlaceholder = te.toHtml(); 1211 wrapper.allowPlaceholder = d->textWrapper.allowPlaceholder; 1212 1213 // update the text. also in the Widget to which is connected 1214 1215 setText(wrapper); 1216 } else if (d->textWrapper.mode == TextLabel::Mode::LaTeX) { 1217 // call updateText() to re-render the LaTeX-image with the new text colors 1218 d->updateText(); 1219 } 1220 1221 // otherwise when changing theme while the textlabel dock is visible, the 1222 // color comboboxes do not change the color 1223 backgroundColorChanged(d->backgroundColor); 1224 fontColorChanged(d->fontColor); 1225 1226 group = config.group(QStringLiteral("CartesianPlot")); 1227 QPen pen = this->borderPen(); 1228 pen.setColor(group.readEntry(QStringLiteral("BorderColor"), pen.color())); 1229 pen.setStyle((Qt::PenStyle)(group.readEntry(QStringLiteral("BorderStyle"), (int)pen.style()))); 1230 pen.setWidthF(group.readEntry(QStringLiteral("BorderWidth"), pen.widthF())); 1231 this->setBorderPen(pen); 1232 this->setBorderOpacity(group.readEntry(QStringLiteral("BorderOpacity"), this->borderOpacity())); 1233 } 1234 1235 void TextLabel::saveThemeConfig(const KConfig& config) { 1236 KConfigGroup group = config.group(QStringLiteral("Label")); 1237 // TODO 1238 // group.writeEntry("TeXFontColor", (QColor) this->fontColor()); 1239 }